From 70b081d299e30b3dbb8d92312a6c1075f4443eaa Mon Sep 17 00:00:00 2001 From: Johannes Schauer Marin Rodrigues Date: Fri, 25 Mar 2022 14:25:54 +0100 Subject: [PATCH] allow running root mode inside unshare mode --- coverage.sh | 45 ++++++++++++++++++++++++-- mmdebstrap | 92 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/coverage.sh b/coverage.sh index deb233a..a05b75a 100755 --- a/coverage.sh +++ b/coverage.sh @@ -127,7 +127,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s fi fi starttime= -total=182 +total=183 skipped=0 runtests=0 i=1 @@ -712,7 +712,48 @@ else runtests=$((runtests+1)) fi -print_header "mode=unshare,variant=apt: root without cap_sys_admin" +# Same as above but this time we run mmdebstrap in root mode from inside +# an unshare chroot. +print_header "mode=root,variant=apt: root mode inside unshare chroot" +cat << END > shared/test.sh +#!/bin/sh +set -eu +export LC_ALL=C.UTF-8 +if [ ! -e /mmdebstrap-testenv ]; then + echo "this test modifies the system and should only be run inside a container" >&2 + exit 1 +fi +[ "\$(whoami)" = "root" ] +adduser --gecos user --disabled-password user +sysctl -w kernel.unprivileged_userns_clone=1 +cat << 'SCRIPT' > script.sh +#!/bin/sh +set -eu +rootfs="\$1" +mkdir -p "\$rootfs/mnt" +[ -e /usr/bin/mmdebstrap ] && cp -aT /usr/bin/mmdebstrap "\$rootfs/usr/bin/mmdebstrap" +[ -e ./mmdebstrap ] && cp -aT ./mmdebstrap "\$rootfs/mnt/mmdebstrap" +chroot "\$rootfs" env --chdir=/mnt \ + $CMD --mode=root --variant=apt \ + $DEFAULT_DIST /tmp/debian-chroot.tar $mirror +SCRIPT +chmod +x script.sh +runuser -u user -- $CMD --mode=unshare --variant=apt --include=perl,mount \ + --customize-hook=./script.sh \ + --customize-hook="download /tmp/debian-chroot.tar /tmp/debian-chroot.tar" \ + $DEFAULT_DIST /dev/null $mirror +tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - +rm /tmp/debian-chroot.tar script.sh +END +if [ "$HAVE_QEMU" = "yes" ]; then + ./run_qemu.sh + runtests=$((runtests+1)) +else + echo "HAVE_QEMU != yes -- Skipping test..." >&2 + skipped=$((skipped+1)) +fi + +print_header "mode=root,variant=apt: root without cap_sys_admin" cat << END > shared/test.sh #!/bin/sh set -eu diff --git a/mmdebstrap b/mmdebstrap index 4c062c5..72611f4 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -1181,15 +1181,35 @@ sub run_chroot { 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") - or warn "umount /sys failed: $?"; - }; - 0 == system( - 'mount', '-t', 'sysfs', - '-o', 'ro,nosuid,nodev,noexec', 'sys', - "$options->{root}/sys" - ) or error "mount /sys failed: $?"; + # we don't know whether we run in root mode inside an unshared + # user namespace or as real root so we first try the real mount and + # then fall back to mounting in a way that works in unshared mode + if ( + 0 == system( + 'mount', '-t', + 'sysfs', '-o', + 'ro,nosuid,nodev,noexec', 'sys', + "$options->{root}/sys" + ) + ) { + push @cleanup_tasks, sub { + 0 == system('umount', "$options->{root}/sys") + or warn "umount /sys failed: $?"; + }; + } elsif ( + 0 == system('mount', '-o', 'rbind', '/sys', + "$options->{root}/sys")) { + push @cleanup_tasks, sub { + # since we cannot write to /etc/mtab we need --no-mtab + # unmounting /sys only seems to be successful with --lazy + 0 == system( + 'umount', '--no-mtab', + '--lazy', "$options->{root}/sys" + ) or warn "umount /sys failed: $?"; + }; + } else { + error "mount /sys failed: $?"; + } } 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 @@ -1236,24 +1256,42 @@ sub run_chroot { 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 - # which we need to unmount beforehand - if ( - is_mountpoint( - $options->{root} . "/proc/sys/fs/binfmt_misc" - ) - ) { - 0 == system('umount', - "$options->{root}/proc/sys/fs/binfmt_misc") - or error "umount /proc/sys/fs/binfmt_misc failed: $?"; - } - 0 == system('umount', "$options->{root}/proc") - or error "umount /proc failed: $?"; - }; - 0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc', - "$options->{root}/proc") - or error "mount /proc failed: $?"; + # we don't know whether we run in root mode inside an unshared + # user namespace or as real root so we first try the real mount and + # then fall back to mounting in a way that works in unshared + if ( + 0 == system( + 'mount', '-t', 'proc', '-o', 'ro', 'proc', + "$options->{root}/proc" + ) + ) { + push @cleanup_tasks, sub { + # some maintainer scripts mount additional stuff into /proc + # which we need to unmount beforehand + if ( + is_mountpoint( + $options->{root} . "/proc/sys/fs/binfmt_misc" + ) + ) { + 0 == system('umount', + "$options->{root}/proc/sys/fs/binfmt_misc") + or error + "umount /proc/sys/fs/binfmt_misc failed: $?"; + } + 0 == system('umount', "$options->{root}/proc") + or error "umount /proc failed: $?"; + }; + } elsif ( + 0 == system('mount', '-t', 'proc', 'proc', + "$options->{root}/proc")) { + push @cleanup_tasks, sub { + # since we cannot write to /etc/mtab we need --no-mtab + 0 == system('umount', '--no-mtab', "$options->{root}/proc") + or error "umount /proc failed: $?"; + }; + } else { + error "mount /proc failed: $?"; + } } 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