Introduce two new options to Linux sandbox wrapper:
* -n: Create a new network namespace with only loopback interface.
* -r: set the uid/gid inside the sandbox to be root (instead of nobody)
   so that setuid programs like ping can still run when needed.

--
Change-Id: I8ab434e47e0f6933ee9de02e135c8daec39fe73f
Reviewed-on: https://bazel-review.googlesource.com/#/c/2101/
MOS_MIGRATED_REVID=104858163
diff --git a/scripts/bootstrap/compile.sh b/scripts/bootstrap/compile.sh
index 523dab5..f9605b5 100755
--- a/scripts/bootstrap/compile.sh
+++ b/scripts/bootstrap/compile.sh
@@ -332,7 +332,7 @@
 
 log "Compiling namespace-sandbox..."
 if [[ $PLATFORM == "linux" ]]; then
-  run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox.c src/main/tools/process-tools.c -lm
+  run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox.c src/main/tools/network-tools.c src/main/tools/process-tools.c -lm
 else
   run_silent "${CC}" -o ${OUTPUT_DIR}/namespace-sandbox -std=c99 src/main/tools/namespace-sandbox-dummy.c -lm
 fi
diff --git a/src/main/tools/BUILD b/src/main/tools/BUILD
index 67b3888..4a87f65 100644
--- a/src/main/tools/BUILD
+++ b/src/main/tools/BUILD
@@ -1,6 +1,14 @@
 package(default_visibility = ["//src:__subpackages__"])
 
 cc_library(
+    name = "network-tools",
+    srcs = ["network-tools.c"],
+    hdrs = ["network-tools.h"],
+    copts = ["-std=c99"],
+    deps = [":process-tools"],
+)
+
+cc_library(
     name = "process-tools",
     srcs = ["process-tools.c"],
     hdrs = ["process-tools.h"],
@@ -29,7 +37,14 @@
     }),
     copts = ["-std=c99"],
     linkopts = ["-lm"],
-    deps = [":process-tools"],
+    deps = select({
+        "//src:darwin": [],
+        "//src:freebsd": [],
+        "//conditions:default": [
+            ":process-tools",
+            ":network-tools",
+        ],
+    }),
 )
 
 filegroup(
diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c
index 4380e93..ef13213 100644
--- a/src/main/tools/namespace-sandbox.c
+++ b/src/main/tools/namespace-sandbox.c
@@ -32,6 +32,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include "network-tools.h"
 #include "process-tools.h"
 
 #define PRINT_DEBUG(...)                                        \
@@ -65,6 +66,8 @@
   int num_mounts;            // How many mounts were specified
   char **create_dirs;        // empty dirs to create (-d)
   int num_create_dirs;       // How many empty dirs to create were specified
+  int fake_root;             // Pretend to be root inside the namespace.
+  int create_netns;          // If 1, create a new network namespace.
 };
 
 // Child function used by CheckNamespacesSupported() in call to clone().
@@ -92,7 +95,7 @@
   // spend time sleeping and retrying here until it eventually works (or not).
   CHECK_CALL(pid = clone(CheckNamespacesSupportedChild, stackTop,
                          CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS |
-                             CLONE_NEWIPC | SIGCHLD,
+                             CLONE_NEWIPC | CLONE_NEWNET | SIGCHLD,
                          NULL));
   CHECK_CALL(waitpid(pid, NULL, 0));
 
@@ -134,9 +137,12 @@
       "    The -M option specifies which directory to mount, the -m option "
       "specifies where to\n"
       "    mount it in the sandbox.\n"
+      "  -n if set, a new network namespace will be created\n"
+      "  -r if set, make the uid/gid be root, otherwise use nobody\n"
       "  -D  if set, debug info will be printed\n"
       "  -l <file>  redirect stdout to a file\n"
-      "  -L <file>  redirect stderr to a file\n");
+      "  -L <file>  redirect stderr to a file\n"
+      );
   exit(EXIT_FAILURE);
 }
 
@@ -147,7 +153,7 @@
   extern int optind, optopt;
   int c;
 
-  while ((c = getopt(argc, argv, ":CDS:W:t:T:d:M:m:l:L:")) != -1) {
+  while ((c = getopt(argc, argv, ":CDd:l:L:m:M:nrt:T:S:W:")) != -1) {
     switch (c) {
       case 'C':
         // Shortcut for the "does this system support sandboxing" check.
@@ -222,6 +228,12 @@
         }
         opt->mount_targets[opt->num_mounts++] = optarg;
         break;
+      case 'n':
+        opt->create_netns = 1;
+        break;
+      case 'r':
+        opt->fake_root = 1;
+        break;
       case 'D':
         global_debug = true;
         break;
@@ -268,7 +280,7 @@
   }
 }
 
-static void CreateNamespaces() {
+static void CreateNamespaces(int create_netns) {
   // This weird workaround is necessary due to unshare seldomly failing with
   // EINVAL due to a race condition in the Linux kernel (see
   // https://lkml.org/lkml/2015/7/28/833). An alternative would be to use
@@ -277,7 +289,8 @@
   int tries = 0;
   const int max_tries = 100;
   while (tries++ < max_tries) {
-    if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC) ==
+    if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
+                (create_netns ? CLONE_NEWNET : 0)) ==
         0) {
       PRINT_DEBUG("unshare succeeded after %d tries\n", tries);
       return;
@@ -455,7 +468,7 @@
   return r;
 }
 
