In the Linux kernel, the following vulnerability has been resolved:
usb: gadget: f_fs: serialize DMABUF cancel against request completion
ffsepfiledmabufiocomplete() calls usbepfreerequest() on the completed request but leaves priv->req, the back-pointer that ffsdmabuftransfer() set on submission, pointing at the freed memory. A later FUNCTIONFSDMABUFDETACH ioctl or ffsepfilerelease() on the close path still sees priv->req non-NULL under ffs->epslock:
if (priv->ep && priv->req)
usb_ep_dequeue(priv->ep, priv->req);
so usbepdequeue() 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:
The narrower option of clearing priv->req via cmpxchg() in the completion does not close the race: the completion runs without epslock, so a cancel path holding epslock can still observe priv->req non-NULL, race a concurrent completion that clears and frees, and pass the freed pointer to usbepdequeue(). 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 epslock in the sole place that mutates priv->req from the callback direction by moving usbepfreerequest() out of the completion into ffsdmabufcleanup(), the existing work handler scheduled by ffsdmabufsignaldone() on ffs->iocompletionwq. Clear priv->req there under epslock before freeing, and only clear if priv->req still names our request (a subsequent ffsdmabuftransfer() on the same attachment may have queued a new one).
This keeps the existing dummyhcd sync-dequeue invariant: the completion callback is still invoked by the UDC without epslock held (dummyhcd drops its own lock before calling the callback), and the callback now takes no ffs 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 ffsdmabufpriv: ffsdmabuftransfer() takes a ref via ffsdmabufget(), cleanup drops it via ffsdmabufput(), so priv stays live for the cleanup even after the cancel path's listdel + ffsdmabuf_put.
The ffsdmabuftransfer() error path no longer frees usbreq inline: fence->req and fence->ep are set before usbepqueue(), so ffsdmabufcleanup() (scheduled by the error-path ffsdmabufsignaldone()) owns the free regardless of whether the queue succeeded.
Reproduced under KASAN on both detach and close paths against dummyhcd with an observability hook (kasancheckbyte(priv->req) immediately before usbepdequeue) 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 dummyallocrequest, free in dummytimer, fault reached from ffsepfilerelease (close) and from the FUNCTIONFSDMABUFDETACH 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 GLOBALROOTUID, 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.
{
"osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/63xxx/CVE-2026-63894.json",
"cna_assigner": "Linux"
}