CVE-2026-13778
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifservices/device/usb/usb_device_handle_impl.cc |
modified | |
forservices/device/usb/usb_device_handle_impl.cc |
modified |
Files Changed
services/device/public/cpp/device_features.ccservices/device/public/cpp/device_features.hservices/device/usb/usb_device_handle_impl.cc
Patch
From a74fd42d1afa2df0b5e5b85f7db3099ef8725347 Mon Sep 17 00:00:00 2001 From: Rob Pitkin <[email protected]> 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 <[email protected]> Reviewed-by: Matt Reynolds <[email protected]> Reviewed-by: Reilly Grant <[email protected]> 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()}}); + } } } }
Original Bug Report
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.cservices/device/usb/usb_device_handle_impl.ccthird_party/libusb/src/libusb/io.cthird_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::RefreshEndpointMapiterates 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(indarwin_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.
- Connect a malicious USB device that advertises a duplicate bulk endpoint address on both Interface 0 and Interface 1.
- Claim both Interface 0 and Interface 1 via WebUSB.
- Start a bulk transfer on the duplicate endpoint (associated with Interface 1 by Chrome).
- Release Interface 0. Chromium will skip the cancellation check for the active transfer.
- Immediately release Interface 1 or close the device. This triggers
libusb_cancel_transferon the UI thread. - 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
- Strict Validation: Chromium should prevent claiming interfaces that contain endpoints already present in other claimed interfaces for the same device.
- Mapping Consistency: Ensure
UsbDeviceHandleImpl::RefreshEndpointMapuses the same search logic as the underlying platform backend to prevent ownership mismatches. - libusb Hardening: Submit a patch to
libusbto ensure thatdarwin_release_interfaceanddarwin_abort_transfersuse 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.