Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Tracker500079715
Fix commit7535ea86ce21 (chromium/src) +3/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • components/viz/service/gl/gpu_service_impl.cc
  • components/viz/service/gl/gpu_service_impl.h
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • components/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:

  1. GPU Main Thread: When the GPU process starts, the browser process reads cached shaders from disk and sends LoadedBlob IPCs. These are routed to GpuServiceImpl::LoadedBlob, which executes on the GPU Main Thread and calls GetShaderPrefixKey().
  2. 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::store synchronously on the DrDc thread. This calls into GpuServiceImpl::StoreBlobToDisk, which also calls GetShaderPrefixKey().

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:

  1. 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.
  2. Upon restart, the browser process automatically begins loading previously cached shaders from disk and sending a burst of LoadedBlob IPCs. The GPU Main Thread processes these and begins executing GpuServiceImpl::GetShaderPrefixKey().
  3. Concurrently, the attacker submits a crafted CompositorFrame from the renderer containing unique graphical effects (e.g., specific CSS filters) that force Skia to compile a new shader program on the CompositorGpuThread.
  4. Both threads concurrently observe an empty shader_prefix_key_ and race to assign to it, triggering the UAF/Double-Free.
  5. 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:

  1. Eager Initialization: Initialize shader_prefix_key_ once on the main thread during GpuServiceImpl::Initialize() or the class constructor, instead of using lazy initialization. This is the safest approach if the necessary system information is available at startup.
  2. Locking: Introduce a base::Lock inside GetShaderPrefixKey() to protect the lazy initialization block, ensuring that only one thread can assign to shader_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.

View on issue tracker