Critical chrome UAF 🔧 Commit mapped

Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGL
DescriptionUse after free in WebGL
ComponentWebGL
Bug ClassUAF
Tracker546260492
Fix commitf4cb4a33442a (chromium/src) +60/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-01

Changed Functions

FunctionChangeNotes
TEST_P
gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
modified

Files Changed

  • gpu/command_buffer/service/buffer_manager.cc
  • gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
From f4cb4a33442a3b89acdb1d03f000970bd77514f6 Mon Sep 17 00:00:00 2001
From: Brandon Jones <bajones@chromium.org>
Date: Mon, 24 Aug 2026 11:57:18 -0700
Subject: [PATCH] Prevent buffer respec during paused Transform Feedback

Prevents buffer respec while an active transform feedback is
paused when using the validating command decoder. This is undefined
behavior according to ES 3.0 and already blocked by ANGLE. Avoids a
driver bug on Mali GPUs.

Fixed: 546260492
Change-Id: I5edebfcd3d76c41deea6370610001dbb41a2e9d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8281207
Reviewed-by: Kenneth Russell <kbr@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1684886}
---

diff --git a/gpu/command_buffer/service/buffer_manager.cc b/gpu/command_buffer/service/buffer_manager.cc
index 5b733c5..a2e80c5f 100644
--- a/gpu/command_buffer/service/buffer_manager.cc
+++ b/gpu/command_buffer/service/buffer_manager.cc
@@ -428,11 +428,10 @@
 
   if (context_state->bound_transform_feedback &&
       context_state->bound_transform_feedback->active() &&
-      !context_state->bound_transform_feedback->paused() &&
       buffer->IsBoundForTransformFeedback()) {
     ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, "glBufferData",
                             "buffer is bound for transform feedback that is "
-                            "currently active and not paused");
+                            "currently active");
     return;
   }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
index 8da31f35..07ae3167 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
@@ -127,5 +127,64 @@
   }
 }
 
+TEST_P(GLES3DecoderTest, BufferDataWhileTransformFeedbackPausedFails) {
+  const GLenum kTarget = GL_TRANSFORM_FEEDBACK_BUFFER;
+  const GLsizeiptr kBufferSize = 64;
+
+  EXPECT_CALL(*gl_, BindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0))
+      .Times(1)
+      .RetiresOnSaturation();
+  DoBindTransformFeedback(GL_TRANSFORM_FEEDBACK, client_transformfeedback_id_,
+                          kServiceTransformFeedbackId);
+
+  DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);
+  EXPECT_CALL(*gl_, BindBufferBase(kTarget, 0, kServiceBufferId));
+  SpecializedSetup<cmds::BindBufferBase, 0>(true);
+  cmds::BindBufferBase bind_base_cmd;
+  bind_base_cmd.Init(kTarget, 0, client_buffer_id_);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(bind_base_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+
+  DoBufferData(kTarget, kBufferSize);
+
+  TransformFeedback* tf = GetTransformFeedback(client_transformfeedback_id_);
+  ASSERT_TRUE(tf);
+
+  EXPECT_CALL(*gl_, BeginTransformFeedback(GL_POINTS)).Times(1);
+  EXPECT_CALL(*gl_, PauseTransformFeedback()).Times(1);
+  tf->DoBeginTransformFeedback(GL_POINTS);
+  tf->DoPauseTransformFeedback();
+  EXPECT_TRUE(tf->active());
+  EXPECT_TRUE(tf->paused());
+
+  // BufferData (respecification) must fail with GL_INVALID_OPERATION even while
+  // paused.
+  cmds::BufferData buffer_data_cmd;
+  buffer_data_cmd.Init(kTarget, kBufferSize * 2, 0, 0, GL_STREAM_DRAW);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_data_cmd));
+  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
+
+  // BufferSubData while paused is permitted by ES 3.0.
+  EXPECT_CALL(*gl_, BufferSubData(kTarget, 0, 4, _)).Times(1);
+  cmds::BufferSubData buffer_sub_data_cmd;
+  buffer_sub_data_cmd.Init(kTarget, 0, 4, shared_memory_id_,
+                           shared_memory_offset_);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_sub_data_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+
+  // End transform feedback and ensure BufferData succeeds again.
+  EXPECT_CALL(*gl_, EndTransformFeedback()).Times(1);
+  tf->DoEndTransformFeedback();
+  EXPECT_FALSE(tf->active());
+
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR));
+  EXPECT_CALL(*gl_, BufferData(kTarget, kBufferSize * 2, _, GL_STREAM_DRAW))
+      .Times(1);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_data_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
 }  // namespace gles2
 }  // namespace gpu
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
index 8da31f35..07ae3167 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
@@ -127,5 +127,64 @@
   }
 }
 
