try unsharing before automatically choosing unshare mode

This commit is contained in:
Johannes Schauer Marin Rodrigues 2023-03-15 17:08:12 +01:00
parent 2614924925
commit 055e1719b9
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
3 changed files with 83 additions and 59 deletions

View file

@ -305,14 +305,23 @@ sub shellescape {
sub test_unshare_userns { sub test_unshare_userns {
my $verbose = shift; my $verbose = shift;
my $unshare_fail = shift; my $fail = shift;
if ($EFFECTIVE_USER_ID == 0) {
my $msg = "cannot unshare user namespace when executing as root"; local *maybe_warn = sub {
my $msg = shift;
if ($verbose) { if ($verbose) {
if ($fail) {
error $msg;
} else {
warning $msg; warning $msg;
}
} else { } else {
debug $msg; debug $msg;
} }
};
if ($EFFECTIVE_USER_ID == 0) {
maybe_warn("cannot unshare user namespace when executing as root");
return 0; return 0;
} }
# arguments to syscalls have to be stored in their own variable or # arguments to syscalls have to be stored in their own variable or
@ -326,12 +335,7 @@ sub test_unshare_userns {
if ($ret == 0) { if ($ret == 0) {
exit 0; exit 0;
} else { } else {
my $msg = "unshare syscall failed: $!"; maybe_warn("unshare syscall failed: $!");
if ($verbose) {
warning $msg;
} else {
debug $msg;
}
exit 1; exit 1;
} }
} }
@ -344,120 +348,140 @@ sub test_unshare_userns {
system "newuidmap 2>/dev/null"; system "newuidmap 2>/dev/null";
if (($? >> 8) != 1) { if (($? >> 8) != 1) {
if (($? >> 8) == 127) { if (($? >> 8) == 127) {
my $msg = "cannot find newuidmap"; maybe_warn("cannot find newuidmap");
if ($verbose) {
if ($unshare_fail) {
error $msg;
} else { } else {
warning $msg; maybe_warn("newuidmap returned unknown exit status: $?");
}
} else {
debug $msg;
}
} else {
my $msg = "newuidmap returned unknown exit status: $?";
if ($verbose) {
warning $msg;
} else {
debug $msg;
}
} }
return 0; return 0;
} }
system "newgidmap 2>/dev/null"; system "newgidmap 2>/dev/null";
if (($? >> 8) != 1) { if (($? >> 8) != 1) {
if (($? >> 8) == 127) { if (($? >> 8) == 127) {
my $msg = "cannot find newgidmap"; maybe_warn("cannot find newgidmap");
if ($verbose) {
warning $msg;
} else { } else {
debug $msg; maybe_warn("newgidmap returned unknown exit status: $?");
} }
return 0;
}
my @idmap = read_subuid_subgid($verbose);
if (scalar @idmap == 0) {
maybe_warn("failed to parse /etc/subuid and /etc/subgid");
return 0;
}
# too much can go wrong when doing the dance required to unsharing the user
# namespace, so instead of adding more complexity to support maybe_warn()
# to a function that is already too complex, we use eval()
eval {
$pid = get_unshare_cmd(
sub {
if ($EFFECTIVE_USER_ID == 0) {
exit 0;
} else { } else {
my $msg = "newgidmap returned unknown exit status: $?"; exit 1;
if ($verbose) {
warning $msg;
} else {
debug $msg;
} }
},
\@idmap
);
waitpid $pid, 0;
if ($? != 0) {
maybe_warn("failed to unshare the user namespace");
return 0;
} }
};
if ($@) {
maybe_warn($@);
return 0; return 0;
} }
return 1; return 1;
} }
sub read_subuid_subgid() { sub read_subuid_subgid {
my $verbose = shift;
my @result = ();
my $username = getpwuid $REAL_USER_ID; my $username = getpwuid $REAL_USER_ID;
my ($subid, $num_subid, $fh, $n); my ($subid, $num_subid, $fh, $n);
my @result = ();
local *maybe_warn = sub {
my $msg = shift;
if ($verbose) {
warning $msg;
} else {
debug $msg;
}
};
if (!-e "/etc/subuid") { if (!-e "/etc/subuid") {
warning "/etc/subuid doesn't exist"; maybe_warn("/etc/subuid doesn't exist");
return; return;
} }
if (!-r "/etc/subuid") { if (!-r "/etc/subuid") {
warning "/etc/subuid is not readable"; maybe_warn("/etc/subuid is not readable");
return; return;
} }
open $fh, "<", "/etc/subuid" open $fh, "<", "/etc/subuid"
or error "cannot open /etc/subuid for reading: $!"; or maybe_warn("cannot open /etc/subuid for reading: $!");
if (!$fh) {
return;
}
while (my $line = <$fh>) { while (my $line = <$fh>) {
($n, $subid, $num_subid) = split(/:/, $line, 3); ($n, $subid, $num_subid) = split(/:/, $line, 3);
last if ($n eq $username); last if ($n eq $username);
} }
close $fh; close $fh;
if (!length $subid) { if (!length $subid) {
warning "/etc/subuid is empty"; maybe_warn("/etc/subuid is empty");
return; return;
} }
if ($n ne $username) { if ($n ne $username) {
warning "no entry in /etc/subuid for $username"; maybe_warn("no entry in /etc/subuid for $username");
return; return;
} }
push @result, ["u", 0, $subid, $num_subid]; push @result, ["u", 0, $subid, $num_subid];
if (scalar(@result) < 1) { if (scalar(@result) < 1) {
warning "/etc/subuid does not contain an entry for $username"; maybe_warn("/etc/subuid does not contain an entry for $username");
return; return;
} }
if (scalar(@result) > 1) { if (scalar(@result) > 1) {
warning "/etc/subuid contains multiple entries for $username"; maybe_warn("/etc/subuid contains multiple entries for $username");
return; return;
} }
if (!-e "/etc/subgid") { if (!-e "/etc/subgid") {
warning "/etc/subgid doesn't exist"; maybe_warn("/etc/subgid doesn't exist");
return; return;
} }
if (!-r "/etc/subgid") { if (!-r "/etc/subgid") {
warning "/etc/subgid is not readable"; maybe_warn("/etc/subgid is not readable");
return; return;
} }
open $fh, "<", "/etc/subgid" open $fh, "<", "/etc/subgid"
or error "cannot open /etc/subgid for reading: $!"; or maybe_warn("cannot open /etc/subgid for reading: $!");
if (!$fh) {
return;
}
while (my $line = <$fh>) { while (my $line = <$fh>) {
($n, $subid, $num_subid) = split(/:/, $line, 3); ($n, $subid, $num_subid) = split(/:/, $line, 3);
last if ($n eq $username); last if ($n eq $username);
} }
close $fh; close $fh;
if (!length $subid) { if (!length $subid) {
warning "/etc/subgid is empty"; maybe_warn("/etc/subgid is empty");
return; return;
} }
if ($n ne $username) { if ($n ne $username) {
warning "no entry in /etc/subgid for $username"; maybe_warn("no entry in /etc/subgid for $username");
return; return;
} }
push @result, ["g", 0, $subid, $num_subid]; push @result, ["g", 0, $subid, $num_subid];
if (scalar(@result) < 2) { if (scalar(@result) < 2) {
warning "/etc/subgid does not contain an entry for $username"; maybe_warn("/etc/subgid does not contain an entry for $username");
return; return;
} }
if (scalar(@result) > 2) { if (scalar(@result) > 2) {
warning "/etc/subgid contains multiple entries for $username"; maybe_warn("/etc/subgid contains multiple entries for $username");
return; return;
} }
@ -4353,7 +4377,7 @@ sub main() {
} }
my @idmap = (); my @idmap = ();
if ($EFFECTIVE_USER_ID != 0) { if ($EFFECTIVE_USER_ID != 0) {
@idmap = read_subuid_subgid; @idmap = read_subuid_subgid 1;
} }
my $pid = get_unshare_cmd( my $pid = get_unshare_cmd(
sub { sub {
@ -5665,7 +5689,7 @@ sub main() {
# for unshare mode the rootfs directory has to have appropriate # for unshare mode the rootfs directory has to have appropriate
# permissions # permissions
if ($EFFECTIVE_USER_ID != 0 and $options->{mode} eq 'unshare') { if ($EFFECTIVE_USER_ID != 0 and $options->{mode} eq 'unshare') {
@idmap = read_subuid_subgid; @idmap = read_subuid_subgid 1;
# sanity check # sanity check
if ( scalar(@idmap) != 2 if ( scalar(@idmap) != 2
|| $idmap[0][0] ne 'u' || $idmap[0][0] ne 'u'

View file

@ -13,4 +13,4 @@ if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2 echo expected failure but got exit $ret >&2
exit 1 exit 1
fi fi
rm -r /tmp/debian-chroot [ ! -e /tmp/debian-chroot ]

View file

@ -14,4 +14,4 @@ if [ "$ret" = 0 ]; then
echo expected failure but got exit $ret >&2 echo expected failure but got exit $ret >&2
exit 1 exit 1
fi fi
rm -r /tmp/debian-chroot [ ! -e /tmp/debian-chroot ]