allow unshare as root user

- this is useful when you are already root and want the benefits of
   unsharing the mount namespace to prevent messing up your system
 - if the unshare mode is used as root, the user namespace is not unshared
   anymore and newuidmap, setuid and friends are not called anymore
 - if the unshare mode is used as non-root test if the user namespace can be
   unshared, otherwise test if the mount namespace can be unshared
This commit is contained in:
Johannes 'josch' Schauer 2021-01-13 16:05:57 +01:00
parent 0f6741d01a
commit 4693034138
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 48 additions and 26 deletions

View file

@ -498,29 +498,24 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi fi
print_header "mode=unshare,variant=apt: fail with unshare as root user" print_header "mode=unshare,variant=apt: unshare as root user"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then [ "\$(whoami)" = "root" ]
echo "this test modifies the system and should only be run inside a container" >&2 $CMD --mode=unshare --variant=apt \
exit 1 --customize-hook='chroot "\$1" sh -c "test -e /proc/self/fd"' \
fi $DEFAULT_DIST /tmp/debian-chroot.tar $mirror
sysctl -w kernel.unprivileged_userns_clone=1 tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
ret=0 rm /tmp/debian-chroot.tar
$CMD --mode=unshare --variant=apt $DEFAULT_DIST /tmp/debian-chroot $mirror || ret=\$?
if [ "\$ret" = 0 ]; then
echo expected failure but got exit \$ret >&2
exit 1
fi
END END
if [ "$HAVE_QEMU" = "yes" ]; then if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh ./run_qemu.sh
runtests=$((runtests+1)) runtests=$((runtests+1))
else else
echo "HAVE_QEMU != yes -- Skipping test..." >&2 ./run_null.sh SUDO
skipped=$((skipped+1)) runtests=$((runtests+1))
fi fi
for variant in essential apt minbase buildd important standard; do for variant in essential apt minbase buildd important standard; do

View file

