Chrome · WebGL
CVE-2026-84352
UAF in WebGL
Overview
Critical
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pgpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc |
modified |
Files Changed
gpu/command_buffer/service/buffer_manager.ccgpu/command_buffer/service/gles2_cmd_decoder_unittest_buffers.cc
Patch
From f4cb4a33442a3b89acdb1d03f000970bd77514f6 Mon Sep 17 00:00:00 2001 From: Brandon Jones <[email protected]> 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 <[email protected]> Commit-Queue: Brandon Jones <[email protected]> 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.
References
On This Page