+TEST_P(GLES3DecoderTest, BufferDataWhileTransformFeedbackPausedFails) {
+  const GLenum kTarget = GL_TRANSFORM_FEEDBACK_BUFFER;
+  const GLsizeiptr kBufferSize = 64;
+
+  EXPECT_CALL(*gl_, BindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0))
+      .Times(1)
+      .RetiresOnSaturation();
+  DoBindTransformFeedback(GL_TRANSFORM_FEEDBACK, client_transformfeedback_id_,
+                          kServiceTransformFeedbackId);
+
+  DoBindBuffer(kTarget, client_buffer_id_, kServiceBufferId);
+  EXPECT_CALL(*gl_, BindBufferBase(kTarget, 0, kServiceBufferId));
+  SpecializedSetup<cmds::BindBufferBase, 0>(true);
+  cmds::BindBufferBase bind_base_cmd;
+  bind_base_cmd.Init(kTarget, 0, client_buffer_id_);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(bind_base_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+
+  DoBufferData(kTarget, kBufferSize);
+
+  TransformFeedback* tf = GetTransformFeedback(client_transformfeedback_id_);
+  ASSERT_TRUE(tf);
+
+  EXPECT_CALL(*gl_, BeginTransformFeedback(GL_POINTS)).Times(1);
+  EXPECT_CALL(*gl_, PauseTransformFeedback()).Times(1);
+  tf->DoBeginTransformFeedback(GL_POINTS);
+  tf->DoPauseTransformFeedback();
+  EXPECT_TRUE(tf->active());
+  EXPECT_TRUE(tf->paused());
+
+  // BufferData (respecification) must fail with GL_INVALID_OPERATION even while
+  // paused.
+  cmds::BufferData buffer_data_cmd;
+  buffer_data_cmd.Init(kTarget, kBufferSize * 2, 0, 0, GL_STREAM_DRAW);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_data_cmd));
+  EXPECT_EQ(GL_INVALID_OPERATION, GetGLError());
+
+  // BufferSubData while paused is permitted by ES 3.0.
+  EXPECT_CALL(*gl_, BufferSubData(kTarget, 0, 4, _)).Times(1);
+  cmds::BufferSubData buffer_sub_data_cmd;
+  buffer_sub_data_cmd.Init(kTarget, 0, 4, shared_memory_id_,
+                           shared_memory_offset_);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_sub_data_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+
+  // End transform feedback and ensure BufferData succeeds again.
+  EXPECT_CALL(*gl_, EndTransformFeedback()).Times(1);
+  tf->DoEndTransformFeedback();
+  EXPECT_FALSE(tf->active());
+
+  EXPECT_CALL(*gl_, GetError())
+      .WillOnce(Return(GL_NO_ERROR))
+      .WillOnce(Return(GL_NO_ERROR));
+  EXPECT_CALL(*gl_, BufferData(kTarget, kBufferSize * 2, _, GL_STREAM_DRAW))
+      .Times(1);
+  EXPECT_EQ(error::kNoError, ExecuteCmd(buffer_data_cmd));
+  EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
 }  // namespace gles2
 }  // namespace gpu
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.