From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 9BD4020374 for ; Tue, 12 Mar 2019 04:00:46 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 04/13] spawn: support RLIMIT_CPU, RLIMIT_DATA and RLIMIT_CORE Date: Tue, 12 Mar 2019 04:00:37 +0000 Message-Id: <20190312040046.4619-5-e@80x24.org> In-Reply-To: <20190312040046.4619-1-e@80x24.org> References: <20190312040046.4619-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: 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 91a3c12..8ea255a 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 743db22..8692b76 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 db3f2dc..5abedc9 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; -- EW