Compare commits

..

3 commits

Author SHA1 Message Date
ff9b76ed19
improve debug and error message wording 2023-03-16 22:18:49 +01:00
Jakub Wilk
a719ffd20a
Fix apt.conf permissions
Having world-writable apt.conf may be exploited by locals users to
execute arbitrary code in the context of the user running mmdebstrap.
2023-03-16 21:56:16 +01:00
55cae49ec7
let test_unshare_userns error out itself if necessary 2023-03-16 08:14:39 +01:00

View file

@ -305,23 +305,18 @@ sub shellescape {
sub test_unshare_userns { sub test_unshare_userns {
my $verbose = shift; my $verbose = shift;
my $fail = shift;
local *maybe_warn = sub { local *maybe_error = sub {
my $msg = shift; my $msg = shift;
if ($verbose) { if ($verbose) {
if ($fail) { error $msg;
error $msg;
} else {
warning $msg;
}
} else { } else {
debug $msg; debug $msg;
} }
}; };
if ($EFFECTIVE_USER_ID == 0) { if ($EFFECTIVE_USER_ID == 0) {
maybe_warn("cannot unshare user namespace when executing as root"); maybe_error("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
@ -335,7 +330,7 @@ sub test_unshare_userns {
if ($ret == 0) { if ($ret == 0) {
exit 0; exit 0;
} else { } else {
maybe_warn("unshare syscall failed: $!"); maybe_error("unshare syscall failed: $!");
exit 1; exit 1;
} }
} }
@ -348,28 +343,28 @@ 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) {
maybe_warn("cannot find newuidmap"); maybe_error("cannot find newuidmap");
} else { } else {
maybe_warn("newuidmap returned unknown exit status: $?"); maybe_error("newuidmap returned unknown exit status: $?");
} }
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) {
maybe_warn("cannot find newgidmap"); maybe_error("cannot find newgidmap");
} else { } else {
maybe_warn("newgidmap returned unknown exit status: $?"); maybe_error("newgidmap returned unknown exit status: $?");
} }
return 0; return 0;
} }
my @idmap = read_subuid_subgid($verbose); my @idmap = read_subuid_subgid($verbose);
if (scalar @idmap == 0) { if (scalar @idmap == 0) {
maybe_warn("failed to parse /etc/subuid and /etc/subgid"); maybe_error("failed to parse /etc/subuid and /etc/subgid");
return 0; return 0;
} }
# too much can go wrong when doing the dance required to unsharing the user # 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() # namespace, so instead of adding more complexity to support maybe_error()
# to a function that is already too complex, we use eval() # to a function that is already too complex, we use eval()
eval { eval {
$pid = get_unshare_cmd( $pid = get_unshare_cmd(
@ -384,12 +379,12 @@ sub test_unshare_userns {
); );
waitpid $pid, 0; waitpid $pid, 0;
if ($? != 0) { if ($? != 0) {
maybe_warn("failed to unshare the user namespace"); maybe_error("failed to unshare the user namespace");
return 0; return 0;
} }
}; };
if ($@) { if ($@) {
maybe_warn($@); maybe_error($@);
return 0; return 0;
} }
return 1; return 1;
@ -2270,7 +2265,7 @@ sub run_setup() {
# we have to make the config file world readable so that a possible # we have to make the config file world readable so that a possible
# /usr/lib/apt/solvers/apt process which is run by the _apt user is also # /usr/lib/apt/solvers/apt process which is run by the _apt user is also
# able to read it # able to read it
chmod 0666, "$tmpfile" or error "cannot chmod $tmpfile: $!"; chmod 0644, "$tmpfile" or error "cannot chmod $tmpfile: $!";
if ($verbosity_level >= 3) { if ($verbosity_level >= 3) {
0 == system('apt-get', '--version') 0 == system('apt-get', '--version')
or error "apt-get --version failed: $?"; or error "apt-get --version failed: $?";
@ -4372,8 +4367,8 @@ 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 (scalar @ARGV >= 1 && $ARGV[0] eq "--unshare-helper") { if (scalar @ARGV >= 1 && $ARGV[0] eq "--unshare-helper") {
if ($EFFECTIVE_USER_ID != 0 && !test_unshare_userns(1)) { if ($EFFECTIVE_USER_ID != 0) {
exit 1; test_unshare_userns(1);
} }
my @idmap = (); my @idmap = ();
if ($EFFECTIVE_USER_ID != 0) { if ($EFFECTIVE_USER_ID != 0) {
@ -4805,7 +4800,8 @@ sub main() {
} }
exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG; exec 'fakechroot', 'fakeroot', @prefix, $PROGRAM_NAME, @ARGVORIG;
} else { } else {
error "unable to pick chroot mode automatically"; error( "unable to pick chroot mode automatically (use --mode for "
. "manual selection)");
} }
info "automatically chosen mode: $options->{mode}"; info "automatically chosen mode: $options->{mode}";
} elsif ($options->{mode} eq 'root') { } elsif ($options->{mode} eq 'root') {
@ -4840,26 +4836,8 @@ sub main() {
} }
# ...or we are not root and then we need to be able to unshare the user # ...or we are not root and then we need to be able to unshare the user
# namespace. # namespace.
if ($EFFECTIVE_USER_ID != 0 && !test_unshare_userns(1, 1)) { if ($EFFECTIVE_USER_ID != 0) {
my $procfile = '/proc/sys/kernel/unprivileged_userns_clone'; test_unshare_userns(1);
open(my $fh, '<', $procfile)
or error "failed to open $procfile: $!";
chomp(
my $content = do { local $/; <$fh> }
);
close($fh);
if ($content ne "1") {
info "/proc/sys/kernel/unprivileged_userns_clone is set to"
. " $content";
info "Try running:";
info " sudo sysctl -w kernel.unprivileged_userns_clone=1";
info "or permanently enable unprivileged usernamespaces by"
. " putting the setting into /etc/sysctl.d/";
info "THIS SETTING HAS SECURITY IMPLICATIONS!";
info "Refer to https://bugs.debian.org/cgi-bin/"
. "bugreport.cgi?bug=898446";
}
exit 1;
} }
} elsif ($options->{mode} eq 'chrootless') { } elsif ($options->{mode} eq 'chrootless') {
if ($EFFECTIVE_USER_ID == 0) { if ($EFFECTIVE_USER_ID == 0) {
@ -5195,12 +5173,14 @@ sub main() {
my $keyring my $keyring
= get_keyring_by_suite($options->{suite}, \%suite_by_vendor); = get_keyring_by_suite($options->{suite}, \%suite_by_vendor);
if (!defined $keyring) { if (!defined $keyring) {
debug "get_keyring_by_suite() cannot find keyring";
return ''; return '';
} }
# we can only check if we need the signed-by entry if we u # we can only check if we need the signed-by entry if we u
# automatically chosen keyring exists # automatically chosen keyring exists
if (!defined $keyring || !-e $keyring) { if (!defined $keyring || !-e $keyring) {
debug "found keyring does not exist";
return ''; return '';
} }
@ -5245,7 +5225,7 @@ sub main() {
# find all the fingerprints of the keys apt currently # find all the fingerprints of the keys apt currently
# knows about # knows about
my @keyrings = (); my @keyrings = ();
opendir my $dh, "$options->{apttrustedparts}" opendir my $dh, $options->{apttrustedparts}
or error "cannot read $options->{apttrustedparts}"; or error "cannot read $options->{apttrustedparts}";
while (my $filename = readdir $dh) { while (my $filename = readdir $dh) {
if ($filename !~ /\.(asc|gpg)$/) { if ($filename !~ /\.(asc|gpg)$/) {
@ -5254,7 +5234,7 @@ sub main() {
$filename = "$options->{apttrustedparts}/$filename"; $filename = "$options->{apttrustedparts}/$filename";
# skip empty keyrings # skip empty keyrings
-s "$filename" || next; -s "$filename" || next;
push @keyrings, "$filename"; push @keyrings, $filename;
} }
closedir $dh; closedir $dh;
if (-s $options->{apttrusted}) { if (-s $options->{apttrusted}) {
@ -5262,6 +5242,7 @@ sub main() {
} }
my @aptfingerprints = (); my @aptfingerprints = ();
if (scalar @keyrings == 0) { if (scalar @keyrings == 0) {
debug "no keyring is trusted by apt";
return " [signed-by=\"$keyring\"]"; return " [signed-by=\"$keyring\"]";
} }
info "finding correct signed-by value..."; info "finding correct signed-by value...";
@ -5285,6 +5266,7 @@ sub main() {
} }
print_progress("done"); print_progress("done");
if (scalar @aptfingerprints == 0) { if (scalar @aptfingerprints == 0) {
debug "no fingerprints found";
return " [signed-by=\"$keyring\"]"; return " [signed-by=\"$keyring\"]";
} }
# check if all fingerprints from the keyring that we guessed # check if all fingerprints from the keyring that we guessed
@ -5301,6 +5283,7 @@ sub main() {
# if this fingerprint is not known by apt, then we need # if this fingerprint is not known by apt, then we need
#to add the signed-by option #to add the signed-by option
if (none { $_ eq $1 } @aptfingerprints) { if (none { $_ eq $1 } @aptfingerprints) {
debug "fingerprint $1 is not trusted by apt";
return " [signed-by=\"$keyring\"]"; return " [signed-by=\"$keyring\"]";
} }
} }
@ -6692,9 +6675,9 @@ decides which way this is achieved.
This mode automatically selects a fitting mode. If the effective user id is the This mode automatically selects a fitting mode. If the effective user id is the
one of the superuser, then the B<sudo> mode is chosen. Otherwise, the one of the superuser, then the B<sudo> mode is chosen. Otherwise, the
B<unshare> mode is picked if the system has the sysctl B<unshare> mode is picked if F</etc/subuid> and F</etc/subgid> are set up
C<kernel.unprivileged_userns_clone> set to C<1>. Should that not be the case correctly. Should that not be the case and if the fakechroot binary exists, the
and if the fakechroot binary exists, the B<fakechroot> mode is chosen. B<fakechroot> mode is chosen.
=item B<sudo>, B<root> =item B<sudo>, B<root>