Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker520656244
Fix commit092d0d9b5333 (chromium/src) +479/-13
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
Creditedanonymous
Reported2026-06-07 (per vendor advisory)
Fix landed2026-06-12 (public git)
Disclosed2026-06-23
Patch gap11 days — fix public in source before the advisory

Background

FBO (Framebuffer Object)
a GL container that binds color, depth, and stencil attachments as render targets; each attachment references texture or renderbuffer storage.
Depth/stencil attachment redefinition
reallocating a TextureRef or Renderbuffer (e.g. via TexImage/RenderbufferStorage) that is bound at GL_DEPTH_ATTACHMENT or GL_STENCIL_ATTACHMENT, which frees and replaces the old backing storage.
`recreate_fbo_upon_flush`
the prior driver workaround, disabled here, that rebuilt FBOs on flush rather than surgically detaching before a redefine.
`ScopedDepthStencilReattacher`
an RAII helper in gles2_cmd_decoder.cc that detaches a depth/stencil attachment from every referencing FBO before a redefine and reattaches it after.

Root Cause Analysis

On certain vendors’ GPUs, redefining (reallocating) a depth or stencil texture/renderbuffer while it remains attached to one or more FBOs leaves those FBOs internally referencing the freed attachment storage. The driver does not re-validate or refresh the attachment on redefinition, so the FBO retains a dangling reference to memory that has been released. When such an FBO is next bound and used for rendering, the driver dereferences the stale attachment, producing a use-after-free inside the GPU process. Because a single texture or renderbuffer can be shared across multiple FBOs (both bound and unbound), any of those FBOs can trigger the dangling access, not just the currently bound one.

The fix enumerates every FBO holding the object at GL_DEPTH_ATTACHMENT/GL_STENCIL_ATTACHMENT via FramebufferManager::GetBindingFramebuffersForTexture/GetBindingFramebuffersForRenderbuffer, detaches them before the redefine, and reattaches after so no FBO ever holds a reference to freed storage across the reallocation.

Key insight
The unsafe state is a driver-side dangling attachment that persists across a redefine of shared depth/stencil storage; detaching from all referencing FBOs before reallocation and reattaching after forces the driver to re-bind fresh storage, eliminating the stale reference.

Attack Path

  1. Create shared attachment Allocate a depth or stencil texture/renderbuffer and attach it at GL_DEPTH_ATTACHMENT/GL_STENCIL_ATTACHMENT on one or more FBOs.
  2. Redefine while attached Issue a redefine (e.g. TexImage/RenderbufferStorage) that reallocates the attachment’s backing storage while it remains attached, freeing the old storage on the affected driver.
  3. Dangling reference The referencing FBOs now point at the freed storage, since the driver never refreshed the attachment.
  4. Trigger use Bind an affected FBO and issue a draw or clear, causing the driver to dereference the freed attachment.
  5. Exploit UAF Reclaim the freed region with controlled data to corrupt GPU-process memory.

Impact Assessment

This is a use-after-free in the GPU process reachable from WebGL, which is exposed to untrusted web content. Successful exploitation could corrupt GPU-process memory and, chained further, lead to sandbox escape or remote code execution. Reliability depends on the affected drivers, but the attacker fully controls attachment allocation, redefinition, and FBO usage timing.

Changed Functions

FunctionChangeNotes
TextureAttachment
gpu/command_buffer/service/framebuffer_manager.cc
modified
GetBindingFramebuffersForTexture
gpu/command_buffer/service/framebuffer_manager.cc
modified
if
gpu/command_buffer/service/framebuffer_manager.cc
modified
for
gpu/command_buffer/service/framebuffer_manager.cc
modified
ScopedDepthStencilReattacher
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified
if
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified

Files Changed

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

Audit Directions

  • Redefine-while-attached
    Audit every path that reallocates texture or renderbuffer storage (TexImage, TexStorage, RenderbufferStorage) for objects that may still be attached to any FBO, not just the currently bound one.
  • Shared-attachment enumeration
    Verify that lifecycle operations on attachments consult all referencing FBOs (via GetBindingFramebuffersForTexture/GetBindingFramebuffersForRenderbuffer) rather than assuming a single owner.
  • Driver workaround coverage
    Confirm the vendor gating for ScopedDepthStencilReattacher matches the affected driver set and that disabling recreate_fbo_upon_flush leaves no gap on other quirky drivers.
From 092d0d9b5333e7d68a01f7e0dc44fd3c4f8a5b32 Mon Sep 17 00:00:00 2001
From: Ken Russell <[email protected]>
Date: Fri, 12 Jun 2026 16:27:04 -0700
Subject: [PATCH] Add workaround reattaching depth/stencil attachments.

When a depth/stencil attachment to an FBO (bound or unbound) is
redefined, this workaround first unbinds it from all FBOs and then
reattaches it afterward. Apply this to certain vendors' GPUs.

Disable the previous recreate_fbo_upon_flush workaround, as it has
been demonstrated to be ineffective. Do not remove the code yet as the
new approach must be tested first.

Added tests ensuring the workaround has no unexpected side-effects.

Co-authored with jetski-cli.

Bug: 520656244
Change-Id: Ieaad9410b36092aff9d0f0f46c2373911b24d0ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7910927
Commit-Queue: Kenneth Russell <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1646310}
---

