Bug 1330326 - Make sandboxing policy more configurable via preferences. r?jld draft
authorGian-Carlo Pascutto <gcp@mozilla.com>
Thu, 26 Jan 2017 19:59:50 +0100
changeset 467946 058d894f8fd021a9643d0fdec89ce8e84fd458a7
parent 467945 e39f37936f5b07411921b9875a85fd55241847be
child 543807 dfc527b7a72f566de7fda662e808329c8bb69973
push id43309
push usergpascutto@mozilla.com
push dateMon, 30 Jan 2017 11:35:32 +0000
reviewersjld
bugs1330326
milestone54.0a1
Bug 1330326 - Make sandboxing policy more configurable via preferences. r?jld MozReview-Commit-ID: 9P0bSLLKRWp
browser/app/profile/firefox.js
dom/ipc/ContentChild.cpp
security/sandbox/linux/Sandbox.cpp
security/sandbox/linux/Sandbox.h
security/sandbox/linux/SandboxFilter.cpp
security/sandbox/linux/SandboxFilter.h
security/sandbox/linux/broker/SandboxBroker.cpp
security/sandbox/linux/broker/SandboxBroker.h
security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
--- 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