mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races
@ 2026-10-21 15:06 Raul E Rangel
  2026-10-21 15:06 ` [PATCH 1/6] usbip: vhci_hcd: protect urb->hcpriv with priv_lock in vhci_urb_dequeue() Raul E Rangel
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:06 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel,
	Anirudh Rayabharam, Brian G. Merrell, Suwan Kim

This series fixes a set of URB lifecycle, locking, and disconnect
teardown bugs in drivers/usb/usbip/vhci_hcd that can orphan pending URBs
and wedge usb_kill_urb() and usb_hub_wq in uninterruptible D state when
a virtual USB port disconnects under concurrent transfer/unlink load.

Patch breakdown:
  1. Protect urb->hcpriv accesses in vhci_urb_dequeue() with
     vdev->priv_lock inside vhci->lock (matching vhci_urb_enqueue() and
     the cleanup helpers), and add a !portnum || portnum > VHCI_HC_PORTS
     bounds guard to both vhci_urb_enqueue() and vhci_urb_dequeue().
  2. Track whether an URB has been moved from vdev->priv_tx to
     vdev->priv_rx via priv->submitted under vdev->priv_lock, and
     complete unsent (!priv->submitted) URBs locally in
     vhci_urb_dequeue() instead of queuing a CMD_UNLINK PDU. This
     prevents vhci_device_unlink_cleanup() from freeing the unlink entry
     and orphaning an unsent URB on priv_tx, and prevents CMD_UNLINK
     from being transmitted before CMD_SUBMIT on an active connection.
  3. Clear vdev->ud.tcp_socket under vhci->lock and vdev->ud.lock in
     vhci_shutdown_connection(), reject vhci_urb_enqueue() once
     USBIP_EH_SHUTDOWN is set or tcp_socket is NULL, and drain all
     remaining URBs on vdev->priv_tx and vdev->priv_rx in
     vhci_device_unlink_cleanup().
  4. Move the URB_DMA_MAP_SG clearing from vhci_recv_ret_submit() into
     pickup_urb_and_free_priv() and also clear URB_DMA_MAP_SG in
     vhci_urb_dequeue() and vhci_cleanup_priv_list() so USB core does
     not call dma_unmap_sg() on unmapped scatterlists on any giveback
     path.
  5. Allocate struct vhci_priv with mem_flags before acquiring spinlocks
     in vhci_urb_enqueue(), and return 0 (with VDEV_EVENT_ERROR_MALLOC
     triggered) if struct vhci_unlink allocation fails in
     vhci_urb_dequeue() after usb_hcd_check_unlink_urb() has already
     marked urb->unlinked.
  6. Serialize vdev->udev clearing in vhci_device_reset() under
     vhci->lock and ud->lock (dropping old usb_device and socket
     references outside the spinlocks in both vhci_device_reset() and
     vhci_urb_enqueue()), and reject URBs from a stale usb_device
     (including address-0 requests from an already-registered
     usb_device undergoing USBDEVFS_RESET) if the same rhport is
     re-attached before usb_hub_wq finishes disconnecting the old
     device.

Testing:
  - Built at every commit in the series with W=1 (0 warnings, 0 errors).
  - Verified under User-Mode Linux (ARCH=um, UP with CONFIG_PREEMPT=y,
    CONFIG_PROVE_LOCKING=y, CONFIG_KASAN=y, CONFIG_DEBUG_ATOMIC_SLEEP=y,
    CONFIG_USBIP_VHCI_HCD=y, CONFIG_USBIP_DEBUG=y):
    * Test 1: Abrupt server disconnect mid-transfer while client threads
      loop 32 KiB scatter-gather (num_sgs=2, URB_DMA_MAP_SG) SUBMITURB,
      DISCARDURB, USBDEVFS_CONTROL, and USBDEVFS_RESET (5/5 cycles,
      0 D-state tasks).
    * Test 2: 500 rapid SUBMITURB + DISCARDURB + REAPURB iterations on a
      live connection, plus deterministic verification with a stalled
      tx socket that unlinking 40 unsent URBs on priv_tx completes them
      locally with 0 CMD_UNLINK PDUs sent on the wire.
    * Test 3: Immediate detach + re-attach on the same rhport while a
      client thread submits URBs on the old usbfs fd, confirming all
      stale submits fail with -ENODEV and 0 stale CMD_SUBMIT packets
      reach the new server.
    * Test 3B: Deterministic reproduction of the field deadlock sequence
      (unsent URB queued on priv_tx while tcp_socket != NULL, followed
      by concurrent USBDEVFS_RESET and server socket close), completing
      with 0 D-state tasks.
    * Test 4: Lockdep (debug_locks = 1) and KASAN clean across all runs.
  - Built and soaked for 18.5+ hours (280 cycles) on an SMP gLinux host
    (7.1.6-1rodete1-amd64) where the original usb_disconnect() /
    usb_kill_urb() deadlock was captured, completing 125,000+ EP0
    control transfers, 21,000+ async URB unlinks, 8+ GiB of
    scatter-gather USB Mass Storage bulk reads, 1,000+ mid-transfer
    SIGKILL client aborts, and 850+ live USB/IP socket disconnect
    teardowns and port re-attachments under active load with 0 kernel
    warnings and 0 hung tasks.


Raul E Rangel (6):
  usbip: vhci_hcd: protect urb->hcpriv with priv_lock in
    vhci_urb_dequeue()
  usbip: vhci_hcd: complete unsent URBs locally in vhci_urb_dequeue()
  usbip: vhci_hcd: stop enqueue and drain all pending URBs on disconnect
  usbip: vhci_hcd: clear URB_DMA_MAP_SG on all giveback paths
  usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM
  usbip: vhci_hcd: reject URBs from stale usb_device after port
    re-attach

 drivers/usb/usbip/vhci.h     |   1 +
 drivers/usb/usbip/vhci_hcd.c | 236 +++++++++++++++++++++++------------
 drivers/usb/usbip/vhci_rx.c  |   6 +-
 drivers/usb/usbip/vhci_tx.c  |   1 +
 4 files changed, 162 insertions(+), 82 deletions(-)

-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 1/6] usbip: vhci_hcd: protect urb->hcpriv with priv_lock in vhci_urb_dequeue()
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
@ 2026-10-21 15:06 ` Raul E Rangel
  2026-10-21 15:06 ` [PATCH 2/6] usbip: vhci_hcd: complete unsent URBs locally " Raul E Rangel
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:06 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable,
	Brian G. Merrell

In vhci_urb_dequeue(), urb->hcpriv is read and priv->vdev is
dereferenced while holding only vhci->lock, before acquiring
vdev->priv_lock. However, pickup_urb_and_free_priv() in vhci_rx.c frees
priv and clears urb->hcpriv under vdev->priv_lock without holding
vhci->lock.

If vhci_urb_dequeue() races with pickup_urb_and_free_priv() on a
completing URB, vhci_urb_dequeue() can observe a non-NULL priv pointer
just before pickup_urb_and_free_priv() frees it, leading to a
use-after-free when dereferencing priv->vdev or priv->seqnum, or a
double-free of priv.

Resolve the race by deriving vdev from urb->dev->portnum (matching
vhci_urb_enqueue(), and guarding against portnum == 0 in both) and
acquiring vdev->priv_lock (nested inside vhci->lock) before reading
urb->hcpriv.

Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci_hcd.c | 29 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 15 deletions(-)

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 39e8faf4c18c..3196ea80e7c2 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -704,7 +704,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	struct vhci_device *vdev;
 	unsigned long flags;
 
-	if (portnum > VHCI_HC_PORTS) {
+	if (!portnum || portnum > VHCI_HC_PORTS) {
 		dev_err(hcd_dev(hcd), "invalid port number %d\n", portnum);
 		return -ENODEV;
 	}
@@ -899,34 +899,35 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 	struct vhci_priv *priv;
 	struct vhci_device *vdev;
 	unsigned long flags;
+	u8 portnum = urb->dev->portnum;
+	int ret;
+
+	if (!portnum || portnum > VHCI_HC_PORTS)
+		return -ENODEV;
+	vdev = &vhci_hcd->vdev[portnum - 1];
 
 	spin_lock_irqsave(&vhci->lock, flags);
+	spin_lock(&vdev->priv_lock);
 
 	priv = urb->hcpriv;
 	if (!priv) {
 		/* URB was never linked! or will be soon given back by
 		 * vhci_rx. */
+		spin_unlock(&vdev->priv_lock);
 		spin_unlock_irqrestore(&vhci->lock, flags);
 		return -EIDRM;
 	}
 
-	{
-		int ret = 0;
-
-		ret = usb_hcd_check_unlink_urb(hcd, urb, status);
-		if (ret) {
-			spin_unlock_irqrestore(&vhci->lock, flags);
-			return ret;
-		}
+	ret = usb_hcd_check_unlink_urb(hcd, urb, status);
+	if (ret) {
+		spin_unlock(&vdev->priv_lock);
+		spin_unlock_irqrestore(&vhci->lock, flags);
+		return ret;
 	}
 
 	 /* send unlink request here? */
-	vdev = priv->vdev;
-
 	if (!vdev->ud.tcp_socket) {
 		/* tcp connection is closed */
-		spin_lock(&vdev->priv_lock);
-
 		list_del(&priv->list);
 		kfree(priv);
 		urb->hcpriv = NULL;
@@ -948,8 +949,6 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 		/* tcp connection is alive */
 		struct vhci_unlink *unlink;
 
-		spin_lock(&vdev->priv_lock);
-
 		/* setup CMD_UNLINK pdu */
 		unlink = kzalloc_obj(struct vhci_unlink, GFP_ATOMIC);
 		if (!unlink) {
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 2/6] usbip: vhci_hcd: complete unsent URBs locally in vhci_urb_dequeue()
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
  2026-10-21 15:06 ` [PATCH 1/6] usbip: vhci_hcd: protect urb->hcpriv with priv_lock in vhci_urb_dequeue() Raul E Rangel
