title: taint-mode perl: preserve suid when running external program via system()
body: <p>I'm trying to add a feature to a legacy script. The script is suid, and uses perl -T (taint mode: man perlsec), for extra security. The feature I need to add is implemented in Python. </p>
  
  <p>My problem is that I can't convince perlsec to preserve the suid permissions, no matter how much I launder the environment and my command lines.</p>
  
  <p>This is frustrating, since it preserves the suid for other binaries (such as /bin/id). Is there a undocumented special case for /usr/bin/perl? This seems unlikely.</p>
  
  <p>Does anyone know a way to make this work? (<strong>As-is</strong>: We don't have the resources to re-architect this whole thing.)</p>
  
  <hr>
  
  <p><strong>Solution:</strong> (as per @gbacon)</p>
  
  <pre><code># use the -p option to bash
  system('/bin/bash', '-p', '-c', '/usr/bin/id -un');
  
  # or set real user and group ids
  $&lt; = $&gt;;
  $( = $);
  system('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
  </code></pre>
  
  <p>Gives the desired results!</p>
  
  <hr>
  
  <p>Here's a cut-down version of my script, which still shows my problem.</p>
  
  <pre><code>#!/usr/bin/perl -T
  ## This is an SUID script: man perlsec
  %ENV = ( "PATH" =&gt; "" );
  
  ##### PERLSEC HELPERS #####
  sub tainted (@) {
      # Prevent errors, stringifying
      local(@_, $@, $^W) = @_;  
  
      #let eval catch the DIE signal
      $SIG{__DIE__}  = '';      
      my $retval = not eval { join("",@_), kill 0; 1 };
      $SIG{__DIE__}  = 'myexit';      
  
      return $retval
  }
  
  sub show_taint {
      foreach (@_) {
          my $arg = $_; #prevent "read-only variable" nonsense
          chomp $arg;
          if ( tainted($arg) ) {
              print "TAINT:'$arg'";
          } else {
              print "ok:'$arg'";
          }
          print ", ";
      }
      print "\n";
  }
  
  ### END PERLSEC HELPERS ###
  
  # Are we SUID ? man perlsec
  my $uid = `/usr/bin/id --user` ;
  chomp $uid;
  
  my $reluser = "dt-pdrel";
  my $reluid = `/usr/bin/id --user $reluser 2&gt; /dev/null`;
  chomp $reluid;
  
  if ( $uid ne $reluid ) {
      # what ? we are not anymore SUID ? somebody must do a chmod u+s $current_script
      print STDERR "chmod 4555 $myname\n";
      exit(14);
  }
  
  # comment this line if you don't want to autoflush after every print
  $| = 1;
  
  
  # now, we're safe, single &amp; SUID
  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  # BEGIN of main code itself
  
  
  print "\nENVIRON UNDER BASH:\n";
  run('/bin/bash', '-c', '/bin/env');
  
  print "\nTAINT DEMO:\n";
  print "\@ARGV: ";
  show_taint(@ARGV);
  print "\%ENV: ";
  show_taint(values %ENV);
  print "`cat`: ";
  show_taint(`/bin/cat /etc/host.conf`);
  
  print "\nworks:\n";
  run('/usr/bin/id', '-un');
  run('/usr/bin/id -un');
  
  print "\ndoesn't work:\n";
  run('/bin/bash', '-c', '/usr/bin/id -un');
  run('/bin/bash', '-c', '/bin/date &gt;&gt; /home/dt-pdrel/date');
  run('/bin/date &gt;&gt; /home/dt-pdrel/date');
  run('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
  run('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
  
  
  sub run {
      my @cmd = @_;
      print "\tCMD: '@cmd'\n";
      print "\tSEC: ";
      show_taint(@cmd);
      print "\tOUT: ";
      system @cmd ;
      print "\n";
  }
  </code></pre>
  
  <p>And here's the output:</p>
  
  <pre><code>$ id -un
  bukzor
  
  $ ls -l /proj/test/test.pl
  -rwsr-xr-x 1 testrel asic 1976 Jul 22 14:34 /proj/test/test.pl*
  
  $ /proj/test/test.pl foo bar
  
  ENVIRON UNDER BASH:
          CMD: '/bin/bash -c /bin/env'
          SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/env', 
          OUT: PATH=
  PWD=/proj/test2/bukzor/test_dir/
  SHLVL=1
  _=/bin/env
  
  
  TAINT DEMO:
  @ARGV: TAINT:'foo', TAINT:'bar', 
  %ENV: ok:'', 
  `cat`: TAINT:'order hosts,bind', 
  
  works:
          CMD: '/usr/bin/id -un'
          SEC: ok:'/usr/bin/id', ok:'-un', 
          OUT: testrel
  
          CMD: '/usr/bin/id -un'
          SEC: ok:'/usr/bin/id -un', 
          OUT: testrel
  
  
  doesn't work:
          CMD: '/bin/bash -c /usr/bin/id -un'
          SEC: ok:'/bin/bash', ok:'-c', ok:'/usr/bin/id -un', 
          OUT: bukzor
  
          CMD: '/bin/bash -c /bin/date &gt;&gt; /home/testrel/date'
          SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/date &gt;&gt; /home/testrel/date', 
          OUT: /bin/bash: /home/testrel/date: Permission denied
  
          CMD: '/bin/date &gt;&gt; /home/testrel/date'
          SEC: ok:'/bin/date &gt;&gt; /home/testrel/date', 
          OUT: sh: /home/testrel/date: Permission denied
  
          CMD: '/usr/bin/python -c import os; os.system("/usr/bin/id -un")'
          SEC: ok:'/usr/bin/python', ok:'-c', ok:'import os; os.system("/usr/bin/id -un")', 
          OUT: bukzor
  
          CMD: '/usr/bin/python -c import os; os.system("/usr/bin/id -un")'
          SEC: ok:'/usr/bin/python', ok:'-c', ok:'import os; os.system("/usr/bin/id -un")', 
          OUT: bukzor
  </code></pre>