Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to safely use exec() in Python?

I have been tasked with building an application where an end user can have custom rules to evaluate whether a returned query results in a warning or alert (based on there own thresholds).

I've built a way for the user to template their logic. An example looks like this:

if (abs(<<21>>) >= abs(<<22>>)):
    retVal = <<21>>
else:
    retVal = <<22>>

The <<21>> and <<22>> parameters will be substituted with values found earlier in the program. Once all this substitution occurs I have a very simple if/else block (in this example) that looks like this stored in a variable (execCd):

if (abs(22.0) >= abs(-162.0)):
    retVal = 22.0
else:
    retVal = -162.0

This will exec() correctly. Now, how can I secure this? I've looked at this article: http://lybniz2.sourceforge.net/safeeval.html

My code ends up looking like this:

safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] 
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) 
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)

However, the exec fails when I have the second and third parameter with this exception - NameError: name 'retVal' is not defined

Some of the custom logic the end users have is extensive and much of this changes on a fairly regular basis. I don't want to maintain their custom logic and end users want to be able to test various warning/alert threshold logic quickly.

How can I secure this exec statement from unsafe (either intentional or unintentional) code?

like image 964
Andy Avatar asked Mar 12 '12 18:03

Andy


People also ask

Is exec safe in Python?

Python's built-in exec() function allows you to execute arbitrary Python code from a string or compiled code input. The exec() function can be handy when you need to run dynamically generated Python code, but it can be pretty dangerous if you use it carelessly.

What can I use instead of exec in Python?

The globals() and locals() functions returns the current global and local dictionary, respectively, which may be useful to pass around for use by eval() or execfile().

What is exec () in Python?

The exec() function executes the specified Python code. The exec() function accepts large blocks of code, unlike the eval() function which only accepts a single expression.


2 Answers

The only safe way to use eval or exec is not to use them.

You do not need to use exec. Instead of building a string to execute, parse it into objects, and use that to drive your code execution.

At its simplest, you can store functions in a dict, and use a string to select the function to call. If you're using python syntax, python provides all the utilities to parse itself, and you should use those.

like image 127
Marcin Avatar answered Oct 05 '22 21:10

Marcin


Your exec statement isn't adding retVal to your local environment, but to the safe_dict dictionary. So you can get it back from there:

execCd = """
if (abs(22.0) >= abs(-162.0)):
    retVal = 22.0
else:
    retVal = -162.0
"""

safe_list = ['math','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'de grees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] 
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ]) 
safe_dict['abs'] = abs
exec(execCd,{"__builtins__":None},safe_dict)
retVal = safe_dict["retVal"]
like image 36
David Robinson Avatar answered Oct 05 '22 22:10

David Robinson