CVE-2026-19171
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/base/win/mf_helpers.cc |
modified | |
IMFMediaTypemedia/base/win/mf_helpers.h |
modified | |
MemoryTypeTrackermedia/base/win/mf_helpers.h |
modified | |
SharedContextStatemedia/base/win/mf_helpers.h |
modified | |
ifmedia/gpu/windows/d3d12_video_encode_accelerator.cc |
modified |
Files Changed
media/base/win/mf_helpers.ccmedia/base/win/mf_helpers.hmedia/gpu/windows/d3d12_video_encode_accelerator.cc
Patch
From 38acc8b39421655426c218609986023ad483c241 Mon Sep 17 00:00:00 2001
From: Saifuddin Hitawala <hitawala@chromium.org>
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 <dalecurtis@chromium.org>
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Saifuddin Hitawala <hitawala@chromium.org>
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");
Original Bug Report
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.hmedia/base/win/mf_helpers.ccmedia/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.
- From a sandboxed renderer on Windows, initiate hardware encoding via the
mojom::VideoEncodeAcceleratorProviderinterface. - Submit a GPU-backed
VideoFramewhoseSharedImageis non-mappable, triggering the creation of aSharedImageReadLockwrapping theVideoImageRepresentationand its read-access token. - The lock is attached to the input
IMFSampleviaSetUnknownand handed off to the async HMFT viaProcessInput(). - 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. - Release the source
VideoFramemailbox reference in the renderer so that the representation wrapped by the lock becomes the owning reference (refs_[0]) in theSharedImageBacking. - Close/destroy the encoder from the renderer. MFVEA’s
Destroy()runs, deleting the object and posting~CommandBufferHelperImplto the GPU main thread. - The async HMFT releases the
IMFSampleon a background thread after the helper deletion task has been queued, postingDestroySharedImageResourcesOnGpuThreadto the GPU main thread. - The GPU main thread executes the tasks: it deallocates
CommandBufferHelperImpl(freeing its inlineMemoryTypeTrackermember) and then executesTrackMemFreeon 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.