user1322192
user1322192

Reputation: 118

Python: Using sympy.sympify to perform a safe eval() on mathematical functions

I am writing a program where users need to be able to use self written mathematical functions containing functions from numpy and scipy, eg. scipy.special.wofz().

These functions will be stored in files and imported as strings by the program. I looked around and saw, that eval() or exec() are not a safe way to do it. eg. here.

The security issue would be that good users load a file from evil users who get access to the good users system.

I was thinking about doing something like this:

#!/bin/python
from scipy.special import *
from numpy import *
import sympy

# Define variable a
vars = {"a":1}
# This is the string I get from a file
string = "wofz(a)"

parsed_string = sympy.sympify(string)
parsed_string.evalf(subs=vars)

However, this does not work. It only returns:

wofz(a)

wofz(a) is not evaluated. Is this even supposed to work that way?

I had another idea: So I thought, once this mathematical function got through sympify, it should be safe. I could just simply do something like this:

globals = {wofz:wofz}
eval(str(parsed_string), vars, globals)

which works fine and returns:

(0.36787944117144233+0.60715770584139372j)

Is that safe? I know it's not nice.

Please help.

Upvotes: 4

Views: 2235

Answers (2)

Jon Olav Vik
Jon Olav Vik

Reputation: 1461

If the string is the only untrusted information, I think the following should be safe:

To use eval() with a restricted vocabulary, pass it a second argument that is a dictionary of allowed names, where __builtins__ is defined to something harmless (see http://docs.python.org/library/functions.html#eval).

>>> import numpy as np
>>> d = dict(linspace=np.linspace, range=range, __builtins__=None)
>>> eval("str(1), 2+2")
('1', 4)
>>> eval("str(1)", d)
Traceback (most recent call last)
NameError: name 'str' is not defined

>>> eval("{'a': linspace(0, 0.5, 6)}, range(2)", d)
({'a': array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5])}, [0, 1])
>>> eval("linspace.__dict__", d)
Traceback (most recent call last)
RuntimeError: function attributes not accessible in restricted mode

Upvotes: -2

luke14free
luke14free

Reputation: 2539

Use sympy, it's a way safer option.

import sympy
from sympy.core.function import Function
from sympy.core import S
from sympy import sympify
from sympy.functions import im
from scipy.special import wofz

class Wofz(Function):
    is_real = True
    @classmethod
    def _should_evalf(csl,arg):
        return True
    def as_base_exp(cls):
        return cls,S.One

    def _eval_evalf(cls, prec):
        return sympy.numbers.Number(im(wofz(float(cls.args[0]))))

print sympify("Wofz(2)",{'Wofz':Wofz}).evalf()

Output (you'll have to handle the imaginary part somehow):

0.340026217066065

Upvotes: 4

Related Questions