High firefox Sandbox Escape 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionSandbox escape in the Security: Process Sandboxing component
ComponentSecurity/NSS
Bug ClassSandbox Escape
Tracker2040515
Fix commit7538e5fa889a (firefox) +19/-85
CISA KEVNot listed
CreditedYaqoub Aldurayhim
Disclosed2026-06-16

Changed Functions

FunctionChangeNotes
if
security/sandbox/linux/SandboxFilter.cpp
modified
if
security/sandbox/linux/broker/SandboxBroker.cpp
modified

Files Changed

  • security/sandbox/common/test/SandboxTestingChildTests.h
  • security/sandbox/linux/SandboxBrokerClient.cpp
  • security/sandbox/linux/SandboxBrokerClient.h
  • security/sandbox/linux/SandboxFilter.cpp
  • security/sandbox/linux/broker/SandboxBroker.cpp
  • security/sandbox/linux/broker/SandboxBrokerCommon.cpp
  • security/sandbox/linux/broker/SandboxBrokerCommon.h
  • security/sandbox/linux/gtest/TestBroker.cpp
  • security/sandbox/test/browser_content_sandbox_fs_tests.js
diff --git a/security/sandbox/common/test/SandboxTestingChildTests.h b/security/sandbox/common/test/SandboxTestingChildTests.h
index a42eefea587..9dae8d40761 100644
--- a/security/sandbox/common/test/SandboxTestingChildTests.h
+++ b/security/sandbox/common/test/SandboxTestingChildTests.h
@@ -627,6 +627,12 @@ void RunTestsContent(SandboxTestingChild* child) {
     return fd;
   });
 
+  child->ErrnoValueTest("symlink"_ns, EPERM,
+                        [] { return symlink("something", "/tmp/testlink"); });
+  child->ErrnoValueTest("symlinkat"_ns, EPERM, [] {
+    return symlinkat("something", AT_FDCWD, "/tmp/testlink");
+  });
+
 #  endif  // XP_LINUX
 
 #  ifdef XP_MACOSX
diff --git a/security/sandbox/linux/SandboxBrokerClient.cpp b/security/sandbox/linux/SandboxBrokerClient.cpp
index d8091602974..44672eff5b8 100644
--- a/security/sandbox/linux/SandboxBrokerClient.cpp
+++ b/security/sandbox/linux/SandboxBrokerClient.cpp
@@ -205,11 +205,6 @@ int SandboxBrokerClient::Link(const char* aOldPath, const char* aNewPath) {
   return DoCall(&req, aOldPath, aNewPath, nullptr, false);
 }
 
