1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 # Copyright (C) 2004-2010 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
39 __all__ = ['test_expr', 'safe_eval', 'const_eval']
41 # The time module is usually already provided in the safe_eval environment
42 # but some code, e.g. datetime.datetime.now() (Windows/Python 2.5.2, bug
43 # lp:703841), does import time.
44 _ALLOWED_MODULES = ['_strptime', 'time']
46 _CONST_OPCODES = set(opmap[x] for x in [
47 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'DUP_TOPX',
48 'POP_BLOCK','SETUP_LOOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
49 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
51 _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
52 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
53 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
54 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
55 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR',
56 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
57 'BINARY_OR', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
58 'INPLACE_DIVIDE', 'INPLACE_REMAINDER', 'INPLACE_POWER',
59 'INPLACE_LEFTSHIFT', 'INPLACE_RIGHTSHIFT', 'INPLACE_AND',
60 'INPLACE_XOR','INPLACE_OR'
63 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
64 'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
65 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
66 'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
67 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
68 # New in Python 2.7 - http://bugs.python.org/issue4715 :
69 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
70 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'LOAD_FAST',
74 _logger = logging.getLogger(__name__)
76 def _get_opcodes(codeobj):
77 """_get_opcodes(codeobj) -> [opcodes]
79 Extract the actual opcodes as a list from a code object
81 >>> c = compile("[1 + 2, (1,2)]", "", "eval")
83 [100, 100, 23, 100, 100, 102, 103, 83]
86 byte_codes = codeobj.co_code
87 while i < len(byte_codes):
88 code = ord(byte_codes[i])
91 if code >= HAVE_ARGUMENT:
96 def assert_no_dunder_name(code_obj, expr):
97 """ assert_no_dunder_name(code_obj, expr) -> None
99 Asserts that the code object does not refer to any "dunder name"
100 (__$name__), so that safe_eval prevents access to any internal-ish Python
101 attribute or method (both are loaded via LOAD_ATTR which uses a name, not a
104 Checks that no such name exists in the provided code object (co_names).
106 :param code_obj: code object to name-validate
107 :type code_obj: CodeType
108 :param str expr: expression corresponding to the code object, for debugging
110 :raises NameError: in case a forbidden name (containing two underscores)
111 is found in ``code_obj``
113 .. note:: actually forbids every name containing 2 underscores
115 for name in code_obj.co_names:
117 raise NameError('Access to forbidden name %r (%r)' % (name, expr))
119 def assert_valid_codeobj(allowed_codes, code_obj, expr):
120 """ Asserts that the provided code object validates against the bytecode
121 and name constraints.
123 Recursively validates the code objects stored in its co_consts in case
124 lambdas are being created/used (lambdas generate their own separated code
125 objects and don't live in the root one)
127 :param allowed_codes: list of permissible bytecode instructions
128 :type allowed_codes: set(int)
129 :param code_obj: code object to name-validate
130 :type code_obj: CodeType
131 :param str expr: expression corresponding to the code object, for debugging
133 :raises ValueError: in case of forbidden bytecode in ``code_obj``
134 :raises NameError: in case a forbidden name (containing two underscores)
135 is found in ``code_obj``
137 assert_no_dunder_name(code_obj, expr)
138 for opcode in _get_opcodes(code_obj):
139 if opcode not in allowed_codes:
141 "opcode %s not allowed (%r)" % (opname[opcode], expr))
142 for const in code_obj.co_consts:
143 if isinstance(const, CodeType):
144 assert_valid_codeobj(allowed_codes, const, 'lambda')
146 def test_expr(expr, allowed_codes, mode="eval"):
147 """test_expr(expression, allowed_codes[, mode]) -> code_object
149 Test that the expression contains only the allowed opcodes.
150 If the expression is valid and contains only allowed codes,
151 return the compiled code object.
152 Otherwise raise a ValueError, a Syntax Error or TypeError accordingly.
156 # eval() does not like leading/trailing whitespace
158 code_obj = compile(expr, "", mode)
159 except (SyntaxError, TypeError, ValueError):
160 _logger.debug('Invalid eval expression', exc_info=True)
163 _logger.debug('Disallowed or invalid eval expression', exc_info=True)
164 raise ValueError("%s is not a valid expression" % expr)
166 assert_valid_codeobj(allowed_codes, code_obj, expr)
170 def const_eval(expr):
171 """const_eval(expression) -> value
173 Safe Python constant evaluation
175 Evaluates a string that contains an expression describing
176 a Python constant. Strings that are not valid Python expressions
177 or that contain other code besides the constant raise ValueError.
181 >>> const_eval("[1,2, (3,4), {'foo':'bar'}]")
182 [1, 2, (3, 4), {'foo': 'bar'}]
183 >>> const_eval("1+2")
184 Traceback (most recent call last):
186 ValueError: opcode BINARY_ADD not allowed
188 c = test_expr(expr, _CONST_OPCODES)
192 """expr_eval(expression) -> value
194 Restricted Python expression evaluation
196 Evaluates a string that contains an expression that only
197 uses Python constants. This can be used to e.g. evaluate
198 a numerical expression from an untrusted source.
202 >>> expr_eval("[1,2]*2")
204 >>> expr_eval("__import__('sys').modules")
205 Traceback (most recent call last):
207 ValueError: opcode LOAD_NAME not allowed
209 c = test_expr(expr, _EXPR_OPCODES)
212 def _import(name, globals=None, locals=None, fromlist=None, level=-1):
219 if name in _ALLOWED_MODULES:
220 return __import__(name, globals, locals, level)
221 raise ImportError(name)
223 def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False):
224 """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
226 System-restricted Python expression evaluation
228 Evaluates a string that contains an expression that mostly
229 uses Python constants, arithmetic expressions and the
230 objects directly provided in context.
232 This can be used to e.g. evaluate
233 an OpenERP domain expression from an untrusted source.
235 :throws TypeError: If the expression provided is a code object
236 :throws SyntaxError: If the expression provided is not valid Python
237 :throws NameError: If the expression provided accesses forbidden names
238 :throws ValueError: If the expression provided uses forbidden bytecode
240 if isinstance(expr, CodeType):
241 raise TypeError("safe_eval does not allow direct evaluation of code objects.")
243 if globals_dict is None:
246 # prevent altering the globals/locals from within the sandbox
249 # isinstance() does not work below, we want *exactly* the dict class
250 if (globals_dict is not None and type(globals_dict) is not dict) \
251 or (locals_dict is not None and type(locals_dict) is not dict):
253 "Looks like you are trying to pass a dynamic environment, "
254 "you should probably pass nocopy=True to safe_eval().")
256 globals_dict = dict(globals_dict)
257 if locals_dict is not None:
258 locals_dict = dict(locals_dict)
262 '__import__': _import,
284 return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
286 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: