Instead of assuming that sandboxing will generally work on everything that is Linux, do real auto-detection whether it is supported on the host or not and enable / disable it based on the result.

The warning that is printed when the Linux kernel is too old to support sandboxing can be disabled via a flag.

--
MOS_MIGRATED_REVID=101461120
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index 7bf45c4..66275dc 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -208,6 +208,10 @@
     this.runtime = runtime;
     this.request = request;
 
+    // Create tools before getting the strategies from the modules as some of them need tools to
+    // determine whether the host actually supports certain strategies (e.g. sandboxing).
+    createToolsSymlinks();
+
     this.actionContextProviders =
         getActionContextProvidersFromModules(
             new FilesetActionContextImpl.Provider(
@@ -314,7 +318,6 @@
   }
 
   void init() throws ExecutorInitException {
-    createToolsSymlinks();
     getExecutor();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
index febc5d7..53b707f 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD
@@ -13,6 +13,7 @@
         "//src/main/java:buildtool-runtime",
         "//src/main/java:common",
         "//src/main/java:events",
+        "//src/main/java:options",
         "//src/main/java:packages",
         "//src/main/java:shell",
         "//src/main/java:unix",
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
index b68590c..6866312 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
@@ -18,10 +18,13 @@
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.io.Files;
 import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.analysis.config.BinTools;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.shell.Command;
 import com.google.devtools.build.lib.shell.CommandException;
 import com.google.devtools.build.lib.unix.FilesystemUtils;
 import com.google.devtools.build.lib.util.io.FileOutErr;
+import com.google.devtools.build.lib.util.io.OutErr;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -52,6 +55,33 @@
     this.debug = debug;
   }
 
+  static boolean isSupported(BlazeRuntime runtime) {
+    Path execRoot = runtime.getExecRoot();
+    OutErr outErr = runtime.getReporter().getOutErr();
+    BinTools binTools = runtime.getBinTools();
+
+    List<String> args = new ArrayList<>();
+    args.add(execRoot.getRelative(binTools.getExecPath("namespace-sandbox")).getPathString());
+    args.add("-C");
+
+    ImmutableMap<String, String> env = ImmutableMap.of();
+    File cwd = execRoot.getPathFile();
+
+    Command cmd = new Command(args.toArray(new String[0]), env, cwd);
+    try {
+      cmd.execute(
+          /* stdin */ new byte[] {},
+          Command.NO_OBSERVER,
+          outErr.getOutputStream(),
+          outErr.getErrorStream(),
+          /* killSubprocessOnInterrupt */ true);
+    } catch (CommandException e) {
+      return false;
+    }
+
+    return true;
+  }
+
   /**
    * Runs given
    *
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index bc8785b..0821786 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -20,9 +20,12 @@
 import com.google.devtools.build.lib.actions.ActionContextProvider;
 import com.google.devtools.build.lib.buildtool.BuildRequest;
 import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
+import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.runtime.BlazeModule;
 import com.google.devtools.build.lib.runtime.BlazeRuntime;
 import com.google.devtools.build.lib.runtime.Command;
+import com.google.devtools.build.lib.util.OS;
+import com.google.devtools.common.options.OptionsBase;
 
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -35,16 +38,58 @@
   private final ExecutorService backgroundWorkers = Executors.newCachedThreadPool();
   private BuildRequest buildRequest;
   private BlazeRuntime runtime;
+  private Boolean sandboxingSupported = null;
+
+  public static final String SANDBOX_NOT_SUPPORTED_MESSAGE =
+      "Sandboxed execution is not supported on your system and thus hermeticity of actions cannot "
+          + "be guaranteed. See http://bazel.io/docs/bazel-user-manual.html#sandboxing for more "
+          + "information";
 
   @Override
   public Iterable<ActionContextProvider> getActionContextProviders() {
-    return ImmutableList.<ActionContextProvider>of(
-        new SandboxActionContextProvider(runtime, buildRequest, backgroundWorkers));
+    Preconditions.checkNotNull(buildRequest);
+    Preconditions.checkNotNull(runtime);
+
+    // Cache
+    if (sandboxingSupported == null) {
+      sandboxingSupported = NamespaceSandboxRunner.isSupported(runtime);
+    }
+
+    if (sandboxingSupported) {
+      return ImmutableList.<ActionContextProvider>of(
+          new SandboxActionContextProvider(runtime, buildRequest, backgroundWorkers));
+    }
+
+    // For now, sandboxing is only supported on Linux and there's not much point in showing a scary
+    // warning to the user if they can't do anything about it.
+    if (!buildRequest.getOptions(SandboxOptions.class).ignoreUnsupportedSandboxing
+        && OS.getCurrent() == OS.LINUX) {
+      runtime.getReporter().handle(Event.warn(SANDBOX_NOT_SUPPORTED_MESSAGE));
+    }
+
+    return ImmutableList.of();
   }
 
   @Override
   public Iterable<ActionContextConsumer> getActionContextConsumers() {
-    return ImmutableList.<ActionContextConsumer>of(new SandboxActionContextConsumer());
+    Preconditions.checkNotNull(runtime);
+
+    if (sandboxingSupported == null) {
+      sandboxingSupported = NamespaceSandboxRunner.isSupported(runtime);
+    }
+
+    if (sandboxingSupported) {
+      return ImmutableList.<ActionContextConsumer>of(new SandboxActionContextConsumer());
+    }
+
+    return ImmutableList.of();
+  }
+
+  @Override
+  public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) {
+    return command.builds()
+        ? ImmutableList.<Class<? extends OptionsBase>>of(SandboxOptions.class)
+        : ImmutableList.<Class<? extends OptionsBase>>of();
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
new file mode 100644
index 0000000..ad4fa50
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
@@ -0,0 +1,31 @@
+// Copyright 2015 Google Inc. 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.
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.devtools.common.options.Option;
+import com.google.devtools.common.options.OptionsBase;
+
+/**
+ * Options for sandboxed execution.
+ */
+public class SandboxOptions extends OptionsBase {
+
+  @Option(
+    name = "ignore_unsupported_sandboxing",
+    defaultValue = "false",
+    category = "strategy",
+    help = "Do not print a warning when sandboxed execution is not supported on this system."
+  )
+  public boolean ignoreUnsupportedSandboxing;
+}
diff --git a/src/main/tools/namespace-sandbox-dummy.c b/src/main/tools/namespace-sandbox-dummy.c
index dd9f47f..3693c8d 100644
--- a/src/main/tools/namespace-sandbox-dummy.c
+++ b/src/main/tools/namespace-sandbox-dummy.c
@@ -19,6 +19,6 @@
 // platforms (if we didn't have this file, it would fail with a non-informative
 // message)
 
-int main() {
-  return 0;
+int main(int argc, char *argv[]) {
+  return 1;
 }
diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c
index 9b484d3..33cacb7 100644
--- a/src/main/tools/namespace-sandbox.c
+++ b/src/main/tools/namespace-sandbox.c
@@ -29,6 +29,7 @@
 #include <sys/stat.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
+#include <sys/wait.h>
 #include <unistd.h>
 
 #include "process-tools.h"
@@ -54,16 +55,48 @@
   double timeout_secs;     // How long to wait before killing the child (-T)
   double kill_delay_secs;  // How long to wait before sending SIGKILL in case of
                            // timeout (-t)
-  const char *stdout_path;       // Where to redirect stdout (-l)
-  const char *stderr_path;       // Where to redirect stderr (-L)
-  char *const *args;             // Command to run (--)
-  const char *sandbox_root;      // Sandbox root (-S)
-  const char *working_dir;       // Working directory (-W)
-  char **mount_sources;    // Map of directories to mount, from
-  char **mount_targets;    // sources -> targets (-m)
-  int num_mounts;          // How many mounts were specified
+  const char *stdout_path;   // Where to redirect stdout (-l)
+  const char *stderr_path;   // Where to redirect stderr (-L)
+  char *const *args;         // Command to run (--)
+  const char *sandbox_root;  // Sandbox root (-S)
+  const char *working_dir;   // Working directory (-W)
+  char **mount_sources;      // Map of directories to mount, from
+  char **mount_targets;      // sources -> targets (-m)
+  int num_mounts;            // How many mounts were specified
 };
 
+// Child function used by CheckNamespacesSupported() in call to clone().
+static int CheckNamespacesSupportedChild(void *arg) { return 0; }
+
+// Check whether the required namespaces are supported.
+static int CheckNamespacesSupported() {
+  const int stackSize = 1024 * 1024;
+  char *stack;
+  char *stackTop;
+  pid_t pid;
+
+  // Allocate stack for child.
+  stack = malloc(stackSize);
+  if (stack == NULL) {
+    DIE("malloc failed\n");
+  }
+
+  // Assume stack grows downward.
+  stackTop = stack + stackSize;
+
+  // Create child with own namespaces. We use clone() instead of unshare() here
+  // because of the kernel bug (ref. CreateNamespaces) that lets unshare fail
+  // sometimes. As this check has to run as fast as possible, we can't afford to
+  // 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,
+                         NULL));
+  CHECK_CALL(waitpid(pid, NULL, 0));
+
+  return EXIT_SUCCESS;
+}
+
 // Print out a usage error. argc and argv are the argument counter and vector,
 // fmt is a format,
 // string for the error message to print.
