Low chrome Uninitialized Memory 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized resource in GPU
DescriptionUninitialized resource in GPU
ComponentGPU
Bug ClassUninitialized Memory
Tracker516950646
Fix commit427dfe1b0756 (chromium/src) +207/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/framebuffer_manager.cc
modified
if
gpu/command_buffer/service/gles2_cmd_decoder.cc
modified
TEST_P
gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
modified

Files Changed

  • gpu/command_buffer/service/framebuffer_manager.cc
  • gpu/command_buffer/service/framebuffer_manager.h
  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/gles2_cmd_decoder_unittest_framebuffers.cc
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.