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
--- 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()