CVE-2017-6200: Exposure of Sensitive Information to an Unauthorized Actor (CWE-200)
value: Sandstorm before build 0.203 allows remote attackers to read any specified file under /etc or /run via the sandbox backup function. The root cause is that the findFilesToZip function doesn't filter Line Feed (\n) characters in a directory name.
CWE description: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVSS v3 base score: 6.5
Published: 2018-02-06T16:29Z
Repository: https://github.com/sandstorm-io/sandstorm
Language: Other
Files changed: src/sandstorm/backup.c++
Commit date: 2017-03-01 14:23:15 -0800
Commit message: Improve security of zip/unzip sandbox by switching the sandboxed filesystem from blacklisting directories to whitelisting them.

Diff: diff --git a/src/sandstorm/backup.c++ b/src/sandstorm/backup.c++
index 5d836611b..16d3cf455 100644
--- a/src/sandstorm/backup.c++
+++ b/src/sandstorm/backup.c++
@@ -142,25 +142,26 @@ bool BackupMain::run(kj::StringPtr grainDir) {
   // To really unshare the mount namespace, we also have to make sure all mounts are private.
   // The parameters here were derived by strace'ing `mount --make-rprivate /`.  AFAICT the flags
   // are undocumented.  :(
   KJ_SYSCALL(mount("none", "/", nullptr, MS_REC | MS_PRIVATE, nullptr));
 
-  // Mount root read-only.
-  bind(kj::str(root, "/"), "/tmp", MS_BIND | MS_NOSUID | MS_RDONLY);
-
-  if (access("/tmp/dev/null", F_OK) != 0) {
-    // Looks like we need to bind in /dev.
-    KJ_SYSCALL(mount("/dev", "/tmp/dev", nullptr, MS_BIND, nullptr));
+  // Create tmpfs root to whitelist directories that we want to bind in.
+  KJ_SYSCALL(mount("tmpfs", "/tmp", "tmpfs", 0, "size=8m,nr_inodes=128,mode=755"));
+
+  // Bind in whitelisted directories.
+  const char* WHITELIST[] = { "dev", "bin", "lib", "lib64", "usr" };
+  for (const char* dir: WHITELIST) {
+    auto src = kj::str(root, "/", dir);
+    auto dst = kj::str("/tmp/", dir);
+    if (access(src.cStr(), F_OK) == 0) {
+      KJ_SYSCALL(mkdir(dst.cStr(), 0755));
+      bind(src, dst, MS_BIND | MS_NOSUID | MS_RDONLY);
+    }
   }
 
-  // Hide sensitive directories.
-  KJ_SYSCALL(mount("tmpfs", "/tmp/proc", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));
-  KJ_SYSCALL(mount("tmpfs", "/tmp/var", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));
-  KJ_SYSCALL(mount("tmpfs", "/tmp/etc", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));
-
-  // Mount inner tmpfs.
-  KJ_SYSCALL(mount("tmpfs", "/tmp/tmp", "tmpfs", 0, "size=8m,nr_inodes=128,mode=777"));
+  // Make sandboxed /tmp.
+  KJ_SYSCALL(mkdir("/tmp/tmp", 0777));
 
   // Bind in the grain's `data` (=`sandbox`).
   KJ_SYSCALL(mkdir("/tmp/tmp/data", 0777));
   bind(kj::str(grainDir, "/sandbox"), "/tmp/tmp/data",
        MS_NODEV | MS_NOSUID | MS_NOEXEC | (restore ? 0 : MS_RDONLY));

Vulnerable code:   // Mount root read-only.
  bind(kj::str(root, "/"), "/tmp", MS_BIND | MS_NOSUID | MS_RDONLY);

  if (access("/tmp/dev/null", F_OK) != 0) {
    // Looks like we need to bind in /dev.
    KJ_SYSCALL(mount("/dev", "/tmp/dev", nullptr, MS_BIND, nullptr));
  // Hide sensitive directories.
  KJ_SYSCALL(mount("tmpfs", "/tmp/proc", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));
  KJ_SYSCALL(mount("tmpfs", "/tmp/var", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));
  KJ_SYSCALL(mount("tmpfs", "/tmp/etc", "tmpfs", 0, "size=32k,nr_inodes=8,mode=000"));

  // Mount inner tmpfs.
  KJ_SYSCALL(mount("tmpfs", "/tmp/tmp", "tmpfs", 0, "size=8m,nr_inodes=128,mode=777"));
Fixed code:   // Create tmpfs root to whitelist directories that we want to bind in.
  KJ_SYSCALL(mount("tmpfs", "/tmp", "tmpfs", 0, "size=8m,nr_inodes=128,mode=755"));

  // Bind in whitelisted directories.
  const char* WHITELIST[] = { "dev", "bin", "lib", "lib64", "usr" };
  for (const char* dir: WHITELIST) {
    auto src = kj::str(root, "/", dir);
    auto dst = kj::str("/tmp/", dir);
    if (access(src.cStr(), F_OK) == 0) {
      KJ_SYSCALL(mkdir(dst.cStr(), 0755));
      bind(src, dst, MS_BIND | MS_NOSUID | MS_RDONLY);
    }
  // Make sandboxed /tmp.
  KJ_SYSCALL(mkdir("/tmp/tmp", 0777));