Rework /dev, /sys, /proc mounting

- assume all entries in @devfiles to be in /dev
 - allow for /dev, /sys and /proc not to exist in the target and print warning
 - allow for /dev entries as well as /sys and /proc not to exist on the outside
 - simplify umount by storing special options in @umountopts
 - remove superfluous checks for root and unshare mode
 - make sure /dev entries are less than 100 chars in size for tar
This commit is contained in:
Johannes Schauer Marin Rodrigues 2021-03-08 08:04:35 +01:00
parent d52eaa4814
commit 5a3d1ab5c4
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 201 additions and 102 deletions

View file

@ -120,7 +120,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s
fi fi
fi fi
starttime= starttime=
total=191 total=193
skipped=0 skipped=0
runtests=0 runtests=0
i=1 i=1
@ -937,6 +937,51 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi fi
print_header "mode=unshare,variant=apt: missing device nodes outside the chroot"
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
rm /dev/console
adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1
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=custom: missing /dev, /sys, /proc inside the chroot"
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
runuser -u user -- $CMD --mode=unshare --variant=custom --include=dpkg,dash,diffutils,coreutils,libc-bin,sed $DEFAULT_DIST /dev/null $mirror
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=root,variant=apt: chroot directory not accessible by _apt user" print_header "mode=root,variant=apt: chroot directory not accessible by _apt user"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh

View file