@ 2026-10-21 15:06 ` Raul E Rangel
  2026-10-21 15:06 ` [PATCH 3/6] usbip: vhci_hcd: stop enqueue and drain all pending URBs on disconnect Raul E Rangel
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:06 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable,
	Anirudh Rayabharam, Brian G. Merrell

When an URB is enqueued via vhci_urb_enqueue(), its vhci_priv entry is
placed on vdev->priv_tx until vhci_tx_loop() moves it to vdev->priv_rx
in dequeue_from_priv_tx() and sends USBIP_CMD_SUBMIT.

If vhci_urb_dequeue() is called while the URB is still on vdev->priv_tx
and vdev->ud.tcp_socket is non-NULL, vhci_urb_dequeue() currently sets
urb->unlinked and queues a vhci_unlink entry on vdev->unlink_tx without
removing the URB from vdev->priv_tx. This causes two bugs:

1. If vhci_tx_loop() has already exited after an earlier send error or
   been stopped during connection shutdown before tcp_socket is cleared,
   the URB remains on vdev->priv_tx. When vhci_device_unlink_cleanup()
   later runs pickup_urb_and_free_priv(), which only searches
   vdev->priv_rx, the vhci_unlink entry is freed while the URB remains
   linked on ep->urb_list with urb->unlinked set. Subsequent calls to
   vhci_urb_dequeue() from usb_hcd_flush_endpoint() fail with -EBUSY at
   usb_hcd_check_unlink_urb(), leaking the URB giveback and hanging
   usb_kill_urb() in D state forever. (Stopping new enqueues on shutdown
   and draining non-unlinked URBs are addressed in the next patch.)

