CVE-2026-9890
Overview
Files Changed
content/browser/gpu/gpu_data_manager_impl.cccontent/browser/gpu/gpu_data_manager_impl.hcontent/browser/gpu/gpu_data_manager_impl_private.cccontent/browser/gpu/gpu_data_manager_impl_private.hcontent/browser/gpu/gpu_process_host.cccontent/browser/xr/service/xr_runtime_manager_impl.cc
Patch
From 62dc8431a8fd9c0127f82549386416c7a1a7cd58 Mon Sep 17 00:00:00 2001 From: Alexander Cooper <[email protected]> Date: Fri, 15 May 2026 13:41:41 -0700 Subject: [PATCH] Make Xr Luid plumbing more robust The way that XR plumbs the requested LUID over to the GPU process could potentially break if e.g. the GPU process was already starting up at the time XR requested it. This avoids any potenital races on the command line singleton and plumbs the XR LUID across in a more robust way. It was briefly evaluated to use GpuPreferences, but that *also* seems to require modifying the command line, as there did not seem to be a way to override it, and further, the LUID value ultimately *does* need to end up on the command line in the gpu process so that ui/gl initialization code can use it. Because of the fact that the LUID and it's command line is now managed by the GpuDataManager, it is removed from the list of copied switches in GpuProcessHost. Bug: 513135985 Change-Id: I6e7fd6a283fa3c0642e5c65e43db582fe0b2d7db Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7849904 Auto-Submit: Alexander Cooper <[email protected]> Reviewed-by: Kenneth Russell <[email protected]> Reviewed-by: Brandon Jones <[email protected]> Commit-Queue: Alexander Cooper <[email protected]> Cr-Commit-Position: refs/heads/main@{#1631530} --- diff --git a/content/browser/gpu/gpu_data_manager_impl.cc b/content/browser/gpu/gpu_data_manager_impl.cc index 1043afb0..a3ad2e7 100644 --- a/content/browser/gpu/gpu_data_manager_impl.cc +++ b/content/browser/gpu/gpu_data_manager_impl.cc @@ -231,6 +231,21 @@ base::AutoLock auto_lock(lock_); private_->TerminateInfoCollectionGpuProcess(); } + +void GpuDataManagerImpl::SetUseAdapterLuid(const CHROME_LUID& luid) { + base::AutoLock auto_lock(lock_); + private_->SetUseAdapterLuid(luid); +} + +void GpuDataManagerImpl::ClearUseAdapterLuid() { + base::AutoLock auto_lock(lock_); + private_->ClearUseAdapterLuid(); +} + +std::optional<CHROME_LUID> GpuDataManagerImpl::GetUseAdapterLuid() const { + base::AutoLock auto_lock(lock_); + return private_->GetUseAdapterLuid(); +} #endif // BUILDFLAG(IS_WIN) void GpuDataManagerImpl::PostCreateThreads() { diff --git a/content/browser/gpu/gpu_data_manager_impl.h b/content/browser/gpu/gpu_data_manager_impl.h index a360401..a2e44d7 100644 --- a/content/browser/gpu/gpu_data_manager_impl.h +++ b/content/browser/gpu/gpu_data_manager_impl.h @@ -9,6 +9,7 @@ #include <stdint.h> #include <memory> +#include <optional> #include <string> #include "base/no_destructor.h" @@ -34,6 +35,7 @@ #include "ui/gfx/gpu_extra_info.h" #if BUILDFLAG(IS_WIN) +#include "base/win/windows_types.h" #include "ui/gfx/mojom/dxgi_info.mojom.h" #endif @@ -122,6 +124,13 @@ bool DirectXRequested() const; bool VulkanRequested() const; void TerminateInfoCollectionGpuProcess(); + + // Information to Get/Set the LUID that the GPU Process should be launched on. + // Predominantly used by XR, so that we can ensure the GL context is created + // on the GPU that the headset is actually plugged into. + void SetUseAdapterLuid(const CHROME_LUID& luid); + void ClearUseAdapterLuid(); + std::optional<CHROME_LUID> GetUseAdapterLuid() const; #endif // Called from BrowserMainLoop::PostCreateThreads(). // TODO(content/browser/gpu/OWNERS): This should probably use a diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc index 5518072b..97e9f0ac 100644 --- a/content/browser/gpu/gpu_data_manager_impl_private.cc +++ b/content/browser/gpu/gpu_data_manager_impl_private.cc @@ -33,6 +33,7 @@ #include "base/metrics/histogram_macros.h" #include "base/path_service.h" #include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/task/bind_post_task.h" #include "base/trace_event/trace_event.h" @@ -1156,6 +1157,19 @@ if (host) host->ForceShutdown(); } + +void GpuDataManagerImplPrivate::SetUseAdapterLuid(const CHROME_LUID& luid) { + use_adapter_luid_ = luid; +} + +void GpuDataManagerImplPrivate::ClearUseAdapterLuid() { + use_adapter_luid_ = std::nullopt; +} + +std::optional<CHROME_LUID> GpuDataManagerImplPrivate::GetUseAdapterLuid() + const { + return use_adapter_luid_; +} #endif void GpuDataManagerImplPrivate::PostCreateThreads() { @@ -1358,6 +1372,19 @@ if (!use_gl.empty()) { command_line->AppendSwitchASCII(switches::kUseGL, use_gl); } + +#if BUILDFLAG(IS_WIN) + if (browser_command_line->HasSwitch(switches::kUseAdapterLuid)) { + command_line->AppendSwitchASCII( + switches::kUseAdapterLuid, + browser_command_line->GetSwitchValueASCII(switches::kUseAdapterLuid)); + } else if (use_adapter_luid_.has_value()) { + std::string luid_string = + base::NumberToString(use_adapter_luid_->HighPart) + "," + + base::NumberToString(use_adapter_luid_->LowPart); + command_line->AppendSwitchASCII(switches::kUseAdapterLuid, luid_string); + } +#endif } void GpuDataManagerImplPrivate::UpdateGpuPreferences( diff --git a/content/browser/gpu/gpu_data_manager_impl_private.h b/content/browser/gpu/gpu_data_manager_impl_private.h index 067625a..f6e0679a 100644 --- a/content/browser/gpu/gpu_data_manager_impl_private.h +++ b/content/browser/gpu/gpu_data_manager_impl_private.h @@ -85,6 +85,9 @@ bool DirectXRequested() const; bool VulkanRequested() const; void TerminateInfoCollectionGpuProcess(); + void SetUseAdapterLuid(const CHROME_LUID& luid); + void ClearUseAdapterLuid(); + std::optional<CHROME_LUID> GetUseAdapterLuid() const; #endif void PostCreateThreads(); void UpdateDawnInfo(const std::vector<std::string>& dawn_info_list); @@ -259,6 +262,7 @@ bool gpu_info_vulkan_valid_ = false; bool gpu_info_vulkan_requested_ = false; bool gpu_info_vulkan_request_failed_ = false; + std::optional<CHROME_LUID> use_adapter_luid_; #endif // The Dawn info queried from the GPU process. std::vector<std::string> dawn_info_list_; diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc index e7d2274..b6ddfa6 100644 --- a/content/browser/gpu/gpu_process_host.cc +++ b/content/browser/gpu/gpu_process_host.cc @@ -294,7 +294,6 @@ switches::kSkiaGraphiteDawnBackend, switches::kSkiaResourceCacheLimitMb, switches::kTestGLLib, - switches::kUseAdapterLuid, switches::kUseFakeMjpegDecodeAccelerator, switches::kUseGpuInTests, switches::kWebViewDrawFunctorUsesVulkan, diff --git a/content/browser/xr/service/xr_runtime_manager_impl.cc b/content/browser/xr/service/xr_runtime_manager_impl.cc index 86525f8..672eb48 100644 --- a/content/browser/xr/service/xr_runtime_manager_impl.cc +++ b/content/browser/xr/service/xr_runtime_manager_impl.cc @@ -21,6 +21,7 @@ #include "base/trace_event/trace_event.h" #include "base/trace_event/typed_macros.h" #include "build/build_config.h" +#include "content/browser/gpu/gpu_data_manager_impl.h" #include "content/browser/xr/service/xr_frame_sink_client_impl.h" #include "content/browser/xr/webxr_internals/mojom/webxr_internals.mojom.h" #include "content/browser/xr/webxr_internals/webxr_internals_handler_impl.h" @@ -29,7 +30,6 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/device_service.h" #include "content/public/browser/global_routing_id.h" -#include "content/public/browser/gpu_data_manager.h" #include "content/public/browser/gpu_utils.h" #include "content/public/browser/xr_runtime_manager.h" #include "content/public/common/child_process_id_util.h" @@ -425,12 +425,9 @@ // runtime doesn't specify a LUID. DCHECK(luid && (luid->HighPart != 0 || luid->LowPart != 0));
Original Bug Report
Potential Browser Process UAF via Unsynchronized Data Race on base::CommandLine Singleton
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 data race exists in the browser process where the global base::CommandLine singleton is mutated on the UI thread via WebXR while being read concurrently on the process-launcher thread. Because base::CommandLine uses unsynchronized std::map and std::vector containers, this concurrent access can lead to a Use-After-Free (UAF) or memory corruption. The vulnerability is reachable from a compromised renderer and impacts the unsandboxed browser process.
Affected files:
content/browser/xr/service/xr_runtime_manager_impl.ccbase/command_line.cccontent/browser/child_process_launcher_helper.cccontent/browser/child_process_launcher_helper_win.ccsandbox/policy/win/sandbox_win.ccbase/command_line.h
Estimated timestamp from git blame: 2020-06-17
Summary
A data race on the base::CommandLine singleton allows for a potential Use-After-Free (UAF) in the unsandboxed browser process. The race occurs because the global command line is modified on the UI thread (triggered by WebXR Mojo calls) while being accessed on the dedicated process-launcher thread during child process launches.
Root Cause Analysis
In base/command_line.h, command-line switches and arguments are stored in switches_ (std::map) and argv_ (std::vector). These containers are not thread-safe. Although base::CommandLine includes an InstanceBoundSequenceChecker, it is typically compiled out in production builds. Consequently, mutators like AppendSwitch and RemoveSwitch modify the containers without synchronization, which can cause tree rebalancing or node deletions while other threads are traversing the map.
Vulnerability Details
The Writer (UI Thread)
In content/browser/xr/service/xr_runtime_manager_impl.cc, the MakeXrCompatible method appends a switch to the global command line when a compatible GPU adapter LUID is identified:
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kUseAdapterLuid, luid_string);
Additionally, the destructor XRRuntimeManagerImpl::~XRRuntimeManagerImpl removes this switch:
base::CommandLine::ForCurrentProcess()->RemoveSwitch(switches::kUseAdapterLuid);
The Reader (Launcher Thread)
Every child process launch (renderer, GPU, utility, etc.) involves reading the global CommandLine singleton on a dedicated thread pool thread (the launcher thread). Functions like internal::ChildProcessLauncherHelper::LaunchOnLauncherThread perform multiple reads using HasSwitch or GetSwitchValueASCII (e.g., in content/browser/child_process_launcher_helper.cc at line 315).
Impact
Concurrent mutation and traversal of a std::map is undefined behavior. In libc++, this can cause a reader thread to follow pointers of nodes that are being freed or rotated, leading to a browser-process UAF. Because libc++ tree nodes and std::string buffers use bare pointers rather than raw_ptr<>, MiraclePtr does not provide protection for this specific case. A UAF in the browser process is a critical security issue that could lead to a sandbox escape.
Suggested Reproductions Steps (Potential)
- From a compromised renderer, repeatedly invoke
navigator.xr.makeXRCompatible()and immediately drop the Mojo connection to trigger frequentAppendSwitchandRemoveSwitchcalls on the UI thread. - Simultaneously, trigger frequent child process launches (e.g., by creating many cross-site iframes) to force concurrent reads of the
CommandLinesingleton on the launcher thread. - Observe for memory corruption or crashes in the browser process resulting from the data race on the
switches_map.
Suggested Fix
Operations on the global base::CommandLine singleton should be synchronized using a lock, or the singleton should be made immutable after initialization. If runtime mutations are required, they should be performed on a thread-safe wrapper or protected by a global mutex.
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.