Overview

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

Changed Functions

FunctionChangeNotes
if
media/renderers/BUILD.gn
modified
if
media/renderers/win/media_foundation_protection_manager.cc
modified
MediaFoundationProtectionManager
media/renderers/win/media_foundation_protection_manager.h
modified
MEDIA_EXPORT
media/renderers/win/media_foundation_protection_manager.h
modified
DestructionObserver
media/renderers/win/media_foundation_protection_manager_unittest.cc
modified
destroyed_on_correct_sequence_
media/renderers/win/media_foundation_protection_manager_unittest.cc
modified
MediaFoundationProtectionManagerTest
media/renderers/win/media_foundation_protection_manager_unittest.cc
modified
TEST_F
media/renderers/win/media_foundation_protection_manager_unittest.cc
modified

Files Changed

  • media/renderers/BUILD.gn
  • media/renderers/win/media_foundation_protection_manager.cc
  • media/renderers/win/media_foundation_protection_manager.h
  • media/renderers/win/media_foundation_protection_manager_unittest.cc
From 116a72c15f0880be23af530d0967bbc6930ad91d Mon Sep 17 00:00:00 2001
From: Sangbaek Park <[email protected]>
Date: Wed, 27 May 2026 18:02:54 -0700
Subject: [PATCH] media: Fix UAF in MediaFoundationProtectionManager

MediaFoundationProtectionManager lacks a custom Release()
implementation. This allows it to be destructed on an asynchronous
Media Foundation threadpool thread rather than its bound sequence. This
leads to the potential cross-thread destruction of sequence-affine
members such as base::CancelableOnceClosure and base::WeakPtrFactory,
resulting in a race condition with pending tasks on the main task
runner and a Use-After-Free (UAF).

This CL implements a custom Release() override to safely route the
destruction to the designated task_runner_ via DeleteSoon().

Additionally, this CL:
* Adds MEDIA_EXPORT to MediaFoundationProtectionManager so it can be
  safely used inside unit tests during component builds.
* Adds a unit test that successfully reproduces the issue. It tracks
  the destruction sequence using a custom DestructionObserver bound
  to the WaitingCB. The test verifies that destruction occurs on
  the correct sequence, failing without the fix and passing with it.

Tests: { MediaFoundationProtectionManagerTest.DestructionOnTaskRunner }

Bug: 517004487
Change-Id: I0050d9509d8ed130e69eaa9cdfaa5a615228937c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7879769
Reviewed-by: Dale Curtis <[email protected]>
Commit-Queue: Sangbaek Park <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637367}
---

