Bug 1431441 - Part 3 - Start the Mac content sandbox earlier r=Alex_Gaynor
authorHaik Aftandilian <haftandilian@mozilla.com>
Fri, 19 Oct 2018 18:23:16 +0000
changeset 490776 03cb6cfc053da196a21da013bb51c135ec3a9ff8
parent 490775 9bb3261dd6ac56ac770bb5bf9eb2897e9456c8c3
child 490777 abbae9f258346c8964babc3ee9e1ec061b749af1
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersAlex_Gaynor
bugs1431441
milestone64.0a1
Bug 1431441 - Part 3 - Start the Mac content sandbox earlier r=Alex_Gaynor Pass sandbox parameters to content processes on the command line allowing for early sandbox startup. Pref'd off behind "security.sandbox.content.mac.earlyinit" until it's ready to be enabled by default. Once early startup is enabled by default and considered stable, the original sandbox startup code can be removed. Depends on D6719 Differential Revision: https://phabricator.services.mozilla.com/D6720
browser/app/moz.build
browser/app/nsBrowserApp.cpp
browser/app/profile/firefox.js
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
ipc/app/MozillaRuntimeMain.cpp
ipc/glue/GeckoChildProcessHost.cpp
security/sandbox/mac/Sandbox.h
security/sandbox/mac/Sandbox.mm
security/sandbox/mac/SandboxPolicies.h
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -79,16 +79,21 @@ if CONFIG['OS_ARCH'] == 'WINNT':
     LOCAL_INCLUDES += [
         '/browser/app/winlauncher',
     ]
     DELAYLOAD_DLLS += [
         'oleaut32.dll',
         'ole32.dll',
     ]
 
