diff mbox series

[iwl-net] ice: fix integer overflow in ice_get_pfa_module_tlv

Message ID 20240516-iwl-net-2024-05-16-fix-nvm-tlv-issue-v1-1-ecbb6a75961e@intel.com
State Superseded
Headers show
Series [iwl-net] ice: fix integer overflow in ice_get_pfa_module_tlv | expand

Commit Message

Jacob Keller May 16, 2024, 9:18 p.m. UTC
From: Paul Greenwalt <paul.greenwalt@intel.com>

It's possible that an NVM with an invalid tlv_len could cause an integer
overflow of next_tlv which can result an infinite loop.

Fix this issue by changing next_tlv from u16 to u32 to prevent overflow.
Also check that tlv_len is valid and less than pfa_len.

Signed-off-by: Paul Greenwalt <paul.greenwalt@intel.com>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_nvm.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)


---
base-commit: 83e93942796db58652288f0391ac00072401816f
change-id: 20240516-iwl-net-2024-05-16-fix-nvm-tlv-issue-99ebb2c55c52

Best regards,

Comments

Paul Menzel May 17, 2024, 6:52 a.m. UTC | #1
Dear Paul, dear Jacob,


Am 16.05.24 um 23:18 schrieb Jacob Keller:
> From: Paul Greenwalt <paul.greenwalt@intel.com>
> 
> It's possible that an NVM with an invalid tlv_len could cause an integer
> overflow of next_tlv which can result an infinite loop.
> 
> Fix this issue by changing next_tlv from u16 to u32 to prevent overflow.

Why is 32-bit enough?