diff --git a/media/renderers/BUILD.gn b/media/renderers/BUILD.gn
index 74361be..1dca83c 100644
--- a/media/renderers/BUILD.gn
+++ b/media/renderers/BUILD.gn
@@ -151,6 +151,7 @@
 
   if (is_win) {
     sources += [
+      "win/media_foundation_protection_manager_unittest.cc",
       "win/media_foundation_renderer_integration_test.cc",
       "win/media_foundation_renderer_unittest.cc",
       "win/media_foundation_source_wrapper_unittest.cc",
diff --git a/media/renderers/win/media_foundation_protection_manager.cc b/media/renderers/win/media_foundation_protection_manager.cc
index 7ba50a6c..8ccdc29 100644
--- a/media/renderers/win/media_foundation_protection_manager.cc
+++ b/media/renderers/win/media_foundation_protection_manager.cc
@@ -45,6 +45,18 @@
   return S_OK;
 }
 
+IFACEMETHODIMP_(ULONG) MediaFoundationProtectionManager::Release() {
+  ULONG ref_count = InternalRelease();
+  if (ref_count == 0) {
+    if (task_runner_ && !task_runner_->RunsTasksInCurrentSequence()) {
+      task_runner_->DeleteSoon(FROM_HERE, this);
+    } else {
+      delete this;
+    }
+  }
+  return ref_count;
+}
+
 HRESULT MediaFoundationProtectionManager::SetCdmProxy(
     scoped_refptr<MediaFoundationCdmProxy> cdm_proxy) {
   DVLOG_FUNC(1);
diff --git a/media/renderers/win/media_foundation_protection_manager.h b/media/renderers/win/media_foundation_protection_manager.h
index aad7fff..7df51f0 100644
--- a/media/renderers/win/media_foundation_protection_manager.h
+++ b/media/renderers/win/media_foundation_protection_manager.h
@@ -14,6 +14,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
+#include "media/base/media_export.h"
 #include "media/base/waiting.h"
 #include "media/base/win/media_foundation_cdm_proxy.h"
 
@@ -26,7 +27,7 @@
 // required by IMFMediaEngineProtectedContent::SetContentProtectionManager in
 // https://docs.microsoft.com/en-us/windows/win32/api/mfmediaengine/nf-mfmediaengine-imfmediaengineprotectedcontent-setcontentprotectionmanager.
 //
-class MediaFoundationProtectionManager
+class MEDIA_EXPORT MediaFoundationProtectionManager
     : public Microsoft::WRL::RuntimeClass<
           Microsoft::WRL::RuntimeClassFlags<
               Microsoft::WRL::RuntimeClassType::WinRtClassicComMix |
@@ -42,6 +43,9 @@
       WaitingCB waiting_cb);
   HRESULT SetCdmProxy(scoped_refptr<MediaFoundationCdmProxy> cdm_proxy);
 
+  // IUnknown.
+  IFACEMETHODIMP_(ULONG) Release() override;
+
   // IMFContentProtectionManager.
   IFACEMETHODIMP BeginEnableContent(IMFActivate* enabler_activate,
                                     IMFTopology* topology,
diff --git a/media/renderers/win/media_foundation_protection_manager_unittest.cc b/media/renderers/win/media_foundation_protection_manager_unittest.cc
new file mode 100644
index 0000000..9dd8e07b
--- /dev/null
+++ b/media/renderers/win/media_foundation_protection_manager_unittest.cc
@@ -0,0 +1,98 @@
+// 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_protection_manager.h"
+
+#include <wrl/client.h>
+
+#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#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 "base/win/scoped_com_initializer.h"
+#include "media/base/waiting.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+class DestructionObserver
+    : public base::RefCountedThreadSafe<DestructionObserver> {
+ public:
+  DestructionObserver(scoped_refptr<base::SequencedTaskRunner> task_runner,
+                      bool* destroyed_on_correct_sequence)
+      : task_runner_(std::move(task_runner)),
+        destroyed_on_correct_sequence_(destroyed_on_correct_sequence) {}
+
+ private:
+  friend class base::RefCountedThreadSafe<DestructionObserver>;
+  ~DestructionObserver() {
+    *destroyed_on_correct_sequence_ =
+        task_runner_->RunsTasksInCurrentSequence();
+  }
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  raw_ptr<bool> destroyed_on_correct_sequence_;
+};
+
+}  // namespace
+
+class MediaFoundationProtectionManagerTest : public testing::Test {
+ public:
+  MediaFoundationProtectionManagerTest() = default;
+  ~MediaFoundationProtectionManagerTest() override = default;
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  base::win::ScopedCOMInitializer com_initializer_{
+      base::win::ScopedCOMInitializer::kMTA};
+};
+
+TEST_F(MediaFoundationProtectionManagerTest, DestructionOnTaskRunner) {
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::SequencedTaskRunner::GetCurrentDefault();
+
+  bool destroyed_on_correct_sequence = false;
+  auto observer = base::MakeRefCounted<DestructionObserver>(
+      task_runner, &destroyed_on_correct_sequence);
+
+  Microsoft::WRL::ComPtr<MediaFoundationProtectionManager> protection_manager;
+  HRESULT hr =
+      Microsoft::WRL::MakeAndInitialize<MediaFoundationProtectionManager>(
+          &protection_manager, task_runner,
+          base::BindRepeating(
+              [](scoped_refptr<DestructionObserver>, WaitingReason) {},
+              observer));
+  EXPECT_TRUE(SUCCEEDED(hr));
+
+  auto wrapper = protection_manager;
+  protection_manager.Reset();
+  observer.reset();
+
+  base::WaitableEvent event;
+  base::ThreadPool::PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](Microsoft::WRL::ComPtr<MediaFoundationProtectionManager> wrapper,
+             base::WaitableEvent* event) {
+            wrapper.Reset();
+            event->Signal();
+          },
+          std::move(wrapper), &event));
+  event.Wait();
+
+  // Run the current thread's loop to execute the DeleteSoon task.
+  task_environment_.RunUntilIdle();
+
+  // This fails without the Use-After-Free fix for
+  // MediaFoundationProtectionManager.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/renderers/win/media_foundation_protection_manager_unittest.cc b/media/renderers/win/media_foundation_protection_manager_unittest.cc
new file mode 100644
index 0000000..9dd8e07b
--- /dev/null
+++ b/media/renderers/win/media_foundation_protection_manager_unittest.cc
@@ -0,0 +1,98 @@
+// 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_protection_manager.h"
+
+#include <wrl/client.h>
+
+#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#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 "base/win/scoped_com_initializer.h"
+#include "media/base/waiting.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+
+class DestructionObserver
+    : public base::RefCountedThreadSafe<DestructionObserver> {
+ public:
+  DestructionObserver(scoped_refptr<base::SequencedTaskRunner> task_runner,
+                      bool* destroyed_on_correct_sequence)
+      : task_runner_(std::move(task_runner)),
+        destroyed_on_correct_sequence_(destroyed_on_correct_sequence) {}
+
+ private:
+  friend class base::RefCountedThreadSafe<DestructionObserver>;
+  ~DestructionObserver() {
+    *destroyed_on_correct_sequence_ =
+        task_runner_->RunsTasksInCurrentSequence();
+  }
+
+  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  raw_ptr<bool> destroyed_on_correct_sequence_;
+};
+
+}  // namespace
+
+class MediaFoundationProtectionManagerTest : public testing::Test {
+ public:
+  MediaFoundationProtectionManagerTest() = default;
+  ~MediaFoundationProtectionManagerTest() override = default;
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  base::win::ScopedCOMInitializer com_initializer_{
+      base::win::ScopedCOMInitializer::kMTA};
+};
+
+TEST_F(MediaFoundationProtectionManagerTest, DestructionOnTaskRunner) {
+  scoped_refptr<base::SequencedTaskRunner> task_runner =
+      base::SequencedTaskRunner::GetCurrentDefault();
+
+  bool destroyed_on_correct_sequence = false;
+  auto observer = base::MakeRefCounted<DestructionObserver>(
+      task_runner, &destroyed_on_correct_sequence);
+
+  Microsoft::WRL::ComPtr<MediaFoundationProtectionManager> protection_manager;
+  HRESULT hr =
+      Microsoft::WRL::MakeAndInitialize<MediaFoundationProtectionManager>(
+          &protection_manager, task_runner,
+          base::BindRepeating(
+              [](scoped_refptr<DestructionObserver>, WaitingReason) {},
+              observer));
+  EXPECT_TRUE(SUCCEEDED(hr));
+
+  auto wrapper = protection_manager;
+  protection_manager.Reset();
+  observer.reset();
+
+  base::WaitableEvent event;
+  base::ThreadPool::PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](Microsoft::WRL::ComPtr<MediaFoundationProtectionManager> wrapper,
+             base::WaitableEvent* event) {
+            wrapper.Reset();
+            event->Signal();
+          },
+          std::move(wrapper), &event));
+  event.Wait();
+
+  // Run the current thread's loop to execute the DeleteSoon task.
+  task_environment_.RunUntilIdle();
+
+  // This fails without the Use-After-Free fix for
+  // MediaFoundationProtectionManager.
+  EXPECT_TRUE(destroyed_on_correct_sequence);
+}
+
+}  // namespace media
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in MediaFoundationProtectionManager due to cross-thread destruction

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: MediaFoundationProtectionManager lacks a custom Release() implementation, which allows it to be destructed on an asynchronous Media Foundation threadpool thread rather than its bound sequence. This leads to the potential cross-thread destruction of sequence-affine members such as base::CancelableOnceClosure and base::WeakPtrFactory. Consequently, a race condition occurs with pending tasks on the main task runner, potentially resulting in a Use-After-Free (UAF) vulnerability.

