taint-mode perl: preserve suid when running external program via system()
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. 

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.

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.


Does anyone know a way to make this work? (**As-is**: We don't have the resources to re-architect this whole thing.)

Here's a cut-down version of my script, which still shows my problem.

    #!/usr/bin/perl -T
    ## This is an SUID script: man perlsec
    %ENV = ( "PATH" => "" );

    ##### 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> /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 & 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 >> /home/dt-pdrel/date');
    run('/bin/date >> /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";
    }


And here's the output:


    $ 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 >> /home/testrel/date'
            SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/date >> /home/testrel/date', 
            OUT: /bin/bash: /home/testrel/date: Permission denied

            CMD: '/bin/date >> /home/testrel/date'
            SEC: ok:'/bin/date >> /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




taint-mode perl: preserve suid when running external program via system()
===

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. 

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.

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.


Does anyone know a way to make this work? (**As-is**: We don't have the resources to re-architect this whole thing.)

Here's a cut-down version of my script, which still shows my problem.

    #!/usr/bin/perl -T
    ## This is an SUID script: man perlsec
    %ENV = ( "PATH" => "" );

    ##### 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> /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 & 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 >> /home/dt-pdrel/date');
    run('/bin/date >> /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";
    }


And here's the output:


    $ 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 >> /home/testrel/date'
            SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/date >> /home/testrel/date', 
            OUT: /bin/bash: /home/testrel/date: Permission denied

            CMD: '/bin/date >> /home/testrel/date'
            SEC: ok:'/bin/date >> /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