@ -223,10 +223,10 @@ sub get_tar_compressor {
return; return;
} }
sub test_unshare { sub test_unshare_userns {
my $verbose = shift; my $verbose = shift;
if ($EFFECTIVE_USER_ID == 0) { if ($EFFECTIVE_USER_ID == 0) {
my $msg = "cannot use unshare mode when executing as root"; my $msg = "cannot unshare user namespace when executing as root";
if ($verbose) { if ($verbose) {
warning $msg; warning $msg;
} else { } else {
@ -401,9 +401,14 @@ sub get_unshare_cmd {
my $cmd = shift; my $cmd = shift;
my $idmap = shift; my $idmap = shift;
# unsharing the mount namespace (NEWNS) requires CAP_SYS_ADMIN
my $unshare_flags my $unshare_flags
= $CLONE_NEWUSER | $CLONE_NEWNS | $CLONE_NEWPID | $CLONE_NEWUTS = $CLONE_NEWNS | $CLONE_NEWPID | $CLONE_NEWUTS | $CLONE_NEWIPC;
| $CLONE_NEWIPC;
# we only need to add CLONE_NEWUSER if we are not yet root
if ($EFFECTIVE_USER_ID != 0) {
$unshare_flags |= $CLONE_NEWUSER;
}
if (0) { if (0) {
$unshare_flags |= $CLONE_NEWNET; $unshare_flags |= $CLONE_NEWNET;
@ -447,6 +452,11 @@ sub get_unshare_cmd {
# waiting for an EOF. # waiting for an EOF.
0 == sysread $rfh, my $c, 1 or error "read() did not receive EOF"; 0 == sysread $rfh, my $c, 1 or error "read() did not receive EOF";
# the process is already root, so no need for newuidmap/newgidmap
if ($EFFECTIVE_USER_ID == 0) {
exit 0;
}
# The program's new[ug]idmap have to be used because they are # The program's new[ug]idmap have to be used because they are
# setuid root. These privileges are needed to map the ids from # setuid root. These privileges are needed to map the ids from
# /etc/sub[ug]id to the user namespace set up by the parent. # /etc/sub[ug]id to the user namespace set up by the parent.
@ -515,9 +525,11 @@ sub get_unshare_cmd {
# want here, like checking /proc/sys/kernel/ngroups_max (which might # want here, like checking /proc/sys/kernel/ngroups_max (which might
# not exist). It would also also call setgroups() in a way that makes # not exist). It would also also call setgroups() in a way that makes
# the root user be part of the group unknown. # the root user be part of the group unknown.
if ($EFFECTIVE_USER_ID != 0) {
0 == syscall &SYS_setgid, 0 or error "setgid failed: $!"; 0 == syscall &SYS_setgid, 0 or error "setgid failed: $!";
0 == syscall &SYS_setuid, 0 or error "setuid failed: $!"; 0 == syscall &SYS_setuid, 0 or error "setuid failed: $!";
0 == syscall &SYS_setgroups, 0, 0 or error "setgroups failed: $!"; 0 == syscall &SYS_setgroups, 0, 0 or error "setgroups failed: $!";
}
if (1) { if (1) {
# When the pid namespace is also unshared, then processes expect a # When the pid namespace is also unshared, then processes expect a
@ -4134,7 +4146,7 @@ sub main() {
# lxc-usernsexec -- lxc-unshare -s 'MOUNT|PID|UTSNAME|IPC' ... # lxc-usernsexec -- lxc-unshare -s 'MOUNT|PID|UTSNAME|IPC' ...
# but without needing lxc # but without needing lxc
if ($ARGV[0] eq "--unshare-helper") { if ($ARGV[0] eq "--unshare-helper") {
if (!test_unshare(1)) { if (!test_unshare_userns(1)) {
exit 1; exit 1;
} }
my @idmap = read_subuid_subgid; my @idmap = read_subuid_subgid;
@ -4408,9 +4420,9 @@ sub main() {
# if mmdebstrap is executed as root, we assume the user wants root # if mmdebstrap is executed as root, we assume the user wants root
# mode # mode
$options->{mode} = 'root'; $options->{mode} = 'root';
} elsif (test_unshare(0)) { } elsif (test_unshare_userns(0)) {
# otherwise, unshare mode is our best option if test_unshare() # if we are not root, unshare mode is our best option if
# succeeds # test_unshare_userns() succeeds
$options->{mode} = 'unshare'; $options->{mode} = 'unshare';
} elsif (system('fakechroot --version>/dev/null') == 0) { } elsif (system('fakechroot --version>/dev/null') == 0) {
# the next fallback is fakechroot # the next fallback is fakechroot
@ -4449,7 +4461,16 @@ sub main() {
exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG; exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG;
} }
} elsif ($options->{mode} eq 'unshare') { } elsif ($options->{mode} eq 'unshare') {
if (!test_unshare(1)) { # For unshare mode to work we either need to already be the root user
# and then we do not have to unshare the user namespace anymore but we
# need to be able to unshare the mount namespace...
if ($EFFECTIVE_USER_ID == 0
&& 0 != system 'unshare --mount true 2>/dev/null') {
error "unable to unshare the mount namespace";
}
# ...or we are not root and then we need to be able to unshare the user
# namespace.
if ($EFFECTIVE_USER_ID != 0 && !test_unshare_userns(1)) {
my $procfile = '/proc/sys/kernel/unprivileged_userns_clone'; my $procfile = '/proc/sys/kernel/unprivileged_userns_clone';
open(my $fh, '<', $procfile) open(my $fh, '<', $procfile)
or error "failed to open $procfile: $!"; or error "failed to open $procfile: $!";
@ -6132,6 +6153,12 @@ available and you know your subuid/subgid offset (100000 in this example):
$ sudo systemd-nspawn --private-users=100000 \ $ sudo systemd-nspawn --private-users=100000 \
> --directory=./debian-rootfs /bin/bash > --directory=./debian-rootfs /bin/bash
If this mode is used as the root user, the user namespace is not unshared (but
the mount namespace and other still are) and created directories will have
correct ownership information. This is also useful in cases where the root user
wants the benefits of an unshared mount namespace to prevent accidentally
messing up the system.
=item B<fakeroot>, B<fakechroot> =item B<fakeroot>, B<fakechroot>
This mode will exec B<mmdebstrap> again under C<fakechroot fakeroot>. A This mode will exec B<mmdebstrap> again under C<fakechroot fakeroot>. A