http://qs321.pair.com?node_id=701225

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

I want to compile an anonymous subroutine within a Safe compartment, and then execute it from outside. This does not seem to work correctly. If I create a compartment that forbids file operations, and compile an anonymous subroutine that uses IO::File to open a file, it executes properly without complaint:
my $code =<<'END'; sub { my $a = ''; my $f = IO::File->new('/etc/passwd'); while (my $l = $f->getline) { $a .= $l } return $a; } END my $compartment = Safe->new(':default'); $safe = $compartment->reval($code); print $safe->();
If I replace IO::File->new with a direct call to open(), then the call is trapped, as desired. Is there a way to prevent anonymous subs defined within Safe compartments from invoking Class methods?

Replies are listed 'Best First'.
Re: How to use Safe to compile anonymous subs
by moritz (Cardinal) on Jul 30, 2008 at 19:01 UTC
    I don't know if that works for you, but you could try to move the evaluation into the subroutine:
    #!/usr/bin/perl use Safe; use IO::File; my $code =<<'END'; my $a = ''; my $f = IO::File->new('/etc/passwd'); while (my $l = $f->getline) { $a .= $l } return $a; END my $compartment = Safe->new; $safe = sub { $compartment->reval($code) || die $@}; print $safe->();

    This way the reval complains with Can't locate object method "new" via package "IO::File", which seems to me what you want (and a use is caught).

      Thanks, but using the suggested pattern, I don't understand how to pass arguments to the inner code:
      my $code = 'my $a = shift; warn $a'; my $compartment = Safe->new(':default'); $safe = sub { $compartment->reval($code) || die $@ }; $safe->(42);
      Is there any way to do this?
        You can by sharing variables. Somehow I didn't manage to do with simply sharing a variable and localizing it, but I'm quite sure it can be done.

        This is my workaround for now:

        #!/usr/bin/perl use Safe; use IO::File; our @args; my $code =<<'END'; my @args = pass_options(); return "Arguments: @args\n"; END my $compartment = Safe->new; sub pass_options { @args }; $compartment->share('&pass_options'); $safe = sub { local @args = @_; $compartment->reval($code) || die $@}; print $safe->(1, 2, 3); __END__ Arguments: 123

        This takes the sideway of sharing a sub that returns the arguments. Not ideal, but at least it works.

        This seems to work. The localised copy is shared into the compartments namespace.
        #!/usr/bin/perl use Safe; use IO::File; my $compartment = Safe->new; my $safe = sub { local @SHAREDARGS = @_; $compartment->share('@SHAREDARGS'); my $code = 'print "Got: ", join (", ", @SHAREDARGS),"\n" +'; $compartment->reval($code) || die "die $@" }; print $safe->(42, 47, 11 , "xyz"); __END__ Got: 42, 47, 11, xyz
        What you want is probably:

        my $code = 'sub { my $arg = shift; warn $arg; }';

        Update: Since the above looks alot like your original post, here is more.

        I've never used Safe but my doc's indicate that you need a namespace as the first arg to Safe->new.

        use Safe; my $code = 'sub { my $arg = shift; warn $arg; }'; my $safe = Safe->new(); my $ssub = $safe->reval( $code) || die ; $ssub->( "hello$/" ); &$ssub( "hello$/" );

        Be well,
        rir

Re: How to use Safe to compile anonymous subs
by zshzn (Hermit) on Jul 30, 2008 at 23:00 UTC
    I'm curious just what version of Safe you're using, given the usage problems.

    Can't use ":default" as root name at Safe.pl line 10

    Anyways, it doesn't work like that. When you reval($code), it analyzes the code path it has to build. Look at the optree for the sub you generate. Using a subroutine is part of :base_core, so no problem there.

    If you remove the sub{} wrapper in $code you can get closer to understanding your problems.