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

From: 김민서

Date: Thu Jul 02 2026 - 04:09:21 EST


Hi Alan,

Thank you for taking a look.

I do not want to over-specify the exact fix, but my tentative view from
the reproducer runs and code inspection is that the key invariant is that
struct kiocb_priv should either remain valid while it can still be reached
by ep_aio_cancel() for the same iocb, or it should be made unreachable from
the cancel path in a synchronized way.

In the immediate completion path I am looking at, ep_aio_complete()
appears to clear fields in priv and then free priv before iocb->private is
set to NULL and before iocb->ki_complete() runs. Meanwhile ep_aio_cancel()
reads iocb->private and immediately dereferences priv->epdata.

That seems to explain both symptoms I observed: a NULL-deref if
iocb->private is already NULL, and a UAF if cancellation observes a stale
non-NULL priv pointer.

I may be missing some ordering guarantee in the AIO core, but from the
reproducer this is the window that seems relevant. So I think a NULL check
in ep_aio_cancel() would likely handle the repeated NULL-deref symptom,
but may not address the stale non-NULL case. A safer direction might be to
ensure sufficient serialization or lifetime ordering around iocb->private /
struct kiocb_priv, so that either priv remains valid for the cancel
callback, or cancellation is already unreachable before priv is freed for
that iocb.

Best regards,
Minseo Kim

2026년 7월 1일 (수) 오전 11:13, Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>님이 작성:
>
> 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