This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Implement $^A tainting
[perl5.git] / t / op / taint.t
old mode 100755 (executable)
new mode 100644 (file)
index f661108..86372bd
@@ -17,13 +17,17 @@ use Config;
 use File::Spec::Functions;
 
 BEGIN { require './test.pl'; }
-plan tests => 260;
+plan tests => 336;
 
 $| = 1;
 
 use vars qw($ipcsysv); # did we manage to load IPC::SysV?
 
+my ($old_env_path, $old_env_dcl_path, $old_env_term);
 BEGIN {
+   $old_env_path = $ENV{'PATH'};
+   $old_env_dcl_path = $ENV{'DCL$PATH'};
+   $old_env_term = $ENV{'TERM'};
   if ($^O eq 'VMS' && !defined($Config{d_setenv})) {
       $ENV{PATH} = $ENV{PATH};
       $ENV{TERM} = $ENV{TERM} ne ''? $ENV{TERM} : 'dummy';
@@ -38,17 +42,15 @@ BEGIN {
   }
 }
 
-my $Is_MacOS    = $^O eq 'MacOS';
 my $Is_VMS      = $^O eq 'VMS';
 my $Is_MSWin32  = $^O eq 'MSWin32';
 my $Is_NetWare  = $^O eq 'NetWare';
 my $Is_Dos      = $^O eq 'dos';
 my $Is_Cygwin   = $^O eq 'cygwin';
 my $Is_OpenBSD  = $^O eq 'openbsd';
-my $Invoke_Perl = $Is_VMS      ? 'MCR Sys$Disk:[]Perl.' :
+my $Invoke_Perl = $Is_VMS      ? 'MCR Sys$Disk:[]Perl.exe' :
                   $Is_MSWin32  ? '.\perl'               :
-                  $Is_MacOS    ? ':perl'                :
-                  $Is_NetWare  ? 'perl'                 : 
+                  $Is_NetWare  ? 'perl'                 :
                                  './perl'               ;
 my @MoreEnv = qw/IFS CDPATH ENV BASH_ENV/;
 
@@ -57,11 +59,22 @@ if ($Is_VMS) {
     for $x ('DCL$PATH', @MoreEnv) {
        ($old{$x}) = $ENV{$x} =~ /^(.*)$/ if exists $ENV{$x};
     }
+    # VMS note:  PATH and TERM are automatically created by the C
+    # library in VMS on reference to the their keys in %ENV.
+    # There is currently no way to determine if they did not exist
+    # before this test was run.
     eval <<EndOfCleanup;
        END {
-           \$ENV{PATH} = '' if $Config{d_setenv};
-           warn "# Note: logical name 'PATH' may have been deleted\n";
+           \$ENV{PATH} = \$old_env_path;
+           warn "# Note: logical name 'PATH' may have been created\n";
+           \$ENV{'TERM'} = \$old_env_term;
+           warn "# Note: logical name 'TERM' may have been created\n";
            \@ENV{keys %old} = values %old;
+           if (defined \$old_env_dcl_path) {
+               \$ENV{'DCL\$PATH'} = \$old_env_dcl_path;
+           } else {
+               delete \$ENV{'DCL\$PATH'};
+           }
        }
 EndOfCleanup
 }
@@ -119,7 +132,7 @@ sub test ($;$) {
 }
 
 # We need an external program to call.
-my $ECHO = ($Is_MSWin32 ? ".\\echo$$" : $Is_MacOS ? ":echo$$" : ($Is_NetWare ? "echo$$" : "./echo$$"));
+my $ECHO = ($Is_MSWin32 ? ".\\echo$$" : ($Is_NetWare ? "echo$$" : "./echo$$"));
 END { unlink $ECHO }
 open PROG, "> $ECHO" or die "Can't create $ECHO: $!";
 print PROG 'print "@ARGV\n"', "\n";
@@ -150,8 +163,7 @@ my $TEST = catfile(curdir(), 'TEST');
            };
        }
     }
-
-    $ENV{PATH} = '';
+    $ENV{PATH} = ($Is_Cygwin) ? '/usr/bin' : '';
     delete @ENV{@MoreEnv};
     $ENV{TERM} = 'dumb';
 
