CVE-2026-63894 in Linux
Summary
by MITRE • 07/19/2026
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.
Once again VulDB remains the best source for vulnerability data.
Analysis
by VulDB Data Team • 07/19/2026
The vulnerability resides within the Linux kernel's FunctionFS gadget driver, specifically in the handling of DMA buffer operations associated with USB gadget endpoints. This flaw manifests as a use-after-free condition that occurs during the asynchronous processing of DMA buffer transfers. The core issue stems from improper synchronization between the completion path of a DMA buffer operation and the cancellation paths that may occur concurrently. When a DMA buffer transfer completes, the function `ffs_epfile_dmabuf_io_complete()` invokes `usb_ep_free_request()` on the completed request object, yet it fails to clear the back-pointer `priv->req` which was established during submission by `ffs_dmabuf_transfer()`. This leaves a dangling pointer that continues to reference freed memory.
The race condition becomes exploitable when either a `FUNCTIONFS_DMABUF_DETACH` ioctl or an `ffs_epfile_release()` call occurs, both of which check if `priv->req` is non-NULL while holding the `ffs->eps_lock`. These paths then pass the stale pointer to `usb_ep_dequeue()`, resulting in a use-after-free scenario where the kernel attempts to access memory that has already been freed. This vulnerability is particularly concerning because it operates on the USB gadget subsystem, which can be accessed through privileged device drivers and services rather than directly from unprivileged user space.
The technical implementation of this flaw demonstrates a classic lifetime management race condition that aligns with CWE-415: Double Free and CWE-416: Use After Free. The problem is exacerbated by the fact that different USB controller implementations handle dequeuing differently, with dummy_hcd merely performing pointer comparisons while SG-capable UDCs like chipidea and cdnsp directly dereference request structures, making the use-after-free more likely to cause crashes or memory corruption. The issue is further complicated by the fact that the completion callback runs without holding `eps_lock`, creating a window where concurrent cancel operations can observe the dangling pointer before it gets cleared.
The fix implements a carefully orchestrated synchronization strategy that moves the responsibility for freeing USB requests from the completion callback into a dedicated cleanup work handler. This approach ensures that memory deallocation occurs in a controlled environment with proper locking, preventing the race condition between completion and cancellation paths. The solution involves scheduling cleanup work through `ffs_dmabuf_signal_done()` on `ffs->io_completion_wq` and performing the actual free operation under `eps_lock` after clearing `priv->req` only if it still points to the current request. This ensures that any subsequent operations on the freed memory will not occur, as the cleanup work is serialized and runs with appropriate locking.
The mitigation strategy maintains backward compatibility while providing proper synchronization semantics required by the USB gadget framework. It leverages existing reference counting mechanisms through `ffs_dmabuf_get()` and `ffs_dmabuf_put()` to ensure that the `ffs_dmabuf_priv` structure remains valid during cleanup even after the cancel path has removed it from lists. The error handling paths are also adjusted to defer request freeing until the cleanup work, ensuring consistent behavior regardless of whether the initial queue operation succeeded or failed.
This vulnerability is particularly relevant in environments where FunctionFS devices operate under privileged contexts such as gadget daemons (adbd, UMS, composite gadgets) that may have elevated privileges but not necessarily root access. The attack surface is limited to scenarios where a malicious service with appropriate privileges can manipulate FunctionFS device nodes, making it less accessible from regular user space but still significant for system security. The fix resolves the issue by ensuring proper lifecycle management of USB request objects within the FunctionFS gadget driver, preventing potential crashes or memory corruption that could affect the stability and integrity of the kernel's USB subsystem. This represents a targeted fix to address the specific race condition identified in the Linux kernel's USB gadget framework, consistent with similar fixes applied to other USB subsystem components like usbip-vudc timer fixes.