High chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in Video
DescriptionUninitialized Use in Video
ComponentVideo
Bug ClassUninitialized Memory
Tracker517993381
Fix commitebecb74997d7 (chromium/src) +93/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Changed Functions

FunctionChangeNotes
if
media/base/win/mf_helpers.cc
modified
TEST
media/base/win/mf_helpers_unittest.cc
modified
if
media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
modified

Files Changed

  • media/base/BUILD.gn
  • media/base/win/mf_helpers.cc
  • media/base/win/mf_helpers_unittest.cc
  • media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
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(&copy_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,
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • media/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:

  1. Same-device NV12 copy path (CreateSampleFromTexture): In media/base/win/mf_helpers.cc (line 885), if need_perform_copy is 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 box src_box restricted only to visible_rect. The padding areas (between visible_rect and coded_size) remain uninitialized.

  2. Cross-device / texture-array fallback path (GenerateResourceOnSyncTokenReleased): In media/base/win/mf_helpers.cc (line 1167), a similar pattern occurs where shared_d3d11_device->CreateTexture2D(&texture_desc, nullptr, &shared_texture) creates an uninitialized texture, and CopySubresourceRegion (line 1185) only copies the visible_rect portion.

  3. Encoding copy path (PerformD3DCopy): In media/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. Inside InitializeD3DCopying (line 2974), CreateTexture2D is called with nullptr as the initial data. CopySubresourceRegion is then used to copy only the visible_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.

  1. Establish a Mojo connection to media.mojom.VideoEncodeAcceleratorProvider and initialize a configuration with an NV12 format and a visible size of 1920x1080.
  2. Construct an NV12 SharedImage video frame with a coded_size of 1920x1088 and a visible_rect of (0, 0, 1920, 1080).
  3. Send an Encode request containing this frame to the GPU process.
  4. 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.

View on issue tracker