2. On an active connection, if an URB is enqueued and immediately
   dequeued while vhci_tx_loop() is in vhci_send_cmd_unlink(),
   USBIP_CMD_UNLINK can be transmitted before USBIP_CMD_SUBMIT for the
   same seqnum. The server replies USBIP_RET_UNLINK for the unknown
   seqnum while vhci_tx_loop() is concurrently building or sending
   USBIP_CMD_SUBMIT, racing on urb->hcpriv or leaving an orphaned
   USBIP_RET_SUBMIT that tears down the connection with
   VDEV_EVENT_ERROR_TCP.

Record whether an URB has been dequeued from vdev->priv_tx into
vdev->priv_rx via priv->submitted under vdev->priv_lock, and complete
unsent (!priv->submitted) URBs locally in vhci_urb_dequeue() without
queuing a CMD_UNLINK request. Because vhci_tx_loop() drains
vdev->priv_tx in vhci_send_cmd_submit() before entering
vhci_send_cmd_unlink(), any URB with priv->submitted set is guaranteed
to have its CMD_SUBMIT sent before its CMD_UNLINK.

Fixes: 258c81b341c8 ("usbip: give back URBs for unsent unlink requests during cleanup")
Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci.h     |  1 +
 drivers/usb/usbip/vhci_hcd.c | 14 ++++++--------
 drivers/usb/usbip/vhci_tx.c  |  1 +
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h
index 5659dce1526e..4a62f89b89c7 100644
--- a/drivers/usb/usbip/vhci.h
+++ b/drivers/usb/usbip/vhci.h
@@ -55,6 +55,7 @@ struct vhci_priv {
 
 	struct vhci_device *vdev;
 	struct urb *urb;
+	bool submitted;
 };
 
 struct vhci_unlink {
diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 3196ea80e7c2..7fadb41c01fc 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -925,20 +925,18 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 		return ret;
 	}
 