+if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'Darwin':
+    USE_LIBS += [
+        'mozsandbox',
+    ]
+
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_ARCH'] == 'WINNT':
     # For sandbox includes and the include dependencies those have
     LOCAL_INCLUDES += [
         '/security/sandbox/chromium',
         '/security/sandbox/chromium-shim',
     ]
 
     USE_LIBS += [
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -38,16 +38,20 @@
 #include "mozilla/Sprintf.h"
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/WindowsDllBlocklist.h"
 
 #ifdef LIBFUZZER
 #include "FuzzerDefs.h"
 #endif
 
+#ifdef XP_MACOSX
+#include "mozilla/Sandbox.h"
+#endif
+
 #ifdef MOZ_LINUX_32_SSE2_STARTUP_ERROR
 #include <cpuid.h>
 #include "mozilla/Unused.h"
 
 static bool
 IsSSE2Available()
 {
   // The rest of the app has been compiled to assume that SSE2 is present
@@ -258,16 +262,26 @@ InitXPCOMGlue()
 // NB: This must be extern, as this value is checked elsewhere
 uint32_t gBlocklistInitFlags = eDllBlocklistInitFlagDefault;
 #endif
 
 int main(int argc, char* argv[], char* envp[])
 {
   mozilla::TimeStamp start = mozilla::TimeStamp::Now();
 
+#ifdef XP_MACOSX
+  if (argc > 1 && IsArg(argv[1], "contentproc")) {
+    std::string err;
+    if (!mozilla::EarlyStartMacSandboxIfEnabled(argc, argv, err)) {
+      Output("Sandbox error: %s\n", err.c_str());
+      MOZ_CRASH("Sandbox initialization failed");
+    }
+  }
+#endif
+
 #ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
   // We are launching as a content process, delegate to the appropriate
   // main
   if (argc > 1 && IsArg(argv[1], "contentproc")) {
 #ifdef HAS_DLL_BLOCKLIST
     DllBlocklist_Initialize(eDllBlocklistInitFlagIsChildProcess);
 #endif
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1018,16 +1018,22 @@ pref("security.sandbox.gpu.level", 0);
 
 // Controls whether we disable win32k for the GMP processes.
 // true means that win32k system calls are not permitted.
 // Note: win32k is currently _not_ disabled due to intermittent test failures,
 // where the GMP process fails very early. See bug 1449348.
 pref("security.sandbox.gmp.win32k-disable", false);
 #endif
 
+#if defined(NIGHTLY_BUILD) && defined(XP_MACOSX) && defined(MOZ_SANDBOX)
+// Start the Mac sandbox immediately during child process startup instead
+// of when messaged by the parent after the message loop is running.
+pref("security.sandbox.content.mac.earlyinit", false);
+#endif
+
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox" (nightly only)
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
 // 2 -> "preliminary content sandboxing enabled with profile protection:
 //       write access to home directory is prevented, read and write access
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -105,25 +105,27 @@
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/Unused.h"
 #include "mozilla/HangDetails.h"
 #include "nsAnonymousTemporaryFile.h"
+#include "nsAppDirectoryServiceDefs.h"
 #include "nsAppRunner.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsCExternalHandlerService.h"
 #include "nsCOMPtr.h"
 #include "nsChromeRegistryChrome.h"
 #include "nsConsoleMessage.h"
 #include "nsConsoleService.h"
 #include "nsContentUtils.h"
 #include "nsDebugImpl.h"
+#include "nsDirectoryService.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsEmbedCID.h"
 #include "nsFrameLoader.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
 #include "nsIClipboard.h"
 #include "nsICookie.h"
@@ -209,16 +211,20 @@
 #include "nsLayoutStylesheetCache.h"
 
 #include "mozilla/Sprintf.h"
 
 #ifdef MOZ_WEBRTC
 #include "signaling/src/peerconnection/WebrtcGlobalParent.h"
 #endif
 
+#if defined(XP_MACOSX)
+#include "nsMacUtilsImpl.h"
+#endif
+
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
 
 #if defined(XP_LINUX)
 #include "mozilla/Hal.h"
 #endif
 
@@ -604,16 +610,20 @@ static const char* sObserverTopics[] = {
   "cacheservice:empty-cache",
   "intl:app-locales-changed",
   "intl:requested-locales-changed",
   "cookie-changed",
   "private-cookie-changed",
   "clear-site-data-reload-needed",
 };
 
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+bool ContentParent::sEarlySandboxInit = false;
+#endif
+
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
     new ContentParent(/* aOpener = */ nullptr,
                       NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
@@ -2112,16 +2122,125 @@ ContentParent::DestroyTestShell(TestShel
 
 TestShellParent*
 ContentParent::GetTestShellSingleton()
 {
   PTestShellParent* p = LoneManagedOrNullAsserts(ManagedPTestShellParent());
   return static_cast<TestShellParent*>(p);
 }
 
+#ifdef XP_MACOSX
+void
+ContentParent::AppendSandboxParams(std::vector<std::string> &aArgs)
+{
+  nsCOMPtr<nsIProperties>
+    directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+  if (!directoryService) {
+    MOZ_CRASH("Failed to get the directory service");
+  }
+
+  // Indicates the child should startup the sandbox
+  aArgs.push_back("-sbStartup");
+
+  // The content sandbox level
+  int contentSandboxLevel =
+    Preferences::GetInt("security.sandbox.content.level");
+  std::ostringstream os;
+  os << contentSandboxLevel;
+  std::string contentSandboxLevelString = os.str();
+  aArgs.push_back("-sbLevel");
+  aArgs.push_back(contentSandboxLevelString);
+
+  // Sandbox logging
+  if (Preferences::GetBool("security.sandbox.logging.enabled") ||
+      PR_GetEnv("MOZ_SANDBOX_LOGGING")) {
+    aArgs.push_back("-sbLogging");
+  }
+
+  // For file content processes
+  if (GetRemoteType().EqualsLiteral(FILE_REMOTE_TYPE)) {
+    aArgs.push_back("-sbAllowFileAccess");
+  }
+
+  // Audio access
+  if (!Preferences::GetBool("media.cubeb.sandbox")) {
+    aArgs.push_back("-sbAllowAudio");
+  }
+
+  // .app path (normalized)
+  nsAutoCString appPath;
+  if (!nsMacUtilsImpl::GetAppPath(appPath)) {
+    MOZ_CRASH("Failed to get app dir paths");
+  }
+  aArgs.push_back("-sbAppPath");
+  aArgs.push_back(appPath.get());
+
+  // TESTING_READ_PATH1
+  nsAutoCString testingReadPath1;
+  Preferences::GetCString("security.sandbox.content.mac.testing_read_path1",
+                          testingReadPath1);
+  if (!testingReadPath1.IsEmpty()) {
+    aArgs.push_back("-sbTestingReadPath");
+    aArgs.push_back(testingReadPath1.get());
+  }
+
+  // TESTING_READ_PATH2
+  nsAutoCString testingReadPath2;
+  Preferences::GetCString("security.sandbox.content.mac.testing_read_path2",
+                          testingReadPath2);
+  if (!testingReadPath2.IsEmpty()) {
+    aArgs.push_back("-sbTestingReadPath");
+    aArgs.push_back(testingReadPath2.get());
+  }
+
+  // TESTING_READ_PATH3, TESTING_READ_PATH4. In development builds,
+  // these are used to whitelist the repo dir and object dir respectively.
+  nsresult rv;
+  if (mozilla::IsDevelopmentBuild()) {
+    // Repo dir
+    nsCOMPtr<nsIFile> repoDir;
+    rv = mozilla::GetRepoDir(getter_AddRefs(repoDir));
+    if (NS_FAILED(rv)) {
+      MOZ_CRASH("Failed to get path to repo dir");
+    }
+    nsCString repoDirPath;
+    Unused << repoDir->GetNativePath(repoDirPath);
+    aArgs.push_back("-sbTestingReadPath");
+    aArgs.push_back(repoDirPath.get());
+
+    // Object dir
+    nsCOMPtr<nsIFile> objDir;
+    rv = mozilla::GetObjDir(getter_AddRefs(objDir));
+    if (NS_FAILED(rv)) {
+      MOZ_CRASH("Failed to get path to build object dir");
+    }
+    nsCString objDirPath;
+    Unused << objDir->GetNativePath(objDirPath);
+    aArgs.push_back("-sbTestingReadPath");
+    aArgs.push_back(objDirPath.get());
+  }
+
+  // DEBUG_WRITE_DIR
+#ifdef DEBUG
+  // When a content process dies intentionally (|NoteIntentionalCrash|), for
+  // tests it wants to log that it did this. Allow writing to this location
+  // that the testrunner wants.
+  char *bloatLog = PR_GetEnv("XPCOM_MEM_BLOAT_LOG");
+  if (bloatLog != nullptr) {
+    // |bloatLog| points to a specific file, but we actually write to a sibling
+    // of that path.
+    nsAutoCString bloatDirectoryPath =
+      nsMacUtilsImpl::GetDirectoryPath(bloatLog);
+    aArgs.push_back("-sbDebugWriteDir");
+    aArgs.push_back(bloatDirectoryPath.get());
+  }
+#endif // DEBUG
+}
+#endif // XP_MACOSX
+
 bool
 ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
 {
   AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER);
 
   if (!ContentProcessManager::GetSingleton()) {
     // Shutdown has begun, we shouldn't spawn any more child processes.
     return false;
@@ -2200,16 +2319,22 @@ ContentParent::LaunchSubprocess(ProcessP
   nsPrintfCString schedulerPrefs = Scheduler::GetPrefs();
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+  if (sEarlySandboxInit && IsContentSandboxEnabled()) {
+    AppendSandboxParams(extraArgs);
+  }
+#endif
+
   nsCString parentBuildID(mozilla::PlatformBuildID());
   extraArgs.push_back("-parentBuildID");
   extraArgs.push_back(parentBuildID.get());
 
   // Specify whether the process is recording or replaying an execution.
   if (mRecordReplayState != eNotRecordingOrReplaying) {
     nsPrintfCString buf("%d", mRecordReplayState == eRecording
                               ? (int) recordreplay::ProcessKind::MiddlemanRecording
@@ -2323,16 +2448,27 @@ ContentParent::ContentParent(ContentPare
   // channel. Generally only applies to the situation where we get caught in
   // a deadlock with the plugin process when sending CPOWs.
   GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   bool isFile = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
   mSubprocess = new ContentProcessHost(this, isFile);
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+  // sEarlySandboxInit is statically initialized to false.
+  // Once we've set it to true due to the pref, avoid checking the
+  // pref on subsequent calls. As a result, changing the earlyinit
+  // pref requires restarting the browser to take effect.
+  if (!ContentParent::sEarlySandboxInit) {
+    ContentParent::sEarlySandboxInit =
+      Preferences::GetBool("security.sandbox.content.mac.earlyinit");
+  }
+#endif
 }
 
 ContentParent::~ContentParent()
 {
   if (mForceKillTimer) {
     mForceKillTimer->Cancel();
   }
 
@@ -2593,16 +2729,22 @@ ContentParent::InitInternal(ProcessPrior
   MaybeFileDesc brokerFd = void_t();
   // XXX: Checking the pref here makes it possible to enable/disable sandboxing
   // during an active session. Currently the pref is only used for testing
   // purpose. If the decision is made to permanently rely on the pref, this
   // should be changed so that it is required to restart firefox for the change
   // of value to take effect.
   shouldSandbox = IsContentSandboxEnabled();
 
+#ifdef XP_MACOSX
+  // If the sandbox was initialized during content process
+  // startup, we must not send the SetProcessSandbox message.
+  shouldSandbox = shouldSandbox && !sEarlySandboxInit;
+#endif
+
 #ifdef XP_LINUX
   if (shouldSandbox) {
     MOZ_ASSERT(!mSandboxBroker);
     bool isFileProcess = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
     UniquePtr<SandboxBroker::Policy> policy =
       sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
     if (policy) {
       brokerFd = FileDescriptor();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -1257,16 +1257,18 @@ public:
 
   virtual mozilla::ipc::IPCResult
   RecvStoreUserInteractionAsPermission(const Principal& aPrincipal) override;
 
   // Notify the ContentChild to enable the input event prioritization when
   // initializing.
   void MaybeEnableRemoteInputEventQueue();
 
+  void AppendSandboxParams(std::vector<std::string>& aArgs);
+
 public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
   bool SendRequestMemoryReport(const uint32_t& aGeneration,
                                const bool& aAnonymize,
                                const bool& aMinimizeMemoryUsage,
                                const MaybeFileDesc& aDMDFile) override;
@@ -1384,16 +1386,23 @@ private:
   nsTHashtable<nsCStringHashKey> mActivePermissionKeys;
 
   nsTArray<nsCString> mBlobURLs;
 
   UniquePtr<mozilla::ipc::CrashReporterHost> mCrashReporter;
 
   static uint64_t sNextTabParentId;
   static nsDataHashtable<nsUint64HashKey, TabParent*> sNextTabParents;
+
+#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
+  // When set to true, indicates that content processes should
+  // initialize their sandbox during startup instead of waiting
+  // for the SetProcessSandbox IPDL message.
+  static bool sEarlySandboxInit;
+#endif
 };
 
 } // namespace dom
 } // namespace mozilla
 
 class ParentIdleListener : public nsIObserver
 {
   friend class mozilla::dom::ContentParent;
--- a/ipc/app/MozillaRuntimeMain.cpp
+++ b/ipc/app/MozillaRuntimeMain.cpp
@@ -4,21 +4,33 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "../contentproc/plugin-container.cpp"
 
 #include "mozilla/Bootstrap.h"
 #include "mozilla/WindowsDllBlocklist.h"
 
+#ifdef XP_MACOSX
+#include "mozilla/Sandbox.h"
+#endif
+
 using namespace mozilla;
 
 int
 main(int argc, char *argv[])
 {
+#ifdef XP_MACOSX
+  std::string err;
+  if (!mozilla::EarlyStartMacSandboxIfEnabled(argc, argv, err)) {
+    fprintf(stderr, "Sandbox error: %s\n", err.c_str());
+    MOZ_CRASH("Sandbox initialization failed");
+  }
+#endif
+
 #ifdef HAS_DLL_BLOCKLIST
   DllBlocklist_Initialize(eDllBlocklistInitFlagIsChildProcess);
 #endif
 
   Bootstrap::UniquePtr bootstrap = GetBootstrap();
   if (!bootstrap) {
     return 2;
   }
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -544,16 +544,22 @@ AddAppDirToCommandLine(std::vector<std::
 
 #if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
       // Full path to the profile dir
       nsCOMPtr<nsIFile> profileDir;
       rv = directoryService->Get(NS_APP_USER_PROFILE_50_DIR,
                                  NS_GET_IID(nsIFile),
                                  getter_AddRefs(profileDir));
       if (NS_SUCCEEDED(rv)) {
+        // If the profile doesn't exist, normalization will
+        // fail. But we don't return an error here because some
+        // tests require startup with a missing profile dir.
+        // For users, almost universally, the profile will be in
+        // the home directory and normalization isn't required.
+        mozilla::Unused << profileDir->Normalize();
         nsAutoCString path;
         MOZ_ALWAYS_SUCCEEDS(profileDir->GetNativePath(path));
         aCmdLine.push_back("-profile");
         aCmdLine.push_back(path.get());
       }
 #endif
     }
   }
--- a/security/sandbox/mac/Sandbox.h
+++ b/security/sandbox/mac/Sandbox.h
@@ -62,18 +62,23 @@ typedef struct _MacSandboxInfo {
   std::string profileDir;
   std::string debugWriteDir;
 
   std::string testingReadPath1;
   std::string testingReadPath2;
   std::string testingReadPath3;
   std::string testingReadPath4;
 
+  std::string parentPort;
+  std::string crashServerPort;
+
   bool shouldLog;
 } MacSandboxInfo;
 
 namespace mozilla {
 
 bool StartMacSandbox(MacSandboxInfo const &aInfo, std::string &aErrorMessage);
+bool EarlyStartMacSandboxIfEnabled(int aArgc, char** aArgv,
+                                   std::string &aErrorMessage);
 
 } // namespace mozilla
 
 #endif // mozilla_Sandbox_h
--- a/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -10,16 +10,18 @@
 // linking to nsCocoaFeatures.mm in XUL.
 
 #include "Sandbox.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <CoreFoundation/CoreFoundation.h>
 
+#include <iostream>
+#include <sstream>
 #include <vector>
 
 #include "mozilla/Assertions.h"
 
 // XXX There are currently problems with the /usr/include/sandbox.h file on
 // some/all of the Macs in Mozilla's build system. Further,
 // sandbox_init_with_parameters is not included in the header.  For the time
 // being (until this problem is resolved), we refer directly to what we need
@@ -225,16 +227,24 @@ bool StartMacSandbox(MacSandboxInfo cons
       params.push_back("APP_PATH");
       params.push_back(aInfo.appPath.c_str());
       params.push_back("PROFILE_DIR");
       params.push_back(aInfo.profileDir.c_str());
       params.push_back("HOME_PATH");
       params.push_back(getenv("HOME"));
       params.push_back("HAS_SANDBOXED_PROFILE");
       params.push_back(aInfo.hasSandboxedProfile ? "TRUE" : "FALSE");
+      if (!aInfo.parentPort.empty()) {
+        params.push_back("PARENT_PORT");
+        params.push_back(aInfo.parentPort.c_str());
+      }
+      if (!aInfo.crashServerPort.empty()) {
+        params.push_back("CRASH_PORT");
+        params.push_back(aInfo.crashServerPort.c_str());
+      }
       if (!aInfo.testingReadPath1.empty()) {
         params.push_back("TESTING_READ_PATH1");
         params.push_back(aInfo.testingReadPath1.c_str());
       }
       if (!aInfo.testingReadPath2.empty()) {
         params.push_back("TESTING_READ_PATH2");
         params.push_back(aInfo.testingReadPath2.c_str());
       }
@@ -313,9 +323,171 @@ bool StartMacSandbox(MacSandboxInfo cons
   }
   if (rv) {
     return false;
   }
 
   return true;
 }
 
+/*
+ * Fill |aInfo| with content sandbox params parsed from the provided
+ * command line arguments. Return false if any sandbox parameters needed
+ * for early startup of the sandbox are not present in the arguments.
+ */
+bool
+GetContentSandboxParamsFromArgs(int aArgc, char** aArgv, MacSandboxInfo& aInfo)
+{
+  // Ensure we find these paramaters in the command
+  // line arguments. Return false if any are missing.
+  bool foundSandboxLevel = false;
+  bool foundValidSandboxLevel = false;
+  bool foundParentPort = false;
+  bool foundAppPath = false;
+
+  // Read access directories used in testing
+  int nTestingReadPaths = 0;
+  std::string testingReadPaths[MAX_TESTING_READ_PATHS] = {};
+
+  // Collect sandbox params from CLI arguments
+  for (int i = 0; i < aArgc; i++) {
+
+    if ((strcmp(aArgv[i], "-sbLevel") == 0) && (i + 1 < aArgc)) {
+      std::stringstream ss(aArgv[i+1]);
+      int level = 0;
+      ss >> level;
+      foundSandboxLevel = true;
+      aInfo.level = level;
+      foundValidSandboxLevel = level > 0 && level <= 3 ? true : false;
+      if (!foundValidSandboxLevel) {
+        break;
+      }
+      i++;
+      continue;
+    }
+
+    if (strcmp(aArgv[i], "-sbLogging") == 0) {
+      aInfo.shouldLog = true;
+      continue;
+    }
+
+    if (strcmp(aArgv[i], "-sbAllowFileAccess") == 0) {
+      aInfo.hasFilePrivileges = true;
+      continue;
+    }
+
+    if (strcmp(aArgv[i], "-sbAllowAudio") == 0) {
+      aInfo.hasAudio = true;
+      continue;
+    }
+
+    if ((strcmp(aArgv[i], "-sbAppPath") == 0) && (i + 1 < aArgc)) {
+      foundAppPath = true;
+      aInfo.appPath.assign(aArgv[i+1]);
+      i++;
+      continue;
+    }
+
+    if ((strcmp(aArgv[i], "-sbTestingReadPath") == 0) && (i + 1 < aArgc)) {
+      MOZ_ASSERT(nTestingReadPaths < MAX_TESTING_READ_PATHS);
+      testingReadPaths[nTestingReadPaths] = aArgv[i+1];
+      nTestingReadPaths++;
+      i++;
+      continue;
+    }
+
+    if ((strcmp(aArgv[i], "-profile") == 0) && (i + 1 < aArgc)) {
+      aInfo.hasSandboxedProfile = true;
+      aInfo.profileDir.assign(aArgv[i+1]);
+      i++;
+      continue;
+    }
+
+#ifdef DEBUG
+    if ((strcmp(aArgv[i], "-sbDebugWriteDir") == 0) && (i + 1 < aArgc)) {
+      aInfo.debugWriteDir.assign(aArgv[i+1]);
+      i++;
+      continue;
+    }
+#endif // DEBUG
+
+    // Handle positional arguments
+    if (strstr(aArgv[i], "org.mozilla.machname") != NULL) {
+      foundParentPort = true;
+      aInfo.parentPort.assign(aArgv[i]);
+      continue;
+    }
+
+    if (strstr(aArgv[i], "gecko-crash-server-pipe") != NULL) {
+      aInfo.crashServerPort.assign(aArgv[i]);
+      continue;
+    }
+  }
+
+  if (!foundSandboxLevel) {
+    fprintf(stderr, "Content sandbox disabled due to "
+                    "missing sandbox CLI level parameter.\n");
+    return false;
+  }
+
+  if (!foundValidSandboxLevel) {
+    fprintf(stderr, "Content sandbox disabled due to invalid"
+                    "sandbox level (%d)\n", aInfo.level);
+    return false;
+  }
+
+  if (!foundParentPort) {
+    fprintf(stderr, "Content sandbox disabled due to "
+                    "missing sandbox CLI parent port parameter.\n");
+    return false;
+  }
+
+  if (!foundAppPath) {
+    fprintf(stderr, "Content sandbox disabled due to "
+                    "missing sandbox CLI app path parameter.\n");
+    return false;
+  }
+
+  aInfo.testingReadPath1 = testingReadPaths[0];
+  aInfo.testingReadPath2 = testingReadPaths[1];
+  aInfo.testingReadPath3 = testingReadPaths[2];
+  aInfo.testingReadPath4 = testingReadPaths[3];
+
+  return true;
+}
+
+/*
+ * Returns true if no errors were encountered or if early sandbox startup is
+ * not enabled for this process. Returns false if an error was encountered.
+ */
+bool
+EarlyStartMacSandboxIfEnabled(int aArgc, char** aArgv,
+                              std::string &aErrorMessage)
+{
+  bool earlyStartupEnabled = false;
+
+  // Check for the -sbStartup CLI parameter which
+  // indicates we should start the sandbox now.
+  for (int i = 0; i < aArgc; i++) {
+    if (strcmp(aArgv[i], "-sbStartup") == 0) {
+      earlyStartupEnabled = true;
+      break;
+    }
+  }
+
+  // The sandbox will be started later when/if parent
+  // sends the sandbox startup message. Return true
+  // indicating no errors occurred.
+  if (!earlyStartupEnabled) {
+    return true;
+  }
+
+  MacSandboxInfo info;
+  info.type = MacSandboxType_Content;
+  if (!GetContentSandboxParamsFromArgs(aArgc, aArgv, info)) {
+    return false;
+  }
+
+  return StartMacSandbox(info, aErrorMessage);
+}
+
+
 } // namespace mozilla
--- a/security/sandbox/mac/SandboxPolicies.h
+++ b/security/sandbox/mac/SandboxPolicies.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_SandboxPolicies_h
 #define mozilla_SandboxPolicies_h
 
+#define MAX_TESTING_READ_PATHS 4
+
 namespace mozilla {
 
 static const char pluginSandboxRules[] = R"SANDBOX_LITERAL(
   (version 1)
 
   (define should-log (param "SHOULD_LOG"))
   (define plugin-binary-path (param "PLUGIN_BINARY_PATH"))
   (define app-path (param "APP_PATH"))
@@ -53,16 +55,18 @@ static const char contentSandboxRules[] 
   (define hasProfileDir (param "HAS_SANDBOXED_PROFILE"))
   (define profileDir (param "PROFILE_DIR"))
   (define home-path (param "HOME_PATH"))
   (define debugWriteDir (param "DEBUG_WRITE_DIR"))
   (define testingReadPath1 (param "TESTING_READ_PATH1"))
   (define testingReadPath2 (param "TESTING_READ_PATH2"))
   (define testingReadPath3 (param "TESTING_READ_PATH3"))
   (define testingReadPath4 (param "TESTING_READ_PATH4"))
+  (define parentPort (param "PARENT_PORT"))
+  (define crashPort (param "CRASH_PORT"))
 
   (if (string=? should-log "TRUE")
     (deny default)
     (deny default (with no-log)))
   (debug deny)
   ; These are not included in (deny default)
   (deny process-info*)
   ; This isn't available in some older macOS releases.
@@ -178,16 +182,23 @@ static const char contentSandboxRules[] 
   (define (allow-shared-list domain)
     (allow file-read*
            (home-regex (string-append "/Library/Preferences/" (regex-quote domain)))))
 
   (allow ipc-posix-shm-read-data ipc-posix-shm-write-data
     (ipc-posix-name-regex #"^CFPBS:"))
 
   (allow signal (target self))
+  (if (string? parentPort)
+    (allow mach-lookup (global-name parentPort)))
+  (if (string? crashPort)
+    (allow mach-lookup (global-name crashPort)))
+  (allow mach-lookup (global-name "com.apple.coreservices.launchservicesd"))
+  (allow mach-lookup (global-name "com.apple.windowserver.active"))
+  (allow mach-lookup (global-name "com.apple.lsd.mapdb"))
 
   (if (>= macosMinorVersion 13)
     (allow mach-lookup
       ; bug 1392988
       (xpc-service-name "com.apple.coremedia.videodecoder")
       (xpc-service-name "com.apple.coremedia.videoencoder")))
 
 ; bug 1312273