@@ -112,8 +145,12 @@
   extern int optind, optopt;
   int c;
 
-  while ((c = getopt(argc, argv, ":DS:W:t:T:M:m:l:L:")) != -1) {
+  while ((c = getopt(argc, argv, ":CDS:W:t:T:M:m:l:L:")) != -1) {
     switch (c) {
+      case 'C':
+        // Shortcut for the "does this system support sandboxing" check.
+        exit(CheckNamespacesSupported());
+        break;
       case 'S':
         if (opt->sandbox_root == NULL) {
           opt->sandbox_root = optarg;
@@ -212,9 +249,10 @@
 }
 
 static void CreateNamespaces() {
-  // 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 clone/waitpid instead.
+  // 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
+  // clone/waitpid instead.
   int delay = 1;
   int tries = 0;
   const int max_tries = 100;
@@ -251,7 +289,7 @@
 static void SetupDevices() {
   CHECK_CALL(mkdir("dev", 0755));
   const char *devs[] = {"/dev/null", "/dev/random", "/dev/urandom", "/dev/zero",
-                  NULL};
+                        NULL};
   for (int i = 0; devs[i] != NULL; i++) {
     CreateFile(devs[i] + 1);
     CHECK_CALL(mount(devs[i], devs[i] + 1, NULL, MS_BIND, NULL));
@@ -321,14 +359,16 @@
   // Make sure the home directory exists and is writable.
   const char *homedir;
   if ((homedir = getenv("HOME")) == NULL) {
-      homedir = getpwuid(getuid())->pw_dir;
+    homedir = getpwuid(getuid())->pw_dir;
   }
 
   if (homedir[0] != '/') {
-    DIE("Home directory of user nobody must be an absolute path, but is %s", homedir);
+    DIE("Home directory of user nobody must be an absolute path, but is %s",
+        homedir);
   }
 
-  char *homedir_absolute = malloc(strlen(opt->sandbox_root) + strlen(homedir) + 1);
+  char *homedir_absolute =
+      malloc(strlen(opt->sandbox_root) + strlen(homedir) + 1);
   strcpy(homedir_absolute, opt->sandbox_root);
   strcat(homedir_absolute, homedir);
 
@@ -381,9 +421,9 @@
   // Set group and user mapping from outer namespace to inner:
   // No changes in the parent, be nobody in the child.
   //
-  // 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.
+  // 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));