Bug 1294641 - whitelist reads from the .app directory in the macOS sandbox draft
authorAlex Gaynor <agaynor@mozilla.com>
Fri, 07 Apr 2017 14:53:19 -0400
changeset 563738 f8d01b9f37c84671399e60045604dc12bc53184b
parent 563687 05c212a94183838f12feebb2c3fd483a6eec18c2
child 624558 2dd1c162736877802547afc62853256b9add131d
push id54399
push userbmo:agaynor@mozilla.com
push dateMon, 17 Apr 2017 17:29:23 +0000
bugs1294641
milestone55.0a1
Bug 1294641 - whitelist reads from the .app directory in the macOS sandbox This patch does a few things: a) Adds the resources location from the .app directory to the read whitelist b) When it's a non-packaged build, mach run (and various mach tests) set an environment variable for the repo location which we allow reads from. r=haik,froydnj MozReview-Commit-ID: KNvAoUs5Ati
dom/ipc/ContentChild.cpp
layout/tools/reftest/mach_commands.py
layout/tools/reftest/runreftest.py
python/mozbuild/mozbuild/mach_commands.py
testing/mochitest/mach_commands.py
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
testing/xpcshell/runxpcshelltests.py
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -54,16 +54,17 @@
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/CaptivePortalService.h"
+#include "mozilla/Omnijar.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/widget/WidgetMessageUtils.h"
 #include "nsBaseDragService.h"
 #include "mozilla/media/MediaChild.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/WebBrowserPersistDocumentChild.h"
@@ -1220,17 +1221,17 @@ GetAppPaths(nsCString &aAppPath, nsCStri
   }
 
   nsCOMPtr<nsIFile> appDir;
   nsCOMPtr<nsIProperties> dirSvc =
     do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
   if (!dirSvc) {
     return false;
   }
-  rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR,
+  rv = dirSvc->Get(NS_GRE_DIR,
                    NS_GET_IID(nsIFile), getter_AddRefs(appDir));
   if (NS_FAILED(rv)) {
     return false;
   }
   bool exists;
   rv = appDir->Exists(&exists);
   if (NS_FAILED(rv) || !exists) {
     return false;
@@ -1254,16 +1255,28 @@ GetAppPaths(nsCString &aAppPath, nsCStri
     appDir->GetNativeTarget(aAppDir);
   } else {
     appDir->GetNativePath(aAppDir);
   }
 
   return true;
 }
 
+// Returns whether or not the currently running build is a development build -
+// where development build means "the files in the .app are symlinks to the src
+// directory". This check is implemented by looking for omni.ja in
+// .app/Contents/Resources/.
+static bool
+IsDevelopmentBuild()
+{
+  nsCOMPtr<nsIFile> path = mozilla::Omnijar::GetPath(mozilla::Omnijar::GRE);
+  // If the path doesn't exist, we're a dev build.
+  return path == nullptr;
+}
+
 static bool
 StartMacOSContentSandbox()
 {
   int sandboxLevel = Preferences::GetInt("security.sandbox.content.level");
   if (sandboxLevel < 1) {
     return false;
   }
 
@@ -1297,26 +1310,37 @@ StartMacOSContentSandbox()
     profileDir->Normalize();
     rv = profileDir->GetNativePath(profileDirPath);
     if (NS_FAILED(rv) || profileDirPath.IsEmpty()) {
       MOZ_CRASH("Failed to get profile path");
     }
   }
 
   bool isFileProcess = cc->GetRemoteType().EqualsLiteral(FILE_REMOTE_TYPE);
+  char *developer_repo_dir = nullptr;
+  if (IsDevelopmentBuild()) {
+    // If this is a developer build the resources in the .app are symlinks to
+    // outside of the .app. Therefore in non-release builds we allow reads from
+    // the whole repository. MOZ_DEVELOPER_REPO_DIR is set by mach run.
+    developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
+  }
 
   MacSandboxInfo info;
   info.type = MacSandboxType_Content;
   info.level = sandboxLevel;
   info.hasFilePrivileges = isFileProcess;
   info.shouldLog = Preferences::GetBool("security.sandbox.logging.enabled") ||
                    PR_GetEnv("MOZ_SANDBOX_LOGGING");
   info.appPath.assign(appPath.get());
   info.appBinaryPath.assign(appBinaryPath.get());
-  info.appDir.assign(appDir.get());
+  if (developer_repo_dir != nullptr) {
+    info.appDir.assign(developer_repo_dir);
+  } else {
+    info.appDir.assign(appDir.get());
+  }
   info.appTempDir.assign(tempDirPath.get());
 
   if (profileDir) {
     info.hasSandboxedProfile = true;
     info.profileDir.assign(profileDirPath.get());
   } else {
     info.hasSandboxedProfile = false;
   }
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -217,15 +217,16 @@ class MachCommands(MachCommandBase):
              category='testing',
              description='Run crashtests (Check if crashes on a page).',
              parser=get_parser)
     def run_crashtest(self, **kwargs):
         kwargs["suite"] = "crashtest"
         return self._run_reftest(**kwargs)
 
     def _run_reftest(self, **kwargs):
