[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