Re: [BUG] usb: gadgetfs: KASAN null-ptr-deref and intermittent UAF in ep_aio_cancel()

From: Alan Stern

Date: Wed Sep 16 2026 - 15:55:21 EST


On Wed, Sep 16, 2026 at 11:00:00PM +0900, Minseo Kim wrote:
> Hi Alan,
>
> > Hmmm. Do you know where the unmount operation was getting stuck? Was
> > it the usb_gadget_unregister_driver() call inside dev_release()? I just
> > want to be sure about this.
>
> In these reruns, I found that the unmount task was blocked in
> synchronize_rcu_expedited(), called from namespace_unlock(), rather than
> in usb_gadget_unregister_driver().
>
> I reran the PWRITE callback tail test with both patches applied, using a
> resident helper whose main thread invoked umount2() directly. A monitor
> thread in the helper captured the blocked main thread's kernel stack. In
> three runs from fresh boots, the relevant frames were:
>
> synchronize_rcu_expedited
> namespace_unlock
> path_umount
> __x64_sys_umount
>
> In each of those three runs, the same umount2() call returned
> successfully after I released the callback gate.
>
> USB gadget request completion callbacks run with interrupts disabled, and
> interrupt-disabled regions act as implicit RCU read-side critical
> sections. The observed wait is therefore consistent with the diagnostic
> gate delaying completion of the expedited grace period.
>
> I also added diagnostic markers around dev_release() and
> usb_gadget_unregister_driver(). The test harness signaled the resident
> helper only after the reproducer had closed ep0. In five clean runs from
> fresh boots with these markers, both usb_gadget_unregister_driver() and
> dev_release() had returned before the resident helper invoked umount2(),
> while ep_aio_complete() was still held immediately after
> iocb->ki_complete().

Okay, that's not good. We can't expect to rely on an unintended side
effect. All the completion handlers should finish before
usb_gadget_unregister_driver() returns; we need to enforce that.

I believe this misbehavior is caused by an oversight in the dummy-hcd
driver. Can you repeat these tests with the patch below applied on top
of the other two? It should cause usb_gadget_unregister_driver() to
wait until ep_aio_complete() returns (the actual wait loop is in
dummy-hcd's dummy_udc_async_callbacks() routine).

Alan Stern


Index: usb-devel/drivers/usb/gadget/udc/dummy_hcd.c
===================================================================
--- usb-devel.orig/drivers/usb/gadget/udc/dummy_hcd.c
+++ usb-devel/drivers/usb/gadget/udc/dummy_hcd.c
@@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy
{
bool fifo = req == &dum->fifo_req;

+ ++dum->callback_usage;
spin_unlock(&dum->lock);
usb_gadget_giveback_request(_ep, &req->req);
spin_lock(&dum->lock);
+ --dum->callback_usage;
if (fifo)
dum->fifo_req_busy = 0;
}
@@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_e
req->req.complete = fifo_complete;

list_add_tail(&req->queue, &ep->queue);
+ ++dum->callback_usage;
spin_unlock(&dum->lock);
_req->actual = _req->length;
_req->status = 0;
usb_gadget_giveback_request(_ep, _req);
spin_lock(&dum->lock);
+ --dum->callback_usage;
} else
list_add_tail(&req->queue, &ep->queue);
spin_unlock_irqrestore(&dum->lock, flags);