Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionPrivilege escalation in the IPC component
ComponentDOM
Bug ClassLogic Error
Tracker2010097
Fix commit18e4a847d6c9 (firefox) +91/-1
CISA KEVNot listed
CreditedNika Layzell
Disclosed2026-03-24

Files Changed

  • docshell/base/BrowsingContext.cpp
  • docshell/base/BrowsingContextGroup.cpp
  • docshell/base/BrowsingContextGroup.h
  • dom/base/StructuredCloneHolder.cpp
  • dom/base/StructuredCloneHolder.h
  • dom/ipc/StructuredCloneData.cpp
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
index 0c6cb7a1263..cf7bd83b568 100644
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -1634,6 +1634,10 @@ JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx,
   // destroyed before we try to return a raw JSObject*, so create it in its own
   // scope.
   if (RefPtr<BrowsingContext> context = Get(id)) {
+    if (!context->Group()->IsKnownForChildID(aHolder->GetOriginChildID())) {
+      return nullptr;
+    }
+
     if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) {
       return nullptr;
     }
diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp
index 006381bfde9..8f77c36f9c0 100644
--- a/docshell/base/BrowsingContextGroup.cpp
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -13,6 +13,7 @@
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/StaticPrefs_dom.h"
 #include "mozilla/ThrottledEventQueue.h"
@@ -287,6 +288,54 @@ bool BrowsingContextGroup::IsKnownForMessageReader(
   }
 }
 
