150 lines
4.4 KiB
Perl
Executable File
150 lines
4.4 KiB
Perl
Executable File
#! /usr/bin/env perl
|
|
|
|
use warnings;
|
|
use strict;
|
|
use File::Temp qw/tempdir/;
|
|
use Getopt::Long;
|
|
|
|
umask 022;
|
|
|
|
if (not defined $ENV{L4RE_SANDBOX_REEXEC})
|
|
{
|
|
print "$0: Entering sandbox\n";
|
|
$ENV{L4RE_SANDBOX_REEXEC} = "y";
|
|
system("unshare", "-Urm", $0, @ARGV);
|
|
exit $? >> 8;
|
|
}
|
|
|
|
sub usage
|
|
{
|
|
print <<EOF;
|
|
Usage: $0 args
|
|
|
|
--sys-dir Path to system install (like generated with debootstrap, chroot)
|
|
--dir-ro Additional path to mount read-only.
|
|
--dir-rw Additional path to mount read-write.
|
|
--cmd Command to execute, if not given, execute 'sh'
|
|
--help, -h This help.
|
|
|
|
Two modes are provided:
|
|
--sys-dir is given as '/':
|
|
Only directories specified with --dir-ro will be made read-only. No
|
|
PATH will be reset and all environment variables will be kept.
|
|
|
|
Any other --sys-dir:
|
|
The --sys-dir path will be mounted read-only.
|
|
/proc will be mounted from the host.
|
|
/tmp will be a fresh tmpfs. PATH will be reset and environment variables
|
|
will be cleared.
|
|
|
|
Uses Linux's 'overlay' file-system and namespaces.
|
|
|
|
Example:
|
|
EOF
|
|
print " $0 --sys-dir /tmp/debian \\\n";
|
|
print ' ' x (length($0) + 3), "--dir-ro /path/to/l4re-source \\\n";
|
|
print ' ' x (length($0) + 3), "--dir-rw /path/to/l4re-build/amd64 \\\n";
|
|
print ' ' x (length($0) + 3), "--cmd \"make -C /path/to/l4re-build/amd64\"\n";
|
|
}
|
|
|
|
my @dirs_ro;
|
|
my @dirs_rw;
|
|
my $sys_dir;
|
|
my $mode;
|
|
my $cmd;
|
|
GetOptions("dir-ro=s", \@dirs_ro,
|
|
"dir-rw=s", \@dirs_rw,
|
|
"sys-dir=s", \$sys_dir,
|
|
"cmd=s", \$cmd,
|
|
"help|h", sub { usage(); exit(0); });
|
|
|
|
die "Need --sys-dir" unless defined($sys_dir) && -d $sys_dir;
|
|
|
|
sub sh
|
|
{
|
|
my @cmd = @_;
|
|
print "RUNNING: @cmd\n" if $ENV{V};
|
|
system(@cmd);
|
|
die "Failed to execute '@cmd'" if $? >> 8;
|
|
}
|
|
|
|
sub sh_ignore_exit
|
|
{
|
|
my @cmd = @_;
|
|
print "RUNNING: @cmd\n" if $ENV{V};
|
|
system(@cmd);
|
|
return 0;
|
|
}
|
|
|
|
# Decouple all mounts from the outside world
|
|
# See MS_PRIVATE in `man 2 mount`
|
|
sh("mount --make-rprivate /");
|
|
|
|
if ($sys_dir =~ m,^/+$,)
|
|
{
|
|
sh("mount --bind $_ $_ && mount -o bind,remount,ro $_") foreach (@dirs_ro);
|
|
# for doing everything read-only (not only @dirs_ro), we would need to
|
|
# - make all mounts points read-only (what's a good way of finding them out?)
|
|
# - mount at least /tmp read-write again
|
|
|
|
$cmd = 'sh' unless defined $cmd;
|
|
sh("$cmd");
|
|
|
|
sh("umount $_") foreach (@dirs_ro);
|
|
}
|
|
else
|
|
{
|
|
my $sandbox_dir = tempdir('l4-sandbox-XXXXXXXX', CLEANUP => 0, TMPDIR => 1);
|
|
|
|
sh("mkdir -p $sandbox_dir/upper/dev $sandbox_dir/work $sandbox_dir/system");
|
|
sh("mount -t overlay l4re-build-overlay -o lowerdir=$sys_dir,upperdir=$sandbox_dir/upper,workdir=$sandbox_dir/work $sandbox_dir/system");
|
|
sh("mount -t tmpfs tmpfs $sandbox_dir/system/tmp");
|
|
sh("mount --rbind /dev $sandbox_dir/system/dev"); # On the overlay we get EPERM with reading dev files
|
|
sh("mount --rbind -orw /proc $sandbox_dir/system/proc");
|
|
|
|
foreach my $d (@dirs_ro)
|
|
{
|
|
sh("mkdir -p $sandbox_dir/upper/$d");
|
|
sh("mount --rbind $d $sandbox_dir/system/$d");
|
|
sh("mount -o bind,remount,ro $sandbox_dir/system/$d");
|
|
}
|
|
|
|
foreach my $d (@dirs_rw)
|
|
{
|
|
sh("mkdir -p $sandbox_dir/upper/$d");
|
|
sh("mount --rbind -orw $d $sandbox_dir/system/$d");
|
|
}
|
|
|
|
my $chroot;
|
|
# We need to add potential sbin paths since we only have the original user's
|
|
# PATH environment variable
|
|
for my $dir ((split /:/,$ENV{PATH}), "/sbin", "/usr/sbin")
|
|
{
|
|
my $bin = "$dir/chroot";
|
|
next unless -x $bin;
|
|
$chroot = $bin;
|
|
last;
|
|
}
|
|
|
|
die "Cannot find chroot executable" unless defined $chroot;
|
|
|
|
$cmd = 'sh' unless defined $cmd;
|
|
my $term = $ENV{TERM} || "xterm";
|
|
my $makeflags = $ENV{MAKEFLAGS} || "";
|
|
|
|
# jobserver fifo not reachable in sandbox
|
|
$makeflags =~ s/--jobserver-auth(\s+|=)[^ ]+//;
|
|
|
|
# Assumed path variable inside the sysdir
|
|
# In an ideal world sh would load /etc/profile in the chroot
|
|
my $path = "/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
|
|
|
|
sh("/usr/bin/env -i 'PATH=$path' L4RE_SANDBOX_REEXEC=y BID_SANDBOX_IN_PROGRESS=1 TERM=$term MAKEFLAGS='$makeflags' $chroot $sandbox_dir/system $cmd");
|
|
|
|
sh_ignore_exit("umount $sandbox_dir/system/$_") foreach (@dirs_rw, @dirs_ro);
|
|
sh_ignore_exit("umount $sandbox_dir/system/dev");
|
|
sh_ignore_exit("umount $sandbox_dir/system/tmp");
|
|
sh_ignore_exit("umount -l $sandbox_dir/system/proc");
|
|
sh_ignore_exit("umount $sandbox_dir/system");
|
|
}
|