[PATCH 0/3] usb: core: fix three use-after-free windows in the USB anchor protocol

From: Hongyu Xie

Date: Wed Oct 21 2026 - 23:55:35 EST


Anchors are usually embedded in driver-private structures, and several
anchor API returns -- a non-zero usb_wait_anchor_empty_timeout(),
usb_kill_anchored_urbs(), usb_poison_anchored_urbs() and
usb_scuttle_anchored_urbs() -- license the caller to free that memory.
The wakeup protocol backing this license has three windows in which a
URB giveback or unanchor still touches the anchor after the caller has
been told it is unused:

1. The waiter's condition check and the giveback's suspend_wakeups
decrement plus wake_up() were not serialized, so the waiter could
return while a giveback was still in flight. Reported by syzbot
repeatedly since 2019; patch 1 references the two reports this
analysis is built on.

2. __usb_hcd_giveback_urb() captured urb->anchor at the top of the
function but incremented suspend_wakeups only after DMA unmapping
and the usbmon hooks. A concurrent unanchor could empty the anchor
with suspend_wakeups still zero, so the waiter returned and freed
the anchor underneath the pending increment. No syzbot report is
attributed to this interleaving; it was found by extending the
analysis of patch 1. Patch 2 folds the capture, the increment and
the unanchor into a single anchor->lock critical section.

3. usb_unanchor_urb() and the giveback read urb->anchor and only then
acquire anchor->lock; between the two the anchor can already have
been freed. This gap is as old as usb_unanchor_urb() itself and
cannot be closed with a reference count: the first dereference of
the freshly read pointer would have to be the increment itself.
Patch 3 therefore turns the wakeup protocol into a lifetime
protocol: the lockless anchor reads run in RCU read-side critical
sections, urb->anchor is published with rcu_assign_pointer(), and
every return that lets the caller free the anchor holds a
suspend_wakeups pin across one synchronize_rcu() first.

Patch 3 changes contracts, stated up front:

- usb_kill_anchored_urbs(), usb_poison_anchored_urbs() and
usb_scuttle_anchored_urbs() may now sleep even on an empty anchor.
All in-tree callers already sleep (they sit next to usb_kill_urb()
calls).

- usb_wait_anchor_empty_timeout() returns 1 on success instead of the
remaining jiffies, and a successful wait -- including one with a
zero timeout -- pays one RCU grace period.

- The cost is one grace period per successful teardown exit. On
x86-64 real hardware, 100 consecutive successful waits on an empty
anchor cost 3.1 us per call on the base kernel and 8.2 us with this
series, about +5 us per call. Callers that wait once per batch
(zd1211rw) or tear down several anchors at close (btusb) pay this
per anchor; it is small next to the URB I/O these paths bracket,
but it is the price of the free-after-return guarantee.

No fields are added to struct usb_anchor or struct urb, and no driver
changes are needed.

Testing:

- A QEMU/KASAN attack harness reproduced each window separately
(deterministic arming for the two giveback windows, a forced free
inside the read-to-lock gap for the third) and verified that each
patch closes its own window with zero reports under identical
pressure.

- The full stack also ran on real x86-64 hardware with KASAN kernels
built from the same base. The base kernel hits every probe: both
deterministic windows fire within 100 iterations (hundreds of probe
arm hits), and the natural-race variants hit within 2000 iterations
with no arming at all. The kernel with all three patches runs the
identical workload with zero reports -- the attack still executes
there, its probes arm hundreds of times too, only the windows are
gone. The zero-timeout contract probe (empty anchor, timeout 0)
returns 1 as documented.

checkpatch: 0 errors, 0 warnings for all three patches. Patch 2 keeps
one CHECK for an extern prototype in a header, matching the prevailing
style of that header.

Signed-off-by: Hongyu Xie <xiehongyu1@xxxxxxxxxx>

Hongyu Xie (3):
usb: core: Fix use-after-free in anchor wakeup protocol
usb: core: Close anchor capture-to-increment race in URB giveback
usb: core: Close residual anchor read-to-lock window with RCU

drivers/usb/core/hcd.c | 10 +-
drivers/usb/core/urb.c | 303 +++++++++++++++++++++++++++++++++++------
drivers/usb/core/usb.h | 1 +
include/linux/usb.h | 7 +-
4 files changed, 277 insertions(+), 44 deletions(-)


base-commit: 22430ae5d90ab288b0ee2ad99ae941f4a666b694
--
2.32.0