-int SandboxBrokerClient::Symlink(const char* aOldPath, const char* aNewPath) {
-  Request req = MakeRequest(SANDBOX_FILE_SYMLINK, 0, 0);
-  return DoCall(&req, aOldPath, aNewPath, nullptr, false);
-}
-
 int SandboxBrokerClient::Rename(const char* aOldPath, const char* aNewPath) {
   Request req = MakeRequest(SANDBOX_FILE_RENAME, 0, 0);
   return DoCall(&req, aOldPath, aNewPath, nullptr, false);
diff --git a/security/sandbox/linux/SandboxBrokerClient.h b/security/sandbox/linux/SandboxBrokerClient.h
index cc5e96437dd..2f5c0952618 100644
--- a/security/sandbox/linux/SandboxBrokerClient.h
+++ b/security/sandbox/linux/SandboxBrokerClient.h
@@ -34,7 +34,6 @@ class SandboxBrokerClient final : private SandboxBrokerCommon {
   int Chmod(const char* aPath, int aMode);
   int Link(const char* aPath, const char* aPath2);
   int Mkdir(const char* aPath, int aMode);
-  int Symlink(const char* aOldPath, const char* aNewPath);
   int Rename(const char* aOldPath, const char* aNewPath);
   int Unlink(const char* aPath);
   int Rmdir(const char* aPath);
diff --git a/security/sandbox/linux/SandboxFilter.cpp b/security/sandbox/linux/SandboxFilter.cpp
index 04f7cf4507e..004d2314786 100644
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -299,13 +299,6 @@ class SandboxPolicyCommon : public SandboxPolicyBase {
     return broker->Link(path, path2);
   }
 
-  static intptr_t SymlinkTrap(ArgsRef aArgs, void* aux) {
-    auto broker = static_cast<SandboxBrokerClient*>(aux);
-    auto path = reinterpret_cast<const char*>(aArgs.args[0]);
-    auto path2 = reinterpret_cast<const char*>(aArgs.args[1]);
-    return broker->Symlink(path, path2);
-  }
-
   static intptr_t RenameTrap(ArgsRef aArgs, void* aux) {
     auto broker = static_cast<SandboxBrokerClient*>(aux);
     auto path = reinterpret_cast<const char*>(aArgs.args[0]);
@@ -472,19 +465,6 @@ class SandboxPolicyCommon : public SandboxPolicyBase {
     return broker->Link(path, path2);
   }
 
-  static intptr_t SymlinkAtTrap(ArgsRef aArgs, void* aux) {
-    auto broker = static_cast<SandboxBrokerClient*>(aux);
-    auto path = reinterpret_cast<const char*>(aArgs.args[0]);
-    auto fd2 = static_cast<int>(aArgs.args[1]);
-    auto path2 = reinterpret_cast<const char*>(aArgs.args[2]);
-    if (fd2 != AT_FDCWD && path2[0] != '/') {
-      SANDBOX_LOG("unsupported fd-relative symlinkat(\"%s\", %d, \"%s\")", path,
-                  fd2, path2);
-      return BlockedSyscallTrap(aArgs, nullptr);
-    }
-    return broker->Symlink(path, path2);
-  }
-
   static intptr_t RenameAtTrap(ArgsRef aArgs, void* aux) {
     auto broker = static_cast<SandboxBrokerClient*>(aux);
     auto fd = static_cast<int>(aArgs.args[0]);
@@ -966,7 +946,7 @@ class SandboxPolicyCommon : public SandboxPolicyBase {
         case __NR_mkdir:
           return Trap(MkdirTrap, mBroker);
         case __NR_symlink:
-          return Trap(SymlinkTrap, mBroker);
+          return Error(EPERM);
         case __NR_rename:
           return Trap(RenameTrap, mBroker);
         case __NR_rmdir:
@@ -995,7 +975,7 @@ class SandboxPolicyCommon : public SandboxPolicyBase {
         case __NR_mkdirat:
           return Trap(MkdirAtTrap, mBroker);
         case __NR_symlinkat:
-          return Trap(SymlinkAtTrap, mBroker);
+          return Error(EPERM);
         case __NR_renameat:
           return Trap(RenameAtTrap, mBroker);
         case __NR_unlinkat:
diff --git a/security/sandbox/linux/broker/SandboxBroker.cpp b/security/sandbox/linux/broker/SandboxBroker.cpp
index 261ee725152..886a815b507 100644
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -529,17 +529,6 @@ static int DoStat(const char* aPath, statstruct* aBuff, int aFlags) {
   return statsyscall(aPath, aBuff);
 }
 
-static int DoLink(const char* aPath, const char* aPath2,
-                  SandboxBrokerCommon::Operation aOper) {
-  if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) {
-    return link(aPath, aPath2);
-  }
-  if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) {
-    return symlink(aPath, aPath2);
-  }
-  MOZ_CRASH("SandboxBroker: Unknown link operation");
-}
-
 static int DoConnect(const char* aPath, size_t aLen, int aType,
                      bool aIsAbstract) {
   // Deny SOCK_DGRAM for the same reason it's denied for socketpair.
@@ -910,9 +899,8 @@ void SandboxBroker::ThreadMain(void) {
           break;
 
         case SANDBOX_FILE_LINK:
-        case SANDBOX_FILE_SYMLINK:
           if (permissive || AllowOperation(W_OK | X_OK, perms)) {
-            if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) {
+            if (link(pathBuf, pathBuf2) == 0) {
               resp.mError = 0;
             } else {
               resp.mError = -errno;
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
index 4762be3e19a..396a1c53284 100644
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.cpp
@@ -46,19 +46,8 @@ unsigned SandboxBrokerCommon::OperationToInt(Operation aOp) {
 // static
 const char* SandboxBrokerCommon::OperationDescription(Operation aOp) {
   static constexpr const char* kNames[] = {
-      "open",
-      "access",
-      "stat",
-      "chmod",
-      "link",
-      "symlink",
-      "mkdir",
-      "rename",
-      "rmdir",
-      "unlink",
-      "readlink",
-      "connect",
-      "connect-abstract",
+      "open",   "access", "stat",   "chmod",    "link",    "mkdir",
+      "rename", "rmdir",  "unlink", "readlink", "connect", "connect-abstract",
   };
 
   static_assert(
diff --git a/security/sandbox/linux/broker/SandboxBrokerCommon.h b/security/sandbox/linux/broker/SandboxBrokerCommon.h
index 599431f0ce4..1bdae7a3c38 100644
--- a/security/sandbox/linux/broker/SandboxBrokerCommon.h
+++ b/security/sandbox/linux/broker/SandboxBrokerCommon.h
@@ -32,7 +32,6 @@ class SandboxBrokerCommon {
     SANDBOX_FILE_STAT,
     SANDBOX_FILE_CHMOD,
     SANDBOX_FILE_LINK,
-    SANDBOX_FILE_SYMLINK,
     SANDBOX_FILE_MKDIR,
     SANDBOX_FILE_RENAME,
     SANDBOX_FILE_RMDIR,
@@ -44,11 +43,11 @@ class SandboxBrokerCommon {
   };
   MOZ_USING_ENUM_STATIC(Operation, SANDBOX_FILE_OPEN, SANDBOX_FILE_ACCESS,
                         SANDBOX_FILE_STAT, SANDBOX_FILE_CHMOD,
-                        SANDBOX_FILE_LINK, SANDBOX_FILE_SYMLINK,
-                        SANDBOX_FILE_MKDIR, SANDBOX_FILE_RENAME,
-                        SANDBOX_FILE_RMDIR, SANDBOX_FILE_UNLINK,
-                        SANDBOX_FILE_READLINK, SANDBOX_SOCKET_CONNECT,
-                        SANDBOX_SOCKET_CONNECT_ABSTRACT, SANDBOX_OP_MAX_VALUE);
+                        SANDBOX_FILE_LINK, SANDBOX_FILE_MKDIR,
+                        SANDBOX_FILE_RENAME, SANDBOX_FILE_RMDIR,
+                        SANDBOX_FILE_UNLINK, SANDBOX_FILE_READLINK,
+                        SANDBOX_SOCKET_CONNECT, SANDBOX_SOCKET_CONNECT_ABSTRACT,
+                        SANDBOX_OP_MAX_VALUE);
 
   static bool OperationIsValid(Operation aOp) {
     return static_cast<unsigned>(aOp) <=
diff --git a/security/sandbox/linux/gtest/TestBroker.cpp b/security/sandbox/linux/gtest/TestBroker.cpp
index 1b7b84178d2..ea5f22e14da 100644
--- a/security/sandbox/linux/gtest/TestBroker.cpp
+++ b/security/sandbox/linux/gtest/TestBroker.cpp
@@ -68,9 +68,6 @@ class SandboxBrokerTest : public ::testing::Test {
   int Mkdir(const char* aPath, int aMode) {
     return mClient->Mkdir(aPath, aMode);
   }
-  int Symlink(const char* aPath, const char* bPath) {
-    return mClient->Symlink(aPath, bPath);
-  }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/security/sandbox/common/test/SandboxTestingChildTests.h b/security/sandbox/common/test/SandboxTestingChildTests.h
index a42eefea587..9dae8d40761 100644
--- a/security/sandbox/common/test/SandboxTestingChildTests.h
+++ b/security/sandbox/common/test/SandboxTestingChildTests.h
@@ -627,6 +627,12 @@ void RunTestsContent(SandboxTestingChild* child) {
     return fd;
   });
 
+  child->ErrnoValueTest("symlink"_ns, EPERM,
+                        [] { return symlink("something", "/tmp/testlink"); });
+  child->ErrnoValueTest("symlinkat"_ns, EPERM, [] {
+    return symlinkat("something", AT_FDCWD, "/tmp/testlink");
+  });
+
 #  endif  // XP_LINUX
 
 #  ifdef XP_MACOSX
diff --git a/security/sandbox/test/browser_content_sandbox_fs_tests.js b/security/sandbox/test/browser_content_sandbox_fs_tests.js
index 9e2232f40ce..cc56dc62ca6 100644
--- a/security/sandbox/test/browser_content_sandbox_fs_tests.js
+++ b/security/sandbox/test/browser_content_sandbox_fs_tests.js
@@ -75,10 +75,9 @@ async function createTempFile() {
       createSymlink
     );
     ok(!symlinkCreated.ok, "created a symlink in temp failed");
-    const expectedError = isLinux() ? lazy.LIBC.EACCES : lazy.LIBC.EPERM;
     is(
       symlinkCreated.code,
-      expectedError,
+      lazy.LIBC.EPERM,
       "created a symlink in temp failed with access denied"
     );
   }
Loading diff…