Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in USB
DescriptionUse after free in USB
ComponentUSB
Bug ClassUAF
Tracker499025880
Fix commit17f51b58f066 (chromium/src) +8/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
for
services/device/usb/usb_device_handle_impl.cc
modified

Files Changed

  • services/device/usb/usb_device_handle_impl.cc
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(
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • third_party/libusb/src/libusb/os/darwin_usb.c
  • third_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:

  1. Blocking Thread: SetInterfaceAlternateSetting correctly posts a task to the blocking_task_runner_, which calls libusb_set_interface_alt_setting (third_party/libusb/src/libusb/core.c).
  2. 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.
  3. Darwin Backend (Blocking Thread): Execution enters the macOS backend darwin_set_interface_altsetting (third_party/libusb/src/libusb/os/darwin_usb.c). It calls SetAlternateInterface on the COM proxy, triggering a synchronous Mach IPC call that blocks waiting for the USB device.
  4. Main Sequence: Concurrently, the browser processes the SetConfiguration IPC. As described above, it drops all references to the InterfaceClaimer, triggering libusb_release_interface on the main sequence.
  5. Darwin Backend (Main Sequence): Because the lock was released in Step 2, libusb_release_interface re-acquires it and enters the backend darwin_release_interface. This calls USBInterfaceClose, which immediately aborts the pending operation on the blocking thread. It then calls Release() on the interface COM proxy, freeing the underlying memory to the system malloc heap.
  6. UAF (Blocking Thread): The aborted SetAlternateInterface call wakes up the blocking thread, which then proceeds to unconditionally call get_endpoints(). Inside get_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:

  1. Obtain WebUSB access to a device (either via existing permission or a malicious device).
  2. Using a compromised renderer, send a Mojo IPC to claim an interface.
  3. Send SetInterfaceAlternateSetting followed immediately by SetConfiguration to trigger the race.
  4. During the race window (between Release() on the main thread and GetNumEndpoints() 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.
  5. The virtual call in GetNumEndpoints dereferences 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

  1. Transfer::Cancel() should not immediately clear claimed_interface_ = nullptr. The transfer should retain the reference until it is properly cleaned up in TransferComplete via DeleteSoon on the blocking thread.
  2. In UsbDeviceHandleImpl::SetConfiguration, instead of synchronously calling claimed_interfaces_.clear(), move the interfaces to the blocking thread using blocking_task_runner_->ReleaseSoon, mirroring the correct pattern used in UsbDeviceHandleImpl::Close and UsbDeviceHandleImpl::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.

View on issue tracker