Affected files:

  • media/renderers/win/media_foundation_protection_manager.h
  • media/renderers/win/media_foundation_protection_manager.cc

Estimated timestamp from git blame: 2020-03-11

Root Cause

MediaFoundationProtectionManager is a WRL RuntimeClass handed to the closed-source MFMediaEngine via IMFMediaEngineProtectedContent::SetContentProtectionManager(). MFMediaEngine retains references to it and invokes its methods asynchronously on Media Foundation (MF) threadpool threads.

Unlike its sibling COM wrappers (MediaFoundationSourceWrapper and MediaFoundationStreamWrapper), MediaFoundationProtectionManager does not override Release() to safeguard destruction on the correct sequence. When MFMediaEngine drops the last reference on an MF threadpool thread, the default WRL RuntimeClass::Release() is invoked, running the destructor off the bound sequence runner (task_runner_).

This leads to the potential cross-thread destruction of sequence-affine members:

scoped_refptr<base::SequencedTaskRunner> task_runner_;
WaitingCB waiting_cb_;
base::CancelableOnceClosure waiting_for_key_time_out_cb_;   // must be destroyed on bound sequence
...
base::WeakPtrFactory<MediaFoundationProtectionManager> weak_factory_{this};  // Invalidate() must run on bound sequence

Because WeakReference::Flag::Invalidate() sequence assertions are restricted to DCHECK builds, in official/release builds the cross-thread destruction proceeds unchecked, causing a race condition with pending tasks on task_runner_ that hold live WeakPtrs.

