implement dpkg-realpath in perl so that we don't need to run tar inside the chroot anymore for modes other than fakechroot and proot

This commit is contained in:
Johannes 'josch' Schauer 2020-08-15 18:29:17 +02:00
parent dc67c1f4be
commit c2c270390b
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1

View file

@ -2585,6 +2585,49 @@ sub checkokthx {
return; return;
} }
# resolve a path inside a chroot
sub chrooted_realpath {
my $root = shift;
my $src = shift;
my $result = $root;
my $prefix;
# relative paths are relative to the root of the chroot
# remove prefixed slashes
$src =~ s{^/+}{};
my $loop = 0;
while (length $src) {
if ($loop > 25) {
error "too many levels of symbolic links";
}
# Get the first directory component.
($prefix, $src) = split m{/+}, $src, 2;
# Resolve the first directory component.
if ($prefix eq ".") {
# Ignore, stay at the same directory.
} elsif ($prefix eq "..") {
# Go up one directory.
$result =~ s{(.*)/[^/]*}{$1};
# but not further than the root
if ($result !~ m/^\Q$root\E/) {
$result = $root;
}
} elsif (-l "$result/$prefix") {
my $dst = readlink "$result/$prefix";
if ($dst =~ s{^/+}{}) {
# Absolute pathname, reset result back to $root.
$result = $root;
}
$src = length $src ? "$dst/$src" : $dst;
$loop++;
} else {
# Otherwise append the prefix.
$result = "$result/$prefix";
}
}
return $result;
}
sub hookhelper { sub hookhelper {
# we put everything in an eval block because that way we can easily handle # we put everything in an eval block because that way we can easily handle
# errors without goto labels or much code duplication: the error handler # errors without goto labels or much code duplication: the error handler
@ -2597,15 +2640,6 @@ sub hookhelper {
$verbosity_level = $ARGV[5]; $verbosity_level = $ARGV[5];
my $command = $ARGV[6]; my $command = $ARGV[6];
# unless we are in the setup hook (where there is no tar inside the
# chroot) we need to run tar on the inside because otherwise, possible
# absolute symlinks in the path given via --directory are not
# correctly resolved
#
# FIXME: the issue above can be fixed by a function that is able to
# resolve absolute symlinks even inside the chroot directory to a full
# path that is valid on the outside -- fakechroot and proot have their
# own reasons, see below
my @cmdprefix = (); my @cmdprefix = ();
my @tarcmd = ( my @tarcmd = (
'tar', '--numeric-owner', '--xattrs', '--format=pax', 'tar', '--numeric-owner', '--xattrs', '--format=pax',
@ -2634,7 +2668,7 @@ sub hookhelper {
push @cmdprefix, 'proot', '--root-id', "--rootfs=$root", push @cmdprefix, 'proot', '--root-id', "--rootfs=$root",
'--cwd=/', "--qemu=$qemu"; '--cwd=/', "--qemu=$qemu";
} elsif (any { $_ eq $mode } ('root', 'chrootless', 'unshare')) { } elsif (any { $_ eq $mode } ('root', 'chrootless', 'unshare')) {
push @cmdprefix, '/usr/sbin/chroot', $root; # not chrooting in this case
} else { } else {
error "unknown mode: $mode"; error "unknown mode: $mode";
} }
@ -2657,21 +2691,43 @@ sub hookhelper {
# outside # outside
my $directory; my $directory;
if ($hook eq 'setup') { if ($hook eq 'setup') {
$directory = "$root/$outpath"; # tar runs outside, so acquire the correct path
$directory = chrooted_realpath $root, $outpath;
} elsif ( } elsif (
any { $_ eq $hook } any { $_ eq $hook }
('extract', 'essential', 'customize') ('extract', 'essential', 'customize')
) { ) {
$directory = $outpath; if (any { $_ eq $mode } ('fakechroot', 'proot')) {
# tar will run inside the chroot
$directory = $outpath;
} elsif (
any { $_ eq $mode }
('root', 'chrootless', 'unshare')
) {
$directory = chrooted_realpath $root, $outpath;
} else {
error "unknown mode: $mode";
}
} else { } else {
error "unknown hook: $hook"; error "unknown hook: $hook";
} }
# FIXME: here we would like to check if the path inside the # if chrooted_realpath was used and if neither fakechroot or
# chroot given by $directory actually exists but we cannot # proot were used (absolute symlinks will be broken) we can
# because we are missing a function that can resolve even # check and potentially fail early if the target does not exist
# paths including absolute symlinks to paths that are valid if (none { $_ eq $mode } ('fakechroot', 'proot')) {
# outside the chroot my $dirtocheck = $directory;
if ($command eq 'upload') {
# check the parent directory instead
$dirtocheck =~ s/(.*)\/[^\/]*/$1/;
}
if (!-e $dirtocheck) {
error "path does not exist: $dirtocheck";
}
if (!-d $dirtocheck) {
error "path is not a directory: $dirtocheck";
}
}
my $fh; my $fh;
if ($command eq 'upload') { if ($command eq 'upload') {
@ -2779,21 +2835,40 @@ sub hookhelper {
# outside # outside
my $directory; my $directory;
if ($hook eq 'setup') { if ($hook eq 'setup') {
$directory = "$root/$ARGV[$i]"; # tar runs outside, so acquire the correct path
$directory = chrooted_realpath $root, $ARGV[$i];
} elsif ( } elsif (
any { $_ eq $hook } any { $_ eq $hook }
('extract', 'essential', 'customize') ('extract', 'essential', 'customize')
) { ) {
$directory = $ARGV[$i]; if (any { $_ eq $mode } ('fakechroot', 'proot')) {
# tar will run inside the chroot
$directory = $ARGV[$i];
} elsif (
any { $_ eq $mode }
('root', 'chrootless', 'unshare')
) {
$directory = chrooted_realpath $root, $ARGV[$i];
} else {
error "unknown mode: $mode";
}
} else { } else {
error "unknown hook: $hook"; error "unknown hook: $hook";
} }
# FIXME: here we would like to check if the path inside the # if chrooted_realpath was used and if neither fakechroot or
# chroot given by $directory actually exists but we cannot # proot were used (absolute symlinks will be broken) we can
# because we are missing a function that can resolve even # check and potentially fail early if the source does not exist
# paths including absolute symlinks to paths that are valid if (none { $_ eq $mode } ('fakechroot', 'proot')) {
# outside the chroot if (!-e $directory) {
error "path does not exist: $directory";
}
if ($command eq 'download') {
if (!-f $directory) {
error "path is not a file: $directory";
}
}
}
my $fh; my $fh;
if ($command eq 'download') { if ($command eq 'download') {
@ -5444,12 +5519,12 @@ The path on the outside is relative to current directory of the original
B<mmdebstrap> invocation. The path inside the chroot must already exist. Paths B<mmdebstrap> invocation. The path inside the chroot must already exist. Paths
outside the chroot are created as necessary. outside the chroot are created as necessary.
To be able to resolve even absolute symlinks, C<tar> or C<sh> and C<cat> are In B<fakechroot> and B<proot> mode, C<tar>, or C<sh> and C<cat> have to be run
executed inside the chroot for the B<essential> and B<customize> hooks. This inside the chroot or otherwise, symlinks will be wrongly resolved and/or
means that the special hooks might fail for the B<extract> and B<custom> permissions will be off. This means that the special hooks might fail in
variants if no C<tar> or C<sh> and C<cat> is available inside the chroot. B<fakechroot> and B<proot> mode for the B<setup> hook or for the B<extract> and
Since nothing is yet installed at the time of the B<setup> hook, no absolute B<custom> variants if no C<tar> or C<sh> and C<cat> is available inside the
symlinks in paths inside the chroot are supported during that hook. chroot.
=over 8 =over 8