> Also check that tlv_len is valid and less than pfa_len.
> 
> Signed-off-by: Paul Greenwalt <paul.greenwalt@intel.com>
> Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> ---
>   drivers/net/ethernet/intel/ice/ice_nvm.c | 8 ++++++--
>   1 file changed, 6 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c b/drivers/net/ethernet/intel/ice/ice_nvm.c
> index 84eab92dc03c..9e58d319355f 100644
> --- a/drivers/net/ethernet/intel/ice/ice_nvm.c
> +++ b/drivers/net/ethernet/intel/ice/ice_nvm.c
> @@ -441,7 +441,7 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
>   		       u16 module_type)
>   {
>   	u16 pfa_len, pfa_ptr;

By the way, is pfa_ptr an actual pointer address?

> -	u16 next_tlv;
> +	u32 next_tlv;
>   	int status;
>   
>   	status = ice_read_sr_word(hw, ICE_SR_PFA_PTR, &pfa_ptr);
> @@ -458,7 +458,7 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
>   	 * of TLVs to find the requested one.
>   	 */
>   	next_tlv = pfa_ptr + 1;
> -	while (next_tlv < pfa_ptr + pfa_len) {
> +	while (next_tlv < ((u32)pfa_ptr + pfa_len)) {
>   		u16 tlv_sub_module_type;
>   		u16 tlv_len;
>   
> @@ -474,6 +474,10 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
>   			ice_debug(hw, ICE_DBG_INIT, "Failed to read TLV length.\n");
>   			break;
>   		}
> +		if (tlv_len > pfa_len) {
> +			ice_debug(hw, ICE_DBG_INIT, "Invalid TLV length.\n");

Please print both values. Should this be at least a warning, if it’s not 
an expected situation?

> +			return -EINVAL;
> +		}
>   		if (tlv_sub_module_type == module_type) {
>   			if (tlv_len) {
>   				*module_tlv = next_tlv;


Kind regards,

Paul
Jacob Keller May 17, 2024, 9:23 p.m. UTC | #2
> -----Original Message-----
> From: Paul Menzel <pmenzel@molgen.mpg.de>
> Sent: Thursday, May 16, 2024 11:52 PM
> To: Keller, Jacob E <jacob.e.keller@intel.com>
> Cc: Greenwalt, Paul <paul.greenwalt@intel.com>; intel-wired-lan@lists.osuosl.org
> Subject: Re: [Intel-wired-lan] [PATCH iwl-net] ice: fix integer overflow in
> ice_get_pfa_module_tlv
> 
> Dear Paul, dear Jacob,
> 
> 
> Am 16.05.24 um 23:18 schrieb Jacob Keller:
> > From: Paul Greenwalt <paul.greenwalt@intel.com>
> >
> > It's possible that an NVM with an invalid tlv_len could cause an integer
> > overflow of next_tlv which can result an infinite loop.
> >
> > Fix this issue by changing next_tlv from u16 to u32 to prevent overflow.
> 
> Why is 32-bit enough?
> 

The TLV size is 16bit, so we'd need to have more than 2^16 TLVs to overflow a 32 bit length. In particular the NVMs which this fixes were reporting an incorrect TLV length of 0xFFFF, which caused us to overflow the size. This resulted in the driver incorrectly looping over and over in such a way that it never exited the TLV loop. This is a buggy NVM, and the goal here is to simply prevent the driver from infinitely looping, as this causes devlink dev info to lockup and prevents driver removal or any other form of recovery.

I suppose an alternative fix would be to use something like the overflow.h helpers which saturate instead of overflowing, but those are all size_t based.

> > Also check that tlv_len is valid and less than pfa_len.
> >
> > Signed-off-by: Paul Greenwalt <paul.greenwalt@intel.com>
> > Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
> > ---
> >   drivers/net/ethernet/intel/ice/ice_nvm.c | 8 ++++++--
> >   1 file changed, 6 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c
> b/drivers/net/ethernet/intel/ice/ice_nvm.c
> > index 84eab92dc03c..9e58d319355f 100644
> > --- a/drivers/net/ethernet/intel/ice/ice_nvm.c
> > +++ b/drivers/net/ethernet/intel/ice/ice_nvm.c
> > @@ -441,7 +441,7 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16
> *module_tlv, u16 *module_tlv_len,
> >   		       u16 module_type)
> >   {
> >   	u16 pfa_len, pfa_ptr;
> 
> By the way, is pfa_ptr an actual pointer address?
> 

Its an offset into the Shadow RAM. The terminology used by both the datasheet and the NVM documentation is a pointer: we read from the Shadow RAM  the ICE_SR_PFA_PTR, which points us to where the PFA starts. Its not a pointer in the kernel or C technical sense.

> > -	u16 next_tlv;
> > +	u32 next_tlv;
> >   	int status;
> >
> >   	status = ice_read_sr_word(hw, ICE_SR_PFA_PTR, &pfa_ptr);
> > @@ -458,7 +458,7 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16
> *module_tlv, u16 *module_tlv_len,
> >   	 * of TLVs to find the requested one.
> >   	 */
> >   	next_tlv = pfa_ptr + 1;
> > -	while (next_tlv < pfa_ptr + pfa_len) {
> > +	while (next_tlv < ((u32)pfa_ptr + pfa_len)) {
> >   		u16 tlv_sub_module_type;
> >   		u16 tlv_len;
> >
> > @@ -474,6 +474,10 @@ ice_get_pfa_module_tlv(struct ice_hw *hw, u16
> *module_tlv, u16 *module_tlv_len,
> >   			ice_debug(hw, ICE_DBG_INIT, "Failed to read TLV
> length.\n");
> >   			break;
> >   		}
> > +		if (tlv_len > pfa_len) {
> > +			ice_debug(hw, ICE_DBG_INIT, "Invalid TLV length.\n");
> 
> Please print both values. Should this be at least a warning, if it’s not
> an expected situation?

I can change this. Its unexpected as it is caused by a misconfigured NVM. Strictly speaking I think this check itself is sufficient since the PFA length cant be greater than 16bits. Changing the other values to 32 bit ensures that those don't overflow and incorrectly compare true as well. The original fix was developed by Paul Greenwalt, so he might have some context I don't.

> 
> > +			return -EINVAL;
> > +		}
> >   		if (tlv_sub_module_type == module_type) {
> >   			if (tlv_len) {
> >   				*module_tlv = next_tlv;
> 
> 
> Kind regards,
> 
> Paul
diff mbox series

Patch

diff --git a/drivers/net/ethernet/intel/ice/ice_nvm.c b/drivers/net/ethernet/intel/ice/ice_nvm.c
index 84eab92dc03c..9e58d319355f 100644
--- a/drivers/net/ethernet/intel/ice/ice_nvm.c
+++ b/drivers/net/ethernet/intel/ice/ice_nvm.c
@@ -441,7 +441,7 @@  ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
 		       u16 module_type)
 {
 	u16 pfa_len, pfa_ptr;
-	u16 next_tlv;
+	u32 next_tlv;
 	int status;
 
 	status = ice_read_sr_word(hw, ICE_SR_PFA_PTR, &pfa_ptr);
@@ -458,7 +458,7 @@  ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
 	 * of TLVs to find the requested one.
 	 */
 	next_tlv = pfa_ptr + 1;
-	while (next_tlv < pfa_ptr + pfa_len) {
+	while (next_tlv < ((u32)pfa_ptr + pfa_len)) {
 		u16 tlv_sub_module_type;
 		u16 tlv_len;
 
@@ -474,6 +474,10 @@  ice_get_pfa_module_tlv(struct ice_hw *hw, u16 *module_tlv, u16 *module_tlv_len,
 			ice_debug(hw, ICE_DBG_INIT, "Failed to read TLV length.\n");
 			break;
 		}
+		if (tlv_len > pfa_len) {
+			ice_debug(hw, ICE_DBG_INIT, "Invalid TLV length.\n");
+			return -EINVAL;
+		}
 		if (tlv_sub_module_type == module_type) {
 			if (tlv_len) {
 				*module_tlv = next_tlv;