1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 # Copyright (C) 2004-2014 OpenERP s.a. (<http://www.openerp.com>).
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 ##############################################################################
21 safe_eval module - methods intended to provide more restricted alternatives to
22 evaluate simple and/or untrusted code.
24 Methods in this module are typically used as alternatives to eval() to parse
25 OpenERP domain strings, conditions and expressions, mostly based on locals
26 condition/math builtins.
29 # Module partially ripped from/inspired by several different sources:
30 # - http://code.activestate.com/recipes/286134/
31 # - safe_eval in lp:~xrg/openobject-server/optimize-5.0
32 # - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
34 from opcode import HAVE_ARGUMENT, opmap, opname
35 from types import CodeType
38 from .misc import ustr
42 __all__ = ['test_expr', 'safe_eval', 'const_eval']
44 # The time module is usually already provided in the safe_eval environment
45 # but some code, e.g. datetime.datetime.now() (Windows/Python 2.5.2, bug
46 # lp:703841), does import time.
47 _ALLOWED_MODULES = ['_strptime', 'time']
49 _CONST_OPCODES = set(opmap[x] for x in [
50 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'DUP_TOPX',
51 'POP_BLOCK','SETUP_LOOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
52 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR', 'STORE_MAP'] if x in opmap)
54 _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
55 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
56 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
57 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
58 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR',
59 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
60 'BINARY_OR', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
61 'INPLACE_DIVIDE', 'INPLACE_REMAINDER', 'INPLACE_POWER',
62 'INPLACE_LEFTSHIFT', 'INPLACE_RIGHTSHIFT', 'INPLACE_AND',
63 'INPLACE_XOR','INPLACE_OR'
66 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
67 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
68 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
69 'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
70 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', 'BREAK_LOOP',
71 'CONTINUE_LOOP', 'RAISE_VARARGS',
72 # New in Python 2.7 - http://bugs.python.org/issue4715 :
73 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
74 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'LOAD_FAST',
75 'LOAD_GLOBAL', # Only allows access to restricted globals
78 _logger = logging.getLogger(__name__)
80 def _get_opcodes(codeobj):
81 """_get_opcodes(codeobj) -> [opcodes]
83 Extract the actual opcodes as a list from a code object
85 >>> c = compile("[1 + 2, (1,2)]", "", "eval")
87 [100, 100, 23, 100, 100, 102, 103, 83]
90 byte_codes = codeobj.co_code
91 while i < len(byte_codes):
92 code = ord(byte_codes[i])
95 if code >= HAVE_ARGUMENT:
100 def assert_no_dunder_name(code_obj, expr):
101 """ assert_no_dunder_name(code_obj, expr) -> None
103 Asserts that the code object does not refer to any "dunder name"
104 (__$name__), so that safe_eval prevents access to any internal-ish Python
105 attribute or method (both are loaded via LOAD_ATTR which uses a name, not a
108 Checks that no such name exists in the provided code object (co_names).
110 :param code_obj: code object to name-validate
111 :type code_obj: CodeType
112 :param str expr: expression corresponding to the code object, for debugging
114 :raises NameError: in case a forbidden name (containing two underscores)
115 is found in ``code_obj``
117 .. note:: actually forbids every name containing 2 underscores
119 for name in code_obj.co_names:
121 raise NameError('Access to forbidden name %r (%r)' % (name, expr))
123 def assert_valid_codeobj(allowed_codes, code_obj, expr):
124 """ Asserts that the provided code object validates against the bytecode
125 and name constraints.
127 Recursively validates the code objects stored in its co_consts in case
128 lambdas are being created/used (lambdas generate their own separated code
129 objects and don't live in the root one)
131 :param allowed_codes: list of permissible bytecode instructions
132 :type allowed_codes: set(int)
133 :param code_obj: code object to name-validate
134 :type code_obj: CodeType
135 :param str expr: expression corresponding to the code object, for debugging
137 :raises ValueError: in case of forbidden bytecode in ``code_obj``
138 :raises NameError: in case a forbidden name (containing two underscores)
139 is found in ``code_obj``
141 assert_no_dunder_name(code_obj, expr)
142 for opcode in _get_opcodes(code_obj):
143 if opcode not in allowed_codes:
145 "opcode %s not allowed (%r)" % (opname[opcode], expr))
146 for const in code_obj.co_consts:
147 if isinstance(const, CodeType):
148 assert_valid_codeobj(allowed_codes, const, 'lambda')
150 def test_expr(expr, allowed_codes, mode="eval"):
151 """test_expr(expression, allowed_codes[, mode]) -> code_object
153 Test that the expression contains only the allowed opcodes.
154 If the expression is valid and contains only allowed codes,
155 return the compiled code object.
156 Otherwise raise a ValueError, a Syntax Error or TypeError accordingly.
160 # eval() does not like leading/trailing whitespace
162 code_obj = compile(expr, "", mode)
163 except (SyntaxError, TypeError, ValueError):
167 exc_info = sys.exc_info()
168 raise ValueError, '"%s" while compiling\n%r' % (ustr(e), expr), exc_info[2]
169 assert_valid_codeobj(allowed_codes, code_obj, expr)
173 def const_eval(expr):
174 """const_eval(expression) -> value
176 Safe Python constant evaluation
178 Evaluates a string that contains an expression describing
179 a Python constant. Strings that are not valid Python expressions
180 or that contain other code besides the constant raise ValueError.
184 >>> const_eval("[1,2, (3,4), {'foo':'bar'}]")
185 [1, 2, (3, 4), {'foo': 'bar'}]
186 >>> const_eval("1+2")
187 Traceback (most recent call last):
189 ValueError: opcode BINARY_ADD not allowed
191 c = test_expr(expr, _CONST_OPCODES)
195 """expr_eval(expression) -> value
197 Restricted Python expression evaluation
199 Evaluates a string that contains an expression that only
200 uses Python constants. This can be used to e.g. evaluate
201 a numerical expression from an untrusted source.
205 >>> expr_eval("[1,2]*2")
207 >>> expr_eval("__import__('sys').modules")
208 Traceback (most recent call last):
210 ValueError: opcode LOAD_NAME not allowed
212 c = test_expr(expr, _EXPR_OPCODES)
215 def _import(name, globals=None, locals=None, fromlist=None, level=-1):
222 if name in _ALLOWED_MODULES:
223 return __import__(name, globals, locals, level)
224 raise ImportError(name)
226 def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False, locals_builtins=False):
227 """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
229 System-restricted Python expression evaluation
231 Evaluates a string that contains an expression that mostly
232 uses Python constants, arithmetic expressions and the
233 objects directly provided in context.
235 This can be used to e.g. evaluate
236 an OpenERP domain expression from an untrusted source.
238 :throws TypeError: If the expression provided is a code object
239 :throws SyntaxError: If the expression provided is not valid Python
240 :throws NameError: If the expression provided accesses forbidden names
241 :throws ValueError: If the expression provided uses forbidden bytecode
243 if isinstance(expr, CodeType):
244 raise TypeError("safe_eval does not allow direct evaluation of code objects.")
246 if globals_dict is None:
249 # prevent altering the globals/locals from within the sandbox
252 # isinstance() does not work below, we want *exactly* the dict class
253 if (globals_dict is not None and type(globals_dict) is not dict) \
254 or (locals_dict is not None and type(locals_dict) is not dict):
256 "Looks like you are trying to pass a dynamic environment, "
257 "you should probably pass nocopy=True to safe_eval().")
259 globals_dict = dict(globals_dict)
260 if locals_dict is not None:
261 locals_dict = dict(locals_dict)
265 '__import__': _import,
277 'enumerate': enumerate,
298 'isinstance': isinstance,
305 if locals_dict is None:
307 locals_dict.update(globals_dict.get('__builtins__'))
308 c = test_expr(expr, _SAFE_OPCODES, mode=mode)
310 return eval(c, globals_dict, locals_dict)
311 except openerp.osv.orm.except_orm:
313 except openerp.exceptions.Warning:
315 except openerp.exceptions.RedirectWarning:
317 except openerp.exceptions.AccessDenied:
319 except openerp.exceptions.AccessError:
323 exc_info = sys.exc_info()
324 raise ValueError, '"%s" while evaluating\n%r' % (ustr(e), expr), exc_info[2]
326 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: