http://www.perlmonks.org?node_id=1018212

gideondsouza has asked for the wisdom of the Perl Monks concerning the following question:

So I have a little 'executor.pl' script that looks like this:

## executor.pl use Safe; if($ARGV[0]) { my $code = $ARGV[0]; my $compartment = new Safe; $compartment->deny(qw(:base_io... MANY OP CODES....)); $compartment->permit(qw(print say pack unpack require caller)); ##I want to pass something into the STDIN for the $code reval- +ed my $result = $compartment->reval($code); if ($@) { print "Unsafe code detected: $@"; } }

And I do this to invoke executor.pl :

open(FILE,"perl executor.pl '$code' 2>&1 |") my @output=<FILE>; my $resp = join('',@output);

Question: Is there a way I can pass some argument into the code being reval-ed as STDIN ?. So essentially I can reval some code and then pass it some known arg to its STDIN

Please don't worry, this isn't any production code. I'm just doing this as a learning/curiosity exercise

Replies are listed 'Best First'.
Re: Passing argument into STDIN inside safe.pm reval
by 7stud (Deacon) on Feb 12, 2013 at 02:10 UTC

    Actually, Safe has a share() method, which you should be able to use on STDIN--but it doesn't work for me:

    use strict; use warnings; use 5.012; use Safe; if($ARGV[0]) { my $code = $ARGV[0]; my $compartment = Safe->new; $compartment->share('*STDIN'); $compartment->permit_only(qw{ print readline }); $compartment->reval($code); if ($@) { print "Unsafe code detected: $@"; } } --output:-- $ perl 2.pl ' print "Enter some stdin: "; my $line = readline *STDIN; print "From STDIN: $line"; ' Unsafe code detected: 'private value' trapped by operation mask at (ev +al 5) line 1.

    I can't even get share() to work on a simple scalar variable:

    use strict; use warnings; use 5.012; use Safe; our $x = 10; if($ARGV[0]) { my $code = $ARGV[0]; my $compartment = Safe->new; $compartment->share('$x'); $compartment->permit_only(qw{ print readline}); $compartment->reval($code); if ($@) { print "Unsafe code detected: $@"; } } --output:-- $ perl 2.pl 'print "$x\n";' Unsafe code detected: 'private value' trapped by operation mask at (ev +al 5) line 1.

    I'm really sick of that non-informative error message!

      Hey thanks a ton for your replies and effort!

      I guess running the code in a virtual environment is the only way out!

      I'm trying to sort of understand and mimic www.perltuts.com. The author says this on his blog :

      Evaluator is a simple daemon that runs on a virtual machine, it accepts the code, builds the Perl package and evals it capturing the output with Capture::Tiny. It does some timeout checking and fork limiting of course. The virtual machine (qemu Debian image) allows you to run a real Perl code on a real machine. This can be used when writing advanced tutorials including IO, networking, forks etc (I tried Safe but wanted more freedom). It is reset every hour from a snaphot.

      So I' playing about on Amazon EC2 and was thinking of running a new instance that will just evaluate code and return the result. I'm thinking I can use Time::Out to timeout operations, but (1) how do I do a fork limit? (2) Am I pretty safe if I allow any perl code to run but restart my VM every hour or something?

      I've tried to replace permit_only with permit in your example, and it worked for me.

      Still need to figure out what should I place into permit_only so that it allows to eval '1' (which is obviously safe).

        That's confounding. I printed out the masks for both permit(qw(print readline)) and permit_only(qw(print readline)) using printf():
        printf "%vd \n", $compartment->mask;

        ...and they are clearly different, which means a compartment allows some default operations that permit_only() must be erasing. Rereading the Safe docs, there is a set of default operations bundled under :default that are allowed. You have to check the Opcode docs to see which operations that :default includes. I'm not sure which ones I erased with permit_only() that are needed to reval() my sample code--but in any case it looks like if you want to use permit_only(), then you probably need a pretty good grasp of perl internals.

        I did a bunch of trial and error, and to get permit_only() to work on my sample code, I need all these:

        $compartment->permit_only(qw( print readline :base_core :base_mem :base_orig) );
        The :default bundle of operations that a compartment allows consists of a bevy of other bundles:

        :base_core 
        :base_mem 
        :base_loop 
        :base_orig 
        :base_thread
        

        See the Opcode docs for which operations are included in each of those bundles.

Re: Passing argument into STDIN inside safe.pm reval
by 7stud (Deacon) on Feb 11, 2013 at 19:22 UTC

    I don't think a string has a STDIN filehandle. After you eval the string, it becomes part of your program. Your program has a STDIN filehandle.

    Although, you can perform IO on a string (using open).

    So essentially I can reval some code and then pass it some known arg to its STDIN

    How about using root to insert the input?

Re: Passing argument into STDIN inside safe.pm reval
by 7stud (Deacon) on Feb 12, 2013 at 00:12 UTC

    You can insert your program's STDIN filehandle into the compartment like this:

    use strict; use warnings; use 5.012; use Safe; if($ARGV[0]) { my $code = $ARGV[0]; my $compartment = Safe->new; my $namespace_name = $compartment->root(); { no strict; *{$namespace_name . "::STDIN"} = *main::STDIN; } $compartment->deny_only(qw(chdir)); #<----CHECK THIS*** $compartment->reval($code); if ($@) { print "Unsafe code detected: $@"; } } --output:-- $ $ perl 2.pl ' print "Enter some stdin: "; my $line = <STDIN>; print "From STDIN: $line"; ' Enter some stdin: hello From STDIN: hello

    But I can't figure out how to permit_only() what is necessary for the line input operator(<$infile>) to work. I tried permit_only(qw{ print <> }), which produced the error:

    Unknown operator prefix "<>"

    And because perlop says:

    <FILEHANDLE> may also be spelled readline(*FILEHANDLE). 

    I tried permit_only(qw{ print readline }), but that produced this error:

    Unsafe code detected: 'private value' trapped by operation mask at (ev +al 5) line 1. use strict; use warnings; use 5.012; use Safe; if($ARGV[0]) { my $code = $ARGV[0]; my $compartment = Safe->new; my $namespace_name = $compartment->root(); { no strict; *{$namespace_name . "::STDIN"} = *main::STDIN; } $compartment->permit_only(qw(print readline)); $compartment->reval($code); if ($@) { print "Unsafe code detected: $@"; } } --output:-- $ perl 2.pl ' print "Enter some stdin: "; my $line = readline *STDIN; print "From STDIN: $line"; ' Unsafe code detected: 'private value' trapped by operation mask at (ev +al 5) line 1.