In root mode, check whether it's possible to mount

- even if the user is root, they might not have permission to mount
 - check for CAP_SYS_ADMIN and unshare --mount before proceeding
 - allow one to disable the check with --skip=check/canmount
 - this is useful in container environments like docker
This commit is contained in:
Johannes 'josch' Schauer 2021-01-13 18:40:24 +01:00
parent 205f5c2692
commit 7bd733fb8b
Signed by: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 140 additions and 15 deletions

View file

@ -119,7 +119,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s
fi fi
fi fi
starttime= starttime=
total=186 total=189
skipped=0 skipped=0
runtests=0 runtests=0
i=1 i=1
@ -518,6 +518,76 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi fi
print_header "mode=root,variant=apt: fail with root without cap_sys_admin"
cat << END > shared/test.sh
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
ret=0
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
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
else
./run_null.sh SUDO
runtests=$((runtests+1))
fi
print_header "mode=root,variant=apt: fail without mounted /proc"
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
exit 1
fi
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
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
else
./run_null.sh SUDO
runtests=$((runtests+1))
fi
for variant in essential apt minbase buildd important standard; do for variant in essential apt minbase buildd important standard; do
for format in tar squashfs ext2; do for format in tar squashfs ext2; do
print_header "mode=unshare/root,variant=$variant: check for bit-by-bit identical $format output" print_header "mode=unshare/root,variant=$variant: check for bit-by-bit identical $format output"

View file

@ -49,16 +49,17 @@ use version;
# from sched.h # from sched.h
# use typeglob constants because "use constant" has several drawback as # use typeglob constants because "use constant" has several drawback as
# explained in the documentation for the Readonly CPAN module # explained in the documentation for the Readonly CPAN module
*CLONE_NEWNS = \0x20000; *CLONE_NEWNS = \0x20000; # mount namespace
*CLONE_NEWUTS = \0x4000000; *CLONE_NEWUTS = \0x4000000; # utsname
*CLONE_NEWIPC = \0x8000000; *CLONE_NEWIPC = \0x8000000; # ipc
*CLONE_NEWUSER = \0x10000000; *CLONE_NEWUSER = \0x10000000; # user
*CLONE_NEWPID = \0x20000000; *CLONE_NEWPID = \0x20000000; # pid
*CLONE_NEWNET = \0x40000000; *CLONE_NEWNET = \0x40000000; # net
our ( *_LINUX_CAPABILITY_VERSION_3 = \0x20080522;
$CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC, *CAP_SYS_ADMIN = \21;
$CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET our ($CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC,
); $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET,
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN);
# type codes: # type codes:
# 0 -> normal file # 0 -> normal file
@ -943,7 +944,9 @@ sub run_chroot {
} }
} elsif ($type == 3 or $type == 4) { } elsif ($type == 3 or $type == 4) {
# character/block special # character/block special
if (!$options->{havemknod}) { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping bind-mounting $fname";
} elsif (!$options->{havemknod}) {
open my $fh, '>', "$options->{root}/$fname" open my $fh, '>', "$options->{root}/$fname"
or error "cannot open $options->{root}/$fname: $!"; or error "cannot open $options->{root}/$fname: $!";
close $fh; close $fh;
@ -970,6 +973,10 @@ sub run_chroot {
"$options->{root}/$fname") "$options->{root}/$fname")
or error "mount $fname failed: $?"; or error "mount $fname failed: $?";
} }
} elsif ($type == 5
&& $options->{mode} eq 'root'
&& !$options->{canmount}) {
warning "skipping bind-mounting $fname";
} elsif ($type == 5) { # directory } elsif ($type == 5) { # directory
if (!$options->{havemknod}) { if (!$options->{havemknod}) {
if ( if (
@ -1040,7 +1047,9 @@ 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') { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping mount sysfs";
} 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")
or warn "umount /sys failed: $?"; or warn "umount /sys failed: $?";
@ -1080,7 +1089,9 @@ sub run_chroot {
} else { } else {
error "unknown mode: $options->{mode}"; error "unknown mode: $options->{mode}";
} }
if ($options->{mode} eq 'root') { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping mount proc";
} 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
# which we need to unmount beforehand # which we need to unmount beforehand
@ -4500,6 +4511,47 @@ 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
# 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') {
# 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.
#
# We test for CAP_SYS_ADMIN using the capget syscall.
# We cannot use cap_get_proc from sys/capability.h because Perl.
# We don't use capsh because we don't want to depend on libcap2-bin
my $hdrp = pack(
"Li", # __u32 followed by int
$_LINUX_CAPABILITY_VERSION_3, # available since Linux 2.6.26
0 # caps of this process
);
my $datap = pack("LLLLLL", 0, 0, 0, 0, 0, 0); # six __u32
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";
}
# To test whether we can use mount without actually trying to mount
# something we try unsharing the mount namespace. If this is allowed,
# then we are also allowed to mount.
if (0 == system 'unshare --mount true 2>/dev/null') {
$options->{canmount} = 1;
} else {
# if we cannot unshare the mount namespace as root, then we also
# cannot mount
error "root mode requires mount but unshare --mount failed";
}
}
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) {
@ -6123,7 +6175,8 @@ the B<proot> mode is used if the proot binary exists.
This mode directly executes chroot and is the same mode of operation as is This mode directly executes chroot and is the same mode of operation as is
used by debootstrap. It is the only mode that can directly create a directory used by debootstrap. It is the only mode that can directly create a directory
chroot with the right permissions. If the chroot directory is not accessible chroot with the right permissions. If the chroot directory is not accessible
by the _apt user, then apt sandboxing will be automatically disabled. by the _apt user, then apt sandboxing will be automatically disabled. This mode
needs to be able to mount and thus requires C<SYS_CAP_ADMIN>.
=item B<unshare> =item B<unshare>
@ -6426,6 +6479,8 @@ 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.