Skip to contentSkip to conversation

wifi: mt76: refine USB/SDIO TX path error handling

PATCH Superseded Eason Lai 3 participants 12 messages Aug 19 – Sep 14

Subsystems (3)
lore
Share & tools
lore

Eason Lai

author
#0 lore raw

v3 cover wifi: mt76: refine USB/SDIO TX path error handling

This series fixes a UAF in the SDIO TX path when out of memory and a memory leak in the USB TX path.

Eason Lai (2):
wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory
wifi: mt76: fix memory leak in USB TX path

 drivers/net/wireless/mediatek/mt76/tx.c  |  2 +-
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 2 files changed, 27 insertions(+), 6 deletions(-)
Signature · 1 line
2.45.2
author
#1 lore raw

PATCH v3 1/2 wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory

Using __skb_pad() instead of skb_pad() in mt76_skb_adjust_pad() to prevent double-free, as the caller handles cleanup.

Fixes 808f2767d421 ("wifi: mt76: mt792x: Fix memory leak in SDIO TX path")

---
v2: no v2 change
v3: add Signed-off-by: Devin Wittmayer and send as series

---
 drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/wireless/mediatek/mt76/tx.c +1 -1
index 3707ee19e4ae..ec503521ae63 100644
@@ -879,7 +879,7 @@ int mt76_skb_adjust_pad(struct sk_buff *skb, int pad)
 		}
 	}
 
-	if (skb_pad(last, pad))
+	if (__skb_pad(last, pad, false))
 		return -ENOMEM;
 
 	__skb_put(last, pad);
Signature · 1 line
2.45.2
author
#2 lore raw

PATCH v3 2/2 wifi: mt76: fix memory leak in USB TX path

In reply to Eason Lai

When errors occur in mt76u_tx_queue_skb(), the skb is not properly freed, leading to memory leaks. This happens when:
- The queue is full
- tx_prepare_skb() drops zero-length frames (such as WMM NULL frames) to prevent hardware TX hangs
- Buffer setup fails

Fix by properly releasing the skb with ieee80211_tx_status_ext() when the queue is full or tx_prepare_skb() fails, and using mt76_tx_complete_skb() when mt76u_tx_setup_buffers() fails (since tx_prepare_skb() has already succeeded and may have allocated resources that need cleanup)

Fixes b40b15e1521f ("mt76: add usb support to mt76 layer")

---
v2:
- add error handling for queue full and tx_prepare_skb()
- correct error handling for mt76u_tx_setup_buffers()
v3: add Tested-by: Devin Wittmayer and cc stable

---
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 1 file changed, 26 insertions(+), 5 deletions(-)
drivers/net/wireless/mediatek/mt76/usb.c +26 -5
index a9af3aa6b80a..dfc12f6944a7 100644
@@ -893,6 +893,9 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 		   enum mt76_txq_id qid, struct sk_buff *skb,
 		   struct mt76_wcid *wcid, struct ieee80211_sta *sta)
 {
+	struct ieee80211_tx_status status = {
+		.sta = sta,
+	};
 	struct mt76_tx_info tx_info = {
 		.skb = skb,
 	};
@@ -900,17 +903,27 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	u16 idx = q->head;
 	int err;
 
-	if (q->queued == q->ndesc)
-		return -ENOSPC;
+	if (q->queued == q->ndesc) {
+		err = -ENOSPC;
+		goto err_free_skb;
+	}
 
 	skb->prev = skb->next = NULL;
 	err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info);
 	if (err < 0)
-		return err;
+		goto err_free_skb;
 
 	err = mt76u_tx_setup_buffers(dev, tx_info.skb, q->entry[idx].urb);
-	if (err < 0)
-		return err;
+	if (err < 0) {
+		/*
+		 * mt76_tx_status_skb_get() walks the idr and dereferences
+		 * a freed skb. This SKB is not counted in non-AQL counter
+		 * due to error return, so using 0xffff as wcid to keep
+		 * balanced.
+		 */
+		mt76_tx_complete_skb(dev, 0xffff, tx_info.skb);
+		goto err_ret;
+	}
 
 	mt76u_fill_bulk_urb(dev, USB_DIR_OUT, q->ep, q->entry[idx].urb,
 			    mt76u_complete_tx, &q->entry[idx]);
