CVE-2026-11696
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/base/win/mf_helpers.cc |
modified | |
TESTmedia/base/win/mf_helpers_unittest.cc |
modified | |
ifmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc |
modified |
Files Changed
media/base/BUILD.gnmedia/base/win/mf_helpers.ccmedia/base/win/mf_helpers_unittest.ccmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Patch
From ebecb74997d7acdeeb3a808e8abc8ec37ced5444 Mon Sep 17 00:00:00 2001 From: Sangbaek Park <[email protected]> Date: Mon, 01 Jun 2026 17:29:46 -0700 Subject: [PATCH] media: Fix uninitialized GPU VRAM leak in MFVEA NV12 copy paths When the Windows Media Foundation Video Encode Accelerator (MFVEA) performs a zero-copy GPU encode, it may allocate intermediate D3D11 textures to copy NV12 video frames. Previously, these textures were allocated using the `coded_size` of the video frame, but only the `visible_rect` portion was copied over via `CopySubresourceRegion`. This left the padding region between the visible rect and the coded size uninitialized, potentially leaking stale GPU memory to the compressed video stream. This CL updates the D3D11 texture allocation logic in the copy paths (`CreateSampleFromTexture`, `GenerateResourceOnSyncTokenReleased`, and `PerformD3DCopy`) to allocate the intermediate textures using the exact dimensions of the `visible_rect`. This ensures that the texture is completely filled by `CopySubresourceRegion`, eliminating any uninitialized padding and preventing cross-origin information disclosure. The newly added unit test (repro case) fails without this fix. Tests added: { MFHelpersTest.CreateSampleFromTextureDoesNotLeakUninitializedMemory } Bug: 517993381 Change-Id: Ifffcc8df7fb735ebd96c030fa46f75831298c12b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7886163 Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Sangbaek Park <[email protected]> Cr-Commit-Position: refs/heads/main@{#1639808} --- diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn index edcb8cc..82ef507 100644 --- a/media/base/BUILD.gn +++ b/media/base/BUILD.gn @@ -718,6 +718,7 @@ sources += [ "win/dxgi_device_scope_handle_unittest.cc", "win/media_foundation_package_locator_unittest.cc", + "win/mf_helpers_unittest.cc", ] deps += [ "//media", diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc index be0929e..2911c218 100644 --- a/media/base/win/mf_helpers.cc +++ b/media/base/win/mf_helpers.cc @@ -895,6 +895,8 @@ if (need_perform_copy) { D3D11_TEXTURE2D_DESC desc; input_texture->GetDesc(&desc); + desc.Width = static_cast<UINT>(frame->visible_rect().width()); + desc.Height = static_cast<UINT>(frame->visible_rect().height()); desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = D3D11_BIND_VIDEO_ENCODER; desc.ArraySize = 1; @@ -1163,6 +1165,8 @@ texture_desc.CPUAccessFlags = 0; texture_desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX; + texture_desc.Width = static_cast<UINT>(frame->visible_rect().width()); + texture_desc.Height = static_cast<UINT>(frame->visible_rect().height()); Microsoft::WRL::ComPtr<ID3D11Texture2D> shared_texture; hr = shared_d3d11_device->CreateTexture2D(&texture_desc, nullptr, &shared_texture); diff --git a/media/base/win/mf_helpers_unittest.cc b/media/base/win/mf_helpers_unittest.cc new file mode 100644 index 0000000..d39e2bc5 --- /dev/null +++ b/media/base/win/mf_helpers_unittest.cc @@ -0,0 +1,84 @@ +// 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/base/win/mf_helpers.h" + +#include <d3d11.h> +#include <mfapi.h> +#include <wrl/client.h> + +#include "base/memory/scoped_refptr.h" +#include "media/base/video_frame.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { +namespace { + +TEST(MFHelpersTest, CreateSampleFromTextureDoesNotLeakUninitializedMemory) { + 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"; + } + } + + // Create a texture with a larger coded size than the visible size. + 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 a smaller visible rect. + gfx::Size coded_size(1920, 1088); + gfx::Rect visible_rect(0, 0, 1920, 1080); + gfx::Size natural_size(1920, 1080); + scoped_refptr<VideoFrame> frame = + VideoFrame::CreateFrame(PIXEL_FORMAT_NV12, coded_size, visible_rect, + natural_size, base::TimeDelta()); + + // Create the sample and perform the copy. + Microsoft::WRL::ComPtr<IMFSample> sample = CreateSampleFromTexture( + device, frame, input_texture, /*need_perform_copy=*/true); + ASSERT_TRUE(sample); + + // Get the texture from the sample. + Microsoft::WRL::ComPtr<IMFMediaBuffer> buffer; + hr = sample->GetBufferByIndex(0, &buffer); + ASSERT_TRUE(SUCCEEDED(hr)); + + Microsoft::WRL::ComPtr<IMFDXGIBuffer> dxgi_buffer; + hr = buffer.As(&dxgi_buffer); + ASSERT_TRUE(SUCCEEDED(hr)); + + Microsoft::WRL::ComPtr<ID3D11Texture2D> output_texture; + hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&output_texture)); + ASSERT_TRUE(SUCCEEDED(hr)); + + D3D11_TEXTURE2D_DESC output_desc; + output_texture->GetDesc(&output_desc); + + // The copied texture should exactly match the visible rect, not the coded + // size. This ensures no uninitialized padding is left. + EXPECT_EQ(output_desc.Width, static_cast<UINT>(visible_rect.width())); + EXPECT_EQ(output_desc.Height, static_cast<UINT>(visible_rect.height())); +} + +} // namespace +} // namespace media 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 81eed05..1549ada 100644 --- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc +++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc @@ -2974,23 +2974,21 @@ HRESULT MediaFoundationVideoEncodeAccelerator::InitializeD3DCopying( ID3D11Texture2D* input_texture) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - D3D11_TEXTURE2D_DESC input_desc = {}; - input_texture->GetDesc(&input_desc); // Return early if `copied_d3d11_texture_` is already the correct size, // avoiding the overhead of creating a new destination texture. if (copied_d3d11_texture_) { D3D11_TEXTURE2D_DESC copy_desc = {}; copied_d3d11_texture_->GetDesc(©_desc); - if (input_desc.Width == copy_desc.Width && - input_desc.Height == copy_desc.Height) { + if (static_cast<UINT>(input_visible_size_.width()) == copy_desc.Width && + static_cast<UINT>(input_visible_size_.height()) == copy_desc.Height) { return S_OK; } } ComD3D11Device texture_device; input_texture->GetDevice(&texture_device); D3D11_TEXTURE2D_DESC copy_desc = { - .Width = input_desc.Width, - .Height = input_desc.Height, + .Width = static_cast<UINT>(input_visible_size_.width()), + .Height = static_cast<UINT>(input_visible_size_.height()), .MipLevels = 1, .ArraySize = 1, .Format = DXGI_FORMAT_NV12,
Regression Test / PoC
diff --git a/media/base/win/mf_helpers_unittest.cc b/media/base/win/mf_helpers_unittest.cc
new file mode 100644
index 0000000..d39e2bc5
--- /dev/null
+++ b/media/base/win/mf_helpers_unittest.cc
@@ -0,0 +1,84 @@
+// 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/base/win/mf_helpers.h"
+
+#include <d3d11.h>
+#include <mfapi.h>
+#include <wrl/client.h>
+
+#include "base/memory/scoped_refptr.h"
+#include "media/base/video_frame.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+namespace {
+
+TEST(MFHelpersTest, CreateSampleFromTextureDoesNotLeakUninitializedMemory) {
+ 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";
+ }
+ }
+
+ // Create a texture with a larger coded size than the visible size.
+ 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 a smaller visible rect.
+ gfx::Size coded_size(1920, 1088);
+ gfx::Rect visible_rect(0, 0, 1920, 1080);
+ gfx::Size natural_size(1920, 1080);
+ scoped_refptr<VideoFrame> frame =
+ VideoFrame::CreateFrame(PIXEL_FORMAT_NV12, coded_size, visible_rect,
+ natural_size, base::TimeDelta());
+
+ // Create the sample and perform the copy.
+ Microsoft::WRL::ComPtr<IMFSample> sample = CreateSampleFromTexture(
+ device, frame, input_texture, /*need_perform_copy=*/true);
+ ASSERT_TRUE(sample);
+
+ // Get the texture from the sample.
+ Microsoft::WRL::ComPtr<IMFMediaBuffer> buffer;
+ hr = sample->GetBufferByIndex(0, &buffer);
+ ASSERT_TRUE(SUCCEEDED(hr));
+
+ Microsoft::WRL::ComPtr<IMFDXGIBuffer> dxgi_buffer;
+ hr = buffer.As(&dxgi_buffer);
+ ASSERT_TRUE(SUCCEEDED(hr));
+
+ Microsoft::WRL::ComPtr<ID3D11Texture2D> output_texture;
+ hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&output_texture));
+ ASSERT_TRUE(SUCCEEDED(hr));
+
+ D3D11_TEXTURE2D_DESC output_desc;
+ output_texture->GetDesc(&output_desc);
+
+ // The copied texture should exactly match the visible rect, not the coded
+ // size. This ensures no uninitialized padding is left.
+ EXPECT_EQ(output_desc.Width, static_cast<UINT>(visible_rect.width()));
+ EXPECT_EQ(output_desc.Height, static_cast<UINT>(visible_rect.height()));
+}
+
+} // namespace
+} // namespace media
Original Bug Report
Potential Uninitialized GPU VRAM Leak in Media Foundation Video Encode Accelerator
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: A potential information disclosure vulnerability exists in the Windows Media Foundation Video Encode Accelerator (MFVEA) during GPU-accelerated zero-copy video encoding. When allocating D3D11 textures for NV12 encoding where the coded size exceeds the visible size, the padding regions are left uninitialized. Because the hardware encoder processes the full texture dimensions, a compromised renderer could potentially extract stale cross-origin GPU VRAM data from the returned compressed stream.
Affected files:
media/base/win/mf_helpers.ccmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Estimated timestamp from git blame: 2024-12-05
Description
There is a potential information disclosure (uninitialized GPU VRAM leak) in the Windows Media Foundation Video Encode Accelerator (MediaFoundationVideoEncodeAccelerator) zero-copy GPU encoding path.
When handling video frames where the coded_size is larger than the visible_rect, the GPU process allocates new default-heap D3D11 textures to perform necessary copies (e.g., due to different devices or to avoid concurrency issues). However, these textures are allocated without initial data, and only the subregion corresponding to the visible_rect is populated via CopySubresourceRegion. The remaining padding area contains uninitialized stale GPU VRAM. The hardware encoder subsequently processes the entire macroblock-aligned texture (including the uninitialized padding), which gets compressed and returned to the renderer, potentially disclosing cross-origin graphics memory (such as WebGL or canvas contents from other processes).
Root Cause & Technical Details
There are multiple locations where uninitialized textures are created and partially populated with only visible subregions:
-
Same-device NV12 copy path (
CreateSampleFromTexture): Inmedia/base/win/mf_helpers.cc(line 885), ifneed_perform_copyis true, the texture is allocated using:hr = device->CreateTexture2D(&desc, nullptr, &copied_texture);The second argument is
nullptr, leaving the texture uninitialized. Subsequently,device_context->CopySubresourceRegion(line 918) is called with a source boxsrc_boxrestricted only tovisible_rect. The padding areas (betweenvisible_rectandcoded_size) remain uninitialized. -
Cross-device / texture-array fallback path (
GenerateResourceOnSyncTokenReleased): Inmedia/base/win/mf_helpers.cc(line 1167), a similar pattern occurs whereshared_d3d11_device->CreateTexture2D(&texture_desc, nullptr, &shared_texture)creates an uninitialized texture, andCopySubresourceRegion(line 1185) only copies thevisible_rectportion. -
Encoding copy path (
PerformD3DCopy): Inmedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc, when scaling is not required but copy is needed to prevent concurrent access issues,PerformD3DCopy(line 3013) is invoked. InsideInitializeD3DCopying(line 2974),CreateTexture2Dis called withnullptras the initial data.CopySubresourceRegionis then used to copy only thevisible_rect(line 3063).
In all these cases, the sample is constructed, and its active length is set to the full buffer size (input_buffer->SetCurrentLength(buffer_length)) inside PopulateInputSampleBufferGpu (lines 2421-2425). This advertises the uninitialized padding to the encoder.
In contrast, the CPU readback path in CopyInputSampleBufferFromGpu (lines 2297-2302) explicitly zero-fills trailing padding bytes via std::ranges::fill to prevent information disclosure. This mitigation is missing from the GPU-to-GPU paths.
Potential Steps to Trigger (Hypothetical)
Note: Our tooling does not currently support code execution to verify these steps.
- Establish a Mojo connection to
media.mojom.VideoEncodeAcceleratorProviderand initialize a configuration with an NV12 format and a visible size of1920x1080. - Construct an NV12 SharedImage video frame with a
coded_sizeof1920x1088and avisible_rectof(0, 0, 1920, 1080). - Send an
Encoderequest containing this frame to the GPU process. - Read and decode the compressed output bitstream. The pixels in the padding rows (y-indices 1080 to 1087) could potentially contain stale GPU memory data.
Suggested Fix
Ensure that the allocated D3D11 textures are cleared or that the padding regions are zero-initialized prior to handing them over to the encoder. Alternatively, in the copy paths (CreateSampleFromTexture, GenerateResourceOnSyncTokenReleased, and PerformD3DCopy), any allocated destination textures should be initialized/cleared to black/zeroes if the visible rectangle does not cover the entire texture size.
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
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.