diff options
Diffstat (limited to 'src/main')
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); } } |