From 5a5f57b404fa3139534cfcaef99a744b5638f46b Mon Sep 17 00:00:00 2001 From: Johannes Schauer Marin Rodrigues Date: Thu, 26 Aug 2021 15:40:27 +0200 Subject: [PATCH] Automatically skip using mount if that's not possible - instead of throwing an error, just print a warning - can now run as root without cap_sys_admin - can now run without mount installed - --skip=check/canmount is not needed anymore --- coverage.sh | 61 ++++++++++------------------------- mmdebstrap | 91 ++++++++++++++++++++++++++++------------------------- 2 files changed, 65 insertions(+), 87 deletions(-) diff --git a/coverage.sh b/coverage.sh index ea0bada..361c406 100755 --- a/coverage.sh +++ b/coverage.sh @@ -120,7 +120,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s fi fi starttime= -total=218 +total=217 skipped=0 runtests=0 i=1 @@ -655,19 +655,18 @@ else runtests=$((runtests+1)) fi -print_header "mode=root,variant=apt: fail with root without cap_sys_admin" +print_header "mode=unshare,variant=apt: root without cap_sys_admin" cat << END > shared/test.sh #!/bin/sh set -eu export LC_ALL=C.UTF-8 -ret=0 +[ "\$(whoami)" = "root" ] capsh --drop=cap_sys_admin -- -c 'exec "\$@"' exec \ - $CMD --mode=root --variant=apt $DEFAULT_DIST /tmp/debian-chroot $mirror || ret=\$? -if [ "\$ret" = 0 ]; then - echo expected failure but got exit \$ret >&2 - exit 1 -fi -[ ! -e /tmp/debian-chroot ] + $CMD --mode=root --variant=apt \ + --customize-hook='chroot "\$1" sh -c "test ! -e /proc/self/fd"' \ + $DEFAULT_DIST /tmp/debian-chroot.tar $mirror +tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - +rm /tmp/debian-chroot.tar END if [ "$CONTAINER" = "lxc" ]; then # see https://stackoverflow.com/questions/65748254/ @@ -681,45 +680,19 @@ else runtests=$((runtests+1)) fi -print_header "mode=root,variant=apt: fail without mounted /proc" +print_header "mode=root,variant=apt: mount is missing" cat << END > shared/test.sh #!/bin/sh set -eu export LC_ALL=C.UTF-8 -# success with /proc mounted -$CMD --mode=root --variant=apt \ - --customize-hook='chroot "\$1" bash -c "test \\"\\\$(cat <(echo foobar))\\" = foobar"' \ - $DEFAULT_DIST /dev/null $mirror -# failure without /proc mounted (using --skip=check/canmount) -ret=0 -$CMD --mode=root --variant=apt \ - --customize-hook='chroot "\$1" bash -c "test \\"\\\$(cat <(echo foobar))\\" = foobar"' \ - --skip=check/canmount $DEFAULT_DIST /tmp/debian-chroot $mirror || ret=\$? -if [ "\$ret" = 0 ]; then - echo expected failure but got exit \$ret >&2 +if [ ! -e /mmdebstrap-testenv ]; then + echo "this test modifies the system and should only be run inside a container" >&2 exit 1 fi -rm -r /tmp/debian-chroot -END -if [ "$HAVE_QEMU" = "yes" ]; then - ./run_qemu.sh - runtests=$((runtests+1)) -else - ./run_null.sh SUDO - runtests=$((runtests+1)) -fi - - -print_header "mode=unshare,variant=apt: root without cap_sys_admin but --skip=check/canmount" -cat << END > shared/test.sh -#!/bin/sh -set -eu -export LC_ALL=C.UTF-8 -[ "\$(whoami)" = "root" ] -capsh --drop=cap_sys_admin -- -c 'exec "\$@"' exec \ - $CMD --mode=root --variant=apt \ - --customize-hook='chroot "\$1" sh -c "test ! -e /proc/self/fd"' \ - --skip=check/canmount $DEFAULT_DIST /tmp/debian-chroot.tar $mirror +for p in /bin /usr/bin /sbin /usr/sbin; do + rm -f "\$p/mount" +done +$CMD --mode=root --variant=apt $DEFAULT_DIST /tmp/debian-chroot.tar $mirror tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - rm /tmp/debian-chroot.tar END @@ -727,8 +700,8 @@ if [ "$HAVE_QEMU" = "yes" ]; then ./run_qemu.sh runtests=$((runtests+1)) else - ./run_null.sh SUDO - runtests=$((runtests+1)) + echo "HAVE_QEMU != yes -- Skipping test..." >&2 + skipped=$((skipped+1)) fi for variant in essential apt minbase buildd important standard; do diff --git a/mmdebstrap b/mmdebstrap index 2a20f8e..f100e26 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -1009,7 +1009,8 @@ sub run_chroot { } } elsif ($type == 3 or $type == 4) { # character/block special - if ($options->{mode} eq 'root' && !$options->{canmount}) { + if ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !$options->{canmount}) { warning "skipping bind-mounting ./dev/$fname"; } elsif (!$options->{havemknod}) { if (!-d "$options->{root}/dev") { @@ -1051,7 +1052,7 @@ sub run_chroot { or error "mount ./dev/$fname failed: $?"; } } elsif ($type == 5 - && $options->{mode} eq 'root' + && (any { $_ eq $options->{mode} } ('root', 'unshare')) && !$options->{canmount}) { warning "skipping bind-mounting ./dev/$fname"; } elsif ($type == 5) { # directory @@ -1135,11 +1136,21 @@ sub run_chroot { # We can only mount /proc and /sys after extracting the essential # set because if we mount it before, then base-files will not be able # to extract those - if ($options->{mode} eq 'root' && !$options->{canmount}) { + if ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !$options->{canmount}) { warning "skipping mount sysfs"; - } elsif ($options->{mode} eq 'root' && !-d "$options->{root}/sys") { + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-d "$options->{root}/sys") { warning("skipping mounting of sysfs because the" . " /sys directory is missing in the target"); + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-e "/sys") { + warning("skipping bind-mounting /sys because" + . " /sys does not exist on the outside"); + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-d "/sys") { + warning("skipping bind-mounting /sys because" + . " /sys on the outside is not a directory"); } elsif ($options->{mode} eq 'root') { push @cleanup_tasks, sub { 0 == system('umount', "$options->{root}/sys") @@ -1150,15 +1161,6 @@ sub run_chroot { '-o', 'ro,nosuid,nodev,noexec', 'sys', "$options->{root}/sys" ) or error "mount /sys failed: $?"; - } elsif ($options->{mode} eq 'unshare' && !-d "$options->{root}/sys") { - warning("skipping bind-mounting /sys because the" - . " /sys directory is missing in the target"); - } elsif ($options->{mode} eq 'unshare' && !-e "/sys") { - warning("skipping bind-mounting /sys because" - . " /sys does not exist on the outside"); - } elsif ($options->{mode} eq 'unshare' && !-d "/sys") { - warning("skipping bind-mounting /sys because" - . " /sys on the outside is not a directory"); } elsif ($options->{mode} eq 'unshare') { # naturally we have to clean up after ourselves in sudo mode where # we do a real mount. But we also need to unmount in unshare mode @@ -1189,11 +1191,21 @@ sub run_chroot { } else { error "unknown mode: $options->{mode}"; } - if ($options->{mode} eq 'root' && !$options->{canmount}) { + if ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !$options->{canmount}) { warning "skipping mount proc"; - } elsif ($options->{mode} eq 'root' && !-d "$options->{root}/proc") { + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-d "$options->{root}/proc") { warning("skipping mounting of proc because the" . " /proc directory is missing in the target"); + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-e "/proc") { + warning("skipping bind-mounting /proc because" + . " /proc does not exist on the outside"); + } elsif ((any { $_ eq $options->{mode} } ('root', 'unshare')) + && !-d "/proc") { + warning("skipping bind-mounting /proc because" + . " /proc on the outside is not a directory"); } elsif ($options->{mode} eq 'root') { push @cleanup_tasks, sub { # some maintainer scripts mount additional stuff into /proc @@ -1213,16 +1225,6 @@ sub run_chroot { 0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc', "$options->{root}/proc") or error "mount /proc failed: $?"; - } elsif ($options->{mode} eq 'unshare' && !-d "$options->{root}/proc") - { - warning("skipping bind-mounting /proc because the" - . " /proc directory is missing in the target"); - } elsif ($options->{mode} eq 'unshare' && !-e "/proc") { - warning("skipping bind-mounting /proc because" - . " /proc does not exist on the outside"); - } elsif ($options->{mode} eq 'unshare' && !-d "/proc") { - warning("skipping bind-mounting /proc because" - . " /proc on the outside is not a directory"); } elsif ($options->{mode} eq 'unshare') { # naturally we have to clean up after ourselves in sudo mode where # we do a real mount. But we also need to unmount in unshare mode @@ -4648,13 +4650,8 @@ sub main() { error "unknown mode: $options->{mode}"; } - # By default, mount is not used. This is so that mounting is skipped if the - # user supplies --skip=check/canmount. This only gets enabled if - # CAP_SYS_ADMIN and unshare --mount are available in root mode. - $options->{canmount} = 0; - if (any { $_ eq 'check/canmount' } @{ $options->{skip} }) { - info "skipping check/canmount as requested"; - } elsif ($options->{mode} eq 'root') { + $options->{canmount} = 1; + if ($options->{mode} eq 'root') { # It's possible to be root but not be able to mount anything. # This is for example the case when running under docker. # Mounting needs CAP_SYS_ADMIN which might not be available. @@ -4671,11 +4668,10 @@ sub main() { 0 == syscall &SYS_capget, $hdrp, $datap or error "capget failed: $!"; my ($effective, undef) = unpack "LLLLLL", $datap; - if (($effective >> $CAP_SYS_ADMIN) & 1 == 1) { - # we have CAP_SYS_ADMIN, and thus can mount - $options->{canmount} = 1; - } else { - error "root mode requires mount which requires CAP_SYS_ADMIN"; + if (($effective >> $CAP_SYS_ADMIN) & 1 != 1) { + # we don't have CAP_SYS_ADMIN, and thus cannot mount + warning "cannot mount because of missing capability CAP_SYS_ADMIN"; + $options->{canmount} = 0; } # To test whether we can use mount without actually trying to mount # something we try unsharing the mount namespace. If this is allowed, @@ -4685,15 +4681,26 @@ sub main() { # we get 'cannot change root filesystem propagation' when running # mmdebstrap inside a chroot for which the root of the chroot is not # its own mount point. - if (0 == system 'unshare --mount --propagation unchanged -- true') { - $options->{canmount} = 1; - } else { + if (0 != system 'unshare --mount --propagation unchanged -- true') { # if we cannot unshare the mount namespace as root, then we also # cannot mount - error "root mode requires mount but unshare --mount failed"; + warning "cannot mount because unshare --mount failed"; + $options->{canmount} = 0; } } + if (any { $_ eq $options->{mode} } ('root', 'unshare')) { + if (system('mount --version>/dev/null') != 0) { + warning "cannot execute mount"; + $options->{canmount} = 0; + } + } + + # we can only possibly mount in root and unshare mode + if (none { $_ eq $options->{mode} } ('root', 'unshare')) { + $options->{canmount} = 0; + } + my @architectures = (); foreach my $archs (@{ $options->{architectures} }) { foreach my $arch (split /[,\s]+/, $archs) { @@ -6689,8 +6696,6 @@ Upon startup, several checks are carried out, like: =item * which mode to use and whether prerequisites are met -=item * if you are root, check whether you have the ability to mount. This check requires the C program from the C package and can be disabled by using B<--skip=check/canmount>. - =item * whether the requested architecture can be executed (requires arch-test) using qemu binfmt_misc support. This requires arch-test and can be disabled using B<--skip=check/qemu> =item * how the apt sources can be assembled from I, I and B<--components> and/or from standard input as deb822 or one-line format and whether the required GPG keys exist.