Bug 1580271: enhance sandbox on OpenBSD with unveil() r=gcp
authorjoshua stein <jcs@jcs.org>
Fri, 08 Nov 2019 07:31:09 +0000
changeset 501231 faf2b623b315b7faf436ae69b9464f286f2ddd24
parent 501230 8e2be8ec03fc1f6eab748d35240c41c1656724f8
child 501232 0e0f33fd72b8334cfdde8b4dfdb3f237a60a43a7
push id100077
push usergpascutto@mozilla.com
push dateFri, 08 Nov 2019 07:42:46 +0000
treeherderautoland@0e0f33fd72b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp
bugs1580271
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1580271: enhance sandbox on OpenBSD with unveil() r=gcp ExpandUnveilPath() takes care of expanding potentially environment-specific XDG_DATA/CONFIG/CACHE_HOME dirs. The unveil config files lists the allowed paths & modes. 'disable' in the files will disable the corresponding pledge/unveil syscall. Differential Revision: https://phabricator.services.mozilla.com/D51387
dom/ipc/ContentChild.cpp
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -133,16 +133,17 @@
 #  elif defined(XP_MACOSX)
 #    include "mozilla/Sandbox.h"
 #  elif defined(__OpenBSD__)
 #    include <unistd.h>
 #    include <sys/stat.h>
 #    include <err.h>
 #    include <fstream>
 #    include "nsILineInputStream.h"
+#    include "SpecialSystemDirectory.h"
 #  endif
 #  if defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
 #    include "mozilla/SandboxTestingChild.h"
 #  endif
 #endif
 
 #include "mozilla/Unused.h"
 
@@ -4130,26 +4131,45 @@ mozilla::ipc::IPCResult ContentChild::Re
 
 }  // namespace dom
 
 #if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
 
 static LazyLogModule sPledgeLog("OpenBSDSandbox");
 
 NS_IMETHODIMP
