Chrome · GPU
CVE-2026-78977
Uninitialized Memory in GPU
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pgpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc |
modified | |
ifgpu/command_buffer/service/texture_manager.cc |
modified |
Files Changed
gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.ccgpu/command_buffer/service/texture_manager.cc
Patch
From 5300747616b2910e05deb6fd0b936b51697eee63 Mon Sep 17 00:00:00 2001 From: Tzarial <[email protected]> Date: Tue, 21 Jul 2026 06:15:38 -0700 Subject: [PATCH] [agy][gpu] Defer cleared state in TexSubImage ValidateAndDoTexSubImage marked the destination level as cleared before issuing the driver glTex(Sub)Image{2,3}D call and never checked whether the upload succeeded. If the driver returned an error, the level was left marked SafeToRenderFrom() even though no pixels were written. This CL defers the commit of the cleared state until after the GL call succeeds, using ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER and PEEK_GL_ERROR. Fixed: 516864349 Test: gpu_unittests Change-Id: I90dbaf1b04a44b1340b64d5cc20831f8b7e920a7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8108701 Commit-Queue: Tzarial <[email protected]> Reviewed-by: Geoff Lang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1665413} --- diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc index 407f6b9f..aab63f97 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc @@ -594,6 +594,62 @@ texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr)); } +TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotMarkLevelAsCleared) { + DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId); + DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, + 0); + + TextureManager* manager = group().texture_manager(); + TextureRef* texture_ref = manager->GetTexture(client_texture_id_); + ASSERT_TRUE(texture_ref != nullptr); + Texture* texture = texture_ref->texture(); + EXPECT_FALSE(texture->SafeToRenderFrom()); + + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_OUT_OF_MEMORY)) + .RetiresOnSaturation(); + EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, + GL_UNSIGNED_BYTE, shared_memory_address_.get())) + .Times(1) + .RetiresOnSaturation(); + cmds::TexSubImage2D cmd; + cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, + shared_memory_id_, kSharedMemoryOffset, GL_FALSE); + EXPECT_EQ(error::kNoError, ExecuteCmd(cmd)); + EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError()); + EXPECT_FALSE(texture->SafeToRenderFrom()); +} + +TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotExpandClearedRect) { + DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId); + DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, + 0); + + TextureManager* manager = group().texture_manager(); + TextureRef* texture_ref = manager->GetTexture(client_texture_id_); + ASSERT_TRUE(texture_ref != nullptr); + Texture* texture = texture_ref->texture(); + EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0)); + + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_OUT_OF_MEMORY)) + .RetiresOnSaturation(); + EXPECT_CALL(*gl_, + TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, + GL_UNSIGNED_BYTE, shared_memory_address_.get())) + .Times(1) + .RetiresOnSaturation(); + cmds::TexSubImage2D cmd; + cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, + shared_memory_id_, kSharedMemoryOffset, GL_FALSE); + EXPECT_EQ(error::kNoError, ExecuteCmd(cmd)); + EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError()); + EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0)); + EXPECT_FALSE(texture->SafeToRenderFrom()); +} + TEST_P(GLES2DecoderTest, CopyTexImage2DGLError) { GLenum target = GL_TEXTURE_2D; GLint level = 0; @@ -2634,6 +2690,10 @@ SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId, GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0); + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA, GL_UNSIGNED_BYTE, shared_memory_address_.get())) @@ -2694,6 +2754,10 @@ SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId, GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0); + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA, GL_UNSIGNED_BYTE, shared_memory_address_.get())) @@ -2722,6 +2786,10 @@ DoBindBuffer(GL_PIXEL_UNPACK_BUFFER, client_buffer_id_, kServiceBufferId); DoBufferData(GL_PIXEL_UNPACK_BUFFER, 8); + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, 0)) .Times(1) @@ -3478,6 +3546,10 @@ GL_FLOAT, 0, 0); + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, kWidth, kHeight, 0, GL_RGBA, GL_FLOAT, shared_memory_address_.get())) .Times(1) @@ -3510,6 +3582,10 @@ SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId, GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, 0, kHeight - 1, kWidth, 1, 0); + EXPECT_CALL(*gl_, GetError()) + .WillOnce(Return(GL_NO_ERROR)) + .WillOnce(Return(GL_NO_ERROR)) + .RetiresOnSaturation(); EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, _, GL_RGBA, GL_FLOAT, shared_memory_address_.get())) .Times(2) diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc index 5565b575..c8a45997 100644 --- a/gpu/command_buffer/service/texture_manager.cc +++ b/gpu/command_buffer/service/texture_manager.cc @@ -2876,6 +2876,9 @@ &tex_height, &tex_depth); DCHECK(ok); bool full_image; + bool set_cleared = false; + bool set_cleared_rect = false; + gfx::Rect cleared_rect_to_set; if (args.xoffset != 0 || args.yoffset != 0 || args.zoffset != 0 || args.width != tex_width || args.height != tex_height || args.depth != tex_depth) { @@ -2890,7 +2893,8 @@ texture->GetLevelClearedRect(args.target, args.level) .size() .GetArea()); - SetLevelClearedRect(texture_ref, args.target, args.level, cleared_rect); + cleared_rect_to_set = cleared_rect; + set_cleared_rect = !texture->IsLevelCleared(args.target, args.level); } else { // Otherwise clear part of texture level that is not already cleared. if (!ClearTextureLevel(decoder, texture_ref, args.target, args.level)) { @@ -2901,11 +2905,19 @@ } full_image = false; } else { - SetLevelCleared(texture_ref, args.target, args.level, true); + set_cleared = !texture->IsLevelCleared(args.target, args.level); full_image = true; } + // Defer committing the cleared state until the driver upload succeeds. + // See https://crbug.com/516864349 + const bool update_cleared_state = set_cleared || set_cleared_rect; + if (update_cleared_state) { + ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state, function_name); + } + Buffer* buffer = state->bound_pixel_unpack_buffer.get(); + bool uploaded = false; if (texture_state->unpack_overlapping_rows_separately_unpack_buffer && buffer) { @@ -2922,31 +2934,34 @@ // work around driver bug. DoTexSubImageRowByRowWorkaround(texture_state, state, args, unpack_params); - return; + uploaded = true; } }
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
index 407f6b9f..aab63f97 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_textures.cc
@@ -594,6 +594,62 @@
texture->GetLevelSize(GL_TEXTURE_2D, level, &width, &height, nullptr));
}
+TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotMarkLevelAsCleared) {
+ DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
+ DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
+ 0);
+
+ TextureManager* manager = group().texture_manager();
+ TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
+ ASSERT_TRUE(texture_ref != nullptr);
+ Texture* texture = texture_ref->texture();
+ EXPECT_FALSE(texture->SafeToRenderFrom());
+
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_OUT_OF_MEMORY))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, shared_memory_address_.get()))
+ .Times(1)
+ .RetiresOnSaturation();
+ cmds::TexSubImage2D cmd;
+ cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE,
+ shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
+ EXPECT_FALSE(texture->SafeToRenderFrom());
+}
+
+TEST_P(GLES2DecoderTest, TexSubImage2DGLErrorDoesNotExpandClearedRect) {
+ DoBindTexture(GL_TEXTURE_2D, client_texture_id_, kServiceTextureId);
+ DoTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0,
+ 0);
+
+ TextureManager* manager = group().texture_manager();
+ TextureRef* texture_ref = manager->GetTexture(client_texture_id_);
+ ASSERT_TRUE(texture_ref != nullptr);
+ Texture* texture = texture_ref->texture();
+ EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0));
+
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_OUT_OF_MEMORY))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*gl_,
+ TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA,
+ GL_UNSIGNED_BYTE, shared_memory_address_.get()))
+ .Times(1)
+ .RetiresOnSaturation();
+ cmds::TexSubImage2D cmd;
+ cmd.Init(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE,
+ shared_memory_id_, kSharedMemoryOffset, GL_FALSE);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_OUT_OF_MEMORY, GetGLError());
+ EXPECT_EQ(gfx::Rect(), texture->GetLevelClearedRect(GL_TEXTURE_2D, 0));
+ EXPECT_FALSE(texture->SafeToRenderFrom());
+}
+
TEST_P(GLES2DecoderTest, CopyTexImage2DGLError) {
GLenum target = GL_TEXTURE_2D;
GLint level = 0;
@@ -2634,6 +2690,10 @@
SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_NO_ERROR))
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_,
TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA,
GL_UNSIGNED_BYTE, shared_memory_address_.get()))
@@ -2694,6 +2754,10 @@
SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
GL_UNSIGNED_BYTE, 0, 1, 2, 1, 0);
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_NO_ERROR))
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_,
TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, 1, GL_RGBA,
GL_UNSIGNED_BYTE, shared_memory_address_.get()))
@@ -2722,6 +2786,10 @@
DoBindBuffer(GL_PIXEL_UNPACK_BUFFER, client_buffer_id_, kServiceBufferId);
DoBufferData(GL_PIXEL_UNPACK_BUFFER, 8);
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_NO_ERROR))
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA,
GL_UNSIGNED_BYTE, 0))
.Times(1)
@@ -3478,6 +3546,10 @@
GL_FLOAT,
0,
0);
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_NO_ERROR))
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, kWidth, kHeight, 0,
GL_RGBA, GL_FLOAT, shared_memory_address_.get()))
.Times(1)
@@ -3510,6 +3582,10 @@
SetupClearTextureExpectations(kServiceTextureId, kServiceTextureId,
GL_TEXTURE_2D, GL_TEXTURE_2D, 0, GL_RGBA,
GL_FLOAT, 0, kHeight - 1, kWidth, 1, 0);
+ EXPECT_CALL(*gl_, GetError())
+ .WillOnce(Return(GL_NO_ERROR))
+ .WillOnce(Return(GL_NO_ERROR))
+ .RetiresOnSaturation();
EXPECT_CALL(*gl_, TexSubImage2D(GL_TEXTURE_2D, 0, 0, _, _, _, GL_RGBA,
GL_FLOAT, shared_memory_address_.get()))
.Times(2)
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