Compare commits

...

12 commits

Author SHA1 Message Date
2b832e0128
add jessie-or-older extract hook 2022-12-23 10:06:28 +01:00
a7b7e16033
move extract hook execution after run_prepare so that fakechroot works in it 2022-12-23 10:06:28 +01:00
eb98dfbaee
apt also needs /var/lib to exist 2022-12-23 10:06:28 +01:00
6c5210a94f
error out early if setup fails and thus the ext2 block reader returns EOF 2022-12-23 10:06:28 +01:00
a6a31e60eb
make sure that the unshared user has read access to the included package files 2022-12-23 10:06:28 +01:00
0dfd9adf2b
make sure absolute package paths start with a slash and are readable files 2022-12-23 10:06:28 +01:00
2fd3d768e8
avoid division by zero in progress computation 2022-12-23 10:06:27 +01:00
ccd8919e67
add tests/unshare-include-deb 2022-12-23 10:06:27 +01:00
b39713def5
remove last traces of proot 2022-12-23 10:06:27 +01:00
348c582866
coverage.py: error out if tests are missing from ./tests or from coverage.txt 2022-12-23 10:06:27 +01:00
67fbe118f3
tests/i386-which-can-be-executed-without-qemu: fixup spurious merged-/usr problem 2022-12-23 10:06:27 +01:00
5a263b5532
tests/file-mirror: wrap lines 2022-12-23 10:06:27 +01:00
10 changed files with 229 additions and 21 deletions

View file

@ -207,13 +207,27 @@ def main():
# parse coverage.txt # parse coverage.txt
config_order, config_dict = parse_config("coverage.txt") config_order, config_dict = parse_config("coverage.txt")
if set(os.listdir("tests")) - set(config_order): indirbutnotcovered = set(
[d for d in os.listdir("tests") if not d.startswith(".")]
) - set(config_order)
if indirbutnotcovered:
print( print(
"test(s) missing from coverage.txt: %s" "test(s) missing from coverage.txt: %s"
% (", ".join(sorted(set(os.listdir("tests")) - set(config_order)))), % (", ".join(sorted(indirbutnotcovered))),
file=sys.stderr, file=sys.stderr,
) )
exit(1) exit(1)
coveredbutnotindir = set(config_order) - set(
[d for d in os.listdir("tests") if not d.startswith(".")]
)
if coveredbutnotindir:
print(
"test(s) missing from ./tests: %s"
% (", ".join(sorted(coveredbutnotindir))),
file=sys.stderr,
)
exit(1)
# produce the list of tests using the cartesian product of all allowed # produce the list of tests using the cartesian product of all allowed
# dists, modes, variants and formats of a given test # dists, modes, variants and formats of a given test

View file

@ -74,7 +74,6 @@ SOURCE_DATE_EPOCH=$(date --date="$(grep-dctrl -s Date -n '' "$mirrordir/dists/$D
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
: "${HAVE_UNSHARE:=yes}" : "${HAVE_UNSHARE:=yes}"
: "${HAVE_PROOT:=yes}"
: "${HAVE_BINFMT:=yes}" : "${HAVE_BINFMT:=yes}"
# by default, use the mmdebstrap executable in the current directory together # by default, use the mmdebstrap executable in the current directory together

View file

@ -347,6 +347,14 @@ Test: variant-custom-timeout
Test: include-deb-file Test: include-deb-file
Test: unshare-include-deb
Modes: unshare
Needs-QEMU: true
Test: pivot_root Test: pivot_root
Modes: root unshare Modes: root unshare
Needs-QEMU: true Needs-QEMU: true
Test: jessie-or-older
Needs-QEMU: true
Variants: essential apt minbase

View file

@ -0,0 +1,48 @@
#!/bin/sh
set -eu
if [ "${MMDEBSTRAP_VERBOSITY:-1}" -ge 3 ]; then
set -x
fi
TARGET="$1"
for f in available diversions cmethopt; do
if [ ! -e "$TARGET/var/lib/dpkg/$f" ]; then
touch "$TARGET/var/lib/dpkg/$f"
fi
done
if [ -z "${MMDEBSTRAP_ESSENTIAL+x}" ]; then
MMDEBSTRAP_ESSENTIAL=
for f in "$TARGET/var/cache/apt/archives/"*.deb; do
[ -f "$f" ] || continue
f="${f#"$TARGET"}"
MMDEBSTRAP_ESSENTIAL="$MMDEBSTRAP_ESSENTIAL $f"
done
fi
fname_base_passwd=
fname_base_files=
fname_dpkg=
for pkg in $MMDEBSTRAP_ESSENTIAL; do
pkgname=$(dpkg-deb --show --showformat='${Package}' "$TARGET/$pkg")
# shellcheck disable=SC2034
case $pkgname in
base-passwd) fname_base_passwd=$pkg;;
base-files) fname_base_files=$pkg;;
dpkg) fname_dpkg=$pkg;;
esac
done
for var in base_passwd base_files dpkg; do
eval 'val=$fname_'"$var"
[ -z "$val" ] && continue
chroot "$TARGET" dpkg --install --force-depends "$val"
done
# shellcheck disable=SC2086
chroot "$TARGET" dpkg --unpack --force-depends $MMDEBSTRAP_ESSENTIAL
chroot "$TARGET" dpkg --configure --pending

