forked from josch/mmdebstrap
add undocumented --chrooted-*-hook calling pivot_root in unshare mode
This commit is contained in:
parent
449fb248e2
commit
ea146ad108
3 changed files with 173 additions and 6 deletions
|
@ -346,3 +346,7 @@ Test: error-if-stdout-is-tty
|
||||||
Test: variant-custom-timeout
|
Test: variant-custom-timeout
|
||||||
|
|
||||||
Test: include-deb-file
|
Test: include-deb-file
|
||||||
|
|
||||||
|
Test: pivot_root
|
||||||
|
Modes: root unshare
|
||||||
|
Needs-QEMU: true
|
||||||
|
|
120
mmdebstrap
120
mmdebstrap
|
@ -62,12 +62,17 @@ use version;
|
||||||
*_LINUX_CAPABILITY_VERSION_3 = \0x20080522;
|
*_LINUX_CAPABILITY_VERSION_3 = \0x20080522;
|
||||||
*CAP_SYS_ADMIN = \21;
|
*CAP_SYS_ADMIN = \21;
|
||||||
*PR_CAPBSET_READ = \23;
|
*PR_CAPBSET_READ = \23;
|
||||||
|
# from sys/mount.h
|
||||||
|
*MS_BIND = \0x1000;
|
||||||
|
*MS_REC = \0x4000;
|
||||||
|
*MNT_DETACH = \2;
|
||||||
our (
|
our (
|
||||||
$CLONE_NEWNS, $CLONE_NEWUTS,
|
$CLONE_NEWNS, $CLONE_NEWUTS,
|
||||||
$CLONE_NEWIPC, $CLONE_NEWUSER,
|
$CLONE_NEWIPC, $CLONE_NEWUSER,
|
||||||
$CLONE_NEWPID, $CLONE_NEWNET,
|
$CLONE_NEWPID, $CLONE_NEWNET,
|
||||||
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN,
|
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN,
|
||||||
$PR_CAPBSET_READ
|
$PR_CAPBSET_READ, $MS_BIND,
|
||||||
|
$MS_REC, $MNT_DETACH
|
||||||
);
|
);
|
||||||
|
|
||||||
#<<<
|
#<<<
|
||||||
|
@ -1608,6 +1613,55 @@ sub run_hooks {
|
||||||
|
|
||||||
{
|
{
|
||||||
foreach my $script (@{ $options->{"${name}_hook"} }) {
|
foreach my $script (@{ $options->{"${name}_hook"} }) {
|
||||||
|
my $type = $script->[0];
|
||||||
|
$script = $script->[1];
|
||||||
|
|
||||||
|
if ($type eq "pivoted") {
|
||||||
|
info "running --chrooted-$name-hook in shell: sh -c "
|
||||||
|
. "'$script'";
|
||||||
|
my $pid = fork() // error "fork() failed: $!";
|
||||||
|
if ($pid == 0) {
|
||||||
|
# child
|
||||||
|
my @cmdprefix = ();
|
||||||
|
if ($options->{mode} eq 'fakechroot') {
|
||||||
|
# we are calling the chroot executable instead of
|
||||||
|
# chrooting the process so that fakechroot can handle
|
||||||
|
# it
|
||||||
|
@cmdprefix = ('chroot', $options->{root});
|
||||||
|
} elsif ($options->{mode} eq 'root') {
|
||||||
|
# unsharing the mount namespace is not enough for
|
||||||
|
# pivot_root to work as root (why?) unsharing the user
|
||||||
|
# namespace as well (but without remapping) makes
|
||||||
|
# pivot_root work (why??) but still makes later lazy
|
||||||
|
# umounts fail (why???). Since pivot_root is mainly
|
||||||
|
# useful for being able to run unshare mode inside
|
||||||
|
# unshare mode, we fall back to just calling chroot()
|
||||||
|
# until somebody has motivation and time to figure out
|
||||||
|
# what is going on.
|
||||||
|
chroot $options->{root}
|
||||||
|
or error "failed to chroot(): $!";
|
||||||
|
$options->{root} = "/";
|
||||||
|
chdir "/" or error "failed chdir() to /: $!";
|
||||||
|
} elsif ($options->{mode} eq 'unshare') {
|
||||||
|
0 == syscall &SYS_unshare, $CLONE_NEWNS
|
||||||
|
or error "unshare() failed: $!";
|
||||||
|
pivot_root($options->{root});
|
||||||
|
} else {
|
||||||
|
error "unknown mode: $options->{mode}";
|
||||||
|
}
|
||||||
|
0 == system(@cmdprefix, 'env', @env_opts, 'sh', '-c',
|
||||||
|
$script)
|
||||||
|
or error "command failed: $script";
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
waitpid($pid, 0);
|
||||||
|
$? == 0 or error "chrooted hook failed with exit code $?";
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# inode and device number of chroot before
|
||||||
|
my ($dev_before, $ino_before, undef) = stat($options->{root});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$script =~ /^(
|
$script =~ /^(
|
||||||
copy-in|copy-out
|
copy-in|copy-out
|
||||||
|
@ -1660,6 +1714,26 @@ sub run_hooks {
|
||||||
'sh', '-c', $script, 'exec', $options->{root})
|
'sh', '-c', $script, 'exec', $options->{root})
|
||||||
or error "command failed: $script";
|
or error "command failed: $script";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# If the chroot directory vanished, check if pivot_root was
|
||||||
|
# performed.
|
||||||
|
#
|
||||||
|
# Running pivot_root is only really useful in the customize-hooks
|
||||||
|
# because mmdebstrap uses apt from the outside to install packages
|
||||||
|
# and that will fail after pivot_root because the process doesn't
|
||||||
|
# have access to the system on the outside anymore.
|
||||||
|
if (!-e $options->{root}) {
|
||||||
|
my ($dev_root, $ino_root, undef) = stat("/");
|
||||||
|
if ($dev_before == $dev_root and $ino_before == $ino_root) {
|
||||||
|
info "detected pivot_root, changing chroot directory to /";
|
||||||
|
# the old chroot directory is now /
|
||||||
|
# the hook probably executed pivot_root
|
||||||
|
$options->{root} = "/";
|
||||||
|
chdir "/" or error "failed chdir() to /: $!";
|
||||||
|
} else {
|
||||||
|
error "chroot directory $options->{root} vanished";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3146,6 +3220,23 @@ sub chrooted_realpath {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub pivot_root {
|
||||||
|
my $root = shift;
|
||||||
|
my $target = "/mnt";
|
||||||
|
my $put_old = "tmp";
|
||||||
|
0 == syscall &SYS_mount, $root, $target, 0, $MS_REC | $MS_BIND, 0
|
||||||
|
or error "mount failed: $!";
|
||||||
|
chdir "/mnt" or error "failed chdir() to /mnt: $!";
|
||||||
|
0 == syscall &SYS_pivot_root, my $new_root = ".", $put_old
|
||||||
|
or error "pivot_root failed: $!";
|
||||||
|
chroot "." or error "failed to chroot() to .: $!";
|
||||||
|
0 == syscall &SYS_umount2, $put_old, $MNT_DETACH
|
||||||
|
or error "umount2 failed: $!";
|
||||||
|
0 == syscall &SYS_umount2, my $sys = "sys", $MNT_DETACH
|
||||||
|
or error "umount2 failed: $!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sub hookhelper {
|
sub hookhelper {
|
||||||
my ($root, $mode, $hook, $qemu, $verbosity, $command, @args) = @_;
|
my ($root, $mode, $hook, $qemu, $verbosity, $command, @args) = @_;
|
||||||
$verbosity_level = $verbosity;
|
$verbosity_level = $verbosity;
|
||||||
|
@ -4367,16 +4458,25 @@ sub main() {
|
||||||
'force-check-gpg' =>
|
'force-check-gpg' =>
|
||||||
sub { push @{ $options->{noop} }, 'force-check-gpg'; },
|
sub { push @{ $options->{noop} }, 'force-check-gpg'; },
|
||||||
'setup-hook=s' => sub {
|
'setup-hook=s' => sub {
|
||||||
push @{ $options->{setup_hook} }, $_[1];
|
push @{ $options->{setup_hook} }, ["normal", $_[1]];
|
||||||
},
|
},
|
||||||
'extract-hook=s' => sub {
|
'extract-hook=s' => sub {
|
||||||
push @{ $options->{extract_hook} }, $_[1];
|
push @{ $options->{extract_hook} }, ["normal", $_[1]];
|
||||||
|
},
|
||||||
|
'chrooted-extract-hook=s' => sub {
|
||||||
|
push @{ $options->{extract_hook} }, ["pivoted", $_[1]];
|
||||||
},
|
},
|
||||||
'essential-hook=s' => sub {
|
'essential-hook=s' => sub {
|
||||||
push @{ $options->{essential_hook} }, $_[1];
|
push @{ $options->{essential_hook} }, ["normal", $_[1]];
|
||||||
|
},
|
||||||
|
'chrooted-essential-hook=s' => sub {
|
||||||
|
push @{ $options->{essential_hook} }, ["pivoted", $_[1]];
|
||||||
},
|
},
|
||||||
'customize-hook=s' => sub {
|
'customize-hook=s' => sub {
|
||||||
push @{ $options->{customize_hook} }, $_[1];
|
push @{ $options->{customize_hook} }, ["normal", $_[1]];
|
||||||
|
},
|
||||||
|
'chrooted-customize-hook=s' => sub {
|
||||||
|
push @{ $options->{customize_hook} }, ["pivoted", $_[1]];
|
||||||
},
|
},
|
||||||
'hook-directory=s' => sub {
|
'hook-directory=s' => sub {
|
||||||
my ($opt_name, $opt_value) = @_;
|
my ($opt_name, $opt_value) = @_;
|
||||||
|
@ -4411,7 +4511,7 @@ sub main() {
|
||||||
# list of hooks
|
# list of hooks
|
||||||
foreach my $hook (keys %scripts) {
|
foreach my $hook (keys %scripts) {
|
||||||
push @{ $options->{"${hook}_hook"} },
|
push @{ $options->{"${hook}_hook"} },
|
||||||
(sort @{ $scripts{$hook} });
|
(map { ["normal", $_] } (sort @{ $scripts{$hook} }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# Sometimes --simulate fails even though non-simulate succeeds because
|
# Sometimes --simulate fails even though non-simulate succeeds because
|
||||||
|
@ -4452,6 +4552,14 @@ sub main() {
|
||||||
if (scalar @{ $options->{"${hook}_hook"} } > 0) {
|
if (scalar @{ $options->{"${hook}_hook"} } > 0) {
|
||||||
warning "In dry-run mode, --$hook-hook options have no effect";
|
warning "In dry-run mode, --$hook-hook options have no effect";
|
||||||
}
|
}
|
||||||
|
if ($options->{mode} eq 'chrootless') {
|
||||||
|
foreach my $script (@{ $options->{"${hook}_hook"} }) {
|
||||||
|
if ($script->[0] eq "pivoted") {
|
||||||
|
error "--chrooted-$hook-hook are illegal in "
|
||||||
|
. "chrootless mode";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
55
tests/pivot_root
Normal file
55
tests/pivot_root
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
export LC_ALL=C.UTF-8
|
||||||
|
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
|
||||||
|
trap "rm -f /tmp/chroot1.tar /tmp/chroot2.tar /tmp/chroot3.tar /tmp/mmdebstrap" EXIT INT TERM
|
||||||
|
|
||||||
|
if [ ! -e /mmdebstrap-testenv ]; then
|
||||||
|
echo "this test modifies the system and should only be run inside a container" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
|
||||||
|
adduser --gecos user --disabled-password user
|
||||||
|
fi
|
||||||
|
|
||||||
|
prefix=
|
||||||
|
[ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --"
|
||||||
|
|
||||||
|
MMDEBSTRAP=
|
||||||
|
[ -e /usr/bin/mmdebstrap ] && MMDEBSTRAP=/usr/bin/mmdebstrap
|
||||||
|
[ -e ./mmdebstrap ] && MMDEBSTRAP=./mmdebstrap
|
||||||
|
|
||||||
|
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
|
||||||
|
--include=mount \
|
||||||
|
{{ DIST }} /tmp/chroot1.tar {{ MIRROR }}
|
||||||
|
|
||||||
|
if [ {{ MODE }} = "unshare" ]; then
|
||||||
|
# calling pivot_root in root mode does not work for mysterious reasons:
|
||||||
|
# pivot_root: failed to change root from `.' to `mnt': Invalid argument
|
||||||
|
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --include=mount \
|
||||||
|
--customize-hook="upload $MMDEBSTRAP /$MMDEBSTRAP" \
|
||||||
|
--customize-hook='chmod +x "$1"/'"$MMDEBSTRAP" \
|
||||||
|
--customize-hook='mount -o rbind "$1" /mnt && cd /mnt && /sbin/pivot_root . mnt' \
|
||||||
|
--customize-hook='unshare -U echo nested unprivileged unshare' \
|
||||||
|
--customize-hook='{{ CMD }} --mode=unshare --variant=apt --include=mount {{ DIST }} /tmp/chroot3.tar {{ MIRROR }}' \
|
||||||
|
--customize-hook='copy-out /tmp/chroot3.tar /tmp' \
|
||||||
|
--customize-hook='rm "$1/'"$MMDEBSTRAP"'"' \
|
||||||
|
--customize-hook='umount -l mnt sys' \
|
||||||
|
{{ DIST }} /tmp/chroot2.tar {{ MIRROR }}
|
||||||
|
|
||||||
|
cmp /tmp/chroot1.tar /tmp/chroot2.tar
|
||||||
|
cmp /tmp/chroot1.tar /tmp/chroot3.tar
|
||||||
|
rm /tmp/chroot2.tar /tmp/chroot3.tar
|
||||||
|
fi
|
||||||
|
|
||||||
|
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --include=mount \
|
||||||
|
--customize-hook="upload $MMDEBSTRAP /$MMDEBSTRAP" \
|
||||||
|
--customize-hook='chmod +x "$1"/'"$MMDEBSTRAP" \
|
||||||
|
--chrooted-customize-hook='{{ CMD }} --mode=unshare --variant=apt --include=mount {{ DIST }} /tmp/chroot3.tar {{ MIRROR }}' \
|
||||||
|
--customize-hook='copy-out /tmp/chroot3.tar /tmp' \
|
||||||
|
--customize-hook='rm "$1/'"$MMDEBSTRAP"'"' \
|
||||||
|
{{ DIST }} /tmp/chroot2.tar {{ MIRROR }}
|
||||||
|
|
||||||
|
cmp /tmp/chroot1.tar /tmp/chroot2.tar
|
||||||
|
cmp /tmp/chroot1.tar /tmp/chroot3.tar
|
Loading…
Reference in a new issue