Forwarded: [PATCH] ath9k: defer reg_in URB resubmission to workqueue to fix RCU stall

From: syzbot

Date: Sun Apr 05 2026 - 00:41:54 EST


For archival purposes, forwarding an incoming command email to
linux-kernel@xxxxxxxxxxxxxxx, syzkaller-bugs@xxxxxxxxxxxxxxxx.

***

Subject: [PATCH] ath9k: defer reg_in URB resubmission to workqueue to fix RCU stall
Author: kartikey406@xxxxxxxxx

#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master


ath9k_hif_usb_reg_in_cb() is a URB completion callback that
runs in softirq context via dummy_hcd's hrtimer which is
registered with HRTIMER_MODE_REL_SOFT.

Calling usb_submit_urb() directly from this softirq context
triggers a long synchronous chain:

dummy_urb_enqueue()
hrtimer_start(HRTIMER_MODE_REL_SOFT)
dummy_timer()
__usb_hcd_giveback_urb()
ath9k_hif_usb_reg_in_cb()
usb_submit_urb() <- back to start

This keeps the CPU busy in softirq context indefinitely,
starving the rcu_preempt kthread and causing an RCU stall:

rcu: rcu_preempt kthread starved for 3053 jiffies!
rcu: Unless rcu_preempt kthread gets sufficient CPU time,
OOM is now expected behavior.

Fix this by introducing a small per-resubmission wrapper
struct (reg_in_work) that is freshly allocated on each URB
completion and carries its own work_struct. The resubmission
is then deferred to the system workqueue via schedule_work(),
allowing the softirq to exit quickly.

Using a fresh wrapper per completion avoids races that would
arise from reusing a single embedded work_struct:
- INIT_WORK() is called on a newly allocated struct so
there is no risk of reinitialising a work item that is
still queued or running.
- schedule_work() on a fresh work_struct always succeeds
so no resubmission is ever silently dropped.
- usb_get_urb() is called before schedule_work() and
usb_put_urb() is called last in the worker after all
URB accesses are complete, ensuring the URB remains
valid for the entire lifetime of the work item.

On resubmission failure in the worker the original error
path is preserved: skb and rx_buf are freed and
urb->context is set to NULL before dropping the URB
reference, matching the behaviour of the original
goto free_skb path.

Reported-by: syzbot+9b95da55ba5146a60734@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=9b95da55ba5146a60734
Signed-off-by: Deepanshu Kartikey <kartikey406@xxxxxxxxx>
---
drivers/net/wireless/ath/ath9k/hif_usb.c | 53 ++++++++++++++++++++----
1 file changed, 45 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c
index 8533b88974b2..0f9536b2d0a2 100644
--- a/drivers/net/wireless/ath/ath9k/hif_usb.c
+++ b/drivers/net/wireless/ath/ath9k/hif_usb.c
@@ -731,12 +731,43 @@ static void ath9k_hif_usb_rx_cb(struct urb *urb)
kfree(rx_buf);
}

+struct reg_in_work {
+ struct urb *urb;
+ struct hif_device_usb *hif_dev;
+ struct work_struct work;
+};
+
+static void ath9k_hif_usb_reg_in_resubmit(struct work_struct *work)
+{
+ struct reg_in_work *rw = container_of(work,
+ struct reg_in_work,
+ work);
+ struct urb *urb = rw->urb;
+ struct rx_buf *rx_buf = urb->context;
+
+ int ret;
+
+ usb_anchor_urb(rw->urb, &rw->hif_dev->reg_in_submitted);
+ ret = usb_submit_urb(rw->urb, GFP_KERNEL);
+
+ if (ret) {
+ usb_unanchor_urb(rw->urb);
+ if (rx_buf) {
+ kfree_skb(rx_buf->skb);
+ kfree(rx_buf);
+ urb->context = NULL;
+ }
+ }
+
+ usb_put_urb(urb);
+ kfree(rw);
+}
+
static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
{
struct rx_buf *rx_buf = urb->context;
struct hif_device_usb *hif_dev = rx_buf->hif_dev;
struct sk_buff *skb = rx_buf->skb;
- int ret;

if (!skb)
return;
@@ -786,14 +817,20 @@ static void ath9k_hif_usb_reg_in_cb(struct urb *urb)
}

resubmit:
- usb_anchor_urb(urb, &hif_dev->reg_in_submitted);
- ret = usb_submit_urb(urb, GFP_ATOMIC);
- if (ret) {
- usb_unanchor_urb(urb);
- goto free_skb;
+ {
+ struct reg_in_work *rw;
+
+ rw = kmalloc_obj(*rw, GFP_ATOMIC);
+ if (!rw)
+ goto free_skb;
+
+ rw->urb = urb;
+ rw->hif_dev = hif_dev;
+ usb_get_urb(urb);
+ INIT_WORK(&rw->work, ath9k_hif_usb_reg_in_resubmit);
+ schedule_work(&rw->work);
+ return;
}
-
- return;
free_skb:
kfree_skb(skb);
free_rx_buf:
--
2.43.0