Instituto Nacional de ciberseguridad. Sección Incibe
Instituto Nacional de Ciberseguridad. Sección INCIBE-CERT

CVE-2026-63894

Gravedad CVSS v3.1:
ALTA
Tipo:
No Disponible / Otro tipo
Fecha de publicación:
19/07/2026
Última modificación:
20/07/2026

Descripción

*** Pendiente de traducción *** In the Linux kernel, the following vulnerability has been resolved:<br /> <br /> usb: gadget: f_fs: serialize DMABUF cancel against request completion<br /> <br /> ffs_epfile_dmabuf_io_complete() calls usb_ep_free_request() on the<br /> completed request but leaves priv-&gt;req, the back-pointer that<br /> ffs_dmabuf_transfer() set on submission, pointing at the freed<br /> memory. A later FUNCTIONFS_DMABUF_DETACH ioctl or<br /> ffs_epfile_release() on the close path still sees priv-&gt;req<br /> non-NULL under ffs-&gt;eps_lock:<br /> <br /> if (priv-&gt;ep &amp;&amp; priv-&gt;req)<br /> usb_ep_dequeue(priv-&gt;ep, priv-&gt;req);<br /> <br /> so usb_ep_dequeue() is called on a freed usb_request.<br /> <br /> On dummy_hcd the dequeue path only walks a live queue and<br /> pointer-compares, so the freed pointer reads without faulting and<br /> KASAN requires an explicit check at the FunctionFS call site to<br /> surface the use-after-free. On SG-capable in-tree UDCs the<br /> dequeue path dereferences the supplied request immediately:<br /> <br /> * chipidea&amp;#39;s ep_dequeue() does<br /> container_of(req, struct ci_hw_req, req) and reads<br /> hwreq-&gt;req.status before acquiring its own lock.<br /> * cdnsp&amp;#39;s cdnsp_gadget_ep_dequeue() reads request-&gt;status first.<br /> <br /> The narrower option of clearing priv-&gt;req via cmpxchg() in the<br /> completion does not close the race: the completion runs without<br /> eps_lock, so a cancel path holding eps_lock can still observe<br /> priv-&gt;req non-NULL, race a concurrent completion that clears and<br /> frees, and pass the freed pointer to usb_ep_dequeue(). A slightly<br /> longer fix that moves the free into the cleanup work is needed.<br /> <br /> Same class of lifetime race as the recent usbip-vudc timer fix [1].<br /> <br /> Take eps_lock in the sole place that mutates priv-&gt;req from the<br /> callback direction by moving usb_ep_free_request() out of the<br /> completion into ffs_dmabuf_cleanup(), the existing work handler<br /> scheduled by ffs_dmabuf_signal_done() on<br /> ffs-&gt;io_completion_wq. Clear priv-&gt;req there under eps_lock<br /> before freeing, and only clear if priv-&gt;req still names our<br /> request (a subsequent ffs_dmabuf_transfer() on the same<br /> attachment may have queued a new one).<br /> <br /> This keeps the existing dummy_hcd sync-dequeue invariant: the<br /> completion callback is still invoked by the UDC without<br /> eps_lock held (dummy_hcd drops its own lock before calling the<br /> callback), and the callback now takes no f_fs lock at all.<br /> Serialization against the cancel path happens in cleanup, which<br /> runs from the workqueue with no f_fs lock held on entry.<br /> <br /> The priv ref count protects the containing ffs_dmabuf_priv:<br /> ffs_dmabuf_transfer() takes a ref via ffs_dmabuf_get(), cleanup<br /> drops it via ffs_dmabuf_put(), so priv stays live for the<br /> cleanup even after the cancel path&amp;#39;s list_del + ffs_dmabuf_put.<br /> <br /> The ffs_dmabuf_transfer() error path no longer frees usb_req<br /> inline: fence-&gt;req and fence-&gt;ep are set before usb_ep_queue(),<br /> so ffs_dmabuf_cleanup() (scheduled by the error-path<br /> ffs_dmabuf_signal_done()) owns the free regardless of whether<br /> the queue succeeded.<br /> <br /> Reproduced under KASAN on both detach and close paths against<br /> dummy_hcd with an observability hook<br /> (kasan_check_byte(priv-&gt;req) immediately before usb_ep_dequeue)<br /> at the two FunctionFS cancel sites to surface the stale-pointer<br /> access; the hook is not part of this patch. The KASAN<br /> allocator / free stacks in the captured splats identify the<br /> same request: alloc in dummy_alloc_request, free in<br /> dummy_timer, fault reached from ffs_epfile_release (close) and<br /> from the FUNCTIONFS_DMABUF_DETACH ioctl (detach). With the<br /> patch applied, both paths are silent under the same hook.<br /> <br /> The bug is reached from the FunctionFS device node, which in<br /> real deployments is owned by the privileged gadget daemon<br /> (adbd, UMS, composite gadget services, etc.); it is not<br /> reachable from unprivileged userspace or from a USB host on the<br /> cable. FunctionFS mounts default to GLOBAL_ROOT_UID, but the<br /> filesystem supports uid=, gid=, and fmode= delegation to a<br /> non-root gadget daemon, so on real deployments the attacker may<br /> be a less-privileged service rather than root.