Remove unneeded internal module now that we use Linux::Clone.

This commit is contained in:
Ryan Voots 2017-05-07 13:22:46 -07:00
parent 865dc0b66d
commit 58f401e837
5 changed files with 80 additions and 132 deletions

View file

@ -1,5 +1,11 @@
Revision history for Sys::Linux::Namespaces 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 0.012 - May 4 2017
* Fix borken mount options * Fix borken mount options

View file

@ -5,13 +5,24 @@ use strict;
use warnings; use warnings;
use Sys::Linux::Mount qw/:all/; 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 POSIX qw/_exit/;
use Time::HiRes qw/sleep/;
use Moo; use Moo;
use Carp qw/croak/; use Carp qw/croak/;
has no_proc => (is => 'rw'); 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/) { for my $p (qw/tmp mount pid net ipc user uts sysvsem/) {
my $pp = "private_$p"; my $pp = "private_$p";
@ -22,13 +33,13 @@ sub _uflags {
my $self = shift; my $self = shift;
my $uflags = 0; my $uflags = 0;
$uflags |= CLONE_NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc)); $uflags |= Linux::Clone::NEWNS if ($self->private_tmp || $self->private_mount || ($self->private_pid && !$self->no_proc));
$uflags |= CLONE_NEWPID if ($self->private_pid); $uflags |= Linux::Clone::NEWPID if ($self->private_pid);
$uflags |= CLONE_NEWNET if ($self->private_net); $uflags |= Linux::Clone::NEWNET if ($self->private_net);
$uflags |= CLONE_NEWIPC if ($self->private_ipc); $uflags |= Linux::Clone::NEWIPC if ($self->private_ipc);
$uflags |= CLONE_NEWUSER if ($self->private_user); $uflags |= Linux::Clone::NEWUSER if ($self->private_user);
$uflags |= CLONE_NEWUTS if ($self->private_uts); $uflags |= Linux::Clone::NEWUTS if ($self->private_uts);
$uflags |= CLONE_SYSVSEM if ($self->private_sysvsem); $uflags |= Linux::Clone::SYSVSEM if ($self->private_sysvsem);
return $uflags; return $uflags;
} }
@ -37,16 +48,29 @@ sub _subprocess {
my ($self, $code, %args) = @_; my ($self, $code, %args) = @_;
croak "_subprocess requires a CODE ref" unless ref $code eq 'CODE'; 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); croak "Failed to fork: $!" if ($pid < 0);
if ($pid) {
waitpid($pid, 0); # block and wait on child my $sighandler =
return $?; local %SIG = map {my $q=$_; $q => sub {
} else { debug "got signal $q in $$\n";
$code->(%args); kill 'TERM', $pid;
_exit(0); sleep(0.2);
} kill 'KILL', $pid;
kill 'KILL', $pid;
}} ($self->term_child ? @signames : ());
waitpid($pid, 0);
return $?
} }
sub pre_setup { sub pre_setup {
@ -85,7 +109,7 @@ sub setup {
my $uflags = $self->_uflags; my $uflags = $self->_uflags;
$self->pre_setup(%args); $self->pre_setup(%args);
unshare($uflags); Linux::Clone::unshare($uflags);
$self->post_setup(%args); $self->post_setup(%args);
return 1; return 1;
@ -99,22 +123,11 @@ sub run {
croak "Run must be given a codref to run" unless ref $code eq "CODE"; croak "Run must be given a codref to run" unless ref $code eq "CODE";
$self->_subprocess(sub {
$self->pre_setup(%args); $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->_subprocess(sub {
$self->post_setup(%args); $self->post_setup(%args);
$code->(%args); $code->(%args);
}, %args); }, %args);
} else {
$self->setup(%args);
$code->(%args);
}
}, %args);
return 1; return 1;
} }
@ -186,6 +199,10 @@ Create a private PID namespace. This requires the use of C<< ->run() >>.
This requires a C<code> parameter either to C<new()> or to C<setup()> This requires a C<code> parameter either to C<new()> or to C<setup()>
Also sets up a private /proc mount by default 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 =item no_proc
Don't setup a private /proc mount when doing private_pid Don't setup a private /proc mount when doing private_pid

View file

@ -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;

View file

@ -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 <sched.h>
// 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));

View file

@ -7,6 +7,7 @@ use Test::SharedFork;
# test 1 # test 1
use Sys::Linux::Namespace; use Sys::Linux::Namespace;
$Sys::Linux::Namespace::debug = 1;
# test 2 # test 2
SKIP: { SKIP: {
@ -19,13 +20,37 @@ SKIP: {
ok(my $pid_ns = Sys::Linux::Namespace->new(private_tmp => 1, private_pid => 1), "Setup pid object"); 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($$, 1, "We're init");
is_deeply([grep {m|/proc/\d+/|} glob '/proc/*/'], ['/proc/1/'], "Only /proc/1/ exists"); is_deeply([grep {m|/proc/\d+/|} glob '/proc/*/'], ['/proc/1/'], "Only /proc/1/ exists");
}); });
ok($ret, "run code in sandbox"); 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"); ok($namespace->setup(), "Setup namespace in current process");
is_deeply([glob "/tmp/*"], [], "No files present in /tmp"); is_deeply([glob "/tmp/*"], [], "No files present in /tmp");
} }