CVE-2026-63894
usb: gadget: f_fs: serialize DMABUF cancel against request completion
Description
In the Linux kernel, the following vulnerability has been resolved: usb: gadget: f_fs: serialize DMABUF cancel against request completion 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.
INFO
Published Date :
July 19, 2026, 4:17 p.m.
Last Modified :
July 27, 2026, 5:44 p.m.
Remotely Exploit :
No
Source :
416baaa9-dc9f-4396-8d5f-8c081fb06d67
CVSS Scores
| Score | Version | Severity | Vector | Exploitability Score | Impact Score | Source |
|---|---|---|---|---|---|---|
| CVSS 3.1 | HIGH | 416baaa9-dc9f-4396-8d5f-8c081fb06d67 |
Solution
- Move request freeing into cleanup work.
- Clear pointer before freeing.
- Acquire lock during cleanup.
- Update device drivers.
References to Advisories, Solutions, and Tools
Here, you will find a curated list of external links that provide in-depth
information, practical solutions, and valuable tools related to
CVE-2026-63894.
CWE - Common Weakness Enumeration
While CVE identifies
specific instances of vulnerabilities, CWE categorizes the common flaws or
weaknesses that can lead to vulnerabilities. CVE-2026-63894 is
associated with the following CWEs:
Common Attack Pattern Enumeration and Classification (CAPEC)
Common Attack Pattern Enumeration and Classification
(CAPEC)
stores attack patterns, which are descriptions of the common attributes and
approaches employed by adversaries to exploit the CVE-2026-63894
weaknesses.
We scan GitHub repositories to detect new proof-of-concept exploits. Following list is a collection of public exploits and proof-of-concepts, which have been published on GitHub (sorted by the most recently updated).
Results are limited to the first 15 repositories due to potential performance issues.
The following list is the news that have been mention
CVE-2026-63894 vulnerability anywhere in the article.
The following table lists the changes that have been made to the
CVE-2026-63894 vulnerability over time.
Vulnerability history details can be useful for understanding the evolution of a vulnerability, and for identifying the most recent changes that may impact the vulnerability's severity, exploitability, or other characteristics.
-
CVE Modified by 416baaa9-dc9f-4396-8d5f-8c081fb06d67
Jul. 20, 2026
Action Type Old Value New Value Added CVSS V3.1 AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H -
New CVE Received by 416baaa9-dc9f-4396-8d5f-8c081fb06d67
Jul. 19, 2026
Action Type Old Value New Value Added Affected [{'repo': 'https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git', 'vendor': 'Linux', 'product': 'Linux', 'versions': [{'status': 'affected', 'version': '7b07a2a7ca02a20124b552be96c5a56910795488', 'lessThan': 'c872d8a065b3b499ce4c3ad168b5d34b68524f66', 'versionType': 'git'}, {'status': 'affected', 'version': '7b07a2a7ca02a20124b552be96c5a56910795488', 'lessThan': 'c7d421123b98d5e9c1c84bd9957aba36f1cbb4ca', 'versionType': 'git'}, {'status': 'affected', 'version': '7b07a2a7ca02a20124b552be96c5a56910795488', 'lessThan': '552dae28dbeb5f7c4fafcda43962dc46569f58a0', 'versionType': 'git'}, {'status': 'affected', 'version': '7b07a2a7ca02a20124b552be96c5a56910795488', 'lessThan': '2796646f6d892c1eb6818c7ca41fdfa12568e8d1', 'versionType': 'git'}], 'programFiles': ['drivers/usb/gadget/function/f_fs.c'], 'defaultStatus': 'unaffected'}, {'repo': 'https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git', 'vendor': 'Linux', 'product': 'Linux', 'versions': [{'status': 'affected', 'version': '6.9'}, {'status': 'unaffected', 'version': '0', 'lessThan': '6.9', 'versionType': 'semver'}, {'status': 'unaffected', 'version': '6.12.93', 'versionType': 'semver', 'lessThanOrEqual': '6.12.*'}, {'status': 'unaffected', 'version': '6.18.35', 'versionType': 'semver', 'lessThanOrEqual': '6.18.*'}, {'status': 'unaffected', 'version': '7.0.12', 'versionType': 'semver', 'lessThanOrEqual': '7.0.*'}, {'status': 'unaffected', 'version': '7.1', 'versionType': 'original_commit_for_fix', 'lessThanOrEqual': '*'}], 'programFiles': ['drivers/usb/gadget/function/f_fs.c'], 'defaultStatus': 'affected'}] Added Description In the Linux kernel, the following vulnerability has been resolved: usb: gadget: f_fs: serialize DMABUF cancel against request completion 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. Added Reference https://git.kernel.org/stable/c/2796646f6d892c1eb6818c7ca41fdfa12568e8d1 Added Reference https://git.kernel.org/stable/c/552dae28dbeb5f7c4fafcda43962dc46569f58a0 Added Reference https://git.kernel.org/stable/c/c7d421123b98d5e9c1c84bd9957aba36f1cbb4ca Added Reference https://git.kernel.org/stable/c/c872d8a065b3b499ce4c3ad168b5d34b68524f66