Critical watch CVSS 7.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
7.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebUSB
DescriptionUse after free in WebUSB
ComponentWebUSB
Sub-componentUSB
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker513167952
Fix commita74fd42d1afa (chromium/src) +51/-2
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-14 (per vendor advisory)
Fix landed2026-05-16 (public git)
Disclosed2026-06-30
Patch gap45 days — fix public in source before the advisory

Background

WebUSB
Chromium API letting a web page request access to and communicate with USB devices, backed by the device service process.
`endpoint_map_`
internal map in UsbDeviceHandleImpl keyed by endpoint address, resolving each endpoint to its owning interface for routing transfers.
libusb
cross-platform USB backend used on macOS whose interface-claiming logic resolves duplicate endpoint addresses by “first match”.
Endpoint Aliasing
a malicious device advertising the same endpoint address on two different interfaces, causing divergent endpoint ownership between Chromium and the backend.

Root Cause Analysis

USB device descriptors are fully attacker-controlled, and Chromium’s RefreshEndpointMap populated endpoint_map_ by walking interfaces in order of appearance and overwriting any existing entry for a duplicate endpoint address, so the last interface to declare an aliased endpoint won ownership. libusb, by contrast, resolves the same duplicate endpoint to the FIRST interface that claims it. When a device advertised identical endpoint addresses across two distinct interfaces, the two layers disagreed on which interface owns the endpoint. A transfer, claim, or release that Chromium routed via its (last-match) mapping then targeted an interface/endpoint that libusb had already freed or reassigned under its (first-match) view, causing libusb to dereference stale interface/endpoint state. This mapping divergence is the root cause: a use-after-free driven purely by inconsistent endpoint-to-interface bookkeeping, not by a memory error inside Chromium itself.

The fix aligns Chromium with libusb’s first-match semantics and refuses claims that would introduce an aliased endpoint.

Key insight
The bug is not a single freed pointer but a semantic mismatch: two independent components each maintain a correct-looking endpoint→interface map, yet disagree on ownership of a duplicated endpoint, so an operation valid under one map operates on freed state under the other. Consistency between the mappings, not the validity of either alone, is the security invariant.

Attack Path

  1. A malicious USB device (or one behind a compromised WebUSB origin) crafts descriptors advertising the SAME endpoint address on two different interfaces.
  2. The page claims both interfaces; Chromium’s RefreshEndpointMap overwrites the aliased endpoint entry so endpoint_map_ points it at the later interface, while libusb bound it to the earlier one.
  3. The page releases or reconfigures one interface, freeing/reassigning the underlying libusb interface and endpoint state under libusb’s first-match view.
  4. A subsequent transfer or claim/release routed by Chromium’s divergent mapping is dispatched to libusb against the now-stale interface/endpoint.
  5. libusb dereferences the freed structure, triggering a use-after-free in the device service on macOS.

Impact Assessment

Use-after-free inside libusb within the browser/device-service process on macOS, reachable from a web origin that has been granted (or can socially obtain) WebUSB access to a malicious or emulated device. Depending on heap grooming, the freed-then-reused libusb state can yield memory corruption enabling information disclosure or code execution outside the renderer sandbox, making this a high-severity, sandbox-external primitive. Exploitation requires attacker control of device descriptors and a WebUSB permission grant, which narrows but does not eliminate real-world reach.

Changed Functions

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

Files Changed

  • services/device/public/cpp/device_features.cc
  • services/device/public/cpp/device_features.h
  • services/device/usb/usb_device_handle_impl.cc

Audit Directions

  • Cross-layer state mirrors
    audit any Chromium bookkeeping (endpoint, interface, handle, or resource maps) that mirrors a backend/OS library’s own state; verify both sides resolve conflicts and duplicates identically, since divergence alone can produce UAF without a local memory bug.
  • Attacker-controlled descriptor parsing
    review WebUSB/WebHID/WebBluetooth paths that ingest device-supplied descriptors for assumptions of uniqueness (endpoint addresses, interface numbers, string indices); duplicate or aliased identifiers should be rejected or canonicalized before use.
  • Claim/release lifecycle routing
    trace transfer and claim/release dispatch to confirm the interface/endpoint resolved at operation time is the same one the backend still owns, especially after reconfiguration or partial release.
From a74fd42d1afa2df0b5e5b85f7db3099ef8725347 Mon Sep 17 00:00:00 2001
From: Rob Pitkin <robpitkin@google.com>
Date: Fri, 15 May 2026 17:24:36 -0700
Subject: [PATCH] usb: harden WebUSB against Endpoint Aliasing UAF on macOS

This CL implements a defense hardening fix in Chromium's WebUSB
implementation to mitigate a Use-After-Free (UAF) vulnerability in
libusb on macOS.

The vulnerability arises from inconsistent endpoint-to-interface mapping
between Chromium and libusb when a malicious device advertises duplicate
endpoint addresses across different interfaces. Libusb uses a "first
match" behavior when claiming interfaces, while Chromium overwrites
entries in `endpoint_map_` based on order of appearance.

