From f4a3865c00502a17711f2f08aecda1fb1521670f Mon Sep 17 00:00:00 2001 From: Johannes Schauer Marin Rodrigues Date: Mon, 5 Sep 2022 05:50:50 +0200 Subject: [PATCH] Remove support for proot. The proot mode was broken from the start because in contrast to fakechroot, no ownership information can be retained across multiple invocations of proot. Since mmdebstrap started using apt from the outside by setting DPkg::Chroot-Directory in mmdebstrap 0.8.0 proot mode was finally completely broken because proot cannot wrap the chroot call done by apt. Users of proot are recommended to run mmdebstrap in fakechroot mode and then use proot with the resulting directory. --- README.md | 6 +- mmdebstrap | 204 ++++++++--------------------- tests/create-arm64-tarball | 2 - tests/special-hooks-with-mode-mode | 2 +- 4 files changed, 60 insertions(+), 154 deletions(-) diff --git a/README.md b/README.md index ea21c13..990ddb6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Summary: - chroot with apt in 11 seconds - gzipped tarball with apt is 27M small - bit-by-bit reproducible output - - unprivileged operation using Linux user namespaces, fakechroot or proot + - unprivileged operation using Linux user namespaces or fakechroot - can operate on filesystems mounted with nodev - foreign architecture chroots with qemu-user - variant installing only Essential:yes packages and dependencies @@ -78,9 +78,9 @@ privileges to create a file (the chroot tarball) in one's home directory. Thus, mmdebstrap provides multiple options to create a chroot tarball with the right permissions **without superuser privileges**. This avoids a whole class of bugs like #921815. Depending on what is available, it uses either Linux user -namespaces, fakechroot or proot. Debootstrap supports fakechroot but will not +namespaces or fakechroot. Debootstrap supports fakechroot but will not create a tarball with the right permissions by itself. Support for Linux user -namespaces and proot is missing (see bugs #829134 and #698347, respectively). +namespaces is missing (see #829134). When creating a chroot tarball with debootstrap, the temporary chroot directory cannot be on a filesystem that has been mounted with nodev. In unprivileged diff --git a/mmdebstrap b/mmdebstrap index 92a1080..6a24616 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -1263,13 +1263,8 @@ sub run_chroot { error "unsupported type: $type"; } } - } elsif ( - any { $_ eq $options->{mode} } - ('proot', 'fakechroot', 'chrootless') - ) { - # we cannot mount in fakechroot and proot mode - # in proot mode we have /dev bind-mounted already through - # --bind=/dev + } elsif (any { $_ eq $options->{mode} } ('fakechroot', 'chrootless')) { + # we cannot mount in fakechroot mode } else { error "unknown mode: $options->{mode}"; } @@ -1344,13 +1339,8 @@ sub run_chroot { # type, bad option, bad superblock" error 0 == system('mount', '-o', 'rbind', '/sys', "$options->{root}/sys") or error "mount /sys failed: $?"; - } elsif ( - any { $_ eq $options->{mode} } - ('proot', 'fakechroot', 'chrootless') - ) { - # we cannot mount in fakechroot and proot mode - # in proot mode we have /proc bind-mounted already through - # --bind=/proc + } elsif (any { $_ eq $options->{mode} } ('fakechroot', 'chrootless')) { + # we cannot mount in fakechroot mode } else { error "unknown mode: $options->{mode}"; } @@ -1422,13 +1412,8 @@ sub run_chroot { }; 0 == system('mount', '-t', 'proc', 'proc', "$options->{root}/proc") or error "mount /proc failed: $?"; - } elsif ( - any { $_ eq $options->{mode} } - ('proot', 'fakechroot', 'chrootless') - ) { - # we cannot mount in fakechroot and proot mode - # in proot mode we have /sys bind-mounted already through - # --bind=/sys + } elsif (any { $_ eq $options->{mode} } ('fakechroot', 'chrootless')) { + # we cannot mount in fakechroot mode } else { error "unknown mode: $options->{mode}"; } @@ -1588,13 +1573,11 @@ sub run_hooks { )\ /x ) { info "running special hook: $script"; - if ( - any { $_ eq $options->{variant} } ('extract', 'custom') - and any { $_ eq $options->{mode} } - ('fakechroot', 'proot') and $name ne 'setup' - ) { + if ((any { $_ eq $options->{variant} } ('extract', 'custom')) + and $options->{mode} eq 'fakechroot' + and $name ne 'setup') { info "the copy-in, copy-out, tar-in and tar-out commands" - . " in fakechroot mode or proot mode might fail in" + . " in fakechroot mode might fail in" . " extract and custom variants because there might be" . " no tar inside the chroot"; } @@ -2120,14 +2103,14 @@ sub run_setup() { copy($tmpfile, \*STDERR); } - if (none { $_ eq $options->{mode} } ('fakechroot', 'proot')) { + if ($options->{mode} ne 'fakechroot') { # Apt dropping privileges to another user than root is not useful in - # fakechroot and proot mode because all users are faked and thus there - # is no real privilege difference anyways. We could set - # APT::Sandbox::User "root" in fakechroot and proot mode but we don't - # because if we would, then /var/cache/apt/archives/partial/ and - # /var/lib/apt/lists/partial/ would not be owned by the _apt user - # if mmdebstrap was run in fakechroot or proot mode. + # fakechroot mode because all users are faked and thus there is no real + # privilege difference anyways. We could set APT::Sandbox::User "root" + # in fakechroot mode but we don't because if we would, then + # /var/cache/apt/archives/partial/ and /var/lib/apt/lists/partial/ + # would not be owned by the _apt user if mmdebstrap was run in + # fakechroot mode. # # when apt-get update is run by the root user, then apt will attempt to # drop privileges to the _apt user. This will fail if the _apt user @@ -2539,29 +2522,15 @@ sub run_prepare { # make sure that APT_CONFIG and TMPDIR are not set when executing # anything inside the chroot my @chrootcmd = ('env', '--unset=APT_CONFIG', '--unset=TMPDIR'); - if ($options->{mode} eq 'proot') { - push @chrootcmd, - ( - 'proot', '--root-id', - '--bind=/dev', '--bind=/proc', - '--bind=/sys', "--rootfs=$options->{root}", - '--cwd=/' - ); - } elsif ( - any { $_ eq $options->{mode} } - ('root', 'unshare', 'fakechroot') - ) { + if (any { $_ eq $options->{mode} } ('root', 'unshare', 'fakechroot')) { push @chrootcmd, ('chroot', $options->{root}); } else { error "unknown mode: $options->{mode}"; } - # copy qemu-user-static binary into chroot or setup proot with - # --qemu + # copy qemu-user-static binary into chroot if (defined $options->{qemu}) { - if ($options->{mode} eq 'proot') { - push @chrootcmd, "--qemu=qemu-$options->{qemu}"; - } elsif ($options->{mode} eq 'fakechroot') { + if ($options->{mode} eq 'fakechroot') { # Make sure that the fakeroot and fakechroot shared # libraries exist for the right architecture open my $fh, '-|', 'dpkg-architecture', '-a', @@ -2647,29 +2616,21 @@ sub run_prepare { } # some versions of coreutils use the renameat2 system call in mv. - # This breaks certain versions of fakechroot and proot. Here we do + # This breaks certain versions of fakechroot. Here we do # a sanity check and warn the user in case things might break. - if (any { $_ eq $options->{mode} } ('fakechroot', 'proot') - and -e "$options->{root}/bin/mv") { + if ($options->{mode} eq 'fakechroot' + and -e "$options->{root}/bin/mv") { mkdir "$options->{root}/000-move-me" or error "cannot create directory: $!"; my $ret = system @chrootcmd, '/bin/mv', '/000-move-me', '/001-delete-me'; if ($ret != 0) { - if ($options->{mode} eq 'proot') { - info "the /bin/mv binary inside the chroot doesn't" - . " work under proot"; - info "this is likely due to missing support for" - . " renameat2 in proot"; - info "see https://github.com/proot-me/PRoot/issues/147"; - } else { - info "the /bin/mv binary inside the chroot doesn't" - . " work under fakechroot"; - info "with certain versions of coreutils and glibc," - . " this is due to missing support for renameat2 in" - . " fakechroot"; - info "see https://github.com/dex4er/fakechroot/issues/60"; - } + info "the /bin/mv binary inside the chroot doesn't" + . " work under fakechroot"; + info "with certain versions of coreutils and glibc," + . " this is due to missing support for renameat2 in" + . " fakechroot"; + info "see https://github.com/dex4er/fakechroot/issues/60"; info "expect package post installation scripts not to work"; rmdir "$options->{root}/000-move-me" or error "cannot rmdir: $!"; @@ -2739,10 +2700,8 @@ sub run_essential() { '--force-depends' ], PKGS => [map { "$options->{root}/$_" } @{$essential_pkgs}] }); - } elsif ( - any { $_ eq $options->{mode} } - ('root', 'unshare', 'fakechroot', 'proot') - ) { + } elsif (any { $_ eq $options->{mode} } ('root', 'unshare', 'fakechroot')) + { # install the extracted packages properly # we need --force-depends because dpkg does not take Pre-Depends # into account and thus doesn't install them in the right order @@ -2862,10 +2821,8 @@ sub run_install() { PKGS => [@pkgs_to_install], }); } - } elsif ( - any { $_ eq $options->{mode} } - ('root', 'unshare', 'fakechroot', 'proot') - ) { + } elsif (any { $_ eq $options->{mode} } ('root', 'unshare', 'fakechroot')) + { if ($options->{variant} ne 'custom' and scalar @pkgs_to_install > 0) { # Advantage of running apt on the outside instead of inside the @@ -3172,26 +3129,12 @@ sub hookhelper { . 'delete=atime,delete=ctime' ); if ($hook eq 'setup') { - if ($mode eq 'proot') { - # since we cannot run tar inside the chroot under proot during - # the setup hook because the chroot is empty, we have to run - # tar from the outside, which leads to all files being owned - # by the user running mmdebstrap. To let the ownership - # information not be completely off, we force all files be - # owned by the root user. - push @tarcmd, '--owner=0', '--group=0'; - } } elsif (any { $_ eq $hook } ('extract', 'essential', 'customize')) { if ($mode eq 'fakechroot') { # Fakechroot requires tar to run inside the chroot or # otherwise absolute symlinks will include the path to the # root directory push @cmdprefix, 'chroot', $root; - } elsif ($mode eq 'proot') { - # proot requires tar to run inside proot or otherwise - # permissions will be completely off - push @cmdprefix, 'proot', '--root-id', "--rootfs=$root", - '--cwd=/', "--qemu=$qemu"; } elsif (any { $_ eq $mode } ('root', 'chrootless', 'unshare')) { # not chrooting in this case } else { @@ -3222,7 +3165,7 @@ sub hookhelper { any { $_ eq $hook } ('extract', 'essential', 'customize') ) { - if (any { $_ eq $mode } ('fakechroot', 'proot')) { + if ($mode eq 'fakechroot') { # tar will run inside the chroot $directory = $outpath; } elsif ( @@ -3237,10 +3180,10 @@ sub hookhelper { error "unknown hook: $hook"; } - # if chrooted_realpath was used and if neither fakechroot or - # proot were used (absolute symlinks will be broken) we can + # if chrooted_realpath was used and if fakechroot + # was used (absolute symlinks will be broken) we can # check and potentially fail early if the target does not exist - if (none { $_ eq $mode } ('fakechroot', 'proot')) { + if ($mode ne 'fakechroot') { my $dirtocheck = $directory; if ($command eq 'upload') { # check the parent directory instead @@ -3369,7 +3312,7 @@ sub hookhelper { any { $_ eq $hook } ('extract', 'essential', 'customize') ) { - if (any { $_ eq $mode } ('fakechroot', 'proot')) { + if ($mode eq 'fakechroot') { # tar will run inside the chroot $directory = $ARGV[$i]; } elsif ( @@ -3384,10 +3327,10 @@ sub hookhelper { error "unknown hook: $hook"; } - # if chrooted_realpath was used and if neither fakechroot or - # proot were used (absolute symlinks will be broken) we can + # if chrooted_realpath was used and if fakechroot + # was used (absolute symlinks will be broken) we can # check and potentially fail early if the source does not exist - if (none { $_ eq $mode } ('fakechroot', 'proot')) { + if ($mode ne 'fakechroot') { if (!-e $directory) { error "path does not exist: $directory"; } @@ -4506,8 +4449,7 @@ sub main() { if ($options->{mode} eq 'sudo') { $options->{mode} = 'root'; } - my @valid_modes - = ('auto', 'root', 'unshare', 'fakechroot', 'proot', 'chrootless'); + my @valid_modes = ('auto', 'root', 'unshare', 'fakechroot', 'chrootless'); if (none { $_ eq $options->{mode} } @valid_modes) { error "invalid mode. Choose from " . (join ', ', @valid_modes); } @@ -4631,9 +4573,6 @@ sub main() { @prefix = ($EXECUTABLE_NAME, '-MDevel::Cover=-silent,-nogcov'); } exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG; - } elsif (can_execute 'proot') { - # and lastly, proot - $options->{mode} = 'proot'; } else { error "unable to pick chroot mode automatically"; } @@ -4642,10 +4581,6 @@ sub main() { if ($EFFECTIVE_USER_ID != 0) { error "need to be root"; } - } elsif ($options->{mode} eq 'proot') { - if (!can_execute 'proot') { - error "need working proot binary"; - } } elsif ($options->{mode} eq 'fakechroot') { if (&{$check_fakechroot_running}()) { # fakechroot is already running @@ -5376,10 +5311,10 @@ sub main() { if (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) { if ($format ne 'null') { - if ( any { $_ eq $options->{variant} } ('extract', 'custom') - and any { $_ eq $options->{mode} } ('fakechroot', 'proot')) { + if (any { $_ eq $options->{variant} } ('extract', 'custom') + and $options->{mode} eq 'fakechroot') { info "creating a tarball or squashfs image or ext2 image in" - . " fakechroot mode or proot mode might fail in extract and" + . " fakechroot mode might fail in extract and" . " custom variants because there might be no tar inside the" . " chroot"; } @@ -5517,7 +5452,7 @@ sub main() { $? == 0 or error "havemknod failed"; } elsif ( any { $_ eq $options->{mode} } - ('root', 'fakechroot', 'proot', 'chrootless') + ('root', 'fakechroot', 'chrootless') ) { $options->{havemknod} = havemknod($options->{root}); } else { @@ -5678,7 +5613,7 @@ sub main() { ); } elsif ( any { $_ eq $options->{mode} } - ('root', 'fakechroot', 'proot', 'chrootless') + ('root', 'fakechroot', 'chrootless') ) { $pid = fork() // error "fork() failed: $!"; if ($pid == 0) { @@ -5741,18 +5676,6 @@ sub main() { 0 == system('chroot', $options->{root}, 'tar', @taropts, '-C', '/', '.') or error "tar failed: $?"; - } elsif ($options->{mode} eq 'proot') { - # proot requires tar to run inside proot or otherwise - # permissions will be completely off - my @qemuopt = (); - if (defined $options->{qemu}) { - push @qemuopt, "--qemu=qemu-$options->{qemu}"; - push @taropts, "--exclude=./host-rootfs"; - } - 0 == system('proot', '--root-id', - "--rootfs=$options->{root}", '--cwd=/', @qemuopt, - 'tar', @taropts, '-C', '/', '.') - or error "tar failed: $?"; } elsif ( any { $_ eq $options->{mode} } ('root', 'chrootless') @@ -5990,15 +5913,11 @@ sub main() { } } elsif ( any { $_ eq $options->{mode} } - ('root', 'fakechroot', 'proot', 'chrootless') + ('root', 'fakechroot', 'chrootless') ) { # without unshare, we use the system's rm to recursively remove the # temporary directory just to make sure that we do not accidentally # remove more than we should by using --one-file-system. - # - # --interactive=never is needed when in proot mode, the - # write-protected file /apt/apt.conf.d/01autoremove-kernels is to - # be removed. 0 == system('rm', '--interactive=never', '--recursive', '--preserve-root', '--one-file-system', $options->{root}) or error "rm failed: $?"; @@ -6119,7 +6038,7 @@ B. See the section B for more information. Choose how to perform the chroot operation and create a filesystem with ownership information different from the current user. Valid mode Is are -B, B, B, B, B, B, B and +B, B, B, B, B, B and B. The default mode is B. See the section B for more information. @@ -6444,8 +6363,7 @@ This mode automatically selects a fitting mode. If the effective user id is the one of the superuser, then the B mode is chosen. Otherwise, the B mode is picked if the system has the sysctl C set to C<1>. Should that not be the case -and if the fakechroot binary exists, the B mode is chosen. Lastly, -the B mode is used if the proot binary exists. +and if the fakechroot binary exists, the B mode is chosen. =item B, B @@ -6497,15 +6415,6 @@ package B until version 0.132. This mode will also not work with a different libc inside the chroot than on the outside. See the section B in B. -=item B - -This mode will carry out all calls to chroot with proot instead. Since -ownership information is only retained while proot is still running, this will -lead to wrong ownership information in the final directory (everything will be -owned by the user that executed B) and tarball (everything will be -owned by the root user). Extended attributes are not retained. This mode is -useful if you plan to use the chroot with proot. - =item B Uses the dpkg option C<--force-script-chrootless> to install packages into @@ -6707,12 +6616,11 @@ directory of the chroot. The path on the outside is relative to current directory of the original B invocation. The path inside the chroot must already exist. Paths outside the chroot are created as necessary. -In B and B mode, C, or C and C have to be run -inside the chroot or otherwise, symlinks will be wrongly resolved and/or -permissions will be off. This means that the special hooks might fail in -B and B mode for the B hook or for the B and -B variants if no C or C and C is available inside the -chroot. +In B mode, C, or C and C have to be run inside the +chroot or otherwise, symlinks will be wrongly resolved and/or permissions will +be off. This means that the special hooks might fail in B mode for +the B hook or for the B and B variants if no C or +C and C is available inside the chroot. =over 8 @@ -7216,7 +7124,7 @@ This section lists some differences to debootstrap. =item * Default mirrors for stable releases include updates and security mirror -=item * Multiple ways to operate as non-root: fakechroot, proot, unshare +=item * Multiple ways to operate as non-root: fakechroot and unshare =item * twice as fast diff --git a/tests/create-arm64-tarball b/tests/create-arm64-tarball index f115206..0b52d7b 100644 --- a/tests/create-arm64-tarball +++ b/tests/create-arm64-tarball @@ -21,7 +21,6 @@ prefix= $prefix {{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} # we ignore differences between architectures by ignoring some files # and renaming others -# in proot mode, some extra files are put there by proot { tar -tf /tmp/debian-chroot.tar \ | grep -v '^\./lib/ld-linux-aarch64\.so\.1$' \ | grep -v '^\./lib/aarch64-linux-gnu/ld-linux-aarch64\.so\.1$' \ @@ -40,6 +39,5 @@ $prefix {{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST | grep -v '^\./usr/share/doc/[^/]\+/changelog\(\.Debian\)\?\.amd64\.gz$' \ | grep -v '^\./usr/share/man/man8/i386\.8\.gz$' \ | grep -v '^\./usr/share/man/man8/x86_64\.8\.gz$'; - [ "{{ MODE }}" = "proot" ] && printf "./etc/ld.so.preload\n"; } | sort | diff -u - tar2.txt rm /tmp/debian-chroot.tar diff --git a/tests/special-hooks-with-mode-mode b/tests/special-hooks-with-mode-mode index eda67bf..90e89a7 100644 --- a/tests/special-hooks-with-mode-mode +++ b/tests/special-hooks-with-mode-mode @@ -19,7 +19,7 @@ prefix= [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --" [ "{{ MODE }}" = "fakechroot" ] && prefix="$prefix fakechroot fakeroot" symlinktarget=/real -case {{ MODE }} in fakechroot|proot) symlinktarget='$1/real';; esac +[ "{{ MODE }}" = "fakechroot" ] && symlinktarget='$1/real' echo copy-in-setup > /tmp/copy-in-setup echo copy-in-essential > /tmp/copy-in-essential echo copy-in-customize > /tmp/copy-in-customize