diff options
Diffstat (limited to 'dist/Safe/t')
-rw-r--r-- | dist/Safe/t/safe1.t | 67 | ||||
-rw-r--r-- | dist/Safe/t/safe2.t | 153 | ||||
-rw-r--r-- | dist/Safe/t/safe3.t | 46 | ||||
-rw-r--r-- | dist/Safe/t/safeload.t | 26 | ||||
-rw-r--r-- | dist/Safe/t/safeops.t | 428 | ||||
-rw-r--r-- | dist/Safe/t/safeuniversal.t | 46 |
6 files changed, 766 insertions, 0 deletions
diff --git a/dist/Safe/t/safe1.t b/dist/Safe/t/safe1.t new file mode 100644 index 0000000000..385d6610c5 --- /dev/null +++ b/dist/Safe/t/safe1.t @@ -0,0 +1,67 @@ +#!./perl -w +$|=1; +BEGIN { + require Config; import Config; + if ($Config{'extensions'} !~ /\bOpcode\b/ && $Config{'osname'} ne 'VMS') { + print "1..0\n"; + exit 0; + } + +} + +# Tests Todo: +# 'main' as root + +package test; # test from somewhere other than main + +use vars qw($bar); + +use Opcode 1.00, qw(opdesc opset opset_to_ops opset_to_hex + opmask_add full_opset empty_opset opcodes opmask define_optag); + +use Safe 1.00; + +my $last_test; # initalised at end +print "1..$last_test\n"; + +my $t = 1; +my $cpt; +# create and destroy some automatic Safe compartments first +$cpt = new Safe or die; +$cpt = new Safe or die; +$cpt = new Safe or die; + +$cpt = new Safe "Root" or die; + +foreach(1..3) { + $foo = 42; + + $cpt->share(qw($foo)); + + print ${$cpt->varglob('foo')} == 42 ? "ok $t\n" : "not ok $t\n"; $t++; + + ${$cpt->varglob('foo')} = 9; + + print $foo == 9 ? "ok $t\n" : "not ok $t\n"; $t++; + + print $cpt->reval('$foo') == 9 ? "ok $t\n" : "not ok $t\n"; $t++; + # check 'main' has been changed: + print $cpt->reval('$::foo') == 9 ? "ok $t\n" : "not ok $t\n"; $t++; + print $cpt->reval('$main::foo') == 9 ? "ok $t\n" : "not ok $t\n"; $t++; + # check we can't see our test package: + print $cpt->reval('$test::foo') ? "not ok $t\n" : "ok $t\n"; $t++; + print $cpt->reval('${"test::foo"}') ? "not ok $t\n" : "ok $t\n"; $t++; + + $cpt->erase; # erase the compartment, e.g., delete all variables + + print $cpt->reval('$foo') ? "not ok $t\n" : "ok $t\n"; $t++; + + # Note that we *must* use $cpt->varglob here because if we used + # $Root::foo etc we would still see the original values! + # This seems to be because the compiler has created an extra ref. + + print ${$cpt->varglob('foo')} ? "not ok $t\n" : "ok $t\n"; $t++; +} + +print "ok $last_test\n"; +BEGIN { $last_test = 28 } diff --git a/dist/Safe/t/safe2.t b/dist/Safe/t/safe2.t new file mode 100644 index 0000000000..2548dcc6e8 --- /dev/null +++ b/dist/Safe/t/safe2.t @@ -0,0 +1,153 @@ +#!./perl -w +$|=1; +BEGIN { + require Config; import Config; + if ($Config{'extensions'} !~ /\bOpcode\b/ && $Config{'osname'} ne 'VMS') { + print "1..0\n"; + exit 0; + } +} + +# Tests Todo: +# 'main' as root + +use vars qw($bar); + +use Opcode 1.00, qw(opdesc opset opset_to_ops opset_to_hex + opmask_add full_opset empty_opset opcodes opmask define_optag); + +use Safe 1.00; + +my $last_test; # initalised at end +print "1..$last_test\n"; + +# Set up a package namespace of things to be visible to the unsafe code +$Root::foo = "visible"; +$bar = "invisible"; + +# Stop perl from moaning about identifies which are apparently only used once +$Root::foo .= ""; + +my $cpt; +# create and destroy a couple of automatic Safe compartments first +$cpt = new Safe or die; +$cpt = new Safe or die; + +$cpt = new Safe "Root"; + +$cpt->permit(qw(:base_io)); + +$cpt->reval(q{ system("echo not ok 1"); }); +if ($@ =~ /^'?system'? trapped by operation mask/) { + print "ok 1\n"; +} else { + print "#$@" if $@; + print "not ok 1\n"; +} + +$cpt->reval(q{ + print $foo eq 'visible' ? "ok 2\n" : "not ok 2\n"; + print $main::foo eq 'visible' ? "ok 3\n" : "not ok 3\n"; + print defined($bar) ? "not ok 4\n" : "ok 4\n"; + print defined($::bar) ? "not ok 5\n" : "ok 5\n"; + print defined($main::bar) ? "not ok 6\n" : "ok 6\n"; +}); +print $@ ? "not ok 7\n#$@" : "ok 7\n"; + +$foo = "ok 8\n"; +%bar = (key => "ok 9\n"); +@baz = (); push(@baz, "o", "10"); $" = 'k '; +$glob = "ok 11\n"; +@glob = qw(not ok 16); + +sub sayok { print "ok @_\n" } + +$cpt->share(qw($foo %bar @baz *glob sayok)); +$cpt->share('$"') unless $Config{use5005threads}; + +$cpt->reval(q{ + package other; + sub other_sayok { print "ok @_\n" } + package main; + print $foo ? $foo : "not ok 8\n"; + print $bar{key} ? $bar{key} : "not ok 9\n"; + (@baz) ? print "@baz\n" : print "not ok 10\n"; + print $glob; + other::other_sayok(12); + $foo =~ s/8/14/; + $bar{new} = "ok 15\n"; + @glob = qw(ok 16); +}); +print $@ ? "not ok 13\n#$@" : "ok 13\n"; +$" = ' '; +print $foo, $bar{new}, "@glob\n"; + +$Root::foo = "not ok 17"; +@{$cpt->varglob('bar')} = qw(not ok 18); +${$cpt->varglob('foo')} = "ok 17"; +@Root::bar = "ok"; +push(@Root::bar, "18"); # Two steps to prevent "Identifier used only once..." + +print "$Root::foo\n"; +print "@{$cpt->varglob('bar')}\n"; + +use strict; + +print 1 ? "ok 19\n" : "not ok 19\n"; +print 1 ? "ok 20\n" : "not ok 20\n"; + +my $m1 = $cpt->mask; +$cpt->trap("negate"); +my $m2 = $cpt->mask; +my @masked = opset_to_ops($m1); +print $m2 eq opset("negate", @masked) ? "ok 21\n" : "not ok 21\n"; + +print eval { $cpt->mask("a bad mask") } ? "not ok 22\n" : "ok 22\n"; + +print $cpt->reval("2 + 2") == 4 ? "ok 23\n" : "not ok 23\n"; + +$cpt->mask(empty_opset); +my $t_scalar = $cpt->reval('print wantarray ? "not ok 24\n" : "ok 24\n"'); +print $cpt->reval('@ary=(6,7,8);@ary') == 3 ? "ok 25\n" : "not ok 25\n"; +my @t_array = $cpt->reval('print wantarray ? "ok 26\n" : "not ok 26\n"; (2,3,4)'); +print $t_array[2] == 4 ? "ok 27\n" : "not ok 27\n"; + +my $t_scalar2 = $cpt->reval('die "foo bar"; 1'); +print defined $t_scalar2 ? "not ok 28\n" : "ok 28\n"; +print $@ =~ /foo bar/ ? "ok 29\n" : "not ok 29\n"; + +# --- rdo + +my $t = 30; +$! = 0; +my $nosuch = '/non/existant/file.name'; +open(NOSUCH, $nosuch); +if ($@) { + my $errno = $!; + die "Eek! Attempting to open $nosuch failed, but \$! is still 0" unless $!; + $! = 0; + $cpt->rdo($nosuch); + print $! == $errno ? "ok $t\n" : sprintf "not ok $t # \"$!\" is %d (expected %d)\n", $!, $errno; $t++; +} else { + die "Eek! Didn't expect $nosuch to be there."; +} +close(NOSUCH); + +# test #31 is gone. +print "ok $t\n"; $t++; + +#my $rdo_file = "tmp_rdo.tpl"; +#if (open X,">$rdo_file") { +# print X "999\n"; +# close X; +# $cpt->permit_only('const', 'leaveeval'); +# print $cpt->rdo($rdo_file) == 999 ? "ok $t\n" : "not ok $t\n"; $t++; +# unlink $rdo_file; +#} +#else { +# print "# test $t skipped, can't open file: $!\nok $t\n"; $t++; +#} + + +print "ok $last_test\n"; +BEGIN { $last_test = 32 } diff --git a/dist/Safe/t/safe3.t b/dist/Safe/t/safe3.t new file mode 100644 index 0000000000..1f99f49ed9 --- /dev/null +++ b/dist/Safe/t/safe3.t @@ -0,0 +1,46 @@ +#!perl -w + +BEGIN { + require Config; import Config; + if ($Config{'extensions'} !~ /\bOpcode\b/ + && $Config{'extensions'} !~ /\bPOSIX\b/ + && $Config{'osname'} ne 'VMS') + { + print "1..0\n"; + exit 0; + } +} + +use strict; +use warnings; +use POSIX qw(ceil); +use Test::More tests => 2; +use Safe; + +my $safe = new Safe; +$safe->deny('add'); + +my $masksize = ceil( Opcode::opcodes / 8 ); +# Attempt to change the opmask from within the safe compartment +$safe->reval( qq{\$_[1] = qq/\0/ x } . $masksize ); + +# Check that it didn't work +$safe->reval( q{$x + $y} ); +# Written this way to keep the Test::More that comes with perl 5.6.2 happy +ok( $@ =~ /^'?addition \(\+\)'? trapped by operation mask/, + 'opmask still in place with reval' ); + +my $safe2 = new Safe; +$safe2->deny('add'); + +open my $fh, '>nasty.pl' or die "Can't write nasty.pl: $!\n"; +print $fh <<EOF; +\$_[1] = "\0" x $masksize; +EOF +close $fh; +$safe2->rdo('nasty.pl'); +$safe2->reval( q{$x + $y} ); +# Written this way to keep the Test::More that comes with perl 5.6.2 happy +ok( $@ =~ /^'?addition \(\+\)'? trapped by operation mask/, + 'opmask still in place with rdo' ); +END { unlink 'nasty.pl' } diff --git a/dist/Safe/t/safeload.t b/dist/Safe/t/safeload.t new file mode 100644 index 0000000000..2d2c3ccb4a --- /dev/null +++ b/dist/Safe/t/safeload.t @@ -0,0 +1,26 @@ +#!perl + +BEGIN { + require Config; + import Config; + if ($Config{'extensions'} !~ /\bOpcode\b/) { + print "1..0\n"; + exit 0; + } + # Can we load the version module ? + eval { require version; 1 } or do { + print "1..0 # no version.pm\n"; + exit 0; + }; + delete $INC{"version.pm"}; +} + +use strict; +use Test::More; +use Safe; +plan(tests => 1); + +my $c = new Safe; +$c->permit(qw(require caller entereval unpack)); +my $r = $c->reval(q{ use version; 1 }); +ok( defined $r, "Can load version.pm in a Safe compartment" ) or diag $@; diff --git a/dist/Safe/t/safeops.t b/dist/Safe/t/safeops.t new file mode 100644 index 0000000000..bd8217d8dc --- /dev/null +++ b/dist/Safe/t/safeops.t @@ -0,0 +1,428 @@ +#!perl +# Tests that all ops can be trapped by a Safe compartment + +BEGIN { + unless ($ENV{PERL_CORE}) { + # this won't work outside of the core, so exit + print "1..0 # skipped: PERL_CORE unset\n"; exit 0; + } +} +use Config; +BEGIN { + if ($Config{'extensions'} !~ /\bOpcode\b/ && $Config{'osname'} ne 'VMS') { + print "1..0\n"; exit 0; + } +} + +use strict; +use Test::More; +use Safe; + +# Read the op names and descriptions directly from opcode.pl +my @op; +my %code; + +while (<DATA>) { + chomp; + die "Can't match $_" unless /^([a-z_0-9]+)\t+(.*)/; + $code{$1} = $2; +} + +open my $fh, '<', '../../opcode.pl' or die "Can't open opcode.pl: $!"; +while (<$fh>) { + last if /^__END__/; +} +while (<$fh>) { + chomp; + next if !$_ or /^#/; + my ($op, $opname) = split /\t+/; + push @op, [$op, $opname, $code{$op}]; +} +close $fh; + +plan(tests => scalar @op); + +sub testop { + my ($op, $opname, $code) = @_; + pass("$op : skipped") and return if $code =~ /^SKIP/; + pass("$op : skipped") and return if $code =~ m://|~~: && $] < 5.010; + my $c = new Safe; + $c->deny_only($op); + $c->reval($code); + like($@, qr/'\Q$opname\E' trapped by operation mask/, $op); +} + +foreach (@op) { + if ($_->[2]) { + testop @$_; + } else { + local $TODO = "No test yet for $_->[1]"; + fail(); + } +} + +# things that begin with SKIP are skipped, for various reasons (notably +# optree modified by the optimizer -- Safe checks are done before the +# optimizer modifies the optree) + +__DATA__ +null SKIP +stub SKIP +scalar scalar $x +pushmark print @x +wantarray wantarray +const 42 +gvsv SKIP (set by optimizer) $x +gv SKIP *x +gelem *x{SCALAR} +padsv SKIP my $x +padav SKIP my @x +padhv SKIP my %x +padany SKIP (not implemented) +pushre SKIP split /foo/ +rv2gv *x +rv2sv $x +av2arylen $#x +rv2cv f() +anoncode sub { } +prototype prototype 'foo' +refgen \($x,$y) +srefgen SKIP \$x +ref ref +bless bless +backtick qx/ls/ +glob <*.c> +readline <FH> +rcatline SKIP (set by optimizer) $x .= <F> +regcmaybe SKIP (internal) +regcreset SKIP (internal) +regcomp SKIP (internal) +match /foo/ +qr qr/foo/ +subst s/foo/bar/ +substcont SKIP (set by optimizer) +trans y:z:t: +sassign $x = $y +aassign @x = @y +chop chop @foo +schop chop +chomp chomp @foo +schomp chomp +defined defined +undef undef +study study +pos pos +preinc ++$i +i_preinc SKIP (set by optimizer) +predec --$i +i_predec SKIP (set by optimizer) +postinc $i++ +i_postinc SKIP (set by optimizer) +postdec $i-- +i_postdec SKIP (set by optimizer) +pow $x ** $y +multiply $x * $y +i_multiply SKIP (set by optimizer) +divide $x / $y +i_divide SKIP (set by optimizer) +modulo $x % $y +i_modulo SKIP (set by optimizer) +repeat $x x $y +add $x + $y +i_add SKIP (set by optimizer) +subtract $x - $y +i_subtract SKIP (set by optimizer) +concat $x . $y +stringify "$x" +left_shift $x << 1 +right_shift $x >> 1 +lt $x < $y +i_lt SKIP (set by optimizer) +gt $x > $y +i_gt SKIP (set by optimizer) +le $i <= $y +i_le SKIP (set by optimizer) +ge $i >= $y +i_ge SKIP (set by optimizer) +eq $x == $y +i_eq SKIP (set by optimizer) +ne $x != $y +i_ne SKIP (set by optimizer) +ncmp $i <=> $y +i_ncmp SKIP (set by optimizer) +slt $x lt $y +sgt $x gt $y +sle $x le $y +sge $x ge $y +seq $x eq $y +sne $x ne $y +scmp $x cmp $y +bit_and $x & $y +bit_xor $x ^ $y +bit_or $x | $y +negate -$x +i_negate SKIP (set by optimizer) +not !$x +complement ~$x +atan2 atan2 1 +sin sin 1 +cos cos 1 +rand rand +srand srand +exp exp 1 +log log 1 +sqrt sqrt 1 +int int +hex hex +oct oct +abs abs +length length +substr substr $x, 1 +vec vec +index index +rindex rindex +sprintf sprintf '%s', 'foo' +formline formline +ord ord +chr chr +crypt crypt 'foo','bar' +ucfirst ucfirst +lcfirst lcfirst +uc uc +lc lc +quotemeta quotemeta +rv2av @a +aelemfast SKIP (set by optimizer) +aelem $a[1] +aslice @a[1,2] +each each %h +values values %h +keys keys %h +delete delete $h{Key} +exists exists $h{Key} +rv2hv %h +helem $h{kEy} +hslice @h{kEy} +unpack unpack +pack pack +split split /foo/ +join join $a, @b +list @x = (1,2) +lslice SKIP @x[1,2] +anonlist [1,2] +anonhash { a => 1 } +splice splice @x, 1, 2, 3 +push push @x, $x +pop pop @x +shift shift @x +unshift unshift @x +sort sort @x +reverse reverse @x +grepstart grep { $_ eq 'foo' } @x +grepwhile SKIP grep { $_ eq 'foo' } @x +mapstart map $_ + 1, @foo +mapwhile SKIP (set by optimizer) +range SKIP +flip 1..2 +flop 1..2 +and $x && $y +or $x || $y +xor $x xor $y +cond_expr $x ? 1 : 0 +andassign $x &&= $y +orassign $x ||= $y +method Foo->$x() +entersub f() +leavesub sub f{} f() +leavesublv sub f:lvalue{return $x} f() +caller caller +warn warn +die die +reset reset +lineseq SKIP +nextstate SKIP +dbstate SKIP (needs debugger) +unstack while(0){} +enter SKIP +leave SKIP +scope SKIP +enteriter SKIP +iter SKIP +enterloop SKIP +leaveloop SKIP +return return +last last +next next +redo redo THIS +dump dump +goto goto THERE +exit exit 0 +open open FOO +close close FOO +pipe_op pipe FOO,BAR +fileno fileno FOO +umask umask 0755, 'foo' +binmode binmode FOO +tie tie +untie untie +tied tied +dbmopen dbmopen +dbmclose dbmclose +sselect SKIP (set by optimizer) +select select FOO +getc getc FOO +read read FOO +enterwrite write +leavewrite SKIP +prtf printf +print print +sysopen sysopen +sysseek sysseek +sysread sysread +syswrite syswrite +send send +recv recv +eof eof FOO +tell tell +seek seek FH, $pos, $whence +truncate truncate FOO, 42 +fcntl fcntl +ioctl ioctl +flock flock FOO, 1 +socket socket +sockpair socketpair +bind bind +connect connect +listen listen +accept accept +shutdown shutdown +gsockopt getsockopt +ssockopt setsockopt +getsockname getsockname +getpeername getpeername +lstat lstat FOO +stat stat FOO +ftrread -R +ftrwrite -W +ftrexec -X +fteread -r +ftewrite -w +fteexec -x +ftis -e +fteowned SKIP -O +ftrowned SKIP -o +ftzero -z +ftsize -s +ftmtime -M +ftatime -A +ftctime -C +ftsock -S +ftchr -c +ftblk -b +ftfile -f +ftdir -d +ftpipe -p +ftlink -l +ftsuid -u +ftsgid -g +ftsvtx -k +fttty -t +fttext -T +ftbinary -B +chdir chdir '/' +chown chown +chroot chroot +unlink unlink 'foo' +chmod chmod 511, 'foo' +utime utime +rename rename 'foo', 'bar' +link link 'foo', 'bar' +symlink symlink 'foo', 'bar' +readlink readlink 'foo' +mkdir mkdir 'foo' +rmdir rmdir 'foo' +open_dir opendir DIR +readdir readdir DIR +telldir telldir DIR +seekdir seekdir DIR, $pos +rewinddir rewinddir DIR +closedir closedir DIR +fork fork +wait wait +waitpid waitpid +system system +exec exec +kill kill +getppid getppid +getpgrp getpgrp +setpgrp setpgrp +getpriority getpriority +setpriority setpriority +time time +tms times +localtime localtime +gmtime gmtime +alarm alarm +sleep sleep 1 +shmget shmget +shmctl shmctl +shmread shmread +shmwrite shmwrite +msgget msgget +msgctl msgctl +msgsnd msgsnd +msgrcv msgrcv +semget semget +semctl semctl +semop semop +require use strict +dofile do 'file' +entereval eval "1+1" +leaveeval eval "1+1" +entertry SKIP eval { 1+1 } +leavetry SKIP eval { 1+1 } +ghbyname gethostbyname 'foo' +ghbyaddr gethostbyaddr 'foo' +ghostent gethostent +gnbyname getnetbyname 'foo' +gnbyaddr getnetbyaddr 'foo' +gnetent getnetent +gpbyname getprotobyname 'foo' +gpbynumber getprotobynumber 42 +gprotoent getprotoent +gsbyname getservbyname 'name', 'proto' +gsbyport getservbyport 'a', 'b' +gservent getservent +shostent sethostent +snetent setnetent +sprotoent setprotoent +sservent setservent +ehostent endhostent +enetent endnetent +eprotoent endprotoent +eservent endservent +gpwnam getpwnam +gpwuid getpwuid +gpwent getpwent +spwent setpwent +epwent endpwent +ggrnam getgrnam +ggrgid getgrgid +ggrent getgrent +sgrent setgrent +egrent endgrent +getlogin getlogin +syscall syscall +lock SKIP +threadsv SKIP +setstate SKIP +method_named $x->y() +dor $x // $y +dorassign $x //= $y +once SKIP {use feature 'state'; state $foo = 42;} +say SKIP {use feature 'say'; say "foo";} +smartmatch $x ~~ $y +aeach SKIP each @t +akeys SKIP keys @t +avalues SKIP values @t +custom SKIP (no way) diff --git a/dist/Safe/t/safeuniversal.t b/dist/Safe/t/safeuniversal.t new file mode 100644 index 0000000000..95867c5a1f --- /dev/null +++ b/dist/Safe/t/safeuniversal.t @@ -0,0 +1,46 @@ +#!perl + +BEGIN { + require Config; + import Config; + if ($Config{'extensions'} !~ /\bOpcode\b/) { + print "1..0\n"; + exit 0; + } +} + +use strict; +use warnings; +use Test::More; +use Safe; +plan(tests => 6); + +my $c = new Safe; +$c->permit(qw(require caller)); + +my $no_warn_redef = ($] != 5.008009) + ? q(no warnings 'redefine';) + : q($SIG{__WARN__}=sub{};); +my $r = $c->reval($no_warn_redef . q! + sub UNIVERSAL::isa { "pwned" } + (bless[],"Foo")->isa("Foo"); +!); + +is( $r, "pwned", "isa overriden in compartment" ); +is( (bless[],"Foo")->isa("Foo"), 1, "... but not outside" ); + +sub Foo::foo {} + +$r = $c->reval($no_warn_redef . q! + sub UNIVERSAL::can { "pwned" } + (bless[],"Foo")->can("foo"); +!); + +is( $r, "pwned", "can overriden in compartment" ); +is( (bless[],"Foo")->can("foo"), \&Foo::foo, "... but not outside" ); + +$r = $c->reval(q! + utf8::is_utf8("\x{100}"); +!); +is( $@, '', 'can call utf8::is_valid' ); +is( $r, 1, '... returns 1' ); |