CVE-2026-63894

Source
https://cve.org/CVERecord?id=CVE-2026-63894
Import Source
https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-63894.json
JSON Data
https://api.osv.dev/v1/vulns/CVE-2026-63894
Downstream
Published
2026-07-19T14:55:04.882Z
Modified
2026-07-22T05:30:07.797793463Z
Severity
  • 7.8 (High) CVSS_V3 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H CVSS Calculator
Summary
usb: gadget: f_fs: serialize DMABUF cancel against request completion
Details

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:

  • chipidea's epdequeue() does containerof(req, struct cihwreq, req) and reads hwreq->req.status before acquiring its own lock.
  • cdnsp's cdnspgadgetep_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 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.

Database specific
{
    "osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/63xxx/CVE-2026-63894.json",
    "cna_assigner": "Linux"
}
References

Affected packages

Git / git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

Affected ranges

Type
GIT
Repo
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
Events
Introduced
7b07a2a7ca02a20124b552be96c5a56910795488
Fixed
c872d8a065b3b499ce4c3ad168b5d34b68524f66
Fixed
c7d421123b98d5e9c1c84bd9957aba36f1cbb4ca
Fixed
552dae28dbeb5f7c4fafcda43962dc46569f58a0
Fixed
2796646f6d892c1eb6818c7ca41fdfa12568e8d1

Database specific

source
"https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-63894.json"

Linux / Kernel

Package

Name
Kernel

Affected ranges

Type
ECOSYSTEM
Events
Introduced
6.9.0
Fixed
6.12.93
Type
ECOSYSTEM
Events
Introduced
6.13.0
Fixed
6.18.35
Type
ECOSYSTEM
Events
Introduced
6.19.0
Fixed
7.0.12

Database specific

source
"https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2026-63894.json"