Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactBuffer overflow in WebGL
DescriptionBuffer overflow in WebGL
ComponentWebGL
Sub-componentGPU
Bug ClassOOB
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
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.
Independent review by AIPlausible
Reviewer notes: The missing level < base_level check in CanRenderTo is confirmed by the diff and test. The claimed GPU-process out-of-bounds write is stated as fact, but the diff shows no write path or buffer sizing; the overflow mechanism is inferred.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker548130125
Fix commit3b907e173dc4 (chromium/src) +14/-0
CWECWE-122 (Heap buffer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-18 (per vendor advisory)
Fix landed2026-08-24 (public git)
Disclosed2026-09-08
Patch gap15 days — fix public in source before the advisory

Background

WebGL
a browser API that exposes GPU-accelerated 3D rendering to untrusted JavaScript by routing draw and texture commands through Chrome’s GPU process.
GPU command buffer
the IPC layer in gpu/command_buffer/service that validates client-issued GL commands before replaying them against the real driver.
`TEXTURE_BASE_LEVEL`
a per-texture parameter (base_level_) that sets the lowest mipmap level the GL considers part of the texture for sampling and framebuffer completeness.
Framebuffer completeness
the OpenGL ES rule set that a framebuffer object must satisfy before it may be rendered to, per ES 3.0 section 4.4.4.1.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: The missing level < base_level check in CanRenderTo is confirmed by the diff and test. The claimed GPU-process out-of-bounds write is stated as fact, but the diff shows no write path or buffer sizing; the overflow mechanism is inferred.

The vulnerable path is Texture::CanRenderTo in gpu/command_buffer/service/texture_manager.cc, which decides whether a given texture level may serve as a color/depth attachment for framebuffer rendering. The method validated cube completeness, that level fell inside the valid mip range, and (only for level > base_level_) texture completeness, but it never rejected the case where level < base_level_. ES 3.0 section 4.4.4.1 requires that a framebuffer attachment whose texture level is below TEXTURE_BASE_LEVEL be treated as incomplete, so the missing check violated the invariant that only levels within the base-through-max range are renderable. Because SetLevelInfo can define storage for level 0 while TEXTURE_BASE_LEVEL is subsequently raised to 1, the attachment at level 0 could be attached and rendered to even though it is not a valid, dimensionally-consistent target.

The fix adds an explicit if (level < base_level_) return false; guard so any below-base level is rejected as an incomplete attachment before rendering proceeds.

Key insight
The single core mistake was that CanRenderTo only guarded the level > base_level_ branch and silently accepted level < base_level_, leaving a gap in framebuffer-completeness validation; the fix closes it by unconditionally rejecting levels below base_level_.

Attack Path

  1. Create a texture and define a low level From WebGL, allocate a texture and call the equivalent of SetLevelInfo on level 0 to give it storage and dimensions.
  2. Raise the base level Set TEXTURE_BASE_LEVEL to 1 (or higher) so level 0 now lies below base_level_ and is outside the logically valid mip range.
  3. Attach the below-base level Bind level 0 of the texture as a framebuffer color attachment, which the unpatched CanRenderTo wrongly reports as renderable.
  4. Render to the stale target Issue draw or clear commands so the GPU writes into a level whose completeness invariants were never enforced, producing an out-of-bounds buffer write.

Impact Assessment

An attacker running WebGL-capable JavaScript gains an out-of-bounds write inside Chrome’s GPU process by rendering into a framebuffer attachment that should have been rejected as incomplete. The corruption occurs in the sandboxed GPU process, and the primary precondition is the ability to run WebGL and manipulate TEXTURE_BASE_LEVEL relative to a defined level, which is available to any web page. The metadata classes this as a critical buffer overflow, consistent with attacker-controlled rendering into an under-validated texture level.

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/texture_manager.cc
modified
TEST_F
gpu/command_buffer/service/texture_manager_unittest.cc
modified

Files Changed

  • gpu/command_buffer/service/texture_manager_unittest.cc
  • gpu/command_buffer/service/texture_manager.cc

Audit Directions

  • Range-partial validation
    Flag completeness or bounds checks that only guard one side of a range (here only level > base_level_), since the untested level < base_level_ case is exactly where the invariant leaked.
  • Mutable parameters versus cached state
    Review paths where a parameter like base_level_ can change after level storage is defined via SetLevelInfo, ensuring every consumer re-validates against the current base/max rather than assuming level 0 is always valid.
  • Spec-mandated attachment rules
    Cross-check CanRenderTo and related framebuffer-attachment validators against ES 3.0 section 4.4.4.1 completeness conditions to catch other omitted incompleteness cases.
From 3b907e173dc4a643728a928f3d239fd99e1f2950 Mon Sep 17 00:00:00 2001
From: Zhenyao Mo <[email protected]>
Date: Mon, 24 Aug 2026 14:23:54 -0700
Subject: [PATCH] gpu: Reject FBO attachment levels below TEXTURE_BASE_LEVEL

Per ES 3.0 section 4.4.4.1, a framebuffer attachment is incomplete if
its texture level is less than TEXTURE_BASE_LEVEL. Chrome was missing
this check in Texture::CanRenderTo, potentially allowing a level <
base_level to be rendered to.

Bug: 548130125
Change-Id: Ie13bb528873610aaf61e1768a91928574d5d4653
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8280604
Commit-Queue: Zhenyao Mo <[email protected]>
Commit-Queue: Geoff Lang <[email protected]>
Auto-Submit: Zhenyao Mo <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1685010}
---
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index c9a764c..bc6a850 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -1768,6 +1768,9 @@
   if (face_infos_.size() == 6 && !cube_complete())
     return false;
   DCHECK(level >= 0 && level < static_cast<GLint>(MaxValidMipLevel()));
