CVE-2026-11060
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/cdm/win/media_foundation_cdm.cc |
modified |
Files Changed
media/cdm/win/media_foundation_cdm.cc
Patch
From 20d81a09abb1313dbaeee0d9ee3bdac368db76a0 Mon Sep 17 00:00:00 2001
From: Sangbaek Park <sangbaekpark@google.com>
Date: Tue, 07 Apr 2026 10:13:32 -0700
Subject: [PATCH] media: Fix data race in CdmProxyImpl
CdmProxyImpl internal members (`input_trust_authorities_`,
`last_key_ids_`, and `trusted_input_`) are accessed and mutated
concurrently by the Chromium media task runner and the Windows Media
Foundation (MF) threadpool.
Because `absl::flat_hash_map` is not thread-safe, this lack of
synchronization leads to a data race that can cause map corruption,
double-frees of COM pointers, and potential Use-After-Free (UAF)
vulnerabilities.
This CL addresses the issue by:
1. Introducing a `base::Lock` to synchronize read and write access to
the mutable state in `CdmProxyImpl`.
2. Adding `base::AutoLock` in `GetInputTrustAuthority`, `SetLastKeyId`,
`RefreshTrustedInput`, and `OnHardwareContextReset`.
Bug: 499018355
Change-Id: I4af960c4edcb37f52a2565c996bbe94f67d20821
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7729681
Commit-Queue: Sangbaek Park <sangbaekpark@chromium.org>
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1610846}
---
diff --git a/media/cdm/win/media_foundation_cdm.cc b/media/cdm/win/media_foundation_cdm.cc
index 8424314c..6374f15 100644
--- a/media/cdm/win/media_foundation_cdm.cc
+++ b/media/cdm/win/media_foundation_cdm.cc
@@ -18,6 +18,7 @@
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_propvariant.h"
#include "base/win/win_util.h"
@@ -166,6 +167,7 @@
REFIID riid,
IUnknown** object_out) override {
DVLOG_FUNC(1);
+ base::AutoLock lock(lock_);
if (input_trust_authorities_.count(stream_id)) {
RETURN_IF_FAILED(input_trust_authorities_[stream_id].CopyTo(object_out));
@@ -195,12 +197,14 @@
HRESULT SetLastKeyId(uint32_t stream_id, REFGUID key_id) override {
DVLOG_FUNC(1);
+ base::AutoLock lock(lock_);
last_key_ids_[stream_id] = key_id;
return S_OK;
}
HRESULT RefreshTrustedInput() override {
DVLOG_FUNC(1);
+ base::AutoLock lock(lock_);
// Refresh all decryptors of the last key IDs.
for (const auto& entry : input_trust_authorities_) {
@@ -234,12 +238,15 @@
}
void OnHardwareContextReset() override {
- // Hardware context reset happens, all the crypto sessions are in invalid
- // states. So drop everything here.
- // TODO(xhwang): Keep the `last_key_ids_` here for faster resume.
- trusted_input_.Reset();
- input_trust_authorities_.clear();
- last_key_ids_.clear();
+ {
+ base::AutoLock lock(lock_);
+ // Hardware context reset happens, all the crypto sessions are in invalid
+ // states. So drop everything here.
+ // TODO(xhwang): Keep the `last_key_ids_` here for faster resume.
+ trusted_input_.Reset();
+ input_trust_authorities_.clear();
+ last_key_ids_.clear();
+ }
// `CdmEvent::kHardwareContextReset` will be reported in
// `hardware_context_reset_cb_` below.
@@ -283,18 +290,22 @@
base::RepeatingClosure hardware_context_reset_cb_;
MediaFoundationCdm::CdmEventCB cdm_event_cb_;
+ // Lock to protect the mutable members. The data can be accessed from Chromium
+ // thread and MF thread pool threads.
+ base::Lock lock_;
+
// Store IMFTrustedInput to avoid potential performance cost.
- ComPtr<IMFTrustedInput> trusted_input_;
+ ComPtr<IMFTrustedInput> trusted_input_ GUARDED_BY(lock_);
// |stream_id| to IMFInputTrustAuthority (ITA) mapping. Serves two purposes:
// 1. The same ITA should always be returned in GetInputTrustAuthority() for
// the same |stream_id|.
// 2. The ITA must keep alive for decryptors to work.
absl::flat_hash_map<uint32_t, ComPtr<IMFInputTrustAuthority>>
- input_trust_authorities_;
+ input_trust_authorities_ GUARDED_BY(lock_);
// |stream_id| to last used key ID mapping.
- absl::flat_hash_map<uint32_t, GUID> last_key_ids_;
+ absl::flat_hash_map<uint32_t, GUID> last_key_ids_ GUARDED_BY(lock_);
};
} // namespace
Original Bug Report
Potential Data Race in CdmProxyImpl leading to UAF in MediaFoundationService
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 without the security team.
Overview: CdmProxyImpl instances are shared across multiple MediaFoundationRenderers that use the same Content Decryption Module (CDM). Its internal absl::flat_hash_map members are mutated concurrently by the Chromium task runner and the Windows Media Foundation threadpool without synchronization. This lack of thread safety can lead to map corruption, a double-free of COM pointers, and potential RCE in the MediaFoundationService utility process.
Affected files:
media/cdm/win/media_foundation_cdm.ccmedia/cdm/win/media_foundation_cdm.h
Estimated timestamp from git blame: 2021-06-01
Summary
CdmProxyImpl (defined in media/cdm/win/media_foundation_cdm.cc) is a RefCountedThreadSafe class that implements MediaFoundationCdmProxy. A single instance of CdmProxyImpl is shared across all MediaFoundationRenderer instances that are bound to the same MediaFoundationCdm (i.e., when multiple video elements share the same MediaKeys object).
CdmProxyImpl maintains two absl::flat_hash_map members:
input_trust_authorities_: Maps a stream ID to itsComPtr<IMFInputTrustAuthority>.last_key_ids_: Maps a stream ID to its last used Key ID.
These maps are accessed and mutated concurrently from two different execution contexts without any synchronization primitives (such as a base::Lock), leading to a critical data race.
Technical Details
The vulnerability is triggered by concurrent access from the following threads:
-
Windows Media Foundation (MF) Threadpool:
MediaFoundationSourceWrapper::GetInputTrustAuthorityis called by the OS on an MF threadpool thread. This directly callsCdmProxyImpl::GetInputTrustAuthority, which inserts the createdIMFInputTrustAuthorityintoinput_trust_authorities_.MediaFoundationSourceWrapper::~MediaFoundationSourceWrapperis a COM object destructor that runs on the thread releasing the final reference, which is often an MF threadpool thread when the pipeline tears down. This destructor callsCdmProxyImpl::SetLastKeyId, modifyinglast_key_ids_.
-
Chromium Media Task Runner:
- When a
MediaFoundationRendereris initialized, it callsMediaFoundationSourceWrapper::SetCdmProxy()on the media task runner. - This invokes
CdmProxyImpl::RefreshTrustedInput(), which iterates overinput_trust_authorities_and then explicitly callsclear()on bothinput_trust_authorities_andlast_key_ids_.
- When a
Because absl::flat_hash_map is not thread-safe, a concurrent insertion (from the MF threadpool) and clear() (from the task runner) causes undefined behavior. It can tear the internal control bytes of the map, tricking the map’s slot destruction logic into destructing a ComPtr that is simultaneously being written or has already been freed. This results in a Use-After-Free (UAF) and a double Release() call on the COM object.
Potential Exploit Scenario
Note: These are potential steps to trigger the bug. We don’t yet have a working proof of concept that has been successfully run.
- An attacker creates a malicious web page that instantiates an Encrypted Media Extensions (EME) session, creating a
MediaKeysobject. - The attacker creates two HTML
<video>elements (Video 1 and Video 2) and attaches the exact sameMediaKeysobject to both viasetMediaKeys(). - The attacker assigns an encrypted source to Video 1 and starts playback. The browser creates a
MediaFoundationRendererin theMediaFoundationServiceutility process. - The
MediaFoundationRendererinitializes and creates a sharedCdmProxyImplinstance. - Video 1’s Media Foundation pipeline starts up on the Windows MF threadpool. It requests the Input Trust Authority, causing the MF thread to insert an item into the
input_trust_authorities_map. - Concurrently, the attacker assigns a source to Video 2 to start its playback.
- Video 2’s
MediaFoundationRendererinitializes on the Chromium media task runner, acquiring the sameCdmProxyImplinstance. - Video 2’s initialization calls
RefreshTrustedInput(), which begins iterating and callingclear()on the map. - The concurrent mutation and clearing corrupts the hash map. The map destroys a
ComPtrleading to a doubleRelease(). - Since
Release()performs a virtual call (vtable->Release()), an attacker who has groomed the heap can hijack the control flow, potentially achieving Remote Code Execution (RCE) within theMediaFoundationServiceprocess.
Recommendation
Synchronize access to the internal maps in CdmProxyImpl.
- The most straightforward fix is to add a
base::LocktoCdmProxyImpland acquire it inGetInputTrustAuthority,SetLastKeyId,RefreshTrustedInput, andOnHardwareContextReset. - Alternatively, restrict all map modifications to a single sequence (e.g., the Chromium media task runner) by using
base::BindPostTaskto bounce calls from the MF threadpool to the task runner, though this may introduce latency into the COM calls.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
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.