High watch CVSS 5.3 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in Video
DescriptionUninitialized Use in Video
ComponentVideo
Sub-componentMedia
Bug ClassUninitialized Memory
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker517993381
Fix commitebecb74997d7 (chromium/src) +93/-6
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-29 (per vendor advisory)
Fix landed2026-06-02 (public git)
Disclosed2026-06-08
Patch gap6 days — fix public in source before the advisory

Background

`MFVEA`
the Windows Media Foundation Video Encode Accelerator, which drives hardware H.264/HEVC encode by feeding NV12 frames into intermediate D3D11 textures.
`coded_size` vs `visible_rect`
coded_size is the alignment-padded storage dimension of a VideoFrame; visible_rect is the actual picture region, always contained within it.
`CopySubresourceRegion`
the D3D11 texture copy that transfers only the visible_rect region into the destination texture, touching no bytes outside it.
Uninitialized use
reading GPU memory that was never written after allocation, so it retains stale contents of previously-freed textures.

Root Cause Analysis

The MFVEA copy paths allocated the intermediate D3D11 texture using frame->coded_size(), which includes alignment padding beyond the visible picture. However, only the visible_rect portion of the source was copied in via CopySubresourceRegion, so the padding rows and columns between visible_rect and coded_size were never written after allocation. D3D11 does not zero-initialize texture memory, so that padding region held the stale, uninitialized contents of previously-freed GPU VRAM. The encoder then read and encoded the full texture, embedding leaked GPU memory into the compressed output.

The fix sets texture_desc.Width/Height to frame->visible_rect().width()/height() so the intermediate texture is exactly the visible size and is fully initialized by the copy.

Key insight
Sizing an allocation to a padded dimension while only copying the smaller visible region silently leaves the padding uninitialized. Because a GPU texture’s backing VRAM is recycled from freed allocations and never zeroed, that gap becomes a direct leak of stale, potentially cross-origin pixel data into the encoded stream.

Attack Path

  1. Trigger hardware encode A page drives a MediaRecorder/WebRTC/WebCodecs H.264 or HEVC encode on Windows, routing frames through MFVEA’s zero-copy GPU path.
  2. Force a padded frame Supply frames whose coded_size exceeds visible_rect so the intermediate texture carries an uninitialized padding region.
  3. Encode stale VRAM The encoder consumes the full padded texture, embedding recycled GPU memory into the compressed stream.
  4. Read back the leak The page reads its own recorded/received bitstream and recovers the leaked pixels, potentially cross-origin content from prior GPU allocations.

Impact Assessment

An uninitialized read of recycled GPU VRAM leaks stale texture memory into a compressed video stream the originating page can read back. Because prior allocations may hold cross-origin rendered content, this is a cross-origin information disclosure that defeats the same-origin policy. It requires only ordinary access to hardware video encode on Windows and no elevated privileges.

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

Audit Directions

  • Allocate-padded / copy-visible mismatch
    Flag any texture, buffer, or surface sized from coded_size (or another padded/aligned dimension) whose fill only covers visible_rect; the delta is uninitialized and reachable by downstream consumers.
  • Non-zeroing GPU allocations
    Audit other D3D11/GPU intermediate allocations (GenerateResourceOnSyncTokenReleased and sibling copy paths) that assume fresh memory is zeroed; confirm every allocated region is written before it is encoded, read back, or shared across origins.
From ebecb74997d7acdeeb3a808e8abc8ec37ced5444 Mon Sep 17 00:00:00 2001
From: Sangbaek Park <sangbaekpark@google.com>
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 <dalecurtis@chromium.org>
Commit-Queue: Sangbaek Park <sangbaekpark@chromium.org>
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 vm...@google.com

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