Chrome · GPU
CVE-2026-79040
Uninitialized Memory in GPU
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/framebuffer_manager.cc |
modified | |
ifgpu/command_buffer/service/gles2_cmd_decoder.cc |
modified | |
TEST_Pgpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc |
modified |
Files Changed
gpu/command_buffer/service/framebuffer_manager.ccgpu/command_buffer/service/framebuffer_manager.hgpu/command_buffer/service/gles2_cmd_decoder.ccgpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
Patch
From 427dfe1b0756fe81fb374503e3230ef2610e1bba Mon Sep 17 00:00:00 2001 From: Tzarial <[email protected]> Date: Wed, 15 Jul 2026 16:23:19 -0700 Subject: [PATCH] [agy][gpu] Clear both components of packed D/S When a packed depth-stencil renderbuffer or texture is attached at only GL_DEPTH_ATTACHMENT or only GL_STENCIL_ATTACHMENT, ClearUnclearedAttachments selected glClear bits by attachment-point key and so wrote only one component before MarkAttachmentsAsCleared flipped the single per-image cleared flag. Detect this case, temporarily bind the packed image at the empty depth/stencil point so the driver has both buffers to write, clear with both GL_DEPTH_BUFFER_BIT and GL_STENCIL_BUFFER_BIT, then restore the empty point. Fixed: 516950646 Test: gpu_unittests Change-Id: I100bc653d8dbf5f9707fd35c4d113872c274b462 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086561 Reviewed-by: Kai Ninomiya <[email protected]> Commit-Queue: Kai Ninomiya <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662933} --- diff --git a/gpu/command_buffer/service/framebuffer_manager.cc b/gpu/command_buffer/service/framebuffer_manager.cc index 26a8aef..213b97f 100644 --- a/gpu/command_buffer/service/framebuffer_manager.cc +++ b/gpu/command_buffer/service/framebuffer_manager.cc @@ -1081,6 +1081,37 @@ RestoreDrawBuffers(); } +// static +void Framebuffer::BindAttachmentToPoint(GLenum target, + GLenum attachment_point, + const Attachment* attachment) { + if (!attachment) { + glFramebufferRenderbufferEXT(target, attachment_point, GL_RENDERBUFFER, 0); + return; + } + if (attachment->IsRenderbufferAttachment()) { + const RenderbufferAttachment* rb = + static_cast<const RenderbufferAttachment*>(attachment); + glFramebufferRenderbufferEXT(target, attachment_point, GL_RENDERBUFFER, + rb->renderbuffer()->service_id()); + } else if (attachment->IsTextureAttachment()) { + const TextureAttachment* tex = + static_cast<const TextureAttachment*>(attachment); + if (tex->Is3D()) { + glFramebufferTextureLayer(target, attachment_point, + tex->texture()->service_id(), tex->level(), + tex->layer()); + } else if (tex->samples() > 0) { + glFramebufferTexture2DMultisampleEXT( + target, attachment_point, tex->target(), tex->texture()->service_id(), + tex->level(), tex->samples()); + } else { + glFramebufferTexture2DEXT(target, attachment_point, tex->target(), + tex->texture()->service_id(), tex->level()); + } + } +} + void Framebuffer::OnInsertUpdateLastColorAttachmentId(GLenum attachment) { if (attachment >= GL_COLOR_ATTACHMENT0 && attachment < GL_COLOR_ATTACHMENT0 + manager_->max_color_attachments_) { diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h index 376149ba..6b750cf2 100644 --- a/gpu/command_buffer/service/framebuffer_manager.h +++ b/gpu/command_buffer/service/framebuffer_manager.h @@ -120,6 +120,13 @@ // Re-attaches all current attachments for recreateFbo workaround. void ReattachAttachments(GLenum framebuffer_target); + // Binds |attachment| at |attachment_point| on the framebuffer currently + // bound at |target|, or detaches whatever is at |attachment_point| if + // |attachment| is null. Only modifies driver-side state. + static void BindAttachmentToPoint(GLenum target, + GLenum attachment_point, + const Attachment* attachment); + // Attaches a renderbuffer to a particlar attachment. // Pass null to detach. void AttachRenderbuffer( diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc index 1e076c33..cfaf482 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc @@ -7275,14 +7275,42 @@ } } - if (framebuffer->HasUnclearedAttachment(GL_STENCIL_ATTACHMENT)) { + const Framebuffer::Attachment* depth_attachment = + framebuffer->GetAttachment(GL_DEPTH_ATTACHMENT); + const Framebuffer::Attachment* stencil_attachment = + framebuffer->GetAttachment(GL_STENCIL_ATTACHMENT); + bool clear_depth = depth_attachment && !depth_attachment->cleared(); + bool clear_stencil = stencil_attachment && !stencil_attachment->cleared(); + + // A packed depth-stencil image attached at only one of the depth/stencil + // points must be bound and cleared at both points so that both components + // are initialized before the image is marked as cleared. + GLenum filled_depth_stencil_point = 0; + if (clear_depth && !stencil_attachment && + (GLES2Util::GetChannelsForFormat(depth_attachment->internal_format()) & + GLES2Util::kStencil) != 0) { + filled_depth_stencil_point = GL_STENCIL_ATTACHMENT; + Framebuffer::BindAttachmentToPoint(target, GL_STENCIL_ATTACHMENT, + depth_attachment); + clear_stencil = true; + } else if (clear_stencil && !depth_attachment && + (GLES2Util::GetChannelsForFormat( + stencil_attachment->internal_format()) & + GLES2Util::kDepth) != 0) { + filled_depth_stencil_point = GL_DEPTH_ATTACHMENT; + Framebuffer::BindAttachmentToPoint(target, GL_DEPTH_ATTACHMENT, + stencil_attachment); + clear_depth = true; + } + + if (clear_stencil) { api()->glClearStencilFn(0); state_.SetDeviceStencilMaskSeparate(GL_FRONT, kDefaultStencilMask); state_.SetDeviceStencilMaskSeparate(GL_BACK, kDefaultStencilMask); clear_bits |= GL_STENCIL_BUFFER_BIT; } - if (framebuffer->HasUnclearedAttachment(GL_DEPTH_ATTACHMENT)) { + if (clear_depth) { api()->glClearDepthFn(1.0f); state_.SetDeviceDepthMask(GL_TRUE); clear_bits |= GL_DEPTH_BUFFER_BIT; @@ -7304,6 +7332,11 @@ } } + if (filled_depth_stencil_point) { + Framebuffer::BindAttachmentToPoint(target, filled_depth_stencil_point, + nullptr); + } + if (cleared_int_renderbuffers || clear_bits) { if (reset_draw_buffers) framebuffer->RestoreDrawBuffers(); diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc index 134e4404..977afca 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc @@ -3951,6 +3951,140 @@ } } +TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtDepthOnly) { + // A DEPTH24_STENCIL8 renderbuffer attached at GL_DEPTH_ATTACHMENT only must + // have both its depth and stencil components cleared before being marked as + // cleared. + DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1, + GL_NO_ERROR); + + GLuint color_renderbuffer = client_renderbuffer_id_ + 1; + GLuint color_renderbuffer_service = kServiceRenderbufferId + 1; + EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _)) + .WillOnce(SetArgPointee<1>(color_renderbuffer_service)) + .RetiresOnSaturation(); + GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer); + DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service); + DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR); + + DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_, + kServiceFramebufferId); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, color_renderbuffer, + color_renderbuffer_service, GL_NO_ERROR); + DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, client_renderbuffer_id_, + kServiceRenderbufferId, GL_NO_ERROR); + + // The packed renderbuffer is bound at the stencil point for the duration of + // the implicit clear so that glClear writes both components. + EXPECT_CALL(*gl_, FramebufferRenderbufferEXT( + GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, kServiceRenderbufferId)) + .Times(1) + .RetiresOnSaturation(); + EXPECT_CALL( + *gl_, FramebufferRenderbufferEXT( + GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0)) + .Times(1) + .RetiresOnSaturation(); + SetupExpectationsForFramebufferClearing( + GL_DRAW_FRAMEBUFFER, // target + GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | + GL_STENCIL_BUFFER_BIT, // clear bits + 0, 0, 0, 0, // color + 0, // stencil
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
index 134e4404..977afca 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
@@ -3951,6 +3951,140 @@
}
}
+TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtDepthOnly) {
+ // A DEPTH24_STENCIL8 renderbuffer attached at GL_DEPTH_ATTACHMENT only must
+ // have both its depth and stencil components cleared before being marked as
+ // cleared.
+ DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
+ GL_NO_ERROR);
+
+ GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
+ GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
+ EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
+ .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
+ .RetiresOnSaturation();
+ GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
+ DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
+
+ DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
+ kServiceFramebufferId);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service, GL_NO_ERROR);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId, GL_NO_ERROR);
+
+ // The packed renderbuffer is bound at the stencil point for the duration of
+ // the implicit clear so that glClear writes both components.
+ EXPECT_CALL(*gl_, FramebufferRenderbufferEXT(
+ GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, kServiceRenderbufferId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(
+ GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0))
+ .Times(1)
+ .RetiresOnSaturation();
+ SetupExpectationsForFramebufferClearing(
+ GL_DRAW_FRAMEBUFFER, // target
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
+ GL_STENCIL_BUFFER_BIT, // clear bits
+ 0, 0, 0, 0, // color
+ 0, // stencil
+ 1.0f, // depth
+ false, // scissor test
+ 0, 0, 128, 64);
+ SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB
+ true, // Framebuffer has depth
+ false, // Framebuffer has stencil
+ 0x1111, // color bits
+ true, // depth mask
+ false, // depth enabled
+ 0, // front stencil mask
+ 0, // back stencil mask
+ false); // stencil enabled
+ EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
+ EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();
+
+ cmds::Clear cmd;
+ cmd.Init(GL_COLOR_BUFFER_BIT);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
+TEST_P(GLES3DecoderTest, ClearDepthStencilRenderbufferAttachedAtStencilOnly) {
+ // A DEPTH24_STENCIL8 renderbuffer attached at GL_STENCIL_ATTACHMENT only
+ // must have both its depth and stencil components cleared before being
+ // marked as cleared.
+ DoBindRenderbuffer(GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1, 1,
+ GL_NO_ERROR);
+
+ GLuint color_renderbuffer = client_renderbuffer_id_ + 1;
+ GLuint color_renderbuffer_service = kServiceRenderbufferId + 1;
+ EXPECT_CALL(*gl_, GenRenderbuffersEXT(1, _))
+ .WillOnce(SetArgPointee<1>(color_renderbuffer_service))
+ .RetiresOnSaturation();
+ GenHelper<cmds::GenRenderbuffersImmediate>(color_renderbuffer);
+ DoBindRenderbuffer(GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service);
+ DoRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1, GL_NO_ERROR);
+
+ DoBindFramebuffer(GL_FRAMEBUFFER, client_framebuffer_id_,
+ kServiceFramebufferId);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, color_renderbuffer,
+ color_renderbuffer_service, GL_NO_ERROR);
+ DoFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, client_renderbuffer_id_,
+ kServiceRenderbufferId, GL_NO_ERROR);
+
+ // The packed renderbuffer is bound at the depth point for the duration of
+ // the implicit clear so that glClear writes both components.
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, kServiceRenderbufferId))
+ .Times(1)
+ .RetiresOnSaturation();
+ EXPECT_CALL(
+ *gl_, FramebufferRenderbufferEXT(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, 0))
+ .Times(1)
+ .RetiresOnSaturation();
+ SetupExpectationsForFramebufferClearing(
+ GL_DRAW_FRAMEBUFFER, // target
+ GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
+ GL_STENCIL_BUFFER_BIT, // clear bits
+ 0, 0, 0, 0, // color
+ 0, // stencil
+ 1.0f, // depth
+ false, // scissor test
+ 0, 0, 128, 64);
+ SetupExpectationsForApplyingDirtyState(false, // Framebuffer is RGB
+ false, // Framebuffer has depth
+ true, // Framebuffer has stencil
+ 0x1111, // color bits
+ false, // depth mask
+ false, // depth enabled
+ 0xFFFFFFFFU, // front stencil mask
+ 0xFFFFFFFFU, // back stencil mask
+ false); // stencil enabled
+ EXPECT_CALL(*gl_, DrawBuffersARB(_, _)).Times(1).RetiresOnSaturation();
+ EXPECT_CALL(*gl_, Clear(GL_COLOR_BUFFER_BIT)).Times(1).RetiresOnSaturation();
+
+ cmds::Clear cmd;
+ cmd.Init(GL_COLOR_BUFFER_BIT);
+ EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
+ EXPECT_EQ(GL_NO_ERROR, GetGLError());
+}
+
TEST_P(GLES2DecoderManualInitTest, MESAFramebufferFlipYExtensionEnabled) {
InitState init;
init.gl_version = "OpenGL ES 3.1";
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