-static void SetupUserNamespace(int uid, int gid) {
+static void SetupUserNamespace(int uid, int gid, int new_uid, int new_gid) {
   // Disable needs for CAP_SETGID
   int r = WriteFile("/proc/self/setgroups", "deny");
   if (r < 0 && errno != ENOENT) {
@@ -471,11 +484,11 @@
   // We can't be root in the child, because some code may assume that running as
   // root grants it certain capabilities that it doesn't in fact have. It's
   // safer to let the child think that it is just a normal user.
-  CHECK_CALL(WriteFile("/proc/self/uid_map", "%d %d 1\n", kNobodyUid, uid));
-  CHECK_CALL(WriteFile("/proc/self/gid_map", "%d %d 1\n", kNobodyGid, gid));
+  CHECK_CALL(WriteFile("/proc/self/uid_map", "%d %d 1\n", new_uid, uid));
+  CHECK_CALL(WriteFile("/proc/self/gid_map", "%d %d 1\n", new_gid, gid));
 
-  CHECK_CALL(setresuid(kNobodyUid, kNobodyUid, kNobodyUid));
-  CHECK_CALL(setresgid(kNobodyGid, kNobodyGid, kNobodyGid));
+  CHECK_CALL(setresuid(new_uid, new_uid, new_uid));
+  CHECK_CALL(setresgid(new_gid, new_gid, new_gid));
 }
 
 static void ChangeRoot(struct Options *opt) {
@@ -588,14 +601,23 @@
   PRINT_DEBUG("working dir is %s\n",
               (opt.working_dir != NULL) ? opt.working_dir : "/ (default)");
 
-  CreateNamespaces();
+  CreateNamespaces(opt.create_netns);
+  if (opt.create_netns) {
+    // Enable the loopback interface because some application may want
+    // to use it.
+    BringupInterface("lo");
+  }
 
   // Make our mount namespace private, so that further mounts do not affect the
   // outside environment.
   CHECK_CALL(mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL));
 
   SetupDirectories(&opt);
-  SetupUserNamespace(uid, gid);
+  if (opt.fake_root) {
+    SetupUserNamespace(uid, gid, 0, 0);
+  } else {
+    SetupUserNamespace(uid, gid, kNobodyUid, kNobodyGid);
+  }
   ChangeRoot(&opt);
 
   SpawnCommand(opt.args, opt.timeout_secs);
diff --git a/src/main/tools/network-tools.c b/src/main/tools/network-tools.c
new file mode 100644
index 0000000..6e088d4
--- /dev/null
+++ b/src/main/tools/network-tools.c
@@ -0,0 +1,46 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#define _GNU_SOURCE
+
+#include <net/if.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "process-tools.h"
+#include "network-tools.h"
+
+void BringupInterface(const char *name) {
+  int fd;
+
+  struct ifreq ifr;
+
+  CHECK_CALL(fd = socket(AF_INET, SOCK_DGRAM, 0));
+
+  memset(&ifr, 0, sizeof(ifr));
+  strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+
+  CHECK_CALL(ioctl(fd, SIOCGIFINDEX, &ifr));
+
+  // Enable the interface
+  ifr.ifr_flags |= IFF_UP;
+  CHECK_CALL(ioctl(fd, SIOCSIFFLAGS, &ifr));
+
+  CHECK_CALL(close(fd));
+}
diff --git a/src/main/tools/network-tools.h b/src/main/tools/network-tools.h
new file mode 100644
index 0000000..9c90aab
--- /dev/null
+++ b/src/main/tools/network-tools.h
@@ -0,0 +1,21 @@
+// Copyright 2015 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NETWORK_TOOLS_H__
+#define NETWORK_TOOLS_H__
+
+// Bring up the given network interface like "lo".
+void BringupInterface(const char *name);
+
+#endif  // NETWORK_TOOLS_H__
diff --git a/src/test/shell/bazel/namespace-runner_test.sh b/src/test/shell/bazel/namespace-runner_test.sh
index 4e8f91a..2b52012 100755
--- a/src/test/shell/bazel/namespace-runner_test.sh
+++ b/src/test/shell/bazel/namespace-runner_test.sh
@@ -59,7 +59,7 @@
 #define _GNU_SOURCE
 #include <sched.h>
 int main() {
-  return unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER);
+  return unshare(CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWUSER | CLONE_NEWNET);
 }
 EOF
   cat <<'EOF' >test/BUILD
@@ -93,6 +93,26 @@
   assert_output "hi there" ""
 }
 
+function test_default_user_is_nobody() {
+  $WRAPPER $WRAPPER_DEFAULT_OPTS -l $OUT -L $ERR -- /usr/bin/id || fail
+  assert_output "uid=65534 gid=65534 groups=65534" ""
+}
+
+function test_user_switched_to_root() {
+  $WRAPPER $WRAPPER_DEFAULT_OPTS -r -l $OUT -L $ERR -- /usr/bin/id || fail
+  assert_output "uid=0 gid=0 groups=65534,0" ""
+}
+
+function test_network_namespace() {
+  $WRAPPER $WRAPPER_DEFAULT_OPTS -n -l $OUT -L $ERR  -- /bin/ip link ls || fail
+  assert_contains "LOOPBACK,UP" "$OUT"
+}
+
+function test_ping_loopback() {
+  $WRAPPER $WRAPPER_DEFAULT_OPTS -n -r -l $OUT -L $ERR  -- /bin/ping -c 1 127.0.0.1 || fail
+  assert_contains "1 received" "$OUT"
+}
+
 function test_to_stderr() {
   $WRAPPER $WRAPPER_DEFAULT_OPTS -l $OUT -L $ERR -- /bin/bash -c "/bin/echo hi there >&2" || fail
   assert_output "" "hi there"