-	 /* send unlink request here? */
-	if (!vdev->ud.tcp_socket) {
-		/* tcp connection is closed */
+	/*
+	 * Give back the URB locally if the TCP connection is closed or the URB
+	 * is still on priv_tx (not yet picked up by vhci_tx_loop), so no
+	 * CMD_UNLINK is needed.
+	 */
+	if (!vdev->ud.tcp_socket || !priv->submitted) {
 		list_del(&priv->list);
 		kfree(priv);
 		urb->hcpriv = NULL;
 
 		spin_unlock(&vdev->priv_lock);
 
-		/*
-		 * If tcp connection is alive, we have sent CMD_UNLINK.
-		 * vhci_rx will receive RET_UNLINK and give back the URB.
-		 * Otherwise, we give back it here.
-		 */
 		usb_hcd_unlink_urb_from_ep(hcd, urb);
 
 		spin_unlock_irqrestore(&vhci->lock, flags);
diff --git a/drivers/usb/usbip/vhci_tx.c b/drivers/usb/usbip/vhci_tx.c
index 32e6fabccf72..997db6cfa382 100644
--- a/drivers/usb/usbip/vhci_tx.c
+++ b/drivers/usb/usbip/vhci_tx.c
@@ -40,6 +40,7 @@ static struct vhci_priv *dequeue_from_priv_tx(struct vhci_device *vdev)
 
 	list_for_each_entry_safe(priv, tmp, &vdev->priv_tx, list) {
 		list_move_tail(&priv->list, &vdev->priv_rx);
+		priv->submitted = true;
 		spin_unlock_irqrestore(&vdev->priv_lock, flags);
 		return priv;
 	}
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 3/6] usbip: vhci_hcd: stop enqueue and drain all pending URBs on disconnect
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
  2026-10-21 15:06 ` [PATCH 1/6] usbip: vhci_hcd: protect urb->hcpriv with priv_lock in vhci_urb_dequeue() Raul E Rangel
  2026-10-21 15:06 ` [PATCH 2/6] usbip: vhci_hcd: complete unsent URBs locally " Raul E Rangel
@ 2026-10-21 15:06 ` Raul E Rangel
  2026-10-21 15:06 ` [PATCH 4/6] usbip: vhci_hcd: clear URB_DMA_MAP_SG on all giveback paths Raul E Rangel
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:06 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable,
	Anirudh Rayabharam, Brian G. Merrell

Even with vhci_urb_dequeue() completing unsent URBs locally when
unlinked, several races in vhci_shutdown_connection() allow URBs to be
enqueued after shutdown begins or remain stranded on vdev->priv_tx and
vdev->priv_rx across disconnect and port reset:

1. vhci_urb_enqueue() only rejects URBs when vdev->ud.status is
   VDEV_ST_NULL or VDEV_ST_ERROR. Because ud.status remains VDEV_ST_USED
   after USBIP_EH_SHUTDOWN is set and until vhci_device_reset() runs
   after vhci_shutdown_connection(), vhci_urb_enqueue() continues
   accepting new URBs onto vdev->priv_tx even after tcp_tx has stopped.

2. vhci_shutdown_connection() clears vdev->ud.tcp_socket without holding
   vhci->lock or vdev->ud.lock, racing with vhci_urb_enqueue() and
   vhci_urb_dequeue().

3. vhci_device_unlink_cleanup() only processes vdev->unlink_tx and
   vdev->unlink_rx, leaving non-unlinked URBs on vdev->priv_tx and
   vdev->priv_rx until asynchronous rh_port_disconnect() work runs on
   usb_hub_wq. If a task holds udev->dev.mutex while waiting on an URB,
   usb_disconnect() blocks on udev->dev.mutex. Moreover, because
   vhci_device_reset() immediately sets ud.status to VDEV_ST_NULL, any
   entries left on vdev->priv_tx when the port is re-attached via
   attach_store() will be transmitted by the new connection's tcp_tx
   thread to the new server.

In a live deadlock captured with drgn on a hung host:
- rx thread logged "connection closed" and set USBIP_EH_SHUTDOWN, and
  vhci_shutdown_connection() stopped tcp_rx and tcp_tx (29.6519 s)
  before clearing tcp_socket (29.6716 s).
- In kernel memory (drgn), vdev->priv_tx still held never-transmitted
  URB seqnum 17193284 with urb->unlinked = -ESHUTDOWN and
  use_count = 1, while vdev->priv_rx held unanswered URB
  seqnum 17193282 with urb->unlinked = 0 and vdev->unlink_tx/unlink_rx
  were empty.
- A concurrent USBDEVFS_RESET ioctl holding udev->dev.mutex called
  usb_hcd_flush_endpoint() -> usb_kill_urb(), blocking in D state on
  seqnum 17193284 while usb_hub_wq blocked in usb_disconnect() waiting
  for udev->dev.mutex, freezing the vhci root hub.

Fix this to guarantee all pending URBs are given back when the
connection is torn down (note that while a connection remains up,
usb_kill_urb() still waits for RET_UNLINK or connection teardown):
- Clear vdev->ud.tcp_socket under vhci->lock and vdev->ud.lock in
  vhci_shutdown_connection(), and reject vhci_urb_enqueue() (with a
  rate-limited error) when USBIP_EH_SHUTDOWN is set or tcp_socket is
  NULL.
- Drain and give back all remaining URBs on vdev->priv_tx and
  vdev->priv_rx in vhci_device_unlink_cleanup() via
  vhci_cleanup_priv_list(), using list_first_entry() loops across lock
  drops.

Fixes: 258c81b341c8 ("usbip: give back URBs for unsent unlink requests during cleanup")
Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci_hcd.c | 115 ++++++++++++++++++++++++-----------
 1 file changed, 81 insertions(+), 34 deletions(-)

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 7fadb41c01fc..78ea1fc7906e 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -727,8 +727,11 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	/* refuse enqueue for dead connection */
 	spin_lock(&vdev->ud.lock);
 	if (vdev->ud.status == VDEV_ST_NULL ||
-	    vdev->ud.status == VDEV_ST_ERROR) {
-		dev_err(dev, "enqueue for inactive port %d\n", vdev->rhport);
+	    vdev->ud.status == VDEV_ST_ERROR ||
+	    (vdev->ud.event & USBIP_EH_SHUTDOWN) ||
+	    !vdev->ud.tcp_socket) {
+		dev_err_ratelimited(dev, "enqueue for inactive port %d\n",
+				    vdev->rhport);
 		spin_unlock(&vdev->ud.lock);
 		spin_unlock_irqrestore(&vhci->lock, flags);
 		return -ENODEV;
@@ -982,18 +985,20 @@ static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
 	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
 	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);
 	struct vhci *vhci = vhci_hcd->vhci;
-	struct vhci_unlink *unlink, *tmp;
+	struct vhci_unlink *unlink;
 	unsigned long flags;
 
 	spin_lock_irqsave(&vhci->lock, flags);
 	spin_lock(&vdev->priv_lock);
 
-	list_for_each_entry_safe(unlink, tmp, unlink_list, list) {
+	while (!list_empty(unlink_list)) {
 		struct urb *urb;
 
+		unlink = list_first_entry(unlink_list, struct vhci_unlink, list);
+		list_del(&unlink->list);
+
 		urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
 		if (!urb) {
-			list_del(&unlink->list);
 			kfree(unlink);
 			continue;
 		}
@@ -1002,8 +1007,6 @@ static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
 
 		usb_hcd_unlink_urb_from_ep(hcd, urb);
 
-		list_del(&unlink->list);
-
 		spin_unlock(&vdev->priv_lock);
 		spin_unlock_irqrestore(&vhci->lock, flags);
 
@@ -1019,6 +1022,50 @@ static void vhci_cleanup_unlink_list(struct vhci_device *vdev,
 	spin_unlock_irqrestore(&vhci->lock, flags);
 }
 
+/*
+ * Drain any remaining URBs still queued on priv_tx (e.g. enqueued before
+ * USBIP_EH_SHUTDOWN was set or after tcp_tx stopped) or waiting for a reply
+ * on priv_rx so no orphaned URBs remain after connection teardown.
+ */
+static void vhci_cleanup_priv_list(struct vhci_device *vdev,
+				   struct list_head *priv_list)
+{
+	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
+	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);
+	struct vhci *vhci = vhci_hcd->vhci;
+	struct vhci_priv *priv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&vhci->lock, flags);
+	spin_lock(&vdev->priv_lock);
+
+	while (!list_empty(priv_list)) {
+		struct urb *urb;
+
+		priv = list_first_entry(priv_list, struct vhci_priv, list);
+		urb = priv->urb;
+
+		list_del(&priv->list);
+		kfree(priv);
+		urb->hcpriv = NULL;
+
+		urb->status = -ENODEV;
+
+		usb_hcd_unlink_urb_from_ep(hcd, urb);
+
+		spin_unlock(&vdev->priv_lock);
+		spin_unlock_irqrestore(&vhci->lock, flags);
+
+		usb_hcd_giveback_urb(hcd, urb, urb->status);
+
+		spin_lock_irqsave(&vhci->lock, flags);
+		spin_lock(&vdev->priv_lock);
+	}
+
+	spin_unlock(&vdev->priv_lock);
+	spin_unlock_irqrestore(&vhci->lock, flags);
+}
+
 static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
 {
 	/* give back URB of unsent unlink request */
@@ -1026,17 +1073,26 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
 
 	/* give back URB of unanswered unlink request */
 	vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);
+
+	/* give back remaining unsent and unanswered URBs */
+	vhci_cleanup_priv_list(vdev, &vdev->priv_tx);
+	vhci_cleanup_priv_list(vdev, &vdev->priv_rx);
 }
 
 /*
- * The important thing is that only one context begins cleanup.
- * This is why error handling and cleanup become simple.
- * We do not want to consider race condition as possible.
+ * Tear down an active virtual port connection. After stopping tcp_rx and
+ * tcp_tx, tcp_socket is cleared under vhci->lock and ud->lock so concurrent
+ * vhci_urb_enqueue() and vhci_urb_dequeue() calls are serialized before
+ * vhci_device_unlink_cleanup() drains all pending URBs.
  */
 static void vhci_shutdown_connection(struct usbip_device *ud)
 {
 	struct vhci_device *vdev = container_of(ud, struct vhci_device, ud);
-	struct usb_hcd *hcd = vhci_hcd_to_hcd(vdev_to_vhci_hcd(vdev));
+	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
+	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);
+	struct vhci *vhci = vhci_hcd->vhci;
+	struct socket *socket;
+	unsigned long flags;
 
 	/* need this? see stub_dev.c */
 	if (ud->tcp_socket) {
@@ -1056,34 +1112,25 @@ static void vhci_shutdown_connection(struct usbip_device *ud)
 	dev_info(hcd_dev(hcd), "stop threads\n");
 
 	/* active connection is closed */
-	if (vdev->ud.tcp_socket) {
-		sockfd_put(vdev->ud.tcp_socket);
-		vdev->ud.tcp_socket = NULL;
-		vdev->ud.sockfd = -1;
-	}
+	spin_lock_irqsave(&vhci->lock, flags);
+	spin_lock(&vdev->ud.lock);
+	socket = vdev->ud.tcp_socket;
+	vdev->ud.tcp_socket = NULL;
+	vdev->ud.sockfd = -1;
+	spin_unlock(&vdev->ud.lock);
+	spin_unlock_irqrestore(&vhci->lock, flags);
+
+	if (socket)
+		sockfd_put(socket);
 	dev_info(hcd_dev(hcd), "release socket\n");
 
 	vhci_device_unlink_cleanup(vdev);
 
 	/*
-	 * rh_port_disconnect() is a trigger of ...
-	 *   usb_disable_device():
-	 *	disable all the endpoints for a USB device.
-	 *   usb_disable_endpoint():
-	 *	disable endpoints. pending urbs are unlinked(dequeued).
-	 *
-	 * NOTE: After calling rh_port_disconnect(), the USB device drivers of a
-	 * detached device should release used urbs in a cleanup function (i.e.
-	 * xxx_disconnect()). Therefore, vhci_hcd does not need to release
-	 * pushed urbs and their private data in this function.
-	 *
-	 * NOTE: vhci_dequeue() must be considered carefully. When shutting down
-	 * a connection, vhci_shutdown_connection() expects vhci_dequeue()
-	 * gives back pushed urbs and frees their private data by request of
-	 * the cleanup function of a USB driver. When unlinking a urb with an
-	 * active connection, vhci_dequeue() does not give back the urb which
-	 * is actually given back by vhci_rx after receiving its return pdu.
-	 *
+	 * rh_port_disconnect() triggers asynchronous USB device disconnect on
+	 * usb_hub_wq (usb_disconnect() -> usb_disable_device()). All pending
+	 * URBs and unlink requests on vdev have already been unlinked and
+	 * given back synchronously by vhci_device_unlink_cleanup() above.
 	 */
 	rh_port_disconnect(vdev);
 
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 4/6] usbip: vhci_hcd: clear URB_DMA_MAP_SG on all giveback paths
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
                   ` (2 preceding siblings ...)
  2026-10-21 15:06 ` [PATCH 3/6] usbip: vhci_hcd: stop enqueue and drain all pending URBs on disconnect Raul E Rangel
