High chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized resource in Video
DescriptionUninitialized resource in Video
ComponentVideo
Sub-componentMedia
Bug ClassUninitialized Memory
Tracker553118338
Fix commit6e1324133a06 (chromium/src) +98/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
BeginScopedReadAccess
gpu/command_buffer/service/shared_image/shared_image_representation.cc
modified
TestVideoImageRepresentation
gpu/command_buffer/service/shared_image/test_image_backing.h
modified
TEST_F
gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
modified

Files Changed

  • gpu/command_buffer/service/shared_image/test_image_backing.cc
  • gpu/command_buffer/service/shared_image/test_image_backing.h
  • gpu/command_buffer/service/shared_image/shared_image_representation.cc
  • gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
  • gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
  • gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
From 6e1324133a06341a4f726b9c9066f20d2f9d1237 Mon Sep 17 00:00:00 2001
From: Sangbaek Park <sangbaekpark@chromium.org>
Date: Wed, 02 Sep 2026 08:32:34 -0700
Subject: [PATCH] media: Check IsCleared in VideoImageRepresentation read access

VideoImageRepresentation::BeginScopedReadAccess() was missing an
IsCleared() check before granting read access to the underlying
SharedImage backing. When hardware video encoders consume an uncleared
SharedImage created without initial pixel data, uninitialized GPU
memory could potentially be encoded and returned in the bitstream to a
compromised renderer.

This CL adds an unconditional IsCleared() check to
VideoImageRepresentation::BeginScopedReadAccess(), matching sibling read
representations (Skia, Overlay). It also corrects the AccessMode passed
to ScopedAccessBase in VideoImageRepresentation::ScopedReadAccess from
kWrite to kRead.

A new unit test is added to SharedImageRepresentationTest to ensure
uncleared SharedImages cannot be read via VideoImageRepresentation, and
existing tests are updated with SetCleared().

Bug: 553118338
Change-Id: I26a41b6579230d2c2854dcf6eaba25665a3b2e71
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8298633
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Sangbaek Park <sangbaekpark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1690766}
---
diff --git a/gpu/command_buffer/service/shared_image/shared_image_representation.cc b/gpu/command_buffer/service/shared_image/shared_image_representation.cc
index 5eb26cc..a9aa13a2 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_representation.cc
@@ -1220,7 +1220,7 @@
 VideoImageRepresentation::ScopedReadAccess::ScopedReadAccess(
     base::PassKey<VideoImageRepresentation> /* pass_key */,
     VideoImageRepresentation* representation)
-    : ScopedAccessBase(representation, AccessMode::kWrite) {}
+    : ScopedAccessBase(representation, AccessMode::kRead) {}
 
 VideoImageRepresentation::ScopedReadAccess::~ScopedReadAccess() {
   representation()->EndReadAccess();
@@ -1228,6 +1228,13 @@
 
 std::unique_ptr<VideoImageRepresentation::ScopedReadAccess>
 VideoImageRepresentation::BeginScopedReadAccess() {
+  if (!IsCleared()) {
+    LOG(ERROR)
+        << "Attempt to read from an uninitialized SharedImage. debug_label: "
+        << debug_label();
+    return nullptr;
+  }
+
   if (!BeginReadAccess()) {
     return nullptr;
   }
diff --git a/gpu/command_buffer/service/shared_image/test_image_backing.cc b/gpu/command_buffer/service/shared_image/test_image_backing.cc
index 71fdabe..5fcebb8 100644
--- a/gpu/command_buffer/service/shared_image/test_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/test_image_backing.cc
@@ -365,6 +365,13 @@
                                                           tracker);
 }
 
+std::unique_ptr<VideoImageRepresentation> TestImageBacking::ProduceVideo(
+    SharedImageManager* manager,
+    MemoryTypeTracker* tracker,
+    VideoDevice device) {
+  return std::make_unique<TestVideoImageRepresentation>(manager, this, tracker);
+}
+
 bool TestOverlayImageRepresentation::BeginReadAccess(
     gfx::GpuFenceHandle& acquire_fence) {
   return true;
@@ -386,4 +393,29 @@
 }
 #endif  // BUILDFLAG(IS_APPLE)
 
