Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in GPU
DescriptionInsufficient validation of untrusted input in GPU
ComponentGPU
Bug ClassLogic Error
Tracker501461853
Fix commitbb73e653f903 (chromium/src) +32/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified
for
gpu/command_buffer/service/renderbuffer_manager.cc
modified

Files Changed

  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/renderbuffer_manager.cc
  • gpu/command_buffer/service/renderbuffer_manager.h
From bb73e653f903aa2f8a77cd3e53646ef952573f77 Mon Sep 17 00:00:00 2001
From: Ken Russell <[email protected]>
Date: Wed, 29 Apr 2026 10:11:09 -0700
Subject: [PATCH] Make renderbuffer allocation failures more robust.

In the validating command decoder, if allocation of a multisampled
renderbuffer fails, record that so that the framebuffer to which it's
attached becomes incomplete.

Fixed: 501461853
Change-Id: I8b03146dea04a799a5f4a3a84784039ef8c003ef
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7800071
Auto-Submit: Kenneth Russell <[email protected]>
Reviewed-by: Vasiliy Telezhnikov <[email protected]>
Commit-Queue: Vasiliy Telezhnikov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1622547}
---

diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index d2132c9..7ea21c6 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -8069,9 +8069,12 @@
                                                width, height, kDoNotForce);
   GLenum error =
       LOCAL_PEEK_GL_ERROR("glRenderbufferStorageMultisampleCHROMIUM");
+
   if (error == GL_NO_ERROR) {
     renderbuffer_manager()->SetInfoAndInvalidate(renderbuffer, samples,
                                                  internalformat, width, height);
+  } else {
+    renderbuffer_manager()->SetAllocationFailed(renderbuffer);
   }
 }
 
diff --git a/gpu/command_buffer/service/renderbuffer_manager.cc b/gpu/command_buffer/service/renderbuffer_manager.cc
index 6cdb9186..34e46de 100644
--- a/gpu/command_buffer/service/renderbuffer_manager.cc
+++ b/gpu/command_buffer/service/renderbuffer_manager.cc
@@ -113,6 +113,18 @@
   }
 }
 
+void Renderbuffer::SetAllocationFailed() {
+  samples_ = 1;
+  internal_format_ = 0x0;
+  width_ = 0;
+  height_ = 0;
+  cleared_ = false;
+  allocated_ = false;
+  for (auto& point : framebuffer_attachment_points_) {
+    point.first->UnmarkAsComplete();
+  }
+}
+
 void Renderbuffer::AddToSignature(std::string* signature) const {
   DCHECK(signature);
   RenderbufferSignature signature_data(internal_format_,
@@ -239,6 +251,19 @@
   }
 }
 
+void RenderbufferManager::SetAllocationFailed(Renderbuffer* renderbuffer) {
+  DCHECK(renderbuffer);
+  if (!renderbuffer->cleared()) {
+    --num_uncleared_renderbuffers_;
+  }
+  memory_type_tracker_->TrackMemFree(renderbuffer->EstimatedSize());
+  renderbuffer->SetAllocationFailed();
+  memory_type_tracker_->TrackMemAlloc(renderbuffer->EstimatedSize());
+  if (!renderbuffer->cleared()) {
+    ++num_uncleared_renderbuffers_;
+  }
+}
+
 void RenderbufferManager::SetCleared(Renderbuffer* renderbuffer,
                                      bool cleared) {
   DCHECK(renderbuffer);
diff --git a/gpu/command_buffer/service/renderbuffer_manager.h b/gpu/command_buffer/service/renderbuffer_manager.h
index 5eabeb9..f4dcc50 100644
--- a/gpu/command_buffer/service/renderbuffer_manager.h
+++ b/gpu/command_buffer/service/renderbuffer_manager.h
@@ -108,6 +108,8 @@
                             GLsizei width,
                             GLsizei height);
 
+  void SetAllocationFailed();
+
   void MarkAsDeleted() {
     client_id_ = 0;
   }
@@ -179,6 +181,8 @@
                             GLsizei width,
                             GLsizei height);
 
+  void SetAllocationFailed(Renderbuffer* renderbuffer);
+
   void SetCleared(Renderbuffer* renderbuffer, bool cleared);
 
   // Must call before destruction.
Loading diff…

Original Bug Report

reported by [email protected]

Potential state desync in GLES2 decoder via Adreno MSAA resize workaround on OOM

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.

