CVE-2026-12294
Overview
Background
- Remote type
- the string tag (e.g.
SharedWebRemoteType,DEFAULT_REMOTE_TYPE,NOT_REMOTE_TYPE) that identifies which content-process category a document or worker is assigned to under process isolation. - Shared worker
- a
SharedWorkerscript shared across same-origin browsing contexts and hosted in a content process brokered by the parent-processSharedWorkerService. - `ThreadsafeContentParentHandle`
- a parent-process handle that names the actual content process that sent a given IPC request, retrievable via
BackgroundParent::GetContentParentHandle. - Fission
- Gecko’s site-isolation architecture that places origins in distinct content processes, using each process’s remote type to enforce the boundary.
Root Cause Analysis
When a content process created a SharedWorker, SharedWorker::Constructor computed the target remote type in the content process and shipped it to the parent inside RemoteWorkerData, and the parent-side SharedWorkerService::GetOrCreateWorkerManagerOnMainThread recomputed it by calling RemoteWorkerManager::GetRemoteType, which for a shared worker derived its preferred remote type from ContentChild::GetSingleton()->GetRemoteType(). That derivation was wrong in the parent process, where ContentChild::GetSingleton() returns null, so the code silently fell back to the generic SharedWebRemoteType instead of the requesting process’s real remote type — meaning the parent’s decision was not anchored to the trusted identity of the process that actually asked. The violated invariant is that a shared worker must be placed in / matched to a content process consistent with the true remote type of the requesting process as known to the parent, never a value supplied or inferred from the content side.
The fix removes the ContentChild-based guess entirely and adds an explicit aCurrentRemoteType parameter to GetRemoteType, requiring each caller to pass an authoritative value. For shared workers the parent now derives that value from aContentParentHandle->GetRemoteType() — the trusted ThreadsafeContentParentHandle of the real sender — and the content-supplied copyData.remoteType() is no longer trusted or copied back. This works because the process-placement decision is now made in the parent using an unforgeable, correct source of the requester’s identity.
ContentChild::GetSingleton() derivation that degrades to a generic default in the parent, plus a content-supplied remoteType() — instead of the parent’s trusted view of the sender. The fix threads the sender’s real remote type from ThreadsafeContentParentHandle::GetRemoteType() into GetRemoteType, so isolation is computed against the true process identity.Attack Path
- Compromise a content process The attacker first achieves code execution inside a sandboxed content process, giving control over the IPC messages it emits.
- Craft a shared-worker request
The compromised process issues a
SharedWorkerconstruction whoseRemoteWorkerData(principal and self-reportedremoteType()) is chosen to steer process selection. - Exploit the mis-derived remote type
In
GetOrCreateWorkerManagerOnMainThreadthe parent recomputes the remote type using the genericSharedWebRemoteTypefallback rather than the requester’s true remote type, so isolation is evaluated against the wrong process. - Land the worker in an unintended process The shared worker is created in or matched to a content process that should not host that origin’s worker, breaching the Fission boundary.
- Cross the boundary Through the misplaced shared worker the attacker reaches state or execution context in a process outside its intended sandbox, escalating out of the origin’s isolation.
Impact Assessment
SharedWorkerService path that brokers shared-worker placement across the content-process boundary. The precondition is prior compromise of a content process (or the ability to drive malicious shared-worker IPC), consistent with the SandboxEscape classification and high impact.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/workers/remoteworkers/RemoteWorkerManager.cpp |
modified | |
GetOrCreateWorkerManagerRunnabledom/workers/sharedworkers/SharedWorkerService.cpp |
modified | |
Rundom/workers/sharedworkers/SharedWorkerService.cpp |
modified | |
RemoteWorkerDatadom/workers/sharedworkers/SharedWorkerService.h |
modified | |
SharedWorkerManagerdom/workers/sharedworkers/SharedWorkerService.h |
modified | |
SharedWorkerParentdom/workers/sharedworkers/SharedWorkerService.h |
modified | |
ThreadsafeContentParentHandledom/workers/sharedworkers/SharedWorkerService.h |
modified | |
UniqueMessagePortIddom/workers/sharedworkers/SharedWorkerService.h |
modified |
Files Changed
dom/serviceworkers/ServiceWorkerPrivate.cppdom/workers/remoteworkers/RemoteWorkerManager.cppdom/workers/remoteworkers/RemoteWorkerManager.hdom/workers/sharedworkers/SharedWorker.cppdom/workers/sharedworkers/SharedWorkerService.cppdom/workers/sharedworkers/SharedWorkerService.h
Audit Directions
- Process identity from the sender, not the payloadIn any parent/broker IPC handler, derive a process’s remote type or origin from the actor’s
ThreadsafeContentParentHandle/ContentParent, never from a value carried in the message body. - `ContentChild::GetSingleton()` in shared codeFlag logic that calls
ContentChild::GetSingleton()(or similar child-only singletons) in code that can run in the parent process, where it returns null and silently degrades to a default. - Remote-type / isolation computationsReview every call to
GetRemoteType,IsolationOptionsForWorker, andMatchRemoteTypeto confirm the “current” remote type is an authoritative parent-side value and that content-suppliedremoteType()fields are treated as untrusted.
Patch
diff --git a/dom/serviceworkers/ServiceWorkerPrivate.cpp b/dom/serviceworkers/ServiceWorkerPrivate.cpp
index eb1948f0005..5bda51baf72 100644
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -714,7 +714,8 @@ nsresult ServiceWorkerPrivate::Initialize() {
}
auto remoteType = RemoteWorkerManager::GetRemoteType(
- principal, WorkerKind::WorkerKindService);
+ principal, WorkerKind::WorkerKindService,
+ SharedWebRemoteType(principal->OriginAttributesRef()));
if (NS_WARN_IF(remoteType.isErr())) {
return remoteType.unwrapErr();
}
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.cpp b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
index 781c1330e73..24467b59e35 100644
--- a/dom/workers/remoteworkers/RemoteWorkerManager.cpp
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.cpp
@@ -120,7 +120,8 @@ bool RemoteWorkerManager::MatchRemoteType(const nsACString& processRemoteType,
// static
Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
- const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind) {
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind,
+ const nsACString& aCurrentRemoteType) {
AssertIsOnMainThread();
MOZ_ASSERT_IF(aWorkerKind == WorkerKind::WorkerKindService,
@@ -133,20 +134,8 @@ Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
return NOT_REMOTE_TYPE;
}
- nsCString preferredRemoteType =
- SharedWebRemoteType(aPrincipal->OriginAttributesRef());
- if (aWorkerKind == WorkerKind::WorkerKindShared) {
- if (auto* contentChild = ContentChild::GetSingleton()) {
- // For a shared worker set the preferred remote type to the content
- // child process remote type.
- preferredRemoteType = contentChild->GetRemoteType();
- } else if (aPrincipal->IsSystemPrincipal()) {
- preferredRemoteType = NOT_REMOTE_TYPE;
- }
- }
-
auto result = IsolationOptionsForWorker(
- aPrincipal, aWorkerKind, preferredRemoteType, FissionAutostart());
+ aPrincipal, aWorkerKind, aCurrentRemoteType, FissionAutostart());
if (NS_WARN_IF(result.isErr())) {
LOG(("GetRemoteType Abort: IsolationOptionsForWorker failed"));
return Err(NS_ERROR_DOM_ABORT_ERR);
@@ -161,7 +150,7 @@ Result<nsCString, nsresult> RemoteWorkerManager::GetRemoteType(
("GetRemoteType workerType=%s, principal=%s, "
"preferredRemoteType=%s, selectedRemoteType=%s",
aWorkerKind == WorkerKind::WorkerKindService ? "service" : "shared",
- principalOrigin.get(), preferredRemoteType.get(),
+ principalOrigin.get(), PromiseFlatCString(aCurrentRemoteType).get(),
options.mRemoteType.get()));
}
diff --git a/dom/workers/remoteworkers/RemoteWorkerManager.h b/dom/workers/remoteworkers/RemoteWorkerManager.h
index 1fb5debdcda..d61e120ab15 100644
--- a/dom/workers/remoteworkers/RemoteWorkerManager.h
+++ b/dom/workers/remoteworkers/RemoteWorkerManager.h
@@ -46,7 +46,8 @@ class RemoteWorkerManager final {
* launched.
*/
static Result<nsCString, nsresult> GetRemoteType(
- const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind);
+ const nsCOMPtr<nsIPrincipal>& aPrincipal, WorkerKind aWorkerKind,
+ const nsACString& aCurrentRemoteType);
static bool HasExtensionPrincipal(const RemoteWorkerData& aData);
diff --git a/dom/workers/sharedworkers/SharedWorker.cpp b/dom/workers/sharedworkers/SharedWorker.cpp
index a74d9258b77..5ea65c62e66 100644
--- a/dom/workers/sharedworkers/SharedWorker.cpp
+++ b/dom/workers/sharedworkers/SharedWorker.cpp
@@ -250,13 +250,6 @@ already_AddRefed<SharedWorker> SharedWorker::Constructor(
MOZ_ASSERT(loadInfo.mCookieJarSettings);
net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings)->Serialize(cjsData);
- auto remoteType = RemoteWorkerManager::GetRemoteType(
- loadInfo.mPrincipal, WorkerKind::WorkerKindShared);
- if (NS_WARN_IF(remoteType.isErr())) {
- aRv.Throw(remoteType.unwrapErr());
- return nullptr;
- }
-
Maybe<RFPTargetSet> overriddenFingerprintingSettingsArg;
if (loadInfo.mOverriddenFingerprintingSettings.isSome()) {
overriddenFingerprintingSettingsArg.emplace(
@@ -273,7 +266,7 @@ already_AddRefed<SharedWorker> SharedWorker::Constructor(
loadInfo.mIsOn3PCBExceptionList,
OriginTrials::FromWindow(nsGlobalWindowInner::Cast(window)),
void_t() /* OptionalServiceWorkerData */, agentClusterId,
- remoteType.unwrap());
+ DEFAULT_REMOTE_TYPE /* ignored */);
PSharedWorkerChild* pActor = actorChild->SendPSharedWorkerConstructor(
remoteWorkerData, loadInfo.mWindowID, portIdentifier.release());
diff --git a/dom/workers/sharedworkers/SharedWorkerService.cpp b/dom/workers/sharedworkers/SharedWorkerService.cpp
index 4d536ea9cf2..6e2a874df4e 100644
--- a/dom/workers/sharedworkers/SharedWorkerService.cpp
+++ b/dom/workers/sharedworkers/SharedWorkerService.cpp
@@ -8,6 +8,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/StaticMutex.h"
+#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/RemoteWorkerManager.h" // RemoteWorkerManager::GetRemoteType
#include "mozilla/dom/RemoteWorkerTypes.h"
@@ -31,14 +32,15 @@ StaticRefPtr<SharedWorkerService> sSharedWorkerService;
class GetOrCreateWorkerManagerRunnable final : public Runnable {
public:
- GetOrCreateWorkerManagerRunnable(SharedWorkerService* aService,
- SharedWorkerParent* aActor,
- const RemoteWorkerData& aData,
- uint64_t aWindowID,
- const MessagePortIdentifier& aPortIdentifier)
+ GetOrCreateWorkerManagerRunnable(
+ SharedWorkerService* aService,
+ ThreadsafeContentParentHandle* aContentParentHandle,
+ SharedWorkerParent* aActor, const RemoteWorkerData& aData,
+ uint64_t aWindowID, const MessagePortIdentifier& aPortIdentifier)
: Runnable("GetOrCreateWorkerManagerRunnable"),
mBackgroundEventTarget(GetCurrentSerialEventTarget()),
mService(aService),
+ mContentParentHandle(aContentParentHandle),
mActor(aActor),
mData(aData),
mWindowID(aWindowID),
@@ -47,7 +49,8 @@ class GetOrCreateWorkerManagerRunnable final : public Runnable {
NS_IMETHOD
Run() {
mService->GetOrCreateWorkerManagerOnMainThread(
- mBackgroundEventTarget, mActor, mData, mWindowID, mPortIdentifier);
+ mBackgroundEventTarget, mContentParentHandle, mActor, mData, mWindowID,
+ mPortIdentifier);
return NS_OK;
}
@@ -55,6 +58,7 @@ class GetOrCreateWorkerManagerRunnable final : public Runnable {
private:
nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
RefPtr<SharedWorkerService> mService;
+ RefPtr<ThreadsafeContentParentHandle> mContentParentHandle;
RefPtr<SharedWorkerParent> mActor;
RemoteWorkerData mData;
uint64_t mWindowID;
@@ -154,19 +158,23 @@ void SharedWorkerService::GetOrCreateWorkerManager(
uint64_t aWindowID, const MessagePortIdentifier& aPortIdentifier) {
AssertIsOnBackgroundThread();
+ RefPtr<ThreadsafeContentParentHandle> contentParentHandle =
+ BackgroundParent::GetContentParentHandle(aActor->Manager());
+
// The real check happens on main-thread.
RefPtr<GetOrCreateWorkerManagerRunnable> r =
- new GetOrCreateWorkerManagerRunnable(this, aActor, aData, aWindowID,
- aPortIdentifier);
+ new GetOrCreateWorkerManagerRunnable(this, contentParentHandle, aActor,
+ aData, aWindowID, aPortIdentifier);
nsresult rv = SchedulerGroup::Dispatch(r.forget());
(void)NS_WARN_IF(NS_FAILED(rv));
}
void SharedWorkerService::GetOrCreateWorkerManagerOnMainThread(
- nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
- const RemoteWorkerData& aData, uint64_t aWindowID,
- UniqueMessagePortId& aPortIdentifier) {
+ nsIEventTarget* aBackgroundEventTarget,
+ ThreadsafeContentParentHandle* aContentParentHandle,
+ SharedWorkerParent* aActor, const RemoteWorkerData& aData,
+ uint64_t aWindowID, UniqueMessagePortId& aPortIdentifier) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aBackgroundEventTarget);
MOZ_ASSERT(aActor);
@@ -180,17 +188,19 @@ void SharedWorkerService::GetOrCreateWorkerManagerOnMainThread(
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
+
+ nsCString currentRemoteType = aContentParentHandle
+ ? aContentParentHandle->GetRemoteType()
+ : NOT_REMOTE_TYPE;
auto remoteType = RemoteWorkerManager::GetRemoteType(
- principal, WorkerKind::WorkerKindShared);
+ principal, WorkerKind::WorkerKindShared, currentRemoteType);
if (NS_WARN_IF(remoteType.isErr())) {
ErrorPropagationOnMainThread(aBackgroundEventTarget, aActor,
remoteType.unwrapErr());
return;
}
- if (!remoteType.unwrap().Equals(copyData.remoteType())) {
- copyData.remoteType() = remoteType.unwrap();
- }
+ copyData.remoteType() = remoteType.unwrap();
auto partitionedPrincipalOrErr =
PrincipalInfoToPrincipal(copyData.partitionedPrincipalInfo());
diff --git a/dom/workers/sharedworkers/SharedWorkerService.h b/dom/workers/sharedworkers/SharedWorkerService.h
index 3f76c25badf..6b6d18826db 100644
--- a/dom/workers/sharedworkers/SharedWorkerService.h
+++ b/dom/workers/sharedworkers/SharedWorkerService.h
@@ -23,6 +23,7 @@ class MessagePortIdentifier;
class RemoteWorkerData;
class SharedWorkerManager;
class SharedWorkerParent;
+class ThreadsafeContentParentHandle;
class UniqueMessagePortId;
/**
@@ -48,9 +49,10 @@ class SharedWorkerService final {
const MessagePortIdentifier& aPortIdentifier);
void GetOrCreateWorkerManagerOnMainThread(
- nsIEventTarget* aBackgroundEventTarget, SharedWorkerParent* aActor,
- const RemoteWorkerData& aData, uint64_t aWindowID,
- UniqueMessagePortId& aPortIdentifier);
+ nsIEventTarget* aBackgroundEventTarget,
+ ThreadsafeContentParentHandle* aContentParentHandle,
+ SharedWorkerParent* aActor, const RemoteWorkerData& aData,
+ uint64_t aWindowID, UniqueMessagePortId& aPortIdentifier);
void RemoveWorkerManagerOnMainThread(SharedWorkerManager* aManager);