diff --git a/gpu/command_buffer/service/framebuffer_manager.cc b/gpu/command_buffer/service/framebuffer_manager.cc
index 430d8a2d6..2dce5944 100644
--- a/gpu/command_buffer/service/framebuffer_manager.cc
+++ b/gpu/command_buffer/service/framebuffer_manager.cc
@@ -138,6 +138,10 @@
   scoped_refptr<Renderbuffer> renderbuffer_;
 };
 
+GLint Framebuffer::Attachment::layer() const {
+  return 0;
+}
+
 class TextureAttachment
     : public Framebuffer::Attachment {
  public:
@@ -188,7 +192,7 @@
 
   GLsizei samples() const override { return samples_; }
 
-  GLint layer() const { return layer_; }
+  GLint layer() const override { return layer_; }
 
   GLenum target() const override { return target_; }
 
@@ -1237,5 +1241,46 @@
       framebuffer_state_change_count_;
 }
 
+std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>>
+FramebufferManager::GetBindingFramebuffersForTexture(TextureRef* texture_ref) {
+  std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>> result;
+  if (!texture_ref) {
+    return result;
+  }
+  for (const auto& pair : framebuffers_) {
+    Framebuffer* framebuffer = pair.second.get();
+    for (GLenum attachment_point :
+         {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}) {
+      const Framebuffer::Attachment* attachment =
+          framebuffer->GetAttachment(attachment_point);
+      if (attachment && attachment->IsTexture(texture_ref)) {
+        result.push_back({pair.second, attachment_point});
+      }
+    }
+  }
+  return result;
+}
+
+std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>>
+FramebufferManager::GetBindingFramebuffersForRenderbuffer(
+    Renderbuffer* renderbuffer) {
+  std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>> result;
+  if (!renderbuffer) {
+    return result;
+  }
+  for (const auto& pair : framebuffers_) {
+    Framebuffer* framebuffer = pair.second.get();
+    for (GLenum attachment_point :
+         {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}) {
+      const Framebuffer::Attachment* attachment =
+          framebuffer->GetAttachment(attachment_point);
+      if (attachment && attachment->IsRenderbuffer(renderbuffer)) {
+        result.push_back({pair.second, attachment_point});
+      }
+    }
+  }
+  return result;
+}
+
 }  // namespace gles2
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h
index c77b6bf2..376149ba 100644
--- a/gpu/command_buffer/service/framebuffer_manager.h
+++ b/gpu/command_buffer/service/framebuffer_manager.h
@@ -58,6 +58,7 @@
     virtual bool IsRenderbuffer(Renderbuffer* renderbuffer) const = 0;
     virtual bool IsSameAttachment(const Attachment* attachment) const = 0;
     virtual bool Is3D() const = 0;
+    virtual GLint layer() const;
 
     // If it's a 3D texture attachment, return true if
     // FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER is smaller than the number of
@@ -383,6 +384,12 @@
 
   bool IsComplete(const Framebuffer* framebuffer);
 
