CVE-2026-8544
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
MFAsyncCallbackProxymedia/gpu/windows/media_foundation_video_encode_accelerator_win.cc |
modified | |
VideoRateControlWrappermedia/gpu/windows/media_foundation_video_encode_accelerator_win.h |
modified | |
TemporalScalabilityIdExtractormedia/gpu/windows/media_foundation_video_encode_accelerator_win.h |
modified | |
VEAEncodingLatencyMetricsHelpermedia/gpu/windows/media_foundation_video_encode_accelerator_win.h |
modified |
Files Changed
media/gpu/windows/media_foundation_video_encode_accelerator_win.ccmedia/gpu/windows/media_foundation_video_encode_accelerator_win.h
Patch
From 70f35b06c6330d6b46c0689a93545ef33041da79 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Wed, 01 Apr 2026 01:41:59 -0700
Subject: [PATCH] media: Fix UAF bug in MediaFoundationVideoEncodeAccelerator
The MF VEA used fake COM refcounting while registering its `this`
pointer with the Media Foundation API. This led to a use after free bug
if the encoder was deleted while an OS callback was pending.
This change decouples the encoder's lifetime from the OS callback's
lifetime by removing `IMFAsyncCallback` from the main class and
introducing `MFAsyncCallbackProxy`, a true COM object. The proxy
receives the events and safely posts them to the parent class via
`base::WeakPtr`, ensuring that the main object is not accessed after
it is freed.
Bug: 497151750
Change-Id: I29d6969cc5db8d30e9e2ee2793d34ab326d560f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7712934
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608366}
---
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 36c3960c..58983a5 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -11,6 +11,7 @@
#include <mfapi.h>
#include <mferror.h>
#include <mftransform.h>
+#include <wrl/implements.h>
#include <algorithm>
#include <iterator>
@@ -212,6 +213,67 @@
} // namespace
+// A proxy class that implements IMFAsyncCallback and routes the events back to
+// the MediaFoundationVideoEncodeAccelerator safely via a WeakPtr. This
+// decouples the encoder's lifetime from the OS callback's lifetime. If the
+// encoder is destroyed while a callback is pending, the WeakPtr will be
+// invalidated, and the posted task will be safely dropped, preventing a
+// use-after-free.
+class MFAsyncCallbackProxy
+ : public Microsoft::WRL::RuntimeClass<
+ Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
+ IMFAsyncCallback> {
+ public:
+ MFAsyncCallbackProxy(
+ scoped_refptr<base::SequencedTaskRunner> task_runner,
+ base::WeakPtr<MediaFoundationVideoEncodeAccelerator> parent)
+ : task_runner_(std::move(task_runner)),
+ parent_weak_ptr_(std::move(parent)) {}
+
+ ~MFAsyncCallbackProxy() override = default;
+
+ IFACEMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue) override {
+ *pdwFlags = MFASYNC_FAST_IO_PROCESSING_CALLBACK;
+ *pdwQueue = MFASYNC_CALLBACK_QUEUE_TIMER;
+ return S_OK;
+ }
+
+ IFACEMETHODIMP Invoke(IMFAsyncResult* pAsyncResult) override {
+ MediaEventType event_type = MEUnknown;
+ HRESULT status = GetEvent(pAsyncResult, &event_type);
+
+ // Invoke() is called on some random OS thread, so we must post to our event
+ // handler since MediaFoundationVideoEncodeAccelerator is single threaded.
+ task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &MediaFoundationVideoEncodeAccelerator::MediaEventHandler,
+ parent_weak_ptr_, event_type, status));
+ return S_OK;
+ }
+
+ private:
+ HRESULT GetEvent(IMFAsyncResult* pAsyncResult, MediaEventType* event_type) {
+ Microsoft::WRL::ComPtr<IUnknown> state;
+ RETURN_IF_FAILED(pAsyncResult->GetState(&state));
+
+ Microsoft::WRL::ComPtr<IMFMediaEventGenerator> event_generator;
+ RETURN_IF_FAILED(state.As(&event_generator));
+
+ Microsoft::WRL::ComPtr<IMFMediaEvent> media_event;
+ RETURN_IF_FAILED(event_generator->EndGetEvent(pAsyncResult, &media_event));
+
+ RETURN_IF_FAILED(media_event->GetType(event_type));
+
+ HRESULT status = S_OK;
+ RETURN_IF_FAILED(media_event->GetStatus(&status));
+ return status;
+ }
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ base::WeakPtr<MediaFoundationVideoEncodeAccelerator> parent_weak_ptr_;
+};
+
struct MediaFoundationVideoEncodeAccelerator::PendingInput {
PendingInput() = default;
~PendingInput() = default;
@@ -280,7 +342,6 @@
~MediaFoundationVideoEncodeAccelerator() {
DVLOG(3) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(async_callback_ref_.IsOne());
}
VideoEncodeAccelerator::SupportedProfiles
@@ -617,7 +678,16 @@
return false;
}
- event_generator_->BeginGetEvent(this, nullptr);
+ proxy_callback_ = Microsoft::WRL::Make<MFAsyncCallbackProxy>(
+ task_runner_, weak_factory_.GetWeakPtr());
+
+ hr = event_generator_->BeginGetEvent(proxy_callback_.Get(),
+ event_generator_.Get());
+ if (FAILED(hr)) {
+ NotifyErrorStatus({EncoderStatus::Codes::kEncoderInitializationError,
+ "Couldn't begin get event: " + PrintHr(hr)});
+ return false;
+ }
// Start the asynchronous processing model
hr = encoder_->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
@@ -2615,7 +2685,12 @@
default:
break;
}
- event_generator_->BeginGetEvent(this, nullptr);
+ HRESULT hr = event_generator_->BeginGetEvent(proxy_callback_.Get(),
+ event_generator_.Get());
+ if (FAILED(hr)) {
+ NotifyErrorStatus({EncoderStatus::Codes::kSystemAPICallError,
+ "Failed to begin get event: " + PrintHr(hr)});
+ }
}
void MediaFoundationVideoEncodeAccelerator::SetState(State state) {
@@ -3004,47 +3079,4 @@
return initialized;
}
-HRESULT MediaFoundationVideoEncodeAccelerator::GetParameters(DWORD* pdwFlags,
- DWORD* pdwQueue) {
- *pdwFlags = MFASYNC_FAST_IO_PROCESSING_CALLBACK;
- *pdwQueue = MFASYNC_CALLBACK_QUEUE_TIMER;
- return S_OK;
-}
-
-HRESULT MediaFoundationVideoEncodeAccelerator::Invoke(
- IMFAsyncResult* pAsyncResult) {
- ComMFMediaEvent media_event;
- RETURN_IF_FAILED(event_generator_->EndGetEvent(pAsyncResult, &media_event));
-
- MediaEventType event_type = MEUnknown;
- RETURN_IF_FAILED(media_event->GetType(&event_type));
-
- HRESULT status = S_OK;
- media_event->GetStatus(&status);
-
- // Invoke() is called on some random OS thread, so we must post to our event
- // handler since MediaFoundationVideoEncodeAccelerator is single threaded.
- task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(&MediaFoundationVideoEncodeAccelerator::MediaEventHandler,
- weak_ptr_, event_type, status));
- return status;
-}
-
-ULONG MediaFoundationVideoEncodeAccelerator::AddRef() {
- return async_callback_ref_.Increment();
-}
-
-ULONG MediaFoundationVideoEncodeAccelerator::Release() {
- DCHECK(!async_callback_ref_.IsOne());
- return async_callback_ref_.Decrement() ? 1 : 0;
-}
-
-HRESULT MediaFoundationVideoEncodeAccelerator::QueryInterface(REFIID riid,
- void** ppv) {
- static const QITAB kQI[] = {
- QITABENT(MediaFoundationVideoEncodeAccelerator, IMFAsyncCallback), {0}};
- return QISearch(this, kQI, riid, ppv);
-}
-
} // namespace media
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
index e59a26c2..746b4cd 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.h
@@ -45,6 +45,7 @@
class VideoRateControlWrapper;
class TemporalScalabilityIdExtractor;
class VEAEncodingLatencyMetricsHelper;
Original Bug Report
Potential Use-after-free in MediaFoundationVideoEncodeAccelerator via Windows MF callback
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential use-after-free (UAF) vulnerability exists in the MediaFoundationVideoEncodeAccelerator due to a race condition between object destruction and callbacks from the Windows Media Foundation thread pool. The class registers a raw pointer with the OS but uses fake COM reference counting, meaning explicit destruction does not wait for pending callbacks to drain. If an attacker triggers destruction while an OS callback is pending, the OS thread will invoke a virtual method on the freed object, potentially leading to Remote Code Execution (RCE) in the GPU process.
Affected files:
media/gpu/windows/media_foundation_video_encode_accelerator_win.ccmedia/gpu/windows/media_foundation_video_encode_accelerator_win.h
Estimated timestamp from git blame: 2023-07-10
Background
The MediaFoundationVideoEncodeAccelerator (MFVEA) class in the Windows GPU process implements the IMFAsyncCallback interface to receive events from the Windows Media Foundation (MF) framework. It uses an asynchronous processing model where events are handled via callbacks on a system-managed thread pool (MFASYNC_CALLBACK_QUEUE_TIMER).
The Vulnerability
The vulnerability is a potential use-after-free (UAF) that occurs because MFVEA objects can be deleted while a callback is still pending or actively executing on a Media Foundation thread.
-
Insecure Callback Registration: During initialization and whenever an event is received, MFVEA arms a callback by passing its
thispointer to the MF framework:event_generator_->BeginGetEvent(this, nullptr);The MF framework stores this raw pointer and invokes the
Invoke()method on an OS thread Chromium does not control. -
Fake Reference Counting: Although MFVEA implements
AddRefandRelease, it uses “fake” COM refcounting. TheRelease()method decrements an atomic counter but never deletes the object:ULONG MediaFoundationVideoEncodeAccelerator::Release() { DCHECK(!async_callback_ref_.IsOne()); return async_callback_ref_.Decrement() ? 1 : 0; }Consequently, the internal
AddRefperformed by the Media Foundation framework does not extend the lifetime of the C++ object. -
Unsafe Destruction: The object’s lifetime is managed by the
Destroy()method, which is reachable from a renderer process via Mojo (e.g.,videoEncoder.close()).Destroy()callsactivate_->ShutdownObject()and then immediately deletesthis:void MediaFoundationVideoEncodeAccelerator::Destroy() { if (activate_) { activate_->ShutdownObject(); } delete this; }However,
ShutdownObject()does not synchronously drain the Media Foundation work queue or guarantee that noInvoke()callbacks will run after it returns. -
Race Condition and Use-After-Free: A race condition occurs when
Destroy()is called while an MF thread is entering or executingInvoke(). TheInvoke()method is a virtual function call. If the object is deleted before the MF thread makes this call, the OS thread will execute a virtual method call on a freed object pointer.
MiraclePtr Bypass
Because the dangling pointer is held internally by the Windows Media Foundation framework (as a raw C/C++ pointer) rather than by a Chromium data structure using base::raw_ptr<T>, PartitionAlloc’s MiraclePtr (BackupRefPtr) does not track this reference. The memory is fully freed and not quarantined, making it immediately available for reallocation and exploitation.
Potential Attacker Steps (Unverified)
- An attacker compromises a renderer process or uses the WebCodecs API (
VideoEncoder). - The attacker configures the
VideoEncoderfor a Windows Media Foundation codec (e.g., H.264). - The GPU process creates and initializes a
MediaFoundationVideoEncodeAccelerator(MFVEA), registeringthiswith the MF thread pool. - The attacker abruptly closes the encoder (
videoEncoder.close()), destroying the Mojo pipe. - The GPU process executes
MFVEA::Destroy(), callingdelete this;. - The attacker rapidly allocates memory in the GPU process (e.g., via WebGL or creating other media objects) to reclaim the freed MFVEA memory block, placing a fake vtable at the start.
- A pending MF callback executes on an OS worker thread, calling
pCallback->Invoke(pAsyncResult). - The OS thread dereferences the attacker’s fake vtable, hijacking control flow and achieving RCE in the sandboxed GPU process (a Sandbox Escape from the renderer).
Recommended Fix
Synchronization must be re-introduced to ensure destruction does not proceed while a callback is active. This can be achieved in two ways:
- Implement Real COM Reference Counting: Use
Microsoft::WRL::RuntimeClassor similar mechanisms so that the object is only deleted when all OS-level references are released. This ensures the object outlives the MF thread pool callbacks. - Restore the Shutdown Lock Pattern: Similar to other Chromium Media Foundation implementations (e.g.,
MediaEngineNotifyImpl), introduce a lock (destroy_lock_) and a shutdown flag (in_shutdown_). InDestroy(), setin_shutdown_ = trueunder the lock. At the beginning ofInvoke(), acquire the lock and return early ifin_shutdown_is true. Note: this alone does not prevent the UAF ifDestroy()still immediately callsdelete this;. The object must be kept alive, orInvoke()must not be allowed to execute afterdelete this;. Therefore, proper COM refcounting is the most robust solution.
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.