CVE-2026-9945
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/renderers/win/media_foundation_source_wrapper.cc |
modified | |
MediaFoundationSourceWrappermedia/renderers/win/media_foundation_source_wrapper.h |
modified | |
MEDIA_EXPORTmedia/renderers/win/media_foundation_source_wrapper.h |
modified | |
MediaFoundationSourceWrapperTestmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
MediaFoundationSourceWrapperTestmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
TEST_Fmedia/renderers/win/media_foundation_source_wrapper_unittest.cc |
modified | |
ifmedia/renderers/win/media_foundation_stream_wrapper.cc |
modified |
Files Changed
media/renderers/BUILD.gnmedia/renderers/win/media_foundation_source_wrapper.ccmedia/renderers/win/media_foundation_source_wrapper.hmedia/renderers/win/media_foundation_source_wrapper_unittest.ccmedia/renderers/win/media_foundation_stream_wrapper.ccmedia/renderers/win/media_foundation_stream_wrapper.h
Patch
From 41ffa38dcb7f3ce786130a2834c51b00cc0e78a9 Mon Sep 17 00:00:00 2001
From: Sangbaek Park <sangbaekpark@chromium.org>
Date: Tue, 28 Apr 2026 11:59:07 -0700
Subject: [PATCH] media: Fix cross-thread use-after-free in MediaFoundationStreamWrapper
MediaFoundationStreamWrapper uses default COM reference counting for
lifetime management, allowing it to be destructed on a background MF
thread. This introduces a Time-of-Check to Time-of-Use (TOCTOU) race
condition when executing tasks posted to the Chromium media thread
via base::WeakPtr, potentially leading to a use-after-free.
This CL enforces that MediaFoundationStreamWrapper is always
destroyed on the correct sequenced task runner by overriding Release()
to use DeleteSoon().
The same solution is applied to MediaFoundationSourceWrapper.
Tests: { MediaFoundationStreamWrapperTest.DestructionOnTaskRunner,
MediaFoundationSourceWrapperTest.DestructionOnTaskRunner }
Bug: 503565293
Change-Id: Ib9044672195157f30e29905c8f1565a63c411ab2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7787693
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Sangbaek Park <sangbaekpark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1621947}
---
diff --git a/media/renderers/BUILD.gn b/media/renderers/BUILD.gn
index 67642cb1..74361be 100644
--- a/media/renderers/BUILD.gn
+++ b/media/renderers/BUILD.gn
@@ -153,6 +153,7 @@
sources += [
"win/media_foundation_renderer_integration_test.cc",
"win/media_foundation_renderer_unittest.cc",
+ "win/media_foundation_source_wrapper_unittest.cc",
"win/media_foundation_stream_wrapper_unittest.cc",
]
deps += [ "//media/test:pipeline_integration_test_base" ]
diff --git a/media/renderers/win/media_foundation_source_wrapper.cc b/media/renderers/win/media_foundation_source_wrapper.cc
index 2f7ade0..a06b3d7 100644
--- a/media/renderers/win/media_foundation_source_wrapper.cc
+++ b/media/renderers/win/media_foundation_source_wrapper.cc
@@ -35,6 +35,18 @@
}
}
+IFACEMETHODIMP_(ULONG) MediaFoundationSourceWrapper::Release() {
+ ULONG ref_count = InternalRelease();
+ if (ref_count == 0) {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ task_runner_->DeleteSoon(FROM_HERE, this);
+ } else {
+ delete this;
+ }
+ }
+ return ref_count;
+}
+
HRESULT MediaFoundationSourceWrapper::RuntimeClassInitialize(
MediaResource* media_resource,
MediaLog* media_log,
diff --git a/media/renderers/win/media_foundation_source_wrapper.h b/media/renderers/win/media_foundation_source_wrapper.h
index 4bc0657..1a14e52e 100644
--- a/media/renderers/win/media_foundation_source_wrapper.h
+++ b/media/renderers/win/media_foundation_source_wrapper.h
@@ -14,6 +14,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
+#include "media/base/media_export.h"
#include "media/base/media_resource.h"
#include "media/base/win/media_foundation_cdm_proxy.h"
#include "media/renderers/win/media_foundation_stream_wrapper.h"
@@ -31,7 +32,7 @@
// Note: The methods in this class can be called on two different threads -
// Chromium thread and MF threadpool thread.
//
-class MediaFoundationSourceWrapper
+class MEDIA_EXPORT MediaFoundationSourceWrapper
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<
Microsoft::WRL::RuntimeClassType::ClassicCom>,
@@ -44,6 +45,8 @@
MediaFoundationSourceWrapper();
~MediaFoundationSourceWrapper() override;
+ IFACEMETHODIMP_(ULONG) Release() override;
+
// This is only called on |task_runner|.
void DetachResource();
diff --git a/media/renderers/win/media_foundation_source_wrapper_unittest.cc b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
new file mode 100644
index 0000000..fd90b344
--- /dev/null
+++ b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "media/renderers/win/media_foundation_source_wrapper.h"
+
+#include <mfapi.h>
+
+#include <memory>
+
+#include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "media/base/media_util.h"
+#include "media/base/mock_filters.h"
+#include "media/base/test_helpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::MakeAndInitialize;
+
+class MediaFoundationSourceWrapperTest : public testing::Test {
+ public:
+ MediaFoundationSourceWrapperTest() {
+ source_wrapper_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+
+ null_media_log_ = std::make_unique<NullMediaLog>();
+
+ MakeAndInitialize<MediaFoundationSourceWrapper>(
+ &mf_source_wrapper_, &media_resource_, null_media_log_.get(),
+ source_wrapper_task_runner_);
+ }
+
+ ~MediaFoundationSourceWrapperTest() override {
+ mf_source_wrapper_.Reset();
+ task_environment_.RunUntilIdle();
+ }
+
+ protected:
+ ComPtr<MediaFoundationSourceWrapper> mf_source_wrapper_;
+ base::test::TaskEnvironment task_environment_;
+ scoped_refptr<base::SequencedTaskRunner> source_wrapper_task_runner_;
+ testing::NiceMock<MockMediaResource> media_resource_;
+ std::unique_ptr<MediaLog> null_media_log_;
+};
+
+// Initializes a MediaFoundationSourceWrapper inside a simulated task runner
+// environment, then manually triggers its destruction from a background thread
+// to verify that it successfully bounces the destruction back to the sequenced
+// task runner.
+TEST_F(MediaFoundationSourceWrapperTest, DestructionOnTaskRunner) {
+ auto wrapper = mf_source_wrapper_;
+ mf_source_wrapper_.Reset();
+
+ base::WaitableEvent event;
+ base::ThreadPool::PostTask(
+ FROM_HERE, base::BindOnce(
+ [](ComPtr<MediaFoundationSourceWrapper> wrapper,
+ base::WaitableEvent* event) {
+ wrapper.Reset();
+ event->Signal();
+ },
+ std::move(wrapper), &event));
+ event.Wait();
+
+ // Wait for the task runner to process the deletion.
+ task_environment_.RunUntilIdle();
+}
+
+} // namespace media
diff --git a/media/renderers/win/media_foundation_stream_wrapper.cc b/media/renderers/win/media_foundation_stream_wrapper.cc
index 18bb9c19..3633ea79b9 100644
--- a/media/renderers/win/media_foundation_stream_wrapper.cc
+++ b/media/renderers/win/media_foundation_stream_wrapper.cc
@@ -39,6 +39,18 @@
MediaFoundationStreamWrapper::MediaFoundationStreamWrapper() = default;
MediaFoundationStreamWrapper::~MediaFoundationStreamWrapper() = default;
+IFACEMETHODIMP_(ULONG) MediaFoundationStreamWrapper::Release() {
+ ULONG ref_count = InternalRelease();
+ if (ref_count == 0) {
+ if (!task_runner_->RunsTasksInCurrentSequence()) {
+ task_runner_->DeleteSoon(FROM_HERE, this);
+ } else {
+ delete this;
+ }
+ }
+ return ref_count;
+}
+
/*static*/
HRESULT MediaFoundationStreamWrapper::Create(
int stream_id,
diff --git a/media/renderers/win/media_foundation_stream_wrapper.h b/media/renderers/win/media_foundation_stream_wrapper.h
index a60e60ff..5a4d44d 100644
--- a/media/renderers/win/media_foundation_stream_wrapper.h
+++ b/media/renderers/win/media_foundation_stream_wrapper.h
Regression Test / PoC
diff --git a/media/renderers/win/media_foundation_source_wrapper_unittest.cc b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
new file mode 100644
index 0000000..fd90b344
--- /dev/null
+++ b/media/renderers/win/media_foundation_source_wrapper_unittest.cc
@@ -0,0 +1,74 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "media/renderers/win/media_foundation_source_wrapper.h"
+
+#include <mfapi.h>
+
+#include <memory>
+
+#include "base/synchronization/waitable_event.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/task_environment.h"
+#include "media/base/media_util.h"
+#include "media/base/mock_filters.h"
+#include "media/base/test_helpers.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::MakeAndInitialize;
+
+class MediaFoundationSourceWrapperTest : public testing::Test {
+ public:
+ MediaFoundationSourceWrapperTest() {
+ source_wrapper_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
+
+ null_media_log_ = std::make_unique<NullMediaLog>();
+
+ MakeAndInitialize<MediaFoundationSourceWrapper>(
+ &mf_source_wrapper_, &media_resource_, null_media_log_.get(),
+ source_wrapper_task_runner_);
+ }
+
+ ~MediaFoundationSourceWrapperTest() override {
+ mf_source_wrapper_.Reset();
+ task_environment_.RunUntilIdle();
+ }
+
+ protected:
+ ComPtr<MediaFoundationSourceWrapper> mf_source_wrapper_;
+ base::test::TaskEnvironment task_environment_;
+ scoped_refptr<base::SequencedTaskRunner> source_wrapper_task_runner_;
+ testing::NiceMock<MockMediaResource> media_resource_;
+ std::unique_ptr<MediaLog> null_media_log_;
+};
+
+// Initializes a MediaFoundationSourceWrapper inside a simulated task runner
+// environment, then manually triggers its destruction from a background thread
+// to verify that it successfully bounces the destruction back to the sequenced
+// task runner.
+TEST_F(MediaFoundationSourceWrapperTest, DestructionOnTaskRunner) {
+ auto wrapper = mf_source_wrapper_;
+ mf_source_wrapper_.Reset();
+
+ base::WaitableEvent event;
+ base::ThreadPool::PostTask(
+ FROM_HERE, base::BindOnce(
+ [](ComPtr<MediaFoundationSourceWrapper> wrapper,
+ base::WaitableEvent* event) {
+ wrapper.Reset();
+ event->Signal();
+ },
+ std::move(wrapper), &event));
+ event.Wait();
+
+ // Wait for the task runner to process the deletion.
+ task_environment_.RunUntilIdle();
+}
+
+} // namespace media
diff --git a/media/renderers/win/media_foundation_stream_wrapper_unittest.cc b/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
index 38bbc4e..0b317d75 100644
--- a/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
+++ b/media/renderers/win/media_foundation_stream_wrapper_unittest.cc
@@ -86,6 +86,7 @@
~MediaFoundationStreamWrapperTest() override {
mf_video_stream_wrapper_.Reset();
+ task_environment_.RunUntilIdle();
}
// Polls the MediaFoundationStreamWrapper object for queued media events.
@@ -342,4 +343,27 @@
VerifyBufferedPostFlushSamplesProcessed(/*startEvent*/ false);
}
+// Initializes a MediaFoundationStreamWrapper inside a simulated task runner
+// environment, then manually triggers its destruction from a background thread
+// to verify that it successfully bounces the destruction back to the sequenced
+// task runner.
+TEST_F(MediaFoundationStreamWrapperTest, DestructionOnTaskRunner) {
+ auto wrapper = mf_video_stream_wrapper_;
+ mf_video_stream_wrapper_.Reset();
+
+ base::WaitableEvent event;
+ base::ThreadPool::PostTask(
+ FROM_HERE, base::BindOnce(
+ [](ComPtr<MediaFoundationStreamWrapper> wrapper,
+ base::WaitableEvent* event) {
+ wrapper.Reset();
+ event->Signal();
+ },
+ std::move(wrapper), &event));
+ event.Wait();
+
+ // Wait for the task runner to process the deletion.
+ task_environment_.RunUntilIdle();
+}
+
} // namespace media
Original Bug Report
Potential Cross-Thread Use-After-Free in MediaFoundationStreamWrapper
Flapjack, 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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: MediaFoundationStreamWrapper’s lifetime is managed by COM reference counting, permitting its destruction on a background Media Foundation thread. This creates a Time-of-Check to Time-of-Use (TOCTOU) race condition when executing tasks posted to the Chromium media thread via base::WeakPtr. A successful race can lead to a Use-After-Free (UAF) and arbitrary code execution in the MediaFoundationService utility process.
Affected files:
media/renderers/win/media_foundation_stream_wrapper.ccmedia/renderers/win/media_foundation_stream_wrapper.h
Estimated timestamp from git blame: 2025-11-24
Summary
A potential cross-thread Use-After-Free (UAF) vulnerability exists in MediaFoundationStreamWrapper (media/renderers/win/media_foundation_stream_wrapper.cc). The class relies on default COM reference counting for lifetime management, meaning its destruction can occur on a background Windows Media Foundation (MF) threadpool thread. However, it uses a base::WeakPtrFactory to post tasks to the Chromium media thread. Because base::WeakPtr is not thread-safe for concurrent destruction and dereferencing, a Time-of-Check to Time-of-Use (TOCTOU) race condition exists that allows the media thread to execute methods on freed memory.
Technical Details
MediaFoundationStreamWrapper implements IMFMediaStream and inherits from Microsoft::WRL::RuntimeClass. It interacts with both the Chromium media pipeline and the asynchronous MF pipeline. When the MF pipeline requests a sample (RequestSample), the wrapper posts a task to the Chromium media thread:
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MediaFoundationStreamWrapper::ProcessRequestsIfPossible,
weak_factory_.GetWeakPtr()));
Because there is no custom Release() override that trampolines destruction to the task_runner_, if the MF pipeline drops the final COM reference, ~MediaFoundationStreamWrapper() executes synchronously on the MF background thread. This destroys the weak_factory_.
Concurrently, when the task executes on the Chromium media thread, the base::internal::InvokeHelper evaluates the WeakPtr. It unwraps the pointer, checks its validity, and then invokes the bound method. If the MF thread invalidates the WeakPtr and frees the MediaFoundationStreamWrapper’s memory after the WeakPtr’s validity has been confirmed but before or during the execution of the target method, a UAF occurs.
Notably, the underlying pointer within base::WeakPtr is declared as RAW_PTR_EXCLUSION T* ptr_; (base/memory/weak_ptr.h). Therefore, the freed memory is not quarantined by MiraclePtr (BackupRefPtr), leaving it immediately exploitable.
Potential Attacker Steps
Note: These are suggested steps; our tooling has not executed a live Proof of Concept.
- Trigger playback: An attacker creates a webpage hosting DRM-protected media or specific codecs that initialize the
MediaFoundationRendererinside theMediaFoundationServiceutility process. - Queue tasks: The MF pipeline asynchronously calls
RequestSample(), queuingProcessRequestsIfPossible()tasks bound toWeakPtrs on the Chromium media thread. - Initiate Teardown: The attacker rapidly destroys the
<video>element. The Chromium pipeline drops its COM references to theMediaFoundationSourceWrapperand its streams. - Race Condition: The MF pipeline subsequently shuts down and drops its final reference to the
MediaFoundationStreamWrapperon a background thread. - TOCTOU Gap: On the Chromium media thread, the queued task begins execution.
InvokeHelperconfirms theWeakPtris valid and resolves it to a raw C++ reference. - Object Destruction: Before the method fully executes, the MF thread destroys the
MediaFoundationStreamWrapperand its memory is deallocated. - Heap Grooming: The attacker leverages cross-process heap grooming (via Mojo IPCs to the utility process) to immediately reallocate the freed memory block with forged contents (e.g., an unlocked
base::Lockstate and a controlleddemuxer_stream_pointer). - Exploitation:
ProcessRequestsIfPossible()executes on the attacker-controlled memory block. It acquires the forged lock, reads the forgeddemuxer_stream_pointer, and makes a virtual method call (demuxer_stream_->Read()), redirecting control flow and achieving Remote Code Execution (RCE) in the utility process.
Suggested Fix
Implement a custom thread-hopping Release() override in MediaFoundationStreamWrapper (and similar COM-visible classes in this directory, like MediaFoundationSourceWrapper) to ensure the object is always destructed on the correct Chromium sequence.
For example:
ULONG STDMETHODCALLTYPE Release() override {
ULONG ref_count = Microsoft::WRL::RuntimeClass<...>::Release();
if (ref_count == 0) {
// Note: requires modifying the class to not auto-delete in the base Release(),
// or using a custom Deleter with a smart pointer instead of strict WRL inheritance.
}
return ref_count;
}
Alternatively, use base::RefCountedDeleteOnSequence paradigms, or wrap the WRL RuntimeClass inside an inner object so the outer Chromium-facing class controls the teardown strictly on the task_runner_.
Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f
Results 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.