@@ -159,7 +171,7 @@ my $TEST = catfile(curdir(), 'TEST');
 
     SKIP: {
         skip "Environment tainting tests skipped", 4
-          if $Is_MSWin32 || $Is_NetWare || $Is_VMS || $Is_Dos || $Is_MacOS;
+          if $Is_MSWin32 || $Is_NetWare || $Is_VMS || $Is_Dos;
 
        my @vars = ('PATH', @MoreEnv);
        while (my $v = $vars[0]) {
@@ -271,7 +283,7 @@ my $TEST = catfile(curdir(), 'TEST');
 # How about command-line arguments? The problem is that we don't
 # always get some, so we'll run another process with some.
 SKIP: {
-    my $arg = catfile(curdir(), "arg$$");
+    my $arg = tempfile();
     open PROG, "> $arg" or die "Can't create $arg: $!";
     print PROG q{
        eval { join('', @ARGV), kill 0 };
@@ -381,7 +393,7 @@ SKIP: {
 
 # Operations which affect directories can't use tainted data.
 {
-    test !eval { mkdir "foo".$TAINT, 0755.$TAINT0 }, 'mkdir';
+    test !eval { mkdir "foo".$TAINT, 0755 . $TAINT0 }, 'mkdir';
     test $@ =~ /^Insecure dependency/, $@;
 
     test !eval { rmdir $TAINT }, 'rmdir';
@@ -404,8 +416,7 @@ SKIP: {
     test !eval { require $foo }, 'require';
     test $@ =~ /^Insecure dependency/, $@;
 
-    my $filename = "./taintB$$";       # NB: $filename isn't tainted!
-    END { unlink $filename if defined $filename }
+    my $filename = tempfile(); # NB: $filename isn't tainted!
     $foo = $filename . $TAINT;
     unlink $filename;  # in any case
 
@@ -417,8 +428,7 @@ SKIP: {
     # just because Errno possibly failing.
     test eval('$!{ENOENT}') ||
        $! == 2 || # File not found
-       ($Is_Dos && $! == 22) ||
-       ($^O eq 'mint' && $! == 33);
+       ($Is_Dos && $! == 22);
 
     test !eval { open FOO, "> $foo" }, 'open for write';
     test $@ =~ /^Insecure dependency/, $@;
@@ -492,8 +502,7 @@ SKIP: {
        my $foo = "x" x 979;
        taint_these $foo;
        local *FOO;
-       my $temp = "./taintC$$";
-       END { unlink $temp }
+       my $temp = tempfile();
        test open(FOO, "> $temp"), "Couldn't open $temp for write: $!";
 
        test !eval { ioctl FOO, $TAINT0, $foo }, 'ioctl';
@@ -616,7 +625,6 @@ SKIP: {
        unlink($symlink);
        my $sl = "/something/naughty";
        # it has to be a real path on Mac OS
-       $sl = MacPerl::MakePath((MacPerl::Volumes())[0]) if $Is_MacOS;
        symlink($sl, $symlink) or die "symlink: $!\n";
        my $readlink = readlink($symlink);
        test tainted $readlink;
@@ -961,15 +969,11 @@ TODO: {
     };
     test !$@;
 
-    SKIP: {
-        skip "no exec() on MacOS Classic" if $Is_MacOS;
-
-       eval { 
-            no warnings;
-            exec("lskdfj does not exist","with","args"); 
-        };
-       test !$@;
-    }
+    eval {
+       no warnings;
+       exec("lskdfj does not exist","with","args"); 
+    };
+    test !$@;
 
     # If you add tests here update also the above skip block for VMS.
 }
@@ -1124,13 +1128,19 @@ TERNARY_CONDITIONALS: {
 
 {
     my @a;
-    local $::TODO = 1;
-    $a[0] = $^X;
-    my $i = 0;
-    while($a[0]=~ m/(.)/g ) {
-       last if $i++ > 10000;
-    }
-    cmp_ok $i, '<', 10000, "infinite m//g";
+    $a[0] = $^X . '-';
+    $a[0]=~ m/(.)/g;
+    cmp_ok pos($a[0]), '>', 0, "infinite m//g on arrays (aelemfast)";
+
+    my $i = 1;
+    $a[$i] = $^X . '-';
+    $a[$i]=~ m/(.)/g;
+    cmp_ok pos($a[$i]), '>', 0, "infinite m//g on arrays (aelem)";
+
+    my %h;
+    $h{a} = $^X . '-';
+    $h{a}=~ m/(.)/g;
+    cmp_ok pos($h{a}), '>', 0, "infinite m//g on hashes (helem)";
 }
 
 SKIP:
@@ -1216,17 +1226,237 @@ SKIP:
 }
 
 {
-    local $::TODO = "eval currently ignores tainting";
     my $val = 0;
     my $tainted = '1' . $TAINT;
     eval '$val = eval $tainted;';
     is ($val, 0, "eval doesn't like tainted strings");
     like ($@, qr/^Insecure dependency in eval/);
 
-    # Rather nice code to get a tainted by from Rick Delaney
-    open 0 or die $!;
-    $tainted=(<0>,<0>);
+    # Rather nice code to get a tainted undef by from Rick Delaney
+    open FH, "test.pl" or die $!;
+    seek FH, 0, 2 or die $!;
+    $tainted = <FH>;
 
     eval 'eval $tainted';
     like ($@, qr/^Insecure dependency in eval/);
 }
+
+foreach my $ord (78, 163, 256) {
+    # 47195
+    my $line = 'A1' . $TAINT . chr $ord;
+    chop $line;
+    is($line, 'A1');
+    $line =~ /(A\S*)/;
+    ok(!tainted($1), "\\S match with chr $ord");
+}
+
+{
+    # 59998
+    sub cr { my $x = crypt($_[0], $_[1]); $x }
+    sub co { my $x = ~$_[0]; $x }
+    my ($a, $b);
+    $a = cr('hello', 'foo' . $TAINT);
+    $b = cr('hello', 'foo');
+    ok(tainted($a),  "tainted crypt");
+    ok(!tainted($b), "untainted crypt");
+    $a = co('foo' . $TAINT);
+    $b = co('foo');
+    ok(tainted($a),  "tainted complement");
+    ok(!tainted($b), "untainted complement");
+}
+
+{
+    my @data = qw(bonk zam zlonk qunckkk);
+    # Clearly some sort of usenet bang-path
+    my $string = $TAINT . join "!", @data;
+
+    ok(tainted($string), "tainted data");
+
+    my @got = split /!|,/, $string;
+
+    # each @got would be useful here, but I want the test for earlier perls
+    for my $i (0 .. $#data) {
+       ok(tainted($got[$i]), "tainted result $i");
+       is($got[$i], $data[$i], "correct content $i");
+    }
+
+    ok(tainted($string), "still tainted data");
+
+    my @got = split /[!,]/, $string;
+
+    # each @got would be useful here, but I want the test for earlier perls
+    for my $i (0 .. $#data) {
+       ok(tainted($got[$i]), "tainted result $i");
+       is($got[$i], $data[$i], "correct content $i");
+    }
+
+    ok(tainted($string), "still tainted data");
+
+    my @got = split /!/, $string;
+
+    # each @got would be useful here, but I want the test for earlier perls
+    for my $i (0 .. $#data) {
+       ok(tainted($got[$i]), "tainted result $i");
+       is($got[$i], $data[$i], "correct content $i");
+    }
+}
+
+# Bug RT #52552 - broken by change at git commit id f337b08
+{
+    my $x = $TAINT. q{print "Hello world\n"};
+    my $y = pack "a*", $x;
+    ok(tainted($y), "pack a* preserves tainting");
+
+    my $z = pack "A*", q{print "Hello world\n"}.$TAINT;
+    ok(tainted($z), "pack A* preserves tainting");
+
+    my $zz = pack "a*a*", q{print "Hello world\n"}, $TAINT;
+    ok(tainted($zz), "pack a*a* preserves tainting");
+}
+
+# Bug RT #61976 tainted $! would show numeric rather than string value
+
+{
+    my $tainted_path = substr($^X,0,0) . "/no/such/file";
+    my $err;
+    # $! is used in a tainted expression, so gets tainted
+    open my $fh, $tainted_path or $err= "$!";
+    unlike($err, qr/^\d+$/, 'tainted $!');
+}
+
+{
+    # #6758: tainted values become untainted in tied hashes
+    #         (also applies to other value magic such as pos)
+
+
+    package P6758;
+
+    sub TIEHASH { bless {} }
+    sub TIEARRAY { bless {} }
+
+    my $i = 0;
+
+    sub STORE {
+       main::ok(main::tainted($_[1]), "tied arg1 tainted");
+       main::ok(main::tainted($_[2]), "tied arg2 tainted");
+        $i++;
+    }
+
+    package main;
+
+    my ($k,$v) = qw(1111 val);
+    taint_these($k,$v);
+    tie my @array, 'P6758';
+    tie my %hash , 'P6758';
+    $array[$k] = $v;
+    $hash{$k} = $v;
+    ok $i == 2, "tied STORE called correct number of times";
+}
+
+# Bug RT #45167 the return value of sprintf sometimes wasn't tainted
+# when the args were tainted. This only occured on the first use of
+# sprintf; after that, its TARG has taint magic attached, so setmagic
+# at the end works.  That's why there are multiple sprintf's below, rather
+# than just one wrapped in an inner loop. Also, any plantext betwerrn
+# fprmat entires would correctly cause tainting to get set. so test with
+# "%s%s" rather than eg "%s %s".
+
+{
+    for my $var1 ($TAINT, "123") {
+       for my $var2 ($TAINT0, "456") {
+           my @s;
+           push @s, sprintf '%s', $var1, $var2;
+           push @s, sprintf ' %s', $var1, $var2;
+           push @s, sprintf '%s%s', $var1, $var2;
+           for (0..2) {
+               ok( !(
+                       tainted($s[$_]) xor
+                       (tainted($var1) || ($_==2 && tainted($var2)))
+                   ),
+                   "sprintf fmt$_, '$var1', '$var2'");
+           }
+       }
+    }
+}
+
+
+# Bug RT #67962: old tainted $1 gets treated as tainted
+# in next untainted # match
+
+{
+    use re 'taint';
+    "abc".$TAINT =~ /(.*)/; # make $1 tainted
+    ok(tainted($1), '$1 should be tainted');
+
+    my $untainted = "abcdef";
+    ok(!tainted($untainted), '$untainted should be untainted');
+    $untainted =~ s/(abc)/$1/;
+    ok(!tainted($untainted), '$untainted should still be untainted');
+    $untainted =~ s/(abc)/x$1/;
+    ok(!tainted($untainted), '$untainted should yet still be untainted');
+}
+
+{
+    fresh_perl_is(<<'end', "ok", { switches => [ '-T' ] },
+    $TAINT = substr($^X, 0, 0);
+    formline('@'.('<'x("2000".$TAINT)).' | @*', 'hallo', 'welt');
+    print "ok";
+end
+    "formline survives a tainted dynamic picture");
+}
+
+{
+    ok(!tainted($^A), "format accumulator not tainted yet");
+    formline('@ | @*', 'hallo' . $TAINT, 'welt');
+    ok(tainted($^A), "tainted formline argument makes a tainted accumulator");
+    $^A = "";
+    ok(!tainted($^A), "accumulator can be explicitly untainted");
+    formline('@' .('<'*5) . ' | @*', 'hallo', 'welt');
+    ok(!tainted($^A), "accumulator still untainted");
+    $^A = "" . $TAINT;
+    ok(tainted($^A), "accumulator can be explicitly tainted");
+    formline('@' .('<'*5) . ' | @*', 'hallo', 'welt');
+    ok(tainted($^A), "accumulator still tainted");
+    $^A = "";
+    ok(!tainted($^A), "accumulator untainted again");
+    formline('@' .('<'*5) . ' | @*', 'hallo', 'welt');
+    ok(!tainted($^A), "accumulator still untainted");
+    formline('@' .('<'*(5+$TAINT0)) . ' | @*', 'hallo', 'welt');
+    TODO: {
+        local $::TODO = "get magic handled too late?";
+        ok(tainted($^A), "the accumulator should be tainted already");
+    }
+    ok(tainted($^A), "tainted formline picture makes a tainted accumulator");
+}
+
+# This may bomb out with the alarm signal so keep it last
+SKIP: {
+    skip "No alarm()"  unless $Config{d_alarm};
+    # Test from RT #41831]
+    # [PATCH] Bug & fix: hang when using study + taint mode (perl 5.6.1, 5.8.x)
+
+    my $DATA = <<'END' . $TAINT;
+line1 is here
+line2 is here
+line3 is here
+line4 is here
+
+END
+
+    #study $DATA;
+
+    ## don't set $SIG{ALRM}, since we'd never get to a user-level handler as
+    ## perl is stuck in a regexp infinite loop!
+
+    alarm(10);
+
+    if ($DATA =~ /^line2.*line4/m) {
+       fail("Should not be a match")
+    } else {
+       pass("Match on tainted multiline data should fail promptly");
+    }
+
+    alarm(0);
+}
+__END__
+# Keep the previous test last