+bool TestVideoImageRepresentation::BeginWriteAccess() {
+  return true;
+}
+
+void TestVideoImageRepresentation::EndWriteAccess() {}
+
+bool TestVideoImageRepresentation::BeginReadAccess() {
+  return true;
+}
+
+void TestVideoImageRepresentation::EndReadAccess() {}
+
+#if BUILDFLAG(IS_WIN)
+D3D11TextureAndArrayIndex TestVideoImageRepresentation::GetD3D11Texture()
+    const {
+  return {nullptr, 0};
+}
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+AHardwareBuffer* TestVideoImageRepresentation::GetAHardwareBuffer() const {
+  return nullptr;
+}
+#endif
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/test_image_backing.h b/gpu/command_buffer/service/shared_image/test_image_backing.h
index 5bbdabae..470d477 100644
--- a/gpu/command_buffer/service/shared_image/test_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/test_image_backing.h
@@ -93,6 +93,10 @@
   std::unique_ptr<OverlayImageRepresentation> ProduceOverlay(
       SharedImageManager* manager,
       MemoryTypeTracker* tracker) override;
+  std::unique_ptr<VideoImageRepresentation> ProduceVideo(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      VideoDevice device) override;
 
  private:
   std::vector<raw_ptr<gles2::Texture>> textures_;
@@ -136,6 +140,28 @@
 #endif  // BUILDFLAG(IS_APPLE)
 };
 
+class TestVideoImageRepresentation : public VideoImageRepresentation {
+ public:
+  TestVideoImageRepresentation(SharedImageManager* manager,
+                               SharedImageBacking* backing,
+                               MemoryTypeTracker* tracker)
+      : VideoImageRepresentation(manager, backing, tracker) {}
+
+ private:
+  bool BeginWriteAccess() override;
+  void EndWriteAccess() override;
+  bool BeginReadAccess() override;
+  void EndReadAccess() override;
+
+#if BUILDFLAG(IS_WIN)
+  D3D11TextureAndArrayIndex GetD3D11Texture() const override;
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+  AHardwareBuffer* GetAHardwareBuffer() const override;
+#endif
+};
+
 }  // namespace gpu
 
 #endif  // GPU_COMMAND_BUFFER_SERVICE_SHARED_IMAGE_TEST_IMAGE_BACKING_H_
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
index a347ca5..220a664 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
@@ -2367,6 +2367,7 @@
       shared_image_manager_.Register(std::move(owned_backing),
                                      memory_type_tracker_.get());
   ASSERT_TRUE(shared_image_ref);
+  shared_image_ref->SetCleared();
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
   UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index 13f1050..98fcca28b 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -379,6 +379,7 @@
   ASSERT_TRUE(backing);
   std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
       shared_image_manager_.Register(std::move(backing), &memory_type_tracker_);
+  shared_image->SetCleared();
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
   UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
diff --git a/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc b/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
index 77415822..9e3bfab 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
@@ -262,4 +262,34 @@
   EXPECT_TRUE(representation->IsCleared());
 }
 
