Medium CVSS 7.4 firefox Sandbox Escape 🔧 Commit mapped

Overview

Medium
Severity
7.4
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionLeaking of file descriptors from the fork server to web content processes could allow for privilege escalation attacks.
ComponentSecurity/NSS
Bug ClassSandbox Escape
Tracker1949987
Fix commit77a968c0cd35 (firefox) +14/-3
CWECWE-403
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedThinker Li
Disclosed2025-04-01

Background

`MiniTransceiver`
The fork server’s IPC channel object; it communicates over a single IPC file descriptor.
`UniqueFileHandle` / `ipcHandle`
RAII owner of a Unix FD. release() relinquishes ownership (returns the raw fd and stops tracking it); extract() moves ownership out.

Root Cause Analysis

Extra file descriptors were leaking into the fork server and therefore into the content processes it forks, and the fork server’s own IPC FD was not reliably owned for the lifetime of its transceiver. In ForkServer::ForkServer, the channel was built with MakeUnique<MiniTransceiver>(ipcHandle->release(), ...): release() hands the raw fd to the transceiver but drops RAII ownership, so the FD’s lifetime is untracked (and nothing guarantees it is held only as long as the transceiver, nor closed correctly). More importantly, the launcher attached process-wide FDs to the fork server that a fork server has no business holding: the crash reporter notification pipe (CrashReporter::GetChildNotificationPipe() via sCrashReporter) was added whenever crash reporting was enabled, and AttachSandboxReporter added a sandbox-reporter FD. Because the fork server passes its inherited FDs down to every forked content process, those privileged descriptors leaked across the process boundary into sandboxed web-content processes, where they should not exist. The advisory frames this as “leaking of file descriptors from the fork server to web content processes [that] could allow for privilege escalation” (CWE-403, uncontrolled resource/FD leak).

The patch closes each leak. ForkServer now keeps the IPC FD in a new owned member UniqueFileHandle mIpcFd = ipcHandle.extract() and constructs the transceiver from mIpcFd.get(), so the descriptor is held exactly as long as the transceiver and managed via RAII. BaseProcessLauncher::DoSetup no longer adds the crash-reporter pipe when mProcessType == GeckoProcessType_ForkServer. PosixProcessLauncher::DoSetup asserts that a fork-server launch carries exactly one FD argument (mChildArgs.mFiles.size() == 1), i.e. only its IPC channel. SandboxLaunch::Configure skips AttachSandboxReporter for the fork server. Together these ensure the fork server inherits only its single IPC FD, so no privileged descriptor is propagated to forked content processes. The advisory’s “LogicError” label understates it: the diff is a concrete FD-leak / resource-management fix with a (inferred) privilege-escalation consequence.

Key insight
The fork server inherited descriptors it did not need (crash-reporter pipe, sandbox reporter) and loosely owned its IPC FD; since every fork inherits the server’s FDs, those privileged descriptors leaked into sandboxed content processes — the fix restricts the fork server to a single, RAII-owned IPC FD.

Attack Path

  1. Compromised renderer An attacker gains code execution in a sandboxed content process (precondition; the FD leak is a post-compromise escalation aid, inferred).
  2. Inherited FD That content process was forked by the fork server and inherited the leaked privileged descriptor(s) — e.g. the crash-reporter notification pipe or sandbox-reporter FD.
  3. FD abuse The attacker operates on the inherited descriptor from inside the sandbox to reach functionality or a process the sandbox was meant to deny (inferred; the diff proves the leak, not the specific abuse).
  4. Privilege escalation Using the unexpected channel/resource, the attacker influences a more-privileged component, weakening or escaping the content sandbox.
  5. Primitive A sandbox-escape / privilege-escalation stepping stone granted by descriptors that should never have crossed into content processes.

Impact Assessment

This is a Linux sandboxing/IPC defect: leaked file descriptors flow from the fork server into the web-content (renderer) processes it spawns, which run under the content sandbox. On its own the bug is an uncontrolled resource leak; its security value is as an escalation primitive — a sandboxed, already-compromised content process gains access to descriptors (crash-reporter / sandbox-reporter) it should not possess. The actual privilege gained depends on what those FDs permit and is (inferred); the diff substantiates the leak and its elimination, not a complete escape. Gating: Linux builds with MOZ_ENABLE_FORKSERVER and crash reporting/sandboxing enabled. CVSS C:H/I:H/A:N, AC:H reflects the post-compromise, condition-dependent nature.

