High firefox UAF 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the WebRTC: Signaling component
ComponentDOM
Bug ClassUAF
Tracker1992760
Fix commit83beb3d15379 (firefox) +35/-17
CISA KEVNot listed
CreditedIgor Morgenstern
Disclosed2025-12-09

Changed Functions

FunctionChangeNotes
if
dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
modified
TakeFrame
dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
modified

Files Changed

  • dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
  • dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
  • testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
index 8d85c62f87d..abf2b82101c 100644
--- a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
+++ b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp
@@ -13,8 +13,21 @@
 #include "nsIGlobalObject.h"
 
 namespace mozilla::dom {
-NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(RTCEncodedFrameBase, (mGlobal),
-                                         (mData))
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(RTCEncodedFrameBase)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCEncodedFrameBase)
+  using ::ImplCycleCollectionUnlink;
+  tmp->DetachData();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCEncodedFrameBase)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(RTCEncodedFrameBase)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS(mData)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCEncodedFrameBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCEncodedFrameBase)
 
@@ -42,13 +55,29 @@ RTCEncodedFrameState::RTCEncodedFrameState(
     uint64_t aCounter, unsigned long aTimestamp)
     : mFrame(std::move(aFrame)), mCounter(aCounter), mTimestamp(aTimestamp) {}
 
-RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
+RTCEncodedFrameBase::~RTCEncodedFrameBase() { DetachData(); }
+
+void RTCEncodedFrameBase::DetachData() {
+  // We might have handled this in unlink already
+  if (mGlobal) {
+    AutoJSAPI jsapi;
+    if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
+      return;
+    }
+
+    JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData);
+    if (rootedData) {
+      JS::DetachArrayBuffer(jsapi.cx(), rootedData);
+    }
+  }
+}
 
 unsigned long RTCEncodedFrameBase::Timestamp() const {
   return mState.mTimestamp;
 }
 
 void RTCEncodedFrameBase::SetData(const ArrayBuffer& aData) {
+  DetachData();
   mData.set(aData.Obj());
   if (mState.mFrame) {
     aData.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
@@ -67,20 +96,7 @@ uint64_t RTCEncodedFrameBase::GetCounter() const { return mState.mCounter; }
 
 std::unique_ptr<webrtc::TransformableFrameInterface>
 RTCEncodedFrameBase::TakeFrame() {
-  if (mState.mFrame) {
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(mGlobal)) {
-      MOZ_CRASH("Could not init JSAPI!");
-    }
-    // If the JS buffer was transferred (or otherwise detached), neuter native.
-    JS::Rooted<JSObject*> rootedData(jsapi.cx(), mData);
-    if (rootedData && JS::IsDetachedArrayBufferObject(rootedData)) {
-      mState.mFrame.reset();
-      return nullptr;
-    }
-    // Still attached: detach now since we're consuming the frame.
-    JS::DetachArrayBuffer(jsapi.cx(), rootedData);
-  }
+  DetachData();
   return std::move(mState.mFrame);
 }
 
diff --git a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
index 96653fee38a..35bf8bb0b06 100644
--- a/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
+++ b/dom/media/webrtc/jsapi/RTCEncodedFrameBase.h
@@ -68,6 +68,7 @@ class RTCEncodedFrameBase : public nsISupports, public nsWrapperCache {
 
  protected:
   virtual ~RTCEncodedFrameBase();
+  void DetachData();
 
   // forbid copy/move to protect mState
   RTCEncodedFrameBase(const RTCEncodedFrameBase&) = delete;
diff --git a/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html b/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
index bcf2963661c..025073e4ba8 100644
--- a/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
+++ b/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+<meta name="timeout" content="long">
 <title>RTCRtpScriptTransform Insertable Streams - Worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html b/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
index bcf2963661c..025073e4ba8 100644
--- a/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
+++ b/testing/web-platform/tests/webrtc-encoded-transform/RTCRtpScriptTransform-sender-worker-single-frame.https.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
+<meta name="timeout" content="long">
 <title>RTCRtpScriptTransform Insertable Streams - Worker</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
Loading diff…