When a device is attached to a vhci_hcd port, the USB hub worker thread
calls hub_port_connect() and acquires the address0_mutex. It then sends a
synchronous USB control message to the device. If the user-space server
does not reply, the control message times out, and usb_kill_urb() is called
to cancel the URB. usb_kill_urb() invokes the HCD's dequeue function,
vhci_urb_dequeue(), which queues a CMD_UNLINK request to the socket and
expects a RET_UNLINK reply from user-space. usb_kill_urb() then waits
indefinitely for the URB's use_count to drop to 0.
If the user-space server never replies with RET_UNLINK, the URB is never
given back, and the worker thread blocks forever in usb_kill_urb(), holding
the address0_mutex. Since address0_mutex is shared between the primary (USB
2.0) and shared (USB 3.0) HCDs, any subsequent device attach on the other
HCD will block forever attempting to acquire the same mutex, leading to a
system-wide hung task:
INFO: task kworker/1:2:798 blocked for more than 15 seconds.
task:kworker/1:2 state:D stack:23848 pid:798 tgid:798 ppid:2
task_flags:0x4208060 flags:0x00080000
Workqueue: usb_hub_wq hub_event
Call Trace:
__schedule+0x17e7/0x5630 kernel/sched/core.c:7234
schedule+0x164/0x2b0 kernel/sched/core.c:7326
schedule_preempt_disabled+0x13/0x30 kernel/sched/core.c:7383
__mutex_lock_common kernel/locking/mutex.c:726 [inline]
__mutex_lock+0x7bf/0x1550 kernel/locking/mutex.c:821
hub_port_connect drivers/usb/core/hub.c:5465 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
port_event drivers/usb/core/hub.c:5871 [inline]
hub_event+0x20c9/0x4d30 drivers/usb/core/hub.c:5953
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0x92d/0xe10 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
To fix this, introduce a timeout mechanism for unlink requests in vhci_hcd.
When vhci_urb_dequeue() queues a CMD_UNLINK, it now starts a timer with a
4-second timeout. If the timer expires before a RET_UNLINK is received, the
timer callback assumes the server is unresponsive and triggers a connection
teardown via usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP). When the
connection is shut down, vhci_device_unlink_cleanup() iterates over the
pending unlink requests and manually gives back the URBs, unblocking
usb_kill_urb() and releasing the address0_mutex.
Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: syzbot+a7edecbf389d11a369d4@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=a7edecbf389d11a369d4
Link: https://syzkaller.appspot.com/ai_job?id=1b7c6a35-c4a5-4834-9631-46f7e83cbd57
To: "Greg Kroah-Hartman"
To:
To: "Shuah Khan"
To: "Valentina Manea"
To: "Takahiro Hirofuchi"
Cc: "Hongren Zheng"
Cc:
---
diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h
index 5659dce15..98e3bfb29 100644
--- a/drivers/usb/usbip/vhci.h
+++ b/drivers/usb/usbip/vhci.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -65,6 +66,9 @@ struct vhci_unlink {
/* seqnum of the unlink target */
unsigned long unlink_seqnum;
+
+ struct timer_list unlink_timer;
+ struct vhci_device *vdev;
};
enum hub_speed {
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 39e8faf4c..5cc276f46 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -892,6 +892,14 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
* => send RET_UNLINK
*
*/
+static void vhci_unlink_timeout(struct timer_list *t)
+{
+ struct vhci_unlink *unlink = timer_container_of(unlink, t, unlink_timer);
+ struct vhci_device *vdev = unlink->vdev;
+
+ usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_TCP);
+}
+
static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd);
@@ -964,6 +972,10 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
dev_info(hcd_dev(hcd), "seqnum max\n");
unlink->unlink_seqnum = priv->seqnum;
+ unlink->vdev = vdev;
+
+ timer_setup(&unlink->unlink_timer, vhci_unlink_timeout, 0);
+ mod_timer(&unlink->unlink_timer, jiffies + msecs_to_jiffies(4000));
/* send cmd_unlink and try to cancel the pending URB in the
* peer */
@@ -997,7 +1009,12 @@ static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
if (!urb) {
list_del(&unlink->list);
+ spin_unlock(&vdev->priv_lock);
+ spin_unlock_irqrestore(&vhci->lock, flags);
+ timer_delete_sync(&unlink->unlink_timer);
kfree(unlink);
+ spin_lock_irqsave(&vhci->lock, flags);
+ spin_lock(&vdev->priv_lock);
continue;
}
@@ -1010,6 +1027,7 @@ static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
spin_unlock(&vdev->priv_lock);
spin_unlock_irqrestore(&vhci->lock, flags);
+ timer_delete_sync(&unlink->unlink_timer);
usb_hcd_giveback_urb(hcd, urb, urb->status);
spin_lock_irqsave(&vhci->lock, flags);
diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c
index a678e7c89..658a464a1 100644
--- a/drivers/usb/usbip/vhci_rx.c
+++ b/drivers/usb/usbip/vhci_rx.c
@@ -178,6 +178,7 @@ static void vhci_recv_ret_unlink(struct vhci_device *vdev,
usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status);
}
+ timer_delete_sync(&unlink->unlink_timer);
kfree(unlink);
}
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 syzkaller@googlegroups.com.