]> Sergey Matveev's repositories - public-inbox.git/commitdiff
spawn: support RLIMIT_CPU, RLIMIT_DATA and RLIMIT_CORE
authorEric Wong <e@80x24.org>
Mon, 25 Feb 2019 05:14:10 +0000 (05:14 +0000)
committerEric Wong <e@80x24.org>
Thu, 4 Apr 2019 09:13:58 +0000 (09:13 +0000)
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
lib/PublicInbox/SpawnPP.pm
t/spawn.t

index 91a3c123e736635a833fcc6fefa810616349fb9c..8ea255af25caa34e167ea5b1ab522559b468bb7e 100644 (file)
@@ -22,6 +22,8 @@ our @EXPORT_OK = qw/which spawn popen_rd/;
 my $vfork_spawn = <<'VFORK_SPAWN';
 #include <sys/types.h>
 #include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/resource.h>
 #include <unistd.h>
 #include <alloca.h>
 #include <signal.h>
@@ -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;
 }
 
index 743db224ad0b8609ce80927255c0c61f8f301baf..8692b767571a1a26c0674a29a184502823e384ab 100644 (file)
@@ -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: $!";
                }
index db3f2dc97de3a32fdfef45a98699fabf7ac562aa..5abedc96b727b186a6bd1998b4cdec980ae24795 100644 (file)
--- 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;