Medium chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in GPU
DescriptionInteger overflow in GPU
ComponentGPU
Bug ClassInteger Overflow
Tracker513416699
Fix commitc031348f90c6 (chromium/src) +5/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Files Changed

  • gpu/command_buffer/service/framebuffer_manager.h
From c031348f90c6d59f704721af18eb7915e538b345 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Tue, 28 Jul 2026 05:34:11 -0700
Subject: [PATCH] gpu: Widen FramebufferManager state-change counter to 64 bits

FramebufferManager::IncFramebufferStateChangeCount() uses a counter that
is compared for equality against a snapshot taken when a framebuffer was
last marked complete. With a 32-bit counter (and the high bit pinned),
the value repeats every 2^31 increments, so after enough texture-level
updates IsComplete() can return true for a framebuffer whose attachments
have since changed, skipping IsPossiblyComplete() and leaving stale draw
buffer masks.

Widen the counter and per-framebuffer snapshot to uint64_t so the period
is unreachable in practice.

Fixed: 513416699
Change-Id: Ifd2211b5ec3d0dffeec19e01bb6b98444d94a77f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8156419
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1669380}
---

diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h
index 6b750cf2..66065a13 100644
--- a/gpu/command_buffer/service/framebuffer_manager.h
+++ b/gpu/command_buffer/service/framebuffer_manager.h
@@ -274,12 +274,12 @@
     TextureManager* texture_manager,
     bool cleared);
 
-  void MarkAsComplete(unsigned state_id) {
+  void MarkAsComplete(uint64_t state_id) {
     UpdateDrawBufferMasks();
     framebuffer_complete_state_count_id_ = state_id;
   }
 
-  unsigned framebuffer_complete_state_count_id() const {
+  uint64_t framebuffer_complete_state_count_id() const {
     return framebuffer_complete_state_count_id_;
   }
 
@@ -303,7 +303,7 @@
   bool has_been_bound_;
 
   // state count when this framebuffer was last checked for completeness.
-  unsigned framebuffer_complete_state_count_id_;
+  uint64_t framebuffer_complete_state_count_id_;
 
   // A map of attachments.
   using AttachmentMap =
@@ -400,7 +400,7 @@
   void IncFramebufferStateChangeCount() {
     // make sure this is never 0.
     framebuffer_state_change_count_ =
-        (framebuffer_state_change_count_ + 1) | 0x80000000U;
+        (framebuffer_state_change_count_ + 1) | (uint64_t{1} << 63);
   }
 
  private:
@@ -419,7 +419,7 @@
 
   // Incremented anytime anything changes that might effect framebuffer
   // state.
-  unsigned framebuffer_state_change_count_;
+  uint64_t framebuffer_state_change_count_;
 
   // Counts the number of Framebuffer allocated with 'this' as its manager.
   // Allows to check no Framebuffer will outlive this.
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential validation bypass in GLES2 FramebufferManager via 31-bit counter wrap

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A 31-bit rolling counter in the GLES2 validating command decoder can be wrapped by an attacker, leading to stale framebuffer state snapshots being treated as current. This allows a bypass of critical validation checks that ensure fragment shader output types match bound attachment formats. On platforms using the validating decoder, such as certain Android devices, this can result in undefined GPU operations and potential memory corruption in the unsandboxed GPU process.

Affected files:

  • gpu/command_buffer/service/framebuffer_manager.h
  • gpu/command_buffer/service/framebuffer_manager.cc
  • gpu/command_buffer/service/texture_manager.cc
  • gpu/command_buffer/service/gles2_cmd_decoder.cc

Estimated timestamp from git blame: 2011-12-20

Summary

A potential logic flaw in the GLES2 validating command decoder’s FramebufferManager allows an attacker to bypass framebuffer validation through a 31-bit integer wrap. By wrapping a rolling state counter, an attacker can make a stale framebuffer state snapshot appear current. This bypasses the re-calculation of internal state masks (such as draw_buffer_type_mask_), allowing fragment shaders to perform undefined operations against bound attachments, which can lead to memory corruption in native GPU drivers.

Root Cause Analysis

The FramebufferManager maintains a global rolling counter, framebuffer_state_change_count_, which is incremented whenever a resource (texture or renderbuffer) is modified in a way that might affect framebuffer completeness. Framebuffers store a snapshot of this counter (framebuffer_complete_state_count_id_) when they are validated. If the snapshot matches the global counter, the decoder short-circuits validation and uses cached state masks.

The counter increment logic in gpu/command_buffer/service/framebuffer_manager.h ensures the 31st bit is always set:

void IncFramebufferStateChangeCount() {
  framebuffer_state_change_count_ =
      (framebuffer_state_change_count_ + 1) | 0x80000000U;
}

This results in a cycle of exactly 2^31 distinct values.

A vulnerability arises from an asymmetry in how textures and renderbuffers handle invalidation. When a renderbuffer is modified, it iterates through its attachment points and explicitly calls UnmarkAsComplete() on attached framebuffers. However, when a texture’s level information is re-specified (e.g., via glTexImage2D), Texture::SetLevelInfo in gpu/command_buffer/service/texture_manager.cc only increments the global counter and fails to invalidate the attached framebuffers directly. This allows a framebuffer to retain a stale snapshot that can eventually be collided with if the global counter wraps.

Potential Attack Sequence

An attacker could potentially follow these steps to trigger the bypass:

  1. Setup: Create a Framebuffer (FBO_A) and attach a FLOAT-renderable texture (Tex_1).
  2. Initialize Snapshot: Trigger validation of FBO_A (e.g., via a dummy draw call). The decoder caches the “FLOAT” type in draw_buffer_type_mask_ and stores the current global counter (G) as a snapshot.
  3. Modify Texture: Re-specify Tex_1 to an INTEGER format using glTexImage2D. This increments the global counter but does not invalidate FBO_A’s snapshot.
  4. Wrap Counter: Repeatedly modify a second, dummy texture to increment the global counter 2^31 - 1 times until it wraps back to the value G.
  5. Bypass Validation: Issue a draw call with FBO_A bound using a shader that outputs FLOAT values. The decoder sees the snapshot matches the current counter G, skips validation, and relies on the stale “FLOAT” mask.
  6. Undefined Operation: The ValidateAndAdjustDrawBuffers check incorrectly passes, and the FLOAT shader output is forwarded to the INTEGER attachment. This undefined operation can trigger memory corruption in the native GPU driver.

Impact

On Android, the GPU process is effectively unsandboxed. A successful exploit leading to memory corruption in a native GPU driver could result in a full sandbox escape and device compromise. While the validating decoder is primarily used on specific hardware where the passthrough decoder is blocklisted, it remains a critical security boundary for those devices.

Suggested Fix

  1. Update the Texture class to track its framebuffer attachment points, similar to the Renderbuffer class, and ensure UnmarkAsComplete() is called on all attached framebuffers whenever texture state changes.
  2. Alternatively, increase the size of the framebuffer_state_change_count_ to 64 bits to make wrapping computationally infeasible.

Note: These steps and the resulting vulnerability are potential findings based on code analysis; a functional proof of concept has not yet been executed.

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.

View on issue tracker