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

From: Alan Stern

Date: Tue Jun 30 2026 - 22:14:42 EST


On Wed, Jul 01, 2026 at 08:08:34AM +0900, 김민서 wrote:
> Hello,
>
> I am reporting a USB gadgetfs AIO cancellation bug reproduced on upstream
> v7.2-rc1, commit dc59e4fea9d83f03bad6bddf3fa2e52491777482, with KASAN
> enabled. In my test environment, the reproducer repeatedly triggers a
> KASAN null-ptr-deref/general protection fault in ep_aio_cancel(). I also
> observed an intermittent slab-use-after-free at the same dereference site.
>
> Target file:
> drivers/usb/gadget/legacy/inode.c
> Subsystem: USB gadgetfs
> Git tree: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
> Git head: dc59e4fea9d83f03bad6bddf3fa2e52491777482
> Kernel release: v7.2-rc1
>
> Observed crash:
>
> KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f]
> RIP: ep_aio_cancel+0x47/0xf0 drivers/usb/gadget/legacy/inode.c:455
>
> Additional intermittent UAF observation:
>
> BUG: KASAN: slab-use-after-free in ep_aio_cancel+0x25/0x90
> drivers/usb/gadget/legacy/inode.c:455
>
> Relevant stack:
>
> ep_aio_cancel
> __do_sys_io_cancel
> __se_sys_io_cancel
> __x64_sys_io_cancel
> do_syscall_64
> entry_SYSCALL_64_after_hwframe
>
> Root cause analysis:
>
> The crash appears to involve a race between gadgetfs AIO completion and
> AIO cancellation.
>
> In drivers/usb/gadget/legacy/inode.c, ep_aio_cancel() does:
>
> struct kiocb_priv *priv = iocb->private;
> ...
> epdata = priv->epdata;
>
> At the same time, ep_aio_complete() can clear fields in the same private
> object and then free it on the completion path before setting
> iocb->private to NULL:
>
> priv->req = NULL;
> priv->epdata = NULL;
> ...
> kfree(req->buf);
> kfree(priv->to_free);
> kfree(priv);
> iocb->private = NULL;
>
> My current understanding is that completion and cancellation are not
> effectively serialized around this lifetime transition.
>
> If cancellation observes NULL after iocb->private has been cleared,
> ep_aio_cancel() dereferences priv->epdata through a NULL priv pointer and
> reports the null-ptr-deref.
>
> If cancellation observes the old non-NULL priv pointer after completion
> has freed it, or if completion frees priv after cancellation loads it but
> before cancellation reads priv->epdata, the same dereference can surface
> as a slab-use-after-free.

Great! You clearly put a lot of work into finding this problem. How do
you suggest we fix it?

Alan Stern