From 7bd733fb8b7597798119ce9293dd78ac602dc8cf Mon Sep 17 00:00:00 2001 From: Johannes 'josch' Schauer Date: Wed, 13 Jan 2021 18:40:24 +0100 Subject: [PATCH] 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 --- coverage.sh | 72 +++++++++++++++++++++++++++++++++++++++++++++- mmdebstrap | 83 ++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 140 insertions(+), 15 deletions(-) diff --git a/coverage.sh b/coverage.sh index 54cc0e0..8028494 100755 --- a/coverage.sh +++ b/coverage.sh @@ -119,7 +119,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s fi fi starttime= -total=186 +total=189 skipped=0 runtests=0 i=1 @@ -518,6 +518,76 @@ else runtests=$((runtests+1)) 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 format in tar squashfs ext2; do print_header "mode=unshare/root,variant=$variant: check for bit-by-bit identical $format output" diff --git a/mmdebstrap b/mmdebstrap index 8315ec5..33c4211 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -49,16 +49,17 @@ use version; # from sched.h # use typeglob constants because "use constant" has several drawback as # explained in the documentation for the Readonly CPAN module -*CLONE_NEWNS = \0x20000; -*CLONE_NEWUTS = \0x4000000; -*CLONE_NEWIPC = \0x8000000; -*CLONE_NEWUSER = \0x10000000; -*CLONE_NEWPID = \0x20000000; -*CLONE_NEWNET = \0x40000000; -our ( - $CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC, - $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET -); +*CLONE_NEWNS = \0x20000; # mount namespace +*CLONE_NEWUTS = \0x4000000; # utsname +*CLONE_NEWIPC = \0x8000000; # ipc +*CLONE_NEWUSER = \0x10000000; # user +*CLONE_NEWPID = \0x20000000; # pid +*CLONE_NEWNET = \0x40000000; # net +*_LINUX_CAPABILITY_VERSION_3 = \0x20080522; +*CAP_SYS_ADMIN = \21; +our ($CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC, + $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET, + $_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN); # type codes: # 0 -> normal file @@ -943,7 +944,9 @@ sub run_chroot { } } elsif ($type == 3 or $type == 4) { # 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" or error "cannot open $options->{root}/$fname: $!"; close $fh; @@ -970,6 +973,10 @@ sub run_chroot { "$options->{root}/$fname") or error "mount $fname failed: $?"; } + } elsif ($type == 5 + && $options->{mode} eq 'root' + && !$options->{canmount}) { + warning "skipping bind-mounting $fname"; } elsif ($type == 5) { # directory if (!$options->{havemknod}) { if ( @@ -1040,7 +1047,9 @@ 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') { + if ($options->{mode} eq 'root' && !$options->{canmount}) { + warning "skipping mount sysfs"; + } elsif ($options->{mode} eq 'root') { push @cleanup_tasks, sub { 0 == system('umount', "$options->{root}/sys") or warn "umount /sys failed: $?"; @@ -1080,7 +1089,9 @@ sub run_chroot { } else { 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 { # some maintainer scripts mount additional stuff into /proc # which we need to unmount beforehand @@ -4500,6 +4511,47 @@ 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') { + # 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 = (); foreach my $archs (@{ $options->{architectures} }) { foreach my $arch (split /[,\s]+/, $archs) { @@ -6123,7 +6175,8 @@ the B mode is used if the proot binary exists. 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 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. =item B @@ -6426,6 +6479,8 @@ 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.