From 18e6b410a7f4e24b67740978d101cd665edea6cc Mon Sep 17 00:00:00 2001 From: Xin Gao Date: Tue, 20 Dec 2016 12:20:36 +0000 Subject: Add customized path mounting in Bazel sandbox. RELNOTES: New flag --sandbox_add_mount_pair to specify customized source:target path pairs to bind mount inside the sandbox. -- Change-Id: Ifbacfc0e16bbaedcf5b6d3937799710f2cfa3d58 Reviewed-on: https://cr.bazel.build/7150 PiperOrigin-RevId: 142542381 MOS_MIGRATED_REVID=142542381 --- .../build/lib/sandbox/LinuxSandboxRunner.java | 17 +++- .../build/lib/sandbox/LinuxSandboxedStrategy.java | 74 +++++++++++++-- .../devtools/build/lib/sandbox/SandboxOptions.java | 60 +++++++++++- src/main/tools/linux-sandbox-options.cc | 62 ++++++------ src/main/tools/linux-sandbox-options.h | 6 +- src/main/tools/linux-sandbox-pid1.cc | 13 ++- .../build/lib/sandbox/SandboxOptionsTest.java | 104 +++++++++++++++++++++ src/test/shell/bazel/bazel_sandboxing_test.sh | 91 ++++++++++++++++++ src/test/shell/bazel/linux-sandbox_test.sh | 87 +++++++++++++++++ 9 files changed, 465 insertions(+), 49 deletions(-) create mode 100644 src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java 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 writableDirs; private final Set inaccessiblePaths; private final Set tmpfsPaths; - private final Set bindMounts; + // a mapping of paths to bind mount + private final Map bindMounts; private final boolean sandboxDebug; LinuxSandboxRunner( @@ -56,7 +57,7 @@ final class LinuxSandboxRunner extends SandboxRunner { Set writableDirs, Set inaccessiblePaths, Set tmpfsPaths, - Set bindMounts, + Map 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 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 getBindMounts(BlazeDirectories blazeDirs) { + private SortedMap getReadOnlyBindMounts( + BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException { Path tmpPath = blazeDirs.getFileSystem().getPath("/tmp"); - ImmutableSet.Builder bindMounts = ImmutableSet.builder(); + final SortedMap 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 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 bindMounts) throws UserExecException { + for (SortedMap.Entry 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> { + + @Override + public ImmutableMap.Entry convert(String input) throws OptionsParsingException { + + List paths = Lists.newArrayList(); + for (String path : input.split("(? 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 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> 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 make a file or directory inaccessible for the " "sandboxed process\n" " -e mount an empty tmpfs on a directory\n" - " -b bind mount a file or directory inside the sandbox\n" + " -M/-m 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> 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> 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(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(c)); opt.working_dir = strdup(optarg); } else { Usage(args->front(), @@ -173,32 +180,33 @@ static void ParseCommandLine(unique_ptr> 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(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(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(c)); opt.tmpfs_dirs.push_back(strdup(optarg)); break; - case 'b': - if (optarg[0] != '/') { + case 'M': + ValidateIsAbsolutePath(optarg, args->front(), static_cast(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(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 inaccessible_files; // Directories where to mount an empty tmpfs (-e) std::vector tmpfs_dirs; - // Files or directories to explicitly bind mount into the sandbox (-b) - std::vector bind_mounts; + // Source of files or directories to explicitly bind mount in the sandbox (-M) + std::vector bind_mount_sources; + // Target of files or directories to explicitly bind mount in the sandbox (-m) + std::vector 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); } } diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java new file mode 100644 index 0000000000..0441f782e1 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java @@ -0,0 +1,104 @@ +// Copyright 2016 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. + +package com.google.devtools.build.lib.sandbox; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.common.options.OptionsParsingException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@code SandboxOptions}. */ +@RunWith(JUnit4.class) +public final class SandboxOptionsTest extends SandboxTestCase { + + private ImmutableMap.Entry pathPair; + + @Test + public void testParsingAdditionalMounts_SinglePathWithoutColonSucess() throws Exception { + String source = "/a/bc/def/gh"; + String target = source; + String input = source; + pathPair = new SandboxOptions.MountPairConverter().convert(input); + assertMountPair(pathPair, source, target); + } + + @Test + public void testParsingAdditionalMounts_SinglePathWithColonSucess() throws Exception { + String source = "/a/b:c/def/gh"; + String target = source; + String input = "/a/b\\:c/def/gh"; + pathPair = new SandboxOptions.MountPairConverter().convert(input); + assertMountPair(pathPair, source, target); + } + + @Test + public void testParsingAdditionalMounts_PathPairWithoutColonSucess() throws Exception { + String source = "/a/bc/def/gh"; + String target = "/1/2/3/4/5"; + String input = source + ":" + target; + pathPair = new SandboxOptions.MountPairConverter().convert(input); + assertMountPair(pathPair, source, target); + } + + @Test + public void testParsingAdditionalMounts_PathPairWithColonSucess() throws Exception { + String source = "/a:/bc:/d:ef/gh"; + String target = ":/1/2/3/4/5"; + String input = "/a\\:/bc\\:/d\\:ef/gh:\\:/1/2/3/4/5"; + pathPair = new SandboxOptions.MountPairConverter().convert(input); + assertMountPair(pathPair, source, target); + } + + @Test + public void testParsingAdditionalMounts_TooManyPaths() throws Exception { + String input = "a/bc/def/gh:/1/2/3:x/y/z"; + try { + pathPair = new SandboxOptions.MountPairConverter().convert(input); + fail(); + } catch (OptionsParsingException e) { + assertEquals( + e.getMessage(), + "Input must be a single path to mount inside the sandbox or " + + "a mounting pair in the form of 'source:target'"); + } + } + + @Test + public void testParsingAdditionalMounts_EmptyInput() throws Exception { + String input = ""; + try { + pathPair = new SandboxOptions.MountPairConverter().convert(input); + fail(); + } catch (OptionsParsingException e) { + assertEquals( + e.getMessage(), + "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'"); + } + } + + private static void assertMountPair( + ImmutableMap.Entry pathPair, String source, String target) { + assertEquals(pathPair.getKey(), source); + assertEquals(pathPair.getValue(), target); + } +} diff --git a/src/test/shell/bazel/bazel_sandboxing_test.sh b/src/test/shell/bazel/bazel_sandboxing_test.sh index 5d2199bddc..df015bb5f4 100755 --- a/src/test/shell/bazel/bazel_sandboxing_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_test.sh @@ -445,6 +445,97 @@ EOF expect_log "Executing genrule //:test failed: linux-sandbox failed: error executing command" } +function test_sandbox_mount_customized_path () { + # Create BUILD file + cat > BUILD <<'EOF' +package(default_visibility = ["//visibility:public"]) + +cc_binary( + name = "hello-world", + srcs = ["hello-world.cc"], +) +EOF + + # Create cc file + cat > hello-world.cc << 'EOF' +#include +int main(int argc, char** argv) { + std::cout << "Hello, world!" << std::endl; + return 0; +} +EOF + + # Create WORKSPACE file + cat > WORKSPACE <<'EOF' +local_repository( + name = 'x86_64_unknown_linux_gnu', + path = './downloaded_toolchain', +) +EOF + + # Prepare the sandbox root + SANDBOX_ROOT="${TEST_TMPDIR}/sandbox.root" + mkdir -p ${SANDBOX_ROOT} + + # Define the mount source and target. + source="${TEST_TMPDIR}/workspace/downloaded_toolchain/x86_64-unknown-linux-gnu/sysroot/lib64/ld-2.19.so" + target_root="${TEST_SRCDIR}/mount_targets" + target_folder="${target_root}/x86_64-unknown-linux-gnu/sysroot/lib64" + target="${target_folder}/ld-2.19.so" + + # Download the toolchain package and unpack it. + wget -q https://asci-toolchain.appspot.com.storage.googleapis.com/toolchain-testing/mount_path_toolchain.tar.gz + mkdir downloaded_toolchain + tar -xf mount_path_toolchain.tar.gz -C ./downloaded_toolchain + chmod -R 0755 downloaded_toolchain + + # Replace the target_root_placeholder with the actual target_root + sed -i "s|target_root_placeholder|$target_root|g" downloaded_toolchain/CROSSTOOL + + # Prepare the bazel command flags + flags="--crosstool_top=@x86_64_unknown_linux_gnu//:toolchain --verbose_failures --spawn_strategy=sandboxed" + flags="${flags} --sandbox_add_mount_pair=${source}:${target}" + + # Execute the bazel build command without creating the target. Should fail. + bazel clean --expunge &> $TEST_log + bazel build $flags //:hello-world &> $TEST_log && fail "Should fail" + expect_log "Bazel only supports bind mounting on top of existing files/directories." + + # Create the mount target manually as Bazel does not create target paths + mkdir -p ${target_folder} + touch ${target} + + # Execute bazel build command again. Should build. + bazel clean --expunge &> $TEST_log + bazel build $flags //:hello-world &> $TEST_log || fail "Should build" + + # Remove the mount target folder as Bazel does not do the cleanup + rm -rf ${target_root}/x86_64-unknown-linux-gnu + + # Assert that output binary exists + test -f bazel-bin/hello-world || fail "output not found" + + # Use linux_sandbox binary to run bazel-bin/hello-world binary in the sandbox environment + # First, no path mounting. The execution should fail. + echo "Run the binary bazel-bin/hello-world without mounting the path" + $linux_sandbox -D -S ${SANDBOX_ROOT} -- bazel-bin/hello-world &> $TEST_log || code=$? + expect_log "child exited normally with exitcode 1" + + # Second, with path mounting. The execution should succeed. + echo "Run the binary bazel-bin/hello-world with mounting the path" + # Create the mount target manually as sandbox binary does not create target paths + mkdir -p ${target_folder} + touch ${target} + $linux_sandbox -D -S ${SANDBOX_ROOT} \ + -M ${source} \ + -m ${target} \ + -- bazel-bin/hello-world &> $TEST_log || code=$? + expect_log "Hello, world!" + expect_log "child exited normally with exitcode 0" + # Remove the mount target folder as sandbox binary does not do the cleanup + rm -rf ${target_root}/x86_64-unknown-linux-gnu +} + # The test shouldn't fail if the environment doesn't support running it. check_supported_platform || exit 0 check_sandbox_allowed || exit 0 diff --git a/src/test/shell/bazel/linux-sandbox_test.sh b/src/test/shell/bazel/linux-sandbox_test.sh index 549f84fe88..c97f1901d4 100755 --- a/src/test/shell/bazel/linux-sandbox_test.sh +++ b/src/test/shell/bazel/linux-sandbox_test.sh @@ -28,12 +28,15 @@ readonly OUT_DIR="${TEST_TMPDIR}/out" readonly OUT="${OUT_DIR}/outfile" readonly ERR="${OUT_DIR}/errfile" readonly SANDBOX_DIR="${OUT_DIR}/sandbox" +readonly SANDBOX_ROOT="${TEST_TMPDIR}/sandbox.root" +readonly MOUNT_TARGET_ROOT="${TEST_SRCDIR}/targets" SANDBOX_DEFAULT_OPTS="-W $SANDBOX_DIR" function set_up { rm -rf $OUT_DIR mkdir -p $SANDBOX_DIR + mkdir -p $SANDBOX_ROOT } function test_basic_functionality() { @@ -111,6 +114,90 @@ function test_debug_logging() { expect_log "child exited normally with exitcode 0" } +function test_mount_additional_paths_success() { + mkdir -p ${TEST_TMPDIR}/foo + mkdir -p ${TEST_TMPDIR}/bar + touch ${TEST_TMPDIR}/testfile + mkdir -p ${MOUNT_TARGET_ROOT}/foo + touch ${MOUNT_TARGET_ROOT}/sandboxed_testfile + + touch /tmp/sandboxed_testfile + $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \ + -M ${TEST_TMPDIR}/foo -m ${MOUNT_TARGET_ROOT}/foo \ + -M ${TEST_TMPDIR}/bar \ + -M ${TEST_TMPDIR}/testfile -m ${MOUNT_TARGET_ROOT}/sandboxed_testfile \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "bind mount: ${TEST_TMPDIR}/foo -> ${MOUNT_TARGET_ROOT}/foo\$" + # mount a directory to the same path inside the sanxbox + expect_log "bind mount: ${TEST_TMPDIR}/bar -> ${TEST_TMPDIR}/bar\$" + # mount a file to a customized path inside the sandbox + expect_log "bind mount: ${TEST_TMPDIR}/testfile -> ${MOUNT_TARGET_ROOT}/sandboxed_testfile\$" + expect_log "child exited normally with exitcode 0" + rm -rf ${MOUNT_TARGET_ROOT}/foo + rm -rf ${MOUNT_TARGET_ROOT}/sandboxed_testfile +} + +function test_mount_additional_paths_relative_path() { + touch ${TEST_TMPDIR}/testfile + $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \ + -M ${TEST_TMPDIR}/testfile -m tmp/sandboxed_testfile \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "The -m option must be used with absolute paths only.\$" +} + +function test_mount_additional_paths_leading_m() { + mkdir -p ${TEST_TMPDIR}/foo + touch ${TEST_TMPDIR}/testfile + $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \ + -m /tmp/foo \ + -M ${TEST_TMPDIR}/testfile -m /tmp/sandboxed_testfile \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "The -m option must be strictly preceded by an -M option.\$" +} + +function test_mount_additional_paths_m_not_preceeded_by_M() { + mkdir -p ${TEST_TMPDIR}/foo + mkdir -p ${TEST_TMPDIR}/bar + touch ${TEST_TMPDIR}/testfile + $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \ + -M ${TEST_TMPDIR}/testfile -m /tmp/sandboxed_testfile \ + -m /tmp/foo \ + -M ${TEST_TMPDIR}/bar \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "The -m option must be strictly preceded by an -M option.\$" +} + +function test_mount_additional_paths_other_flag_between_M_m_pair() { + mkdir -p ${TEST_TMPDIR}/bar + touch ${TEST_TMPDIR}/testfile + $linux_sandbox $SANDBOX_DEFAULT_OPTS -S ${SANDBOX_ROOT} \ + -M ${TEST_TMPDIR}/testfile -D -m /tmp/sandboxed_testfile \ + -M ${TEST_TMPDIR}/bar \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "The -m option must be strictly preceded by an -M option.\$" +} + +function test_mount_additional_paths_multiple_sources_mount_to_one_target() { + mkdir -p ${TEST_TMPDIR}/foo + mkdir -p ${TEST_TMPDIR}/bar + mkdir -p ${MOUNT_TARGET_ROOT}/foo + $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \ + -M ${TEST_TMPDIR}/foo -m ${MOUNT_TARGET_ROOT}/foo \ + -M ${TEST_TMPDIR}/bar -m ${MOUNT_TARGET_ROOT}/foo \ + -- /bin/true &> $TEST_log || code=$? + # mount a directory to a customized path inside the sandbox + expect_log "bind mount: ${TEST_TMPDIR}/foo -> ${MOUNT_TARGET_ROOT}/foo\$" + # mount a new source directory to the same target, which will overwrite the previous source path + expect_log "bind mount: ${TEST_TMPDIR}/bar -> ${MOUNT_TARGET_ROOT}/foo\$" + expect_log "child exited normally with exitcode 0" + rm -rf ${MOUNT_TARGET_ROOT}/foo +} + function test_redirect_output() { $linux_sandbox $SANDBOX_DEFAULT_OPTS -l $OUT -L $ERR -- /bin/bash -c "echo out; echo err >&2" &> $TEST_log || code=$? assert_equals "out" "$(cat $OUT)" -- cgit v1.2.3