Look up another CVE · open CVE-2026-63894 in the interactive tracker
CVE-2026-63894
usb: gadget: f_fs: serialize DMABUF cancel against request completion
Published 2026-07-19 (3 months ago) by the Linux kernel CNA. Subsystem drivers/usb. Debian urgency: unimportant.
What to do, per Debian release
Debian's own status for CVE-2026-63894 in every release this site tracks, and what each one means in practice. Every status below is the one the Debian Security Team publishes for this CVE. source
| Release | Status | Version | What this means |
|---|---|---|---|
| Debian 12bookworm | not affected | 6.1.190-1 | Nothing to do. bookworm never shipped the vulnerable code. |
| Debian 13trixie | fixed | 6.12.94-1 | Update the kernel and reboot. Fixed in 6.12.94-1. A running kernel keeps the old code until the machine restarts. |
| Debian 14forky | fixed | 7.0.12-1 | Update the kernel and reboot. Fixed in 7.0.12-1. A running kernel keeps the old code until the machine restarts. |
| Debian unstablesid | fixed | 7.0.12-1 | Update the kernel and reboot. Fixed in 7.0.12-1. A running kernel keeps the old code until the machine restarts. |
The vulnerability
What the bug is
ffs_epfile_dmabuf_io_complete() calls usb_ep_free_request() on the completed request but leaves priv->req, the back-pointer that ffs_dmabuf_transfer() set on submission, pointing at the freed memory. A later FUNCTIONFS_DMABUF_DETACH ioctl or ffs_epfile_release() on the close path still sees priv->req non-NULL under ffs->eps_lock: if (priv->ep && priv->req) usb_ep_dequeue(priv->ep, priv->req); so usb_ep_dequeue() is called on a freed usb_request. On dummy_hcd the dequeue path only walks a live queue and pointer-compares, so the freed pointer reads without faulting and KASAN requires an explicit check at the FunctionFS call site to surface the use-after-free. On SG-capable in-tree UDCs the dequeue path dereferences the supplied request immediately: * chipidea's ep_dequeue() does container_of(req, struct ci_hw_req, req) and reads hwreq->req.status before acquiring its own lock. * cdnsp's cdnsp_gadget_ep_dequeue() reads request->status first. The narrower option of clearing priv->req via cmpxchg() in the completion does not close the race: the completion runs without eps_lock, so a cancel path holding eps_lock can still observe priv->req non-NULL, race a concurrent completion that clears and frees, and pass the freed pointer to usb_ep_dequeue(). A slightly longer fix that moves the free into the cleanup work is needed. Same class of lifetime race as the recent usbip-vudc timer fix [1]. Take eps_lock in the sole place that mutates priv->req from the callback direction by moving usb_ep_free_request() out of the completion into ffs_dmabuf_cleanup(), the existing work handler scheduled by ffs_dmabuf_signal_done() on ffs->io_completion_wq. Clear priv->req there under eps_lock before freeing, and only clear if priv->req still names our request (a subsequent ffs_dmabuf_transfer() on the same attachment may have queued a new one). This keeps the existing dummy_hcd sync-dequeue invariant: the completion callback is still invoked by the UDC without eps_lock held (dummy_hcd drops its own lock before calling the callback), and the callback now takes no f_fs lock at all. Serialization against the cancel path happens in cleanup, which runs from the workqueue with no f_fs lock held on entry. The priv ref count protects the containing ffs_dmabuf_priv: ffs_dmabuf_transfer() takes a ref via ffs_dmabuf_get(), cleanup drops it via ffs_dmabuf_put(), so priv stays live for the cleanup even after the cancel path's list_del + ffs_dmabuf_put. The ffs_dmabuf_transfer() error path no longer frees usb_req inline: fence->req and fence->ep are set before usb_ep_queue(), so ffs_dmabuf_cleanup() (scheduled by the error-path ffs_dmabuf_signal_done()) owns the free regardless of whether the queue succeeded. Reproduced under KASAN on both detach and close paths against dummy_hcd with an observability hook (kasan_check_byte(priv->req) immediately before usb_ep_dequeue) at the two FunctionFS cancel sites to surface the stale-pointer access; the hook is not part of this patch. The KASAN allocator / free stacks in the captured splats identify the same request: alloc in dummy_alloc_request, free in dummy_timer, fault reached from ffs_epfile_release (close) and from the FUNCTIONFS_DMABUF_DETACH ioctl (detach). With the patch applied, both paths are silent under the same hook. The bug is reached from the FunctionFS device node, which in real deployments is owned by the privileged gadget daemon (adbd, UMS, composite gadget services, etc.); it is not reachable from unprivileged userspace or from a USB host on the cable. FunctionFS mounts default to GLOBAL_ROOT_UID, but the filesystem supports uid=, gid=, and fmode= delegation to a non-root gadget daemon, so on real deployments the attacker may be a less-privileged service rather than root.
Where it lives
The fix touches these files. If the subsystem is one you do not use, the practical exposure is lower, though the package is still the vulnerable one.
drivers/usb/gadget/function/f_fs.c
- What this is
- USB SUBSYSTEMMAINTAINERS section matched on drivers/usb/ · linux-usb@vger.kernel.org · Supported
- Kernel module
- usb_f_fs
Check it on the machine
grep -wE 'CONFIG_OF|CONFIG_PCI|CONFIG_USB|CONFIG_USB_F_FS|CONFIG_USB_GADGET|CONFIG_USB_PHY|CONFIG_USB_SUPPORT' /boot/config-$(uname -r)Reads the booted kernel's own configuration, on the machine and nowhere else. =y is built in, =m is a module, and an is not set line means the code is not there.
lsmod | grep -w usb_f_fsPrints a line if that module is loaded right now, and nothing if it is not. One that is not loaded can still be loaded later by anything that needs it.
Show whether Debian builds this, per release
Debian builds this, but not on every flavour. At least one release below builds the code on some of its kernel flavours and not on others, so the answer depends on the architecture and flavour installed.
- bookwormMnot built on amd64/amd64, amd64/cloud-amd64, amd64/rt/amd64, arm64/cloud-arm64, i386/686, i386/686-pae, i386/rt/686-pae, ppc64el/powerpc64le
- trixieMnot built on amd64/amd64, amd64/cloud-amd64, amd64/rt/amd64, arm64/cloud-arm64, armel/rpi, ppc64el/powerpc64le, ppc64el/powerpc64le-64k, s390x/s390x
- forky, sidMnot built on amd64/amd64, amd64/cloud-amd64, amd64/rt-amd64, arm64/cloud-arm64, loong64/loong64, ppc64el/powerpc64le, ppc64el/powerpc64le-64k, s390x/s390x
- M
- a module where it is built at all; some flavours do not build it
- Build switches
- CONFIG_OF, CONFIG_PCI, CONFIG_USB, CONFIG_USB_F_FS, CONFIG_USB_GADGET, CONFIG_USB_PHY, CONFIG_USB_SUPPORT
- Reads as
- all on means the file is compiled; any one off means it is not.
From the configuration Debian publishes for every kernel flavour it builds. It covers only the architectures Debian ships a linux-config package for, and nothing about a kernel you built yourself. Debian's own status above stays authoritative about the source package.MAINTAINERS kbuild Makefiles Debian linux-config packages
Signals
- Published
- 2026-07-19 (3 months ago)source
- CVSS
- 7.8 high
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:Hsource recompute it - Attack vector
- local (AV:L)source
- EPSS
- 0.17%, higher than 6.0% of all CVEssource
- CISA KEV
- not listedthe KEV catalogue
- Debian urgency
- unimportantsource
- Subsystem
- drivers/usb
Upstream fixes
The versions the CNA records as introducing and fixing this, and the commit for each. source
| Introduced | Fixed in | Commit |
|---|---|---|
| 6.9 | 6.12.93 | c872d8a065b3 |
| 6.9 | 6.18.35 | c7d421123b98 |
| 6.9 | 7.0.12 | 552dae28dbeb |
| 6.9 | 7.1 | 2796646f6d89 |
Why the CNA scored it this way
Quoted from the kernel CNA's own scoring file, word for word. A highlighted metric carries the most severe value CVSS v3.1 defines for it, which is what pushes the score up. source
- AV:LAttack vector, local
- Exploitation requires local access to FunctionFS endpoint device nodes via FUNCTIONFS_DMABUF_DETACH/FUNCTIONFS_DMABUF_TRANSFER ioctls or close(), not USB host traffic or physical cable manipulation; the fix commit explicitly states it is unreachable from a remote USB host.
- AC:LAttack complexity, low
- The attacker controls both sides of the race by concurrently issuing DMABUF transfer and detach/close ioctls from threads they own, and can retry until the completion/cancel window is hit.
- PR:LPrivileges required, low
- While mounts default to root, FunctionFS supports uid=/gid=/fmode= delegation to non-root gadget daemons (adbd, MTP, composite services), and the fix commit identifies a less-privileged local service as the realistic attacker on deployed systems.
- UI:NUser interaction, none
- No victim action is required; the attacker triggers the vulnerability through their own ioctl and close calls racing transfer completion.
- S:UScope, unchanged
- Impact is kernel memory corruption and privilege escalation within the host kernel security boundary, not a cross-authority escape such as VM escape or IOMMU bypass.
- C:HConfidentiality impact, high
- A use-after-free on usb_request is passed to usb_ep_dequeue(), and SG-capable in-tree UDCs (chipidea, cdnsp) dereference the freed request immediately, enabling arbitrary kernel memory reads.
- I:HIntegrity impact, high
- The UAF passes a dangling usb_request into UDC dequeue paths that perform container_of and structure field writes on the freed object, providing a standard heap corruption primitive for arbitrary write or code execution.
- A:HAvailability impact, high
- Passing a freed usb_request into usb_ep_dequeue() on real UDC hardware causes immediate invalid dereferences and kernel oops/panic, and UAF exploitation paths also enable denial of service.