+OpenBSDFindPledgeUnveilFilePath(const char* file, nsACString& result) {
+  struct stat st;
+
+  // Allow overriding files in /etc/$MOZ_APP_NAME
+  result.Assign(nsPrintfCString("/etc/%s/%s", MOZ_APP_NAME, file));
+  if (stat(PromiseFlatCString(result).get(), &st) == 0) {
+    return NS_OK;
+  }
+
+  // Or look in the system default directory
+  result.Assign(nsPrintfCString(
+      "/usr/local/lib/%s/browser/defaults/preferences/%s", MOZ_APP_NAME, file));
+  if (stat(PromiseFlatCString(result).get(), &st) == 0) {
+    return NS_OK;
+  }
+
+  errx(1, "can't locate %s", file);
+}
+
+NS_IMETHODIMP
 OpenBSDPledgePromises(const nsACString& aPath) {
   // Using NS_LOCAL_FILE_CONTRACTID/NS_LOCALFILEINPUTSTREAM_CONTRACTID requires
   // a lot of setup before they are supported and we want to pledge early on
   // before all of that, so read the file directly
   std::ifstream input(PromiseFlatCString(aPath).get());
 
   // Build up one line of pledge promises without comments
   nsAutoCString promises;
   bool disabled = false;
-
   int linenum = 0;
   for (std::string tLine; std::getline(input, tLine);) {
     nsAutoCString line(tLine.c_str());
     linenum++;
 
     // Cut off any comments at the end of the line, also catches lines
     // that are entirely a comment
     int32_t hash = line.FindChar('#');
@@ -4183,57 +4203,171 @@ OpenBSDPledgePromises(const nsACString& 
       err(1, "%s: pledge(%s) failed", PromiseFlatCString(aPath).get(),
           promises.get());
     }
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-OpenBSDFindPledgeFilePath(const char* file, nsACString& result) {
-  struct stat st;
-
-  // Allow overriding files in /etc/$MOZ_APP_NAME
-  result.Assign(nsPrintfCString("/etc/%s/%s", MOZ_APP_NAME, file));
-  if (stat(PromiseFlatCString(result).get(), &st) == 0) {
-    return NS_OK;
+void ExpandUnveilPath(nsAutoCString& path) {
+  // Expand $XDG_CONFIG_HOME to the environment variable, or ~/.config
+  nsCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME"));
+  if (xdgConfigHome.IsEmpty()) {
+    xdgConfigHome = "~/.config";
+  }
+  path.ReplaceSubstring("$XDG_CONFIG_HOME", xdgConfigHome.get());
+
+  // Expand $XDG_CACHE_HOME to the environment variable, or ~/.cache
+  nsCString xdgCacheHome(PR_GetEnv("XDG_CACHE_HOME"));
+  if (xdgCacheHome.IsEmpty()) {
+    xdgCacheHome = "~/.cache";
+  }
+  path.ReplaceSubstring("$XDG_CACHE_HOME", xdgCacheHome.get());
+
+  // Expand $XDG_DATA_HOME to the environment variable, or ~/.local/share
+  nsCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME"));
+  if (xdgDataHome.IsEmpty()) {
+    xdgDataHome = "~/.local/share";
+  }
+  path.ReplaceSubstring("$XDG_DATA_HOME", xdgDataHome.get());
+
+  // Expand leading ~ to the user's home directory
+  nsCOMPtr<nsIFile> homeDir;
+  nsresult rv =
+      GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir));
+  if (NS_FAILED(rv)) {
+    errx(1, "failed getting home directory");
+  }
+  if (path.FindChar('~') == 0) {
+    nsCString tHome(homeDir->NativePath());
+    tHome.Append(Substring(path, 1, path.Length() - 1));
+    path = tHome.get();
+  }
+}
+
+void MkdirP(nsAutoCString& path) {
+  // nsLocalFile::CreateAllAncestors would be nice to use
+
+  nsAutoCString tPath("");
+  for (const nsACString& dir : path.Split('/')) {
+    struct stat st;
+
+    if (dir.IsEmpty()) {
+      continue;
+    }
+
+    tPath.Append("/");
+    tPath.Append(dir);
+
+    if (stat(tPath.get(), &st) == -1) {
+      if (mkdir(tPath.get(), 0700) == -1) {
+        err(1, "failed mkdir(%s) while MkdirP(%s)",
+            PromiseFlatCString(tPath).get(), PromiseFlatCString(path).get());
+      }
+    }
   }
-
-  // Or look in the system default directory
-  result.Assign(nsPrintfCString(
-      "/usr/local/lib/%s/browser/defaults/preferences/%s", MOZ_APP_NAME, file));
-  if (stat(PromiseFlatCString(result).get(), &st) == 0) {
-    return NS_OK;
+}
+
+NS_IMETHODIMP
+OpenBSDUnveilPaths(const nsACString& uPath, const nsACString& pledgePath) {
+  // Using NS_LOCAL_FILE_CONTRACTID/NS_LOCALFILEINPUTSTREAM_CONTRACTID requires
+  // a lot of setup before they are allowed/supported and we want to pledge and
+  // unveil early on before all of that is setup
+  std::ifstream input(PromiseFlatCString(uPath).get());
+
+  bool disabled = false;
+  int linenum = 0;
+  for (std::string tLine; std::getline(input, tLine);) {
+    nsAutoCString line(tLine.c_str());
+    linenum++;
+
+    // Cut off any comments at the end of the line, also catches lines
+    // that are entirely a comment
+    int32_t hash = line.FindChar('#');
+    if (hash >= 0) {
+      line = Substring(line, 0, hash);
+    }
+    line.CompressWhitespace(true, true);
+    if (line.IsEmpty()) {
+      continue;
+    }
+
+    if (linenum == 1 && line.EqualsLiteral("disable")) {
+      disabled = true;
+      break;
+    }
+
+    int32_t space = line.FindChar(' ');
+    if (space <= 0) {
+      errx(1, "%s: line %d: invalid format", PromiseFlatCString(uPath).get(),
+           linenum);
+    }
+
+    nsAutoCString uPath(Substring(line, 0, space));
+    ExpandUnveilPath(uPath);
+
+    nsAutoCString perms(Substring(line, space + 1, line.Length() - space - 1));
+
+    MOZ_LOG(sPledgeLog, LogLevel::Debug,
+            ("%s: unveil(%s, %s)\n", PromiseFlatCString(uPath).get(),
+             uPath.get(), perms.get()));
+    if (unveil(uPath.get(), perms.get()) == -1 && errno != ENOENT) {
+      err(1, "%s: unveil(%s, %s) failed", PromiseFlatCString(uPath).get(),
+          uPath.get(), perms.get());
+    }
   }
-
-  errx(1, "can't locate %s", file);
+  input.close();
+
+  if (disabled) {
+    warnx("%s: disabled", PromiseFlatCString(uPath).get());
+  } else {
+    if (unveil(PromiseFlatCString(pledgePath).get(), "r") == -1) {
+      err(1, "unveil(%s, r) failed", PromiseFlatCString(pledgePath).get());
+    }
+  }
+
+  return NS_OK;
 }
 
 bool StartOpenBSDSandbox(GeckoProcessType type) {
   nsAutoCString pledgeFile;
+  nsAutoCString unveilFile;
 
   switch (type) {
-    case GeckoProcessType_Default:
-      OpenBSDFindPledgeFilePath("pledge.main", pledgeFile);
+    case GeckoProcessType_Default: {
+      OpenBSDFindPledgeUnveilFilePath("pledge.main", pledgeFile);
+      OpenBSDFindPledgeUnveilFilePath("unveil.main", unveilFile);
+
+      // Ensure dconf dir exists before we veil the filesystem
+      nsAutoCString dConf("$XDG_CACHE_HOME/dconf");
+      ExpandUnveilPath(dConf);
+      MkdirP(dConf);
       break;
+    }
 
     case GeckoProcessType_Content:
-      OpenBSDFindPledgeFilePath("pledge.content", pledgeFile);
+      OpenBSDFindPledgeUnveilFilePath("pledge.content", pledgeFile);
+      OpenBSDFindPledgeUnveilFilePath("unveil.content", unveilFile);
       break;
 
     case GeckoProcessType_GPU:
-      pledgeFile.Append("pledge.gpu");
+      OpenBSDFindPledgeUnveilFilePath("pledge.gpu", pledgeFile);
+      OpenBSDFindPledgeUnveilFilePath("unveil.gpu", unveilFile);
       break;
 
     default:
       MOZ_ASSERT(false, "unknown process type");
       return false;
   }
 
+  if (NS_WARN_IF(NS_FAILED(OpenBSDUnveilPaths(unveilFile, pledgeFile)))) {
+    errx(1, "failed reading/parsing %s", unveilFile.get());
+  }
+
   if (NS_WARN_IF(NS_FAILED(OpenBSDPledgePromises(pledgeFile)))) {
     errx(1, "failed reading/parsing %s", pledgeFile.get());
   }
 
   // Don't overwrite an existing session dbus address, but ensure it is set
   if (!PR_GetEnv("DBUS_SESSION_BUS_ADDRESS")) {
     PR_SetEnv("DBUS_SESSION_BUS_ADDRESS=");
   }