@ 2026-10-21 15:06 ` Raul E Rangel
  2026-10-21 15:07 ` [PATCH 5/6] usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM Raul E Rangel
  2026-10-21 15:07 ` [PATCH 6/6] usbip: vhci_hcd: reject URBs from stale usb_device after port re-attach Raul E Rangel
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:06 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable, Suwan Kim

In vhci_send_cmd_submit(), URB_DMA_MAP_SG is set on urb->transfer_flags
when urb->num_sgs is non-zero so that setup_cmd_submit_pdu() packs
USBIP_URB_DMA_MAP_SG into the wire PDU, even though vhci_hcd never maps
the scatterlist with dma_map_sg().

Currently, only vhci_recv_ret_submit() clears URB_DMA_MAP_SG before
calling usb_hcd_giveback_urb(). If an URB with scatter-gather entries
is instead given back via vhci_recv_ret_unlink(),
vhci_cleanup_unlink_list(), vhci_cleanup_priv_list(), or
vhci_urb_dequeue(), __usb_hcd_giveback_urb() invokes
usb_hcd_unmap_urb_for_dma(), which sees URB_DMA_MAP_SG and calls
dma_unmap_sg() on an unmapped scatterlist.

Move the URB_DMA_MAP_SG clearing from vhci_recv_ret_submit() into
pickup_urb_and_free_priv() (covering vhci_recv_ret_submit(),
vhci_recv_ret_unlink(), and vhci_cleanup_unlink_list()), and also clear
URB_DMA_MAP_SG in vhci_urb_dequeue() and vhci_cleanup_priv_list() so all
URB giveback paths clear the flag.

Fixes: ea44d190764b ("usbip: Implement SG support to vhci-hcd and stub driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci_hcd.c | 6 ++++++
 drivers/usb/usbip/vhci_rx.c  | 6 +++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 78ea1fc7906e..522cbaa6eb0a 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -934,6 +934,9 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 	 * CMD_UNLINK is needed.
 	 */
 	if (!vdev->ud.tcp_socket || !priv->submitted) {
+		if (urb->num_sgs)
+			urb->transfer_flags &= ~URB_DMA_MAP_SG;
+
 		list_del(&priv->list);
 		kfree(priv);
 		urb->hcpriv = NULL;
@@ -1045,6 +1048,9 @@ static void vhci_cleanup_priv_list(struct vhci_device *vdev,
 		priv = list_first_entry(priv_list, struct vhci_priv, list);
 		urb = priv->urb;
 
+		if (urb->num_sgs)
+			urb->transfer_flags &= ~URB_DMA_MAP_SG;
+
 		list_del(&priv->list);
 		kfree(priv);
 		urb->hcpriv = NULL;
diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c
index a678e7c89837..7e69e4b1bace 100644
--- a/drivers/usb/usbip/vhci_rx.c
+++ b/drivers/usb/usbip/vhci_rx.c
@@ -42,6 +42,9 @@ struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum)
 				 seqnum, status);
 		}
 
+		if (urb->num_sgs)
+			urb->transfer_flags &= ~URB_DMA_MAP_SG;
+
 		list_del(&priv->list);
 		kfree(priv);
 		urb->hcpriv = NULL;
@@ -95,9 +98,6 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev,
 	if (usbip_dbg_flag_vhci_rx)
 		usbip_dump_urb(urb);
 
-	if (urb->num_sgs)
-		urb->transfer_flags &= ~URB_DMA_MAP_SG;
-
 	usbip_dbg_vhci_rx("now giveback urb %u\n", pdu->base.seqnum);
 
 	spin_lock_irqsave(&vhci->lock, flags);
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 5/6] usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
                   ` (3 preceding siblings ...)
  2026-10-21 15:06 ` [PATCH 4/6] usbip: vhci_hcd: clear URB_DMA_MAP_SG on all giveback paths Raul E Rangel
@ 2026-10-21 15:07 ` Raul E Rangel
  2026-10-21 15:07 ` [PATCH 6/6] usbip: vhci_hcd: reject URBs from stale usb_device after port re-attach Raul E Rangel
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:07 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable,
	Brian G. Merrell

In vhci_urb_enqueue(), usb_hcd_link_urb_to_ep() links the URB onto the
endpoint before calling vhci_tx_urb(), which allocates struct vhci_priv
with GFP_ATOMIC while holding vhci->lock. If that allocation fails,
vhci_tx_urb() triggers VDEV_EVENT_ERROR_MALLOC and returns while
vhci_urb_enqueue() still returns 0, leaving urb->hcpriv NULL and the URB
linked on ep->urb_list where vhci_urb_dequeue() rejects it with -EIDRM.
Moreover, failing a single URB submission should return -ENOMEM to the
caller rather than tearing down the entire USB/IP connection.

Allocate struct vhci_priv in vhci_urb_enqueue() using the caller's
mem_flags before acquiring vhci->lock, pass it to vhci_tx_urb(), and
free it on early-exit and non-transmit paths.

In vhci_urb_dequeue(), if allocating struct vhci_unlink fails after
usb_hcd_check_unlink_urb() has already marked urb->unlinked, return 0
after triggering VDEV_EVENT_ERROR_MALLOC so callers know the unlink was
accepted and connection teardown will drain priv_rx and give back the
URB.

Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci_hcd.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 522cbaa6eb0a..27dba51392b7 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -665,18 +665,12 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
 	return retval;
 }
 
-static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev)
+static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev,
+			struct vhci_priv *priv)
 {
-	struct vhci_priv *priv;
 	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
 	unsigned long flags;
 
-	priv = kzalloc_obj(struct vhci_priv, GFP_ATOMIC);
-	if (!priv) {
-		usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC);
-		return;
-	}
-
 	spin_lock_irqsave(&vdev->priv_lock, flags);
 
 	priv->seqnum = (u32)atomic_inc_return(&vhci_hcd->seqnum);
@@ -702,6 +696,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	u8 portnum = urb->dev->portnum;
 	int ret = 0;
 	struct vhci_device *vdev;
+	struct vhci_priv *priv;
 	unsigned long flags;
 
 	if (!portnum || portnum > VHCI_HC_PORTS) {
@@ -716,11 +711,16 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 		return -EINVAL;
 	}
 
+	priv = kzalloc_obj(struct vhci_priv, mem_flags);
+	if (!priv)
+		return -ENOMEM;
+
 	spin_lock_irqsave(&vhci->lock, flags);
 
 	if (urb->status != -EINPROGRESS) {
 		dev_err(dev, "URB already unlinked!, status %d\n", urb->status);
 		spin_unlock_irqrestore(&vhci->lock, flags);
+		kfree(priv);
 		return urb->status;
 	}
 
@@ -734,6 +734,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 				    vdev->rhport);
 		spin_unlock(&vdev->ud.lock);
 		spin_unlock_irqrestore(&vhci->lock, flags);
+		kfree(priv);
 		return -ENODEV;
 	}
 	spin_unlock(&vdev->ud.lock);
@@ -829,7 +830,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	}
 
 out:
-	vhci_tx_urb(urb, vdev);
+	vhci_tx_urb(urb, vdev, priv);
 	spin_unlock_irqrestore(&vhci->lock, flags);
 
 	return 0;
@@ -837,6 +838,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 no_need_xmit:
 	usb_hcd_unlink_urb_from_ep(hcd, urb);
 no_need_unlink:
+	kfree(priv);
 	if (!ret) {
 		/* usb_hcd_giveback_urb() should be called with
 		 * irqs disabled
@@ -956,10 +958,18 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 		/* setup CMD_UNLINK pdu */
 		unlink = kzalloc_obj(struct vhci_unlink, GFP_ATOMIC);
 		if (!unlink) {
+			/*
+			 * Leave urb->unlinked set and return 0 so callers know
+			 * the unlink was accepted and usb_hcd_flush_endpoint()
+			 * does not busy-spin retrying unlink1(); the shutdown
+			 * triggered below (assuming usbip_event_add() can
+			 * allocate its event node) will drain priv_rx and give
+			 * back the URB.
+			 */
 			spin_unlock(&vdev->priv_lock);
 			spin_unlock_irqrestore(&vhci->lock, flags);
 			usbip_event_add(&vdev->ud, VDEV_EVENT_ERROR_MALLOC);
-			return -ENOMEM;
+			return 0;
 		}
 
 		unlink->seqnum = atomic_inc_return(&vhci_hcd->seqnum);
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH 6/6] usbip: vhci_hcd: reject URBs from stale usb_device after port re-attach
  2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
                   ` (4 preceding siblings ...)
  2026-10-21 15:07 ` [PATCH 5/6] usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM Raul E Rangel
@ 2026-10-21 15:07 ` Raul E Rangel
  5 siblings, 0 replies; 7+ messages in thread