+  if (level < base_level_) {
+    return false;
+  }
   if (level > base_level_ && !texture_complete()) {
     return false;
   }
diff --git a/gpu/command_buffer/service/texture_manager_unittest.cc b/gpu/command_buffer/service/texture_manager_unittest.cc
index 8d06b15..50fa6397 100644
--- a/gpu/command_buffer/service/texture_manager_unittest.cc
+++ b/gpu/command_buffer/service/texture_manager_unittest.cc
@@ -742,6 +742,17 @@
   manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1,
                          0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect());
   EXPECT_TRUE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 0));
+
+  // Verify that rendering to a level < base_level is not allowed.
+  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1))
+      .Times(1)
+      .RetiresOnSaturation();
+  manager_->SetParameteri("", error_state_.get(), texture_ref_.get(),
+                          GL_TEXTURE_BASE_LEVEL, 1);
+  manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 1, GL_RGBA, 0, 0, 1,
+                         0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect());
+  EXPECT_FALSE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 0));
+  EXPECT_TRUE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 1));
 }
 
 TEST_F(TextureTest, CanNotRenderTo) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/texture_manager_unittest.cc b/gpu/command_buffer/service/texture_manager_unittest.cc
index 8d06b15..50fa6397 100644
--- a/gpu/command_buffer/service/texture_manager_unittest.cc
+++ b/gpu/command_buffer/service/texture_manager_unittest.cc
@@ -742,6 +742,17 @@
   manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 1,
                          0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect());
   EXPECT_TRUE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 0));
+
+  // Verify that rendering to a level < base_level is not allowed.
+  EXPECT_CALL(*gl_, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1))
+      .Times(1)
+      .RetiresOnSaturation();
+  manager_->SetParameteri("", error_state_.get(), texture_ref_.get(),
+                          GL_TEXTURE_BASE_LEVEL, 1);
+  manager_->SetLevelInfo(texture_ref_.get(), GL_TEXTURE_2D, 1, GL_RGBA, 0, 0, 1,
+                         0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect());
+  EXPECT_FALSE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 0));
+  EXPECT_TRUE(texture_ref_->texture()->CanRenderTo(feature_info.get(), 1));
 }
 
 TEST_F(TextureTest, CanNotRenderTo) {
Loading diff…

Original Bug Report

reported by [email protected]

IMG: A heap out-of-bounds write by superset-inverted page-map diff

  • Attack surface: WebGL2 mip-level rendering and base-level changes from an untrusted web page.
  • Impact: Deterministic linear heap out-of-bounds write in the unsandboxed GPU process shared by all tabs.

Android internal bug: b/548123452

View on issue tracker