CVE-2026-17706
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/base/win/mf_helpers.cc |
modified | |
MFHelpersAlignmentTestmedia/base/win/mf_helpers_unittest.cc |
modified | |
ifmedia/base/win/mf_helpers_unittest.cc |
modified | |
GetSupportedProfilesmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc |
modified |
Files Changed
media/base/win/mf_helpers.ccmedia/base/win/mf_helpers_unittest.ccmedia/gpu/BUILD.gnmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Patch
From 5a3e59883dbf977e24391b57340381d63b7e073e Mon Sep 17 00:00:00 2001 From: Sangbaek Park <[email protected]> Date: Tue, 16 Jun 2026 16:16:11 -0700 Subject: [PATCH] media: 4:2:0 alignment validation in MF VideoEncodeAccelerator Added the 4:2:0 subsampled format alignment validation in QueueInput() so that unaligned visible rects are securely and correctly rejected. Unit tests added: : { MediaFoundationVideoEncodeAcceleratorAlignmentTest.*, MFHelpersAlignmentTest.* } Bug: 519693032 Change-Id: Ica81fd85c10ebb9de394c88a2f7fadf33295e961 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7905334 Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Sangbaek Park <[email protected]> Cr-Commit-Position: refs/heads/main@{#1647964} --- diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc index a5d937dc..4e3cfa2 100644 --- a/media/base/win/mf_helpers.cc +++ b/media/base/win/mf_helpers.cc @@ -919,6 +919,20 @@ HRESULT hr; if (need_perform_copy) { + if (frame->format() == PIXEL_FORMAT_NV12 || + frame->format() == PIXEL_FORMAT_I420 || + frame->format() == PIXEL_FORMAT_YV12 || + frame->format() == PIXEL_FORMAT_NV21) { + const gfx::Rect& visible_rect = frame->visible_rect(); + if (visible_rect.x() % 2 != 0 || visible_rect.y() % 2 != 0 || + visible_rect.width() % 2 != 0 || visible_rect.height() % 2 != 0) { + DLOG(ERROR) << "Source visible_rect is not properly aligned for 4:2:0 " + "subsampled format."; + return nullptr; + } + } else { + NOTREACHED(); + } D3D11_TEXTURE2D_DESC desc; input_texture->GetDesc(&desc); desc.Width = static_cast<UINT>(frame->visible_rect().width()); @@ -1183,6 +1197,21 @@ nullptr, &shared_handle); } if (FAILED(hr) || is_texture_array) { + if (frame->format() == PIXEL_FORMAT_NV12 || + frame->format() == PIXEL_FORMAT_I420 || + frame->format() == PIXEL_FORMAT_YV12 || + frame->format() == PIXEL_FORMAT_NV21) { + const gfx::Rect& visible_rect = frame->visible_rect(); + if (visible_rect.x() % 2 != 0 || visible_rect.y() % 2 != 0 || + visible_rect.width() % 2 != 0 || visible_rect.height() % 2 != 0) { + RETURN_ON_FAILURE_WITH_CALLBACK( + E_INVALIDARG, + "Source visible_rect is not properly aligned for " + "4:2:0 subsampled format."); + } + } else { + NOTREACHED(); + } TRACE_EVENT0("media", "CopyTextureOnCreateSharedHandleFailed"); texture_desc.Usage = D3D11_USAGE_DEFAULT; texture_desc.BindFlags = diff --git a/media/base/win/mf_helpers_unittest.cc b/media/base/win/mf_helpers_unittest.cc index d39e2bc5..b8391d8 100644 --- a/media/base/win/mf_helpers_unittest.cc +++ b/media/base/win/mf_helpers_unittest.cc @@ -80,5 +80,68 @@ EXPECT_EQ(output_desc.Height, static_cast<UINT>(visible_rect.height())); } +class MFHelpersAlignmentTest + : public ::testing::TestWithParam<VideoPixelFormat> {}; + +TEST_P(MFHelpersAlignmentTest, + CreateSampleFromTextureRejectsUnalignedVisibleRect) { + Microsoft::WRL::ComPtr<ID3D11Device> device; + Microsoft::WRL::ComPtr<ID3D11DeviceContext> context; + HRESULT hr = + D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, + 0, D3D11_SDK_VERSION, &device, nullptr, &context); + if (FAILED(hr)) { + // Fallback to WARP if hardware is not available. + hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, nullptr, + 0, D3D11_SDK_VERSION, &device, nullptr, &context); + if (FAILED(hr)) { + GTEST_SKIP() << "D3D11 device creation failed"; + } + } + + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = 1920; + desc.Height = 1088; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_NV12; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture; + hr = device->CreateTexture2D(&desc, nullptr, &input_texture); + ASSERT_TRUE(SUCCEEDED(hr)); + + // Create a video frame with an unaligned visible rect. + gfx::Size coded_size(1922, 1082); + gfx::Rect visible_rect(1, 1, 1920, 1080); + gfx::Size natural_size(1920, 1080); + VideoPixelFormat format = GetParam(); + scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame( + format, coded_size, visible_rect, natural_size, base::TimeDelta()); + + if (!frame) { + // VideoFrame::CreateFrame natively validates alignment for some formats + // (e.g., I420) and might return null, in which case we safely skip. + GTEST_SKIP() << "Cannot create unaligned frame natively for format " + << format; + } + + // Create the sample and perform the copy. + Microsoft::WRL::ComPtr<IMFSample> sample = CreateSampleFromTexture( + device, frame, input_texture, /*need_perform_copy=*/true); + + // Because the visible_rect is unaligned, CreateSampleFromTexture should fail. + EXPECT_FALSE(sample); +} + +INSTANTIATE_TEST_SUITE_P(All, + MFHelpersAlignmentTest, + ::testing::Values(PIXEL_FORMAT_NV12, + PIXEL_FORMAT_I420, + PIXEL_FORMAT_YV12, + PIXEL_FORMAT_NV21)); + } // namespace } // namespace media diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn index bd7ed419..1ce3e8d1 100644 --- a/media/gpu/BUILD.gn +++ b/media/gpu/BUILD.gn @@ -697,6 +697,7 @@ "windows/d3d12_video_encode_delegate_unittest.cc", "windows/d3d12_video_encode_delegate_unittest.h", "windows/d3d12_video_encode_h264_delegate_unittest.cc", + "windows/media_foundation_video_encode_accelerator_win_unittest.cc", "windows/mf_audio_encoder_unittest.cc", "windows/mf_video_processor_accelerator_unittest.cc", "windows/scoped_d3d_buffers_unittest.cc", diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc index b9eebb46..d8eb5c36 100644 --- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc +++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc @@ -346,6 +346,18 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } +void MediaFoundationVideoEncodeAccelerator::InitializeForTesting( + Client* client, + std::unique_ptr<MediaLog> media_log, + const gfx::Size& input_visible_size, + scoped_refptr<DXGIDeviceManager> dxgi_device_manager) { + client_ = client; + media_log_ = std::move(media_log); + state_ = kEncoding; + input_visible_size_ = input_visible_size; + dxgi_device_manager_ = std::move(dxgi_device_manager); +} + VideoEncodeAccelerator::SupportedProfiles MediaFoundationVideoEncodeAccelerator::GetSupportedProfiles() { TRACE_EVENT0("gpu,startup", @@ -444,8 +456,9 @@ low_latency_mode_ = config.require_low_delay; drop_frame_thresh_percentage_ = config.drop_frame_thresh_percentage; - if (config.HasTemporalLayer()) + if (config.HasTemporalLayer()) { num_temporal_layers_ = config.spatial_layers.front().num_of_temporal_layers; + } input_since_keyframe_count_ = 0; zero_layer_counter_ = 0; @@ -818,6 +831,22 @@ scoped_refptr<media::VideoFrame> frame, const VideoEncoder::EncodeOptions& options, bool discard_output) { + if (frame && (frame->format() == PIXEL_FORMAT_NV12 || + frame->format() == PIXEL_FORMAT_I420 || + frame->format() == PIXEL_FORMAT_YV12 || + frame->format() == PIXEL_FORMAT_NV21)) { + const gfx::Rect& visible_rect = frame->visible_rect(); + if (visible_rect.x() % 2 != 0 || visible_rect.y() % 2 != 0 || + visible_rect.width() % 2 != 0 || visible_rect.height() % 2 != 0) { + NotifyErrorStatus({EncoderStatus::Codes::kInvalidInputFrame,
Regression Test / PoC
diff --git a/media/base/win/mf_helpers_unittest.cc b/media/base/win/mf_helpers_unittest.cc
index d39e2bc5..b8391d8 100644
--- a/media/base/win/mf_helpers_unittest.cc
+++ b/media/base/win/mf_helpers_unittest.cc
@@ -80,5 +80,68 @@
EXPECT_EQ(output_desc.Height, static_cast<UINT>(visible_rect.height()));
}
+class MFHelpersAlignmentTest
+ : public ::testing::TestWithParam<VideoPixelFormat> {};
+
+TEST_P(MFHelpersAlignmentTest,
+ CreateSampleFromTextureRejectsUnalignedVisibleRect) {
+ Microsoft::WRL::ComPtr<ID3D11Device> device;
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
+ HRESULT hr =
+ D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr,
+ 0, D3D11_SDK_VERSION, &device, nullptr, &context);
+ if (FAILED(hr)) {
+ // Fallback to WARP if hardware is not available.
+ hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, nullptr,
+ 0, D3D11_SDK_VERSION, &device, nullptr, &context);
+ if (FAILED(hr)) {
+ GTEST_SKIP() << "D3D11 device creation failed";
+ }
+ }
+
+ D3D11_TEXTURE2D_DESC desc = {};
+ desc.Width = 1920;
+ desc.Height = 1088;
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = DXGI_FORMAT_NV12;
+ desc.SampleDesc.Count = 1;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> input_texture;
+ hr = device->CreateTexture2D(&desc, nullptr, &input_texture);
+ ASSERT_TRUE(SUCCEEDED(hr));
+
+ // Create a video frame with an unaligned visible rect.
+ gfx::Size coded_size(1922, 1082);
+ gfx::Rect visible_rect(1, 1, 1920, 1080);
+ gfx::Size natural_size(1920, 1080);
+ VideoPixelFormat format = GetParam();
+ scoped_refptr<VideoFrame> frame = VideoFrame::CreateFrame(
+ format, coded_size, visible_rect, natural_size, base::TimeDelta());
+
+ if (!frame) {
+ // VideoFrame::CreateFrame natively validates alignment for some formats
+ // (e.g., I420) and might return null, in which case we safely skip.
+ GTEST_SKIP() << "Cannot create unaligned frame natively for format "
+ << format;
+ }
+
+ // Create the sample and perform the copy.
+ Microsoft::WRL::ComPtr<IMFSample> sample = CreateSampleFromTexture(
+ device, frame, input_texture, /*need_perform_copy=*/true);
+
+ // Because the visible_rect is unaligned, CreateSampleFromTexture should fail.
+ EXPECT_FALSE(sample);
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ MFHelpersAlignmentTest,
+ ::testing::Values(PIXEL_FORMAT_NV12,
+ PIXEL_FORMAT_I420,
+ PIXEL_FORMAT_YV12,
+ PIXEL_FORMAT_NV21));
+
} // namespace
} // namespace media
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc
new file mode 100644
index 0000000..70059b5e57
--- /dev/null
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win_unittest.cc
@@ -0,0 +1,196 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/gpu/windows/media_foundation_video_encode_accelerator_win.h"
+
+#include <d3d11.h>
+#include <wrl/client.h>
+
+#include <memory>
+
+#include "base/functional/callback_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/task_environment.h"
+#include "base/win/scoped_handle.h"
+#include "components/viz/common/resources/shared_image_format.h"
+#include "gpu/command_buffer/client/test_shared_image_interface.h"
+#include "media/base/encoder_status.h"
+#include "media/base/media_log.h"
+#include "media/base/media_util.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_frame.h"
+#include "media/base/win/dxgi_device_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/gpu_memory_buffer_handle.h"
+
+namespace media {
+
+namespace {
+
+class MockEncoderClient : public VideoEncodeAccelerator::Client {
+ public:
+ void RequireBitstreamBuffers(unsigned int input_count,
+ const gfx::Size& input_coded_size,
+ size_t output_buffer_size) override {}
+ void BitstreamBufferReady(int32_t bitstream_buffer_id,
+ const BitstreamBufferMetadata& metadata) override {}
+ void NotifyErrorStatus(const EncoderStatus& status) override {
+ status_ = status;
+ }
+ void NotifyEncoderInfoChange(const VideoEncoderInfo& info) override {}
+
+ EncoderStatus GetLastStatus() const { return status_; }
+
+ private:
+ EncoderStatus status_ = EncoderStatus::Codes::kOk;
+};
+
+class TestMediaFoundationVideoEncodeAccelerator
+ : public MediaFoundationVideoEncodeAccelerator {
+ public:
+ using MediaFoundationVideoEncodeAccelerator::InitializeForTesting;
+
+ TestMediaFoundationVideoEncodeAccelerator(
+ const gpu::GpuPreferences& gpu_preferences,
+ const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
+ CHROME_LUID luid)
+ : MediaFoundationVideoEncodeAccelerator(gpu_preferences,
+ gpu_workarounds,
+ luid) {}
+ ~TestMediaFoundationVideoEncodeAccelerator() override = default;
+};
+
+} // namespace
+
+class MediaFoundationVideoEncodeAcceleratorTest : public ::testing::Test {
+ protected:
+ void SetUpFakeEncoder(TestMediaFoundationVideoEncodeAccelerator* encoder,
+ VideoEncodeAccelerator::Client* client,
+ Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device) {
+ encoder->InitializeForTesting(
+ client, std::make_unique<NullMediaLog>(), gfx::Size(1920, 1080),
+ DXGIDeviceManager::Create(CHROME_LUID{0, 0}, d3d11_device.Get()));
+ }
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+class MediaFoundationVideoEncodeAcceleratorAlignmentTest
+ : public MediaFoundationVideoEncodeAcceleratorTest,
+ public ::testing::WithParamInterface<VideoPixelFormat> {};
+
+TEST_P(MediaFoundationVideoEncodeAcceleratorAlignmentTest,
+ RejectUnalignedVisibleRect) {
+ // Step 1: Initialize a D3D11 device. Try hardware first, then fallback to
+ // WARP (software) if unavailable.
+ Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;
+ Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;
+ HRESULT hr =
+ D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr,
+ 0, D3D11_SDK_VERSION, &d3d11_device, nullptr, &context);
+ if (FAILED(hr)) {
+ hr =
+ D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, 0, nullptr, 0,
+ D3D11_SDK_VERSION, &d3d11_device, nullptr, &context);
+ if (FAILED(hr)) {
+ GTEST_SKIP() << "D3D11 device creation failed";
+ }
+ }
+
+ // Step 2: Set up a fake MediaFoundationVideoEncodeAccelerator with a mock
+ // client.
+ auto encoder = base::WrapUnique(new TestMediaFoundationVideoEncodeAccelerator(
+ gpu::GpuPreferences(), gpu::GpuDriverBugWorkarounds(),
+ CHROME_LUID{0, 0}));
+
+ MockEncoderClient client;
+ SetUpFakeEncoder(encoder.get(), &client, d3d11_device);
+
+ // Step 3: Define an intentionally unaligned visible_rect (odd
+ // coordinates/dimensions) for a 4:2:0 format.
+ gfx::Rect visible_rect(1, 1, 1919, 1079);
+ gfx::Size natural_size(1919, 1079);
+ VideoPixelFormat format = GetParam();
+ scoped_refptr<VideoFrame> frame;
+
+ if (format == PIXEL_FORMAT_NV12) {
+ // Step 4a: For NV12, simulate a GPU-backed frame. Create a shared D3D11
+ // texture and wrap it in a SharedImage and VideoFrame.
+ D3D11_TEXTURE2D_DESC desc = {};
+ desc.Width = 1920;
+ desc.Height = 1080;
+ desc.MipLevels = 1;
+ desc.ArraySize = 1;
+ desc.Format = DXGI_FORMAT_NV12;
+ desc.SampleDesc.Count = 1;
+ desc.Usage = D3D11_USAGE_DEFAULT;
+ desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+ desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
+ D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
+
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+ hr = d3d11_device->CreateTexture2D(&desc, nullptr, &texture);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
+ hr = texture.As(&dxgi_resource);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ HANDLE shared_handle;
+ hr = dxgi_resource->CreateSharedHandle(nullptr, DXGI_SHARED_RESOURCE_READ,
+ nullptr, &shared_handle);
+ ASSERT_HRESULT_SUCCEEDED(hr);
+
+ gfx::GpuMemoryBufferHandle gmb_handle{
+ gfx::DXGIHandle(base::win::ScopedHandle(shared_handle))};
+ gmb_handle.type = gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE;
+
+ auto test_sii = base::MakeRefCounted<gpu::TestSharedImageInterface>();
+ auto shared_image = test_sii->CreateSharedImage(
+ {viz::MultiPlaneFormat::kNV12, gfx::Size(1920, 1080), gfx::ColorSpace(),
+ gpu::SHARED_IMAGE_USAGE_DISPLAY_READ,
+ "MediaFoundationVideoEncodeAcceleratorTest"},
+ gpu::kNullSurfaceHandle, gfx::BufferUsage::GPU_READ,
+ std::move(gmb_handle));
+
+ frame = VideoFrame::WrapMappableSharedImage(
+ std::move(shared_image), test_sii->GenVerifiedSyncToken(),
+ base::DoNothing(), visible_rect, natural_size, base::TimeDelta());
+ } else {
+ // Step 4b: For memory-backed frames (I420, YV12, NV21), use
+ // CreateZeroInitializedFrame instead of D3D texture mocks.
+ frame = VideoFrame::CreateZeroInitializedFrame(
+ format, gfx::Size(1920, 1080), visible_rect, natural_size,
+ base::TimeDelta());
+ }
+
+ if (!frame) {
+ // Step 5: Check if the frame was successfully created.
+ // VideoFrame::CreateZeroInitializedFrame natively validates alignment and
+ // might return null, in which case we safely skip.
+ GTEST_SKIP() << "Cannot create unaligned frame natively for format "
+ << format;
+ }
+
+ encoder->Encode(frame, false);
+ task_environment_.RunUntilIdle();
+
+ // Step 6: Attempt to encode the frame. Because the visible_rect is unaligned,
+ // the encoder should immediately reject the frame and safely report
+ // kInvalidInputFrame via the client.
+ EXPECT_EQ(client.GetLastStatus().code(),
+ EncoderStatus::Codes::kInvalidInputFrame);
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+ MediaFoundationVideoEncodeAcceleratorAlignmentTest,
+ ::testing::Values(PIXEL_FORMAT_NV12,
+ PIXEL_FORMAT_I420,
+ PIXEL_FORMAT_YV12,
+ PIXEL_FORMAT_NV21));
+
+} // namespace media
Original Bug Report
Potential GPU VRAM leak in MFVEA PerformD3DCopy due to unaligned NV12 visible_rect
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: In MediaFoundationVideoEncodeAccelerator on Windows, a compromised renderer can supply an unaligned visible_rect for NV12 video frames. This results in an invalid source box for D3D11 CopySubresourceRegion, causing the copy operation to silently fail. Consequently, the uninitialized destination texture is encoded and returned to the renderer, potentially leaking cross-origin GPU VRAM.
Affected files:
media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Estimated timestamp from git blame: 2024-06-25
Root Cause Analysis
In MediaFoundationVideoEncodeAccelerator on Windows, when the frame size matches the configured size, the accelerator attempts to copy the frame’s texture to avoid concurrent usage glitches. This is performed via PerformD3DCopy inside media/gpu/windows/media_foundation_video_encode_accelerator_win.cc:
-
Uninitialized Texture Allocation (
InitializeD3DCopying): The destination texturecopied_d3d11_texture_is allocated withnullptras thepInitialDataargument, which leaves its contents uninitialized (stale GPU memory):// media/gpu/windows/media_foundation_video_encode_accelerator_win.cc HRESULT hr = texture_device->CreateTexture2D(©_desc, nullptr, &copied_d3d11_texture); -
Unaligned Box Construction and Copy (
PerformD3DCopy): The code checks thatvisible_rectis within the bounds of the input texture’s dimensions. However, it does not check that the coordinates are 2x2 aligned for chroma subsampling. It then constructs aD3D11_BOXdirectly using these coordinates:D3D11_BOX src_box = {static_cast<UINT>(visible_rect.x()), static_cast<UINT>(visible_rect.y()), 0, static_cast<UINT>(visible_rect.right()), static_cast<UINT>(visible_rect.bottom()), 1}; device_context->CopySubresourceRegion(copied_d3d11_texture_.Get(), 0, 0, 0, 0, input_texture, 0, &src_box); -
Silent Failure and Information Leak: For
DXGI_FORMAT_NV12textures, the D3D11 runtime requires the box dimensions and offsets to align to the format’s 2x2 subsampling grid. If the renderer provides odd-aligned coordinates (e.g.,x = 1, y = 1), theCopySubresourceRegioncall is rejected by the D3D11 runtime (D3D11_MESSAGE_ID_COPYSUBRESOURCEREGION_INVALIDSOURCEBOX) and silently no-ops.Since there is no return-value check or fallback handling, the uninitialized
copied_d3d11_texture_is wrapped into anIMFSampleand passed to the MFT hardware encoder viaProcessInput. The encoder compresses this uninitialized GPU memory and returns the bitstream containing stale GPU graphics data back to the renderer.
Suggested Attack Steps
Note: The following are potential steps as these findings are based on static analysis and have not been validated with a dynamic proof-of-concept.
- From a compromised renderer, initialize a
VideoEncodeAcceleratorwith formatPIXEL_FORMAT_NV12and a standard target resolution (e.g.,1920x1080). - Allocate a slightly larger multi-planar NV12
SharedImage(e.g.,1922x1082) backed by a DXGI shared resource handle. - Request an encode with a
visible_rectcontaining odd coordinates (e.g.,gfx::Rect(1, 1, 1920, 1080)). - The Mojo deserializer verifies that
visible_rectis contained within thecoded_sizeof1922x1082but does not enforce any alignment requirements, letting the request proceed. PerformD3DCopybuilds the unalignedD3D11_BOXand executes the copy, which silently fails on NV12. The stale VRAM is then encoded and returned to the renderer viaBitstreamBufferReady.
Suggested Fix
To remediate this issue, validate that the coordinates of visible_rect are properly aligned to the subsampling requirements of the video format before performing the copy.
For PIXEL_FORMAT_NV12, enforce that visible_rect.x(), visible_rect.y(), visible_rect.width(), and visible_rect.height() are aligned on even boundaries, or return E_INVALIDARG if they are not. Alternatively, zero-initialize the texture on allocation or clear it when a copy fails to avoid leaking stale memory.
Evaluated with Chrome root at commit: 9ebf4302210513a012c901d87a2668b3aadf8cc1
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.