Changes made:

1. Added a check in UsbDeviceHandleImpl::ClaimInterface to reject
claiming an interface if any of its alternate settings contain an
endpoint that is already present in endpoint_map_ and belongs to a
different interface.

2. Modified UsbDeviceHandleImpl::RefreshEndpointMap to not overwrite
existing entries when processing duplicate endpoints. This ensures that
the mapping matches libusb's "first match" behavior.

Bug: 513167952
Change-Id: I18551e0ab45f0f076079befcac85f6d2fc8c0111
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7851679
Commit-Queue: Rob Pitkin <robpitkin@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631665}
---

diff --git a/services/device/public/cpp/device_features.cc b/services/device/public/cpp/device_features.cc
index c5765957..0e89d75 100644
--- a/services/device/public/cpp/device_features.cc
+++ b/services/device/public/cpp/device_features.cc
@@ -55,6 +55,11 @@
 BASE_FEATURE(kWebUsbEnforceStandardRequestAllowlist,
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// When enabled, WebUSB rejects claiming interfaces that share endpoints with
+// already claimed interfaces, and avoids overwriting endpoint mapping entries.
+// See crbug.com/513167952.
+BASE_FEATURE(kWebUsbHardenEndpointAliasing, base::FEATURE_ENABLED_BY_DEFAULT);
+
 // When enabled, accessing the navigator.hid attribute does not prevent the
 // frame from entering the back forward cache.
 BASE_FEATURE(kWebHidAttributeAllowsBackForwardCache,
diff --git a/services/device/public/cpp/device_features.h b/services/device/public/cpp/device_features.h
index f817240..393545f4 100644
--- a/services/device/public/cpp/device_features.h
+++ b/services/device/public/cpp/device_features.h
@@ -29,6 +29,7 @@
 DEVICE_FEATURES_EXPORT BASE_DECLARE_FEATURE(kWebUsbBlocklist);
 DEVICE_FEATURES_EXPORT BASE_DECLARE_FEATURE(
     kWebUsbProtectedClassControlTransferBlock);
+DEVICE_FEATURES_EXPORT BASE_DECLARE_FEATURE(kWebUsbHardenEndpointAliasing);
 DEVICE_FEATURES_EXPORT BASE_DECLARE_FEATURE(
     kWebUsbEnforceStandardRequestAllowlist);
 DEVICE_FEATURES_EXPORT BASE_DECLARE_FEATURE(
diff --git a/services/device/usb/usb_device_handle_impl.cc b/services/device/usb/usb_device_handle_impl.cc
index d4d31ffe..9b6c743 100644
--- a/services/device/usb/usb_device_handle_impl.cc
+++ b/services/device/usb/usb_device_handle_impl.cc
@@ -21,6 +21,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "components/device_event_log/device_event_log.h"
+#include "services/device/public/cpp/device_features.h"
 #include "services/device/public/cpp/usb/usb_utils.h"
 #include "services/device/usb/usb_context.h"
 #include "services/device/usb/usb_descriptors.h"
@@ -589,6 +590,37 @@
     return;
   }
 
+  if (base::FeatureList::IsEnabled(features::kWebUsbHardenEndpointAliasing)) {
+    // Prevent claiming interfaces that contain endpoints already present in
+    // other claimed interfaces. See crbug.com/513167952.
+    const mojom::UsbConfigurationInfo* config =
+        device_->GetActiveConfiguration();
+    if (config) {
+      for (const auto& interface : config->interfaces) {
+        if (interface->interface_number == interface_number) {
+          for (const auto& alternate : interface->alternates) {
+            for (const auto& endpoint : alternate->endpoints) {
+              uint8_t endpoint_address =
+                  ConvertEndpointNumberToAddress(*endpoint);
+              const auto it = endpoint_map_.find(endpoint_address);
+              if (it != endpoint_map_.end() &&
+                  it->second.interface->interface_number != interface_number) {
+                USB_LOG(ERROR) << "Cannot claim interface " << interface_number
+                               << " because it shares endpoint "
+                               << static_cast<int>(endpoint_address)
+                               << " with an already claimed interface.";
+                task_runner_->PostTask(
+                    FROM_HERE, base::BindOnce(std::move(callback), false));
+                return;
+              }
+            }
+          }
+          break;
+        }
+      }
+    }
+  }
+
   blocking_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&UsbDeviceHandleImpl::ClaimInterfaceBlocking,
                                 this, interface_number, std::move(callback)));