Files Changed

  • ipc/glue/ForkServer.cpp
  • ipc/glue/ForkServer.h
  • ipc/glue/GeckoChildProcessHost.cpp
  • security/sandbox/linux/launch/SandboxLaunch.cpp

Audit Directions

  • Other FDs reaching the fork server
    Audit all mChildArgs.mFiles / fds_to_remap contributors (logging, profiler, GPU, IPC relays) for descriptors added unconditionally that would be inherited by forked children; the new single-FD assertion is the invariant to uphold.
  • `release()` vs `extract()` ownership
    Grep for UniqueFileHandle/UniquePtr ->release() feeding long-lived objects; untracked raw FDs/pointers are both leak and double-close hazards.
  • Fork inheritance hygiene
    Review what the fork server leaves open across fork() (CLOEXEC flags, explicit closes) to ensure no additional inherited resources (sockets, shared memory handles) cross into sandboxed children.
diff --git a/ipc/glue/ForkServer.cpp b/ipc/glue/ForkServer.cpp
index 7b846b5d807..ba368de1575 100644
--- a/ipc/glue/ForkServer.cpp
+++ b/ipc/glue/ForkServer.cpp
@@ -47,7 +47,9 @@ ForkServer::ForkServer(int* aArgc, char*** aArgv) : mArgc(aArgc), mArgv(aArgv) {
     MOZ_CRASH("forkserver missing ipcHandle argument");
   }
 
-  mTcver = MakeUnique<MiniTransceiver>(ipcHandle->release(),
+  // Hold our IPC FD while our MiniTransceiver is alive.
+  mIpcFd = ipcHandle.extract();
+  mTcver = MakeUnique<MiniTransceiver>(mIpcFd.get(),
                                        DataBufferClear::AfterReceiving);
 }
 
diff --git a/ipc/glue/ForkServer.h b/ipc/glue/ForkServer.h
index eb04a2089d5..51fb473e922 100644
--- a/ipc/glue/ForkServer.h
+++ b/ipc/glue/ForkServer.h
@@ -27,6 +27,7 @@ class ForkServer {
   static bool RunForkServer(int* aArgc, char*** aArgv);
 
  private:
+  UniqueFileHandle mIpcFd;
   UniquePtr<MiniTransceiver> mTcver;
 
   int* mArgc;
diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp
index b0c74483ac4..755f90a4594 100644
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -1120,7 +1120,8 @@ Result<Ok, LaunchError> BaseProcessLauncher::DoSetup() {
   geckoargs::sParentPid.Put(static_cast<uint64_t>(base::GetCurrentProcId()),
                             mChildArgs);
 
-  if (!CrashReporter::IsDummy() && CrashReporter::GetEnabled()) {
+  if (!CrashReporter::IsDummy() && CrashReporter::GetEnabled() &&
+      mProcessType != GeckoProcessType_ForkServer) {
 #if defined(MOZ_WIDGET_COCOA) || defined(XP_WIN)
     geckoargs::sCrashReporter.Put(CrashReporter::GetChildNotificationPipe(),
                                   mChildArgs);
@@ -1310,6 +1311,12 @@ Result<Ok, LaunchError> PosixProcessLauncher::DoSetup() {
 
   mChildArgs.mArgs.push_back(ChildProcessType());
 
+#  ifdef MOZ_ENABLE_FORKSERVER
+  MOZ_ASSERT(mProcessType != GeckoProcessType_ForkServer ||
+                 mChildArgs.mFiles.size() == 1,
+             "The ForkServer only expects a single FD argument");
+#  endif
+
 #  if !defined(MOZ_WIDGET_ANDROID)
   // Add any files which need to be transferred to fds_to_remap.
   // NOTE: This doesn't transfer ownership of the files out of `mChildArgs`.
diff --git a/security/sandbox/linux/launch/SandboxLaunch.cpp b/security/sandbox/linux/launch/SandboxLaunch.cpp
index 0c916941158..0c2f62b5d59 100644
--- a/security/sandbox/linux/launch/SandboxLaunch.cpp
+++ b/security/sandbox/linux/launch/SandboxLaunch.cpp
@@ -306,7 +306,8 @@ bool SandboxLaunch::Configure(GeckoProcessType aType, SandboxingKind aKind,
   // At this point, we know we'll be using sandboxing; generic
   // sandboxing support goes here.
   PreloadSandboxLib(&aOptions->env_map);
-  if (!AttachSandboxReporter(aExtraOpts)) {
+  if (aType != GeckoProcessType_ForkServer &&
+      !AttachSandboxReporter(aExtraOpts)) {
     return false;
   }
 
Loading diff…