From a38af218d793e0e7b0ebcd73dc96807a653f834e Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 4 May 2017 13:51:34 -0700 Subject: [PATCH] Update API and tests. --- Changes | 8 ++- lib/Sys/Linux/Mount.pm | 29 ++++++---- lib/Sys/Linux/Mount.xs | 5 ++ lib/Sys/Linux/Namespace.pm | 105 +++++++++++++++++++++---------------- t/01-use-ok.t | 3 +- t/02-namespace.t | 20 +++++-- t/tmp/keep | 0 7 files changed, 109 insertions(+), 61 deletions(-) create mode 100644 t/tmp/keep diff --git a/Changes b/Changes index bc478ea..f0b377e 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,12 @@ Revision history for Sys::Linux::Namespaces -0.003 - TBD +0.010 - May 4 2017 + * More stable API + * Added umount support to Sys::Linux::Mount + * Setup a private /proc by default when using private_pid + * Better test handling of forked processes + +0.003 - May 4 2017 * Fix language in docs 0.002 - May 3 2017 diff --git a/lib/Sys/Linux/Mount.pm b/lib/Sys/Linux/Mount.pm index 9b2cae7..3ee12a0 100644 --- a/lib/Sys/Linux/Mount.pm +++ b/lib/Sys/Linux/Mount.pm @@ -2,6 +2,8 @@ package Sys::Linux::Mount; use strict; use warnings; +use Carp qw/carp/; + require Exporter; our @ISA = qw/Exporter/; require XSLoader; @@ -10,25 +12,34 @@ XSLoader::load(); my @mount_consts = qw/MS_RDONLY MS_NOSUID MS_NODEV MS_NOEXEC MS_SYNCHRONOUS MS_REMOUNT MS_MANDLOCK MS_DIRSYNC MS_NOATIME MS_NODIRATIME MS_BIND MS_MOVE MS_REC MS_SILENT MS_POSIXACL MS_UNBINDABLE MS_PRIVATE MS_SLAVE MS_SHARED MS_RELATIME MS_KERNMOUNT MS_I_VERSION MS_STRICTATIME MS_LAZYTIME MS_ACTIVE MS_NOUSER/; -our @EXPORT_OK = (@mount_consts, qw/mount/); +our @EXPORT_OK = (@mount_consts, qw/mount umount/); our %EXPORT_TAGS = ( 'consts' => \@mount_consts, - 'all' => [@mount_consts, qw/mount/], + 'all' => [@mount_consts, qw/mount umount/], ); sub mount { - my ($source, $target, $filesystem, $flags, $options_hr) = @_; + my ($source, $target, $filesystem, $flags, $options_hr) = @_; - my $options_str = join ',', map {"$_=".$options_hr->{$_}} keys %$options_hr; + my $options_str = join ',', map {"$_=".$options_hr->{$_}} keys %$options_hr; - my $ret = _mount_sys($source//"", $target//"", $filesystem//"", $flags//0, $options_str//""); + my $ret = _mount_sys($source//"", $target//"", $filesystem//"", $flags//0, $options_str//""); - if ($ret != 0) { - die "mount failed: $ret $!"; - } + if ($ret != 0) { + carp "mount failed: $ret $!"; + } - return; + return 1; +} + +sub umount { + my ($target) = @_; + + carp "No argument given to umount()" unless $target; + + my $ret = _umount_sys($target); + return 1; } use constant {MS_RDONLY => 1, diff --git a/lib/Sys/Linux/Mount.xs b/lib/Sys/Linux/Mount.xs index 6358fb7..415b86f 100644 --- a/lib/Sys/Linux/Mount.xs +++ b/lib/Sys/Linux/Mount.xs @@ -16,3 +16,8 @@ SV *_mount_sys(const char *source, const char *target, const char *filesystem, u CODE: ST(0) = sv_newmortal(); sv_setiv(ST(0), mount(source, target, filesystem, mountflags, (const void *) data)); + +SV *_umount_sys(const char *target) + CODE: + ST(0) = sv_newmortal(); + sv_setiv(ST(0), umount(target)); diff --git a/lib/Sys/Linux/Namespace.pm b/lib/Sys/Linux/Namespace.pm index 17bfe42..d304483 100644 --- a/lib/Sys/Linux/Namespace.pm +++ b/lib/Sys/Linux/Namespace.pm @@ -11,13 +11,13 @@ use POSIX qw/_exit/; use Moo; use Carp qw/carp/; +has no_proc => (is => 'rw'); + for my $p (qw/tmp mount pid net ipc user uts sysvsem/) { my $pp = "private_$p"; has $pp => (is => 'rw'); } -has code => (is => 'rw'); # code to run in the namespace - sub _uflags { my $self = shift; my $uflags = 0; @@ -53,10 +53,7 @@ sub pre_setup { my ($self, %args) = @_; die "Private net is not yet supported" if $self->private_net; - if ($self->private_pid && - ((ref $self->code ne 'CODE') || - (ref $args{code} ne 'CODE'))) { - + if ($self->private_pid && (ref $args{code} ne 'CODE' || !$args{_run})) { die "Private PID space requires a coderef to become the new PID 1"; } } @@ -69,15 +66,18 @@ sub post_setup { } if ($self->private_tmp) { - if (ref $self->private_tmp eq 'HASH') { - mount("/tmp", "/tmp", "tmpfs", 0, undef); - mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, $self->private_tmp); - } elsif (ref $self->private_tmp) { # TODO do this with a constraint? - die "Bad ref type passed as private_tmp"; - } else { - mount("/tmp", "/tmp", "tmpfs", 0, undef); - mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, undef); - } + my $data = undef; + $data = $self->private_tmp if (ref $self->private_tmp eq 'HASH'); + + eval {umount("/tmp")}; # ignore it if it wasn't mounted + mount("/tmp", "/tmp", "tmpfs", 0, undef); + mount("/tmp", "/tmp", "tmpfs", MS_PRIVATE, $data); + } + + if ($self->private_pid && !$self->no_proc) { + eval {umount("/proc")}; # ignore it if it wasn't mounted already + mount("/proc", "/proc", "proc", 0, undef); + mount("/proc", "/proc", "proc", MS_PRIVATE, undef); } } @@ -85,30 +85,36 @@ sub setup { my ($self, %args) = @_; my $uflags = $self->_uflags; - $self->pre_setup(%args); - my $code = $args{code} // $self->code(); + unshare($uflags); + $self->post_setup(%args); - if ($code) { - $self->_subprocess(sub { - unshare($uflags); + return 1; +} - # We've just unshared, if we wanted a private pid space we MUST fork again. - if ($self->private_pid) { - $self->_subprocess(sub { - $self->post_setup(%args); - $code->(%args); - }, %args); - } else { - $self->post_setup(%args); +sub run { + my ($self, %args) = @_; + + my $code = $args{code}; + $args{_run} = 1; + + carp "Run must be given a codref to run" unless ref $code eq "CODE"; + my $uflags = $self->_uflags; + $self->pre_setup(%args); + + $self->_subprocess(sub { + $self->setup(%args); + + # We've just unshared, if we wanted a private pid space we MUST fork again. + if ($self->private_pid) { + $self->_subprocess(sub { $code->(%args); - } - }, %args); - } else { - unshare($uflags); - $self->post_setup(%args); - } + }, %args); + } else { + $code->(%args); + } + }, %args); return 1; } @@ -127,7 +133,7 @@ Sys::Linux::Namespace - A Module for setting up linux namespaces # Create a namespace with a private /tmp my $ns1 = Sys::Linux::Namespace->new(private_tmp => 1); - $ns1->setup(code => sub { + $ns1->run(code => sub { # This code has it's own completely private /tmp filesystem open(my $fh, "new(private_tmp => 1, private_pid => 1); - $ns2->setup(code => sub { + $ns2->run(code => sub { # I will only see PID 1. I can fork anything I want and they will only see me # if I die they die too. use Data::Dumper; @@ -146,7 +152,7 @@ Sys::Linux::Namespace - A Module for setting up linux namespaces # We're back to our previous global /tmp and PID namespace # all processes and private filesystems have been removed - # Now let's set up a private /tmp + # Now let's set up a private /tmp for the rest of the process $ns1->setup(); # We're now permanently (for this process) using a private /tmp. @@ -164,11 +170,6 @@ All arguments are passed in like a hash. =over 1 -=item code - -A coderef to run when setting up the namespaces. This gets run in a child process that's isolated from the parent. -If you don't pass one in during construction or to C then the namespace changes will happen to the current process. - =item private_mount Setup a private mount namespace, this makes every currently mounted filesystem private to our process. @@ -181,7 +182,13 @@ Takes either a true value, or a hashref with options to pass to the mount syscal =item private_pid -Create a private PID namespace. This requires a C parameter either to C or to C +Create a private PID namespace. This requires the use of C<< ->run() >>. +This requires a C parameter either to C or to C +Also sets up a private /proc mount by default + +=item no_proc + +Don't setup a private /proc mount when doing private_pid =item private_net @@ -209,11 +216,17 @@ Create a new System V Semaphore namespace. This will let you create new semapho Engage the namespaces with all the configured options. -All arguments are passed by name like a hash. +=head2 C -You may pass in a C parameter to run in a child process, this overrides one provided during construction. +Engage the namespaces on an unsuspecting coderef. Arguments are passed in like a hash -Any other parameters are passed through to your coderef if present. +=over 1 + +=item code + +The coderef to run. It will receive all arguments passed to C<< ->run() >> as a hash. + +=back =head1 AUTHOR diff --git a/t/01-use-ok.t b/t/01-use-ok.t index ebd0571..0db9824 100644 --- a/t/01-use-ok.t +++ b/t/01-use-ok.t @@ -1,6 +1,7 @@ -use Test::More tests => 3; +use Test::More; use_ok "Sys::Linux::Mount"; use_ok "Sys::Linux::Unshare"; use_ok "Sys::Linux::Namespace"; +done_testing; diff --git a/t/02-namespace.t b/t/02-namespace.t index 2210c31..736b6ed 100644 --- a/t/02-namespace.t +++ b/t/02-namespace.t @@ -1,14 +1,26 @@ -use Test::More tests => 4; +BEGIN { + $ENV{TMPDIR} = 't/tmp/' +} + +use Test::More; +use Test::SharedFork; # test 1 use_ok("Sys::Linux::Namespace"); # test 2 SKIP: { - skip "Need to be root to run test", 3 unless $< == 0; + skip "Need to be root to run test", 5 unless $< == 0; ok(my $namespace = Sys::Linux::Namespace->new(private_tmp => 1), "Setup object"); - ok($namespace->setup(code => sub { + my $ret = $namespace->run(code => sub { is_deeply([glob "/tmp/*"], [], "No files present in /tmp"); - }), "run code in sandbox"); + }); + + ok($ret, "run code in sandbox"); + + ok($namespace->setup(), "Setup namespace in current process"); + is_deeply([glob "/tmp/*"], [], "No files present in /tmp"); } + +done_testing; diff --git a/t/tmp/keep b/t/tmp/keep new file mode 100644 index 0000000..e69de29