Medium CVSS 5.1 firefox Memory Corruption 🔧 Commit mapped

Overview

Medium
Severity
5.1
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bugs present in Firefox 133, Thunderbird 133, Firefox ESR 128.5, and Thunderbird 128.5. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentDOM
Bug ClassMemory Corruption
Tracker1827142
Fix commit7ff2c73af1df (firefox) +16/-9
CWECWE-787 (Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedAndrew Osmond and the Mozilla Fuzzing Team
Disclosed2025-01-07

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 MFCDMChild that lazily launches the remote MF Media Engine CDM utility process and hooks up the IPDL actor, producing a RemotePromise that resolves once the remote side is ready.
`MozPromiseHolder`
A helper (here mRemotePromiseHolder) that owns the settle-side of a MozPromise; Ensure hands back the associated promise and RejectIfExists/Resolve settle 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.

Key insight
The core mistake was kicking off asynchronous remote-process initialization from the constructor, so the object started its IPDL lifecycle before it was fully constructed and could be initialized more than once; the fix defers initialization to an explicit, idempotent EnsureRemote() call guarded by mRemotePromise, guaranteeing single, well-ordered setup.

Attack Path

  1. 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 an MFCDMChild.
  2. Constructor-time async launch The old constructor immediately invoked EnsureRemote(), spinning up the utility-process launch and IPDL wiring while the MFCDMChild was not yet a fully established, singly-owned actor.
  3. Race / double init Concurrent construction/capability paths or re-entrant creation lead to mRemotePromiseHolder and mRemoteRequest being set up or settled against inconsistent state, since initialization was not gated to run once.
  4. 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.
  5. Exploitation With careful control of timing, the attacker shapes the corrupted allocation to achieve arbitrary code execution in the affected process.

Impact Assessment

An attacker who can run script that exercises the EME/CDM path can drive 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

FunctionChangeNotes
if
dom/media/eme/mediafoundation/WMFCDMImpl.cpp
modified
for
dom/media/eme/mediafoundation/WMFCDMImpl.cpp
modified
mShutdown
dom/media/ipc/MFCDMChild.cpp
modified
if
dom/media/ipc/MFCDMChild.cpp
modified

Files Changed

  • dom/media/eme/mediafoundation/WMFCDMImpl.cpp
  • dom/media/ipc/MFCDMChild.cpp
  • dom/media/ipc/MFCDMChild.h

Audit Directions

  • Constructor-initiated async work
    Look for classes, especially IPDL actors, that launch processes, dispatch runnables, or start MozPromise chains from their constructor before the object is fully owned and thread-anchored.
  • Missing idempotency guards
    Flag lazy-initialization methods that lack a “already initialized” early return, allowing MozPromiseHolder/MozPromiseRequestHolder state to be established or settled more than once.
  • Thread-affinity of promise holders
    Audit uses of mManagerThread, MozPromiseHolder::Ensure, and Track(mRemoteRequest) to confirm they are only invoked after the manager thread and actor are valid and from the intended thread.
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;
Loading diff…