[PATCH 5/6] usbip: vhci_hcd: allocate vhci_priv outside lock and handle ENOMEM

From: Raul E Rangel

Date: Wed Oct 21 2026 - 11:12:49 EST


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@xxxxxxxxxxxxxxx
Assisted-by: Gemini
Signed-off-by: Raul E Rangel <rrangel@xxxxxxxxxxxx>
---

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