Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Media
DescriptionUse after free in Media
ComponentMedia
Bug ClassUAF
Tracker537832446
Fix commit38acc8b39421 (chromium/src) +25/-19
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-06

Changed Functions

FunctionChangeNotes
if
media/base/win/mf_helpers.cc
modified
IMFMediaType
media/base/win/mf_helpers.h
modified
MemoryTypeTracker
media/base/win/mf_helpers.h
modified
SharedContextState
media/base/win/mf_helpers.h
modified
if
media/gpu/windows/d3d12_video_encode_accelerator.cc
modified

Files Changed

  • media/base/win/mf_helpers.cc
  • media/base/win/mf_helpers.h
  • media/gpu/windows/d3d12_video_encode_accelerator.cc
From 38acc8b39421655426c218609986023ad483c241 Mon Sep 17 00:00:00 2001
From: Saifuddin Hitawala <[email protected]>
Date: Thu, 23 Jul 2026 12:29:16 -0700
Subject: [PATCH] [media] Keep ref on MemoryTypeTracker in SharedImageReadLock

Add MemoryTypeTracker ref on SharedImageReadLock to avoid UAF based
on unexpected destruction orders, as the lock can be passed to
IMFSample. We will eventually get rid of MemoryTypeTracker in
SharedImage to instead use MemoryTracker directly which is
RefCountedThreadSafe and should avoid future issues.

Note that this does not pass CommandBufferHelper as we want to get
rid of it as well eventually.

Bug: 537832446
Change-Id: I048a2e20527748c0e169d6b285d0cab5d6c22fa9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8139742
Reviewed-by: Dale Curtis <[email protected]>
Reviewed-by: Vasiliy Telezhnikov <[email protected]>
Commit-Queue: Saifuddin Hitawala <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1667317}
---

diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc
index 4e3cfa2..00e115a 100644
--- a/media/base/win/mf_helpers.cc
+++ b/media/base/win/mf_helpers.cc
@@ -51,17 +51,16 @@
     std::unique_ptr<gpu::VideoImageRepresentation> representation,
     std::unique_ptr<gpu::VideoImageRepresentation::ScopedReadAccess>
         scoped_read_access,
-    scoped_refptr<gpu::SharedContextState> context_state) {
-  if (context_state && context_state->MakeCurrent(nullptr, /*needs_gl=*/true)) {
-    scoped_read_access.reset();
-    representation.reset();
-  } else {
-    if (representation) {
-      representation->OnContextLost();
-    }
-    scoped_read_access.reset();
-    representation.reset();
+    scoped_refptr<gpu::SharedContextState> context_state,
+    std::unique_ptr<gpu::MemoryTypeTracker> tracker) {
+  const bool context_current =
+      context_state && context_state->MakeCurrent(nullptr, /*needs_gl=*/true);
+  if (representation && !context_current) {
+    representation->OnContextLost();
   }
+  scoped_read_access.reset();
+  representation.reset();
+  tracker.reset();
 }
 
 }  // namespace
@@ -71,11 +70,13 @@
     std::unique_ptr<gpu::VideoImageRepresentation::ScopedReadAccess>
         scoped_read_access,
     scoped_refptr<VideoFrame> frame,
-    scoped_refptr<gpu::SharedContextState> context_state)
+    scoped_refptr<gpu::SharedContextState> context_state,
+    std::unique_ptr<gpu::MemoryTypeTracker> tracker)
     : representation_(std::move(representation)),
       scoped_read_access_(std::move(scoped_read_access)),
       frame_(std::move(frame)),
       context_state_(std::move(context_state)),
+      tracker_(std::move(tracker)),
       task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
 
 SharedImageReadLock::~SharedImageReadLock() {
@@ -83,7 +84,7 @@
       FROM_HERE,
       base::BindOnce(&DestroySharedImageResourcesOnGpuThread,
                      std::move(representation_), std::move(scoped_read_access_),
-                     std::move(context_state_)));
+                     std::move(context_state_), std::move(tracker_)));
 }
 
 using Microsoft::WRL::ComPtr;
@@ -1134,10 +1135,11 @@
   bool use_same_device = (encoder_device.Get() == shared_d3d11_device.Get());
   gpu::SharedImageManager* shared_image_manager =
       command_buffer_helper->GetSharedImageManager();
