Perl's eval built-in form provides programs with access to Perl's internal parser and evaluator. It may be called with a scalar argument (that is, a string) or with an expression that evaluates to a scalar argument, or it may be called with a block.

The eval built-in has one important role. It traps any errors that would otherwise be fatal to the program and stores them in the $@ package variable. This role means that eval is critical to proper exception handling. If the code being evaluated is itself invalid (perhaps because it contains a syntax error), again, the error does not cause program termination, but is instead trapped by eval and saved in the $@ variable.

When invoked with a block, the Perl parser compiles the block argument at the same time that it compiles the rest of the code, before it begins execution. Consequently, syntax errors or other compile-time errors are reported during compilation, before the program has begun executing.

However, when eval invoked with a string argument, the argument is parsed and compiled only when the eval form actually executes, at run-time. Consequently, a syntax error is reported only when the eval form is actually executed. Furthermore, the argument gets compiled every time that eval is executed. Consequently, the block form of eval has better performance and reliability than the string form of eval.

But, these issues aside, the string form of eval also allows it to execute any code. If an attacker can control the value of a scalar argument to eval, the attacker can cause any arbitrary code to be executed with the privileges of the running program. Therefore, the string form of eval must not be used.

Noncompliant Code Example

Perl normally signals a fatal error if division by zero occurs. The eval built-in can be used to prevent such an error from terminating the program. This noncompliant code example uses the string-based eval to reduce the severity of a division-by-zero error to a mere warning.

my $a = $ARGV[0];
my $b = $ARGV[1];
my $answer = 0;
eval qq{ \$answer = $a / $b };
carp $@ if $@;
print "The quotient is $answer\n";

As shown below, when given normal input, this program behaves as expected:

% ./divide.pl 18 3
The quotient is 6
% 

It also gracefully handles division by zero:

% ./divide.pl 18 0
Illegal division by zero at (eval 1) line 1.
The quotient is 0
% 

But it also allows the caller to invoke arbitrary Perl code:

% ./divide.pl 18 '6 ; print "Surprise!\n"'
Surprise!
The quotient is 3
% 

Compliant Solution

This compliant solution uses the block-based form of eval. In addition to foiling any attempts to evaluate untrusted code, this form of eval parses its argument at compile time rather than run-time. Performance is improved, and any syntax errors with the code are still caught and reported.

my $a = $ARGV[0];
my $b = $ARGV[1];
my $answer = 0;
eval { $answer = $a / $b; };
carp $@ if $@;
print "The quotient is $answer\n";

As shown below, this code behaves as in the previous example, but the division operation causes a warning when given non-numeric (malicious) input and ignores the malicious code.

% ./divide.pl 18 3
The quotient is 6
% ./divide.pl 18 0
Illegal division by zero at ./divide.pl line 12.
The quotient is 0
% ./divide.pl 18 '6 ; print "Surprise!\n"'
Argument "6 ; print "Surprise!\\n"" isn't numeric in division (/) at ./divide.pl line 12.
The quotient is 3
% 

Noncompliant Code Example

This noncompliant code example attempts to load a module that is specified by a variable.

my $module = "Foo::Bar";
# ...
require $module;

This code does not behave properly, as require does no pathname interpolation when loading a module specified by a variable, and so it attempts to search the filesystem for a file named Foo::Bar.

Noncompliant Code Example

The perlfunc manpage recommends using the string-based form of eval when importing a module with require or use, where the name of the module may be stored in a variable. To wit:

If EXPR is a bareword, the require assumes a ".pm" extension and replaces "::" with "/" in the filename for you, to make it easy to load standard modules. ... In other words, if you try this:

require Foo::Bar; # a splendid bareword

the require function will actually look for the "Foo/Bar.pm" file in the directories specified in the @INC array.

But if you try this:

$class = 'Foo::Bar';
require $class; # $class is not a bareword
# or
require "Foo::Bar"; # not a bareword because of the ""

the require function will look for the "Foo::Bar" file in the @INC array and will complain about not finding "Foo::Bar" there. In this case you can do:

eval "require $class";

This noncompliant code example uses eval to load a module specified by a variable.

my $module = "Foo::Bar";
# ...
eval "require $module";

While this code properly searches the include paths for the file Foo/Bar.pm, it also suffers from command injection, as shown in the following transcript:

% perl -e 'eval "use $ARGV[0]";'  'Foo::Bar'
package Foo::Bar loaded
% perl -e 'eval "use $ARGV[0]";'  'Foo::Bar ; print "Surprise!\n"'
package Foo::Bar loaded
Surprise!
% 

Compliant Solution

This compliant solution uses the built-in Module::Load package which provides the ability to import modules specified by a variable. The load function prevents command injection.

use Module::Load;
 
my $module = "Foo::Bar";
# ...
load $module;

Exceptions

IDS35-PL-EX0: This rule specifically forbids passing a scalar string or expression to eval. It does not forbid passing a block to eval.

eval  $x ;    # string-based, noncompliant, evaluates value of $x
eval "$x";    # string-based, noncompliant, evaluates value of $x
eval '$x';    # string-based, noncompliant, returns value of $x (unevaluated)
eval {$x};    #  block-based,    compliant, returns value of $x (unevaluated)

Risk Assessment

Using string-based eval can lead to arbitrary code execution.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

IDS35-PL

high

likely

medium

P18

L1

Automated Detection

Tool

Diagnostic

Perl::Critic

BuiltinFunctions::ProhibitStringyEval

Taint mode

Insecure dependency in eval

Bibliography

 

 


4 Comments

  1. Under IDS35:EX0, it says this code is evaluated as block and therefore acceptable:

    eval '$x';

    That is not true.  It's a string.  The string just isn't being interpolated.  But that doesn't mean it can't be abused.

     

    1. I fixed this exception. eval '$x' is indeed a string, not a block. I don't think it can be used by an attacker to run arbitrary code. On the other paw, it is a useless feature, as it returns the value $x unevaluated. So it should still be noncompliant, as only the block form of eval should be used.

  2. However, it appears to be the only way to load modules that are not specified by barewords

    There are modules to provide this kind of functionality out of the box (e.g. Module::Load, Class::Load and probably many others that I don't know about) or it is possible to use Module::Runtime (via module_notional_filename) to transform the module/class name to a string that is OK with require.

    1. Thanks for the info. I've replaced that exception with code samples showing the bad way to load files, with a good way that uses Module::Load.