+  std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>>
+  GetBindingFramebuffersForTexture(TextureRef* texture_ref);
+
+  std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>>
+  GetBindingFramebuffersForRenderbuffer(Renderbuffer* renderbuffer);
+
   void IncFramebufferStateChangeCount() {
     // make sure this is never 0.
     framebuffer_state_change_count_ =
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index e91a6778..38ebebae 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -726,6 +726,7 @@
   friend class ScopedFramebufferCopyBinder;
   friend class BackFramebuffer;
   friend class BackTexture;
+  friend class ScopedDepthStencilReattacher;
 
   enum FramebufferOperation {
     kFramebufferDiscard,
@@ -2550,6 +2551,157 @@
   ERRORSTATE_CLEAR_REAL_GL_ERRORS(error_state_, function_name_);
 }
 
+class ScopedDepthStencilReattacher {
+ public:
+  ScopedDepthStencilReattacher(GLES2DecoderImpl* decoder,
+                               TextureRef* texture_ref);
+  ScopedDepthStencilReattacher(GLES2DecoderImpl* decoder,
+                               Renderbuffer* renderbuffer);
+  ~ScopedDepthStencilReattacher();
+
+ private:
+  struct SavedAttachmentInfo {
+    scoped_refptr<Framebuffer> framebuffer;
+    GLenum attachment_point;
+    GLenum texture_target = 0;
+    GLint texture_level = 0;
+    GLsizei texture_samples = 0;
+    GLint texture_layer = 0;
+    bool is_texture = false;
+    bool is_renderbuffer = false;
+  };
+
+  void Initialize();
+
+  raw_ptr<GLES2DecoderImpl> decoder_;
+  raw_ptr<TextureRef> texture_ref_ = nullptr;
+  raw_ptr<Renderbuffer> renderbuffer_ = nullptr;
+  std::vector<SavedAttachmentInfo> saved_attachments_;
+  scoped_refptr<Framebuffer> old_read_fbo_;
+  scoped_refptr<Framebuffer> old_draw_fbo_;
+};
+
+ScopedDepthStencilReattacher::ScopedDepthStencilReattacher(
+    GLES2DecoderImpl* decoder,
+    TextureRef* texture_ref)
+    : decoder_(decoder), texture_ref_(texture_ref) {
+  Initialize();
+}
+
+ScopedDepthStencilReattacher::ScopedDepthStencilReattacher(
+    GLES2DecoderImpl* decoder,
+    Renderbuffer* renderbuffer)
+    : decoder_(decoder), renderbuffer_(renderbuffer) {
+  Initialize();
+}
+
+void ScopedDepthStencilReattacher::Initialize() {
+  if (!decoder_->workarounds().reattach_fbo_depth_stencil_on_reallocation) {
+    return;
+  }
+
+  std::vector<std::pair<scoped_refptr<Framebuffer>, GLenum>> detached_fbos;
+  if (texture_ref_) {
+    detached_fbos =
+        decoder_->framebuffer_manager()->GetBindingFramebuffersForTexture(
+            texture_ref_);
+  } else if (renderbuffer_) {
+    detached_fbos =
+        decoder_->framebuffer_manager()->GetBindingFramebuffersForRenderbuffer(
+            renderbuffer_);
+  }
+
+  if (detached_fbos.empty()) {
+    return;
+  }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/tests/gl_clear_framebuffer_unittest.cc b/gpu/command_buffer/tests/gl_clear_framebuffer_unittest.cc
index 123aa81..704c02a 100644
--- a/gpu/command_buffer/tests/gl_clear_framebuffer_unittest.cc
+++ b/gpu/command_buffer/tests/gl_clear_framebuffer_unittest.cc
@@ -706,4 +706,254 @@
   glDeleteRenderbuffers(1, &rb);
 }
 
+class GLReattachFboDepthStencilTest : public GLClearFramebufferTest {
+ protected:
+  void SetUp() override {
+    GpuDriverBugWorkarounds workarounds;
+    if (GetParam()) {
+      workarounds.reattach_fbo_depth_stencil_on_reallocation = true;
+    }
+    gl_.InitializeWithWorkarounds(GetGlManagerOptions(), workarounds);
+  }
+
+  void RunReattachFboDepthStencilTest(bool use_texture,
+                                      bool bind_fbo_during_reallocation);
+};
+
+INSTANTIATE_TEST_SUITE_P(GLReattachFboDepthStencilTestWithParam,
+                         GLReattachFboDepthStencilTest,
+                         ::testing::Bool());
+
+void GLReattachFboDepthStencilTest::RunReattachFboDepthStencilTest(
+    bool use_texture,
+    bool bind_fbo_during_reallocation) {
+  GLuint fbo = 0;
+  glGenFramebuffers(1, &fbo);
+  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+  GLuint color_tex = 0;
+  glGenTextures(1, &color_tex);
+  glBindTexture(GL_TEXTURE_2D, color_tex);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+               nullptr);
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                         color_tex, 0);
+
+  GLuint depth_obj = 0;
+  if (use_texture) {
+    glGenTextures(1, &depth_obj);
+    glBindTexture(GL_TEXTURE_2D, depth_obj);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 16, 16, 0,
+                 GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+                           depth_obj, 0);
+  } else {
+    glGenRenderbuffers(1, &depth_obj);
+    glBindRenderbuffer(GL_RENDERBUFFER, depth_obj);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+                              GL_RENDERBUFFER, depth_obj);
+  }
+
+  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
+            glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+  // Clear color to Green, depth to 1.0 (far).
+  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+  glClearDepthf(1.0f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  const uint8_t kGreen[] = {0, 255, 0, 255};
+  const uint8_t kRed[] = {255, 0, 0, 255};
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  // Enable depth test. Draw Red quad at depth 0.5. Should pass (0.5 < 1.0).
+  glEnable(GL_DEPTH_TEST);
+  glDepthFunc(GL_LESS);
+
+  InitDraw();
+  SetDrawDepth(0.5f);
+  SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
+  DrawQuad();
+
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr));
+
+  // Clear color to Green, depth to 0.0 (near).
+  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+  glClearDepthf(0.0f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  // Draw Red quad at depth 0.5. Should fail (0.5 is not < 0.0).
+  DrawQuad();
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  if (!bind_fbo_during_reallocation) {
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  }
+
+  // Redefine depth attachment (reallocate to 16x16).
+  if (use_texture) {
+    glBindTexture(GL_TEXTURE_2D, depth_obj);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 16, 16, 0,
+                 GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
+  } else {
+    glBindRenderbuffer(GL_RENDERBUFFER, depth_obj);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 16, 16);
+  }
+
+  if (!bind_fbo_during_reallocation) {
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+  }
+
+  // Clear depth to 1.0.
+  glClearDepthf(1.0f);
+  glClear(GL_DEPTH_BUFFER_BIT);
+
+  // Draw Red quad at depth 0.5. Should pass (0.5 < 1.0).
+  SetDrawDepth(0.5f);
+  SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
+  DrawQuad();
+
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr));
+
+  glDeleteFramebuffers(1, &fbo);
+  glDeleteTextures(1, &color_tex);
+  if (use_texture) {
+    glDeleteTextures(1, &depth_obj);
+  } else {
+    glDeleteRenderbuffers(1, &depth_obj);
+  }
+}
+
+TEST_P(GLReattachFboDepthStencilTest, TextureReallocationBound) {
+  RunReattachFboDepthStencilTest(true, true);
+}
+
+TEST_P(GLReattachFboDepthStencilTest, TextureReallocationUnbound) {
+  RunReattachFboDepthStencilTest(true, false);
+}
+
+TEST_P(GLReattachFboDepthStencilTest, RenderbufferReallocationBound) {
+  RunReattachFboDepthStencilTest(false, true);
+}
+
+TEST_P(GLReattachFboDepthStencilTest, RenderbufferReallocationUnbound) {
+  RunReattachFboDepthStencilTest(false, false);
+}
+
+class ES3ReattachFboDepthStencilTest : public GLReattachFboDepthStencilTest {
+ protected:
+  GLManager::Options GetGlManagerOptions() override {
+    GLManager::Options options;
+    options.context_type = CONTEXT_TYPE_OPENGLES3;
+    return options;
+  }
+
+  bool ShouldSkipTest() const {
+    return (!gl_.decoder() || !gl_.decoder()->GetContextGroup());
+  }
+
+  void RunTexImage2DToTexStorage2DTest(bool bind_fbo_during_reallocation);
+};
+
+INSTANTIATE_TEST_SUITE_P(ES3ReattachFboDepthStencilTestWithParam,
+                         ES3ReattachFboDepthStencilTest,
+                         ::testing::Bool());
+
+void ES3ReattachFboDepthStencilTest::RunTexImage2DToTexStorage2DTest(
+    bool bind_fbo_during_reallocation) {
+  if (ShouldSkipTest()) {
+    return;
+  }
+
+  GLuint fbo = 0;
+  glGenFramebuffers(1, &fbo);
+  glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+  GLuint color_tex = 0;
+  glGenTextures(1, &color_tex);
+  glBindTexture(GL_TEXTURE_2D, color_tex);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+               nullptr);
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                         color_tex, 0);
+
+  GLuint depth_tex = 0;
+  glGenTextures(1, &depth_tex);
+  glBindTexture(GL_TEXTURE_2D, depth_tex);
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 16, 16, 0,
+               GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
+  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
+                         depth_tex, 0);
+
+  EXPECT_EQ(static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE),
+            glCheckFramebufferStatus(GL_FRAMEBUFFER));
+
+  // Clear color to Green, depth to 1.0 (far).
+  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+  glClearDepthf(1.0f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  const uint8_t kGreen[] = {0, 255, 0, 255};
+  const uint8_t kRed[] = {255, 0, 0, 255};
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  // Enable depth test. Draw Red quad at depth 0.5. Should pass.
+  glEnable(GL_DEPTH_TEST);
+  glDepthFunc(GL_LESS);
+
+  InitDraw();
+  SetDrawDepth(0.5f);
+  SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
+  DrawQuad();
+
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr));
+
+  // Clear color to Green, depth to 0.0 (near).
+  glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+  glClearDepthf(0.0f);
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  // Draw Red quad at depth 0.5. Should fail.
+  DrawQuad();
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kGreen, nullptr));
+
+  if (!bind_fbo_during_reallocation) {
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+  }
+
+  // Redefine depth attachment via glTexStorage2D.
+  glBindTexture(GL_TEXTURE_2D, depth_tex);
+  glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_DEPTH_COMPONENT16, 16, 16);
+
+  if (!bind_fbo_during_reallocation) {
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+  }
+
+  // Clear depth to 1.0.
+  glClearDepthf(1.0f);
+  glClear(GL_DEPTH_BUFFER_BIT);
+
+  // Draw Red quad at depth 0.5. Should pass.
+  SetDrawDepth(0.5f);
+  SetDrawColor(1.0f, 0.0f, 0.0f, 1.0f);
+  DrawQuad();
+
+  EXPECT_TRUE(GLTestHelper::CheckPixels(0, 0, 1, 1, 0, kRed, nullptr));
+
+  glDeleteFramebuffers(1, &fbo);
+  glDeleteTextures(1, &color_tex);
+  glDeleteTextures(1, &depth_tex);
+}
+
+TEST_P(ES3ReattachFboDepthStencilTest, TexImage2DToTexStorage2DBound) {
+  RunTexImage2DToTexStorage2DTest(true);
+}
+
+TEST_P(ES3ReattachFboDepthStencilTest, TexImage2DToTexStorage2DUnbound) {
+  RunTexImage2DToTexStorage2DTest(false);
+}
+
 }  // namespace gpu