From: Raul E Rangel @ 2026-10-21 15:07 UTC (permalink / raw)
  To: Valentina Manea, Shuah Khan, Greg Kroah-Hartman
  Cc: Hongren Zheng, linux-kernel, linux-usb, Raul E Rangel, stable,
	Brian G. Merrell

After vhci_shutdown_connection() runs, event_handler() immediately
calls vhci_device_reset(), setting vdev->ud.status to VDEV_ST_NULL and
clearing vdev->udev before usb_hub_wq finishes running usb_disconnect()
on the detached usb_device.

If attach_store() attaches a new connection to the same rhport before
usb_hub_wq disconnects the old usb_device, vdev->ud.tcp_socket becomes
non-NULL and vdev->ud.status becomes VDEV_ST_NOTASSIGNED. Because
vhci_urb_enqueue() maps urb->dev->portnum to vdev without verifying that
urb->dev matches vdev->udev, URBs submitted on the old usb_device pass
the connection state checks and get sent to the new remote device (or,
if a concurrent USBDEVFS_RESET resets the old usb_device to address 0,
overwrite vdev->udev).

Serialize vdev->udev clearing in vhci_device_reset() under vhci->lock
and ud->lock (dropping old usb_device and socket references outside the
spinlocks in both vhci_device_reset() and vhci_urb_enqueue()), and
reject URBs in vhci_urb_enqueue() when urb->dev != vdev->udev and either
usb_pipedevice(urb->pipe) != 0 or urb->dev is already registered
(device_is_registered(&urb->dev->dev)). During initial enumeration and
PORT_INIT_TRIES retries in hub_port_connect(), each candidate usb_device
is unregistered until usb_new_device() runs after hub_port_init() has
assigned vdev->udev, whereas an already-registered usb_device resetting
via usb_reset_device() is only valid when urb->dev == vdev->udev.