View file

@ -391,7 +391,6 @@ components=main
: "${DEFAULT_DIST:=unstable}" : "${DEFAULT_DIST:=unstable}"
: "${HAVE_QEMU:=yes}" : "${HAVE_QEMU:=yes}"
: "${RUN_MA_SAME_TESTS:=yes}" : "${RUN_MA_SAME_TESTS:=yes}"
: "${HAVE_PROOT:=yes}"
# by default, use the mmdebstrap executable in the current directory # by default, use the mmdebstrap executable in the current directory
: "${CMD:=./mmdebstrap}" : "${CMD:=./mmdebstrap}"
@ -505,9 +504,6 @@ if [ "$HAVE_QEMU" = "yes" ]; then
if [ "$DEFAULT_DIST" != "oldstable" ]; then if [ "$DEFAULT_DIST" != "oldstable" ]; then
pkgs="$pkgs,squashfs-tools-ng,genext2fs" pkgs="$pkgs,squashfs-tools-ng,genext2fs"
fi fi
if [ "$HAVE_PROOT" = "yes" ]; then
pkgs="$pkgs,proot"
fi
if [ ! -e ./mmdebstrap ]; then if [ ! -e ./mmdebstrap ]; then
pkgs="$pkgs,mmdebstrap" pkgs="$pkgs,mmdebstrap"
fi fi

View file

