Re: [BUG] usb: gadgetfs: KASAN null-ptr-deref and intermittent UAF in ep_aio_cancel()
Minseo Kim <[email protected]> Fri, 31 Jul 2026 00:55:03 +0900
| Newsgroups | org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAFmvuTVYt2bVaqcyJVUSvNZfss6yzVdPbA5Wk2dtr89w2tz+5w@mail.gmail.com> |
Hi Alan, Sorry for the delayed response, and thank you for following up. > What is the cancel function supposed to do if it is called after the I/O > has completed? Should it return an error code? My current view is yes. If "completed" means that AIO-core completion has removed the iocb from ctx->active_reqs, a later cancellation should fail because the request is no longer cancellable. It should not invoke ep_aio_cancel(), touch the completed request or its private state, or produce another completion event. The current implementation returns -EINVAL in this case, although the exact userspace error to use is a separate AIO-core/API question. The distinction between AIO-core completion and completion of the lower-level USB request is important here. If the USB request has completed but the iocb is still present in ctx->active_reqs, ep_aio_cancel() may still be called. In that narrower interval, the completion-side flow already in progress, including ep_user_copy_worker() where applicable, should remain the sole source of the resulting AIO completion. The cancellation and completion paths must coordinate any cleanup so that the GadgetFS private state and USB request are each released exactly once. If usb_ep_dequeue() reports that the request is no longer active, ep_aio_cancel() should propagate the negative result without initiating a separate completion. In the current implementation, io_cancel() still removes the iocb from ctx->active_reqs as part of its own bookkeeping. Conversely, if cancellation reaches an active USB request first and usb_ep_dequeue() succeeds, the request is returned through the USB completion/giveback path, which should remain the source of the resulting AIO completion. The cancellation and completion paths must still coordinate cleanup so that the GadgetFS private state and USB request are each released exactly once; ep_aio_cancel() should not independently generate another completion of the same iocb. The behavior I observed is consistent with this exactly-once completion and teardown requirement. After AIO-core completion, cancel attempts made both before and after io_getevents() consumed the published event returned -EINVAL and did not change or duplicate the event. In a deliberately widened lower-level completion window, dummy_hcd returned -EINVAL because the USB request was already inactive, and the normal AIO completion remained the only event. Your expectation about partial transfers also matched the observed behavior: in both the PWRITE and PREADV checks, after a 512-byte partial transfer, the single AIO completion event reported res=512 despite a negative USB completion status. For a fix, I think the important invariant is exactly-once completion and teardown: each GadgetFS private object and USB request must be released exactly once, the iocb must be completed exactly once, and no path may dereference any of these objects after its lifetime has ended. One additional point from the v2 results may be relevant to the approach you outlined: delaying kiocb_set_cancel_fn() until after successful submission. Moving it after a successful usb_ep_queue(), by itself, may not be sufficient: once the request has been queued successfully, its completion can run before cancellation is registered. The revised-patch UAF demonstrated this completion-before-registration ordering. The registration and completion paths therefore appear to need an additional ordering or lifetime mechanism. There is also a locking constraint: io_cancel() and free_ioctx_users() can invoke the cancel callback while holding ctx->ctx_lock. A synchronous dequeue giveback must therefore not cause aio_complete_rw() to reacquire that lock while the iocb is still linked and the original caller still holds ctx->ctx_lock. I hope this answers the remaining semantic question and clarifies the constraints that seem relevant to a revised fix. Best regards, Minseo Kim 2026년 7월 30일 (목) 오전 12:31, Alan Stern <[email protected]>님이 작성: > > On Mon, Jul 20, 2026 at 10:18:33PM -0400, Alan Stern wrote: > > I've got some ideas on how to fix these problems, but we should discuss > > a few things first. > > Minseo, any responses to these questions? > > > The real problem is that I don't have a clear idea of how the kernel's > > aio implementation is meant to work, and it isn't documented at all. So > > there are a lot of questions about how the cancel function is supposed > > to interact with the normal I/O pathways. > > > > For instance, what is the cancel function supposed to do if it is called > > before the submission function (ep_aio() in this case) has returned, and > > the submission function eventually returns something other than > > -EIOCBQUEUED (i.e., submission failed and no I/O was started)? Or if it > > is called before the submission function has started the actual I/O > > operation, so there isn't anything to cancel yet? > > Never mind this question; I can avoid the issue by not calling > kiocb_set_cancel_fn() until after the request has been sucessfully > submitted. > > > What is the cancel function supposed to do if it is called after the I/O > > has completed? Should it return an error code? > > > > What is supposed to happen if the I/O is cancelled and then completes > > with no errors? > > Presumably nothing special should happen in this case. It should look > more or less the same as in the previous question. > > > How does the system handle a partial transfer, where the I/O was > > cancelled part way through? How does the user learn how much data was > > transferred before the I/O was cancelled? > > Presumably the second argument to ki_complete() contains this > information. > > This leaves only one question for you to answer. :-) > > Alan Stern