aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java17
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java74
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java60
-rw-r--r--src/main/tools/linux-sandbox-options.cc62
-rw-r--r--src/main/tools/linux-sandbox-options.h6
-rw-r--r--src/main/tools/linux-sandbox-pid1.cc13
6 files changed, 183 insertions, 49 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
index da50cda15a..97151e24ce 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
@@ -45,7 +45,8 @@ final class LinuxSandboxRunner extends SandboxRunner {
private final Set<Path> writableDirs;
private final Set<Path> inaccessiblePaths;
private final Set<Path> tmpfsPaths;
- private final Set<Path> bindMounts;
+ // a <target, source> mapping of paths to bind mount
+ private final Map<Path, Path> bindMounts;
private final boolean sandboxDebug;
LinuxSandboxRunner(
@@ -56,7 +57,7 @@ final class LinuxSandboxRunner extends SandboxRunner {
Set<Path> writableDirs,
Set<Path> inaccessiblePaths,
Set<Path> tmpfsPaths,
- Set<Path> bindMounts,
+ Map<Path, Path> bindMounts,
boolean verboseFailures,
boolean sandboxDebug) {
super(sandboxExecRoot, verboseFailures);
@@ -155,9 +156,15 @@ final class LinuxSandboxRunner extends SandboxRunner {
fileArgs.add(tmpfsPath.getPathString());
}
- for (Path bindMount : bindMounts) {
- fileArgs.add("-b");
- fileArgs.add(bindMount.getPathString());
+ for (ImmutableMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ fileArgs.add("-M");
+ fileArgs.add(bindMount.getValue().getPathString());
+
+ // The file is mounted in a custom location inside the sandbox.
+ if (!bindMount.getKey().equals(bindMount.getValue())) {
+ fileArgs.add("-m");
+ fileArgs.add(bindMount.getKey().getPathString());
+ }
}
if (!allowNetwork) {
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
index b4ed542ab1..dc4578ef61 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
@@ -14,7 +14,9 @@
package com.google.devtools.build.lib.sandbox;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
@@ -31,6 +33,7 @@ import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.Set;
+import java.util.SortedMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -149,7 +152,8 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
}
private SandboxRunner getSandboxRunner(
- Spawn spawn, Path sandboxPath, Path sandboxExecRoot, Path sandboxTempDir) {
+ Spawn spawn, Path sandboxPath, Path sandboxExecRoot, Path sandboxTempDir)
+ throws UserExecException {
if (fullySupported) {
return new LinuxSandboxRunner(
execRoot,
@@ -159,7 +163,7 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
getWritableDirs(sandboxExecRoot, spawn.getEnvironment()),
getInaccessiblePaths(),
getTmpfsPaths(),
- getBindMounts(blazeDirs),
+ getReadOnlyBindMounts(blazeDirs, sandboxExecRoot),
verboseFailures,
sandboxOptions.sandboxDebug);
} else {
@@ -175,15 +179,71 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
return tmpfsPaths.build();
}
- private ImmutableSet<Path> getBindMounts(BlazeDirectories blazeDirs) {
+ private SortedMap<Path, Path> getReadOnlyBindMounts(
+ BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException {
Path tmpPath = blazeDirs.getFileSystem().getPath("/tmp");
- ImmutableSet.Builder<Path> bindMounts = ImmutableSet.builder();
+ final SortedMap<Path, Path> bindMounts = Maps.newTreeMap();
if (blazeDirs.getWorkspace().startsWith(tmpPath)) {
- bindMounts.add(blazeDirs.getWorkspace());
+ bindMounts.put(blazeDirs.getWorkspace(), blazeDirs.getWorkspace());
}
if (blazeDirs.getOutputBase().startsWith(tmpPath)) {
- bindMounts.add(blazeDirs.getOutputBase());
+ bindMounts.put(blazeDirs.getOutputBase(), blazeDirs.getOutputBase());
+ }
+ for (ImmutableMap.Entry<String, String> additionalMountPath :
+ sandboxOptions.sandboxAdditionalMounts) {
+ try {
+ final Path mountTarget = blazeDirs.getFileSystem().getPath(additionalMountPath.getValue());
+ // If source path is relative, treat it as a relative path inside the execution root
+ final Path mountSource = sandboxExecRoot.getRelative(additionalMountPath.getKey());
+ // If a target has more than one source path, the latter one will take effect.
+ bindMounts.put(mountTarget, mountSource);
+ } catch (IllegalArgumentException e) {
+ throw new UserExecException(
+ String.format("Error occurred when analyzing bind mount pairs. %s", e.getMessage()));
+ }
+ }
+ validateBindMounts(bindMounts);
+ return bindMounts;
+ }
+
+ /**
+ * This method does the following things: - If mount source does not exist on the host system,
+ * throw an error message - If mount target exists, check whether the source and target are of the
+ * same type - If mount target does not exist on the host system, throw an error message
+ *
+ * @param bindMounts the bind mounts map with target as key and source as value
+ * @throws UserExecException
+ */
+ private void validateBindMounts(SortedMap<Path, Path> bindMounts) throws UserExecException {
+ for (SortedMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ final Path source = bindMount.getValue();
+ final Path target = bindMount.getKey();
+ // Mount source should exist in the file system
+ if (!source.exists()) {
+ throw new UserExecException(String.format("Mount source '%s' does not exist.", source));
+ }
+ // If target exists, but is not of the same type as the source, then we cannot mount it.
+ if (target.exists()) {
+ boolean areBothDirectories = source.isDirectory() && target.isDirectory();
+ boolean isSourceFile = source.isFile() || source.isSymbolicLink();
+ boolean isTargetFile = target.isFile() || target.isSymbolicLink();
+ boolean areBothFiles = isSourceFile && isTargetFile;
+ if (!(areBothDirectories || areBothFiles)) {
+ // Source and target are not of the same type; we cannot mount it.
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' is not of the same type as mount source '%s'.",
+ target, source));
+ }
+ } else {
+ // Mount target should exist in the file system
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' does not exist. Bazel only supports bind mounting on top of "
+ + "existing files/directories. Please create an empty file or directory at "
+ + "the mount target path according to the type of mount source.",
+ target));
+ }
}
- return bindMounts.build();
}
}
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
index 909e07cc76..b8ceb8094c 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
@@ -14,15 +14,59 @@
package com.google.devtools.build.lib.sandbox;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParsingException;
import java.util.List;
-/**
- * Options for sandboxed execution.
- */
+/** Options for sandboxed execution. */
public class SandboxOptions extends OptionsBase {
+ /**
+ * A converter for customized path mounting pair from the parameter list of a bazel command
+ * invocation. Pairs are expected to have the form 'source:target'.
+ */
+ public static final class MountPairConverter
+ implements Converter<ImmutableMap.Entry<String, String>> {
+
+ @Override
+ public ImmutableMap.Entry<String, String> convert(String input) throws OptionsParsingException {
+
+ List<String> paths = Lists.newArrayList();
+ for (String path : input.split("(?<!\\\\):")) { // Split on ':' but not on '\:'
+ if (path != null && !path.trim().isEmpty()) {
+ paths.add(path.replace("\\:", ":"));
+ } else {
+ throw new OptionsParsingException(
+ "Input "
+ + input
+ + " contains one or more empty paths. "
+ + "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+ }
+
+ if (paths.size() < 1 || paths.size() > 2) {
+ throw new OptionsParsingException(
+ "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+
+ return paths.size() == 1
+ ? Maps.immutableEntry(paths.get(0), paths.get(0))
+ : Maps.immutableEntry(paths.get(0), paths.get(1));
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a single path or a 'source:target' pair";
+ }
+ }
+
@Option(
name = "ignore_unsupported_sandboxing",
defaultValue = "false",
@@ -59,4 +103,14 @@ public class SandboxOptions extends OptionsBase {
+ " (if supported by the sandboxing implementation, ignored otherwise)."
)
public List<String> sandboxTmpfsPath;
+
+ @Option(
+ name = "sandbox_add_mount_pair",
+ allowMultiple = true,
+ converter = MountPairConverter.class,
+ defaultValue = "",
+ category = "config",
+ help = "Add additional path pair to mount in sandbox."
+ )
+ public List<ImmutableMap.Entry<String, String>> sandboxAdditionalMounts;
}
diff --git a/src/main/tools/linux-sandbox-options.cc b/src/main/tools/linux-sandbox-options.cc
index 49e6eafeeb..8f8c15d5d9 100644
--- a/src/main/tools/linux-sandbox-options.cc
+++ b/src/main/tools/linux-sandbox-options.cc
@@ -69,7 +69,11 @@ static void Usage(char *program_name, const char *fmt, ...) {
" -i <file> make a file or directory inaccessible for the "
"sandboxed process\n"
" -e <dir> mount an empty tmpfs on a directory\n"
- " -b <dir> bind mount a file or directory inside the sandbox\n"
+ " -M/-m <source/target> directory to mount inside the sandbox\n"
+ " Multiple directories can be specified and each of them will be "
+ "mounted readonly.\n"
+ " The -M option specifies which directory to mount, the -m option "
+ "specifies where to\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"
@@ -106,15 +110,24 @@ static int CheckNamespacesSupported() {
return EXIT_SUCCESS;
}
+static void ValidateIsAbsolutePath(char *path, char *program_name, char flag) {
+ if (path[0] != '/') {
+ Usage(program_name, "The -%c option must be used with absolute paths only.",
+ flag);
+ }
+}
+
// Parses command line flags from an argv array and puts the results into an
// Options structure passed in as an argument.
static void ParseCommandLine(unique_ptr<vector<char *>> args) {
extern char *optarg;
extern int optind, optopt;
int c;
+ bool source_specified;
while ((c = getopt(args->size(), args->data(),
- ":CS:W:T:t:l:L:w:i:e:b:NRD")) != -1) {
+ ":CS:W:T:t:l:L:w:i:e:M:m:NRD")) != -1) {
+ if (c != 'M' && c != 'm') source_specified = false;
switch (c) {
case 'C':
// Shortcut for the "does this system support sandboxing" check.
@@ -122,22 +135,16 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
break;
case 'S':
if (opt.sandbox_root_dir == NULL) {
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -r option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.sandbox_root_dir = strdup(optarg);
} else {
Usage(args->front(),
- "Multiple root directories (-r) specified, expected one.");
+ "Multiple root directories (-S) specified, expected one.");
}
break;
case 'W':
if (opt.working_dir == NULL) {
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -W option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.working_dir = strdup(optarg);
} else {
Usage(args->front(),
@@ -173,32 +180,33 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
}
break;
case 'w':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -w option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.writable_files.push_back(strdup(optarg));
break;
case 'i':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -i option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.inaccessible_files.push_back(strdup(optarg));
break;
case 'e':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -e option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.tmpfs_dirs.push_back(strdup(optarg));
break;
- case 'b':
- if (optarg[0] != '/') {
+ case 'M':
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
+ // Add the current source path to both source and target lists
+ opt.bind_mount_sources.push_back(strdup(optarg));
+ opt.bind_mount_targets.push_back(strdup(optarg));
+ source_specified = true;
+ break;
+ case 'm':
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
+ if (!source_specified) {
Usage(args->front(),
- "The -b option must be used with absolute paths only.");
+ "The -m option must be strictly preceded by an -M option.");
}
- opt.bind_mounts.push_back(strdup(optarg));
+ opt.bind_mount_targets.pop_back();
+ opt.bind_mount_targets.push_back(strdup(optarg));
+ source_specified = false;
break;
case 'N':
opt.create_netns = true;
diff --git a/src/main/tools/linux-sandbox-options.h b/src/main/tools/linux-sandbox-options.h
index 5554f9e41f..5f33a0676f 100644
--- a/src/main/tools/linux-sandbox-options.h
+++ b/src/main/tools/linux-sandbox-options.h
@@ -40,8 +40,10 @@ struct Options {
std::vector<const char *> inaccessible_files;
// Directories where to mount an empty tmpfs (-e)
std::vector<const char *> tmpfs_dirs;
- // Files or directories to explicitly bind mount into the sandbox (-b)
- std::vector<const char *> bind_mounts;
+ // Source of files or directories to explicitly bind mount in the sandbox (-M)
+ std::vector<const char *> bind_mount_sources;
+ // Target of files or directories to explicitly bind mount in the sandbox (-m)
+ std::vector<const char *> bind_mount_targets;
// Create a new network namespace (-N)
bool create_netns;
// Pretend to be root inside the namespace (-R)
diff --git a/src/main/tools/linux-sandbox-pid1.cc b/src/main/tools/linux-sandbox-pid1.cc
index 4feedd9e02..5e0dff1a01 100644
--- a/src/main/tools/linux-sandbox-pid1.cc
+++ b/src/main/tools/linux-sandbox-pid1.cc
@@ -249,17 +249,20 @@ static void MountFilesystems() {
// Make sure that our working directory is a mount point. The easiest way to
// do this is by bind-mounting it upon itself.
PRINT_DEBUG("working dir: %s", opt.working_dir);
+ PRINT_DEBUG("sandbox root: %s", opt.sandbox_root_dir);
+
CreateTarget(opt.working_dir + 1, true);
if (mount(opt.working_dir, opt.working_dir + 1, NULL, MS_BIND, NULL) < 0) {
DIE("mount(%s, %s, NULL, MS_BIND, NULL)", opt.working_dir,
opt.working_dir + 1);
}
- for (const char *bind_mount : opt.bind_mounts) {
- PRINT_DEBUG("bind mount: %s", bind_mount);
- CreateTarget(bind_mount + 1, IsDirectory(bind_mount));
- if (mount(bind_mount, bind_mount + 1, NULL, MS_BIND, NULL) < 0) {
- DIE("mount(%s, %s, NULL, MS_BIND, NULL)", bind_mount, bind_mount + 1);
+ for (size_t i = 0; i < opt.bind_mount_sources.size(); i++) {
+ const char *source = opt.bind_mount_sources.at(i);
+ const char *target = opt.bind_mount_targets.at(i);
+ PRINT_DEBUG("bind mount: %s -> %s", source, target);
+ if (mount(source, target + 1, NULL, MS_BIND, NULL) < 0) {
+ DIE("mount(%s, %s, NULL, MS_BIND, NULL)", source, target + 1);
}
}