@ -950,7 +950,11 @@ sub run_dpkg_progress {
} }
$num += 1; $num += 1;
} }
if ($total == 0) {
return 0, $status;
} else {
return $num / $total * 100, $status; return $num / $total * 100, $status;
}
}; };
run_progress $get_exec, $line_handler, $line_has_error; run_progress $get_exec, $line_handler, $line_has_error;
return; return;
@ -1539,6 +1543,7 @@ sub setup_mounts {
sub run_hooks { sub run_hooks {
my $name = shift; my $name = shift;
my $options = shift; my $options = shift;
my $essential_pkgs = shift;
if (scalar @{ $options->{"${name}_hook"} } == 0) { if (scalar @{ $options->{"${name}_hook"} } == 0) {
return; return;
@ -1597,6 +1602,12 @@ sub run_hooks {
push @env_opts, push @env_opts,
("MMDEBSTRAP_INCLUDE=" . (join ",", @escaped_includes)); ("MMDEBSTRAP_INCLUDE=" . (join ",", @escaped_includes));
} }
# Give the extract hook access to the essential packages that are about to
# be installed
if ($name eq "extract" and scalar @{$essential_pkgs} > 0) {
push @env_opts,
("MMDEBSTRAP_ESSENTIAL=" . (join " ", @{$essential_pkgs}));
}
# Unset the close-on-exec flag, so that the file descriptor does not # Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec # get closed when we exec
@ -1814,14 +1825,16 @@ sub setup {
} }
eval { eval {
run_hooks('extract', $options);
if ($options->{variant} ne 'extract') {
my $chrootcmd = []; my $chrootcmd = [];
if ($options->{variant} ne 'extract') {
if ($options->{mode} ne 'chrootless') { if ($options->{mode} ne 'chrootless') {
$chrootcmd = run_prepare($options); $chrootcmd = run_prepare($options);
} }
}
run_hooks('extract', $options, $essential_pkgs);
if ($options->{variant} ne 'extract') {
run_essential($options, $essential_pkgs, $chrootcmd, $cached_debs); run_essential($options, $essential_pkgs, $chrootcmd, $cached_debs);
run_hooks('essential', $options); run_hooks('essential', $options);
@ -4359,9 +4372,15 @@ sub main() {
if (!defined $pkg) { if (!defined $pkg) {
error "cannot resolve absolute path of $pkg: $!"; error "cannot resolve absolute path of $pkg: $!";
} }
if ($pkg !~ /^\//) {
error "absolute path of $pkg doesn't start with a slash";
}
if (!-f $pkg) { if (!-f $pkg) {
error "$pkg is not an existing file"; error "$pkg is not an existing file";
} }
if (!-r $pkg) {
error "$pkg is not readable";
}
return $pkg; return $pkg;
}; };
if ($opt_value =~ /^[?~!(]/) { if ($opt_value =~ /^[?~!(]/) {
@ -5566,6 +5585,36 @@ sub main() {
$? == 0 or error "chown failed"; $? == 0 or error "chown failed";
} }
# check if .deb files given by --include are readable by the unshared user
if ($options->{mode} eq 'unshare'
and scalar(grep { /^\// } @{ $options->{include} }) > 0) {
my $pid = get_unshare_cmd(
sub {
my $ret = 0;
foreach my $f (grep { /^\// } @{ $options->{include} }) {
# open the file for real because -r will report the file as
# readable even though open will fail (in contrast to the
# coreutils test utility, perl doesn't use faccessat)
my $res = open(my $fh, '<', $f);
if (!$res) {
warning "unshared user cannot access $f for reading";
$ret = 1;
} else {
close $fh;
}
}
exit $ret;
},
\@idmap
);
waitpid $pid, 0;
if ($? != 0) {
warning "no read access for some packages for the unshared user";
warning "maybe try running mmdebstrap with "
. "--hook-dir=/usr/share/mmdebstrap/hooks/file-mirror-automount";
}
}
# figure out whether we have mknod # figure out whether we have mknod
$options->{havemknod} = 0; $options->{havemknod} = 0;
if ($options->{mode} eq 'unshare') { if ($options->{mode} eq 'unshare') {
@ -5880,7 +5929,13 @@ sub main() {
my $numblocks = 0; my $numblocks = 0;
close $nblkwriter; close $nblkwriter;
if (!$options->{dryrun} && $format eq 'ext2') { if (!$options->{dryrun} && $format eq 'ext2') {
chomp($numblocks = <$nblkreader>); $numblocks = <$nblkreader>;
if (!defined $numblocks) {
# This can happen if the setup process died early and thus closes
# the pipe from the other and. The EOF is turned into undef.
error "failed to read required number of blocks";
}
chomp $numblocks;
} }
close $nblkreader; close $nblkreader;
@ -6806,7 +6861,7 @@ retained.
This section gives an overview of the different steps to create a chroot. At This section gives an overview of the different steps to create a chroot. At
its core, what B<mmdebstrap> does can be put into a 14 line shell script: its core, what B<mmdebstrap> does can be put into a 14 line shell script:
mkdir -p "$2/etc/apt" "$2/var/cache" mkdir -p "$2/etc/apt" "$2/var/cache" "$2/var/lib"
cat << END > "$2/apt.conf" cat << END > "$2/apt.conf"
Apt::Architecture "$(dpkg --print-architecture)"; Apt::Architecture "$(dpkg --print-architecture)";
Apt::Architectures "$(dpkg --print-architecture)"; Apt::Architectures "$(dpkg --print-architecture)";
@ -6902,10 +6957,6 @@ respectively.
Extract the downloaded packages into the rootfs. Extract the downloaded packages into the rootfs.
=item B<extract-hook>
Run B<--extract-hook> options and all F<extract*> scripts in B<--hook-dir>.
=item B<prepare> =item B<prepare>
In B<fakechroot> mode, environment variables C<LD_LIBRARY_PATH> will be set up In B<fakechroot> mode, environment variables C<LD_LIBRARY_PATH> will be set up
@ -6914,6 +6965,10 @@ in. For foreign B<fakechroot> environments, C<LD_LIBRARY_PATH> and
C<QEMU_LD_PREFIX> are set up accordingly. This step is not carried out in C<QEMU_LD_PREFIX> are set up accordingly. This step is not carried out in
B<extract> mode and neither for the B<chrootless> variant. B<extract> mode and neither for the B<chrootless> variant.
=item B<extract-hook>
Run B<--extract-hook> options and all F<extract*> scripts in B<--hook-dir>.
=item B<essential> =item B<essential>
Uses C<dpkg --install> to properly install all packages that have been Uses C<dpkg --install> to properly install all packages that have been

View file

@ -5,6 +5,9 @@ if [ ! -e /mmdebstrap-testenv ]; then
echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2 echo "this test requires the cache directory to be mounted on /mnt and should only be run inside a container" >&2
exit 1 exit 1
fi fi
{{ CMD }} --mode={{ MODE }} --variant=apt --setup-hook='mkdir -p "$1"/mnt/cache/debian; mount -o ro,bind /mnt/cache/debian "$1"/mnt/cache/debian' --customize-hook='umount "$1"/mnt/cache/debian; rmdir "$1"/mnt/cache/debian "$1"/mnt/cache' {{ DIST }} /tmp/debian-chroot.tar "deb file:///mnt/cache/debian {{ DIST }} main" {{ CMD }} --mode={{ MODE }} --variant=apt \
--setup-hook='mkdir -p "$1"/mnt/cache/debian; mount -o ro,bind /mnt/cache/debian "$1"/mnt/cache/debian' \
--customize-hook='umount "$1"/mnt/cache/debian; rmdir "$1"/mnt/cache/debian "$1"/mnt/cache' \
{{ DIST }} /tmp/debian-chroot.tar "deb file:///mnt/cache/debian {{ DIST }} main"
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm /tmp/debian-chroot.tar rm /tmp/debian-chroot.tar

View file

@ -27,7 +27,8 @@ apt-get remove --yes qemu-user-static binfmt-support qemu-user
| grep -v '^\./usr/bin/x86_64$' \ | grep -v '^\./usr/bin/x86_64$' \
| grep -v '^\./usr/lib32/$' \ | grep -v '^\./usr/lib32/$' \
| grep -v '^\./lib32$' \ | grep -v '^\./lib32$' \
| grep -v '^\./lib64/$' \ | grep -v '^\./lib64$' \
| grep -v '^\./usr/lib64/$' \
| grep -v '^\./usr/lib64/ld-linux-x86-64\.so\.2$' \ | grep -v '^\./usr/lib64/ld-linux-x86-64\.so\.2$' \
| grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/$' \ | grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/$' \
| grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/[0-9]\+/$' \ | grep -v '^\./usr/lib/gcc/x86_64-linux-gnu/[0-9]\+/$' \

39
tests/jessie-or-older Normal file
View file

@ -0,0 +1,39 @@
#!/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
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
filter() {
./tarfilter \
--path-exclude=/usr/bin/uncompress \
--path-exclude=/var/cache/debconf/config.dat-old \
--path-exclude=/var/cache/debconf/templates.dat-old \
--path-exclude=/var/lib/dpkg/available \
--path-exclude=/var/lib/dpkg/cmethopt \
--path-exclude=/var/lib/dpkg/status-old \
--path-exclude=/var/lib/shells.state
}
# base for comparison without jessie-or-older hook
{{ CMD }} --mode=root --variant={{ VARIANT }} {{ DIST }} - {{ MIRROR }} | filter > /tmp/debian-chroot-root-normal.tar
# root
{{ CMD }} --mode=root --variant={{ VARIANT }} --hook-dir=./hooks/jessie-or-older {{ DIST }} - {{ MIRROR }} | filter > /tmp/debian-chroot-root.tar
cmp /tmp/debian-chroot-root-normal.tar /tmp/debian-chroot-root.tar
rm /tmp/debian-chroot-root.tar
# unshare
runuser -u user -- {{ CMD }} --mode=unshare --variant={{ VARIANT }} --hook-dir=./hooks/jessie-or-older {{ DIST }} - {{ MIRROR }} | filter > /tmp/debian-chroot-unshare.tar
cmp /tmp/debian-chroot-root-normal.tar /tmp/debian-chroot-unshare.tar
rm /tmp/debian-chroot-unshare.tar
# fakechroot
runuser -u user -- {{ CMD }} --mode=fakechroot --variant={{ VARIANT }} --hook-dir=./hooks/jessie-or-older {{ DIST }} - {{ MIRROR }} | filter > /tmp/debian-chroot-fakechroot.tar
cmp /tmp/debian-chroot-root-normal.tar /tmp/debian-chroot-fakechroot.tar
rm /tmp/debian-chroot-fakechroot.tar
rm /tmp/debian-chroot-root-normal.tar

45
tests/unshare-include-deb Normal file
View file

@ -0,0 +1,45 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
[ "{{ MODE }}" = unshare ]
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
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
fi
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
# instead of obtaining a .deb from our cache, we create a new package because
# otherwise apt might decide to download the package with the same name and
# version from the cache instead of using the local .deb
mkdir -p /tmp/dummypkg/DEBIAN
cat << END > "/tmp/dummypkg/DEBIAN/control"
Package: dummypkg
Priority: optional
Section: oldlibs
Maintainer: Johannes Schauer Marin Rodrigues <josch@debian.org>
Architecture: all
Multi-Arch: foreign
Source: dummypkg
Version: 1
Description: dummypkg
END
dpkg-deb --build "/tmp/dummypkg" "/tmp/dummypkg.deb"
# make the .deb only redable by user which will exclude the unshared user
chmod 600 /tmp/dummypkg.deb
chown user /tmp/dummypkg.deb
ret=0
$prefix {{ CMD }} --variant=apt --mode={{ MODE }} --include="/tmp/dummypkg.deb" \
{{ DIST }} /dev/null {{ MIRROR }} || ret=$?
if [ "$ret" -eq 0 ]; then
echo "expected failure but got exit $ret" >&2
exit 1
fi