Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Codecs
DescriptionHeap buffer overflow in Codecs
ComponentCodecs
Bug ClassOOB
Tracker409619251
Fix commitb094accf6189 (chromium/src) +124/-92
CISA KEVNot listed
CreditedElias Hohl
Disclosed2025-04-15

Changed Functions

FunctionChangeNotes
if
gpu/ipc/common/dxgi_helpers.cc
modified
if
media/base/video_frame.cc
modified
current_length_
media/base/win/mf_helpers.cc
modified

Files Changed

  • gpu/ipc/common/dxgi_helpers.cc
  • gpu/ipc/common/dxgi_helpers.h
  • media/base/video_frame.cc
  • media/base/video_frame.h
  • media/base/win/mf_helpers.cc
From b094accf6189985f07e7bfe576c6a11001099896 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Thu, 10 Apr 2025 19:59:52 -0700
Subject: [PATCH] media: Spanify MediaFoundationVideoEncodeAccelerator

Bug: 409619251, 40285824, 338570700
Change-Id: Icbe9b7deb2d485f11327d1e233b5629480b40aad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6448912
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Mustafa Emre Acer <meacer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1445651}
---

diff --git a/gpu/ipc/common/dxgi_helpers.cc b/gpu/ipc/common/dxgi_helpers.cc
index c152b2c3..ac2c0a4 100644
--- a/gpu/ipc/common/dxgi_helpers.cc
+++ b/gpu/ipc/common/dxgi_helpers.cc
@@ -81,9 +81,6 @@
     Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
   DCHECK(d3d11_device);
 
-  uint8_t* dest_buffer = shared_memory.data();
-  size_t dst_buffer_size = shared_memory.size_bytes();
-
   Microsoft::WRL::ComPtr<ID3D11Device1> device1;
   HRESULT hr = d3d11_device->QueryInterface(IID_PPV_ARGS(&device1));
   if (FAILED(hr)) {
@@ -100,19 +97,18 @@
     return false;
   }
 
-  return CopyD3D11TexToMem(texture.Get(), dest_buffer, dst_buffer_size,
-                           d3d11_device, staging_texture);
+  return CopyD3D11TexToMem(texture.Get(), shared_memory, d3d11_device,
+                           staging_texture);
 }
 
 bool CopyD3D11TexToMem(
     ID3D11Texture2D* src_texture,
-    uint8_t* dst_buffer,
-    size_t buffer_size,
+    base::span<uint8_t> dst_buffer,
     ID3D11Device* d3d11_device,
     Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture) {
   DCHECK(d3d11_device);
   DCHECK(staging_texture);
-  DCHECK(dst_buffer);
+  DCHECK(!dst_buffer.empty());
   DCHECK(src_texture);
 
   D3D11_TEXTURE2D_DESC texture_desc = {};
@@ -124,7 +120,7 @@
     return false;
   }
   size_t copy_size = texture_desc.Height * texture_desc.Width * 3 / 2;
-  if (buffer_size < copy_size) {
+  if (dst_buffer.size() < copy_size) {
     DLOG(ERROR) << "Invalid buffer size for copy.";
     return false;
   }
@@ -199,12 +195,12 @@
   const uint32_t source_stride = mapped_resource.RowPitch;
   const uint32_t dest_stride = texture_desc.Width;
 
-  return libyuv::NV12Copy(source_buffer, source_stride,
-                          source_buffer + texture_desc.Height * source_stride,
-                          source_stride, dst_buffer, dest_stride,
-                          dst_buffer + texture_desc.Height * dest_stride,
-                          dest_stride, texture_desc.Width,
-                          texture_desc.Height) == 0;
+  return libyuv::NV12Copy(
+             source_buffer, source_stride,
+             source_buffer + texture_desc.Height * source_stride, source_stride,
+             dst_buffer.data(), dest_stride,
+             dst_buffer.subspan(texture_desc.Height * dest_stride).data(),
+             dest_stride, texture_desc.Width, texture_desc.Height) == 0;
 }
 
 GPU_EXPORT bool CopyShMemToDXGIBuffer(base::span<uint8_t> shared_memory,
diff --git a/gpu/ipc/common/dxgi_helpers.h b/gpu/ipc/common/dxgi_helpers.h
index 1d580019..0e815f27 100644
--- a/gpu/ipc/common/dxgi_helpers.h
+++ b/gpu/ipc/common/dxgi_helpers.h
@@ -70,8 +70,7 @@
 // input texture size or format. Returns true if succeeded.
 GPU_EXPORT bool CopyD3D11TexToMem(
     ID3D11Texture2D* input_texture,
-    uint8_t* dst_buffer,
-    size_t buffer_size,
+    base::span<uint8_t> dst_buffer,
     ID3D11Device* d3d11_device,
     Microsoft::WRL::ComPtr<ID3D11Texture2D>* staging_texture);
 
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index c50826a..31b7b3de 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -785,6 +785,8 @@
   return frame;
 }
 
