use rm and find instead of remove_tree()

* remove_tree() requires the CWD to be accessible or fails with
   cannot chdir to $CWD from $DIR_TO_DELETE: Permission denied, aborting.
 * CWD is not always accessible -- example: run mmdebstrap from a
   directory only accessible by the current user (like a tempdir) in
   unshare mode
 * find from findutils *also* requires CWD to be accessible but it's
   easier to temporarily change CWD in a subprocess because using
   there is no utility in perl core that changes CWD temporarily and
   cleans up after itself
 * we need to use find from findutils instead of rm in unshare mode
   because the root directory itself might not be removable by the
   unshared user so we only want to remove its subdirectories
This commit is contained in:
Johannes Schauer Marin Rodrigues 2021-10-06 21:20:41 +02:00
parent c2d988b475
commit 4f278deadf
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 67 additions and 50 deletions

View file

@ -127,7 +127,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s
fi fi
fi fi
starttime= starttime=
total=212 total=213
skipped=0 skipped=0
runtests=0 runtests=0
i=1 i=1
@ -1055,6 +1055,39 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi fi
print_header "mode=unshare,variant=apt: CWD directory not accessible by unshared user"
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
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
mkdir /tmp/debian-chroot
chmod 700 /tmp/debian-chroot
chown user:user /tmp/debian-chroot
if [ "$CMD" = "./mmdebstrap" ]; then
CMD=\$(realpath --canonicalize-existing ./mmdebstrap)
elif [ "$CMD" = "perl -MDevel::Cover=-silent,-nogcov ./mmdebstrap" ]; then
CMD="perl -MDevel::Cover=-silent,-nogcov \$(realpath --canonicalize-existing ./mmdebstrap)"
else
CMD="$CMD"
fi
env --chdir=/tmp/debian-chroot runuser -u user -- \$CMD --mode=unshare --variant=apt $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
echo "HAVE_QEMU != yes -- Skipping test..." >&2
skipped=$((skipped+1))
fi
print_header "mode=unshare,variant=apt: create gzip compressed tarball" print_header "mode=unshare,variant=apt: create gzip compressed tarball"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh

View file

@ -29,7 +29,7 @@ use English;
use Getopt::Long; use Getopt::Long;
use Pod::Usage; use Pod::Usage;
use File::Copy; use File::Copy;
use File::Path qw(make_path remove_tree); use File::Path qw(make_path);
use File::Temp qw(tempfile tempdir); use File::Temp qw(tempfile tempdir);
use File::Basename; use File::Basename;
use File::Find; use File::Find;
@ -3057,15 +3057,14 @@ sub run_cleanup() {
# apt since 1.6 creates the auxfiles directory. If apt inside the # apt since 1.6 creates the auxfiles directory. If apt inside the
# chroot is older than that, then it will not know how to clean it. # chroot is older than that, then it will not know how to clean it.
if (-e "$options->{root}/var/lib/apt/lists/auxfiles") { if (-e "$options->{root}/var/lib/apt/lists/auxfiles") {
remove_tree("$options->{root}/var/lib/apt/lists/auxfiles", 0 == system(
{ error => \my $err }); 'rm',
if (@$err) { '--interactive=never',
for my $diag (@$err) { '--recursive',
my ($file, $message) = %$diag; '--preserve-root',
if ($file eq '') { warning "general error: $message"; } '--one-file-system',
else { warning "problem unlinking $file: $message"; } "$options->{root}/var/lib/apt/lists/auxfiles"
} ) or error "rm failed: $?";
}
} }
} }
@ -3135,15 +3134,11 @@ sub run_cleanup() {
next if $entry eq "."; next if $entry eq ".";
next if $entry eq ".."; next if $entry eq "..";
warning "deleting files in /tmp: $entry"; warning "deleting files in /tmp: $entry";
remove_tree("$options->{root}/tmp/$entry", 0 == system(
{ error => \my $err }); 'rm', '--interactive=never',
if (@$err) { '--recursive', '--preserve-root',
for my $diag (@$err) { '--one-file-system', "$options->{root}/tmp/$entry"
my ($file, $message) = %$diag; ) or error "rm failed: $?";
if ($file eq '') { warning "general error: $message"; }
else { warning "problem unlinking $file: $message"; }
}
}
} }
closedir($dh); closedir($dh);
} }
@ -4519,8 +4514,11 @@ sub main() {
$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin"; $ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
} }
foreach my $tool ('dpkg', 'dpkg-deb', 'apt-get', 'apt-cache', 'apt-config', foreach my $tool (
'tar') { 'dpkg', 'dpkg-deb', 'apt-get', 'apt-cache',
'apt-config', 'tar', 'rm', 'find',
'env'
) {
my $found = 0; my $found = 0;
foreach my $path (split /:/, $ENV{PATH}) { foreach my $path (split /:/, $ENV{PATH}) {
if (-f "$path/$tool" && -x _ ) { if (-f "$path/$tool" && -x _ ) {
@ -5922,31 +5920,18 @@ sub main() {
# no risk of removing anything important. # no risk of removing anything important.
$pid = get_unshare_cmd( $pid = get_unshare_cmd(
sub { sub {
# File::Path will produce the error "cannot stat initial # change CWD to chroot directory because find tries to
# working directory" if the working directory cannot be # chdir to the current directory which might not be
# accessed by the unprivileged unshared user. Thus, we # accessible by the unshared user:
# first navigate to the parent of the root directory. # find: Failed to restore initial working directory
chdir "$options->{root}/.." 0 == system('env', "--chdir=$options->{root}", 'find',
or error "unable to chdir() to parent directory of" $options->{root}, '-mount',
. " $options->{root}: $!"; '-mindepth', '1', '-delete')
remove_tree($options->{root}, { error => \my $err }); or error "rm failed: $?";
if (@$err) { # ignore failure in case the unshared user doesn't have the
for my $diag (@$err) { # required permissions -- we attempt again later if
my ($file, $message) = %$diag; # necessary
if ($file eq '') { rmdir "$options->{root}";
warning "general error: $message";
} else {
# the unshared process might not have
# sufficient permissions to remove the root
# directory. We attempt its removal later, so
# don't warn if its removal fails now.
if ($file ne $options->{root}) {
warning
"problem unlinking $file: $message";
}
}
}
}
}, },
\@idmap \@idmap
); );
@ -5954,8 +5939,7 @@ sub main() {
$? == 0 or error "remove_tree failed"; $? == 0 or error "remove_tree failed";
# in unshare mode, the toplevel directory might've been created in # in unshare mode, the toplevel directory might've been created in
# a directory that the unshared user cannot change and thus cannot # a directory that the unshared user cannot change and thus cannot
# delete. If the root directory still exists after remove_tree # delete. We attempt its removal again outside as the normal user.
# above, we attempt its removal again outside as the normal user.
if (-e $options->{root}) { if (-e $options->{root}) {
rmdir "$options->{root}" rmdir "$options->{root}"
or error "cannot rmdir $options->{root}: $!"; or error "cannot rmdir $options->{root}: $!";
@ -5973,7 +5957,7 @@ sub main() {
# be removed. # be removed.
0 == system('rm', '--interactive=never', '--recursive', 0 == system('rm', '--interactive=never', '--recursive',
'--preserve-root', '--one-file-system', $options->{root}) '--preserve-root', '--one-file-system', $options->{root})
or error "rm failed: $!"; or error "rm failed: $?";
} else { } else {
error "unknown mode: $options->{mode}"; error "unknown mode: $options->{mode}";
} }