Fixes: 04679b3489e0 ("Staging: USB/IP: add client driver")
Cc: stable@vger.kernel.org
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
---

 drivers/usb/usbip/vhci_hcd.c | 44 +++++++++++++++++++++++++-----------
 1 file changed, 31 insertions(+), 13 deletions(-)

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 27dba51392b7..e8ff63c2276f 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -693,6 +693,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd);
 	struct vhci *vhci = vhci_hcd->vhci;
 	struct device *dev = &urb->dev->dev;
+	struct usb_device *old = NULL;
 	u8 portnum = urb->dev->portnum;
 	int ret = 0;
 	struct vhci_device *vdev;
@@ -737,6 +738,17 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 		kfree(priv);
 		return -ENODEV;
 	}
+	if ((usb_pipedevice(urb->pipe) != 0 ||
+	     device_is_registered(&urb->dev->dev)) &&
+	    urb->dev != vdev->udev) {
+		dev_err_ratelimited(dev,
+				    "enqueue from stale usb_device on port %d\n",
+				    vdev->rhport);
+		spin_unlock(&vdev->ud.lock);
+		spin_unlock_irqrestore(&vhci->lock, flags);
+		kfree(priv);
+		return -ENODEV;
+	}
 	spin_unlock(&vdev->ud.lock);
 
 	ret = usb_hcd_link_urb_to_ep(hcd, urb);