Loading diff…

Original Bug Report

reported by [email protected]

New UAF trigger for https://issues.chromium.org/issues/493747593 (Fullchain RCE)

VULNERABILITY DETAILS

https://issues.chromium.org/issues/493747593 There is something wrong with the original Qualcomm suggested fix, they don’t quite understand it yet, I retriggered it with the help of LLM.

Use-after-free in Qualcomm Adreno GPU driver (libGLESv2_adreno.so) reachable from web contents via WebGL2 texStorage2D on FBO-attached depth texture. The driver frees the internal 0x80-byte storage container but leaves the FBO’s cached pointer (fbo+0x2a8) dangling. A subsequent glClear triggers the driver’s completeness check which dereferences the dangling pointer, allowing heap spray reoccupation and arbitrary code execution in Chrome’s GPU process.

The bug is in the Adreno GPU driver, reachable through Chrome Browser from remote web content without any user interaction beyond page navigation. No special flags, no renderer patches, no extensions required.

VERSION Chrome Version: 148.0.7778.217 stable

Note that this is the newest version I got on Samsung s25

Operating System: Android 16 (Samsung Galaxy S25 / SM-S9310, Adreno 830 GPU, firmware S9310ZCSBCZE1, driver build f61dec9117) samsung/pa1qzcx/pa1q:16/BP4A.251205.006/S9310ZCSBCZE1_CHCBCZE1:user/release-keys

REPRODUCTION CASE

  1. Serve the attached files with Python:
python3 -m http.server 8899 --bind 127.0.0.1
  1. Set up adb reverse:
adb reverse tcp:8899 tcp:8899
  1. Open in Chrome (no flags):

access http://127.0.0.1:8899/rce1_off.html

  1. GPU process crashes within seconds with fault addr 0x0043434343434343.

Trigger sequence in the PoC:

  • Create depth texture T (texImage2D DEPTH_COMPONENT24 64x64)
  • Create FBO A, attach T as depth attachment
  • bindFramebuffer(null) — cross-FBO trick
  • texStorage2D(T, 1, DEPTH_COMPONENT24, 128, 128) — frees old container, fbo A+0x2a8 dangles
  • Heap spray: re-texImage2D 1024 RGBA16F W=15 textures via PBO — reoccupies freed container with 0x4343
  • bindFramebuffer(A), checkFramebufferStatus, clear(DEPTH_BUFFER_BIT) — driver reads dangling container → crash

FOR CRASHES

Type of crash: GPU process (privileged_process0) Crash State: Original

06-07 17:41:51.703 18049 18049 F DEBUG   : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
06-07 17:41:51.703 18049 18049 F DEBUG   : Build fingerprint: 'samsung/pa1qzcx/pa1q:16/BP4A.251205.006/S9310ZCSBCZE1_CHCBCZE1:user/release-keys'
06-07 17:41:51.703 18049 18049 F DEBUG   : Kernel Release: '6.6.98-android15-8-p93745ce-abogkiS9310ZCSBCZE1-4k'
06-07 17:41:51.703 18049 18049 F DEBUG   : Revision: '11'
06-07 17:41:51.703 18049 18049 F DEBUG   : ABI: 'arm64'
06-07 17:41:51.703 18049 18049 F DEBUG   : Processor: '5'
06-07 17:41:51.703 18049 18049 F DEBUG   : Timestamp: 2026-06-07 17:41:51.580909379+0800
06-07 17:41:51.703 18049 18049 F DEBUG   : Process uptime: 7s
06-07 17:41:51.703 18049 18049 F DEBUG   : Executable: /system/bin/app_process64
06-07 17:41:51.703 18049 18049 F DEBUG   : Cmdline: com.android.chrome:privileged_process0
06-07 17:41:51.703 18049 18049 F DEBUG   : pid: 17705, tid: 17730, name: CrGpuMain  >>> com.android.chrome:privileged_process0 <<<
06-07 17:41:51.703 18049 18049 F DEBUG   : uid: 10422
06-07 17:41:51.703 18049 18049 F DEBUG   : tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
06-07 17:41:51.703 18049 18049 F DEBUG   : pac_enabled_keys: 000000000000000f (PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY)
06-07 17:41:51.703 18049 18049 F DEBUG   : esr: 0000000092000004 (Data Abort Exception 0x24)
06-07 17:41:51.703 18049 18049 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0043434343434343 (read)
06-07 17:41:51.703 18049 18049 F DEBUG   :     x0  b40000741bc7b360  x1  0000000000008d40  x2  0000000000000000  x3  47cb497e67487fe4
06-07 17:41:51.703 18049 18049 F DEBUG   :     x4  0000000000000003  x5  0000000000008d40  x6  a635992e0c8cd953  x7  b40000752bdf09f0
06-07 17:41:51.703 18049 18049 F DEBUG   :     x8  0000000000008cd5  x9  0000000000008d40  x10 0000000000000001  x11 0000006e0008e218
06-07 17:41:51.703 18049 18049 F DEBUG   :     x12 0000000000000000  x13 0000006e00183400  x14 000000000000006a  x15 0000000000000001
06-07 17:41:51.703 18049 18049 F DEBUG   :     x16 0000007394a1ef40  x17 000000769dd5591c  x18 00000073427dc000  x19 0000006e030aa300
06-07 17:41:51.703 18049 18049 F DEBUG   :     x20 0000000000000001  x21 0000000043434343  x22 0000006e030aa330  x23 0000000000000000
06-07 17:41:51.703 18049 18049 F DEBUG   :     x24 0000006e030aa340  x25 0000006e030aa330  x26 0000006e030aa340  x27 4343434343434343
06-07 17:41:51.703 18049 18049 F DEBUG   :     x28 0000000000000004  x29 0000007343905550
06-07 17:41:51.703 18049 18049 F DEBUG   :     lr  005ff5f2b955df24  sp  0000007343905530  pc  0000007394a1f208  pst 0000000020001000
06-07 17:41:51.703 18049 18049 F DEBUG   :     esr 0000000092000004
06-07 17:41:51.703 18049 18049 F DEBUG   : 29 total frames
06-07 17:41:51.703 18049 18049 F DEBUG   : backtrace:
06-07 17:41:51.703 18049 18049 F DEBUG   :       #00 pc 000000000021c208  /vendor/lib64/egl/libGLESv2_adreno.so (!!!0000!804c660e2d6053be57921a5fd6d4bb!f61dec9117!+376) (BuildId: 7930b2c86c3cf072ea40709321b1af75)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #01 pc 000000000918df20  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #02 pc 0000000009197ad0  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #03 pc 00000000091bb004  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #04 pc 00000000077f9ff0  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #05 pc 00000000077f9528  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #06 pc 00000000077f9254  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #07 pc 00000000077f90fc  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #08 pc 00000000077f906c  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #09 pc 00000000077a5da8  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #10 pc 00000000078e7848  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #11 pc 0000000005c7b6ec  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #12 pc 0000000005c3afb0  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #13 pc 0000000005c3ab18  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #14 pc 00000000074e6ad8  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #15 pc 0000000005c9f038  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #16 pc 0000000005c026b0  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #17 pc 0000000005c012f0  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #18 pc 0000000005c01000  /data/app/~~DVixuWwBoqC_JU6cqq4w-A==/com.google.android.trichromelibrary_777821733-W-KsS5MHnN-okuo11GfeGQ==/base.apk!libmonochrome_64.so (offset 0x948000) (BuildId: 21d860e69ceda9d90a1c4aac6abe65adee735df4)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #19 pc 00000000002e4570  /system/framework/arm64/boot.oat (art_jni_trampoline+112) (BuildId: a2d6a559f5778b9b6315baec706b53a709156608)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #20 pc 00000000006680e8  /apex/com.android.art/lib64/libart.so (nterp_helper+152) (BuildId: 11dac5edfc6a4111efd44d494b5f3993)
06-07 17:41:51.703 18049 18049 F DEBUG   :       #21 pc 00000000000e4552  /data/app/~~cL9Qcf_m-_LsOdBMyG4-UA==/com.android.chrome-15HfHhAmKbAZwTLL3jzWpA==/base.apk (offset 0x218000) (sr3.run+574)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #22 pc 00000000000a95e0  /system/framework/arm64/boot.oat (java.lang.Thread.run+64) (BuildId: a2d6a559f5778b9b6315baec706b53a709156608)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #23 pc 00000000002aaf94  /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+612) (BuildId: 11dac5edfc6a4111efd44d494b5f3993)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #24 pc 0000000000270940  /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+220) (BuildId: 11dac5edfc6a4111efd44d494b5f3993)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #25 pc 00000000004bee24  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallback(void*)+1184) (BuildId: 11dac5edfc6a4111efd44d494b5f3993)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #26 pc 00000000004be974  /apex/com.android.art/lib64/libart.so (art::Thread::CreateCallbackWithUffdGc(void*)+8) (BuildId: 11dac5edfc6a4111efd44d494b5f3993)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #27 pc 00000000000866ec  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*) (.__uniq.67847048707805468364044055584648682506)+184) (BuildId: 0669c520dd58ab0634801cae537d6b06)
06-07 17:41:51.704 18049 18049 F DEBUG   :       #28 pc 000000000007900c  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+68) (BuildId: 0669c520dd58ab0634801cae537d6b06)
INFO:root:Stack found. Symbolizing...
Symbolizing stack using ABI=arm64
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x43434343434343 in tid 31376 (CrGpuMain), pid 31303 (ileged_process0)
Build fingerprint: 'samsung/pa1qzcx/pa1q:16/BP4A.251205.006/S9310ZCSBCZE1_CHCBCZE1:user/release-keys'
Revision: '11'
pid: 31303, tid: 31376, name: CrGpuMain  >>> org.chromium.chrome:privileged_process0 <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0043434343434343 (read)

