#!/usr/bin/env bash set -xeuo pipefail srcd=$(cd $(dirname "$0") && pwd) . ${srcd}/libtest.sh bn=$(basename "$0") test_count=0 ok () { test_count=$((test_count + 1)) echo ok $test_count "$@" } ok_skip () { ok "# SKIP" "$@" } done_testing () { echo "1..$test_count" } # Test help ${BWRAP} --help > help.txt assert_file_has_content help.txt "usage: ${BWRAP}" ok "Help works" for ALT in "" "--unshare-user-try" "--unshare-pid" "--unshare-user-try --unshare-pid"; do # Test fuse fs as bind source if [ "x$FUSE_DIR" != "x" ]; then $RUN $ALT --proc /proc --dev /dev --bind $FUSE_DIR /tmp/foo true ok "can bind-mount a FUSE directory with $ALT" else ok_skip "no FUSE support" fi # no --dev => no devpts => no map_root workaround $RUN $ALT --proc /proc true ok "can mount /proc with $ALT" # No network $RUN $ALT --unshare-net --proc /proc --dev /dev true ok "can unshare network, create new /dev with $ALT" # Unreadable file echo -n "expect EPERM: " >&2 # Test caps when bwrap is not setuid if test -n "${bwrap_is_suid:-}"; then CAP="--cap-add ALL" else CAP="" fi if ! cat /etc/shadow >/dev/null && $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /tmp/foo; then assert_not_reached Could read /etc/shadow via /tmp/foo bind-mount fi if ! cat /etc/shadow >/dev/null && $RUN $CAP $ALT --unshare-net --proc /proc --bind /etc/shadow /tmp/foo cat /etc/shadow; then assert_not_reached Could read /etc/shadow fi ok "cannot read /etc/shadow with $ALT" # Unreadable dir if [ "x$UNREADABLE" != "x" ]; then echo -n "expect EPERM: " >&2 if $RUN $ALT --unshare-net --proc /proc --dev /dev --bind $UNREADABLE /tmp/foo cat /tmp/foo; then assert_not_reached Could read $UNREADABLE fi ok "cannot read $UNREADABLE with $ALT" else ok_skip "not sure what unreadable file to use" fi # bind dest in symlink (https://github.com/projectatomic/bubblewrap/pull/119) $RUN $ALT --dir /tmp/dir --symlink dir /tmp/link --bind /etc /tmp/link true ok "can bind a destination over a symlink" done # Test symlink behaviour rm -f ./symlink $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2 readlink ./symlink > target.txt assert_file_has_content target.txt /dev/null ok "--symlink works" $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/null "$(pwd)/symlink" true >&2 ok "--symlink is idempotent" if $RUN --ro-bind / / --bind "$(pwd)" "$(pwd)" --symlink /dev/full "$(pwd)/symlink" true 2>err.txt; then fatal "creating a conflicting symlink should have failed" else assert_file_has_content err.txt "Can't make symlink .*: existing destination is /dev/null" fi ok "--symlink doesn't overwrite a conflicting symlink" # Test devices $RUN --unshare-pid --dev /dev ls -al /dev/{stdin,stdout,stderr,null,random,urandom,fd,core} >/dev/null ok "all expected devices were created" # Test --as-pid-1 $RUN --unshare-pid --as-pid-1 --bind / / bash -c 'echo $$' > as_pid_1.txt assert_file_has_content as_pid_1.txt "1" ok "can run as pid 1" # Test --info-fd and --json-status-fd if $RUN --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'exit 42' 42>info.json 43>json-status.json 2>err.txt; then fatal "should have been exit 42" fi assert_file_has_content info.json '"child-pid": [0-9]' assert_file_has_content json-status.json '"child-pid": [0-9]' assert_file_has_content_literal json-status.json '"exit-code": 42' ok "info and json-status fd" DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L --format "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt) for NS in "ipc" "mnt" "net" "pid" "uts"; do want=$(echo "$DATA" | grep "/proc/self/ns/$NS" | awk '{print $2}') assert_file_has_content info.json "$want" assert_file_has_content json-status.json "$want" done ok "namespace id info in info and json-status fd" if ! command -v strace >/dev/null || ! strace -h | grep -v -e default | grep -e fault >/dev/null; then ok_skip "no strace fault injection" else ! strace -o /dev/null -f -e trace=prctl -e fault=prctl:when=39 $RUN --die-with-parent --json-status-fd 42 true 42>json-status.json assert_not_file_has_content json-status.json '"exit-code": [0-9]' ok "pre-exec failure doesn't include exit-code in json-status" fi notanexecutable=/ $RUN --json-status-fd 42 $notanexecutable 42>json-status.json || true assert_not_file_has_content json-status.json '"exit-code": [0-9]' ok "exec failure doesn't include exit-code in json-status" # These tests require --unshare-user if test -n "${bwrap_is_suid:-}"; then ok_skip "no --cap-add support" ok_skip "no --cap-add support" ok_skip "no --disable-userns" else BWRAP_RECURSE="$BWRAP --unshare-user --uid 0 --gid 0 --cap-add ALL --bind / / --bind /proc /proc" # $BWRAP May be inaccessible due to the user namespace so use /proc/self/exe $BWRAP_RECURSE -- /proc/self/exe --unshare-all --bind / / --bind /proc /proc echo hello > recursive_proc.txt assert_file_has_content recursive_proc.txt "hello" ok "can mount /proc recursively" $BWRAP_RECURSE -- /proc/self/exe --unshare-all ${BWRAP_RO_HOST_ARGS} findmnt > recursive-newroot.txt assert_file_has_content recursive-newroot.txt "/usr" ok "can pivot to new rootfs recursively" $BWRAP --dev-bind / / -- true ! $BWRAP --assert-userns-disabled --dev-bind / / -- true $BWRAP --unshare-user --disable-userns --dev-bind / / -- true ! $BWRAP --unshare-user --disable-userns --dev-bind / / -- $BWRAP --dev-bind / / -- true $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" $BWRAP --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true" $BWRAP_RECURSE --dev-bind / / -- true ! $BWRAP_RECURSE --assert-userns-disabled --dev-bind / / -- true $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- true ! $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- /proc/self/exe --dev-bind / / -- true $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 2 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "echo 100 > /proc/sys/user/max_user_namespaces || true; ! $BWRAP --unshare-user --dev-bind / / -- true" $BWRAP_RECURSE --unshare-user --disable-userns --dev-bind / / -- sh -c "! $BWRAP --unshare-user --dev-bind / / --assert-userns-disabled -- true" ok "can disable nested userns" fi # Test error prefixing if $RUN --unshare-pid --bind /source-enoent /dest true 2>err.txt; then assert_not_reached "bound nonexistent source" fi assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent" ok "error prefixing" if ! ${is_uidzero}; then # When invoked as non-root, check that by default we have no caps left for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" "--as-pid-1"; do e=0 $RUN $OPT --unshare-pid getpcaps 1 >&2 2> caps.test || e=$? sed -e 's/^/# /' < caps.test >&2 test "$e" = 0 assert_not_file_has_content caps.test ': =.*cap' done ok "we have no caps as uid != 0" else capsh --print | sed -e 's/no-new-privs=0/no-new-privs=1/' > caps.expected for OPT in "" "--as-pid-1"; do $RUN $OPT --unshare-pid capsh --print >caps.test diff -u caps.expected caps.test done # And test that we can drop all, as well as specific caps $RUN $OPT --cap-drop ALL --unshare-pid capsh --print >caps.test assert_file_has_content caps.test 'Current: =$' # Check for dropping kill/fowner (we assume all uid 0 callers have this) # But we should still have net_bind_service for example $RUN $OPT --cap-drop CAP_KILL --cap-drop CAP_FOWNER --unshare-pid capsh --print >caps.test # capsh's output format changed from v2.29 -> drops are now indicated with -eip if grep 'Current: =.*+eip$' caps.test; then assert_not_file_has_content caps.test '^Current: =.*cap_kill.*+eip$' assert_not_file_has_content caps.test '^Current: =.*cap_fowner.*+eip$' assert_file_has_content caps.test '^Current: =.*cap_net_bind_service.*+eip$' else assert_file_has_content caps.test '^Current: =eip.*cap_kill.*-eip$' assert_file_has_content caps.test '^Current: =eip.*cap_fowner.*-eip$' assert_not_file_has_content caps.test '^Current: =.*cap_net_bind_service.*-eip$' fi ok "we have the expected caps as uid 0" fi # Test --die-with-parent cat >lockf-n.py < test.args printf '%s--dir\0/tmp/hello/world2\0' '' > test.args2 printf '%s--dir\0/tmp/hello/world3\0' '' > test.args3 $RUN --args 3 --args 4 --args 5 /bin/sh -c 'test -d /tmp/hello/world && test -d /tmp/hello/world2 && test -d /tmp/hello/world3' 3 bin/--inadvisable-executable-name-- echo "echo hello" >> bin/--inadvisable-executable-name-- chmod +x bin/--inadvisable-executable-name-- PATH="${srcd}:$PATH" $RUN -- sh -c "echo hello" > stdout assert_file_has_content stdout hello ok "we can run with --" PATH="$(pwd)/bin:$PATH" $RUN -- --inadvisable-executable-name-- > stdout assert_file_has_content stdout hello ok "we can run an inadvisable executable name with --" if $RUN -- --dev-bind /dev /dev sh -c 'echo should not have run'; then assert_not_reached "'--dev-bind' should have been interpreted as a (silly) executable name" fi ok "options like --dev-bind are defanged by --" if command -v mktemp > /dev/null; then tempfile="$(mktemp /tmp/bwrap-test-XXXXXXXX)" echo "hello" > "$tempfile" $BWRAP --bind / / cat "$tempfile" > stdout assert_file_has_content stdout hello ok "bind-mount of / exposes real /tmp" $BWRAP --bind / / --bind /tmp /tmp cat "$tempfile" > stdout assert_file_has_content stdout hello ok "bind-mount of /tmp exposes real /tmp" if [ -d /mnt ] && [ ! -L /mnt ]; then $BWRAP --bind / / --bind /tmp /mnt cat "/mnt/${tempfile#/tmp/}" > stdout assert_file_has_content stdout hello ok "bind-mount of /tmp onto /mnt exposes real /tmp" else ok_skip "/mnt does not exist or is a symlink" fi else ok_skip "mktemp not found" ok_skip "mktemp not found" ok_skip "mktemp not found" fi if $RUN test -d /tmp/oldroot; then assert_not_reached "/tmp/oldroot should not be visible" fi if $RUN test -d /tmp/newroot; then assert_not_reached "/tmp/newroot should not be visible" fi echo "hello" > input.$$ $BWRAP --bind / / --bind "$(pwd)" /tmp cat /tmp/input.$$ > stdout assert_file_has_content stdout hello if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/oldroot; then assert_not_reached "/tmp/oldroot should not be visible" fi if $BWRAP --bind / / --bind "$(pwd)" /tmp test -d /tmp/newroot; then assert_not_reached "/tmp/newroot should not be visible" fi ok "we can mount another directory onto /tmp" echo "hello" > input.$$ $RUN --bind "$(pwd)" /tmp/here cat /tmp/here/input.$$ > stdout assert_file_has_content stdout hello if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/oldroot; then assert_not_reached "/tmp/oldroot should not be visible" fi if $RUN --bind "$(pwd)" /tmp/here test -d /tmp/newroot; then assert_not_reached "/tmp/newroot should not be visible" fi ok "we can mount another directory inside /tmp" touch some-file mkdir -p some-dir rm -fr new-dir-mountpoint rm -fr new-file-mountpoint $RUN \ --bind "$(pwd -P)/some-dir" "$(pwd -P)/new-dir-mountpoint" \ --bind "$(pwd -P)/some-file" "$(pwd -P)/new-file-mountpoint" \ true command stat -c '%a' new-dir-mountpoint > new-dir-permissions assert_file_has_content new-dir-permissions 755 command stat -c '%a' new-file-mountpoint > new-file-permissions assert_file_has_content new-file-permissions 444 ok "Files and directories created as mount points have expected permissions" if [ -S /dev/log ]; then $RUN --bind / / --bind "$(realpath /dev/log)" "$(realpath /dev/log)" true ok "Can bind-mount a socket (/dev/log) onto a socket" else ok_skip "- /dev/log is not a socket, cannot test bubblewrap#409" fi mkdir -p dir-already-existed chmod 0710 dir-already-existed mkdir -p dir-already-existed2 chmod 0754 dir-already-existed2 rm -fr new-dir-default-perms rm -fr new-dir-set-perms $RUN \ --perms 1741 --dir "$(pwd -P)/new-dir-set-perms" \ --dir "$(pwd -P)/dir-already-existed" \ --perms 0741 --dir "$(pwd -P)/dir-already-existed2" \ --dir "$(pwd -P)/dir-chmod" \ --chmod 1755 "$(pwd -P)/dir-chmod" \ --dir "$(pwd -P)/new-dir-default-perms" \ true command stat -c '%a' new-dir-default-perms > new-dir-permissions assert_file_has_content new-dir-permissions '^755$' command stat -c '%a' new-dir-set-perms > new-dir-permissions assert_file_has_content new-dir-permissions '^1741$' command stat -c '%a' dir-already-existed > dir-permissions assert_file_has_content dir-permissions '^710$' command stat -c '%a' dir-already-existed2 > dir-permissions assert_file_has_content dir-permissions '^754$' command stat -c '%a' dir-chmod > dir-permissions assert_file_has_content dir-permissions '^1755$' ok "Directories created explicitly have expected permissions" rm -fr parent rm -fr parent-of-1777 rm -fr parent-of-0755 rm -fr parent-of-0644 rm -fr parent-of-0750 rm -fr parent-of-0710 rm -fr parent-of-0720 rm -fr parent-of-0640 rm -fr parent-of-0700 rm -fr parent-of-0600 rm -fr parent-of-0705 rm -fr parent-of-0604 rm -fr parent-of-0000 $RUN \ --dir "$(pwd -P)"/parent/dir \ --perms 1777 --dir "$(pwd -P)"/parent-of-1777/dir \ --perms 0755 --dir "$(pwd -P)"/parent-of-0755/dir \ --perms 0644 --dir "$(pwd -P)"/parent-of-0644/dir \ --perms 0750 --dir "$(pwd -P)"/parent-of-0750/dir \ --perms 0710 --dir "$(pwd -P)"/parent-of-0710/dir \ --perms 0720 --dir "$(pwd -P)"/parent-of-0720/dir \ --perms 0640 --dir "$(pwd -P)"/parent-of-0640/dir \ --perms 0700 --dir "$(pwd -P)"/parent-of-0700/dir \ --perms 0600 --dir "$(pwd -P)"/parent-of-0600/dir \ --perms 0705 --dir "$(pwd -P)"/parent-of-0705/dir \ --perms 0604 --dir "$(pwd -P)"/parent-of-0604/dir \ --perms 0000 --dir "$(pwd -P)"/parent-of-0000/dir \ true command stat -c '%a' parent > dir-permissions assert_file_has_content dir-permissions '^755$' command stat -c '%a' parent-of-1777 > dir-permissions assert_file_has_content dir-permissions '^755$' command stat -c '%a' parent-of-0755 > dir-permissions assert_file_has_content dir-permissions '^755$' command stat -c '%a' parent-of-0644 > dir-permissions assert_file_has_content dir-permissions '^755$' command stat -c '%a' parent-of-0750 > dir-permissions assert_file_has_content dir-permissions '^750$' command stat -c '%a' parent-of-0710 > dir-permissions assert_file_has_content dir-permissions '^750$' command stat -c '%a' parent-of-0720 > dir-permissions assert_file_has_content dir-permissions '^750$' command stat -c '%a' parent-of-0640 > dir-permissions assert_file_has_content dir-permissions '^750$' command stat -c '%a' parent-of-0700 > dir-permissions assert_file_has_content dir-permissions '^700$' command stat -c '%a' parent-of-0600 > dir-permissions assert_file_has_content dir-permissions '^700$' command stat -c '%a' parent-of-0705 > dir-permissions assert_file_has_content dir-permissions '^705$' command stat -c '%a' parent-of-0604 > dir-permissions assert_file_has_content dir-permissions '^705$' command stat -c '%a' parent-of-0000 > dir-permissions assert_file_has_content dir-permissions '^700$' chmod -R 0700 parent* rm -fr parent* ok "Directories created as parents have expected permissions" $RUN \ --perms 01777 --tmpfs "$(pwd -P)" \ cat /proc/self/mountinfo >&2 $RUN \ --perms 01777 --tmpfs "$(pwd -P)" \ stat -c '%a' "$(pwd -P)" > dir-permissions assert_file_has_content dir-permissions '^1777$' $RUN \ --tmpfs "$(pwd -P)" \ stat -c '%a' "$(pwd -P)" > dir-permissions assert_file_has_content dir-permissions '^755$' ok "tmpfs has expected permissions" # 1048576 = 1 MiB if test -n "${bwrap_is_suid:-}"; then if $RUN --size 1048576 --tmpfs "$(pwd -P)" true; then assert_not_reached "Should not allow --size --tmpfs when setuid" fi ok "--size --tmpfs is not allowed when setuid" elif df --output=size --block-size=1K "$(pwd -P)" >/dev/null 2>/dev/null; then $RUN \ --size 1048576 --tmpfs "$(pwd -P)" \ df --output=size --block-size=1K "$(pwd -P)" > dir-size assert_file_has_content dir-size '^ *1024$' $RUN \ --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \ stat -c '%a' "$(pwd -P)" > dir-permissions assert_file_has_content dir-permissions '^1777$' $RUN \ --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" \ df --output=size --block-size=1K "$(pwd -P)" > dir-size assert_file_has_content dir-size '^ *1024$' $RUN \ --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \ stat -c '%a' "$(pwd -P)" > dir-permissions assert_file_has_content dir-permissions '^1777$' $RUN \ --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" \ df --output=size --block-size=1K "$(pwd -P)" > dir-size assert_file_has_content dir-size '^ *1024$' ok "tmpfs has expected size" else $RUN --size 1048576 --tmpfs "$(pwd -P)" true $RUN --perms 01777 --size 1048576 --tmpfs "$(pwd -P)" true $RUN --size 1048576 --perms 01777 --tmpfs "$(pwd -P)" true ok_skip "df is too old, cannot test --size --tmpfs fully" fi $RUN \ --file 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^666$' $RUN \ --perms 0640 --file 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^640$' $RUN \ --bind-data 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^600$' $RUN \ --perms 0640 --bind-data 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^640$' $RUN \ --ro-bind-data 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^600$' $RUN \ --perms 0640 --ro-bind-data 0 /tmp/file \ stat -c '%a' /tmp/file < /dev/null > file-permissions assert_file_has_content file-permissions '^640$' ok "files have expected permissions" if $RUN --size 0 --tmpfs /tmp/a true; then assert_not_reached Zero tmpfs size allowed fi if $RUN --size 123bogus --tmpfs /tmp/a true; then assert_not_reached Bogus tmpfs size allowed fi if $RUN --size '' --tmpfs /tmp/a true; then assert_not_reached Empty tmpfs size allowed fi if $RUN --size -12345678 --tmpfs /tmp/a true; then assert_not_reached Negative tmpfs size allowed fi if $RUN --size ' -12345678' --tmpfs /tmp/a true; then assert_not_reached Negative tmpfs size with space allowed fi # This is 2^64 if $RUN --size 18446744073709551616 --tmpfs /tmp/a true; then assert_not_reached Overflowing tmpfs size allowed fi # This is 2^63 + 1; note that the current max size is SIZE_MAX/2 if $RUN --size 9223372036854775809 --tmpfs /tmp/a true; then assert_not_reached Too-large tmpfs size allowed fi ok "bogus tmpfs size not allowed" if $RUN --perms 0640 --perms 0640 --tmpfs /tmp/a true; then assert_not_reached Multiple perms options allowed fi if $RUN --size 1048576 --size 1048576 --tmpfs /tmp/a true; then assert_not_reached Multiple perms options allowed fi ok "--perms and --size only allowed once" FOO= BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout assert_file_has_content stdout barbaz FOO=wrong BAR=baz $RUN --setenv FOO bar sh -c 'echo "$FOO$BAR"' > stdout assert_file_has_content stdout barbaz FOO=wrong BAR=baz $RUN --unsetenv FOO sh -c 'printf "%s%s" "$FOO" "$BAR"' > stdout printf baz > reference assert_files_equal stdout reference FOO=wrong BAR=wrong $RUN --clearenv /usr/bin/env > stdout echo "PWD=$(pwd -P)" > reference assert_files_equal stdout reference ok "environment manipulation" $RUN sh -c 'echo $0' > stdout assert_file_has_content stdout sh $RUN --argv0 sh sh -c 'echo $0' > stdout assert_file_has_content stdout sh $RUN --argv0 right sh -c 'echo $0' > stdout assert_file_has_content stdout right ok "argv0 manipulation" done_testing