From 58f401e837ecd3fd415443a39ba2511bc8877abb Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Sun, 7 May 2017 13:22:46 -0700 Subject: [PATCH] Remove unneeded internal module now that we use Linux::Clone. --- Changes | 6 +++ lib/Sys/Linux/Namespace.pm | 79 +++++++++++++++++++++++--------------- lib/Sys/Linux/Unshare.pm | 74 ----------------------------------- lib/Sys/Linux/Unshare.xs | 26 ------------- t/02-namespace.t | 27 ++++++++++++- 5 files changed, 80 insertions(+), 132 deletions(-) delete mode 100644 lib/Sys/Linux/Unshare.pm delete mode 100644 lib/Sys/Linux/Unshare.xs diff --git a/Changes b/Changes index fe632c6..5422159 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for Sys::Linux::Namespaces +0.013 - May 7 2017 + * Force target mount option to be an absolute path + * Do kill child subprocess on any signal by default. This helps prevent zombie PID 1 processes. + * Switch to an existing implementation of the unshare and clone syscall wrappers. + * Use the clone syscall instead of unshare+fork when we're running a coderef. This creates fewer processes, and has better handling of CLONE_NEWPID. + 0.012 - May 4 2017 * Fix borken mount options diff --git a/lib/Sys/Linux/Namespace.pm b/lib/Sys/Linux/Namespace.pm index e664f4f..969976e 100644 --- a/lib/Sys/Linux/Namespace.pm +++ b/lib/Sys/Linux/Namespace.pm @@ -5,13 +5,24 @@ use strict; use warnings; use Sys::Linux::Mount qw/:all/; -use Sys::Linux::Unshare qw/:all/; +#use Sys::Linux::Unshare qw/:all/; +use Linux::Clone; use POSIX qw/_exit/; +use Time::HiRes qw/sleep/; use Moo; use Carp qw/croak/; has no_proc => (is => 'rw'); +has term_child => (is => 'rw', default => 1); + +our $debug = 0; +sub debug { + print STDERR @_ if $debug; +} + +our $VERSION = v0.013; +my @signames = keys %SIG; # capture before anyone has probably localized it. for my $p (qw/tmp mount pid net ipc user uts sysvsem/) { my $pp = "private_$p"; @@ -22,13 +33,13 @@ sub _uflags { my $self = shift; my $uflags = 0; - $uflags |= CLONE_NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc)); - $uflags |= CLONE_NEWPID if ($self->private_pid); - $uflags |= CLONE_NEWNET if ($self->private_net); - $uflags |= CLONE_NEWIPC if ($self->private_ipc); - $uflags |= CLONE_NEWUSER if ($self->private_user); - $uflags |= CLONE_NEWUTS if ($self->private_uts); - $uflags |= CLONE_SYSVSEM if ($self->private_sysvsem); + $uflags |= Linux::Clone::NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc)); + $uflags |= Linux::Clone::NEWPID if ($self->private_pid); + $uflags |= Linux::Clone::NEWNET if ($self->private_net); + $uflags |= Linux::Clone::NEWIPC if ($self->private_ipc); + $uflags |= Linux::Clone::NEWUSER if ($self->private_user); + $uflags |= Linux::Clone::NEWUTS if ($self->private_uts); + $uflags |= Linux::Clone::SYSVSEM if ($self->private_sysvsem); return $uflags; } @@ -37,16 +48,29 @@ sub _subprocess { my ($self, $code, %args) = @_; croak "_subprocess requires a CODE ref" unless ref $code eq 'CODE'; - my $pid = fork(); + debug "Forking\n"; + my $pid = Linux::Clone::clone (sub { + local $$ = POSIX::getpid(); # try to fix up $$ if we can. + local %SIG = map {$_ => sub {debug "Got signal in $$, exiting"; _exit(0)}} @signames; + debug "Inside Child $$\n"; + + $code->(%args); + _exit(0); # always exit with 0 + }, 0, POSIX::SIGCHLD | $self->_uflags); croak "Failed to fork: $!" if ($pid < 0); - if ($pid) { - waitpid($pid, 0); # block and wait on child - return $?; - } else { - $code->(%args); - _exit(0); - } + + my $sighandler = + local %SIG = map {my $q=$_; $q => sub { + debug "got signal $q in $$\n"; + kill 'TERM', $pid; + sleep(0.2); + kill 'KILL', $pid; + kill 'KILL', $pid; + }} ($self->term_child ? @signames : ()); + + waitpid($pid, 0); + return $? } sub pre_setup { @@ -85,7 +109,7 @@ sub setup { my $uflags = $self->_uflags; $self->pre_setup(%args); - unshare($uflags); + Linux::Clone::unshare($uflags); $self->post_setup(%args); return 1; @@ -99,21 +123,10 @@ sub run { croak "Run must be given a codref to run" unless ref $code eq "CODE"; + $self->pre_setup(%args); $self->_subprocess(sub { - $self->pre_setup(%args); - my $uflags = $self->_uflags; - unshare($uflags); - - # 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->setup(%args); - $code->(%args); - } + $self->post_setup(%args); + $code->(%args); }, %args); return 1; @@ -186,6 +199,10 @@ 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 term_child + +Send a term signal to the child process on any signal, followed shortly by a kill signal. This is the default behavior to prevent zombied processes. + =item no_proc Don't setup a private /proc mount when doing private_pid diff --git a/lib/Sys/Linux/Unshare.pm b/lib/Sys/Linux/Unshare.pm deleted file mode 100644 index c829649..0000000 --- a/lib/Sys/Linux/Unshare.pm +++ /dev/null @@ -1,74 +0,0 @@ -package Sys::Linux::Unshare; - -#use strict; -use warnings; -use Data::Dumper; -require Exporter; -our @ISA = qw/Exporter/; -use Carp qw/croak/; - -require XSLoader; - -XSLoader::load(); - -my @unshare_consts = qw/CSIGNAL CLONE_VM CLONE_FS CLONE_FILES CLONE_SIGHAND CLONE_PTRACE CLONE_VFORK CLONE_PARENT CLONE_THREAD CLONE_NEWNS CLONE_SYSVSEM CLONE_SETTLS CLONE_PARENT_SETTID CLONE_CHILD_CLEARTID CLONE_DETACHED CLONE_UNTRACED CLONE_CHILD_SETTID CLONE_NEWCGROUP CLONE_NEWUTS CLONE_NEWIPC CLONE_NEWUSER CLONE_NEWPID CLONE_NEWNET CLONE_IO/; - -our @EXPORT_OK = (@unshare_consts, qw/unshare/); - -our %EXPORT_TAGS = ( - 'consts' => \@unshare_consts, - 'all' => [@unshare_consts, qw/unshare/], -); - -sub clone { - my ($flags) = @_; - local $! = 0; - my $ret_pid = _clone_sys($flags); - - if ($ret_pid < 0) { - croak "Clone call failed: $ret_pid $!"; - } - - return $ret_pid; -} - -sub unshare { - my ($flags) = @_; - - local $! = 0; - my $ret = _unshare_sys($flags); - - if ($ret != 0) { - croak "unshare failed $ret $!"; - } - - return; -} - -use constant {CSIGNAL => 0x000000ff, - CLONE_VM => 0x00000100, - CLONE_FS => 0x00000200, - CLONE_FILES => 0x00000400, - CLONE_SIGHAND => 0x00000800, - CLONE_PTRACE => 0x00002000, - CLONE_VFORK => 0x00004000, - CLONE_PARENT => 0x00008000, - CLONE_THREAD => 0x00010000, - CLONE_NEWNS => 0x00020000, - CLONE_SYSVSEM => 0x00040000, - CLONE_SETTLS => 0x00080000, - CLONE_PARENT_SETTID => 0x00100000, - CLONE_CHILD_CLEARTID => 0x00200000, - CLONE_DETACHED => 0x00400000, - CLONE_UNTRACED => 0x00800000, - CLONE_CHILD_SETTID => 0x01000000, - CLONE_NEWCGROUP => 0x02000000, - CLONE_NEWUTS => 0x04000000, - CLONE_NEWIPC => 0x08000000, - CLONE_NEWUSER => 0x10000000, - CLONE_NEWPID => 0x20000000, - CLONE_NEWNET => 0x40000000, - CLONE_IO => 0x80000000}; - -1; - diff --git a/lib/Sys/Linux/Unshare.xs b/lib/Sys/Linux/Unshare.xs deleted file mode 100644 index 315c1bc..0000000 --- a/lib/Sys/Linux/Unshare.xs +++ /dev/null @@ -1,26 +0,0 @@ -#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster) -#define _GNU_SOURCE -#include "EXTERN.h" // globals/constant import locations -#include "perl.h" // Perl symbols, structures and constants definition -#include "XSUB.h" // xsubpp functions and macros -#include - -// additional c code goes here - -MODULE = Sys::Linux::Unshare PACKAGE = Sys::Linux::Unshare -PROTOTYPES: ENABLE - - # XS code goes here - - # XS comments begin with " #" to avoid them being interpreted as pre-processor - # directives - -SV * _unshare_sys(int flags) - CODE: - ST(0) = sv_newmortal(); - sv_setiv(ST(0), unshare(flags)); - -SV * _clone_sys(int flags) - CODE: - ST(0) = sv_newmortal(); - sv_setiv(ST(0), clone(NULL, NULL, CLONE_CHILD_CLEARTID|SIGCHLD|flags, NULL)); diff --git a/t/02-namespace.t b/t/02-namespace.t index 7baa09d..7dd6331 100644 --- a/t/02-namespace.t +++ b/t/02-namespace.t @@ -7,6 +7,7 @@ use Test::SharedFork; # test 1 use Sys::Linux::Namespace; +$Sys::Linux::Namespace::debug = 1; # test 2 SKIP: { @@ -19,13 +20,37 @@ SKIP: { ok(my $pid_ns = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1), "Setup pid object"); - $pid_ns->run(code => sub { + $ret = $pid_ns->run(code => sub { is($$, 1, "We're init"); is_deeply([grep {m|/proc/\d+/|} glob '/proc/*/'], ['/proc/1/'], "Only /proc/1/ exists"); }); ok($ret, "run code in sandbox"); + alarm(5); + $pid_ns->run(code => sub { + is($$, 1, "Alarmed init"); + sleep(10); + fail("signal propogation didn't happen"); + }); + + alarm(5); + $pid_ns->run(code => sub { + is($$, 1, "Second alarmed init"); + + my $pid = fork(); + + isnt($pid, undef, "Fork succeeded"); + if (!$pid) { + sleep(30); # sleep a gigantic amount of time in the child + # We should never happen here, because our parent PID 1 should be destroyed by the kernel first + fail("Child of PID 1 lived, $$"); + } else { + waitpid($pid, 0); # wait forever + fail("PID 1 never got reaped"); + } + }); + ok($namespace->setup(), "Setup namespace in current process"); is_deeply([glob "/tmp/*"], [], "No files present in /tmp"); }