@@ -1019,8 +1051,19 @@
       return;
 
     for (const auto& endpoint : interface_info.alternate->endpoints) {
-      endpoint_map_[ConvertEndpointNumberToAddress(*endpoint)] = {
-          interface_info.interface.get(), endpoint.get()};
+      uint8_t endpoint_address = ConvertEndpointNumberToAddress(*endpoint);
+      if (!base::FeatureList::IsEnabled(
+              features::kWebUsbHardenEndpointAliasing)) {
+        endpoint_map_[endpoint_address] = {interface_info.interface.get(),
+                                           endpoint.get()};
+      } else {
+        // Do not overwrite existing entries to match libusb's "first match"
+        // behavior on macOS and avoid Use-After-Free due to mapping
+        // inconsistency. See crbug.com/513167952.
+        endpoint_map_.insert(
+            {endpoint_address,
+             {interface_info.interface.get(), endpoint.get()}});
+      }
     }
   }
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

macOS WebUSB: Potential UAF in Browser Process via Endpoint Aliasing and Race Condition

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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in the macOS WebUSB implementation due to inconsistent endpoint-to-interface mapping between Chromium and libusb. A malicious USB device can exploit this discrepancy to bypass transfer cancellation checks and trigger a race condition during interface release. This leads to a vtable dereference on a freed IOKit interface proxy within the privileged browser process.

Affected files:

  • third_party/libusb/src/libusb/os/darwin_usb.c
  • services/device/usb/usb_device_handle_impl.cc
  • third_party/libusb/src/libusb/io.c
  • third_party/libusb/src/libusb/core.c

Estimated timestamp from git blame: 2016-02-04

Summary

A potential Use-After-Free (UAF) vulnerability has been identified in the macOS backend of WebUSB. The issue arises from a combination of ’endpoint aliasing’—where a malicious USB device provides duplicate endpoint addresses across different interfaces—and a race condition between transfer cancellation and interface release in the underlying libusb library. Because the Device Service runs within the browser process on macOS, this can lead to an attacker-controlled indirect call in a privileged, unsandboxed context.

Vulnerability Details

1. Endpoint Mapping Discrepancy

When a device is claimed, Chrome maintains an internal endpoint_map_ in UsbDeviceHandleImpl to track which claimed interface owns which endpoint. If a malicious device advertises the same endpoint address (e.g., 0x81) on multiple interfaces (e.g., Interface 0 and Interface 1), a mapping discrepancy occurs:

  • Chromium Logic: UsbDeviceHandleImpl::RefreshEndpointMap iterates through claimed interfaces. If an endpoint is duplicated, the map is overwritten, effectively associating the endpoint with the interface having the highest index (Interface 1).
  • libusb Logic: The macOS backend function ep_to_pipeRef (in darwin_usb.c) iterates from index 0 and returns the first match. It associates the endpoint with Interface 0.

2. The Race Condition

When a transfer is initiated on Interface 1, Chrome believes Interface 1 owns it. However, libusb executes the transfer using the IOKit pipe associated with Interface 0.

If the website then releases Interface 0, Chrome’s UsbDeviceHandleImpl::ReleaseInterface iterates through active transfers to perform pre-release cancellation. Because it believes the transfer belongs to Interface 1, it skips cancellation. The interface release is then posted to a background ThreadPool.

On the ThreadPool, libusb_release_interface begins destroying the Interface 0 proxy. Concurrently, if the website releases Interface 1, Chrome triggers libusb_cancel_transfer on the UI thread. Inside libusb, darwin_abort_transfers is called. It uses ep_to_pipeRef to find the owner of the endpoint, which is still Interface 0 (as the release hasn’t completed).

Because libusb_cancel_transfer and libusb_release_interface use disjoint locks (itransfer->lock vs dev->lock), the UI thread may dereference the Interface 0 proxy’s vtable (to call AbortPipe or ClearPipeStallBothEnds) while the ThreadPool is simultaneously freeing that same proxy object.

Potential Steps to Reproduce

Note: These are suggested steps based on source code analysis; the behavior has not been verified with a live Proof of Concept.

  1. Connect a malicious USB device that advertises a duplicate bulk endpoint address on both Interface 0 and Interface 1.
  2. Claim both Interface 0 and Interface 1 via WebUSB.
  3. Start a bulk transfer on the duplicate endpoint (associated with Interface 1 by Chrome).
  4. Release Interface 0. Chromium will skip the cancellation check for the active transfer.
  5. Immediately release Interface 1 or close the device. This triggers libusb_cancel_transfer on the UI thread.
  6. The UI thread and a background thread may race on the destruction and access of the Interface 0 IOKit proxy, leading to a UAF.

Impact

This vulnerability impacts the unsandboxed browser process on macOS. Successful exploitation could allow an attacker with a malicious USB device to achieve arbitrary code execution (RCE) in the context of the browser process. The issue is not mitigated by MiraclePtr as it occurs within the third-party C library libusb.

Suggested Fix

  1. Strict Validation: Chromium should prevent claiming interfaces that contain endpoints already present in other claimed interfaces for the same device.
  2. Mapping Consistency: Ensure UsbDeviceHandleImpl::RefreshEndpointMap uses the same search logic as the underlying platform backend to prevent ownership mismatches.
  3. libusb Hardening: Submit a patch to libusb to ensure that darwin_release_interface and darwin_abort_transfers use synchronized locking or that the interface proxy is nullified before the object is released.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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