twenty four merry days of Perl Feed

Is your code… Safe?

Safe - 2012-12-07

Today we'll have a little chat about the Safe module. What does it do, how does it work and when to use it?

The Purpose

Safe's purpose is to provide a restricted eval() function to perl, which will function as the regular eval(STRING) built-in, except in two important points:

  • This restricted eval() will refuse to compile certain built-ins (the list being customizable, so for example you can prevent compilation of all filesystem access functions, or just some, or none)

  • Moreover, it will compile code in a separate, quarantined namespace, where the data of your main program will not be accessible.

The Example

Quick, a code example, to see what it looks like:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 

 

use v5.14.0;
use warnings;
use Safe;

# create a Safe compartment
my $compartment = Safe->new;
$compartment->deny(qw(:base_loop));

$_ = 2;

# First try
my $result = $compartment->reval( q{ 40 + $_ } );
defined $result or die "Safe compilation error: $@";
say $result;

# Second try
$result = $compartment->reval( q{ $_++ for 1..40 } );
defined $result or die "Safe compilation error: $@";
say $result;

 

In this example we start by creating a fairly restricted Safe compartment, where not only the default set of built-ins is forbidden, but also all loop built-ins (for, while, etc.)

The result of the first try will be 42, since the addition and the variable fetching are still permitted operations. The second try will fail with the error message:

  'foreach loop entry' trapped by operation mask

The Basics

There are a couple of points worth noting even in such a small example.

First, we use the deny() method to deny more operations than the default set. Safe provides deny(), permit(), deny_only() and permit_only() to customize this set more finely; you can pass to those methods lists of individual op names (as known to the perl internals) or handy predefined bundles (like :base_loop). Those bundles are listed in the Opcode man page.

Secondly, just like eval(), a compilation error reported by the reval() method will be in the $@ variable.

Thirdly, we used $_ in the string we've been reval-ing. But the namespaces were supposed to be separated? Did I lie? Of course not, I did not lie, and this isn't a bug in Safe either. The fact is that $_ is one of the few variables that are shared by default between the program's global namespace and the compartment's one.

To verify this, change the "first try" lines to this, and observe how the $result will now be 40 instead of 42:


1: 
2: 

 

our $x = 2; # or "my $x = 2", does not matter
my $result = $compartment->reval( q{ 40 + $x } );

 

Of course, the list of variables you want to share can be changed, too:


1: 
2: 
3: 
4: 

 

our $x = 2;
$compartment->share('$x');
my $result = $compartment->reval( q{ 40 + $x } );
say $result; # will now say '42'

 

You can specify that you want to share functions as well, so the reval-ed code will be able to call them. By default, Safe will share the *_ glob (so, $_, @_, etc.) and a quite long list of built-in functions that are often called behind the scenes (like &UNIVERSAL::isa or &utf8::downgrade). Use the Source for the full list, which is perl-version-dependent.

The Details

So what exactly is this new namespace that Safe is masking main:: under? The root() method allows you to access it, as in the following example:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 

 

use v5.14.0;
use warnings;
use Safe;

my $compartment = Safe->new;
my $root = $compartment->root;

say "root namespace name: $root";

my $result = $compartment->reval( q{ $x = 42 } );
say "result = $result";

no strict 'refs';
say "safe's \$x : ", ${ $root.'::x' }; # 42 too !

 

On My Machine the root() method will return the string Safe::Root0 in this example. So consequently the variable introduced as $x in the evaluated code will be known as $Safe::Root0::x in the outer program.

Also, you'll notice that $x has been compiled by Safe without fussing about Global symbol "$x" requires explicit package name: the ambient pragmas are not passed to the reval(). If you want to enforce strictures in the compilation phase, you have to call reval() with a second boolean parameter set to true:

    my $result = $compartment->reval( q{ our $x = 42 }, 1 );

The Lengths

As you can imagine a popular game is to get Safe execute code that it shouldn't. Safe goes to some lengths to avoid this. Here are two of those, just to excite your imagination:

Destructor destruction. Before exiting from a reval(), Safe will check whether any class gained new methods, and if so, it will delete every DESTROY and AUTOLOAD it finds under its root namespace. This is to prevent destructors or functions created inside the department from being run outside of it (for example if the reval() returns to its caller a newly crafted object).

Closure closing. Safe provides a method wrap_code_ref() that will take a code reference as an argument, and return a version of it wrapped in a reval() (that's the short story -- check the source for the gory details). Subsequently, reval() will check its return values for any code references (recursively, if it returns hash or array references), and will invoke wrap_code_ref() on any code reference found there before passing them to you.

The Caveats

TL;DR: No silver bullet, etc.

Longer version (but really it's just common sense): the name of the Safe module is misleading. If should have been called Restricted::Sortof or something. It has its uses, but making evaluating foreign code safer is not one of these. Even in a very restricted compartment, it's possible to introduce a pathologically slow regular expression, or a pathologically long loop, or a pathologically big string. Any use of Safe for serious security purposes is basically misguided.

See Also

Gravatar Image This article contributed by: Rafaël Garcia-Suarez <rgs@consttype.org>