Potential Vulnerability Mechanism

Based on static analysis, we have identified the following potential path to trigger the UAF:

  1. Playback of encrypted content (such as PlayReady HW-DRM) instantiates MediaFoundationProtectionManager and registers it with MFMediaEngine via SetContentProtectionManager().
  2. MFMediaEngine calls BeginEnableContent() on an MF threadpool thread, which posts OnBeginEnableContent to task_runner_ via a weak pointer. This arms a 500 ms delayed task OnWaitingForKeyTimeOut holding a WeakPtr inside the protection manager.
  3. Upon player teardown (e.g. when the user navigates away or closes the media element), ~MediaFoundationRenderer executes on task_runner_ and shuts down the media engine. However, the media engine may asynchronously retain and release its IMFContentProtectionManager reference on its threadpool after shutdown.
  4. If the media engine drops its last COM reference on the MF thread pool while a delayed or queued task (e.g., OnWaitingForKeyTimeOut) is executing on task_runner_, a race condition occurs. Since WeakPtr sequence validation is disabled on release builds, the task on task_runner_ may proceed, invoking member functions on this just as the MF threadpool calls delete this via the default Release() implementation.
  5. The active execution on task_runner_ then dereferences the freed MediaFoundationProtectionManager or its subobjects, potentially leading to a Use-After-Free (UAF) condition:
void MediaFoundationProtectionManager::OnWaitingForKeyTimeOut() {
  ...
  waiting_for_key_time_out_cb_.Cancel();          // accesses freed CancelableCallbackImpl
  waiting_cb_.Run(WaitingReason::kNoDecryptionKey); // indirect call through freed Callback bind state
}

Note: These steps are based on static analysis of the control flow and object lifecycles; our analysis tools do not currently have the capability to execute code or verify this via an active Proof of Concept.

Impact

This issue potentially leads to a use-after-free within the kMediaFoundationCdm LPAC-sandboxed utility process on Windows. If an attacker can groom or reclaim the freed allocation, they could potentially exploit this to hijack control flow and execute arbitrary code within the utility process.

Suggested Remediation

Implement a custom Release() override in MediaFoundationProtectionManager to ensure that destruction is safely routed to the designated task_runner_ via DeleteSoon if called from a different sequence, similar to sibling wrappers:

IFACEMETHODIMP_(ULONG) MediaFoundationProtectionManager::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;
}

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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