@@ -921,6 +934,14 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	q->queued++;
 
 	return idx;
+
+err_free_skb:
+	status.skb = tx_info.skb;
+	spin_lock_bh(&dev->rx_lock);
+	ieee80211_tx_status_ext(dev->hw, &status);
+	spin_unlock_bh(&dev->rx_lock);
+err_ret:
+	return err;
 }
 
 static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
Signature · 1 line
2.45.2
#3 lore raw
In reply to Eason Lai
Quoted 2 lines · Eason Lai

This series fixes a UAF in the SDIO TX path when out of memory and a memory leak in the USB TX path.

Thanks for turning that around so fast, and for taking all of it. The code reads right to me now.

One thing to sort out before it lands. 2/2 is marked for stable and 1/2 is not, but the two only work together. The padding helper frees the skb itself when it fails, which is fine today because the caller returns without touching it. 2/2 changes that, and starts reporting the prepare failure with a skb that is already gone. 1/2 is what stops the helper freeing. So a stable tree that takes 2/2 on its own gets a use after free where there is no bug today.

It will not get caught on the way through, either. 2/2 applies and passes without 1/2, because the frame returns before the padding runs.

Marking 1/2 for stable is all it takes. The stable rules say you do not have to list a prerequisite that is already in the same series, but only if that patch is itself marked for stable.

The Closes line did not make it onto either patch:

https://github.com/morrownr/mt76/issues/83

Devin

author
#4 lore raw

v4 cover wifi: mt76: refine USB/SDIO TX path error handling

In reply to Eason Lai

This series fixes a UAF in the SDIO TX path when out of memory and a memory leak in the USB TX path.

Eason Lai (2):
wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory
wifi: mt76: fix memory leak in USB TX path

 drivers/net/wireless/mediatek/mt76/tx.c  |  2 +-
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 2 files changed, 27 insertions(+), 6 deletions(-)
Signature · 1 line
2.45.2
author
#5 lore raw

PATCH v4 1/2 wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory

Using __skb_pad() instead of skb_pad() in mt76_skb_adjust_pad() to prevent double-free, as the caller handles cleanup.

Fixes 808f2767d421 ("wifi: mt76: mt792x: Fix memory leak in SDIO TX path")

---
v2: no v2 change
v3: add Signed-off-by: Devin Wittmayer and send as series
v4: add CC stable

---
 drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/wireless/mediatek/mt76/tx.c +1 -1
index 3707ee19e4ae..ec503521ae63 100644
@@ -879,7 +879,7 @@ int mt76_skb_adjust_pad(struct sk_buff *skb, int pad)
 		}
 	}
 
-	if (skb_pad(last, pad))
+	if (__skb_pad(last, pad, false))
 		return -ENOMEM;
 
 	__skb_put(last, pad);
Signature · 1 line
2.45.2
author
#6 lore raw

PATCH v4 2/2 wifi: mt76: fix memory leak in USB TX path

In reply to Eason Lai

When errors occur in mt76u_tx_queue_skb(), the skb is not properly freed, leading to memory leaks. This happens when:
- The queue is full
- tx_prepare_skb() drops zero-length frames (such as WMM NULL frames) to prevent hardware TX hangs
- Buffer setup fails

Fix by properly releasing the skb with ieee80211_tx_status_ext() when the queue is full or tx_prepare_skb() fails, and using mt76_tx_complete_skb() when mt76u_tx_setup_buffers() fails (since tx_prepare_skb() has already succeeded and may have allocated resources that need cleanup)

Fixes b40b15e1521f ("mt76: add usb support to mt76 layer")

---
v2:
- add error handling for queue full and tx_prepare_skb()
- correct error handling for mt76u_tx_setup_buffers()
v3: add Tested-by: Devin Wittmayer and cc stable
v4: add Closes line

---
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 1 file changed, 26 insertions(+), 5 deletions(-)
drivers/net/wireless/mediatek/mt76/usb.c +26 -5
index a9af3aa6b80a..dfc12f6944a7 100644
@@ -893,6 +893,9 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 		   enum mt76_txq_id qid, struct sk_buff *skb,
 		   struct mt76_wcid *wcid, struct ieee80211_sta *sta)
 {
+	struct ieee80211_tx_status status = {
+		.sta = sta,
+	};
 	struct mt76_tx_info tx_info = {
 		.skb = skb,
 	};
@@ -900,17 +903,27 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	u16 idx = q->head;
 	int err;
 
-	if (q->queued == q->ndesc)
-		return -ENOSPC;
+	if (q->queued == q->ndesc) {
+		err = -ENOSPC;
+		goto err_free_skb;
+	}
 
 	skb->prev = skb->next = NULL;
 	err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info);
 	if (err < 0)
