Bug 1308400 - Support file process, whitelist path prefs. r?jld
MozReview-Commit-ID: 3eX06AioPZL
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1080,30 +1080,32 @@ pref("security.sandbox.content.level", 1
#endif
#endif
#if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
// This pref is introduced as part of bug 742434, the naming is inspired from
// its Windows/Mac counterpart, but on Linux it's an integer which means:
// 0 -> "no sandbox"
// 1 -> "content sandbox using seccomp-bpf when available"
-// 2 -> "seccomp-bpf + file broker"
+// 2 -> "seccomp-bpf + write file broker"
+// 3 -> "seccomp-bpf + read/write file brokering"
// Content sandboxing on Linux is currently in the stage of
// 'just getting it enabled', which includes a very permissive whitelist. We
// enable seccomp-bpf on nightly to see if everything is running, or if we need
// to whitelist more system calls.
//
// So the purpose of this setting is to allow nightly users to disable the
// sandbox while we fix their problems. This way, they won't have to wait for
// another nightly release which disables seccomp-bpf again.
//
// This setting may not be required anymore once we decide to permanently
// enable the content sandbox.
-pref("security.sandbox.content.level", 2);
+pref("security.sandbox.content.level", 3);
pref("security.sandbox.content.write_path_whitelist", "");
+pref("security.sandbox.content.read_path_whitelist", "");
pref("security.sandbox.content.syscall_whitelist", "");
#endif
#if defined(XP_MACOSX) || defined(XP_WIN)
#if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
// ID (a UUID when set by gecko) that is used to form the name of a
// sandbox-writable temporary directory to be used by content processes
// when a temporary writable file is required in a level 1 sandbox.
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1613,17 +1613,20 @@ ContentChild::RecvSetProcessSandbox(cons
for (const nsACString& callNrString : extraSyscalls.Split(',')) {
nsresult rv;
int callNr = PromiseFlatCString(callNrString).ToInteger(&rv);
if (NS_SUCCEEDED(rv)) {
syscallWhitelist.push_back(callNr);
}
}
}
- sandboxEnabled = SetContentProcessSandbox(brokerFd, syscallWhitelist);
+ ContentChild* cc = ContentChild::GetSingleton();
+ bool isFileProcess = cc->GetRemoteType().EqualsLiteral(FILE_REMOTE_TYPE);
+ sandboxEnabled = SetContentProcessSandbox(brokerFd, isFileProcess,
+ syscallWhitelist);
}
#elif defined(XP_WIN)
mozilla::SandboxTarget::Instance()->StartSandbox();
#elif defined(XP_MACOSX)
sandboxEnabled = StartMacOSContentSandbox();
#endif
#if defined(MOZ_CRASHREPORTER)
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2442,18 +2442,19 @@ ContentParent::InitInternal(ProcessPrior
// should be changed so that it is required to restart firefox for the change
// of value to take effect.
shouldSandbox = (GetEffectiveContentSandboxLevel() > 0) &&
!PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX");
#ifdef XP_LINUX
if (shouldSandbox) {
MOZ_ASSERT(!mSandboxBroker);
+ bool isFileProcess = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
UniquePtr<SandboxBroker::Policy> policy =
- sSandboxBrokerPolicyFactory->GetContentPolicy(Pid());
+ sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess);
if (policy) {
brokerFd = FileDescriptor();
mSandboxBroker = SandboxBroker::Create(Move(policy), Pid(), brokerFd);
if (!mSandboxBroker) {
KillHard("SandboxBroker::Create failed");
return;
}
MOZ_ASSERT(static_cast<const FileDescriptor&>(brokerFd).IsValid());
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -670,26 +670,28 @@ SandboxEarlyInit(GeckoProcessType aType)
#ifdef MOZ_CONTENT_SANDBOX
/**
* Starts the seccomp sandbox for a content process. Should be called
* only once, and before any potentially harmful content is loaded.
*
* Will normally make the process exit on failure.
*/
bool
-SetContentProcessSandbox(int aBrokerFd, std::vector<int>& aSyscallWhitelist)
+SetContentProcessSandbox(int aBrokerFd, bool aFileProcess,
+ std::vector<int>& aSyscallWhitelist)
{
if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
if (aBrokerFd >= 0) {
close(aBrokerFd);
}
return false;
}
- gSandboxReporterClient.emplace(SandboxReport::ProcType::CONTENT);
+ gSandboxReporterClient.emplace(aFileProcess ? SandboxReport::ProcType::FILE
+ : SandboxReport::ProcType::CONTENT);
// This needs to live until the process exits.
static Maybe<SandboxBrokerClient> sBroker;
if (aBrokerFd >= 0) {
sBroker.emplace(aBrokerFd);
}
SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr),
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -20,17 +20,19 @@ namespace mozilla {
// This must be called early, while the process is still single-threaded.
MOZ_EXPORT void SandboxEarlyInit(GeckoProcessType aType);
#ifdef MOZ_CONTENT_SANDBOX
// Call only if SandboxInfo::CanSandboxContent() returns true.
// (No-op if MOZ_DISABLE_CONTENT_SANDBOX is set.)
// aBrokerFd is the filesystem broker client file descriptor,
// or -1 to allow direct filesystem access.
+// isFileProcess determines whether we allow system wide file reads.
MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd,
+ bool aFileProcess,
std::vector<int>& aSyscallWhitelist);
#endif
#ifdef MOZ_GMP_SANDBOX
// Call only if SandboxInfo::CanSandboxMedia() returns true.
// (No-op if MOZ_DISABLE_GMP_SANDBOX is set.)
// aFilePath is the path to the plugin file.
MOZ_EXPORT void SetMediaPluginSandbox(const char *aFilePath);
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -6,20 +6,23 @@
#include "SandboxBrokerPolicyFactory.h"
#include "SandboxInfo.h"
#include "SandboxLogging.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "mozilla/SandboxSettings.h"
+#include "mozilla/dom/ContentChild.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
#include "SpecialSystemDirectory.h"
#ifdef ANDROID
#include "cutils/properties.h"
#endif
#ifdef MOZ_WIDGET_GTK
#include <glib.h>
@@ -118,24 +121,26 @@ SandboxBrokerPolicyFactory::SandboxBroke
policy->AddTree(rdonly, "/system//usr/share/zoneinfo");
policy->AddPath(rdonly, "/data/local/tmp/profiler.options",
SandboxBroker::Policy::AddAlways); // bug 1029337
mCommonContentPolicy.reset(policy);
#elif defined(MOZ_CONTENT_SANDBOX)
SandboxBroker::Policy* policy = new SandboxBroker::Policy;
- policy->AddDir(rdonly, "/");
policy->AddDir(rdwrcr, "/dev/shm");
+ // Write permssions
+ //
// Add write permissions on the temporary directory. This can come
// from various environment variables (TMPDIR,TMP,TEMP,...) so
// make sure to use the full logic.
nsCOMPtr<nsIFile> tmpDir;
nsresult rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
getter_AddRefs(tmpDir));
+
if (NS_SUCCEEDED(rv)) {
nsAutoCString tmpPath;
rv = tmpDir->GetNativePath(tmpPath);
if (NS_SUCCEEDED(rv)) {
policy->AddDir(rdwrcr, tmpPath.get());
}
}
// If the above fails at any point, fall back to a very good guess.
@@ -158,71 +163,203 @@ SandboxBrokerPolicyFactory::SandboxBroke
// Bug 1321134: DConf's single bit of shared memory
if (const auto userDir = g_get_user_runtime_dir()) {
// The leaf filename is "user" by default, but is configurable.
nsPrintfCString shmPath("%s/dconf/", userDir);
policy->AddPrefix(rdwrcr, shmPath.get());
}
#endif
+ // Read permissions
+ // No read blocking at level 2 and below
+ if (Preferences::GetInt("security.sandbox.content.level") <= 2) {
+ policy->AddDir(rdonly, "/");
+ mCommonContentPolicy.reset(policy);
+ return;
+ }
+ policy->AddPath(rdonly, "/dev/urandom");
+ policy->AddPath(rdonly, "/proc/cpuinfo");
+ policy->AddPath(rdonly, "/proc/meminfo");
+ policy->AddDir(rdonly, "/lib");
+ policy->AddDir(rdonly, "/etc");
+ policy->AddDir(rdonly, "/usr/share");
+ policy->AddDir(rdonly, "/usr/local/share");
+ policy->AddDir(rdonly, "/usr/lib");
+ policy->AddDir(rdonly, "/usr/lib32");
+ policy->AddDir(rdonly, "/usr/lib64");
+ policy->AddDir(rdonly, "/usr/X11R6/lib/X11/fonts");
+ policy->AddDir(rdonly, "/usr/tmp");
+ policy->AddDir(rdonly, "/var/tmp");
+ policy->AddDir(rdonly, "/sys/devices/cpu");
+ policy->AddDir(rdonly, "/sys/devices/system/cpu");
+
+ // Configuration dirs in the homedir that we want to allow read
+ // access to.
+ mozilla::Array<const char*, 3> confDirs = {
+ ".config",
+ ".themes",
+ ".fonts",
+ };
+
+ nsCOMPtr<nsIFile> homeDir;
+ rv = GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> confDir;
+
+ for (auto dir : confDirs) {
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(nsDependentCString(dir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+ }
+
+ // ~/.local/share (for themes)
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(NS_LITERAL_CSTRING(".local"));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(NS_LITERAL_CSTRING("share"));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+ }
+
+ // ~/.fonts.conf (Fontconfig)
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(NS_LITERAL_CSTRING(".fonts.conf"));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddPath(rdonly, tmpPath.get());
+ }
+ }
+ }
+
+ // .pangorc
+ rv = homeDir->Clone(getter_AddRefs(confDir));
+ if (NS_SUCCEEDED(rv)) {
+ rv = confDir->AppendNative(NS_LITERAL_CSTRING(".pangorc"));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = confDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddPath(rdonly, tmpPath.get());
+ }
+ }
+ }
+ }
+
+ // Firefox binary dir.
+ // Note that unlike the previous cases, we use NS_GetSpecialDirectory
+ // instead of GetSpecialSystemDirectory. The former requires a working XPCOM
+ // system, which may not be the case for some tests. For quering for the
+ // location of XPCOM things, we can use it anyway.
+ nsCOMPtr<nsIFile> ffDir;
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(ffDir));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString tmpPath;
+ rv = ffDir->GetNativePath(tmpPath);
+ if (NS_SUCCEEDED(rv)) {
+ policy->AddDir(rdonly, tmpPath.get());
+ }
+ }
+
+ if (mozilla::IsDevelopmentBuild()) {
+ // If this is a developer build the resources are symlinks to outside the binary dir.
+ // Therefore in non-release builds we allow reads from the whole repository.
+ // MOZ_DEVELOPER_REPO_DIR is set by mach run.
+ const char *developer_repo_dir = PR_GetEnv("MOZ_DEVELOPER_REPO_DIR");
+ if (developer_repo_dir) {
+ policy->AddDir(rdonly, developer_repo_dir);
+ }
+ }
+
mCommonContentPolicy.reset(policy);
#endif
}
#ifdef MOZ_CONTENT_SANDBOX
UniquePtr<SandboxBroker::Policy>
-SandboxBrokerPolicyFactory::GetContentPolicy(int aPid)
+SandboxBrokerPolicyFactory::GetContentPolicy(int aPid, bool aFileProcess)
{
// Policy entries that vary per-process (currently the only reason
// that can happen is because they contain the pid) are added here.
MOZ_ASSERT(NS_IsMainThread());
// File broker usage is controlled through a pref.
if (GetEffectiveContentSandboxLevel() <= 1) {
return nullptr;
}
MOZ_ASSERT(mCommonContentPolicy);
+ UniquePtr<SandboxBroker::Policy>
+ policy(new SandboxBroker::Policy(*mCommonContentPolicy));
+
#if defined(MOZ_WIDGET_GONK)
// Allow overriding "unsupported"ness with a pref, for testing.
if (!IsSystemSupported()) {
return nullptr;
}
- UniquePtr<SandboxBroker::Policy>
- policy(new SandboxBroker::Policy(*mCommonContentPolicy));
// Bug 1029337: where the profiler writes the data.
nsPrintfCString profilerLogPath("/data/local/tmp/profile_%d_%d.txt",
GeckoProcessType_Content, aPid);
policy->AddPath(wrlog, profilerLogPath.get());
+#endif
// Bug 1198550: the profiler's replacement for dl_iterate_phdr
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/maps", aPid).get());
// Bug 1198552: memory reporting.
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/statm", aPid).get());
policy->AddPath(rdonly, nsPrintfCString("/proc/%d/smaps", aPid).get());
- return policy;
-#else
- UniquePtr<SandboxBroker::Policy>
- policy(new SandboxBroker::Policy(*mCommonContentPolicy));
-
+#ifndef MOZ_WIDGET_GONK
// Now read any extra paths, this requires accessing user preferences
// so we can only do it now. Our constructor is initialized before
// user preferences are read in.
- nsAdoptingCString extraPathString =
+ nsAdoptingCString extraReadPathString =
+ Preferences::GetCString("security.sandbox.content.read_path_whitelist");
+ AddDynamicPathList(policy.get(), extraReadPathString, rdonly);
+ nsAdoptingCString extraWritePathString =
Preferences::GetCString("security.sandbox.content.write_path_whitelist");
- if (extraPathString) {
- for (const nsACString& path : extraPathString.Split(',')) {
- nsCString trimPath(path);
- trimPath.Trim(" ", true, true);
- policy->AddDynamic(rdwr, trimPath.get());
- }
+ AddDynamicPathList(policy.get(), extraWritePathString, rdwr);
+
+ // file:// processes get global read permissions
+ if (aFileProcess) {
+ policy->AddDir(rdonly, "/");
}
+#endif
// Return the common policy.
return policy;
-#endif
+
+}
+
+void
+SandboxBrokerPolicyFactory::AddDynamicPathList(SandboxBroker::Policy *policy,
+ nsAdoptingCString& pathList,
+ int perms) {
+ if (pathList) {
+ for (const nsACString& path : pathList.Split(',')) {
+ nsCString trimPath(path);
+ trimPath.Trim(" ", true, true);
+ policy->AddDynamic(perms, trimPath.get());
+ }
+ }
}
#endif // MOZ_CONTENT_SANDBOX
} // namespace mozilla
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.h
@@ -11,22 +11,25 @@
namespace mozilla {
class SandboxBrokerPolicyFactory {
public:
SandboxBrokerPolicyFactory();
#ifdef MOZ_CONTENT_SANDBOX
- UniquePtr<SandboxBroker::Policy> GetContentPolicy(int aPid);
+ UniquePtr<SandboxBroker::Policy> GetContentPolicy(int aPid, bool aFileProcess);
#endif
private:
UniquePtr<const SandboxBroker::Policy> mCommonContentPolicy;
// B2G devices tend to have hardware-specific paths used by device
// drivers, so rollout of filesystem isolation will need per-device
// testing. This predicate allows that to happen gradually.
static bool IsSystemSupported();
+ static void AddDynamicPathList(SandboxBroker::Policy *policy,
+ nsAdoptingCString& paths,
+ int perms);
};
} // namespace mozilla
#endif // mozilla_SandboxBrokerPolicyFactory_h
--- a/security/sandbox/linux/reporter/SandboxReporterCommon.h
+++ b/security/sandbox/linux/reporter/SandboxReporterCommon.h
@@ -26,16 +26,17 @@ static const int kSandboxReporterFileDes
// seccomp-bpf policy.
struct SandboxReport {
// In the future this may include finer distinctions than
// GeckoProcessType -- e.g., whether a content process can load
// file:/// URLs, or if it's reserved for content with certain
// user-granted permissions.
enum class ProcType : uint8_t {
CONTENT,
+ FILE,
MEDIA_PLUGIN,
};
// The syscall number and arguments are usually `unsigned long`, but
// that causes ambiguous overload errors with nsACString::AppendInt.
using ULong = UnsignedStdintTypeForSize<sizeof(unsigned long)>::Type;
// This time uses CLOCK_MONOTONIC_COARSE. Displaying or reporting
--- a/security/sandbox/test/browser_content_sandbox_fs.js
+++ b/security/sandbox/test/browser_content_sandbox_fs.js
@@ -243,24 +243,29 @@ async function testFileAccess() {
// Directories/files to test accessing from content processes.
// For directories, we test whether a directory listing is allowed
// or blocked. For files, we test if we can read from the file.
// Each entry in the array represents a test file or directory
// that will be read from either a web or file process.
let tests = [];
+ // The Linux test runners create the temporary profile in the same
+ // system temp dir we give write access to, so this gives a false
+ // positive.
let profileDir = GetProfileDir();
- tests.push({
- desc: "profile dir", // description
- ok: false, // expected to succeed?
- browser: webBrowser, // browser to run test in
- file: profileDir, // nsIFile object
- minLevel: minProfileReadSandboxLevel(), // min level to enable test
- });
+ if (!isLinux()) {
+ tests.push({
+ desc: "profile dir", // description
+ ok: false, // expected to succeed?
+ browser: webBrowser, // browser to run test in
+ file: profileDir, // nsIFile object
+ minLevel: minProfileReadSandboxLevel(), // min level to enable test
+ });
+ }
if (fileContentProcessEnabled) {
tests.push({
desc: "profile dir",
ok: true,
browser: fileBrowser,
file: profileDir,
minLevel: 0,
});
@@ -303,29 +308,25 @@ async function testFileAccess() {
ok: shouldBeReadable,
browser: webBrowser,
file: homeTempDir,
minLevel,
});
}
}
- // Should we enable this /var test on Linux? Once we are running
- // with read access restrictions on Linux, this todo will fail and
- // should then be removed.
- if (isLinux()) {
- todo(level >= minHomeReadSandboxLevel(), "enable /var test on Linux?");
- }
- if (isMac()) {
+ if (isMac() || isLinux()) {
let varDir = GetDir("/var");
- // Mac sandbox rules use /private/var because /var is a symlink
- // to /private/var on OS X. Make sure that hasn't changed.
- varDir.normalize();
- Assert.ok(varDir.path === "/private/var", "/var resolves to /private/var");
+ if (isMac()) {
+ // Mac sandbox rules use /private/var because /var is a symlink
+ // to /private/var on OS X. Make sure that hasn't changed.
+ varDir.normalize();
+ Assert.ok(varDir.path === "/private/var", "/var resolves to /private/var");
+ }
tests.push({
desc: "/var",
ok: false,
browser: webBrowser,
file: varDir,
minLevel: minHomeReadSandboxLevel(),
});