Bug 1330326 - Make sandboxing policy more configurable via preferences. r?jld
MozReview-Commit-ID: 9P0bSLLKRWp
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1010,16 +1010,18 @@ pref("security.sandbox.content.level", 1
//
// 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.write_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.
pref("security.sandbox.content.tempDirSuffix", "");
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -218,16 +218,17 @@ using namespace mozilla::jsipc;
using namespace mozilla::psm;
using namespace mozilla::widget;
#if defined(MOZ_WIDGET_GONK)
using namespace mozilla::system;
#endif
using namespace mozilla::widget;
namespace mozilla {
+
namespace dom {
// IPC sender for remote GC/CC logging.
class CycleCollectWithLogsChild final
: public PCycleCollectWithLogsChild
, public nsICycleCollectorLogSink
{
public:
@@ -1348,17 +1349,30 @@ ContentChild::RecvSetProcessSandbox(cons
if (aBroker.type() == MaybeFileDesc::TFileDescriptor) {
auto fd = aBroker.get_FileDescriptor().ClonePlatformHandle();
brokerFd = fd.release();
// brokerFd < 0 means to allow direct filesystem access, so
// make absolutely sure that doesn't happen if the parent
// didn't intend it.
MOZ_RELEASE_ASSERT(brokerFd >= 0);
}
- sandboxEnabled = SetContentProcessSandbox(brokerFd);
+ // Allow user overrides of seccomp-bpf syscall filtering
+ std::vector<int> syscallWhitelist;
+ nsAdoptingCString extraSyscalls =
+ Preferences::GetCString("security.sandbox.content.syscall_whitelist");
+ if (extraSyscalls) {
+ for (const nsCSubstring& callNrString : extraSyscalls.Split(',')) {
+ nsresult rv;
+ int callNr = PromiseFlatCString(callNrString).ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ syscallWhitelist.push_back(callNr);
+ }
+ }
+ }
+ sandboxEnabled = SetContentProcessSandbox(brokerFd, syscallWhitelist);
}
#elif defined(XP_WIN)
mozilla::SandboxTarget::Instance()->StartSandbox();
#elif defined(XP_MACOSX)
sandboxEnabled = StartMacOSContentSandbox();
#endif
#if defined(MOZ_CRASHREPORTER)
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -25,16 +25,17 @@
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <unistd.h>
+#include <vector>
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/SandboxInfo.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "sandbox/linux/bpf_dsl/codegen.h"
#include "sandbox/linux/bpf_dsl/dump_bpf.h"
#include "sandbox/linux/bpf_dsl/policy.h"
@@ -629,32 +630,33 @@ 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)
+SetContentProcessSandbox(int aBrokerFd, std::vector<int>& aSyscallWhitelist)
{
if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
if (aBrokerFd >= 0) {
close(aBrokerFd);
}
return false;
}
// This needs to live until the process exits.
static Maybe<SandboxBrokerClient> sBroker;
if (aBrokerFd >= 0) {
sBroker.emplace(aBrokerFd);
}
- SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr)));
+ SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr),
+ aSyscallWhitelist));
return true;
}
#endif // MOZ_CONTENT_SANDBOX
#ifdef MOZ_GMP_SANDBOX
/**
* Starts the seccomp sandbox for a media plugin process. Should be
* called only once, and before any potentially harmful content is
--- a/security/sandbox/linux/Sandbox.h
+++ b/security/sandbox/linux/Sandbox.h
@@ -19,17 +19,18 @@ 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.
-MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd);
+MOZ_EXPORT bool SetContentProcessSandbox(int aBrokerFd,
+ 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);
#endif
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -6,31 +6,32 @@
#include "SandboxFilter.h"
#include "SandboxFilterUtil.h"
#include "SandboxBrokerClient.h"
#include "SandboxInfo.h"
#include "SandboxInternal.h"
#include "SandboxLogging.h"
-
#include "mozilla/UniquePtr.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/ipc.h>
#include <linux/net.h>
#include <linux/prctl.h>
#include <linux/sched.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
+#include <vector>
+#include <algorithm>
#include "sandbox/linux/bpf_dsl/bpf_dsl.h"
#include "sandbox/linux/system_headers/linux_seccomp.h"
#include "sandbox/linux/system_headers/linux_syscalls.h"
using namespace sandbox::bpf_dsl;
#define CASES SANDBOX_BPF_DSL_CASES
@@ -342,17 +343,19 @@ public:
#ifdef MOZ_CONTENT_SANDBOX
// The seccomp-bpf filter for content processes is not a true sandbox
// on its own; its purpose is attack surface reduction and syscall
// interception in support of a semantic sandboxing layer. On B2G
// this is the Android process permission model; on desktop,
// namespaces and chroot() will be used.
class ContentSandboxPolicy : public SandboxPolicyCommon {
+private:
SandboxBrokerClient* mBroker;
+ std::vector<int> mSyscallWhitelist;
// Trap handlers for filesystem brokering.
// (The amount of code duplication here could be improved....)
#ifdef __NR_open
static intptr_t OpenTrap(ArgsRef aArgs, void* aux) {
auto broker = static_cast<SandboxBrokerClient*>(aux);
auto path = reinterpret_cast<const char*>(aArgs.args[0]);
auto flags = static_cast<int>(aArgs.args[1]);
@@ -492,17 +495,20 @@ class ContentSandboxPolicy : public Sand
static intptr_t GetPPidTrap(ArgsRef aArgs, void* aux) {
// In a pid namespace, getppid() will return 0. We will return 0 instead
// of the real parent pid to see what breaks when we introduce the
// pid namespace (Bug 1151624).
return 0;
}
public:
- explicit ContentSandboxPolicy(SandboxBrokerClient* aBroker):mBroker(aBroker) { }
+ explicit ContentSandboxPolicy(SandboxBrokerClient* aBroker,
+ const std::vector<int>& aSyscallWhitelist)
+ : mBroker(aBroker),
+ mSyscallWhitelist(aSyscallWhitelist) {}
virtual ~ContentSandboxPolicy() { }
virtual ResultExpr PrctlPolicy() const override {
// Ideally this should be restricted to a whitelist, but content
// uses enough things that it's not trivial to determine it.
return Allow();
}
virtual Maybe<ResultExpr> EvaluateSocketCall(int aCall) const override {
switch(aCall) {
@@ -565,16 +571,24 @@ public:
return Some(Allow());
default:
return SandboxPolicyCommon::EvaluateIpcCall(aCall);
}
}
#endif
virtual ResultExpr EvaluateSyscall(int sysno) const override {
+ // Straight allow for anything that got overriden via prefs
+ if (std::find(mSyscallWhitelist.begin(), mSyscallWhitelist.end(), sysno)
+ != mSyscallWhitelist.end()) {
+ if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
+ SANDBOX_LOG_ERROR("Allowing syscall nr %d via whitelist", sysno);
+ }
+ return Allow();
+ }
if (mBroker) {
// Have broker; route the appropriate syscalls to it.
switch (sysno) {
case __NR_open:
return Trap(OpenTrap, mBroker);
case __NR_openat:
return Trap(OpenAtTrap, mBroker);
case __NR_access:
@@ -829,19 +843,20 @@ public:
default:
return SandboxPolicyCommon::EvaluateSyscall(sysno);
}
}
};
UniquePtr<sandbox::bpf_dsl::Policy>
-GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker)
+GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker,
+ const std::vector<int>& aSyscallWhitelist)
{
- return UniquePtr<sandbox::bpf_dsl::Policy>(new ContentSandboxPolicy(aMaybeBroker));
+ return MakeUnique<ContentSandboxPolicy>(aMaybeBroker, aSyscallWhitelist);
}
#endif // MOZ_CONTENT_SANDBOX
#ifdef MOZ_GMP_SANDBOX
// Unlike for content, the GeckoMediaPlugin seccomp-bpf policy needs
// to be an effective sandbox by itself, because we allow GMP on Linux
// systems where that's the only sandboxing mechanism we can use.
--- a/security/sandbox/linux/SandboxFilter.h
+++ b/security/sandbox/linux/SandboxFilter.h
@@ -2,31 +2,33 @@
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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_SandboxFilter_h
#define mozilla_SandboxFilter_h
+#include <vector>
#include "mozilla/Atomics.h"
#include "mozilla/UniquePtr.h"
namespace sandbox {
namespace bpf_dsl {
class Policy;
}
}
namespace mozilla {
#ifdef MOZ_CONTENT_SANDBOX
class SandboxBrokerClient;
-UniquePtr<sandbox::bpf_dsl::Policy> GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker);
+UniquePtr<sandbox::bpf_dsl::Policy> GetContentSandboxPolicy(SandboxBrokerClient* aMaybeBroker,
+ const std::vector<int>& aSyscallWhitelist);
#endif
#ifdef MOZ_GMP_SANDBOX
struct SandboxOpenedFile {
const char *mPath;
Atomic<int> mFd;
};
--- a/security/sandbox/linux/broker/SandboxBroker.cpp
+++ b/security/sandbox/linux/broker/SandboxBroker.cpp
@@ -206,32 +206,46 @@ SandboxBroker::Policy::AddDir(int aPerms
}
nsDependentCString path(aPath);
MOZ_ASSERT(path.Length() <= kMaxPathLen - 1);
// Enforce trailing / on aPath
if (path[path.Length() - 1] != '/') {
path.Append('/');
}
+
+ Policy::AddPrefixInternal(aPerms, path);
+}
+
+void
+SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath)
+{
+ Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath));
+}
+
+void
+SandboxBroker::Policy::AddPrefixInternal(int aPerms, const nsACString& aPath)
+{
int origPerms;
- if (!mMap.Get(path, &origPerms)) {
+ if (!mMap.Get(aPath, &origPerms)) {
origPerms = MAY_ACCESS;
} else {
MOZ_ASSERT(origPerms & MAY_ACCESS);
}
int newPerms = origPerms | aPerms | RECURSIVE;
if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
- SANDBOX_LOG_ERROR("policy for %s: %d -> %d", aPath, origPerms, newPerms);
+ SANDBOX_LOG_ERROR("policy for %s: %d -> %d", PromiseFlatCString(aPath).get(),
+ origPerms, newPerms);
}
- mMap.Put(path, newPerms);
+ mMap.Put(aPath, newPerms);
}
void
-SandboxBroker::Policy::AddPrefix(int aPerms, const char* aDir,
- const char* aPrefix)
+SandboxBroker::Policy::AddFilePrefix(int aPerms, const char* aDir,
+ const char* aPrefix)
{
size_t prefixLen = strlen(aPrefix);
DIR* dirp = opendir(aDir);
struct dirent* de;
if (!dirp) {
return;
}
while ((de = readdir(dirp))) {
@@ -241,16 +255,35 @@ SandboxBroker::Policy::AddPrefix(int aPe
subPath.Append('/');
subPath.Append(de->d_name);
AddPath(aPerms, subPath.get(), AddAlways);
}
}
closedir(dirp);
}
+void
+SandboxBroker::Policy::AddDynamic(int aPerms, const char* aPath)
+{
+ struct stat statBuf;
+ bool exists = (stat(aPath, &statBuf) == 0);
+
+ if (!exists) {
+ AddPrefix(aPerms, aPath);
+ } else {
+ size_t len = strlen(aPath);
+ if (!len) return;
+ if (aPath[len - 1] == '/') {
+ AddDir(aPerms, aPath);
+ } else {
+ AddPath(aPerms, aPath);
+ }
+ }
+}
+
int
SandboxBroker::Policy::Lookup(const nsACString& aPath) const
{
// Early exit for paths explicitly found in the
// whitelist.
// This means they will not gain extra permissions
// from recursive paths.
int perms = mMap.Get(aPath);
--- a/security/sandbox/linux/broker/SandboxBroker.h
+++ b/security/sandbox/linux/broker/SandboxBroker.h
@@ -75,17 +75,22 @@ class SandboxBroker final
void AddPath(int aPerms, const char* aPath, AddCondition aCond);
// This adds all regular files (not directories) in the tree
// rooted at the given path.
void AddTree(int aPerms, const char* aPath);
// A directory, and all files and directories under it, even those
// added after creation (the dir itself must exist).
void AddDir(int aPerms, const char* aPath);
// All files in a directory with a given prefix; useful for devices.
- void AddPrefix(int aPerms, const char* aDir, const char* aPrefix);
+ void AddFilePrefix(int aPerms, const char* aDir, const char* aPrefix);
+ // Everything starting with the given path, even those files/dirs
+ // added after creation. The file or directory may or may not exist.
+ void AddPrefix(int aPerms, const char* aPath);
+ // Adds a file or dir (end with /) if it exists, and a prefix otherwhise.
+ void AddDynamic(int aPerms, const char* aPath);
// Default: add file if it exists when creating policy or if we're
// conferring permission to create it (log files, etc.).
void AddPath(int aPerms, const char* aPath) {
AddPath(aPerms, aPath,
(aPerms & MAY_CREATE) ? AddAlways : AddIfExistsNow);
}
int Lookup(const nsACString& aPath) const;
int Lookup(const char* aPath) const {
@@ -93,16 +98,17 @@ class SandboxBroker final
}
private:
// ValidatePath checks |path| and returns true if these conditions are met
// * Greater than 0 length
// * Is an absolute path
// * No trailing slash
// * No /../ path traversal
bool ValidatePath(const char* path) const;
+ void AddPrefixInternal(int aPerms, const nsACString& aPath);
};
// Constructing a broker involves creating a socketpair and a
// background thread to handle requests, so it can fail. If this
// returns nullptr, do not use the value of aClientFdOut.
static UniquePtr<SandboxBroker>
Create(UniquePtr<const Policy> aPolicy, int aChildPid,
ipc::FileDescriptor& aClientFdOut);
--- a/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+++ b/security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
@@ -1,16 +1,17 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "SandboxBrokerPolicyFactory.h"
#include "SandboxInfo.h"
+#include "SandboxLogging.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Preferences.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "SpecialSystemDirectory.h"
@@ -62,17 +63,17 @@ SandboxBrokerPolicyFactory::SandboxBroke
// Devices that need write access:
policy->AddPath(rdwr, "/dev/genlock"); // bug 980924
policy->AddPath(rdwr, "/dev/ashmem"); // bug 980947
policy->AddTree(wronly, "/dev/log"); // bug 1199857
// Graphics devices are a significant source of attack surface, but
// there's not much we can do about it without proxying (which is
// very difficult and a perforamnce hit).
- policy->AddPrefix(rdwr, "/dev", "kgsl"); // bug 995072
+ policy->AddFilePrefix(rdwr, "/dev", "kgsl"); // bug 995072
policy->AddPath(rdwr, "/dev/qemu_pipe"); // but 1198410: goldfish gralloc.
// Bug 1198475: mochitest logs. (This is actually passed in via URL
// query param to the mochitest page, and is configurable, so this
// isn't enough in general, but hopefully it's good enough for B2G.)
// Conditional on tests being run, using the same check seen in
// DirectoryProvider.js to set ProfD.
if (access("/data/local/tests/profile", R_OK) == 0) {
@@ -133,19 +134,19 @@ SandboxBrokerPolicyFactory::SandboxBroke
}
}
// If the above fails at any point, fall back to a very good guess.
if (NS_FAILED(rv)) {
policy->AddDir(rdwrcr, "/tmp");
}
// Bug 1308851: NVIDIA proprietary driver when using WebGL
- policy->AddPrefix(rdwr, "/dev", "nvidia");
+ policy->AddFilePrefix(rdwr, "/dev", "nvidia");
- // Bug 1312678: radeonsi/Intel with DRI when using WebGL
+ // Bug 1312678: radeonsi/Intel with DRI when using WebGL
policy->AddDir(rdwr, "/dev/dri");
#ifdef MOZ_ALSA
// Bug 1309098: ALSA support
policy->AddDir(rdwr, "/dev/snd");
#endif
mCommonContentPolicy.reset(policy);
@@ -185,15 +186,29 @@ SandboxBrokerPolicyFactory::GetContentPo
// 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));
+
+ // 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 =
+ Preferences::GetCString("security.sandbox.content.write_path_whitelist");
+ if (extraPathString) {
+ for (const nsCSubstring& path : extraPathString.Split(',')) {
+ nsCString trimPath(path);
+ trimPath.Trim(" ", true, true);
+ policy->AddDynamic(rdwr, trimPath.get());
+ }
+ }
+
// Return the common policy.
return policy;
#endif
}
#endif // MOZ_CONTENT_SANDBOX
} // namespace mozilla