CVE-2026-106273
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
BeginScopedReadAccessgpu/command_buffer/service/shared_image/shared_image_representation.cc |
modified | |
TestVideoImageRepresentationgpu/command_buffer/service/shared_image/test_image_backing.h |
modified | |
TEST_Fgpu/command_buffer/service/shared_image/shared_image_representation_unittest.cc |
modified |
Files Changed
gpu/command_buffer/service/shared_image/test_image_backing.ccgpu/command_buffer/service/shared_image/test_image_backing.hgpu/command_buffer/service/shared_image/shared_image_representation.ccgpu/command_buffer/service/shared_image/shared_image_representation_unittest.ccgpu/command_buffer/service/shared_image/d3d_image_backing_factory_unittest.ccgpu/command_buffer/service/shared_image/gl_texture_image_backing_factory_unittest.cc
Patch
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
Regression Test / PoC
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
Original Bug Report
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-1237media/base/win/mf_helpers.cc:1153-1166media/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.)
- A compromised renderer calls
SharedImageStub::OnCreateSharedImageover its GPU channel with formatNV12orRGBA_8888, usage includingSHARED_IMAGE_USAGE_VIDEO_ENCODE_ACCELERATOR, and no initial pixel data.D3DImageBackingFactory(Windows) creates an uninitializedID3D11Texture2Dand skips callingbacking->SetCleared(). - The renderer binds
viz.mojom.Gpu.CreateVideoEncodeAcceleratorProviderand creates a hardware video encode accelerator session (MediaFoundationVideoEncodeAccelerator). - The renderer calls
Encode()with amedia::mojom::VideoFramewrapping the uncleared SharedImage mailbox. - In the GPU process,
GenerateResourceOnSyncTokenReleasedcallsshared_image_manager->ProduceVideo(...)followed byimage_representation->BeginScopedReadAccess(). - Because
BeginScopedReadAccess()lacks anIsCleared()gate, read access succeeds on the uncleared backing. - 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.
- The compressed bitstream is returned to the renderer via
BitstreamBufferReady, potentially leaking cross-origin GPU data.
Suggested Fix
- Add an unconditional
IsCleared()gate toVideoImageRepresentation::BeginScopedReadAccess()ingpu/command_buffer/service/shared_image/shared_image_representation.ccmatching 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);
}
- Fix the copy-paste error in
VideoImageRepresentation::ScopedReadAccess::ScopedReadAccessconstructor (line 1223) to useAccessMode::kReadinstead ofAccessMode::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.