@ -61,6 +61,7 @@ our ($CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC,
$CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET, $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET,
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN); $_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN);
#<<<
# type codes: # type codes:
# 0 -> normal file # 0 -> normal file
# 1 -> hardlink # 1 -> hardlink
@ -69,23 +70,24 @@ our ($CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC,
# 4 -> block special # 4 -> block special
# 5 -> directory # 5 -> directory
my @devfiles = ( my @devfiles = (
# filename mode type link target major minor # filename mode type link target major minor
["./dev/", oct(755), 5, '', undef, undef], ["", oct(755), 5, '', undef, undef],
["./dev/console", oct(666), 3, '', 5, 1], ["console", oct(666), 3, '', 5, 1],
["./dev/fd", oct(777), 2, '/proc/self/fd', undef, undef], ["fd", oct(777), 2, '/proc/self/fd', undef, undef],
["./dev/full", oct(666), 3, '', 1, 7], ["full", oct(666), 3, '', 1, 7],
["./dev/null", oct(666), 3, '', 1, 3], ["null", oct(666), 3, '', 1, 3],
["./dev/ptmx", oct(666), 3, '', 5, 2], ["ptmx", oct(666), 3, '', 5, 2],
["./dev/pts/", oct(755), 5, '', undef, undef], ["pts/", oct(755), 5, '', undef, undef],
["./dev/random", oct(666), 3, '', 1, 8], ["random", oct(666), 3, '', 1, 8],
["./dev/shm/", oct(755), 5, '', undef, undef], ["shm/", oct(755), 5, '', undef, undef],
["./dev/stderr", oct(777), 2, '/proc/self/fd/2', undef, undef], ["stderr", oct(777), 2, '/proc/self/fd/2', undef, undef],
["./dev/stdin", oct(777), 2, '/proc/self/fd/0', undef, undef], ["stdin", oct(777), 2, '/proc/self/fd/0', undef, undef],
["./dev/stdout", oct(777), 2, '/proc/self/fd/1', undef, undef], ["stdout", oct(777), 2, '/proc/self/fd/1', undef, undef],
["./dev/tty", oct(666), 3, '', 5, 0], ["tty", oct(666), 3, '', 5, 0],
["./dev/urandom", oct(666), 3, '', 1, 9], ["urandom", oct(666), 3, '', 1, 9],
["./dev/zero", oct(666), 3, '', 1, 5], ["zero", oct(666), 3, '', 1, 5],
); );
#>>>
# verbosity levels: # verbosity levels:
# 0 -> print nothing # 0 -> print nothing
@ -918,84 +920,111 @@ sub run_chroot {
foreach my $file (@devfiles) { foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor) my ($fname, $mode, $type, $linkname, $devmajor, $devminor)
= @{$file}; = @{$file};
next if $fname eq './dev/'; next if $fname eq '';
if ($type == 0) { # normal file if ($type == 0) { # normal file
error "type 0 not implemented"; error "type 0 not implemented";
} elsif ($type == 1) { # hardlink } elsif ($type == 1) { # hardlink
error "type 1 not implemented"; error "type 1 not implemented";
} elsif ($type == 2) { # symlink } elsif ($type == 2) { # symlink
if (!$options->{havemknod}) { if (!$options->{havemknod}) {
if ( $options->{mode} eq 'fakechroot' # If we had mknod, then the symlink was already created
and $linkname =~ /^\/proc/) { # in the run_setup function.
# there is no /proc in fakechroot mode if (!-d "$options->{root}/dev") {
warning(
"skipping creation of ./dev/$fname because the"
. " /dev directory is missing in the target"
);
next; next;
} }
if ( push @cleanup_tasks, sub {
any { $_ eq $options->{mode} } unlink "$options->{root}/dev/$fname"
('root', 'unshare') or warn "cannot unlink ./dev/$fname: $!";
) { };
push @cleanup_tasks, sub { symlink $linkname, "$options->{root}/dev/$fname"
unlink "$options->{root}/$fname" or error
or warn "cannot unlink $fname: $!"; "cannot create symlink ./dev/$fname -> $linkname";
}
}
symlink $linkname, "$options->{root}/$fname"
or error "cannot create symlink $fname";
} }
} elsif ($type == 3 or $type == 4) { } elsif ($type == 3 or $type == 4) {
# character/block special # character/block special
if ($options->{mode} eq 'root' && !$options->{canmount}) { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping bind-mounting $fname"; warning "skipping bind-mounting ./dev/$fname";
} elsif (!$options->{havemknod}) { } elsif (!$options->{havemknod}) {
open my $fh, '>', "$options->{root}/$fname" if (!-d "$options->{root}/dev") {
or error "cannot open $options->{root}/$fname: $!"; warning(
close $fh; "skipping creation of ./dev/$fname because the"
if ($options->{mode} eq 'unshare') { . " /dev directory is missing in the target"
push @cleanup_tasks, sub { );
0 == system('umount', '--no-mtab', next;
"$options->{root}/$fname")
or warn "umount $fname failed: $?";
unlink "$options->{root}/$fname"
or warn "cannot unlink $fname: $!";
};
} elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount',
"$options->{root}/$fname")
or warn "umount failed: $?";
unlink "$options->{root}/$fname"
or warn "cannot unlink $fname: $!";
};
} else {
error "unknown mode: $options->{mode}";
} }
0 == system('mount', '-o', 'bind', "/$fname", if (!-e "/dev/$fname") {
"$options->{root}/$fname") warning("skipping creation of ./dev/$fname because"
or error "mount $fname failed: $?"; . " /dev/$fname does not exist"
. " on the outside");
next;
}
if (!-c "/dev/$fname") {
warning("skipping creation of ./dev/$fname because"
. " /dev/$fname on the outside is not a"
. " character special file");
next;
}
open my $fh, '>', "$options->{root}/dev/$fname"
or error
"cannot open $options->{root}/dev/$fname: $!";
close $fh;
my @umountopts = ();
if ($options->{mode} eq 'unshare') {
push @umountopts, '--no-mtab';
}
push @cleanup_tasks, sub {
0 == system('umount', @umountopts,
"$options->{root}/dev/$fname")
or warn "umount ./dev/$fname failed: $?";
unlink "$options->{root}/dev/$fname"
or warn "cannot unlink ./dev/$fname: $!";
};
0 == system('mount', '-o', 'bind', "/dev/$fname",
"$options->{root}/dev/$fname")
or error "mount ./dev/$fname failed: $?";
} }
} elsif ($type == 5 } elsif ($type == 5
&& $options->{mode} eq 'root' && $options->{mode} eq 'root'
&& !$options->{canmount}) { && !$options->{canmount}) {
warning "skipping bind-mounting $fname"; warning "skipping bind-mounting ./dev/$fname";
} elsif ($type == 5) { # directory } elsif ($type == 5) { # directory
if (!-d "$options->{root}/dev") {
warning(
"skipping creation of ./dev/$fname because the"
. " /dev directory is missing in the target");
next;
}
if (!-e "/dev/$fname") {
warning("skipping creation of ./dev/$fname because"
. " /dev/$fname does not exist"
. " on the outside");
next;
}
if (!-d "/dev/$fname") {
warning("skipping creation of ./dev/$fname because"
. " /dev/$fname on the outside is not a"
. " directory");
next;
}
if (!$options->{havemknod}) { if (!$options->{havemknod}) {
if ( # If had mknod, then the directory to bind-mount into
any { $_ eq $options->{mode} } # was already created in the run_setup function.
('root', 'unshare') push @cleanup_tasks, sub {
) { rmdir "$options->{root}/dev/$fname"
push @cleanup_tasks, sub { or warn "cannot rmdir ./dev/$fname: $!";
rmdir "$options->{root}/$fname" };
or warn "cannot rmdir $fname: $!"; if (-e "$options->{root}/dev/$fname") {
} if (!-d "$options->{root}/dev/$fname") {
} error "./dev/$fname already exists but is not"
if (-e "$options->{root}/$fname") { . " a directory";
if (!-d "$options->{root}/$fname") {
error "$fname already exists but is not a"
. " directory";
} }
} else { } else {
my $num_created my $num_created
= make_path "$options->{root}/$fname", = make_path "$options->{root}/dev/$fname",
{ error => \my $err }; { error => \my $err };
if ($err && @$err) { if ($err && @$err) {
error( error(
@ -1007,29 +1036,25 @@ sub run_chroot {
} @$err } @$err
)); ));
} elsif ($num_created == 0) { } elsif ($num_created == 0) {
error "cannot create $options->{root}/$fname"; error
"cannot create $options->{root}/dev/$fname";
} }
} }
chmod $mode, "$options->{root}/$fname" chmod $mode, "$options->{root}/dev/$fname"
or error "cannot chmod $fname: $!"; or error "cannot chmod ./dev/$fname: $!";
} }
my @umountopts = ();
if ($options->{mode} eq 'unshare') { if ($options->{mode} eq 'unshare') {
push @cleanup_tasks, sub { push @umountopts, '--no-mtab';
0 == system('umount', '--no-mtab',
"$options->{root}/$fname")
or warn "umount $fname failed: $?";
};
} elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/$fname")
or warn "umount $fname failed: $?";
};
} else {
error "unknown mode: $options->{mode}";
} }
0 == system('mount', '-o', 'bind', "/$fname", push @cleanup_tasks, sub {
"$options->{root}/$fname") 0 == system('umount', @umountopts,
or error "mount $fname failed: $?"; "$options->{root}/dev/$fname")
or warn "umount ./dev/$fname failed: $?";
};
0 == system('mount', '-o', 'bind', "/dev/$fname",
"$options->{root}/dev/$fname")
or error "mount ./dev/$fname failed: $?";
} else { } else {
error "unsupported type: $type"; error "unsupported type: $type";
} }
@ -1049,6 +1074,9 @@ sub run_chroot {
# to extract those # to extract those
if ($options->{mode} eq 'root' && !$options->{canmount}) { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping mount sysfs"; warning "skipping mount sysfs";
} elsif ($options->{mode} eq 'root' && !-d "$options->{root}/sys") {
warning("skipping mounting of sysfs because the"
. " /sys directory is missing in the target");
} elsif ($options->{mode} eq 'root') { } elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub { push @cleanup_tasks, sub {
0 == system('umount', "$options->{root}/sys") 0 == system('umount', "$options->{root}/sys")
@ -1059,6 +1087,15 @@ sub run_chroot {
'-o', 'ro,nosuid,nodev,noexec', 'sys', '-o', 'ro,nosuid,nodev,noexec', 'sys',
"$options->{root}/sys" "$options->{root}/sys"
) or error "mount /sys failed: $?"; ) or error "mount /sys failed: $?";
} elsif ($options->{mode} eq 'unshare' && !-d "$options->{root}/sys") {
warning("skipping bind-mounting /sys because the"
. " /sys directory is missing in the target");
} elsif ($options->{mode} eq 'unshare' && !-e "/sys") {
warning("skipping bind-mounting /sys because"
. " /sys does not exist on the outside");
} elsif ($options->{mode} eq 'unshare' && !-d "/sys") {
warning("skipping bind-mounting /sys because"
. " /sys on the outside is not a directory");
} elsif ($options->{mode} eq 'unshare') { } elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where # naturally we have to clean up after ourselves in sudo mode where
# we do a real mount. But we also need to unmount in unshare mode # we do a real mount. But we also need to unmount in unshare mode
@ -1091,6 +1128,9 @@ sub run_chroot {
} }
if ($options->{mode} eq 'root' && !$options->{canmount}) { if ($options->{mode} eq 'root' && !$options->{canmount}) {
warning "skipping mount proc"; warning "skipping mount proc";
} elsif ($options->{mode} eq 'root' && !-d "$options->{root}/proc") {
warning("skipping mounting of proc because the"
. " /proc directory is missing in the target");
} elsif ($options->{mode} eq 'root') { } elsif ($options->{mode} eq 'root') {
push @cleanup_tasks, sub { push @cleanup_tasks, sub {
# some maintainer scripts mount additional stuff into /proc # some maintainer scripts mount additional stuff into /proc
@ -1110,6 +1150,16 @@ sub run_chroot {
0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc', 0 == system('mount', '-t', 'proc', '-o', 'ro', 'proc',
"$options->{root}/proc") "$options->{root}/proc")
or error "mount /proc failed: $?"; or error "mount /proc failed: $?";
} elsif ($options->{mode} eq 'unshare' && !-d "$options->{root}/proc")
{
warning("skipping bind-mounting /proc because the"
. " /proc directory is missing in the target");
} elsif ($options->{mode} eq 'unshare' && !-e "/proc") {
warning("skipping bind-mounting /proc because"
. " /proc does not exist on the outside");
} elsif ($options->{mode} eq 'unshare' && !-d "/proc") {
warning("skipping bind-mounting /proc because"
. " /proc on the outside is not a directory");
} elsif ($options->{mode} eq 'unshare') { } elsif ($options->{mode} eq 'unshare') {
# naturally we have to clean up after ourselves in sudo mode where # naturally we have to clean up after ourselves in sudo mode where
# we do a real mount. But we also need to unmount in unshare mode # we do a real mount. But we also need to unmount in unshare mode
@ -1781,24 +1831,25 @@ sub run_setup() {
# there is no /proc in fakechroot mode # there is no /proc in fakechroot mode
next; next;
} }
symlink $linkname, "$options->{root}/$fname" symlink $linkname, "$options->{root}/dev/$fname"
or error "cannot create symlink $fname"; or error "cannot create symlink ./dev/$fname";
next; # chmod cannot work on symlinks next; # chmod cannot work on symlinks
} elsif ($type == 3) { # character special } elsif ($type == 3) { # character special
0 == system('mknod', "$options->{root}/$fname", 'c', 0 == system('mknod', "$options->{root}/dev/$fname", 'c',
$devmajor, $devminor) $devmajor, $devminor)
or error "mknod failed: $?"; or error "mknod failed: $?";
} elsif ($type == 4) { # block special } elsif ($type == 4) { # block special
0 == system('mknod', "$options->{root}/$fname", 'b', 0 == system('mknod', "$options->{root}/dev/$fname", 'b',
$devmajor, $devminor) $devmajor, $devminor)
or error "mknod failed: $?"; or error "mknod failed: $?";
} elsif ($type == 5) { # directory } elsif ($type == 5) { # directory
if (-e "$options->{root}/$fname") { if (-e "$options->{root}/dev/$fname") {
if (!-d "$options->{root}/$fname") { if (!-d "$options->{root}/dev/$fname") {
error "$fname already exists but is not a directory"; error
"./dev/$fname already exists but is not a directory";
} }
} else { } else {
my $num_created = make_path "$options->{root}/$fname", my $num_created = make_path "$options->{root}/dev/$fname",
{ error => \my $err }; { error => \my $err };
if ($err && @$err) { if ($err && @$err) {
error( error(
@ -1808,14 +1859,14 @@ sub run_setup() {
@$err @$err
)); ));
} elsif ($num_created == 0) { } elsif ($num_created == 0) {
error "cannot create $options->{root}/$fname"; error "cannot create $options->{root}/dev/$fname";
} }
} }
} else { } else {
error "unsupported type: $type"; error "unsupported type: $type";
} }
chmod $mode, "$options->{root}/$fname" chmod $mode, "$options->{root}/dev/$fname"
or error "cannot chmod $fname: $!"; or error "cannot chmod ./dev/$fname: $!";
} }
} }
@ -5314,9 +5365,12 @@ sub main() {
foreach my $file (@devfiles) { foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor) my ($fname, $mode, $type, $linkname, $devmajor, $devminor)
= @{$file}; = @{$file};
if (length "./dev/$fname" > 100) {
error "tar entry cannot exceed 100 characters";
}
my $entry = pack( my $entry = pack(
'a100 a8 a8 a8 a12 a12 A8 a1 a100 a8 a32 a32 a8 a8 a155 x12', 'a100 a8 a8 a8 a12 a12 A8 a1 a100 a8 a32 a32 a8 a8 a155 x12',
$fname, "./dev/$fname",
sprintf('%07o', $mode), sprintf('%07o', $mode),
sprintf('%07o', 0), # uid sprintf('%07o', 0), # uid
sprintf('%07o', 0), # gid sprintf('%07o', 0), # gid