From 721368cd04bfbd03c0d9173fff633ae34f16409a Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Mon, 25 Feb 2019 05:14:10 +0000 Subject: spawn: support RLIMIT_CPU, RLIMIT_DATA and RLIMIT_CORE We'll be spawning cgit and git-diff, which can take gigantic amounts of CPU time and/or heap given the right (ermm... wrong) input. Limit the damage that large/expensive diffs can cause. --- lib/PublicInbox/Spawn.pm | 43 +++++++++++++++++++++++++++++++++++++++---- lib/PublicInbox/SpawnPP.pm | 9 +++++++-- t/spawn.t | 18 ++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/PublicInbox/Spawn.pm b/lib/PublicInbox/Spawn.pm index 91a3c123..8ea255af 100644 --- a/lib/PublicInbox/Spawn.pm +++ b/lib/PublicInbox/Spawn.pm @@ -22,6 +22,8 @@ our @EXPORT_OK = qw/which spawn popen_rd/; my $vfork_spawn = <<'VFORK_SPAWN'; #include #include +#include +#include #include #include #include @@ -74,11 +76,12 @@ static void xerr(const char *msg) * whatever we'll need in the future. * Be sure to update PublicInbox::SpawnPP if this changes */ -int public_inbox_fork_exec(int in, int out, int err, - SV *file, SV *cmdref, SV *envref) +int pi_fork_exec(int in, int out, int err, + SV *file, SV *cmdref, SV *envref, SV *rlimref) { AV *cmd = (AV *)SvRV(cmdref); AV *env = (AV *)SvRV(envref); + AV *rlim = (AV *)SvRV(rlimref); const char *filename = SvPV_nolen(file); pid_t pid; char **argv, **envp; @@ -99,12 +102,27 @@ int public_inbox_fork_exec(int in, int out, int err, pid = vfork(); if (pid == 0) { int sig; + I32 i, max; REDIR(in, 0); REDIR(out, 1); REDIR(err, 2); for (sig = 1; sig < NSIG; sig++) signal(sig, SIG_DFL); /* ignore errors on signals */ + + max = av_len(rlim); + for (i = 0; i < max; i += 3) { + struct rlimit rl; + SV **res = av_fetch(rlim, i, 0); + SV **soft = av_fetch(rlim, i + 1, 0); + SV **hard = av_fetch(rlim, i + 2, 0); + + rl.rlim_cur = SvIV(*soft); + rl.rlim_max = SvIV(*hard); + if (setrlimit(SvIV(*res), &rl) < 0) + xerr("sertlimit"); + } + /* * don't bother unblocking, we don't want signals * to the group taking out a subprocess @@ -145,7 +163,7 @@ if (defined $vfork_spawn) { unless (defined $vfork_spawn) { require PublicInbox::SpawnPP; no warnings 'once'; - *public_inbox_fork_exec = *PublicInbox::SpawnPP::public_inbox_fork_exec + *pi_fork_exec = *PublicInbox::SpawnPP::pi_fork_exec } # n.b. we never use absolute paths with this @@ -182,7 +200,24 @@ sub spawn ($;$$) { my $in = $opts->{0} || 0; my $out = $opts->{1} || 1; my $err = $opts->{2} || 2; - my $pid = public_inbox_fork_exec($in, $out, $err, $f, $cmd, \@env); + my $rlim = []; + + foreach my $l (qw(RLIMIT_CPU RLIMIT_CORE RLIMIT_DATA)) { + defined(my $v = $opts->{$l}) or next; + my ($soft, $hard); + if (ref($v)) { + ($soft, $hard) = @$v; + } else { + $soft = $hard = $v; + } + my $r = eval "require BSD::Resource; BSD::Resource::$l();"; + unless (defined $r) { + warn "$l undefined by BSD::Resource: $@\n"; + next; + } + push @$rlim, $r, $soft, $hard; + } + my $pid = pi_fork_exec($in, $out, $err, $f, $cmd, \@env, $rlim); $pid < 0 ? undef : $pid; } diff --git a/lib/PublicInbox/SpawnPP.pm b/lib/PublicInbox/SpawnPP.pm index 743db224..8692b767 100644 --- a/lib/PublicInbox/SpawnPP.pm +++ b/lib/PublicInbox/SpawnPP.pm @@ -9,8 +9,8 @@ use warnings; use POSIX qw(dup2 :signal_h); # Pure Perl implementation for folks that do not use Inline::C -sub public_inbox_fork_exec ($$$$$$) { - my ($in, $out, $err, $f, $cmd, $env) = @_; +sub pi_fork_exec ($$$$$$) { + my ($in, $out, $err, $f, $cmd, $env, $rlim) = @_; my $old = POSIX::SigSet->new(); my $set = POSIX::SigSet->new(); $set->fillset or die "fillset failed: $!"; @@ -22,6 +22,11 @@ sub public_inbox_fork_exec ($$$$$$) { $pid = -1; } if ($pid == 0) { + while (@$rlim) { + my ($r, $soft, $hard) = splice(@$rlim, 0, 3); + BSD::Resource::setrlimit($r, $soft, $hard) or + warn "failed to set $r=[$soft,$hard]\n"; + } if ($in != 0) { dup2($in, 0) or die "dup2 failed for stdin: $!"; } diff --git a/t/spawn.t b/t/spawn.t index db3f2dc9..5abedc96 100644 --- a/t/spawn.t +++ b/t/spawn.t @@ -92,6 +92,24 @@ use PublicInbox::Spawn qw(which spawn popen_rd); isnt($?, 0, '$? set properly: '.$?); } +SKIP: { + eval { + require BSD::Resource; + defined(BSD::Resource::RLIMIT_CPU()) + } or skip 'BSD::Resource::RLIMIT_CPU missing', 3; + my ($r, $w); + pipe($r, $w) or die "pipe: $!"; + my $cmd = ['sh', '-c', 'while true; do :; done']; + my $opt = { RLIMIT_CPU => [ 1, 1 ], RLIMIT_CORE => 0, 1 => fileno($w) }; + my $pid = spawn($cmd, undef, $opt); + close $w or die "close(w): $!"; + my $rset = ''; + vec($rset, fileno($r), 1) = 1; + ok(select($rset, undef, undef, 5), 'child died before timeout'); + is(waitpid($pid, 0), $pid, 'XCPU child process reaped'); + isnt($?, 0, 'non-zero exit status'); +} + done_testing(); 1; -- cgit v1.2.3-24-ge0c7