@@ -753,7 +765,6 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 	 *
 	 */
 	if (usb_pipedevice(urb->pipe) == 0) {
-		struct usb_device *old;
 		__u8 type = usb_pipetype(urb->pipe);
 		struct usb_ctrlrequest *ctrlreq =
 			(struct usb_ctrlrequest *) urb->setup_packet;
@@ -764,13 +775,13 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 			goto no_need_xmit;
 		}
 
-		old = vdev->udev;
 		switch (ctrlreq->bRequest) {
 		case USB_REQ_SET_ADDRESS:
 			/* set_address may come when a device is reset */
 			dev_info(dev, "SetAddress Request (%d) to port %d\n",
 				 ctrlreq->wValue, vdev->rhport);
 
+			old = vdev->udev;
 			vdev->udev = usb_get_dev(urb->dev);
 			/*
 			 * NOTE: A similar operation has been done via
@@ -783,7 +794,6 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 			 * should be dropped.
 			 */
 			dev_pm_syscore_device(&vdev->udev->dev, true);
-			usb_put_dev(old);
 
 			spin_lock(&vdev->ud.lock);
 			vdev->ud.status = VDEV_ST_USED;
@@ -802,6 +812,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 				usbip_dbg_vhci_hc(
 					"Not yet?:Get_Descriptor to device 0 (get max pipe size)\n");
 
+			old = vdev->udev;
 			vdev->udev = usb_get_dev(urb->dev);
 			/*
 			 * Set syscore PM flag for the virtually attached
@@ -814,7 +825,6 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 			 * on disconnect.
 			 */
 			dev_pm_syscore_device(&vdev->udev->dev, true);
-			usb_put_dev(old);
 			goto out;
 
 		default:
@@ -832,6 +842,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 out:
 	vhci_tx_urb(urb, vdev, priv);
 	spin_unlock_irqrestore(&vhci->lock, flags);
+	usb_put_dev(old);
 
 	return 0;
 
@@ -848,6 +859,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 		spin_lock(&vhci->lock);
 	}
 	spin_unlock_irqrestore(&vhci->lock, flags);
+	usb_put_dev(old);
 	return ret;
 }
 
@@ -1156,25 +1168,31 @@ static void vhci_shutdown_connection(struct usbip_device *ud)
 static void vhci_device_reset(struct usbip_device *ud)
 {
 	struct vhci_device *vdev = container_of(ud, struct vhci_device, ud);
-	struct usb_device *old = vdev->udev;
+	struct vhci *vhci = vdev_to_vhci_hcd(vdev)->vhci;
+	struct usb_device *old;
+	struct socket *socket;
 	unsigned long flags;
 
-	spin_lock_irqsave(&ud->lock, flags);
+	spin_lock_irqsave(&vhci->lock, flags);
+	spin_lock(&ud->lock);
 
 	vdev->speed  = 0;
 	vdev->devid  = 0;
 
+	old = vdev->udev;
 	vdev->udev = NULL;
-	usb_put_dev(old);
 
-	if (ud->tcp_socket) {
-		sockfd_put(ud->tcp_socket);
-		ud->tcp_socket = NULL;
-		ud->sockfd = -1;
-	}
+	socket = ud->tcp_socket;
+	ud->tcp_socket = NULL;
+	ud->sockfd = -1;
 	ud->status = VDEV_ST_NULL;
 
-	spin_unlock_irqrestore(&ud->lock, flags);
+	spin_unlock(&ud->lock);
+	spin_unlock_irqrestore(&vhci->lock, flags);
+
+	usb_put_dev(old);
+	if (socket)
+		sockfd_put(socket);
 }
 
 static void vhci_device_unusable(struct usbip_device *ud)
-- 
2.56.0.212.g129ede20d9-goog


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-21 15:08 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21 15:06 [PATCH 0/6] usbip: vhci_hcd: fix URB giveback leaks and disconnect races Raul E Rangel
2026-10-21 15:06 ` [PATCH 1/6] usbip: vhci_hcd: protect urb->hcpriv with priv_lock in vhci_urb_dequeue() Raul E Rangel
2026-10-21 15:06 ` [PATCH 2/6] usbip: vhci_hcd: complete unsent URBs locally " Raul E Rangel
2026-10-21 15:06 ` [PATCH 3/6] usbip: vhci_hcd: stop enqueue and drain all pending URBs on disconnect Raul E Rangel
2026-10-21 15:06 ` [PATCH 4/6] usbip: vhci_hcd: clear URB_DMA_MAP_SG on all giveback paths Raul E Rangel
2026-10-21 15:07 ` [PATCH 5/6] usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM Raul E Rangel
2026-10-21 15:07 ` [PATCH 6/6] usbip: vhci_hcd: reject URBs from stale usb_device after port re-attach Raul E Rangel

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®