CVE-2025-0243
Overview
Background
- `MFCDMChild`
- The content/parent-process IPDL child endpoint (
PMFCDMChild) that proxies Content Decryption Module (CDM) operations to a Windows Media Foundation CDM running in a separate utility process. - `EnsureRemote`
- A method on
MFCDMChildthat lazily launches the remote MF Media Engine CDM utility process and hooks up the IPDL actor, producing aRemotePromisethat resolves once the remote side is ready. - `MozPromiseHolder`
- A helper (here
mRemotePromiseHolder) that owns the settle-side of aMozPromise;Ensurehands back the associated promise andRejectIfExists/Resolvesettle it exactly once. - `MozPromiseRequestHolder`
- A tracker (here
mRemoteRequest) that holds a live async request so it can be disconnected/tracked; it must be driven from a valid manager thread.
Root Cause Analysis
MFCDMChild originally called EnsureRemote() unconditionally from its constructor and stored the result in mRemotePromise, which meant the object began launching the remote utility process and wiring up the IPDL actor before callers had finished constructing and configuring the object. Because construction happened on threads and in orderings the class did not fully control, the constructor-driven EnsureRemote() path could race with — or be re-entered against — the normal lifecycle, and mRemotePromiseHolder/mRemoteRequest could be set up or settled in states that violated the invariant that the remote handshake is initiated exactly once, from a valid mManagerThread, after the object is safely constructed.
The fix removes the constructor call, makes EnsureRemote() return void, and adds an idempotency guard (if (mRemotePromise) return;) so that the remote launch is triggered explicitly by the owner (WMFCDMImpl::Init and WMFCDMCapabilites::GetCapabilities) only once the object is fully live. This closes the window in which stale or doubly-initialized promise/request state could be used, preventing the memory-corruption-class use of a partially constructed or re-entrantly initialized actor.
EnsureRemote() call guarded by mRemotePromise, guaranteeing single, well-ordered setup.Attack Path
- Trigger CDM creation
Web content on a page using Encrypted Media Extensions requests a hardware/software key system, causing
WMFCDMImpl::Init(or a capabilities query) to construct anMFCDMChild. - Constructor-time async launch
The old constructor immediately invoked
EnsureRemote(), spinning up the utility-process launch and IPDL wiring while theMFCDMChildwas not yet a fully established, singly-owned actor. - Race / double init
Concurrent construction/capability paths or re-entrant creation lead to
mRemotePromiseHolderandmRemoteRequestbeing set up or settled against inconsistent state, since initialization was not gated to run once. - Corrupted actor state The resulting dangling/duplicated promise and request state produces a use of freed or inconsistent memory when the remote handshake resolves or rejects, i.e. memory corruption.
- Exploitation With careful control of timing, the attacker shapes the corrupted allocation to achieve arbitrary code execution in the affected process.
Impact Assessment
MFCDMChild construction and provoke memory corruption in the process hosting the CDM child endpoint, which Mozilla rates as moderate and presumes potentially exploitable for arbitrary code execution. The precondition is a build with the Windows Media Foundation CDM support compiled in and reachable, and the ability to initiate CDM initialization or capability queries from content.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/media/eme/mediafoundation/WMFCDMImpl.cpp |
modified | |
fordom/media/eme/mediafoundation/WMFCDMImpl.cpp |
modified | |
mShutdowndom/media/ipc/MFCDMChild.cpp |
modified | |
ifdom/media/ipc/MFCDMChild.cpp |
modified |
Files Changed
dom/media/eme/mediafoundation/WMFCDMImpl.cppdom/media/ipc/MFCDMChild.cppdom/media/ipc/MFCDMChild.h
Audit Directions
- Constructor-initiated async workLook for classes, especially IPDL actors, that launch processes, dispatch runnables, or start
MozPromisechains from their constructor before the object is fully owned and thread-anchored. - Missing idempotency guardsFlag lazy-initialization methods that lack a “already initialized” early return, allowing
MozPromiseHolder/MozPromiseRequestHolderstate to be established or settled more than once. - Thread-affinity of promise holdersAudit uses of
mManagerThread,MozPromiseHolder::Ensure, andTrack(mRemoteRequest)to confirm they are only invoked after the manager thread and actor are valid and from the intended thread.
Patch
diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp
index 9491f4c9ec5..7cc9fe4efd8 100644
--- a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp
+++ b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp
@@ -20,6 +20,7 @@ RefPtr<WMFCDMImpl::InitPromise> WMFCDMImpl::Init(
const WMFCDMImpl::InitParams& aParams) {
if (!mCDM) {
mCDM = MakeRefPtr<MFCDMChild>(mKeySystem);
+ mCDM->EnsureRemote();
}
RefPtr<WMFCDMImpl> self = this;
mCDM->Init(aParams.mOrigin, aParams.mInitDataTypes,
@@ -58,6 +59,7 @@ WMFCDMCapabilites::GetCapabilities(
nsTArray<RefPtr<CapabilitiesPromise>> promises;
for (const auto& request : aRequests) {
RefPtr<MFCDMChild> cdm = new MFCDMChild(request.mKeySystem);
+ cdm->EnsureRemote();
promises.AppendElement(cdm->GetCapabilities(MFCDMCapabilitiesRequest{
nsString{request.mKeySystem},
request.mDecryption == KeySystemConfig::DecryptionInfo::Hardware,
diff --git a/dom/media/ipc/MFCDMChild.cpp b/dom/media/ipc/MFCDMChild.cpp
index 9df86b82f47..4cbc7403970 100644
--- a/dom/media/ipc/MFCDMChild.cpp
+++ b/dom/media/ipc/MFCDMChild.cpp
@@ -73,19 +73,25 @@ MFCDMChild::MFCDMChild(const nsAString& aKeySystem)
: mKeySystem(aKeySystem),
mManagerThread(RemoteDecoderManagerChild::GetManagerThread()),
mState(NS_ERROR_NOT_INITIALIZED),
- mShutdown(false) {
- mRemotePromise = EnsureRemote();
-}
+ mShutdown(false) {}
MFCDMChild::~MFCDMChild() {}
-RefPtr<MFCDMChild::RemotePromise> MFCDMChild::EnsureRemote() {
+void MFCDMChild::EnsureRemote() {
+ if (mRemotePromise) {
+ LOG("already created remote promise");
+ return;
+ }
+
if (!mManagerThread) {
LOG("no manager thread");
mState = NS_ERROR_NOT_AVAILABLE;
- return RemotePromise::CreateAndReject(mState, __func__);
+ mRemotePromise = RemotePromise::CreateAndReject(mState, __func__);
+ return;
}
+ mRemotePromise = mRemotePromiseHolder.Ensure(__func__);
+
RefPtr<MFCDMChild> self = this;
RemoteDecoderManagerChild::LaunchUtilityProcessIfNeeded(
RemoteDecodeIn::UtilityProcess_MFMediaEngineCDM)
@@ -115,7 +121,6 @@ RefPtr<MFCDMChild::RemotePromise> MFCDMChild::EnsureRemote() {
mRemotePromiseHolder.RejectIfExists(rv, __func__);
})
->Track(mRemoteRequest);
- return mRemotePromiseHolder.Ensure(__func__);
}
void MFCDMChild::Shutdown() {
diff --git a/dom/media/ipc/MFCDMChild.h b/dom/media/ipc/MFCDMChild.h
index ec766cab248..2f36b9df0d1 100644
--- a/dom/media/ipc/MFCDMChild.h
+++ b/dom/media/ipc/MFCDMChild.h
@@ -85,6 +85,8 @@ class MFCDMChild final : public PMFCDMChild {
mState = NS_ERROR_NOT_AVAILABLE;
}
}
+
+ void EnsureRemote();
void Shutdown();
nsISerialEventTarget* ManagerThread() { return mManagerThread; }
@@ -95,9 +97,6 @@ class MFCDMChild final : public PMFCDMChild {
private:
~MFCDMChild();
- using RemotePromise = GenericNonExclusivePromise;
- RefPtr<RemotePromise> EnsureRemote();
-
void AssertSendable();
const nsString mKeySystem;
@@ -105,6 +104,7 @@ class MFCDMChild final : public PMFCDMChild {
const RefPtr<nsISerialEventTarget> mManagerThread;
RefPtr<MFCDMChild> mIPDLSelfRef;
+ using RemotePromise = GenericNonExclusivePromise;
RefPtr<RemotePromise> mRemotePromise;
MozPromiseHolder<RemotePromise> mRemotePromiseHolder;
MozPromiseRequestHolder<RemotePromise> mRemoteRequest;