usb: gadget: dummy_hcd: prevent fifo_req reuse during giveback

dummy_hcd embeds a single shared usb_request (dum->fifo_req) that the
"emulated single-request FIFO" fast-path in dummy_queue() reuses for
small IN transfers: it copies the caller's request into it
(req->req = *_req) and queues it, treating list_empty(&fifo_req.queue)
as "the slot is free".

The completion side (dummy_timer/transfer/nuke/dummy_dequeue) follows
the standard pattern: list_del_init(&req->queue) unlinks the request,
then the lock is dropped and usb_gadget_giveback_request() invokes
req->complete().  But list_del_init() makes fifo_req.queue look empty
*before* the completion callback returns, so a concurrent dummy_queue()
on another CPU sees the slot as free, reuses fifo_req and runs
req->req = *_req -- overwriting req->complete while dummy_timer is
mid-calling it.  The indirect call then jumps to a clobbered pointer,
causing a general protection fault / page fault in dummy_timer
(syzkaller extid faf3a6cf579fc65591ca).  The clobbering write is an
in-bounds memcpy on a live shared object, so KASAN cannot flag it.

Add a fifo_req_busy bit covering the shared request's whole lifetime:
set it in dummy_queue() when the FIFO fast-path takes fifo_req (making
it the fast-path guard, replacing the list_empty(&fifo_req.queue)
test), and clear it after the completion callback has returned, via a
dummy_giveback() helper used at all four gadget-request giveback
sites.  The shared slot can no longer be reused until its completion
callback has finished.

Reported-by: syzbot+faf3a6cf579fc65591ca@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=faf3a6cf579fc65591ca
Fixes: 1da177e4c3 ("Linux-2.6.12-rc2")
Cc: stable <stable@kernel.org>
Signed-off-by: Jinchao Wang <wangjinchao600@gmail.com>
Reviewed-by: Alan Stern <stern@rowland.harvard.edu>
Link: https://patch.msgid.link/5db8bba5b3499a86cd2e776f9918126b68b2508b.1784198306.git.wangjinchao600@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Jinchao Wang 2026-07-16 06:42:17 -04:00 committed by Greg Kroah-Hartman
parent fc3afb5728
commit d5e5cd3654

View File

@ -278,6 +278,7 @@ struct dummy {
unsigned ints_enabled:1;
unsigned udc_suspended:1;
unsigned pullup:1;
unsigned fifo_req_busy:1;
/*
* HOST side support
@ -329,6 +330,26 @@ static inline struct dummy *gadget_dev_to_dummy(struct device *dev)
/* DEVICE/GADGET SIDE UTILITY ROUTINES */
/*
* Give back a gadget request with dum->lock dropped around the callback.
* If @req is the shared fifo_req, clear fifo_req_busy afterward: the flag
* was set in dummy_queue() when the shared request was taken and must stay
* set until its completion callback has returned; list_del_init() alone
* makes the request look idle while the callback is still running.
* Caller holds dum->lock and has already done list_del_init() + status.
*/
static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep,
struct dummy_request *req)
{
bool fifo = req == &dum->fifo_req;
spin_unlock(&dum->lock);
usb_gadget_giveback_request(_ep, &req->req);
spin_lock(&dum->lock);
if (fifo)
dum->fifo_req_busy = 0;
}
/* called with spinlock held */
static void nuke(struct dummy *dum, struct dummy_ep *ep)
{
@ -339,9 +360,7 @@ static void nuke(struct dummy *dum, struct dummy_ep *ep)
list_del_init(&req->queue);
req->req.status = -ESHUTDOWN;
spin_unlock(&dum->lock);
usb_gadget_giveback_request(&ep->ep, &req->req);
spin_lock(&dum->lock);
dummy_giveback(dum, &ep->ep, req);
}
}
@ -728,10 +747,11 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req,
/* implement an emulated single-request FIFO */
if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) &&
list_empty(&dum->fifo_req.queue) &&
!dum->fifo_req_busy &&
list_empty(&ep->queue) &&
_req->length <= FIFO_SIZE) {
req = &dum->fifo_req;
dum->fifo_req_busy = 1;
req->req = *_req;
req->req.buf = dum->fifo_buf;
memcpy(dum->fifo_buf, _req->buf, _req->length);
@ -785,9 +805,7 @@ static int dummy_dequeue(struct usb_ep *_ep, struct usb_request *_req)
dev_dbg(udc_dev(dum),
"dequeued req %p from %s, len %d buf %p\n",
req, _ep->name, _req->length, _req->buf);
spin_unlock(&dum->lock);
usb_gadget_giveback_request(_ep, _req);
spin_lock(&dum->lock);
dummy_giveback(dum, _ep, req);
}
spin_unlock_irqrestore(&dum->lock, flags);
return retval;
@ -1523,9 +1541,7 @@ static int transfer(struct dummy_hcd *dum_hcd, struct urb *urb,
if (req->req.status != -EINPROGRESS) {
list_del_init(&req->queue);
spin_unlock(&dum->lock);
usb_gadget_giveback_request(&ep->ep, &req->req);
spin_lock(&dum->lock);
dummy_giveback(dum, &ep->ep, req);
/* requests might have been unlinked... */
rescan = 1;
@ -1910,9 +1926,7 @@ static enum hrtimer_restart dummy_timer(struct hrtimer *t)
dev_dbg(udc_dev(dum), "stale req = %p\n",
req);
spin_unlock(&dum->lock);
usb_gadget_giveback_request(&ep->ep, &req->req);
spin_lock(&dum->lock);
dummy_giveback(dum, &ep->ep, req);
ep->already_seen = 0;
goto restart;
}