From 56dde84e0813b28331bc4e0283dc318d3c12277d Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 15 Feb 2023 02:53:21 -0800 Subject: [PATCH] Move a bunch of tests from bazel-specific to common. PiperOrigin-RevId: 509777090 Change-Id: Ie6b6c3fa63a5e5ae4bced88698a6661d8e2e16fc --- src/test/shell/bazel/BUILD | 14 + .../bazel/bazel_sandboxing_networking_test.sh | 314 +++++++ src/test/shell/bazel/bazel_sandboxing_test.sh | 882 +----------------- src/test/shell/integration/sandboxing_test.sh | 561 ++++++++++- 4 files changed, 889 insertions(+), 882 deletions(-) create mode 100755 src/test/shell/bazel/bazel_sandboxing_networking_test.sh diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD index 41bc626374e664..e0b864961c534a 100644 --- a/src/test/shell/bazel/BUILD +++ b/src/test/shell/bazel/BUILD @@ -974,6 +974,20 @@ sh_test( ":test-deps", "//src/test/shell:sandboxing_test_utils.sh", ], + tags = [ + "no-sandbox", + "no_windows", + ], +) + +sh_test( + name = "bazel_sandboxing_networking_test", + size = "large", + srcs = ["bazel_sandboxing_networking_test.sh"], + data = [ + ":test-deps", + "//src/test/shell:sandboxing_test_utils.sh", + ], tags = [ "no-sandbox", "no_windows", diff --git a/src/test/shell/bazel/bazel_sandboxing_networking_test.sh b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh new file mode 100755 index 00000000000000..51b9fd8cd944d9 --- /dev/null +++ b/src/test/shell/bazel/bazel_sandboxing_networking_test.sh @@ -0,0 +1,314 @@ +#!/bin/bash +# +# Copyright 2023 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. +# +# Test sandboxing spawn strategy +# + +# Set to a host:port address that is outside of the local machine to +# test remote network sandboxing features. +# +# Can be passed in via --test_env=REMOTE_NETWORK_ADDRESS=host:port. +: "${REMOTE_NETWORK_ADDRESS:=}" + +# Load test environment +# Load the test setup defined in the parent directory +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/../integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } +source ${CURRENT_DIR}/../sandboxing_test_utils.sh \ + || { echo "sandboxing_test_utils.sh not found!" >&2; exit 1; } +source ${CURRENT_DIR}/remote_helpers.sh \ + || { echo "remote_helpers.sh not found!" >&2; exit 1; } + +function set_up() { + add_to_bazelrc "build --spawn_strategy=sandboxed" + add_to_bazelrc "build --genrule_strategy=sandboxed" + + sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc +} + +# Prepares common targets and services to be used by all network-related +# tests. The tests for remote network access are only enabled if the +# user has requested them by setting REMOTE_NETWORK_ADDRESS in the +# environment. +function setup_network_tests() { + local tags="${1}"; shift + echo 'stuff to serve' > file_to_serve + + serve_file file_to_serve + + local socket_dir + socket_dir="$(mktemp -d /tmp/test.XXXXXX)" || fail "mktemp failed" + local socket="${socket_dir}/socket" + python $python_server --unix_socket="${socket}" always file_to_serve & + local pid="${!}" + + trap "kill_nc || true; kill '${pid}' || true; rm -f '${socket}'; rmdir '${socket_dir}'" EXIT + + mkdir pkg + cat <pkg/BUILD +genrule( + name = "localhost", + outs = [ "localhost.txt" ], + cmd = "curl -fo \$@ localhost:${nc_port}", + tags = [ ${tags} ], +) + +genrule( + name = "unix-socket", + outs = [ "unix-socket.txt" ], + cmd = "curl --unix-socket ${socket} -fo \$@ irrelevant-url", + tags = [ ${tags} ], +) + +genrule( + name = "loopback", + outs = [ "loopback.txt" ], + cmd = "python $python_server always $(pwd)/file_to_serve >port.txt & " + + "pid=\$\$!; " + + "while ! grep started port.txt; do sleep 1; done; " + + "port=\$\$(head -n 1 port.txt); " + + "curl -fo \$@ localhost:\$\$port; " + + "kill \$\$pid", +) +EOF + + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + local hostname="${REMOTE_NETWORK_ADDRESS%:*}" + local remote_ip + if which host 2>/dev/null; then + remote_ip="$(host -t A "${hostname}" | head -n 1 | awk '{print $4}')" + elif which dig 2>/dev/null; then + remote_ip="$(dig -t A "${hostname}" | grep "^${hostname}" | awk '{print $5}')" + else + fail "Don't know how to query IP of remote host ${hostname}" + fi + if [[ -z "${remote_ip}" ]]; then + fail "No IPv4 connectivity within unsandboxed test" + fi + + cat <>pkg/BUILD +genrule( + name = "remote-ip", + outs = [ "remote-ip.txt" ], + cmd = "curl -fo \$@ ${remote_ip}:80", + tags = [ ${tags} ], +) + +genrule( + name = "remote-name", + outs = [ "remote-name.txt" ], + cmd = "curl -fo \$@ '${REMOTE_NETWORK_ADDRESS}'", + tags = [ ${tags} ], +) +EOF + else + echo "Not registering tests for remote network sandboxing;" \ + "REMOTE_NETWORK_ADDRESS has not been set" + fi +} + +# Checks that the given target name, which must have been created by +# a previous call to setup_network_tests, can access the network. +function check_network_ok() { + local target="${1}"; shift + + ( + # macOS's /bin/bash is ancient and cannot reference $@ when -u is set. + # https://unix.stackexchange.com/questions/16560/bash-su-unbound-variable-with-set-u + set +u + + bazel build "${@}" "pkg:${target}" &>$TEST_log \ + || fail "'${target}' could not access the network" + ) +} + +# Checks that the given target name, which must have been created by +# a previous call to setup_network_tests, cannot access the network. +function check_network_not_ok() { + local target="${1}"; shift + + ( + # macOS's /bin/bash is ancient and cannot reference $@ when -u is set. + # https://unix.stackexchange.com/questions/16560/bash-su-unbound-variable-with-set-u + set +u + + bazel build "${@}" "pkg:${target}" &> $TEST_log \ + && fail "'${target}' trying to use network succeeded but should have failed" || true + ) + [[ ! -f "bazel-genfiles/pkg/${target}.txt" ]] \ + || fail "'${target}' produced output but was expected to fail" +} + +function test_sandbox_network_access() { + setup_network_tests '"some-tag"' + + check_network_ok localhost + check_network_ok unix-socket + check_network_ok loopback + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + check_network_ok remote-ip + check_network_ok remote-name + fi +} + +function test_sandbox_block_network_access() { + setup_network_tests '"some-tag"' + + case "$(uname -s)" in + Linux) + # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity + # within the network namespace... but that doesn't seem to be the case. + check_network_not_ok localhost --experimental_sandbox_default_allow_network=false + ;; + + *) + check_network_ok localhost --experimental_sandbox_default_allow_network=false + ;; + esac + check_network_ok unix-socket --experimental_sandbox_default_allow_network=false + check_network_ok loopback --experimental_sandbox_default_allow_network=false + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=false + check_network_not_ok remote-name --experimental_sandbox_default_allow_network=false + fi +} + +function test_sandbox_network_access_with_local() { + cat >>$TEST_TMPDIR/bazelrc <<'EOF' +# With `--incompatible_legacy_local_fallback` turned off, we need to explicitly +# include a non-sandboxed strategy. +build --spawn_strategy=sandboxed,standalone --genrule_strategy=sandboxed,standalone +EOF + + setup_network_tests '"local"' + + check_network_ok localhost + check_network_ok unix-socket + check_network_ok loopback + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + check_network_ok remote-ip + check_network_ok remote-name + fi +} + +function test_sandbox_network_access_with_requires_network() { + setup_network_tests '"requires-network"' + + check_network_ok localhost --experimental_sandbox_default_allow_network=false + check_network_ok unix-socket --experimental_sandbox_default_allow_network=false + check_network_ok loopback --experimental_sandbox_default_allow_network=false + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + check_network_ok remote-ip --experimental_sandbox_default_allow_network=false + check_network_ok remote-name --experimental_sandbox_default_allow_network=false + fi +} + +function test_sandbox_network_access_with_block_network() { + setup_network_tests '"block-network"' + + case "$(uname -s)" in + Linux) + # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity + # within the network namespace... but that doesn't seem to be the case. + check_network_not_ok localhost --experimental_sandbox_default_allow_network=true + ;; + + *) + check_network_ok localhost --experimental_sandbox_default_allow_network=true + ;; + esac + check_network_ok unix-socket --experimental_sandbox_default_allow_network=true + check_network_ok loopback --experimental_sandbox_default_allow_network=true + if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then + check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=true + check_network_not_ok remote-name --experimental_sandbox_default_allow_network=true + fi +} + +function test_sandbox_can_resolve_own_hostname() { + setup_javatest_support + mkdir -p src/test/java/com/example + cat > src/test/java/com/example/HostNameTest.java <<'EOF' +package com.example; + +import static org.junit.Assert.*; + +import org.junit.Test; +import java.net.*; +import java.io.*; + +public class HostNameTest { + @Test + public void testGetHostName() throws Exception { + // This will throw an exception, if the local hostname cannot be resolved via DNS. + assertNotNull(InetAddress.getLocalHost().getHostName()); + } +} +EOF + cat > src/test/java/com/example/BUILD <<'EOF' +java_test( + name = "HostNameTest", + srcs = ["HostNameTest.java"], + deps = ['//third_party:junit4'], +) +EOF + + bazel test --test_output=streamed src/test/java/com/example:HostNameTest &> $TEST_log \ + || fail "test should have passed" +} + +function test_hostname_inside_sandbox_is_localhost_when_using_sandbox_fake_hostname_flag() { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: fake hostnames not supported in this system" 1>&2 + return 0 + fi + + setup_javatest_support + mkdir -p src/test/java/com/example + cat > src/test/java/com/example/HostNameIsLocalhostTest.java <<'EOF' +package com.example; + +import static org.junit.Assert.*; + +import org.junit.Test; +import java.net.*; +import java.io.*; + +public class HostNameIsLocalhostTest { + @Test + public void testHostNameIsLocalhost() throws Exception { + // This will throw an exception, if the local hostname cannot be resolved via DNS. + assertEquals("localhost", InetAddress.getLocalHost().getHostName()); + } +} +EOF + cat > src/test/java/com/example/BUILD <<'EOF' +java_test( + name = "HostNameIsLocalhostTest", + srcs = ["HostNameIsLocalhostTest.java"], + deps = ['//third_party:junit4'], +) +EOF + + bazel test --sandbox_fake_hostname --test_output=streamed src/test/java/com/example:HostNameIsLocalhostTest &> $TEST_log \ + || fail "test should have passed" +} + +# The test shouldn't fail if the environment doesn't support running it. +check_sandbox_allowed || exit 0 + +run_suite "sandbox" diff --git a/src/test/shell/bazel/bazel_sandboxing_test.sh b/src/test/shell/bazel/bazel_sandboxing_test.sh index 46a5417f2a3abf..d8bfcced88cb59 100755 --- a/src/test/shell/bazel/bazel_sandboxing_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_test.sh @@ -17,12 +17,6 @@ # Test sandboxing spawn strategy # -# Set to a host:port address that is outside of the local machine to -# test remote network sandboxing features. -# -# Can be passed in via --test_env=REMOTE_NETWORK_ADDRESS=host:port. -: "${REMOTE_NETWORK_ADDRESS:=}" - # Load test environment # Load the test setup defined in the parent directory CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -30,64 +24,15 @@ source "${CURRENT_DIR}/../integration_test_setup.sh" \ || { echo "integration_test_setup.sh not found!" >&2; exit 1; } source ${CURRENT_DIR}/../sandboxing_test_utils.sh \ || { echo "sandboxing_test_utils.sh not found!" >&2; exit 1; } -source ${CURRENT_DIR}/remote_helpers.sh \ - || { echo "remote_helpers.sh not found!" >&2; exit 1; } - function set_up { - cat >>$TEST_TMPDIR/bazelrc <<'EOF' -# Testing the sandboxed strategy requires using the sandboxed strategy. While it is the default, -# we want to make sure that this explicitly fails when the strategy is not available on the system -# running the test. -build --spawn_strategy=sandboxed --genrule_strategy=sandboxed -EOF - - export BAZEL_GENFILES_DIR=$(bazel info bazel-genfiles 2>/dev/null) - export BAZEL_BIN_DIR=$(bazel info bazel-bin 2>/dev/null) - - sed -i.bak '/sandbox_tmpfs_path/d' $TEST_TMPDIR/bazelrc + add_to_bazelrc "build --spawn_strategy=sandboxed" + add_to_bazelrc "build --genrule_strategy=sandboxed" +} +function test_sandboxed_tooldir() { mkdir -p examples/genrule - cat << 'EOF' > examples/genrule/a.txt -foo bar bz -EOF - cat << 'EOF' > examples/genrule/b.txt -apples oranges bananas -EOF - - # Create cyclic symbolic links to check whether the strategy catches that. - ln -sf cyclic2 examples/genrule/cyclic1 - ln -sf cyclic1 examples/genrule/cyclic2 - - # Create relative symlinks. - mkdir -p examples/genrule/symlinks/{a,ok/sub} - echo OK > examples/genrule/symlinks/ok/x.txt - ln -s $PWD/examples/genrule/symlinks/ok/sub examples/genrule/symlinks/a/b - ln -s ../x.txt examples/genrule/symlinks/a/b/x.txt - - echo 'stuff to serve' > file_to_serve cat << 'EOF' > examples/genrule/BUILD -genrule( - name = "works", - srcs = [ "a.txt" ], - outs = [ "works.txt" ], - cmd = "wc $(location :a.txt) > $@", -) - -sh_binary( - name = "tool", - srcs = ["tool.sh"], - data = ["datafile"], -) - -genrule( - name = "tools_work", - srcs = [], - outs = ["tools.txt"], - cmd = "$(location :tool) $@", - tools = [":tool"], -) - genrule( name = "tooldir", srcs = [], @@ -95,210 +40,16 @@ genrule( cmd = "ls -l external/bazel_tools/tools/genrule | tee $@ >&2; " + "cat external/bazel_tools/tools/genrule/genrule-setup.sh >&2", ) - -genrule( - name = "relative_symlinks", - srcs = [ "symlinks/a/b/x.txt" ], - outs = [ "relative_symlinks.txt" ], - cmd = "cat $(location :symlinks/a/b/x.txt) > $@", -) - -genrule( - name = "breaks1", - srcs = [ "a.txt" ], - outs = [ "breaks1.txt" ], - cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt &> $@", -) - -genrule( - name = "breaks1_works_with_local", - srcs = [ "a.txt" ], - outs = [ "breaks1_works_with_local.txt" ], - cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@", - local = 1, -) - -genrule( - name = "breaks1_works_with_local_tag", - srcs = [ "a.txt" ], - outs = [ "breaks1_works_with_local_tag.txt" ], - cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@", - tags = [ "local" ], -) - -load('//examples/genrule:starlark.bzl', 'starlark_breaks1') - -starlark_breaks1( - name = "starlark_breaks1", - input = "a.txt", - output = "starlark_breaks1.txt", -) - -starlark_breaks1( - name = "starlark_breaks1_works_with_local_tag", - input = "a.txt", - output = "starlark_breaks1_works_with_local_tag.txt", - action_tags = [ "local" ], -) - -genrule( - name = "breaks3", - srcs = [ "cyclic1", "cyclic2" ], - outs = [ "breaks3.txt" ], - cmd = "wc $(location :cyclic1) > $@", -) - -genrule( - name = "check_sandbox_contain_WORKSPACE", - outs = [ "check_sandbox_contain_WORKSPACE.txt" ], - cmd = "ls -l $$(dirname \"$$(pwd)\") &> $@", -) - -genrule( - name = "check_proc_works", - outs = [ "check_proc_works.txt" ], - cmd = "sh -c 'cd /proc/self && echo $$$$ && exec cat stat | sed \"s/\\([^ ]*\\) .*/\\1/g\"' > $@", -) -EOF - cat << 'EOF' >> examples/genrule/datafile -this is a datafile EOF - # The workspace name is initialized in testenv.sh; use that var rather than - # hardcoding it here. The extra sed pass is so we can selectively expand that - # one var while keeping the rest of the heredoc literal. - cat | sed "s/{{WORKSPACE_NAME}}/$WORKSPACE_NAME/" >> examples/genrule/tool.sh << 'EOF' -#!/bin/sh -set -e -cp $(dirname $0)/tool.runfiles/{{WORKSPACE_NAME}}/examples/genrule/datafile $1 -echo "Tools work!" -EOF - chmod +x examples/genrule/tool.sh - cat << 'EOF' >> examples/genrule/starlark.bzl -def _starlark_breaks1_impl(ctx): - print(ctx.outputs.output.path) - ctx.actions.run_shell( - inputs = [ ctx.file.input ], - outputs = [ ctx.outputs.output ], - command = "wc %s `dirname %s`/b.txt &> %s" % (ctx.file.input.path, - ctx.file.input.path, - ctx.outputs.output.path), - execution_requirements = { tag: '' for tag in ctx.attr.action_tags }, - ) - -starlark_breaks1 = rule( - _starlark_breaks1_impl, - attrs = { - "input": attr.label(mandatory=True, allow_single_file=True), - "output": attr.output(mandatory=True), - "action_tags": attr.string_list(), - }, -) -EOF -} - -function test_sandboxed_genrule() { - bazel build examples/genrule:works &> $TEST_log \ - || fail "Hermetic genrule failed: examples/genrule:works" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/works.txt" ] \ - || fail "Genrule did not produce output: examples/genrule:works" -} - -function test_sandboxed_tooldir() { bazel build examples/genrule:tooldir &> $TEST_log \ || fail "Hermetic genrule failed: examples/genrule:tooldir" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/tooldir.txt" ] \ + [ -f "bazel-genfiles/examples/genrule/tooldir.txt" ] \ || fail "Genrule did not produce output: examples/genrule:works" - cat "${BAZEL_GENFILES_DIR}/examples/genrule/tooldir.txt" > $TEST_log + cat "bazel-genfiles/examples/genrule/tooldir.txt" > $TEST_log expect_log "genrule-setup.sh" } -function test_sandboxed_genrule_with_tools() { - bazel build examples/genrule:tools_work &> $TEST_log \ - || fail "Hermetic genrule failed: examples/genrule:tools_work" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/tools.txt" ] \ - || fail "Genrule did not produce output: examples/genrule:tools_work" -} - -# Test for #400: Linux sandboxing and relative symbolic links. -# -# let A = examples/genrule/symlinks/a/b/x.txt -> ../x.txt -# where examples/genrule/symlinks/a/b -> examples/genrule/symlinks/ok/sub -# thus the realpath of A is example/genrule/symlinks/ok/x.txt -# but if the code doesn't correctly resolve intermediate symlinks and instead -# uses string operations to handle ".." parts, it will arrive at: -# examples/genrule/symlinks/a/x.txt, which is wrong. -# -function test_sandbox_relative_symlink_in_inputs() { - bazel build examples/genrule:relative_symlinks &> $TEST_log \ - || fail "Hermetic genrule failed: examples/genrule:relative_symlinks" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/relative_symlinks.txt" ] \ - || fail "Genrule did not produce output: examples/genrule:relative_symlinks" -} - -function test_sandbox_undeclared_deps() { - output_file="${BAZEL_GENFILES_DIR}/examples/genrule/breaks1.txt" - - bazel build examples/genrule:breaks1 &> $TEST_log \ - && fail "Non-hermetic genrule succeeded: examples/genrule:breaks1" || true - - [ -f "$output_file" ] || - fail "Action did not produce output: $output_file" - - if [ $(wc -l $output_file) -gt 1 ]; then - fail "Output contained more than one line: $output_file" - fi - - fgrep "No such file or directory" $output_file || - fail "Output did not contain expected error message: $output_file" -} - -function test_sandbox_undeclared_deps_with_local() { - bazel build --incompatible_legacy_local_fallback \ - examples/genrule:breaks1_works_with_local &> $TEST_log \ - || fail "Non-hermetic genrule failed even though local=1: examples/genrule:breaks1_works_with_local" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/breaks1_works_with_local.txt" ] \ - || fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local" -} - -function test_sandbox_undeclared_deps_with_local_tag() { - bazel build --incompatible_legacy_local_fallback \ - examples/genrule:breaks1_works_with_local_tag &> $TEST_log \ - || fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:breaks1_works_with_local_tag" - [ -f "${BAZEL_GENFILES_DIR}/examples/genrule/breaks1_works_with_local_tag.txt" ] \ - || fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local_tag" -} - -function test_sandbox_undeclared_deps_with_local_tag_no_fallback() { - bazel build examples/genrule:breaks1_works_with_local_tag &> $TEST_log \ - && fail "Non-hermetic genrule suucceeded even though tags=['local']: examples/genrule:breaks1_works_with_local_tag" \ - || true -} - -function test_sandbox_undeclared_deps_starlark() { - output_file="${BAZEL_BIN_DIR}/examples/genrule/starlark_breaks1.txt" - bazel build examples/genrule:starlark_breaks1 &> $TEST_log \ - && fail "Non-hermetic genrule succeeded: examples/genrule:starlark_breaks1" || true - - [ -f "$output_file" ] || - fail "Action did not produce output: $output_file" - - if [ $(wc -l $output_file) -gt 1 ]; then - fail "Output contained more than one line: $output_file" - fi - - fgrep "No such file or directory" $output_file || - fail "Output did not contain expected error message: $output_file" -} - -function test_sandbox_undeclared_deps_starlark_with_local_tag() { - bazel build --incompatible_legacy_local_fallback \ - examples/genrule:starlark_breaks1_works_with_local_tag &> $TEST_log \ - || fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:starlark_breaks1_works_with_local_tag" - [ -f "${BAZEL_BIN_DIR}/examples/genrule/starlark_breaks1_works_with_local_tag.txt" ] \ - || fail "Action did not produce output: examples/genrule:starlark_breaks1_works_with_local_tag" -} - function test_sandbox_block_filesystem() { # The point of this test is to attempt to read something from the filesystem # that is blocked via --sandbox_block_path= and thus shouldn't be accessible. @@ -329,7 +80,7 @@ genrule( EOF touch pkg/a.txt - local output_file="${BAZEL_GENFILES_DIR}/pkg/breaks.txt" + local output_file="bazel-genfiles/pkg/breaks.txt" bazel build --sandbox_block_path="${block_path}" pkg:breaks \ &> $TEST_log \ @@ -347,282 +98,6 @@ EOF fail "Output did not contain expected error message: $output_file" } -function test_sandbox_cyclic_symlink_in_inputs() { - bazel build examples/genrule:breaks3 &> $TEST_log \ - && fail "Genrule with cyclic symlinks succeeded: examples/genrule:breaks3" || true - [ ! -f "${BAZEL_GENFILES_DIR}/examples/genrule/breaks3.txt" ] || { - output=$(cat "${BAZEL_GENFILES_DIR}/examples/genrule/breaks3.txt") - fail "Genrule with cyclic symlinks breaks3 succeeded with following output: $output" - } -} - -# Prepares common targets and services to be used by all network-related -# tests. The tests for remote network access are only enabled if the -# user has requested them by setting REMOTE_NETWORK_ADDRESS in the -# environment. -function setup_network_tests() { - local tags="${1}"; shift - - serve_file file_to_serve - - local socket_dir - socket_dir="$(mktemp -d /tmp/test.XXXXXX)" || fail "mktemp failed" - local socket="${socket_dir}/socket" - python $python_server --unix_socket="${socket}" always file_to_serve & - local pid="${!}" - - trap "kill_nc || true; kill '${pid}' || true; rm -f '${socket}'; rmdir '${socket_dir}'" EXIT - - mkdir pkg - cat <pkg/BUILD -genrule( - name = "localhost", - outs = [ "localhost.txt" ], - cmd = "curl -fo \$@ localhost:${nc_port}", - tags = [ ${tags} ], -) - -genrule( - name = "unix-socket", - outs = [ "unix-socket.txt" ], - cmd = "curl --unix-socket ${socket} -fo \$@ irrelevant-url", - tags = [ ${tags} ], -) - -genrule( - name = "loopback", - outs = [ "loopback.txt" ], - cmd = "python $python_server always $(pwd)/file_to_serve >port.txt & " - + "pid=\$\$!; " - + "while ! grep started port.txt; do sleep 1; done; " - + "port=\$\$(head -n 1 port.txt); " - + "curl -fo \$@ localhost:\$\$port; " - + "kill \$\$pid", -) -EOF - - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - local hostname="${REMOTE_NETWORK_ADDRESS%:*}" - local remote_ip - if which host 2>/dev/null; then - remote_ip="$(host -t A "${hostname}" | head -n 1 | awk '{print $4}')" - elif which dig 2>/dev/null; then - remote_ip="$(dig -t A "${hostname}" | grep "^${hostname}" | awk '{print $5}')" - else - fail "Don't know how to query IP of remote host ${hostname}" - fi - if [[ -z "${remote_ip}" ]]; then - fail "No IPv4 connectivity within unsandboxed test" - fi - - cat <>pkg/BUILD -genrule( - name = "remote-ip", - outs = [ "remote-ip.txt" ], - cmd = "curl -fo \$@ ${remote_ip}:80", - tags = [ ${tags} ], -) - -genrule( - name = "remote-name", - outs = [ "remote-name.txt" ], - cmd = "curl -fo \$@ '${REMOTE_NETWORK_ADDRESS}'", - tags = [ ${tags} ], -) -EOF - else - echo "Not registering tests for remote network sandboxing;" \ - "REMOTE_NETWORK_ADDRESS has not been set" - fi -} - -# Checks that the given target name, which must have been created by -# a previous call to setup_network_tests, can access the network. -function check_network_ok() { - local target="${1}"; shift - - ( - # macOS's /bin/bash is ancient and cannot reference $@ when -u is set. - # https://unix.stackexchange.com/questions/16560/bash-su-unbound-variable-with-set-u - set +u - - bazel build "${@}" "pkg:${target}" &>$TEST_log \ - || fail "'${target}' could not access the network" - ) -} - -# Checks that the given target name, which must have been created by -# a previous call to setup_network_tests, cannot access the network. -function check_network_not_ok() { - local target="${1}"; shift - - ( - # macOS's /bin/bash is ancient and cannot reference $@ when -u is set. - # https://unix.stackexchange.com/questions/16560/bash-su-unbound-variable-with-set-u - set +u - - bazel build "${@}" "pkg:${target}" &> $TEST_log \ - && fail "'${target}' trying to use network succeeded but should have failed" || true - ) - [[ ! -f "${BAZEL_GENFILES_DIR}/pkg/${target}.txt" ]] \ - || fail "'${target}' produced output but was expected to fail" -} - -function test_sandbox_network_access() { - setup_network_tests '"some-tag"' - - check_network_ok localhost - check_network_ok unix-socket - check_network_ok loopback - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - check_network_ok remote-ip - check_network_ok remote-name - fi -} - -function test_sandbox_block_network_access() { - setup_network_tests '"some-tag"' - - case "$(uname -s)" in - Linux) - # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity - # within the network namespace... but that doesn't seem to be the case. - check_network_not_ok localhost --experimental_sandbox_default_allow_network=false - ;; - - *) - check_network_ok localhost --experimental_sandbox_default_allow_network=false - ;; - esac - check_network_ok unix-socket --experimental_sandbox_default_allow_network=false - check_network_ok loopback --experimental_sandbox_default_allow_network=false - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=false - check_network_not_ok remote-name --experimental_sandbox_default_allow_network=false - fi -} - -function test_sandbox_network_access_with_local() { - cat >>$TEST_TMPDIR/bazelrc <<'EOF' -# With `--incompatible_legacy_local_fallback` turned off, we need to explicitly -# include a non-sandboxed strategy. -build --spawn_strategy=sandboxed,standalone --genrule_strategy=sandboxed,standalone -EOF - - setup_network_tests '"local"' - - check_network_ok localhost - check_network_ok unix-socket - check_network_ok loopback - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - check_network_ok remote-ip - check_network_ok remote-name - fi -} - -function test_sandbox_network_access_with_requires_network() { - setup_network_tests '"requires-network"' - - check_network_ok localhost --experimental_sandbox_default_allow_network=false - check_network_ok unix-socket --experimental_sandbox_default_allow_network=false - check_network_ok loopback --experimental_sandbox_default_allow_network=false - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - check_network_ok remote-ip --experimental_sandbox_default_allow_network=false - check_network_ok remote-name --experimental_sandbox_default_allow_network=false - fi -} - -function test_sandbox_network_access_with_block_network() { - setup_network_tests '"block-network"' - - case "$(uname -s)" in - Linux) - # TODO(jmmv): The linux-sandbox claims to allow localhost connectivity - # within the network namespace... but that doesn't seem to be the case. - check_network_not_ok localhost --experimental_sandbox_default_allow_network=true - ;; - - *) - check_network_ok localhost --experimental_sandbox_default_allow_network=true - ;; - esac - check_network_ok unix-socket --experimental_sandbox_default_allow_network=true - check_network_ok loopback --experimental_sandbox_default_allow_network=true - if [[ -n "${REMOTE_NETWORK_ADDRESS}" ]]; then - check_network_not_ok remote-ip --experimental_sandbox_default_allow_network=true - check_network_not_ok remote-name --experimental_sandbox_default_allow_network=true - fi -} - -function test_sandbox_can_resolve_own_hostname() { - setup_javatest_support - mkdir -p src/test/java/com/example - cat > src/test/java/com/example/HostNameTest.java <<'EOF' -package com.example; - -import static org.junit.Assert.*; - -import org.junit.Test; -import java.net.*; -import java.io.*; - -public class HostNameTest { - @Test - public void testGetHostName() throws Exception { - // This will throw an exception, if the local hostname cannot be resolved via DNS. - assertNotNull(InetAddress.getLocalHost().getHostName()); - } -} -EOF - cat > src/test/java/com/example/BUILD <<'EOF' -java_test( - name = "HostNameTest", - srcs = ["HostNameTest.java"], - deps = ['//third_party:junit4'], -) -EOF - - bazel test --test_output=streamed src/test/java/com/example:HostNameTest &> $TEST_log \ - || fail "test should have passed" -} - -function test_hostname_inside_sandbox_is_localhost_when_using_sandbox_fake_hostname_flag() { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: fake hostnames not supported in this system" 1>&2 - return 0 - fi - - setup_javatest_support - mkdir -p src/test/java/com/example - cat > src/test/java/com/example/HostNameIsLocalhostTest.java <<'EOF' -package com.example; - -import static org.junit.Assert.*; - -import org.junit.Test; -import java.net.*; -import java.io.*; - -public class HostNameIsLocalhostTest { - @Test - public void testHostNameIsLocalhost() throws Exception { - // This will throw an exception, if the local hostname cannot be resolved via DNS. - assertEquals("localhost", InetAddress.getLocalHost().getHostName()); - } -} -EOF - cat > src/test/java/com/example/BUILD <<'EOF' -java_test( - name = "HostNameIsLocalhostTest", - srcs = ["HostNameIsLocalhostTest.java"], - deps = ['//third_party:junit4'], -) -EOF - - bazel test --sandbox_fake_hostname --test_output=streamed src/test/java/com/example:HostNameIsLocalhostTest &> $TEST_log \ - || fail "test should have passed" -} - # TODO(philwo) - this doesn't work on Ubuntu 14.04 due to "unshare" being too # old and not understanding the --user flag. function DISABLED_test_sandbox_different_nobody_uid() { @@ -638,28 +113,6 @@ bazel build examples/genrule:works &> ${TEST_log} EOF } -function test_requires_root() { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: fake usernames not supported in this system" 1>&2 - return 0 - fi - - cat > test.sh <<'EOF' -#!/bin/sh -([ $(id -u) = "0" ] && [ $(id -g) = "0" ]) || exit 1 -EOF - chmod +x test.sh - cat > BUILD <<'EOF' -sh_test( - name = "test", - srcs = ["test.sh"], - tags = ["requires-fakeroot"], -) -EOF - bazel test --test_output=errors :test || fail "test did not pass" - bazel test --nocache_test_results --sandbox_fake_username --test_output=errors :test || fail "test did not pass" -} - # Tests that a pseudoterminal can be opened in linux when --sandbox_explicit_pseudoterminal is active function test_can_enable_pseudoterminals() { if [[ "$(uname -s)" != Linux ]]; then @@ -677,79 +130,7 @@ py_test( srcs = ["test.py"], ) EOF - bazel test --sandbox_explicit_pseudoterminal :test || fail "test did not pass" -} - -# Tests that /proc/self == /proc/$$. This should always be true unless the PID namespace is active without /proc being remounted correctly. -function test_sandbox_proc_self() { - if [[ ! -d /proc/self ]]; then - echo "Skipping tests: requires /proc" 1>&2 - return 0 - fi - - bazel build examples/genrule:check_proc_works >& $TEST_log || fail "build should have succeeded" - - ( - # Catch the head and tail commands failing. - set -e - if [[ "$(head -n1 "${BAZEL_GENFILES_DIR}/examples/genrule/check_proc_works.txt")" \ - != "$(tail -n1 "${BAZEL_GENFILES_DIR}/examples/genrule/check_proc_works.txt")" ]] ; then - fail "Reading PID from /proc/self/stat should have worked, instead have these: $(cat "${BAZEL_GENFILES_DIR}/examples/genrule/check_proc_works.txt")" - fi - ) -} - -function test_succeeding_action_with_ioexception_while_copying_outputs_throws_correct_exception() { - cat > BUILD <<'EOF' -genrule( - name = "test", - outs = ["readonlydir/output.txt"], - cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`", -) -EOF - bazel build :test &> $TEST_log \ - && fail "build should have failed" || true - - # This is the generic "we caught an IOException" log message used by the - # SandboxedStrategy. We don't want to see this in this case, because we have - # special handling that prints a better error message and then lets the - # sandbox code throw the actual ExecException. - expect_not_log "I/O error during sandboxed execution" - - # There was no ExecException during sandboxed execution, because the action - # returned an exit code of 0. - expect_not_log "Executing genrule //:test failed: linux-sandbox failed: error executing command" - - # This is the error message telling us that some output artifacts couldn't be copied. - expect_log "Could not move output artifacts from sandboxed execution" - - # The build fails, because the action didn't generate its output artifact. - expect_log "ERROR:.*Executing genrule //:test failed" -} - -function test_failing_action_with_ioexception_while_copying_outputs_throws_correct_exception() { - cat > BUILD <<'EOF' -genrule( - name = "test", - outs = ["readonlydir/output.txt"], - cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`; exit 1", -) -EOF - bazel build :test &> $TEST_log \ - && fail "build should have failed" || true - - # This is the generic "we caught an IOException" log message used by the - # SandboxedStrategy. We don't want to see this in this case, because we have - # special handling that prints a better error message and then lets the - # sandbox code throw the actual ExecException. - expect_not_log "I/O error during sandboxed execution" - - # This is the error message printed by the EventHandler telling us that some - # output artifacts couldn't be copied. - expect_log "Could not move output artifacts from sandboxed execution" - - # This is the UserExecException telling us that the build failed. - expect_log "Executing genrule //:test failed:" + bazel test --sandbox_explicit_pseudoterminal --verbose_failures --sandbox_debug :test || fail "test did not pass" } function test_sandbox_debug() { @@ -887,253 +268,6 @@ EOF bazel build //pkg:a &>$TEST_log || fail "expected build to succeed" } -function test_read_non_hermetic_tmp { - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" -} - -function test_hermetic_tmp_under_tmp { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 - return 0 - fi - - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p "${temp_dir}/workspace/a" - mkdir -p "${temp_dir}/package-path/b" - mkdir -p "${temp_dir}/repo/c" - mkdir -p "${temp_dir}/output-base" - - cd "${temp_dir}/workspace" - cat > WORKSPACE < a/BUILD <<'EOF' -genrule( - name = "g", - outs = ["go"], - srcs = [], - cmd = "echo GO > $@", -) - -sh_binary( - name = "bin", - srcs = ["bin.sh"], - data = [":s", ":go", "//b:s", "//b:go", "@repo//c:s", "@repo//c:go"], -) - -genrule( - name = "t", - tools = [":bin"], - srcs = [":s", ":go", "//b:s", "//b:go", "@repo//c:s", "@repo//c:go"], - outs = ["to"], - cmd = "\n".join([ - "RUNFILES=$(location :bin).runfiles/__main__", - "S=$(location :s); GO=$(location :go)", - "BS=$(location //b:s); BGO=$(location //b:go)", - "RS=$(location @repo//c:s); RGO=$(location @repo//c:go)", - "for i in $$S $$GO $$BS $$BGO $$RS $$RGO; do", - " echo reading $$i", - " cat $$i >> $@", - "done", - "for i in a/s a/go b/s b/go ../repo/c/s ../repo/c/go; do", - " echo reading $$RUNFILES/$$i", - " cat $$RUNFILES/$$i >> $@", - "done", - ]), -) -EOF - - touch a/bin.sh - chmod +x a/bin.sh - - touch ../repo/WORKSPACE - cat > ../repo/c/BUILD <<'EOF' -exports_files(["s"]) - -genrule( - name = "g", - outs = ["go"], - srcs = [], - cmd = "echo GO > $@", - visibility = ["//visibility:public"], -) -EOF - - cat > ../package-path/b/BUILD <<'EOF' -exports_files(["s"]) - -genrule( - name = "g", - outs = ["go"], - srcs = [], - cmd = "echo GO > $@", - visibility = ["//visibility:public"], -) -EOF - - touch "a/s" "../package-path/b/s" "../repo/c/s" - - cat WORKSPACE - bazel \ - --output_base="${temp_dir}/output-base" \ - build \ - --incompatible_sandbox_hermetic_tmp \ - --package_path="%workspace%:${temp_dir}/package-path" \ - //a:t || fail "build failed" -} - -function test_read_hermetic_tmp { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 - return 0 - fi - - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" -} - -function test_read_hermetic_tmp_user_override { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 - return 0 - fi - - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" -} - -function test_write_non_hermetic_tmp { - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" - [[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist" -} - -function test_write_hermetic_tmp { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 - return 0 - fi - - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" - [[ ! -f "${temp_dir}" ]] || fail "Expected ${temp_dir} to not exit" -} - -function test_write_hermetic_tmp_user_override { - if [[ "$(uname -s)" != Linux ]]; then - echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 - return 0 - fi - - temp_dir=$(mktemp -d /tmp/test.XXXXXX) - trap 'rm -rf ${temp_dir}' EXIT - - mkdir -p pkg - cat > pkg/BUILD <<'EOF' -sh_test( - name = "tmp_test", - srcs = ["tmp_test.sh"], -) -EOF - - cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" - [[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist" -} - # The test shouldn't fail if the environment doesn't support running it. check_sandbox_allowed || exit 0 diff --git a/src/test/shell/integration/sandboxing_test.sh b/src/test/shell/integration/sandboxing_test.sh index 2586e3c5d13737..332d4645dc599a 100755 --- a/src/test/shell/integration/sandboxing_test.sh +++ b/src/test/shell/integration/sandboxing_test.sh @@ -24,6 +24,11 @@ CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CURRENT_DIR}/../integration_test_setup.sh" \ || { echo "integration_test_setup.sh not found!" >&2; exit 1; } +function set_up() { + add_to_bazelrc "build --spawn_strategy=sandboxed" + add_to_bazelrc "build --genrule_strategy=sandboxed" +} + function tear_down() { bazel clean --expunge bazel shutdown @@ -41,8 +46,7 @@ EOF local output_base="$(bazel info output_base)" do_build() { - bazel build --genrule_strategy=sandboxed --sandbox_debug \ - "${extra_args[@]}" //pkg + bazel build --sandbox_debug "${extra_args[@]}" //pkg } do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed" @@ -82,7 +86,7 @@ function do_succeed_when_executor_not_initialized_test() { mkdir pkg mkfifo pkg/BUILD - bazel build --spawn_strategy=sandboxed --nobuild "${@}" //pkg:all \ + bazel build --nobuild "${@}" //pkg:all \ >"${TEST_log}" 2>&1 & local pid="${!}" @@ -128,7 +132,7 @@ EOF local output_base="$(bazel info output_base)" do_build() { - bazel build --genrule_strategy=sandboxed --sandbox_debug //pkg + bazel build --sandbox_debug //pkg } do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed" @@ -157,7 +161,7 @@ EOF local sandbox_base="${output_base}/sandbox" rm -rf ${sandbox_base} - bazel build --genrule_strategy=sandboxed \ + bazel build \ --incompatible_legacy_local_fallback //pkg \ >"${TEST_log}" 2>&1 || fail "Expected build to succeed" @@ -176,7 +180,7 @@ EOF local sandbox_base="${output_base}/sandbox" rm -rf ${sandbox_base} - bazel build --genrule_strategy=sandboxed \ + bazel build \ --noincompatible_legacy_local_fallback //pkg \ >"${TEST_log}" 2>&1 && fail "Expected build to fail" || true # Still warning in this case even when the flag is flipped @@ -211,7 +215,7 @@ EOF local output_base="$(bazel info output_base)" do_build() { - bazel build --genrule_strategy=sandboxed //pkg + bazel build //pkg } do_build >"${TEST_log}" 2>&1 || fail "Expected build to succeed" @@ -244,7 +248,7 @@ EOF # Ensure that, even if we don't clean up the sandbox at all (with # --sandbox_debug), consecutive builds don't step on each other by trying to # reuse previous spawn identifiers. - bazel build --genrule_strategy=sandboxed --sandbox_debug //pkg \ + bazel build --sandbox_debug //pkg \ >"${TEST_log}" 2>&1 || fail "Expected build to succeed" echo foo >>pkg/pkg.in done @@ -325,4 +329,545 @@ EOF >"${TEST_log}" 2>&1 && fail "Expected build to fail" || true } +function test_sandboxed_genrule() { + mkdir -p examples/genrule + cat << 'EOF' > examples/genrule/a.txt +foo bar bz +EOF + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "works", + srcs = [ "a.txt" ], + outs = [ "works.txt" ], + cmd = "wc $(location :a.txt) > $@", +) +EOF + + bazel build examples/genrule:works &> $TEST_log \ + || fail "Hermetic genrule failed: examples/genrule:works" + [ -f "bazel-genfiles/examples/genrule/works.txt" ] \ + || fail "Genrule did not produce output: examples/genrule:works" +} + +function test_sandboxed_genrule_with_tools() { + mkdir -p examples/genrule + + cat << 'EOF' > examples/genrule/BUILD +sh_binary( + name = "tool", + srcs = ["tool.sh"], + data = ["datafile"], +) + +genrule( + name = "tools_work", + srcs = [], + outs = ["tools.txt"], + cmd = "$(location :tool) $@", + tools = [":tool"], +) +EOF + cat << 'EOF' >> examples/genrule/datafile +this is a datafile +EOF + # The workspace name is initialized in testenv.sh; use that var rather than + # hardcoding it here. The extra sed pass is so we can selectively expand that + # one var while keeping the rest of the heredoc literal. + cat | sed "s/{{WORKSPACE_NAME}}/$WORKSPACE_NAME/" >> examples/genrule/tool.sh << 'EOF' +#!/bin/sh + +set -e +cp $(dirname $0)/tool.runfiles/{{WORKSPACE_NAME}}/examples/genrule/datafile $1 +echo "Tools work!" +EOF + chmod +x examples/genrule/tool.sh + + bazel build examples/genrule:tools_work &> $TEST_log \ + || fail "Hermetic genrule failed: examples/genrule:tools_work" + [ -f "bazel-genfiles/examples/genrule/tools.txt" ] \ + || fail "Genrule did not produce output: examples/genrule:tools_work" +} + +# Test for #400: Linux sandboxing and relative symbolic links. +# +# let A = examples/genrule/symlinks/a/b/x.txt -> ../x.txt +# where examples/genrule/symlinks/a/b -> examples/genrule/symlinks/ok/sub +# thus the realpath of A is example/genrule/symlinks/ok/x.txt +# but if the code doesn't correctly resolve intermediate symlinks and instead +# uses string operations to handle ".." parts, it will arrive at: +# examples/genrule/symlinks/a/x.txt, which is wrong. +# +function test_sandbox_relative_symlink_in_inputs() { + mkdir -p examples/genrule + + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "relative_symlinks", + srcs = [ "symlinks/a/b/x.txt" ], + outs = [ "relative_symlinks.txt" ], + cmd = "cat $(location :symlinks/a/b/x.txt) > $@", +) +EOF + + mkdir -p examples/genrule/symlinks/{a,ok/sub} + echo OK > examples/genrule/symlinks/ok/x.txt + ln -s $PWD/examples/genrule/symlinks/ok/sub examples/genrule/symlinks/a/b + ln -s ../x.txt examples/genrule/symlinks/a/b/x.txt + + bazel build examples/genrule:relative_symlinks &> $TEST_log \ + || fail "Hermetic genrule failed: examples/genrule:relative_symlinks" + [ -f "bazel-genfiles/examples/genrule/relative_symlinks.txt" ] \ + || fail "Genrule did not produce output: examples/genrule:relative_symlinks" +} + +function test_sandbox_undeclared_deps() { + output_file="bazel-genfiles/examples/genrule/breaks1.txt" + + mkdir -p examples/genrule + cat << 'EOF' > examples/genrule/a.txt +foo bar bz +EOF + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "breaks1", + srcs = [ "a.txt" ], + outs = [ "breaks1.txt" ], + cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt &> $@", +) +EOF + + bazel build examples/genrule:breaks1 &> $TEST_log \ + && fail "Non-hermetic genrule succeeded: examples/genrule:breaks1" || true + + [ -f "$output_file" ] || + fail "Action did not produce output: $output_file" + + if [ $(wc -l $output_file) -gt 1 ]; then + fail "Output contained more than one line: $output_file" + fi + + fgrep "No such file or directory" $output_file || + fail "Output did not contain expected error message: $output_file" +} + +function test_sandbox_undeclared_deps_with_local() { + mkdir -p examples/genrule + echo "foo bar bz" > examples/genrule/a.txt + echo "apples oranges bananas" > examples/genrule/b.txt + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "breaks1_works_with_local", + srcs = [ "a.txt" ], + outs = [ "breaks1_works_with_local.txt" ], + cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@", + local = 1, +) +EOF + bazel build --incompatible_legacy_local_fallback \ + examples/genrule:breaks1_works_with_local &> $TEST_log \ + || fail "Non-hermetic genrule failed even though local=1: examples/genrule:breaks1_works_with_local" + [ -f "bazel-genfiles/examples/genrule/breaks1_works_with_local.txt" ] \ + || fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local" +} + +function test_sandbox_undeclared_deps_with_local_tag() { + mkdir -p examples/genrule + echo "foo bar bz" > examples/genrule/a.txt + echo "apples oranges bananas" > examples/genrule/b.txt + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "breaks1_works_with_local_tag", + srcs = [ "a.txt" ], + outs = [ "breaks1_works_with_local_tag.txt" ], + cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@", + tags = [ "local" ], +) +EOF + bazel build --incompatible_legacy_local_fallback \ + examples/genrule:breaks1_works_with_local_tag &> $TEST_log \ + || fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:breaks1_works_with_local_tag" + [ -f "bazel-genfiles/examples/genrule/breaks1_works_with_local_tag.txt" ] \ + || fail "Genrule did not produce output: examples/genrule:breaks1_works_with_local_tag" +} + +function test_sandbox_undeclared_deps_with_local_tag_no_fallback() { + mkdir -p examples/genrule + echo "foo bar bz" > examples/genrule/a.txt + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "breaks1_works_with_local_tag", + srcs = [ "a.txt" ], + outs = [ "breaks1_works_with_local_tag.txt" ], + cmd = "wc $(location :a.txt) `dirname $(location :a.txt)`/b.txt > $@", + tags = [ "local" ], +) +EOF + bazel build examples/genrule:breaks1_works_with_local_tag &> $TEST_log \ + && fail "Non-hermetic genrule suucceeded even though tags=['local']: examples/genrule:breaks1_works_with_local_tag" \ + || true +} + +function write_starlark_breaks1() { + cat << 'EOF' >> examples/genrule/starlark.bzl +def _starlark_breaks1_impl(ctx): + print(ctx.outputs.output.path) + ctx.actions.run_shell( + inputs = [ ctx.file.input ], + outputs = [ ctx.outputs.output ], + command = "wc %s `dirname %s`/b.txt &> %s" % (ctx.file.input.path, + ctx.file.input.path, + ctx.outputs.output.path), + execution_requirements = { tag: '' for tag in ctx.attr.action_tags }, + ) + +starlark_breaks1 = rule( + _starlark_breaks1_impl, + attrs = { + "input": attr.label(mandatory=True, allow_single_file=True), + "output": attr.output(mandatory=True), + "action_tags": attr.string_list(), + }, +) +EOF +} + +function test_sandbox_undeclared_deps_starlark() { + mkdir -p examples/genrule + echo "foo bar bz" > examples/genrule/a.txt + echo "apples oranges bananas" > examples/genrule/b.txt + cat << 'EOF' > examples/genrule/BUILD +load('//examples/genrule:starlark.bzl', 'starlark_breaks1') + +starlark_breaks1( + name = "starlark_breaks1", + input = "a.txt", + output = "starlark_breaks1.txt", +) +EOF + write_starlark_breaks1 + output_file="bazel-bin/examples/genrule/starlark_breaks1.txt" + bazel build examples/genrule:starlark_breaks1 &> $TEST_log \ + && fail "Non-hermetic genrule succeeded: examples/genrule:starlark_breaks1" || true + + [ -f "$output_file" ] || + fail "Action did not produce output: $output_file" + + if [ $(wc -l $output_file) -gt 1 ]; then + fail "Output contained more than one line: $output_file" + fi + + fgrep "No such file or directory" $output_file || + fail "Output did not contain expected error message: $output_file" +} + +function test_sandbox_undeclared_deps_starlark_with_local_tag() { + mkdir -p examples/genrule + echo "foo bar bz" > examples/genrule/a.txt + echo "apples oranges bananas" > examples/genrule/b.txt + cat << 'EOF' > examples/genrule/BUILD +load('//examples/genrule:starlark.bzl', 'starlark_breaks1') + +starlark_breaks1( + name = "starlark_breaks1", + input = "a.txt", + output = "starlark_breaks1.txt", +) + +starlark_breaks1( + name = "starlark_breaks1_works_with_local_tag", + input = "a.txt", + output = "starlark_breaks1_works_with_local_tag.txt", + action_tags = [ "local" ], +) +EOF + write_starlark_breaks1 + + bazel build --incompatible_legacy_local_fallback \ + examples/genrule:starlark_breaks1_works_with_local_tag &> $TEST_log \ + || fail "Non-hermetic genrule failed even though tags=['local']: examples/genrule:starlark_breaks1_works_with_local_tag" + [ -f "bazel-bin/examples/genrule/starlark_breaks1_works_with_local_tag.txt" ] \ + || fail "Action did not produce output: examples/genrule:starlark_breaks1_works_with_local_tag" +} + +function test_sandbox_cyclic_symlink_in_inputs() { + mkdir -p examples/genrule + # Create cyclic symbolic links to check whether the strategy catches that. + ln -sf cyclic2 examples/genrule/cyclic1 + ln -sf cyclic1 examples/genrule/cyclic2 + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "breaks3", + srcs = [ "cyclic1", "cyclic2" ], + outs = [ "breaks3.txt" ], + cmd = "wc $(location :cyclic1) > $@", +) +EOF + bazel build examples/genrule:breaks3 &> $TEST_log \ + && fail "Genrule with cyclic symlinks succeeded: examples/genrule:breaks3" || true + [ ! -f "bazel-genfiles/examples/genrule/breaks3.txt" ] || { + output=$(cat "bazel-genfiles/examples/genrule/breaks3.txt") + fail "Genrule with cyclic symlinks breaks3 succeeded with following output: $output" + } +} + + +function test_requires_root() { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: fake usernames not supported in this system" 1>&2 + return 0 + fi + + cat > test.sh <<'EOF' +#!/bin/sh +([ $(id -u) = "0" ] && [ $(id -g) = "0" ]) || exit 1 +EOF + chmod +x test.sh + cat > BUILD <<'EOF' +sh_test( + name = "test", + srcs = ["test.sh"], + tags = ["requires-fakeroot"], +) +EOF + bazel test --test_output=errors :test || fail "test did not pass" + bazel test --nocache_test_results --sandbox_fake_username --test_output=errors :test || fail "test did not pass" +} + +# Tests that /proc/self == /proc/$$. This should always be true unless the PID namespace is active without /proc being remounted correctly. +function test_sandbox_proc_self() { + if [[ ! -d /proc/self ]]; then + echo "Skipping tests: requires /proc" 1>&2 + return 0 + fi + mkdir -p examples/genrule + cat << 'EOF' > examples/genrule/BUILD +genrule( + name = "check_proc_works", + outs = [ "check_proc_works.txt" ], + cmd = "sh -c 'cd /proc/self && echo $$$$ && exec cat stat | sed \"s/\\([^ ]*\\) .*/\\1/g\"' > $@", +) +EOF + + bazel build examples/genrule:check_proc_works >& $TEST_log || fail "build should have succeeded" + + ( + # Catch the head and tail commands failing. + set -e + if [[ "$(head -n1 "bazel-genfiles/examples/genrule/check_proc_works.txt")" \ + != "$(tail -n1 "bazel-genfiles/examples/genrule/check_proc_works.txt")" ]] ; then + fail "Reading PID from /proc/self/stat should have worked, instead have these: $(cat "bazel-genfiles/examples/genrule/check_proc_works.txt")" + fi + ) +} + +function test_succeeding_action_with_ioexception_while_copying_outputs_throws_correct_exception() { + cat > BUILD <<'EOF' +genrule( + name = "test", + outs = ["readonlydir/output.txt"], + cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`", +) +EOF + bazel build :test &> $TEST_log \ + && fail "build should have failed" || true + + # This is the generic "we caught an IOException" log message used by the + # SandboxedStrategy. We don't want to see this in this case, because we have + # special handling that prints a better error message and then lets the + # sandbox code throw the actual ExecException. + expect_not_log "I/O error during sandboxed execution" + + # There was no ExecException during sandboxed execution, because the action + # returned an exit code of 0. + expect_not_log "Executing genrule //:test failed: linux-sandbox failed: error executing command" + + # This is the error message telling us that some output artifacts couldn't be copied. + expect_log "Could not move output artifacts from sandboxed execution" + + # The build fails, because the action didn't generate its output artifact. + expect_log "ERROR:.*Executing genrule //:test failed" +} + +function test_failing_action_with_ioexception_while_copying_outputs_throws_correct_exception() { + cat > BUILD <<'EOF' +genrule( + name = "test", + outs = ["readonlydir/output.txt"], + cmd = "touch $(location readonlydir/output.txt); chmod 0 $(location readonlydir/output.txt); chmod 0500 `dirname $(location readonlydir/output.txt)`; exit 1", +) +EOF + bazel build :test &> $TEST_log \ + && fail "build should have failed" || true + + # This is the generic "we caught an IOException" log message used by the + # SandboxedStrategy. We don't want to see this in this case, because we have + # special handling that prints a better error message and then lets the + # sandbox code throw the actual ExecException. + expect_not_log "I/O error during sandboxed execution" + + # This is the error message printed by the EventHandler telling us that some + # output artifacts couldn't be copied. + expect_log "Could not move output artifacts from sandboxed execution" + + # This is the UserExecException telling us that the build failed. + expect_log "Executing genrule //:test failed:" +} + +function test_read_non_hermetic_tmp { + sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc" + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" +} + +function test_read_hermetic_tmp { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 + return 0 + fi + + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" +} + +function test_read_hermetic_tmp_user_override { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 + return 0 + fi + sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc" + + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" +} + +function test_write_non_hermetic_tmp { + sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc" + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" + [[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist" +} + +function test_write_hermetic_tmp { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 + return 0 + fi + + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" + [[ ! -f "${temp_dir}" ]] || fail "Expected ${temp_dir} to not exit" +} + +function test_write_hermetic_tmp_user_override { + if [[ "$(uname -s)" != Linux ]]; then + echo "Skipping test: --incompatible_sandbox_hermetic_tmp is only supported in Linux" 1>&2 + return 0 + fi + sed -i.bak '/sandbox_tmpfs_path/d' "$bazelrc" + + temp_dir=$(mktemp -d /tmp/test.XXXXXX) + trap 'rm -rf ${temp_dir}' EXIT + + mkdir -p pkg + cat > pkg/BUILD <<'EOF' +sh_test( + name = "tmp_test", + srcs = ["tmp_test.sh"], +) +EOF + + cat > pkg/tmp_test.sh <$TEST_log || fail "Expected test to pass" + [[ -f "${temp_dir}/file" ]] || fail "Expected ${temp_dir}/file to exist" +} + run_suite "sandboxing"