+  auto tracker = std::make_unique<gpu::MemoryTypeTracker>(
+      base::WrapRefCounted(shared_image_stub->memory_tracker()));
   std::unique_ptr<gpu::VideoImageRepresentation> image_representation =
       shared_image_manager->ProduceVideo(
-          shared_d3d11_device, frame->shared_image()->mailbox(),
-          command_buffer_helper->GetMemoryTypeTracker());
+          shared_d3d11_device, frame->shared_image()->mailbox(), tracker.get());
   RETURN_ON_FAILURE_WITH_CALLBACK(image_representation ? S_OK : E_FAIL,
                                   "Failed to produce video");
 
@@ -1152,7 +1154,7 @@
   ComPtr<SharedImageReadLock> si_lock =
       Microsoft::WRL::Make<SharedImageReadLock>(
           std::move(image_representation), std::move(scoped_read_access), frame,
-          std::move(shared_context_state));
+          std::move(shared_context_state), std::move(tracker));
   if (!si_lock) {
     RETURN_ON_FAILURE_WITH_CALLBACK(E_OUTOFMEMORY,
                                     "Failed to create SharedImageReadLock");
diff --git a/media/base/win/mf_helpers.h b/media/base/win/mf_helpers.h
index a5a2a42..73db391f 100644
--- a/media/base/win/mf_helpers.h
+++ b/media/base/win/mf_helpers.h
@@ -33,6 +33,7 @@
 class IMFMediaType;
 
 namespace gpu {
+class MemoryTypeTracker;
 class SharedContextState;
 }  // namespace gpu
 
@@ -261,7 +262,8 @@
       std::unique_ptr<gpu::VideoImageRepresentation::ScopedReadAccess>
           scoped_read_access,
       scoped_refptr<VideoFrame> frame,
-      scoped_refptr<gpu::SharedContextState> context_state);
+      scoped_refptr<gpu::SharedContextState> context_state,
+      std::unique_ptr<gpu::MemoryTypeTracker> tracker);
 
   gpu::VideoImageRepresentation::ScopedReadAccess* access() const {
     return scoped_read_access_.get();
@@ -275,6 +277,7 @@
       scoped_read_access_;
   scoped_refptr<VideoFrame> frame_;
   scoped_refptr<gpu::SharedContextState> context_state_;
+  std::unique_ptr<gpu::MemoryTypeTracker> tracker_;
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 };
 
diff --git a/media/gpu/windows/d3d12_video_encode_accelerator.cc b/media/gpu/windows/d3d12_video_encode_accelerator.cc
index e7b6bae..1762d35 100644
--- a/media/gpu/windows/d3d12_video_encode_accelerator.cc
+++ b/media/gpu/windows/d3d12_video_encode_accelerator.cc
@@ -149,10 +149,11 @@
 
   gpu::SharedImageManager* shared_image_manager =
       command_buffer_helper->GetSharedImageManager();
+  auto tracker = std::make_unique<gpu::MemoryTypeTracker>(
+      base::WrapRefCounted(shared_image_stub->memory_tracker()));
   std::unique_ptr<gpu::VideoImageRepresentation> representation =
       shared_image_manager->ProduceVideo(
-          d3d11_device, frame->shared_image()->mailbox(),
-          command_buffer_helper->GetMemoryTypeTracker());
+          d3d11_device, frame->shared_image()->mailbox(), tracker.get());
   RETURN_ON_FAILURE_WITH_CALLBACK(representation ? S_OK : E_FAIL,
                                   "Failed to produce video");
 