+        kwargs["topsrcdir"] = self.topsrcdir
         process_test_objects(kwargs)
         reftest = self._spawn(ReftestRunner)
         if conditions.is_android(self):
             from mozrunner.devices.android_device import verify_android_device
             verify_android_device(self, install=True, xre=True)
             return reftest.run_android_test(**kwargs)
         return reftest.run_desktop_test(**kwargs)
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -357,16 +357,18 @@ class RefTest(object):
     def environment(self, **kwargs):
         kwargs['log'] = self.log
         return test_environment(**kwargs)
 
     def buildBrowserEnv(self, options, profileDir):
         browserEnv = self.environment(
             xrePath=options.xrePath, debugger=options.debugger)
         browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
+        if hasattr(options, "topsrcdir"):
+            browserEnv["MOZ_DEVELOPER_REPO_DIR"] = options.topsrcdir
 
         if mozinfo.info["asan"]:
             # Disable leak checking for reftests for now
             if "ASAN_OPTIONS" in browserEnv:
                 browserEnv["ASAN_OPTIONS"] += ":detect_leaks=0"
             else:
                 browserEnv["ASAN_OPTIONS"] = "detect_leaks=0"
 
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -1177,17 +1177,19 @@ class RunProgram(MachCommandBase):
                 all(p not in params for p in ['-profile', '--profile', '-P'])
             if no_profile_option_given and not noprofile:
                 path = os.path.join(self.topobjdir, 'tmp', 'scratch_user')
                 if not os.path.isdir(path):
                     os.makedirs(path)
                 args.append('-profile')
                 args.append(path)
 
-        extra_env = {}
+        extra_env = {
+            'MOZ_DEVELOPER_REPO_DIR': self.topsrcdir,
+        }
 
         if not enable_crash_reporter:
             extra_env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
         if disable_e10s:
             extra_env['MOZ_FORCE_DISABLE_E10S'] = '1'
 
         if debug or debugger or debugparams:
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -134,16 +134,17 @@ class MochitestRunner(MozbuildObject):
         # Automation installs its own stream handler to stdout. Since we want
         # all logging to go through us, we just remove their handler.
         remove_handlers = [l for l in logging.getLogger().handlers
                            if isinstance(l, logging.StreamHandler)]
         for handler in remove_handlers:
             logging.getLogger().removeHandler(handler)
 
         options = Namespace(**kwargs)
+        options.topsrcdir = self.topsrcdir
 
         from manifestparser import TestManifest
         if tests and not options.manifestFile:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
             # When developing mochitest-plain tests, it's often useful to be able to
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -997,16 +997,19 @@ class AndroidArguments(ArgumentContainer
 
         if build_obj and 'MOZ_HOST_BIN' in os.environ:
             options.xrePath = os.environ['MOZ_HOST_BIN']
 
         # Only reset the xrePath if it wasn't provided
         if options.xrePath is None:
             options.xrePath = options.utilityPath
 
+        if build_obj:
+            options.topsrcdir = build_obj.topsrcdir
+
         if options.pidFile != "":
             f = open(options.pidFile, 'w')
             f.write("%s" % os.getpid())
             f.close()
 
         # Robocop specific options
         if options.robocopIni != "":
             if not os.path.exists(options.robocopIni):
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1572,16 +1572,19 @@ toolbar#nav-bar {
 
         browserEnv = self.environment(
             xrePath=options.xrePath,
             env=env,
             debugger=debugger,
             dmdPath=options.dmdPath,
             lsanPath=lsanPath)
 
+        if hasattr(options, "topsrcdir"):
+            browserEnv["MOZ_DEVELOPER_REPO_DIR"] = options.topsrcdir
+
         # These variables are necessary for correct application startup; change
         # via the commandline at your own risk.
         browserEnv["XPCOM_DEBUG_BREAK"] = "stack"
 
         # interpolate environment passed with options
         try:
             browserEnv.update(
                 dict(
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -916,16 +916,18 @@ class XPCShellTests(object):
             self.env["MOZ_CRASHREPORTER"] = "1"
         # Don't launch the crash reporter client
         self.env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
         # Don't permit remote connections by default.
         # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
         # enable non-local connections for the purposes of local testing.
         # Don't override the user's choice here.  See bug 1049688.
         self.env.setdefault('MOZ_DISABLE_NONLOCAL_CONNECTIONS', '1')
+        if self.mozInfo.get("topsrcdir") is not None:
+            self.env["MOZ_DEVELOPER_REPO_DIR"] = self.mozInfo["topsrcdir"].encode()
 
     def buildEnvironment(self):
         """
           Create and returns a dictionary of self.env to include all the appropriate env variables and values.
           On a remote system, we overload this to set different values and are missing things like os.environ and PATH.
         """
         self.env = dict(os.environ)
         self.buildCoreEnvironment()