Message ID | 20230103143029.273695-1-pchelkin@ispras.ru (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | Toke Høiland-Jørgensen |
Headers | show |
Series | [v2] wifi: ath9k: hif_usb: clean up skbs if ath9k_hif_usb_rx_stream() fails | expand |
Fedor Pchelkin <pchelkin@ispras.ru> writes: > Syzkaller detected a memory leak of skbs in ath9k_hif_usb_rx_stream(). > While processing skbs in ath9k_hif_usb_rx_stream(), the already allocated > skbs in skb_pool are not freed if ath9k_hif_usb_rx_stream() fails. If we > have an incorrect pkt_len or pkt_tag, the skb is dropped and all the > associated skb_pool buffers should be cleaned, too. > > Found by Linux Verification Center (linuxtesting.org) with Syzkaller. > > Fixes: 6ce708f54cc8 ("ath9k: Fix out-of-bound memcpy in ath9k_hif_usb_rx_stream") > Fixes: 44b23b488d44 ("ath9k: hif_usb: Reduce indent 1 column") > Reported-by: syzbot+e9632e3eb038d93d6bc6@syzkaller.appspotmail.com > Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru> > Signed-off-by: Alexey Khoroshilov <khoroshilov@ispras.ru> > --- > v1->v2: added Reported-by tag > > drivers/net/wireless/ath/ath9k/hif_usb.c | 9 +++++++-- > 1 file changed, 7 insertions(+), 2 deletions(-) > > diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c > index 1a2e0c7eeb02..d02cec114280 100644 > --- a/drivers/net/wireless/ath/ath9k/hif_usb.c > +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c > @@ -586,14 +586,14 @@ static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev, > > if (pkt_tag != ATH_USB_RX_STREAM_MODE_TAG) { > RX_STAT_INC(hif_dev, skb_dropped); > - return; > + goto invalid_pkt; > } > > if (pkt_len > 2 * MAX_RX_BUF_SIZE) { > dev_err(&hif_dev->udev->dev, > "ath9k_htc: invalid pkt_len (%x)\n", pkt_len); > RX_STAT_INC(hif_dev, skb_dropped); > - return; > + goto invalid_pkt; > } > > pad_len = 4 - (pkt_len & 0x3); > @@ -654,6 +654,11 @@ static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev, > skb_pool[i]->len, USB_WLAN_RX_PIPE); > RX_STAT_INC(hif_dev, skb_completed); > } > + return; > +invalid_pkt: > + for (i = 0; i < pool_index; i++) > + kfree_skb(skb_pool[i]); > + return; Hmm, so in the other error cases (if SKB allocation fails), we just 'goto err' and call the receive handler for the packets already in skb_pool. Why can't we do the same here? Also, I think there's another bug in that function, which this change will make worse? Specifically, in the start of that function, hif_dev->remain_skb is moved to skb_pool[0], but not cleared from hif_dev itself. So if we then hit the invalid check and free it, the next time the function is called, we'll get the same remain_skb pointer, which has now been freed. So I think we'll need to clear out hif_dev->remain_skb after moving it to skb_pool. Care to add that as well? -Toke
> Hmm, so in the other error cases (if SKB allocation fails), we just > 'goto err' and call the receive handler for the packets already in > skb_pool. Why can't we do the same here? If SKB allocation fails, then the packets already in skb_pool should be processed by htc rx handler, yes. About the other two cases: if pkt_tag or pkt_len is invalid, then the whole SKB is considered invalid and dropped. That is what the statistics macros tell. So I think we should not process packets from skb_pool which are associated with a dropped SKB. And so just free them instead. > Also, I think there's another bug in that function, which this change > will make worse? Specifically, in the start of that function, > hif_dev->remain_skb is moved to skb_pool[0], but not cleared from > hif_dev itself. So if we then hit the invalid check and free it, the > next time the function is called, we'll get the same remain_skb pointer, > which has now been freed. Sorry, I missed that somehow. Moving 'hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE;' after "ath9k_htc: RX memory allocation error\n" error path should be done, too. hif_dev->rx_remain_len is zeroed after remain_skb processing, so we cannot reference hif_dev->remain_skb unless we explicitly allocate successfully a new one (making rx_remain_len non zero). > So I think we'll need to clear out hif_dev->remain_skb after moving it > to skb_pool. Care to add that as well? Yes, this must be done. I'll add it to patch v3.
Fedor Pchelkin <pchelkin@ispras.ru> writes: >> Hmm, so in the other error cases (if SKB allocation fails), we just >> 'goto err' and call the receive handler for the packets already in >> skb_pool. Why can't we do the same here? > > If SKB allocation fails, then the packets already in skb_pool should be > processed by htc rx handler, yes. About the other two cases: if pkt_tag or > pkt_len is invalid, then the whole SKB is considered invalid and dropped. > That is what the statistics macros tell. So I think we should not process > packets from skb_pool which are associated with a dropped SKB. And so just > free them instead. Hmm, okay, but if we're counting packets, your patch is not incrementing any drop counters for the extra SKBs it's dropping either? They would previously have been counted as 'RX_STAT_INC(hif_dev, skb_allocated)', so shouldn't they now be counted as 'skb_dropped' as well? The single counter increase inside the err if statements refers to the skb that's the function parameter (which AFAICT is a different kind of skb than the ones being allocated and processed in that loop? it's being split into chunks or?). >> Also, I think there's another bug in that function, which this change >> will make worse? Specifically, in the start of that function, >> hif_dev->remain_skb is moved to skb_pool[0], but not cleared from >> hif_dev itself. So if we then hit the invalid check and free it, the >> next time the function is called, we'll get the same remain_skb pointer, >> which has now been freed. > > Sorry, I missed that somehow. > Moving 'hif_dev->rx_remain_len = index - MAX_RX_BUF_SIZE;' after > "ath9k_htc: RX memory allocation error\n" error path should be done, too. > hif_dev->rx_remain_len is zeroed after remain_skb processing, so we cannot > reference hif_dev->remain_skb unless we explicitly allocate successfully a > new one (making rx_remain_len non zero). > >> So I think we'll need to clear out hif_dev->remain_skb after moving it >> to skb_pool. Care to add that as well? > > Yes, this must be done. I'll add it to patch v3. OK, cool!
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index 1a2e0c7eeb02..d02cec114280 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -586,14 +586,14 @@ static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev, if (pkt_tag != ATH_USB_RX_STREAM_MODE_TAG) { RX_STAT_INC(hif_dev, skb_dropped); - return; + goto invalid_pkt; } if (pkt_len > 2 * MAX_RX_BUF_SIZE) { dev_err(&hif_dev->udev->dev, "ath9k_htc: invalid pkt_len (%x)\n", pkt_len); RX_STAT_INC(hif_dev, skb_dropped); - return; + goto invalid_pkt; } pad_len = 4 - (pkt_len & 0x3); @@ -654,6 +654,11 @@ static void ath9k_hif_usb_rx_stream(struct hif_device_usb *hif_dev, skb_pool[i]->len, USB_WLAN_RX_PIPE); RX_STAT_INC(hif_dev, skb_completed); } + return; +invalid_pkt: + for (i = 0; i < pool_index; i++) + kfree_skb(skb_pool[i]); + return; } static void ath9k_hif_usb_rx_cb(struct urb *urb)