Overview: The GLES2 validating decoder implements a workaround for Adreno GPUs that recreates multisampled renderbuffers during a resize. If the subsequent storage allocation fails due to GPU memory exhaustion, the decoder fails to invalidate its internal framebuffer completeness cache. This potentially allows a compromised renderer to bypass validation and dispatch draw commands against a malformed, zero-storage framebuffer, which may lead to memory corruption in the GPU process.

Affected files:

  • gpu/command_buffer/service/renderbuffer_manager.cc
  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/framebuffer_manager.cc

Estimated timestamp from git blame: 2017-06-19

Vulnerability Details

The GLES2 validating command decoder implements a workaround (multisample_renderbuffer_resize_emulation) for Adreno 4xx and 5xx GPUs to handle driver bugs when resizing multisampled renderbuffers.

When a WebGL client resizes an existing MSAA renderbuffer via glRenderbufferStorageMultisample[CHROMIUM|EXT], the decoder triggers Renderbuffer::RegenerateAndBindBackingObjectIfNeeded in gpu/command_buffer/service/renderbuffer_manager.cc. This function:

  1. Deletes the existing GL renderbuffer object.
  2. Generates a new, unallocated GL renderbuffer (size 0).
  3. Re-attaches this new object to all associated framebuffers using raw driver calls (glFramebufferRenderbufferEXT).
  4. Sets allocated_ = false.

Crucially, this workaround helper does not reset the renderbuffer’s internal state (such as cleared_) and does not call UnmarkAsComplete() on the attached Framebuffer objects to invalidate their cached completeness state.

Following this, the decoder attempts to allocate the requested memory via the driver. If this allocation fails (e.g., throwing GL_OUT_OF_MEMORY), GLES2DecoderImpl::DoRenderbufferStorageMultisampleCHROMIUM detects the error and skips the call to renderbuffer_manager()->SetInfoAndInvalidate(...).

Because SetInfoAndInvalidate is bypassed, the decoder’s internal cache is completely desynchronized from the driver:

  • The Framebuffer remains marked as complete in the FramebufferManager cache.
  • The Renderbuffer remains marked as cleared_ = true.
  • The underlying GL driver object is actually a zero-storage, unallocated attachment.

Subsequent commands like glDrawArrays or glBlitFramebuffer will pass GLES2DecoderImpl::CheckFramebufferValid because the stale cache returns true for IsComplete(), and the stale cleared_ flag bypasses ClearUnclearedAttachments. The validating decoder will pass the command directly to the Adreno driver with an invalid FBO, which can lead to undefined behavior or memory corruption in the GPU process.

Potential Attacker Steps

(Note: These are suggested steps based on static analysis; we do not have a working PoC yet.)

  1. From a malicious web page, create a WebGL context ensuring lose_context_when_out_of_memory is set to false (which is the default). This ensures the context survives allocation failures.
  2. Create a multisampled renderbuffer, attach it to a Framebuffer Object (FBO), and issue a glClear. This primes the decoder’s state, marking the FBO as complete and the attachment as cleared.
  3. Intentionally exhaust available GPU memory (e.g., by allocating numerous maximum-size textures).
  4. Attempt to resize the multisampled renderbuffer to a massive size. On an Adreno 4xx/5xx device, this triggers the multisample_renderbuffer_resize_emulation workaround, replacing the underlying GL object. The subsequent allocation will fail due to the OOM condition.
  5. Issue a glDrawArrays or glBlitFramebuffer using the desynchronized FBO. The validating decoder will trust its stale cache and forward the command to the driver, potentially corrupting driver state or GPU process memory.

Suggested Fix

Ensure that the internal state is properly invalidated when the multisample_renderbuffer_resize_emulation workaround is triggered, regardless of whether the subsequent allocation succeeds or fails.

In gpu/command_buffer/service/renderbuffer_manager.cc inside Renderbuffer::RegenerateAndBindBackingObjectIfNeeded, explicitly reset the renderbuffer’s metadata and invalidate the framebuffers:

  allocated_ = false;
  cleared_ = false;
  width_ = 0;
  height_ = 0;
  samples_ = 0;
  
  // Invalidate all attached framebuffers
  for (auto& point : framebuffer_attachment_points_) {
    point.first->UnmarkAsComplete();
  }
  return true;

Alternatively, DoRenderbufferStorageMultisampleCHROMIUM (and the EXT variant) should ensure that if the workaround mutated the backing object, SetInfoAndInvalidate (or a similar invalidation path) is called to reset the state back to unallocated/uncleared even if a GL error occurred.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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