+TEST_F(SharedImageRepresentationTest, VideoClearing) {
+  auto representation = manager_.ProduceVideo({}, mailbox_, tracker_.get());
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin read access.
+  {
+    auto scoped_access = representation->BeginScopedReadAccess();
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should be able to begin write access when uncleared.
+  {
+    auto scoped_access = representation->BeginScopedWriteAccess();
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin read access.
+  {
+    auto scoped_access = representation->BeginScopedReadAccess();
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+}
+
 }  // namespace gpu
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
index a347ca5..220a664 100644
--- a/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.cc
@@ -2367,6 +2367,7 @@
       shared_image_manager_.Register(std::move(owned_backing),
                                      memory_type_tracker_.get());
   ASSERT_TRUE(shared_image_ref);
+  shared_image_ref->SetCleared();
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
   UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
index 13f1050..98fcca28b 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
@@ -379,6 +379,7 @@
   ASSERT_TRUE(backing);
   std::unique_ptr<SharedImageRepresentationFactoryRef> shared_image =
       shared_image_manager_.Register(std::move(backing), &memory_type_tracker_);
+  shared_image->SetCleared();
 
   Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
   UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
diff --git a/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc b/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
index 77415822..9e3bfab 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc
@@ -262,4 +262,34 @@
   EXPECT_TRUE(representation->IsCleared());
 }
 
+TEST_F(SharedImageRepresentationTest, VideoClearing) {
+  auto representation = manager_.ProduceVideo({}, mailbox_, tracker_.get());
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should not be able to begin read access.
+  {
+    auto scoped_access = representation->BeginScopedReadAccess();
+    EXPECT_FALSE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // We should be able to begin write access when uncleared.
+  {
+    auto scoped_access = representation->BeginScopedWriteAccess();
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_FALSE(representation->IsCleared());
+
+  // Clear the SharedImage.
+  representation->SetCleared();
+  EXPECT_TRUE(representation->IsCleared());
+
+  // We can now begin read access.
+  {
+    auto scoped_access = representation->BeginScopedReadAccess();
+    EXPECT_TRUE(scoped_access);
+  }
+  EXPECT_TRUE(representation->IsCleared());
+}
+
 }  // namespace gpu
Loading diff…

Original Bug Report

reported by vm...@google.com

Uninitialized GPU memory disclosure in VideoImageRepresentation::BeginScopedReadAccess

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: VideoImageRepresentation::BeginScopedReadAccess() fails to check if the underlying SharedImage backing is initialized before granting read access. When hardware video encoders consume an uncleared SharedImage created without initial pixel data, uninitialized GPU memory may be encoded and returned in the bitstream. A compromised renderer could potentially use this to leak cross-origin GPU process memory across Site Isolation boundaries.

Affected files:

  • gpu/command_buffer/service/shared_image/shared_image_representation.cc

Estimated timestamp from git blame: 2024-08-16

Potential Uninitialized GPU Memory Disclosure via Missing IsCleared Check in VideoImageRepresentation

Location

  • gpu/command_buffer/service/shared_image/shared_image_representation.cc:1229-1237
  • media/base/win/mf_helpers.cc:1153-1166
  • media/gpu/android/ndk_video_encode_accelerator.cc:1166-1180

Summary

In Chromium’s SharedImage subsystem, read representations (SkiaGaneshImageRepresentation, SkiaGraphiteImageRepresentation, OverlayImageRepresentation, GLTextureImageRepresentationBase) enforce an IsCleared() check prior to allowing read access to prevent reading uninitialized GPU memory. However, VideoImageRepresentation::BeginScopedReadAccess() omits this check entirely:

// gpu/command_buffer/service/shared_image/shared_image_representation.cc:1229-1237
std::unique_ptr<VideoImageRepresentation::ScopedReadAccess>
VideoImageRepresentation::BeginScopedReadAccess() {
  if (!BeginReadAccess()) {
    return nullptr;
  }
  return std::make_unique<ScopedReadAccess>(
      base::PassKey<VideoImageRepresentation>(), this);
}

(Note: ScopedReadAccess constructor at line 1223 also mistakenly registers with AccessMode::kWrite).

Because of the missing check, hardware video encoding paths (such as MediaFoundationVideoEncodeAccelerator on Windows and NdkVideoEncodeAccelerator on Android) can be instructed to read from an uninitialized, uncleared SharedImage backing created without pixel data. The encoder compresses the uninitialized VRAM into a bitstream returned directly to the client.

Potential Attack Flow

(Note: These are potential steps based on static code analysis; automated execution of a working proof-of-concept has not been run.)

  1. A compromised renderer calls SharedImageStub::OnCreateSharedImage over its GPU channel with format NV12 or RGBA_8888, usage including SHARED_IMAGE_USAGE_VIDEO_ENCODE_ACCELERATOR, and no initial pixel data. D3DImageBackingFactory (Windows) creates an uninitialized ID3D11Texture2D and skips calling backing->SetCleared().
  2. The renderer binds viz.mojom.Gpu.CreateVideoEncodeAcceleratorProvider and creates a hardware video encode accelerator session (MediaFoundationVideoEncodeAccelerator).
  3. The renderer calls Encode() with a media::mojom::VideoFrame wrapping the uncleared SharedImage mailbox.
  4. In the GPU process, GenerateResourceOnSyncTokenReleased calls shared_image_manager->ProduceVideo(...) followed by image_representation->BeginScopedReadAccess().
  5. Because BeginScopedReadAccess() lacks an IsCleared() gate, read access succeeds on the uncleared backing.
  6. The uninitialized texture memory (which may contain stale cross-origin compositor tiles or textures from recycled GPU memory pools) is passed to the hardware encoder and compressed.
  7. The compressed bitstream is returned to the renderer via BitstreamBufferReady, potentially leaking cross-origin GPU data.

Suggested Fix

  1. Add an unconditional IsCleared() gate to VideoImageRepresentation::BeginScopedReadAccess() in gpu/command_buffer/service/shared_image/shared_image_representation.cc matching sibling representations:
std::unique_ptr<VideoImageRepresentation::ScopedReadAccess>
VideoImageRepresentation::BeginScopedReadAccess() {
  if (!IsCleared()) {
    LOG(ERROR) << "Attempt to read from an uninitialized SharedImage. debug_label: "
               << debug_label();
    return nullptr;
  }
  if (!BeginReadAccess()) {
    return nullptr;
  }
  return std::make_unique<ScopedReadAccess>(
      base::PassKey<VideoImageRepresentation>(), this);
}
  1. Fix the copy-paste error in VideoImageRepresentation::ScopedReadAccess::ScopedReadAccess constructor (line 1223) to use AccessMode::kRead instead of AccessMode::kWrite.

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker