CVE-2026-11082
Overview
Files Changed
components/viz/service/gl/gpu_service_impl.cccomponents/viz/service/gl/gpu_service_impl.h
Patch
From 7535ea86ce21275986dbea06910cbdc92d885a10 Mon Sep 17 00:00:00 2001
From: Le Hoang Quyen <lehoangquyen@chromium.org>
Date: Wed, 08 Apr 2026 23:42:39 -0700
Subject: [PATCH] [viz] Protect GpuServiceImpl::shader_prefix_key_ with a lock
shader_prefix_key_ can be accessed from multiple threads via
GetShaderPrefixKey(). This CL adds a base::Lock to GpuServiceImpl to
protect it.
Bug: 500079715
Change-Id: If3e871bf3cf3b250fd17803a805660d6f26970a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7734909
Reviewed-by: Colin Blundell <blundell@chromium.org>
Reviewed-by: Quyen Le <lehoangquyen@google.com>
Commit-Queue: Quyen Le <lehoangquyen@google.com>
Cr-Commit-Position: refs/heads/main@{#1611992}
---
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 502be5c..a2886a7 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -774,6 +774,7 @@
}
std::string GpuServiceImpl::GetShaderPrefixKey() {
+ base::AutoLock lock(shader_prefix_key_lock_);
if (shader_prefix_key_.empty()) {
const gpu::GPUInfo::GPUDevice& active_gpu = gpu_info_.active_gpu();
std::string product =
diff --git a/components/viz/service/gl/gpu_service_impl.h b/components/viz/service/gl/gpu_service_impl.h
index c7b4b35..10bb52e 100644
--- a/components/viz/service/gl/gpu_service_impl.h
+++ b/components/viz/service/gl/gpu_service_impl.h
@@ -542,7 +542,8 @@
base::RepeatingClosure wake_up_closure_;
- std::string shader_prefix_key_;
+ base::Lock shader_prefix_key_lock_;
+ std::string shader_prefix_key_ GUARDED_BY(shader_prefix_key_lock_);
// This is flag is controlled by the finch experiment
// ClearGrShaderDiskCacheOnInvalidPrefix. Earlier this flag was assigned in
Original Bug Report
Potential Data Race / UAF in GpuServiceImpl::GetShaderPrefixKey
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 potential data race exists in GpuServiceImpl::GetShaderPrefixKey during the lazy initialization of the shader_prefix_key_ string. The GPU Main thread and the DrDc thread can concurrently modify this string without synchronization. This can lead to severe heap corruption, such as Use-After-Free or Double-Free, in the privileged GPU process.
Affected files:
components/viz/service/gl/gpu_service_impl.cccomponents/viz/service/gl/gpu_service_impl.h
Estimated timestamp from git blame: 2025-08-11
Vulnerability Details
A potential data race and memory corruption vulnerability exists in components/viz/service/gl/gpu_service_impl.cc. The function GpuServiceImpl::GetShaderPrefixKey() lazily initializes a class member std::string shader_prefix_key_ if it is empty. This initialization uses standard string assignment (operator=) and concatenation (operator+=) without any synchronization primitives (like a base::Lock).
Concurrent execution can occur from two different threads:
- GPU Main Thread: When the GPU process starts, the browser process reads cached shaders from disk and sends
LoadedBlobIPCs. These are routed toGpuServiceImpl::LoadedBlob, which executes on the GPU Main Thread and callsGetShaderPrefixKey(). - DrDc Thread (CompositorGpuThread): On Android, the Direct Rendering Display Compositor (DrDc) feature offloads Skia compositing to a dedicated thread. If Skia compiles a new shader, it calls
GrShaderCache::storesynchronously on the DrDc thread. This calls intoGpuServiceImpl::StoreBlobToDisk, which also callsGetShaderPrefixKey().
Because GrShaderCache::store holds a local lock but GpuServiceImpl::LoadedBlob does not acquire it before calling GetShaderPrefixKey(), both threads can race to initialize shader_prefix_key_. The generated prefix string is relatively large (150-300 bytes), meaning it exceeds libc++’s Small String Optimization (SSO) limit and forces a heap allocation. Concurrent assignments will corrupt the std::string internal pointers, leading to a Double-Free or Use-After-Free (UAF) of the GPU process heap. Note that std::string internal buffers are not protected by MiraclePtr.
Potential Attacker Steps
Note: These are suggested/potential steps based on code analysis, as our tooling agent does not have the ability to run code to produce a working proof-of-concept.
To exploit this vulnerability, an attacker with a compromised renderer process could theoretically follow these steps:
- Intentionally crash the GPU process (e.g., via a WebGL out-of-memory or known DoS bug) to force the browser to spawn a fresh GPU process.
- Upon restart, the browser process automatically begins loading previously cached shaders from disk and sending a burst of
LoadedBlobIPCs. The GPU Main Thread processes these and begins executingGpuServiceImpl::GetShaderPrefixKey(). - Concurrently, the attacker submits a crafted
CompositorFramefrom the renderer containing unique graphical effects (e.g., specific CSS filters) that force Skia to compile a new shader program on theCompositorGpuThread. - Both threads concurrently observe an empty
shader_prefix_key_and race to assign to it, triggering the UAF/Double-Free. - By carefully shaping the GPU heap prior to the race, the attacker could reallocate the freed string buffer with a controlled object, overwriting vtables or function pointers to achieve Remote Code Execution (RCE) and a full sandbox escape.
Suggested Fix
There are two primary ways to fix this potential issue:
- Eager Initialization: Initialize
shader_prefix_key_once on the main thread duringGpuServiceImpl::Initialize()or the class constructor, instead of using lazy initialization. This is the safest approach if the necessary system information is available at startup. - Locking: Introduce a
base::LockinsideGetShaderPrefixKey()to protect the lazy initialization block, ensuring that only one thread can assign toshader_prefix_key_.
Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad
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.