@@ -167,7 +168,7 @@
   Microsoft::WRL::ComPtr<SharedImageReadLock> si_lock =
       Microsoft::WRL::Make<SharedImageReadLock>(
           std::move(representation), std::move(scoped_read_access), frame,
-          std::move(shared_context_state));
+          std::move(shared_context_state), std::move(tracker));
   if (!si_lock) {
     RETURN_ON_FAILURE_WITH_CALLBACK(E_OUTOFMEMORY,
                                     "Failed to create SharedImageReadLock");
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in GPU process via asynchronous release of SharedImageReadLock by Media Foundation

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 Use-After-Free (UAF) vulnerability exists in the Windows GPU process when using MediaFoundationVideoEncodeAccelerator (MFVEA). SharedImageReadLock wraps a VideoImageRepresentation holding a raw pointer to a CommandBufferHelperImpl member but does not maintain a reference to keep the helper alive. If an asynchronous hardware encoder releases an IMFSample holding the lock after the encoder and its helper are destroyed, the lock’s destruction accesses freed memory on the GPU main thread.

Affected files:

  • media/base/win/mf_helpers.h
  • media/base/win/mf_helpers.cc
  • media/gpu/windows/media_foundation_video_encode_accelerator_win.cc

Estimated timestamp from git blame: 2026-04-24

Root Cause Analysis

In media/base/win/mf_helpers.cc, a SharedImageReadLock is constructed to manage the lifetime of a VideoImageRepresentation and its ScopedReadAccess. The VideoImageRepresentation holds a raw pointer (tracker_) to an inline MemoryTypeTracker member of a CommandBufferHelperImpl instance:

// media/base/win/mf_helpers.cc
std::unique_ptr<gpu::VideoImageRepresentation> image_representation =
    shared_image_manager->ProduceVideo(
        shared_d3d11_device, frame->shared_image()->mailbox(),
        command_buffer_helper->GetMemoryTypeTracker());

However, SharedImageReadLock does not hold a scoped_refptr<CommandBufferHelper> to guarantee the lifetime of the tracker.

When MFVEA populates input samples for the asynchronous Media Foundation Transform (HMFT), it attaches the SharedImageReadLock directly to the IMFSample COM attributes using SetUnknown with SharedImageReadLock::kSampleExtensionGUID and drops its local queue references. This hands ownership of the lock’s lifetime over to the Media Foundation driver and the OS-managed COM runtime graph.

Upon encoder destruction (MediaFoundationVideoEncodeAccelerator::Destroy()), MFVEA is deleted. No flush command is processed beforehand, which leaves active sample references inside the driver’s queued pipeline. The destruction of MFVEA’s command_buffer_helper_ member drops the last Chrome-side reference to CommandBufferHelperImpl, posting its deletion (~CommandBufferHelperImpl) to the GPU main thread.

Subsequently, when the HMFT completes and releases the IMFSample on an OS background thread, the COM reference count of the SharedImageReadLock drops to 0. Its destructor (~SharedImageReadLock()) runs on the background thread and posts DestroySharedImageResourcesOnGpuThread to the GPU main thread.

Because both deletion tasks are executed sequentially on the GPU main thread, the CommandBufferHelperImpl is deallocated first. The subsequent cleanup task then executes TrackMemFree on the dangling MemoryTypeTracker pointer, causing a Use-After-Free with a virtual call to TrackMemoryAllocatedChange on the freed memory space.

Potential Trigger Path

Note: These are potential steps that an attacker might follow; our analysis is based on static code tracing as we do not have a running PoC.

  1. From a sandboxed renderer on Windows, initiate hardware encoding via the mojom::VideoEncodeAcceleratorProvider interface.
  2. Submit a GPU-backed VideoFrame whose SharedImage is non-mappable, triggering the creation of a SharedImageReadLock wrapping the VideoImageRepresentation and its read-access token.
  3. The lock is attached to the input IMFSample via SetUnknown and handed off to the async HMFT via ProcessInput().
  4. Drop the local queue references in MFVEA via pending_input_queue_.pop_front() so that the lock is kept alive only by the OS-owned driver sample.
  5. Release the source VideoFrame mailbox reference in the renderer so that the representation wrapped by the lock becomes the owning reference (refs_[0]) in the SharedImageBacking.
  6. Close/destroy the encoder from the renderer. MFVEA’s Destroy() runs, deleting the object and posting ~CommandBufferHelperImpl to the GPU main thread.
  7. The async HMFT releases the IMFSample on a background thread after the helper deletion task has been queued, posting DestroySharedImageResourcesOnGpuThread to the GPU main thread.
  8. The GPU main thread executes the tasks: it deallocates CommandBufferHelperImpl (freeing its inline MemoryTypeTracker member) and then executes TrackMemFree on the dangling tracker pointer during representation cleanup, resulting in a virtual call on freed memory.

Suggested Fix

Modify SharedImageReadLock (media/base/win/mf_helpers.h and media/base/win/mf_helpers.cc) to capture and hold a scoped_refptr<CommandBufferHelper> member. Passing the helper into the lock’s constructor guarantees that the CommandBufferHelperImpl (and its inline MemoryTypeTracker) remains alive until all outstanding SharedImageReadLock instances are fully destroyed, preventing the raw tracker pointer from dangling.

Evaluated with Chrome root at commit: 5a99d0c5d2ec6c066f5131e7868ff637440cc3dc


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