CVE-2026-13880
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forservices/device/usb/usb_device_handle_impl.cc |
modified |
Files Changed
services/device/usb/usb_device_handle_impl.cc
Patch
From 17f51b58f066771cf579f57b790a7267b083968b Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Thu, 28 May 2026 11:03:36 -0700
Subject: [PATCH] usb: Fix thread-safety in macOS SetConfiguration
SetConfiguration cleared claimed_interfaces_ on the main thread, causing
InterfaceClaimer destruction (and libusb_release_interface) on the main
thread. This could run concurrently with libusb_set_configuration on the
blocking thread, leading to undefined behavior and potential UAF.
This CL uses ReleaseSoon to release claimed interfaces on the blocking
thread instead, ensuring sequential execution with
SetConfigurationBlocking.
Bug: 499025880
Change-Id: If544f27259742c50b30896d7763aa8afad3dc190
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7877663
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637824}
---
diff --git a/services/device/usb/usb_device_handle_impl.cc b/services/device/usb/usb_device_handle_impl.cc
index 9b6c743..8a75062f 100644
--- a/services/device/usb/usb_device_handle_impl.cc
+++ b/services/device/usb/usb_device_handle_impl.cc
@@ -569,6 +569,14 @@
for (Transfer* transfer : transfers_) {
transfer->Cancel();
}
+
+ // Release all claimed interfaces on the blocking thread. This ensures
+ // that the final reference is released on the right thread and avoids
+ // concurrent calls to libusb_release_interface and libusb_set_configuration
+ // on the same device.
+ for (auto& map_entry : claimed_interfaces_) {
+ blocking_task_runner_->ReleaseSoon(FROM_HERE, std::move(map_entry.second));
+ }
claimed_interfaces_.clear();
blocking_task_runner_->PostTask(
Original Bug Report
macOS WebUSB: Potential UAF in browser process due to SetConfiguration thread-safety violation
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A race condition in WebUSB allows a potential Use-After-Free in the browser process on macOS. UsbDeviceHandleImpl::SetConfiguration destroys InterfaceClaimer objects synchronously on the main thread, violating libusb threading expectations. This races with concurrent operations on the blocking task runner, leading to a UAF of a system-allocated COM object.
Affected files:
services/device/usb/usb_device_handle_impl.ccthird_party/libusb/src/libusb/os/darwin_usb.cthird_party/libusb/src/libusb/core.c
Estimated timestamp from git blame: 2024-06-25
Technical Details
A potential Use-After-Free (UAF) vulnerability exists in the WebUSB implementation on macOS, which could allow a compromised renderer to escape the sandbox and achieve arbitrary code execution in the browser process.
Root Cause
The issue stems from a thread-safety violation in services/device/usb/usb_device_handle_impl.cc. The UsbDeviceHandleImpl::SetConfiguration method cancels pending transfers and then synchronously calls claimed_interfaces_.clear() on the main sequence.
When Transfer::Cancel() is called, it immediately drops its reference to the InterfaceClaimer by setting claimed_interface_ = nullptr. Subsequently, clearing the claimed_interfaces_ map drops the final reference to the InterfaceClaimer, causing its destructor ~InterfaceClaimer to run synchronously on the main sequence.
This violates the assumption that InterfaceClaimer destruction (and the underlying libusb_release_interface call) occurs on the blocking_task_runner_. While debug builds catch this via DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_), release builds compile this check out, allowing a blocking libusb call to execute on the main sequence.
Race Condition and Use-After-Free
A compromised renderer can trigger a race condition by concurrently invoking SetInterfaceAlternateSetting and SetConfiguration via Mojo IPC:
- Blocking Thread:
SetInterfaceAlternateSettingcorrectly posts a task to theblocking_task_runner_, which callslibusb_set_interface_alt_setting(third_party/libusb/src/libusb/core.c). - Lock Release: Inside
libusb_set_interface_alt_setting, the code acquires the device lock, performs validation, and then explicitly releases the lock before calling the OS-specific backend. - Darwin Backend (Blocking Thread): Execution enters the macOS backend
darwin_set_interface_altsetting(third_party/libusb/src/libusb/os/darwin_usb.c). It callsSetAlternateInterfaceon the COM proxy, triggering a synchronous Mach IPC call that blocks waiting for the USB device. - Main Sequence: Concurrently, the browser processes the
SetConfigurationIPC. As described above, it drops all references to theInterfaceClaimer, triggeringlibusb_release_interfaceon the main sequence. - Darwin Backend (Main Sequence): Because the lock was released in Step 2,
libusb_release_interfacere-acquires it and enters the backenddarwin_release_interface. This callsUSBInterfaceClose, which immediately aborts the pending operation on the blocking thread. It then callsRelease()on the interface COM proxy, freeing the underlying memory to the system malloc heap. - UAF (Blocking Thread): The aborted
SetAlternateInterfacecall wakes up the blocking thread, which then proceeds to unconditionally callget_endpoints(). Insideget_endpoints, it fetches the now-freed interface pointer and attempts a virtual function call (GetNumEndpoints), triggering the Use-After-Free.
Impact and Exploitability
Because the COM proxy is allocated by macOS IOKit/CoreFoundation using the system malloc, this memory is not protected by Chrome’s PartitionAlloc or MiraclePtr (BackupRefPtr).
An attacker exploiting this would follow these suggested steps:
- Obtain WebUSB access to a device (either via existing permission or a malicious device).
- Using a compromised renderer, send a Mojo IPC to claim an interface.
- Send
SetInterfaceAlternateSettingfollowed immediately bySetConfigurationto trigger the race. - During the race window (between
Release()on the main thread andGetNumEndpoints()on the blocking thread), perform a heap spray via other IPCs or WebUSB operations to overwrite the freed system malloc chunk with attacker-controlled data. - The virtual call in
GetNumEndpointsdereferences the attacker-controlled vtable, resulting in arbitrary code execution (RCE) in the browser process.
Note: These are potential steps; our tooling has not yet executed a live proof-of-concept.
Suggested Fix
Transfer::Cancel()should not immediately clearclaimed_interface_ = nullptr. The transfer should retain the reference until it is properly cleaned up inTransferCompleteviaDeleteSoonon the blocking thread.- In
UsbDeviceHandleImpl::SetConfiguration, instead of synchronously callingclaimed_interfaces_.clear(), move the interfaces to the blocking thread usingblocking_task_runner_->ReleaseSoon, mirroring the correct pattern used inUsbDeviceHandleImpl::CloseandUsbDeviceHandleImpl::ReleaseInterface.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.