CVE-2026-14010
Overview
Files Changed
media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Patch
From 6a44ecacedc7a5c9ab4b46cc66c9dd66547de0d1 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Wed, 27 May 2026 12:26:03 -0700
Subject: [PATCH] media: Zero-initialize trailing padding in MF VEA input buffers
MFT input buffers are sized to the encoder's reported minimum size
(cbSize), but only written up to the frame allocation size. The
remaining trailing padding is left uninitialized and can be compressed
and leaked in the output video stream.
This change zero-initializes the trailing padding bytes beyond the
written pixel payload in both CPU and GPU input paths.
It also replaces a hardcoded NV12 size calculation in the GPU path with
a dynamic VideoFrame::AllocationSize call to calculate the written
payload size cleanly.
Bug: 516924151
Change-Id: I60fb6a8fd7f19cedde7fdcddb40de50fc78502c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7877627
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637144}
---
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 332340ea..81eed05 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -2143,6 +2143,14 @@
return E_FAIL;
}
+ // Zero-initialize any trailing bytes (padding) in the buffer to prevent
+ // GPU process information disclosure.
+ size_t written_size = dst_y_size + dst_uv_size;
+ base::span<uint8_t> buffer_span = scoped_buffer.as_span();
+ if (buffer_span.size() > written_size) {
+ std::ranges::fill(buffer_span.subspan(written_size), 0);
+ }
+
if (!SupportsSharedImageEncoding(workarounds_)) {
return S_OK;
}
@@ -2282,9 +2290,16 @@
return E_FAIL;
}
size_t copied_bytes =
- input_visible_size_.width() * input_visible_size_.height() * 3 / 2;
+ VideoFrame::AllocationSize(frame->format(), input_visible_size_);
hr = input_buffer->SetCurrentLength(copied_bytes);
RETURN_ON_HR_FAILURE(hr, "Failed to set current buffer length", hr);
+
+ // Zero-initialize any trailing bytes (padding) in the buffer to prevent
+ // GPU process information disclosure.
+ base::span<uint8_t> buffer_span = scoped_buffer.as_span();
+ if (buffer_span.size() > copied_bytes) {
+ std::ranges::fill(buffer_span.subspan(copied_bytes), 0);
+ }
hr = input_sample->RemoveAllBuffers();
RETURN_ON_HR_FAILURE(hr, "Failed to remove buffers from sample", hr);
hr = input_sample->AddBuffer(input_buffer.Get());
Original Bug Report
Potential uninitialized GPU process memory leak in MediaFoundationVideoEncodeAccelerator
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 GPU process within MediaFoundationVideoEncodeAccelerator::PopulateInputSampleBuffer due to mismatched input buffer size configuration. When the encoder’s reported minimum input size (cbSize) exceeds the video frame allocation size, uninitialized trailing heap memory is advertised as valid data via SetCurrentLength. This uninitialized memory can then be encoded into the compressed video stream and returned to an unprivileged renderer.
Affected files:
media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
Estimated timestamp from git blame: 2024-05-01
Root Cause Analysis
In MediaFoundationVideoEncodeAccelerator::PopulateInputSampleBuffer (located in media/gpu/windows/media_foundation_video_encode_accelerator_win.cc), there is a potential GPU-process heap memory disclosure issue.
When allocating a fresh buffer for CPU-backed input frames, the code retrieves the encoder’s reported minimum input size requirements (input_stream_info.cbSize). If cbSize is non-zero, it creates an aligned memory buffer via MFCreateAlignedMemoryBuffer sized to cbSize and immediately sets the buffer’s active length to cbSize:
hr = MFCreateAlignedMemoryBuffer(
input_stream_info.cbSize ? input_stream_info.cbSize
: VideoFrame::AllocationSize(kTargetPixelFormat, input_visible_size_),
..., &input_buffer);
...
hr = input_buffer->SetCurrentLength(
input_stream_info.cbSize ? input_stream_info.cbSize
: VideoFrame::AllocationSize(kTargetPixelFormat, input_visible_size_));
However, the subsequent conversion step only writes VideoFrame::AllocationSize(PIXEL_FORMAT_NV12, input_visible_size_) bytes into the buffer using ConvertAndScale():
auto frame_in_buffer = VideoFrame::WrapExternalYuvData(
kTargetPixelFormat, input_visible_size_, ...,
dst_y_stride, dst_uv_stride, dst_y, dst_uv,
frame->timestamp());
auto status = frame_converter_.ConvertAndScale(*frame, *frame_in_buffer);
When input_stream_info.cbSize is larger than the required allocation size (which commonly occurs due to vendor-specific or macroblock-alignment requirements), the trailing cbSize - AllocationSize bytes remain completely uninitialized. Because the active length was set to cbSize, the MFT is told that the entire buffer contains valid data.
This is confirmed to be an implementation defect by looking at the sibling function CopyInputSampleBufferFromGpu, which correctly sets the active length to the actual number of written bytes:
size_t copied_bytes =
input_visible_size_.width() * input_visible_size_.height() * 3 / 2;
hr = input_buffer->SetCurrentLength(copied_bytes);
Potential Exploitation Steps
An attacker could potentially exploit this behavior using the following steps (note that our tooling agent does not currently have the capability to execute this code or provide a working proof of concept, so these are suggested/potential steps):
- An unprivileged renderer can access this path without any user permission via WebCodecs (
VideoEncoder) or WebRTC APIs. - By specifying an even but non-macroblock-aligned input size (e.g.,
1922x1082), the renderer can force a scenario where the hardware encoder MFT expects alignment (e.g.,1936x1088) and reportscbSize > AllocationSize. - The renderer then submits a CPU-backed frame for encoding.
- When
PopulateInputSampleBufferexecutes, the hardware encoder processes the input buffer, reading and compressing the uninitialized tail padding according to its internal layout requirements, and incorporates this data into the compressed H.264/HEVC/AV1 stream. - The compressed stream containing the leaked GPU process memory is returned to the renderer via
BitstreamBufferReady. Since a newIMFSamplecan be created for every frame, an attacker could continuously sample distinct GPU-process heap regions over time.
Suggested Fix
To prevent this information disclosure, the allocated buffer should be zero-initialized immediately after creation to ensure no stale heap memory is leaked. Alternatively, the unused trailing region should be explicitly zeroed before calling ConvertAndScale():
MediaBufferScopedPointer scoped_buffer(input_buffer.Get());
std::span<uint8_t> buffer_span = scoped_buffer.as_span();
size_t written_size = dst_y_size + dst_uv_size;
if (buffer_span.size() > written_size) {
std::fill(buffer_span.begin() + written_size, buffer_span.end(), 0);
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.