+// TODO(crbug.com/338570700): This method needs to be remove in favour
+// of its span version.
 // static
 scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
     VideoPixelFormat format,
@@ -796,6 +798,31 @@
     const uint8_t* y_data,
     const uint8_t* uv_data,
     base::TimeDelta timestamp) {
+  auto layout = VideoFrameLayout::CreateWithStrides(format, coded_size,
+                                                    {y_stride, uv_stride});
+  if (!layout) {
+    DLOG(ERROR) << "Invalid layout.";
+    return nullptr;
+  }
+
+  return WrapExternalYuvData(
+      format, coded_size, visible_rect, natural_size, y_stride, uv_stride,
+      UNSAFE_TODO(base::span(y_data, layout->planes()[Plane::kY].size)),
+      UNSAFE_TODO(base::span(uv_data, layout->planes()[Plane::kUV].size)),
+      timestamp);
+}
+
+// static
+scoped_refptr<VideoFrame> VideoFrame::WrapExternalYuvData(
+    VideoPixelFormat format,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    const gfx::Size& natural_size,
+    size_t y_stride,
+    size_t uv_stride,
+    base::span<const uint8_t> y_data,
+    base::span<const uint8_t> uv_data,
+    base::TimeDelta timestamp) {
   const StorageType storage = STORAGE_UNOWNED_MEMORY;
   if (!IsValidConfig(format, storage, coded_size, visible_rect, natural_size)) {
     DLOG(ERROR) << __func__ << " Invalid config."
@@ -819,11 +846,9 @@
   auto frame = base::MakeRefCounted<VideoFrame>(base::PassKey<VideoFrame>(),
                                                 *layout, storage, visible_rect,
                                                 natural_size, timestamp);
-  std::array<const uint8_t*, 2> data = {y_data, uv_data};
+  std::array<base::span<const uint8_t>, 2> data = {y_data, uv_data};
   for (size_t plane = 0; plane < NumPlanes(format); ++plane) {
-    // TODO(crbug.com/338570700): y_data, uv_data should be spans
-    frame->data_[plane] =
-        UNSAFE_TODO(base::span(data[plane], layout->planes()[plane].size));
+    frame->data_[plane] = data[plane];
   }
   return frame;
 }
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index c44af122..4a7ca53 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -397,6 +397,17 @@
       const uint8_t* uv_data,
       base::TimeDelta timestamp);
 
+  static scoped_refptr<VideoFrame> WrapExternalYuvData(
+      VideoPixelFormat format,
+      const gfx::Size& coded_size,
+      const gfx::Rect& visible_rect,
+      const gfx::Size& natural_size,
+      size_t y_stride,
+      size_t uv_stride,
+      base::span<const uint8_t> y_data,
+      base::span<const uint8_t> uv_data,
+      base::TimeDelta timestamp);
+
   // Wraps |gpu_memory_buffer|. This will transfer ownership of
   // |gpu_memory_buffer| to the returned VideoFrame.
   // For use in contexts where the GPUMemoryBuffer has no SharedImage
diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc
index 11724a3..d1812736 100644
--- a/media/base/win/mf_helpers.cc
+++ b/media/base/win/mf_helpers.cc
@@ -271,16 +271,20 @@
 }
 
 MediaBufferScopedPointer::MediaBufferScopedPointer(IMFMediaBuffer* media_buffer)
