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 <zork@google.com>
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 <kainino@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
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