[PATCH RFC] mmc: vub300: fix sleeping in softirq during host cleanup

"syzbot" <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
The `vub300` driver uses a timer (`vub300->inactivity_timer`) to monitor
inactivity. When this timer expires, its callback
`vub300_inactivity_timer_expired()` is executed in softirq (atomic)
context. Inside the timer callback, if the device has been disconnected, it
drops its reference to the host by calling `kref_put(&vub300->kref,
vub300_delete)`. If this timer holds the last reference, `vub300_delete()`
is invoked synchronously in the same atomic context.

`vub300_delete()` calls `mmc_free_host()`. Since commit 1036f69e2513 ("mmc:
core: Cancel delayed work before releasing host"), `mmc_free_host()` calls
`cancel_delayed_work_sync()`, which can sleep. This triggers a "BUG:
sleeping function called from invalid context" when executed from the
softirq context:

BUG: sleeping function called from invalid context at
kernel/workqueue.c:4487
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 0, name: swapper/0
...
Call Trace:
 <IRQ>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 __might_resched+0x378/0x4d0 kernel/sched/core.c:9197
 __cancel_work_sync+0x6d/0x110 kernel/workqueue.c:4487
 mmc_free_host+0x19/0x30 drivers/mmc/core/host.c:700
 call_timer_fn+0x192/0x5e0 kernel/time/timer.c:1748
 expire_timers kernel/time/timer.c:1799 [inline]
 __run_timers kernel/time/timer.c:2374 [inline]
 __run_timer_base+0x652/0x8b0 kernel/time/timer.c:2386
 run_timer_base kernel/time/timer.c:2395 [inline]
 run_timer_softirq+0xb7/0x170 kernel/time/timer.c:2405
 handle_softirqs+0x216/0x830 kernel/softirq.c:622
 __do_softirq kernel/softirq.c:656 [inline]
 invoke_softirq kernel/softirq.c:496 [inline]
 __irq_exit_rcu+0xc0/0x210 kernel/softirq.c:735
 irq_exit_rcu+0x9/0x30 kernel/softirq.c:752
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062
 [inline]
 sysvec_apic_timer_interrupt+0x92/0xb0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>

To fix this, defer the cleanup operations in `vub300_delete()` to a
workqueue. A dedicated `work_struct` is added to `struct vub300_mmc_host`
and queued on the driver's `deadworkqueue` when the `kref` reaches zero.
Using `deadworkqueue` ensures that the work is safely flushed during module
unload.

Additionally, the order of cleanup operations is adjusted to prevent a
use-after-free. `usb_put_dev()` is called after `mmc_free_host()` because
`mmc_free_host()` accesses the USB device structure via `host->parent`. The
`udev` pointer is saved in a local variable before `mmc_free_host()` frees
the `vub300` structure.

Fixes: 1036f69e2513 ("mmc: core: Cancel delayed work before releasing host")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=1ee4f3b9228e35f14677
Link: https://syzkaller.appspot.com/ai_job?id=f105b557-f491-4cff-97a1-a15b027fdeec
To: <[email protected]>
To: "Ulf Hansson" <[email protected]>
To: "Geert Uytterhoeven" <[email protected]>
Cc: "Johan Hovold" <[email protected]>
Cc: "Guangshuo Li" <[email protected]>
Cc: <[email protected]>
Cc: "Runyu Xiao" <[email protected]>

---
diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c
index 2dae474dc..28d97024f 100644
--- a/drivers/mmc/host/vub300.c
+++ b/drivers/mmc/host/vub300.c
@@ -346,6 +346,7 @@ struct vub300_mmc_host {
 	struct timer_list inactivity_timer;
 	struct work_struct deadwork;
 	struct work_struct cmndwork;
+	struct work_struct free_work;
 	struct delayed_work pollwork;
 	struct host_controller_info hc_info;
 	struct sd_status_header system_port_status;
@@ -366,21 +367,25 @@ struct vub300_mmc_host {
 #define GET_HC_INF0			1
 #define GET_SYSTEM_PORT_STATUS		0
 
-static void vub300_delete(struct kref *kref)
-{				/* kref callback - softirq */
-	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+static void vub300_free_work(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+		container_of(work, struct vub300_mmc_host, free_work);
 	struct mmc_host *mmc = vub300->mmc;
+	struct usb_device *udev = vub300->udev;
 
 	usb_free_urb(vub300->command_out_urb);
-	vub300->command_out_urb = NULL;
 	usb_free_urb(vub300->command_res_urb);
-	vub300->command_res_urb = NULL;
-	usb_put_dev(vub300->udev);
 	mmc_free_host(mmc);
-	/*
-	 * and hence also frees vub300
-	 * which is contained at the end of struct mmc
-	 */
+	usb_put_dev(udev);
+}
+
+static void vub300_delete(struct kref *kref)
+{
+	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+
+	INIT_WORK(&vub300->free_work, vub300_free_work);
+	queue_work(deadworkqueue, &vub300->free_work);
 }
 
 static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)


base-commit: 075b74841bd0065a3bda3440873c747938e69b68
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
The person who has signed off on the patch is responsible for
addressing comments.
syzbot engineers can be reached at [email protected].
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.