4

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.

Community
  • 1
  • 1
user1322192
  • 118
  • 5

2 Answers2

4

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
luke14free
  • 2,529
  • 1
  • 17
  • 25
  • ahh, so the problem I have is, that there is no definition of the `wofz` function in `sympy.functions`. – user1322192 Apr 09 '12 at 17:16
  • You wanted to use `re` instead of `im` though, right? What does the `is_real` flag do? One could do something like `return Number(re(...)) + Number(im(...))*1j ` to get imaginary numbers in there. – user1322192 Apr 09 '12 at 17:26
  • 1
    @user1322192 You could always contribute one to SymPy, it's open-source after all. See [the development workflow](https://github.com/sympy/sympy/wiki/Development-workflow), it is written fairly clearly. – VPeric Apr 10 '12 at 08:19
  • 6
    For future reference: `sympify` is __not__ safe *at all*! It calls `eval` internally. – li.davidm Oct 14 '13 at 15:35
-2

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
Jon Olav Vik
  • 1,421
  • 2
  • 12
  • 20
  • `eval` is far too dangerous for this approach; see https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html – kaya3 Jan 13 '20 at 17:39