Stack Trace:
  RELADDR   FUNCTION                                                                          FILE:LINE
  000000000021c208  !!!0000!804c660e2d6053be57921a5fd6d4bb!f61dec9117!+376) (BuildId: 7930b2c86c3cf072ea40709321b1af75  /vendor/lib64/egl/libGLESv2_adreno.so
  00000000093c6de8  gpu::gles2::Framebuffer::GetStatus(gpu::gles2::TextureManager*, unsigned int) const  ../../gpu/command_buffer/service/framebuffer_manager.cc:886:19
  v------>  gpu::gles2::GLES2DecoderImpl::DoCheckFramebufferStatus(unsigned int)              ../../gpu/command_buffer/service/gles2_cmd_decoder.cc:7182:23
  00000000093d2194  gpu::gles2::GLES2DecoderImpl::HandleCheckFramebufferStatus(unsigned int, void const volatile*)  ../../gpu/command_buffer/service/gles2_cmd_decoder_autogen.h:347:17
  00000000093f28a8  gpu::error::Error gpu::gles2::GLES2DecoderImpl::DoCommandsImpl<false>(unsigned int, void const volatile*, int, int*)  ../../gpu/command_buffer/service/gles2_cmd_decoder.cc:4756:18
  0000000003f202c4  gpu::CommandBufferService::Flush(int, gpu::AsyncAPIInterface*)                    ../../gpu/command_buffer/service/command_buffer_service.cc:267:35
  00000000094d3a10  gpu::CommandBufferStub::OnAsyncFlush(int, unsigned int, std::__Cr::vector<gpu::SyncToken, std::__Cr::allocator<gpu::SyncToken>> const&)  ../../gpu/ipc/service/command_buffer_stub.cc:504:22
  00000000094d3714  gpu::CommandBufferStub::ExecuteDeferredRequest(gpu::mojom::DeferredCommandBufferRequestParams&, gpu::FenceSyncReleaseDelegate*)  ../../gpu/ipc/service/command_buffer_stub.cc:173:7
  00000000094d90d8  gpu::GpuChannel::ExecuteDeferredRequest(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*)  ../../gpu/ipc/service/gpu_channel.cc:854:13
  v------>  void base::internal::DecayedFunctorTraits<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>::Invoke<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*>(void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&, gpu::FenceSyncReleaseDelegate*&&)  ../../base/functional/bind_internal.h:740:12
  v------>  void base::internal::InvokeHelper<true, base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, void, 0ul, 1ul>::MakeItSo<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), std::__Cr::tuple<base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, gpu::FenceSyncReleaseDelegate*>(void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), std::__Cr::tuple<base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>&&, gpu::FenceSyncReleaseDelegate*&&)  ../../base/functional/bind_internal.h:956:5
  v------>  void base::internal::Invoker<base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, base::internal::BindState<true, true, false, void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, void (gpu::FenceSyncReleaseDelegate*)>::RunImpl<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), std::__Cr::tuple<base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, 0ul, 1ul>(void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), std::__Cr::tuple<base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>&&, std::__Cr::integer_sequence<unsigned long, 0ul, 1ul>, gpu::FenceSyncReleaseDelegate*&&)  ../../base/functional/bind_internal.h:1069:14
  00000000094dbd7c  base::internal::Invoker<base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, base::internal::BindState<true, true, false, void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, void (gpu::FenceSyncReleaseDelegate*)>::RunOnce(base::internal::BindStateBase*, gpu::FenceSyncReleaseDelegate*)  ../../base/functional/bind_internal.h:982:12
  v------>  base::OnceCallback<void (media::DemuxerStream*)>::Run(media::DemuxerStream*) &&   ../../base/functional/callback.h:155:12
  v------>  void base::internal::DecayedFunctorTraits<base::OnceCallback<void (media::DemuxerStream*)>, media::DemuxerStream*&&>::Invoke<base::OnceCallback<void (media::DemuxerStream*)>, media::DemuxerStream*>(base::OnceCallback<void (media::DemuxerStream*)>&&, media::DemuxerStream*&&)  ../../base/functional/bind_internal.h:815:49
  v------>  void base::internal::InvokeHelper<false, base::internal::FunctorTraits<base::OnceCallback<void (media::DemuxerStream*)>&&, media::DemuxerStream*&&>, void, 0ul>::MakeItSo<base::OnceCallback<void (media::DemuxerStream*)>, std::__Cr::tuple<base::internal::UnretainedWrapper<media::DemuxerStream, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>>(base::OnceCallback<void (media::DemuxerStream*)>&&, std::__Cr::tuple<base::internal::UnretainedWrapper<media::DemuxerStream, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>&&)  ../../base/functional/bind_internal.h:932:12
  v------>  void base::internal::Invoker<base::internal::FunctorTraits<base::OnceCallback<void (media::DemuxerStream*)>&&, media::DemuxerStream*&&>, base::internal::BindState<false, true, true, base::OnceCallback<void (media::DemuxerStream*)>, base::internal::UnretainedWrapper<media::DemuxerStream, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunImpl<base::OnceCallback<void (media::DemuxerStream*)>, std::__Cr::tuple<base::internal::UnretainedWrapper<media::DemuxerStream, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, 0ul>(base::OnceCallback<void (media::DemuxerStream*)>&&, std::__Cr::tuple<base::internal::UnretainedWrapper<media::DemuxerStream, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>)  ../../base/functional/bind_internal.h:1069:14
  0000000003841ba4  base::internal::Invoker<base::internal::FunctorTraits<base::OnceCallback<void (void const*)>&&, collaboration::CollaborationController*&&>, base::internal::BindState<false, true, true, base::OnceCallback<void (void const*)>, base::internal::UnretainedWrapper<collaboration::CollaborationController, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunOnce(base::internal::BindStateBase*)  ../../base/functional/bind_internal.h:982:12
  v------>  base::OnceCallback<void ()>::Run() &&                                             ../../base/functional/callback.h:155:12
  0000000003f265b8  gpu::Scheduler::ExecuteSequence(base::IdType<gpu::SyncPointOrderData, unsigned int, 0u, 1u>)  ../../gpu/command_buffer/service/scheduler.cc:739:29
  0000000003f25b0c  gpu::Scheduler::RunNextTask()                                                     ../../gpu/command_buffer/service/scheduler.cc:628:3
  v------>  base::OnceCallback<void ()>::Run() &&                                             ../../base/functional/callback.h:155:12
  0000000006de7a34  base::TaskAnnotator::RunTaskImpl(base::PendingTask&)                              ../../base/task/common/task_annotator.cc:229:34
  v------>  void base::TaskAnnotator::RunTask<base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3>(perfetto::StaticString, base::PendingTask&, base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3&&)  ../../base/task/common/task_annotator.h:112:5
  0000000006dfff70  base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)  ../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:482:23
  0000000006dffae0  base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork()   ../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:345:40
  0000000006d9dd08  base::MessagePumpDefault::Run(base::MessagePump::Delegate*)                       ../../base/message_loop/message_pump_default.cc:65:55
  0000000006e005e0  base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta)  ../../base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:657:12
  0000000006dc8ebc  base::RunLoop::Run(base::Location const&)                                         ../../base/run_loop.cc:135:14
  000000000c5ef9ec  content::GpuMain(content::MainFunctionParams)                                     ../../content/gpu/gpu_main.cc:642:14
  0000000006d765e0  content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*)  ../../content/app/content_main_runner_impl.cc:764:14
  0000000006d77464  content::ContentMainRunnerImpl::Run()                                             ../../content/app/content_main_runner_impl.cc:1165:10
  0000000006d7500c  content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*)  ../../content/app/content_main.cc:356:36
  0000000006d75f68  content::StartContentMain(bool)                                                   ../../content/app/android/content_main_android.cc:54:10
  00000000002c2300  art_quick_generic_jni_trampoline+144) (BuildId: 11dac5edfc6a4111efd44d494b5f3993  /apex/com.android.art/lib64/libart.so
  00000000006680e8  nterp_helper+152) (BuildId: 11dac5edfc6a4111efd44d494b5f3993                      /apex/com.android.art/lib64/libart.so
  00000000002aae76  offset 0x20a1000) (ft1.run+574                                                    /data/app/~~C57UxPkaLjaVkvyrQAYIlw==/org.chromium.chrome-9S4pPjCnfD-K8TFfUMRrtg==/base.apk/libmonochrome.so
  00000000000a95e0  java.lang.Thread.run+64) (BuildId: a2d6a559f5778b9b6315baec706b53a709156608       /system/framework/arm64/boot.oat
  00000000002aaf94  art_quick_invoke_stub+612) (BuildId: 11dac5edfc6a4111efd44d494b5f3993             /apex/com.android.art/lib64/libart.so
  0000000000270940  art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+220) (BuildId: 11dac5edfc6a4111efd44d494b5f3993  /apex/com.android.art/lib64/libart.so
  00000000004bee24  art::Thread::CreateCallback(void*)+1184) (BuildId: 11dac5edfc6a4111efd44d494b5f3993  /apex/com.android.art/lib64/libart.so
  00000000004be974  art::Thread::CreateCallbackWithUffdGc(void*)+8) (BuildId: 11dac5edfc6a4111efd44d494b5f3993  /apex/com.android.art/lib64/libart.so
  00000000000866ec  __pthread_start(void*) (.__uniq.67847048707805468364044055584648682506)+184) (BuildId: 0669c520dd58ab0634801cae537d6b06  /apex/com.android.runtime/lib64/bionic/libc.so
  000000000007900c  __start_thread+68) (BuildId: 0669c520dd58ab0634801cae537d6b06                     /apex/com.android.runtime/lib64/bionic/libc.so

ROOT CAUSE

The bug is in the Qualcomm Adreno GPU driver (libGLESv2_adreno.so, build f61dec9117). When glTexStorage2D is called on a texture that is attached to an FBO as a depth attachment, the driver:

  1. Frees the old 0x80-byte internal storage container associated with the texture
  2. Allocates a new container for the immutable storage
  3. But does NOT update the FBO’s cached pointer at fbo+0x2a8, which still points to the freed container

The cross-FBO trick (bindFramebuffer(null) before texStorage2D) is required because the driver only updates fbo+652 (a dirty flag) for the currently bound FBO. By binding FBO 0 (default) before calling texStorage2D, the driver sets fbo0+652 instead of fboA+652, so FBO A’s cached container pointer remains stale.

When glClear(GL_DEPTH_BUFFER_BIT) is later called on FBO A, the driver’s completeness check function (804c660e) walks the dangling container at fbo+0x2a8. If the freed container has been reoccupied by attacker-controlled data (via RGBA16F texImage2D heap spray), the attacker controls the data read by the completeness check, leading to arbitrary code execution through the driver’s vtable dispatch (20bf65c8 → a970ffdb → BLR vtable[184]).

The ANGLE workaround “recreateFboUponFlush” (CL 7815775, Bug chromium:493747593) recreates the FBO on flush/finish/fenceSync. This is bypassed by using a detached canvas (no compositor flush) and avoiding all synchronous GL calls in the trigger loop.

SUGGESTED FIX

For Chrome/ANGLE:

  • Extend the recreateFboUponFlush workaround to also trigger on glClear/glDraw* when the draw FBO has an immutable depth/stencil texture attachment with stale state, not only on flush/finish/fenceSync.
  • Alternatively, add a workaround that calls glFramebufferTexture2D to re-attach the depth texture after texStorage2D is called on an FBO-attached texture, forcing the driver to refresh its cached container pointer.

For Qualcomm (upstream driver fix):

  • In the glTexStorage2D implementation, iterate all FBOs that reference the respecified texture and update their cached container pointers (fbo+0x2a8) to point to the newly allocated container, or mark them dirty so the next FBO operation refreshes the pointer.

CREDIT INFORMATION Reporter credit: Anymous

View on issue tracker