-		return err;
+		goto err_free_skb;
 
 	err = mt76u_tx_setup_buffers(dev, tx_info.skb, q->entry[idx].urb);
-	if (err < 0)
-		return err;
+	if (err < 0) {
+		/*
+		 * mt76_tx_status_skb_get() walks the idr and dereferences
+		 * a freed skb. This SKB is not counted in non-AQL counter
+		 * due to error return, so using 0xffff as wcid to keep
+		 * balanced.
+		 */
+		mt76_tx_complete_skb(dev, 0xffff, tx_info.skb);
+		goto err_ret;
+	}
 
 	mt76u_fill_bulk_urb(dev, USB_DIR_OUT, q->ep, q->entry[idx].urb,
 			    mt76u_complete_tx, &q->entry[idx]);
@@ -921,6 +934,14 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	q->queued++;
 
 	return idx;
+
+err_free_skb:
+	status.skb = tx_info.skb;
+	spin_lock_bh(&dev->rx_lock);
+	ieee80211_tx_status_ext(dev->hw, &status);
+	spin_unlock_bh(&dev->rx_lock);
+err_ret:
+	return err;
 }
 
 static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
Signature · 1 line
2.45.2
Tested-by on v4 2/2
#7 lore raw

Re: [PATCH v4 2/2] wifi: mt76: fix memory leak in USB TX path

I tested this series on another mt7921u USB device and confirmed that it fixes the zero-length WNM-frame skb/socket-charge leak.

Hardware and software:

x86_64
Netgear USB 0846:9065, mt7921u
Linux 7.1.9-arch1-2
wpa_supplicant 2.12
NetworkManager 1.58.1
firmware HW/SW 0x8a108a10, WM build 20260224110949

I applied both v4 changes without semantic modification to the exact Linux 7.1.9 source and built the mt76 module set against matching Arch headers.

The reproducer used a nonblocking AF_PACKET/SOCK_DGRAM ETH_P_IP socket to send zero-byte payloads to the associated AP and sampled SIOCOUTQ after each send. This produces the same packet shape as wpa_supplicant's synthetic WNM keep-alive without using the supplicant's socket.

With the stock modules, eight sends returned success and SIOCOUTQ increased monotonically by 960 bytes per frame:

960, 1920, 2880, 3840, 4800, 5760, 6720, 7680

With v4 applied, 500 identical frames paced 10 ms apart completed with SIOCOUTQ=0 on all 500 samples. Both the immediate and settled final values were zero. An additional unpaced burst created a temporary backlog that drained completely to zero in 1.35 seconds.

The link remained healthy after the test: 0% gateway and Internet packet loss, and no mt76 pending-TX timeout, firmware reset, warning, or error in the kernel log.

The reproducer and this report were prepared with assistance from OpenAI Codex using GPT-5.6 Sol at x-high reasoning effort. I reviewed the procedure and results and ran the tests on the hardware described above.

#8 lore raw

Re: [PATCH v4 2/2] wifi: mt76: fix memory leak in USB TX path

In reply to Eason Lai
Quoted 4 lines · Eason Lai
Fixes b40b15e1521f ("mt76: add usb support to mt76 layer")

Eason,

Thank you for carrying this through four revisions.

The issue in the Closes line was reported by Sergei Melnichenko, with the stack capture that narrowed it to the USB TX path. He asked to be credited:

Hans Buchheim's Tested-by from 1 September is on this thread as well.

Devin

author
#9 lore raw

v5 cover wifi: mt76: refine USB/SDIO TX path error handling

In reply to Eason Lai

This series fixes a UAF in the SDIO TX path when out of memory and a memory leak in the USB TX path.

Eason Lai (2):
wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory
wifi: mt76: fix memory leak in USB TX path

 drivers/net/wireless/mediatek/mt76/tx.c  |  2 +-
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 2 files changed, 27 insertions(+), 6 deletions(-)
Signature · 1 line
2.45.2
author
#10 lore raw

