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
This commit is contained in:
Johannes Schauer Marin Rodrigues 2021-08-26 15:40:27 +02:00
parent 7ab770267c
commit 5a5f57b404
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 65 additions and 87 deletions

View file

@ -120,7 +120,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s
fi fi
fi fi
starttime= starttime=
total=218 total=217
skipped=0 skipped=0
runtests=0 runtests=0
i=1 i=1
@ -655,19 +655,18 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi 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 cat << END > shared/test.sh
#!/bin/sh #!/bin/sh
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
ret=0 [ "\$(whoami)" = "root" ]
capsh --drop=cap_sys_admin -- -c 'exec "\$@"' exec \ capsh --drop=cap_sys_admin -- -c 'exec "\$@"' exec \
$CMD --mode=root --variant=apt $DEFAULT_DIST /tmp/debian-chroot $mirror || ret=\$? $CMD --mode=root --variant=apt \
if [ "\$ret" = 0 ]; then --customize-hook='chroot "\$1" sh -c "test ! -e /proc/self/fd"' \
echo expected failure but got exit \$ret >&2 $DEFAULT_DIST /tmp/debian-chroot.tar $mirror
exit 1 tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
fi rm /tmp/debian-chroot.tar
[ ! -e /tmp/debian-chroot ]
END END
if [ "$CONTAINER" = "lxc" ]; then if [ "$CONTAINER" = "lxc" ]; then
# see https://stackoverflow.com/questions/65748254/ # see https://stackoverflow.com/questions/65748254/
@ -681,45 +680,19 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi 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 cat << END > shared/test.sh
#!/bin/sh #!/bin/sh
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
# success with /proc mounted if [ ! -e /mmdebstrap-testenv ]; then
$CMD --mode=root --variant=apt \ echo "this test modifies the system and should only be run inside a container" >&2
--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
exit 1 exit 1
fi fi
rm -r /tmp/debian-chroot for p in /bin /usr/bin /sbin /usr/sbin; do
END rm -f "\$p/mount"
if [ "$HAVE_QEMU" = "yes" ]; then done
./run_qemu.sh $CMD --mode=root --variant=apt $DEFAULT_DIST /tmp/debian-chroot.tar $mirror
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
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar rm /tmp/debian-chroot.tar
END END
@ -727,8 +700,8 @@ if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh ./run_qemu.sh
runtests=$((runtests+1)) runtests=$((runtests+1))
else else
./run_null.sh SUDO echo "HAVE_QEMU != yes -- Skipping test..." >&2
runtests=$((runtests+1)) skipped=$((skipped+1))
fi fi
for variant in essential apt minbase buildd important standard; do for variant in essential apt minbase buildd important standard; do

View file