+bool BrowsingContextGroup::IsKnownForChildID(GeckoChildID aChildID) {
+  // If the origin process is unknown, deny synced contexts from it.
+  if (NS_WARN_IF(aChildID == kInvalidGeckoChildID)) {
+    MOZ_ASSERT_UNREACHABLE("Unknown ChildID for BrowsingContextGroup");
+    return false;
+  }
+
+  // Allow deserializing a synced context from the parent process (ID 0), or the
+  // current process by-default.
+  if (aChildID == 0 || aChildID == XRE_GetChildID()) {
+    return true;
+  }
+
+  // If we're not in the parent process, deny any other messages (we shouldn't
+  // be receiving a BrowsingContext directly from a peer process anyways).
+  if (NS_WARN_IF(!XRE_IsParentProcess())) {
+    MOZ_ASSERT_UNREACHABLE("Unexpected peer ChildID for BrowsingContextGroup");
+    return false;
+  }
+
+  // Try to look up the ContentParent for this process.
+  // If we can't, be conservative and deny the request. (We should be
+  // deserializing StructuredCloneData instances containing BrowsingContexts
+  // before the process has a chance to go away)
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  if (NS_WARN_IF(!cpm)) {
+    MOZ_ASSERT_UNREACHABLE(
+        "Unexpected cross-process deserialization late in shutdown");
+    return false;
+  }
+  RefPtr<ContentParent> contentParent =
+      cpm->GetContentProcessById(ContentParentId(aChildID));
+  if (NS_WARN_IF(!contentParent)) {
+    MOZ_ASSERT_UNREACHABLE(
+        "ContentParent dead/missing when deserializing BrowsingContextGroup");
+    return false;
+  }
+
+  // The process should only be able to name this BCG if it is subscribed, or if
+  // the BCG has been destroyed (and has therefore stopped tracking subscribers)
+  if (NS_WARN_IF(!mDestroyed && !mSubscribers.Contains(contentParent))) {
+    MOZ_ASSERT_UNREACHABLE(
+        "Process is not subscribed to this BrowsingContextGroup");
+    return false;
+  }
+  return true;
+}
+
 void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() {
   if (!StaticPrefs::dom_suspend_inactive_enabled()) {
     return;
diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h
index 025098ab650..a4b39a07e6e 100644
--- a/docshell/base/BrowsingContextGroup.h
+++ b/docshell/base/BrowsingContextGroup.h
@@ -92,6 +92,10 @@ class BrowsingContextGroup final : public nsWrapperCache {
   // details.
   bool IsKnownForMessageReader(IPC::MessageReader* aReader);
 
+  // Check if the process with the given ChildID is aware of this
+  // BrowsingContextGroup's existence.
+  bool IsKnownForChildID(GeckoChildID aChildID);
+
   // When a BrowsingContext is being discarded, we may want to keep the
   // corresponding BrowsingContextGroup alive until the other process
   // acknowledges that the BrowsingContext has been discarded. A `KeepAlive`
diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp
index b6fc4bedeba..aab3f84e6b4 100644
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -436,6 +436,8 @@ void StructuredCloneHolder::Write(JSContext* aCx, JS::Handle<JS::Value> aValue,
     return;
   }
 
+  mOriginChildID = mozilla::GetGeckoChildID();
+
   AssertAttachmentsMatchFlags();
 }
 
@@ -463,6 +465,16 @@ void StructuredCloneHolder::Read(JSContext* aCx,
   }
 }
 
+void StructuredCloneHolder::Adopt(JSStructuredCloneData&& aData,
+                                  uint32_t aVersion,
+                                  GeckoChildID aOriginChildID) {
+  StructuredCloneHolderBase::Adopt(std::move(aData), aVersion);
+
+  mOriginChildID = aOriginChildID;
+
+  AssertAttachmentsMatchFlags();
+}
+
 static bool CheckExposedGlobals(JSContext* aCx, JS::Handle<JSObject*> aGlobal,
                                 uint16_t aExposedGlobals) {
   // Sandboxes aren't really DOM globals (though they do set the
diff --git a/dom/base/StructuredCloneHolder.h b/dom/base/StructuredCloneHolder.h
index 2ea10924a51..73caa462ac1 100644
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -15,6 +15,7 @@
 #include "js/TypeDecls.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ProcessType.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/ipc/EagerIPCStream.h"
@@ -217,9 +218,15 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
   void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
             const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv);
 
+  void Adopt(JSStructuredCloneData&& aData,
+             uint32_t aVersion = JS_STRUCTURED_CLONE_VERSION,
+             GeckoChildID aOriginChildID = kInvalidGeckoChildID);
+
   // Call this method to know if this object is keeping some DOM object alive.
   bool HasClonedDOMObjects();
 
+  GeckoChildID GetOriginChildID() const { return mOriginChildID; }
+
   nsTArray<NotNull<RefPtr<BlobImpl>>>& BlobImpls() {
     MOZ_ASSERT(mSupportsCloning,
                "Blobs cannot be taken/set if cloning is not supported.");
@@ -391,6 +398,15 @@ class StructuredCloneHolder : public StructuredCloneHolderBase {
   bool mSupportsCloning;
   bool mSupportsTransferring;
 
+  // In the case where this StructuredCloneHolder was received over IPC, this
+  // should be set to the GeckoChildID which created the message. In the case of
+  // an in-process serialized data structure, it will be set to the current
+  // ChildID.
+  //
+  // This value is _not_ preserved if the object is sent across multiple process
+  // boundaries. It only tracks the most recent IPC hop.
+  GeckoChildID mOriginChildID = kInvalidGeckoChildID;
+
   // SizeOfExcludingThis is inherited from StructuredCloneHolderBase. It doesn't
   // account for objects in the following arrays because a) they're not expected
   // to be stored in long-lived StructuredCloneHolder objects, and b) in the
diff --git a/dom/ipc/StructuredCloneData.cpp b/dom/ipc/StructuredCloneData.cpp
index cf0859c384e..69b4edb69d8 100644
--- a/dom/ipc/StructuredCloneData.cpp
+++ b/dom/ipc/StructuredCloneData.cpp
@@ -54,13 +54,18 @@ void StructuredCloneData::WriteIPCParams(IPC::MessageWriter* aWriter) {
 bool StructuredCloneData::ReadIPCParams(IPC::MessageReader* aReader) {
   MOZ_ASSERT(!mBuffer, "StructuredCloneData was previously initialized");
 
+  GeckoChildID originChildID =
+      aReader->GetActor()
+          ? aReader->GetActor()->ToplevelProtocol()->OtherChildIDMaybeInvalid()
+          : kInvalidGeckoChildID;
+
   uint32_t version;
   JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess);
   if (!ReadParam(aReader, &version) || !ReadParam(aReader, &data)) {
     return false;
   }
 
-  Adopt(std::move(data), version);
+  Adopt(std::move(data), version, originChildID);
 
   if (!std::apply(
           [&](auto&... member) { return ReadParams(aReader, member...); },
Loading diff…