-    : media_buffer_(media_buffer),
-      buffer_(nullptr),
-      max_length_(0),
-      current_length_(0) {
-  HRESULT hr = media_buffer_->Lock(&buffer_.AsEphemeralRawAddr(), &max_length_,
-                                   &current_length_);
+    : media_buffer_(media_buffer) {
+  uint8_t* buffer;
+  DWORD max_length;
+
+  HRESULT hr = media_buffer_->Lock(&buffer, &max_length, nullptr);
   CHECK(SUCCEEDED(hr));
+
+  // SAFETY: `IMFMediaBuffer::Lock` docs states that `max_length` is the maximum
+  // amount of data that can be written to the buffer.
+  data_ = UNSAFE_BUFFERS(base::raw_span<uint8_t>(buffer, max_length));
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
index d62cf58f..f7c7ccc 100644
--- a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
+++ b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
@@ -171,8 +171,8 @@
   template <typename F>
   void ValidateResult(IMFMediaBuffer* buffer, UINT size, F validation_func) {
     MediaBufferScopedPointer scoped_buffer(buffer);
-    ASSERT_EQ(scoped_buffer.current_length(), size);
-    validation_func(scoped_buffer.get());
+    ASSERT_EQ(scoped_buffer.as_span().size(), size);
+    validation_func(scoped_buffer.as_span().data());
   }
 
   scoped_refptr<DXGIDeviceManager> dxgi_device_man_;
Loading diff…

Original Bug Report

reported by el...@cryptosearch.tools

Buffer Overflow (GPU process) in Chrome Windows Media Foundation Encode Accelerator


Report description

Buffer Overflow (GPU process) in Chrome Windows Media Foundation Encode Accelerator


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules

Which URL (or repository) have you found the vulnerability in?

https://www.google.com/chrome/


The problem

Please describe the technical details of the vulnerability

Summary

There is a Buffer Overflow 0-day in Google Chrome on Windows. The Windows Media Foundation Video Encode Accelerator (which is used by default for H.264 encoding) estimates the buffer size required for encoded frames, and never explicitly checks for each encoded frame whether it actually fits in the provided buffer. This allows an attacker who can lure a victim using Windows to his HTML/JS page to trigger a buffer overflow in the highly privileged GPU process by calling the VideoEncoder in javascript using specific settings and high-entropy input data that will result in large encoded frame sizes. The buffer overflow is happening in the shared memory region in the virtual address space that used for inter-process communication.

Vulnerable Code

The vulnerable calls are memcpy here: https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/windows/media_foundation_video_encode_accelerator_win.cc;drc=34737a18832c71452ad1fdb4ca9970439daefcf4;l=2446 https://source.chromium.org/chromium/chromium/src/+/main:media/gpu/windows/media_foundation_video_encode_accelerator_win.cc;drc=34737a18832c71452ad1fdb4ca9970439daefcf4;l=797

The buffer size estimation happens here: https://source.chromium.org/chromium/chromium/src/+/main:media/video/video_encode_accelerator.cc;drc=1e05ab5d3b3951f622ec8f64f326310aa47fc3c5;l=274

While this estimate is sufficient for most cases, an attacker can specifically craft video data that will result in larger frames. The assumption that the encoded frame can never be larger than the input data plus headers is wrong, and the second limit based on the bitrate can be bypassed by using a large constant bitrate. However, in my opinion, the root issue is not the insufficient buffer size estimate, but the fact that an estimate is used at all without a rigid security check for each frame.

Proof of Concept

In our PoC, we need to make the encoder generate at least one oversized frame. I have appended two scripts: video13_configurable.html allows the user to set parameters for video encoding and will trigger on the click of a button. It will automatically and randomly generate high-entropy data to attempt the generation of an oversized frame. It also computes the buffer size using the methods from the source code (“Raw” and “Expected”, and “Final” is the maximum of those two that is used as the actual buffer size). video15_autoload.html will run without user interaction with the right settings (those that triggered the vulnerability on my machine) and a fixed seed for all random calls.

The exact parameters may depend on the browser version used as well as the Windows version, GPU and GPU driver version (everything that could affect how the Windows Media Foundation H.264 encoder is behaving). I executed my PoC on a GPU VPS (Basic GPU VPS - P600) from the hosting provider Database Mart LLC: https://www.vps-mart.com/gpu-server The GPU used is NVIDIA Quadro P600. The driver version (according to nvidia-smi) is 556.39.

The Windows specifications are as follows:

Edition	Windows 10 Pro
Version	22H2
Installed on	‎3/‎6/‎2025
OS build	19045.5608
Experience	Windows Feature Experience Pack 1000.19061.1000.0

The Chrome version is the latest on the Stable Channel, at the time of writing:

135.0.7049.85 (Official Build) (64-bit) (cohort: 135.0.7049.84 Rollout) 

Important note: I was unable to trigger the vulnerability on the exact same version of the Chromium ASAN build - not because it seems fixed (according to the source code, it is not), but because - according to the data of my exploit script - I failed to find parameters that make the encoder exceed the estimated frame size for this version. Apparently, Chromium and Chrome use different code somewhere in the H.264 encoding process, and Chromium generates smaller frames. I was able to generate larger frames in an older ASAN build of Chromium (111.0.5554.0). It should be noted that no ASAN stack trace was generated by this build when the vulnerability was triggered - which is not surprising as the buffer overflow occurs in the shared memory region, where ASAN is not designed to detect buffer overflows, to my knowledge.

Chrome should be started with the following command:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --enable-logging=stderr --v=1  

Use the following settings in the HTML page:

Width: 3400
Height: 100
Bitrate: 800
Framerate: 62
Bitrate Mode: Constant (CBR)
AVC Profile: High Profile (level 5.0)

The pattern settings can be left at default.

Once the vulnerability is triggered you will see a black-white flash covering the whole Chrome window and something like the following output in the command line:

[6824:7236:0409/152447.039:ERROR:gpu_process_host.cc(954)] GPU process exited unexpectedly: exit_code=-1073741819                                       [6824:7236:0409/152447.039:WARNING:gpu_process_host.cc(1395)] The GPU process has crashed 1 time(s)        
[6824:7236:0409/152447.480:WARNING:gpu_process_host.cc(976)] Reinitialized the GPU process after a crash. The reported initialization time was 349 ms     

I have appended a video that shows that the crash happens indeed exactly when we are expecting it according to our buffer size calculations, and otherwise the encoding functions normally.

Potential for ACE Exploitation

This buffer overflow may be exploitable for ACE, which could be very dangerous as it happens in a highly privileged process (GPU process). Furthermore, the buffer overflow happens inside the shared memory region for inter-process communication. This could allow an attacker to tamper with memory sections that are relevant for communicating with the main process, even if it is not possible to gain ACE in the GPU process.

Writing arbitrary data is only constrained by what the H.264 algorithms can produce. I do not expect this to be the major challenge here, but it certainly an additional barrier for an attacker that the submitted data goes through a mathematical transformation first. Furthermore, guard areas, ASLR and other protections might make exploitation hard or impossible. I will investigate this further in the coming weeks and follow up here if successful to (hopefully) receive an increased bounty. I decided to submit this initial report right now in order to allow you immediate mitigation, given the severity of this bug (and to reduce the risk of someone else having submitted this issue first).

Suggested Patch

Check whether the encoder output size is smaller or equal than bitstream_buffer_size_ before calling memcpy.

Impact analysis – Please briefly explain who can exploit the vulnerability, and what they gain when doing so

This vulnerability allows an attacker to trigger a buffer overflow in the highly privileged Chrome GPU process on Windows, which might in the worst case result in Arbitrary Code Execution in the security context of the GPU process. The only user interaction required is visiting a HTML/JS website.


The cause

What version of Chrome have you found the security issue in?

135.0.7049.85 [stable]

Yes, it is related to a crash.

Choose the type of vulnerability

Memory Corruption

How would you like to be publicly acknowledged for your report?

Elias Hohl

View on issue tracker