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

From: 김민서

Date: Tue Jun 30 2026 - 19:09:01 EST


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.

Reproducer:

C reproducer:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/reproducers/repro_ndr_decoylock.c

Additional C reproducer used for the intermittent UAF observation:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/reproducers/repro_uaf_decoylock_107000_96000.c

Symbolized KASAN report:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/reports/clean_report_ndr_inline.txt

Additional symbolized UAF report:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/reports/clean_report_uaf_outline.txt

Kernel config:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/configs/kernel.config.v7.2-rc1-kasan-inline

Additional KASAN_OUTLINE config used for the intermittent UAF observation:
https://raw.githubusercontent.com/neck392/linux-kernel-bug-reports/main/gadgetfs_ep_aio_cancel_minimal_20260630/configs/kernel.config.v7.2-rc1-kasan-outline

Build:

gcc -O2 -Wall -Wextra -pthread -o repro_ndr_decoylock repro_ndr_decoylock.c
gcc -O2 -Wall -Wextra -pthread -o repro_uaf_decoylock_107000_96000
repro_uaf_decoylock_107000_96000.c

Key config options:

CONFIG_USB_GADGETFS=y
CONFIG_USB_DUMMY_HCD=y
CONFIG_AIO=y
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y

Runtime conditions:

x86_64 QEMU/KVM
dummy_hcd + gadgetfs; physical USB hardware is not required
kasan_multi_shot=1
slub_debug=FZPU

The reproducer uses root only for the privileged environment setup
(dummy_hcd-backed gadgetfs setup, mounting gadgetfs, writing the initial
gadget descriptors/configuration through the control endpoint, and
preparing the endpoint file permissions). It then drops to uid/gid 1000
before opening/using the endpoint file(s) and triggering the AIO
completion/cancellation race. The faulting task in the null-ptr-deref
report is uid 1000.

Brief KASAN excerpt:

Oops: general protection fault, probably for non-canonical address
0xdffffc0000000001
KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f]
CPU: 2 UID: 1000 PID: 358 Comm: gdecoylock Not tainted 7.2.0-rc1
RIP: 0010:ep_aio_cancel+0x47/0xf0 drivers/usb/gadget/legacy/inode.c:455

Call Trace:
__do_sys_io_cancel
__se_sys_io_cancel
__x64_sys_io_cancel
do_syscall_64
entry_SYSCALL_64_after_hwframe

If you fix this issue, please add the following tag to the commit:

Reported-by: Minseo Kim <neck3922@xxxxxxxxx>

If you need anything else, please let me know.

Best regards,
Minseo Kim