Firefox · DOM
CVE-2026-4722
Logic Error in DOM
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
docshell/base/BrowsingContext.cppdocshell/base/BrowsingContextGroup.cppdocshell/base/BrowsingContextGroup.hdom/base/StructuredCloneHolder.cppdom/base/StructuredCloneHolder.hdom/ipc/StructuredCloneData.cpp
Patch
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…
References
On This Page