@ -1009,7 +1009,8 @@ sub run_chroot {
} }
} elsif ($type == 3 or $type == 4) { } elsif ($type == 3 or $type == 4) {
# character/block special # 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"; warning "skipping bind-mounting ./dev/$fname";
} elsif (!$options->{havemknod}) { } elsif (!$options->{havemknod}) {
if (!-d "$options->{root}/dev") { if (!-d "$options->{root}/dev") {
@ -1051,7 +1052,7 @@ sub run_chroot {
or error "mount ./dev/$fname failed: $?"; or error "mount ./dev/$fname failed: $?";
} }
} elsif ($type == 5 } elsif ($type == 5
&& $options->{mode} eq 'root' && (any { $_ eq $options->{mode} } ('root', 'unshare'))
&& !$options->{canmount}) { && !$options->{canmount}) {
warning "skipping bind-mounting ./dev/$fname"; warning "skipping bind-mounting ./dev/$fname";
} elsif ($type == 5) { # directory } elsif ($type == 5) { # directory
@ -1135,11 +1136,21 @@ sub run_chroot {
# We can only mount /proc and /sys after extracting the essential # 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 # set because if we mount it before, then base-files will not be able
# to extract those # to extract those
if ($options->{mode} eq 'root' && !$options->{canmount}) { if ((any { $_ eq $options->{mode} } ('root', 'unshare'))
&& !$options->{canmount}) {
warning "skipping mount sysfs"; 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" warning("skipping mounting of sysfs because the"
. " /sys directory is missing in the target"); . " /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') { } elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub { push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/sys") 0 == system('umount', "$options->{root}/sys")
@ -1150,15 +1161,6 @@ sub run_chroot {
'-o', 'ro,nosuid,nodev,noexec', 'sys', '-o', 'ro,nosuid,nodev,noexec', 'sys',
"$options->{root}/sys" "$options->{root}/sys"
) or error "mount /sys failed: $?"; ) 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') { } elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where # 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 # we do a real mount. But we also need to unmount in unshare mode
@ -1189,11 +1191,21 @@ sub run_chroot {
} else { } else {
error "unknown mode: $options->{mode}"; 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"; 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" warning("skipping mounting of proc because the"
. " /proc directory is missing in the target"); . " /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') { } elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub { push @cleanup_tasks, sub {
# some maintainer scripts mount additional stuff into /proc # some maintainer scripts mount additional stuff into /proc
@ -1213,16 +1225,6 @@ sub run_chroot {
0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc', 0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc',
"$options->{root}/proc") "$options->{root}/proc")
or error "mount /proc failed: $?"; 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') { } elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where # 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 # 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}"; error "unknown mode: $options->{mode}";
} }
# By default, mount is not used. This is so that mounting is skipped if the $options->{canmount} = 1;
# user supplies --skip=check/canmount. This only gets enabled if if ($options->{mode} eq 'root') {
# 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') {
# It's possible to be root but not be able to mount anything. # It's possible to be root but not be able to mount anything.
# This is for example the case when running under docker. # This is for example the case when running under docker.
# Mounting needs CAP_SYS_ADMIN which might not be available. # Mounting needs CAP_SYS_ADMIN which might not be available.
@ -4671,11 +4668,10 @@ sub main() {
0 == syscall &SYS_capget, $hdrp, $datap 0 == syscall &SYS_capget, $hdrp, $datap
or error "capget failed: $!"; or error "capget failed: $!";
my ($effective, undef) = unpack "LLLLLL", $datap; my ($effective, undef) = unpack "LLLLLL", $datap;
if (($effective >> $CAP_SYS_ADMIN) & 1 == 1) { if (($effective >> $CAP_SYS_ADMIN) & 1 != 1) {
# we have CAP_SYS_ADMIN, and thus can mount # we don't have CAP_SYS_ADMIN, and thus cannot mount
$options->{canmount} = 1; warning "cannot mount because of missing capability CAP_SYS_ADMIN";
} else { $options->{canmount} = 0;
error "root mode requires mount which requires CAP_SYS_ADMIN";
} }
# To test whether we can use mount without actually trying to mount # To test whether we can use mount without actually trying to mount
# something we try unsharing the mount namespace. If this is allowed, # 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 # we get 'cannot change root filesystem propagation' when running
# mmdebstrap inside a chroot for which the root of the chroot is not # mmdebstrap inside a chroot for which the root of the chroot is not
# its own mount point. # its own mount point.
if (0 == system 'unshare --mount --propagation unchanged -- true') { if (0 != system 'unshare --mount --propagation unchanged -- true') {
$options->{canmount} = 1;
} else {
# if we cannot unshare the mount namespace as root, then we also # if we cannot unshare the mount namespace as root, then we also
# cannot mount # 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 = (); my @architectures = ();
foreach my $archs (@{ $options->{architectures} }) { foreach my $archs (@{ $options->{architectures} }) {
foreach my $arch (split /[,\s]+/, $archs) { 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 * 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<unshare> program from the C<util-linux> 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 * 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<SUITE>, I<MIRROR> and B<--components> and/or from standard input as deb822 or one-line format and whether the required GPG keys exist. =item * how the apt sources can be assembled from I<SUITE>, I<MIRROR> and B<--components> and/or from standard input as deb822 or one-line format and whether the required GPG keys exist.