PATCH v5 1/2 wifi: mt76: mt792x: fix UAF in SDIO TX path when out of memory

Using __skb_pad() instead of skb_pad() in mt76_skb_adjust_pad() to prevent double-free, as the caller handles cleanup.

Fixes 808f2767d421 ("wifi: mt76: mt792x: Fix memory leak in SDIO TX path")

---
v2: no v2 change
v3: add Signed-off-by: Devin Wittmayer and send as series
v4: add CC stable
v5: no change

---
 drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/net/wireless/mediatek/mt76/tx.c +1 -1
index 3707ee19e4ae..ec503521ae63 100644
@@ -879,7 +879,7 @@ int mt76_skb_adjust_pad(struct sk_buff *skb, int pad)
 		}
 	}
 
-	if (skb_pad(last, pad))
+	if (__skb_pad(last, pad, false))
 		return -ENOMEM;
 
 	__skb_put(last, pad);
Signature · 1 line
2.45.2
author
#11 lore raw

PATCH v5 2/2 wifi: mt76: fix memory leak in USB TX path

In reply to Eason Lai

When errors occur in mt76u_tx_queue_skb(), the skb is not properly freed, leading to memory leaks. This happens when:
- The queue is full
- tx_prepare_skb() drops zero-length frames (such as WMM NULL frames) to prevent hardware TX hangs
- Buffer setup fails

Fix by properly releasing the skb with ieee80211_tx_status_ext() when the queue is full or tx_prepare_skb() fails, and using mt76_tx_complete_skb() when mt76u_tx_setup_buffers() fails (since tx_prepare_skb() has already succeeded and may have allocated resources that need cleanup)

---
v2:
- add error handling for queue full and tx_prepare_skb()
- correct error handling for mt76u_tx_setup_buffers()
v3: add Tested-by: Devin Wittmayer and cc stable
v4: add Closes line
v5: add Tested-by: Hans Buchheim and Reported-by: Sergei Melnichenko

---
 drivers/net/wireless/mediatek/mt76/usb.c | 31 ++++++++++++++++++++----
 1 file changed, 26 insertions(+), 5 deletions(-)
drivers/net/wireless/mediatek/mt76/usb.c +26 -5
index a9af3aa6b80a..dfc12f6944a7 100644
@@ -893,6 +893,9 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 		   enum mt76_txq_id qid, struct sk_buff *skb,
 		   struct mt76_wcid *wcid, struct ieee80211_sta *sta)
 {
+	struct ieee80211_tx_status status = {
+		.sta = sta,
+	};
 	struct mt76_tx_info tx_info = {
 		.skb = skb,
 	};
@@ -900,17 +903,27 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	u16 idx = q->head;
 	int err;
 
-	if (q->queued == q->ndesc)
-		return -ENOSPC;
+	if (q->queued == q->ndesc) {
+		err = -ENOSPC;
+		goto err_free_skb;
+	}
 
 	skb->prev = skb->next = NULL;
 	err = dev->drv->tx_prepare_skb(dev, NULL, qid, wcid, sta, &tx_info);
 	if (err < 0)
-		return err;
+		goto err_free_skb;
 
 	err = mt76u_tx_setup_buffers(dev, tx_info.skb, q->entry[idx].urb);
-	if (err < 0)
-		return err;
+	if (err < 0) {
+		/*
+		 * mt76_tx_status_skb_get() walks the idr and dereferences
+		 * a freed skb. This SKB is not counted in non-AQL counter
+		 * due to error return, so using 0xffff as wcid to keep
+		 * balanced.
+		 */
+		mt76_tx_complete_skb(dev, 0xffff, tx_info.skb);
+		goto err_ret;
+	}
 
 	mt76u_fill_bulk_urb(dev, USB_DIR_OUT, q->ep, q->entry[idx].urb,
 			    mt76u_complete_tx, &q->entry[idx]);
@@ -921,6 +934,14 @@ mt76u_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
 	q->queued++;
 
 	return idx;
+
+err_free_skb:
+	status.skb = tx_info.skb;
+	spin_lock_bh(&dev->rx_lock);
+	ieee80211_tx_status_ext(dev->hw, &status);
+	spin_unlock_bh(&dev->rx_lock);
+err_ret:
+	return err;
 }
 
 static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
Signature · 1 line
2.45.2