[IMP] work-in-progress: initial version of safe_eval implementation -- still need fixing

bzr revid: odo@openerp.com-20100503234953-qgbk64bmir95jvzd
This commit is contained in:
Olivier Dony
2010-05-04 00:49:53 +01:00
parent b220dc04dd
commit b020dc8363
20 changed files with 85 additions and 71 deletions
+3
View File
@@ -28,6 +28,7 @@ import zipimport
import osv
import tools
import tools.osutil
from tools.safe_eval import safe_eval as eval
import pooler
@@ -422,6 +423,8 @@ def register_class(m):
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
import traceback
traceback.print_exc()
global loaded
if m in loaded:
+1
View File
@@ -75,6 +75,7 @@
],
'test': [
'test/base_test.xml',
#'test/base_test.yml'
'test/test_context.xml',
'bug_lp541545.xml',
],
+2 -1
View File
@@ -20,6 +20,7 @@
##############################################################################
from osv import fields,osv
from tools.safe_eval import safe_eval as eval
import tools
import time
from tools.config import config
@@ -564,7 +565,7 @@ class actions_server(osv.osv):
'object':obj,
'obj': obj,
}
exec action.code in localdict
eval(action.code, localdict, "exec")
if 'action' in localdict:
return localdict['action']
else:
+1
View File
@@ -25,6 +25,7 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
import netsvc
import tools
from tools.safe_eval import safe_eval as eval
import pooler
from osv import fields, osv
+1
View File
@@ -24,6 +24,7 @@ from osv.orm import browse_null
import ir
import report.custom
from tools.translate import _
from tools.safe_eval import safe_eval as eval
import netsvc
class report_custom(osv.osv):
+1
View File
@@ -22,6 +22,7 @@
from osv import fields,osv
import time
import tools
from tools.safe_eval import safe_eval as eval
class ir_rule(osv.osv):
_name = 'ir.rule'
+1
View File
@@ -22,6 +22,7 @@
from osv import fields,osv
from lxml import etree
from tools import graph
from tools.safe_eval import safe_eval as eval
import tools
import netsvc
import os
+1
View File
@@ -24,6 +24,7 @@ from osv import fields
import os
import tools
from tools.translate import _
from tools.safe_eval import safe_eval as eval
class multi_company_default(osv.osv):
"""
+1
View File
@@ -22,6 +22,7 @@
from osv import fields, osv
from locale import localeconv
import tools
from tools.safe_eval import safe_eval as eval
class lang(osv.osv):
_name = "res.lang"
+5 -5
View File
@@ -56,7 +56,7 @@ from tools.translate import _
import fields
import tools
from tools.safe_eval import safe_eval as eval
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
@@ -353,8 +353,8 @@ def get_pg_type(f):
f_type = ('int4', 'INTEGER')
else:
f_type = ('varchar', 'VARCHAR(%d)' % f_size)
elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
t = eval('fields.'+(f._type))
elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
t = eval('fields.'+(f._type), globals())
f_type = (type_dict[t], type_dict[t])
elif isinstance(f, fields.function) and f._type == 'float':
if f.digits:
@@ -1951,9 +1951,9 @@ class orm_memory(orm_template):
f = True
for arg in result:
if arg[1] =='=':
val =eval('data[arg[0]]'+'==' +' arg[2]')
val =eval('data[arg[0]]'+'==' +' arg[2]', locals())
elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
val =eval('data[arg[0]]'+arg[1] +' arg[2]')
val =eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
elif arg[1] in ['ilike']:
if str(data[arg[0]]).find(str(arg[2]))!=-1:
val= True
+1
View File
@@ -24,6 +24,7 @@ import time
import netsvc
import tools
from tools.safe_eval import safe_eval as eval
import print_xml
import render
from interface import report_int
+2 -1
View File
@@ -23,6 +23,7 @@ import os,types
from lxml import etree
import netsvc
import tools
from tools.safe_eval import safe_eval
import print_fnc
import copy
from osv.orm import browse_null, browse_record
@@ -111,7 +112,7 @@ class document(object):
#Pinky: Why not this ? eval(expr, browser) ?
# name = browser.name
# data_dict = browser._data[self.get_value(browser, 'id')]
return eval(expr, {}, {'obj': record})
return safe_eval(expr, {}, {'obj': record})
def parse_node(self, node, parent, browser, datas=None):
attrs = self.node_attrs_get(node)
+1
View File
@@ -22,6 +22,7 @@
from report.interface import report_int
import pooler
import tools
from tools.safe_eval import safe_eval as eval
from lxml import etree
from report import render
import locale
+1
View File
@@ -34,6 +34,7 @@ import os
from lxml import etree
import base64
from reportlab.platypus.doctemplate import ActionFlowable
from tools.safe_eval import safe_eval as eval
encoding = 'utf-8'
+1
View File
@@ -42,6 +42,7 @@ from lxml import etree
import copy
import locale
import traceback, sys
from tools.safe_eval import safe_eval as eval
_regex = re.compile('\[\[(.+?)\]\]')
+2 -1
View File
@@ -23,6 +23,7 @@ import re
import reportlab
import reportlab.lib.units
from lxml import etree
from tools.safe_eval import safe_eval as eval
_regex = re.compile('\[\[(.+?)\]\]')
@@ -128,4 +129,4 @@ def attr_get(node, attrs, dict={}):
res[key] = unit_get(node.get(key))
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+1
View File
@@ -35,6 +35,7 @@ import netsvc
import osv
import pooler
from config import config
from tools.safe_eval import safe_eval as eval
from yaml_import import convert_yaml_import
class ConvertError(Exception):
+57 -62
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2004-2010 OpenERP s.a. (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
@@ -14,15 +13,15 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
safe_eval module - methods intended to provide more restricted alternatives to
evaluate simple and/or untrusted code.
Methods in this module are typically used as alternatives to eval() to parse
Methods in this module are typically used as alternatives to eval() to parse
OpenERP domain strings, conditions and expressions, mostly based on locals
condition/math builtins.
"""
@@ -30,17 +29,19 @@ condition/math builtins.
# Module partially ripped from/inspired by several different sources:
# - http://code.activestate.com/recipes/286134/
# - safe_eval in lp:~xrg/openobject-server/optimize-5.0
# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
# - python 2.6's ast.literal_eval
from opcode import HAVE_ARGUMENT, opmap, opname
_CONST_CODES = set(opmap[x] for x in [
__all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
_CONST_OPCODES = set(opmap[x] for x in [
'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP',
'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
_EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [
_EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
@@ -48,10 +49,10 @@ _EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [
'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
'BINARY_OR'] if x in opmap))
_SAFE_CODES = _EXPR_CODES.union(set(opmap[x] for x in [
_SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE',
'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE',
'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE',
] if x in opmap))
@@ -66,25 +67,25 @@ def _get_opcodes(codeobj):
"""
i = 0
opcodes = []
s = codeobj.co_code
while i < len(s):
code = ord(s[i])
byte_codes = codeobj.co_code
while i < len(byte_codes):
code = ord(byte_codes[i])
opcodes.append(code)
if code >= HAVE_ARGUMENT:
i += 3
else:
i += 1
return opcodes
return opcodes
def test_expr(expr, allowed_codes):
"""test_expr(expression) -> code_object
def test_expr(expr, allowed_codes, mode="eval"):
"""test_expr(expression, allowed_codes[, mode]) -> code_object
Test that the expression contains only the allowed opcodes.
If the expression is valid and contains only allowed codes,
return the compiled code object. Otherwise raise a ValueError.
"""
try:
code_obj = compile(expr, "", "eval")
code_obj = compile(expr, "", mode)
except:
raise ValueError("%s is not a valid expression" % expr)
for code in _get_opcodes(code_obj):
@@ -111,7 +112,7 @@ def const_eval(expr):
...
ValueError: opcode BINARY_ADD not allowed
"""
c = test_expr(expr, _CONST_CODES)
c = test_expr(expr, _CONST_OPCODES)
return eval(c)
def expr_eval(expr):
@@ -120,7 +121,7 @@ def expr_eval(expr):
Restricted Python expression evaluation
Evaluates a string that contains an expression that only
uses Python constants. This can be used to e.g. evaluate
uses Python constants. This can be used to e.g. evaluate
a numerical expression from an untrusted source.
>>> expr_eval("1+2")
@@ -132,7 +133,7 @@ def expr_eval(expr):
...
ValueError: opcode LOAD_NAME not allowed
"""
c = test_expr(expr, _EXPR_CODES)
c = test_expr(expr, _EXPR_OPCODES)
return eval(c)
@@ -143,31 +144,31 @@ try:
# first, try importing directly
from ast import literal_eval
except ImportError:
from _ast import *
import _ast as ast
def _convert(node):
if isinstance(node, Str):
if isinstance(node, ast.Str):
return node.s
elif isinstance(node, Num):
elif isinstance(node, ast.Num):
return node.n
elif isinstance(node, Tuple):
elif isinstance(node, ast.Tuple):
return tuple(map(_convert, node.elts))
elif isinstance(node, List):
elif isinstance(node, ast.List):
return list(map(_convert, node.elts))
elif isinstance(node, Dict):
elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
elif isinstance(node, Name):
elif isinstance(node, ast.Name):
if node.id in SAFE_CONSTANTS:
return SAFE_CONSTANTS[node.id]
raise ValueError('malformed or disallowed expression')
def parse(expr, filename='<unknown>', mode='eval'):
"""parse(source[, filename], mode]] -> code object
Parse an expression into an AST node.
Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
"""
return compile(expr, filename, mode, PyCF_ONLY_AST)
return compile(expr, filename, mode, ast.PyCF_ONLY_AST)
def literal_eval(node_or_string):
"""literal_eval(expression) -> value
@@ -175,10 +176,10 @@ except ImportError:
expression. The string or node provided may only consist of the
following Python literal structures: strings, numbers, tuples,
lists, dicts, booleans, and None.
>>> literal_eval('[1,True,"spam"]')
[1, True, 'spam']
>>> literal_eval('1+3')
Traceback (most recent call last):
...
@@ -186,23 +187,23 @@ except ImportError:
"""
if isinstance(node_or_string, basestring):
node_or_string = parse(node_or_string)
if isinstance(node_or_string, Expression):
if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
return _convert(node_or_string)
def safe_eval(expr, context = None):
"""safe_eval(expression, context) -> value
def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval"):
"""safe_eval(expression[, globals[, locals[, mode]]]) -> value
System-restricted Python expression evaluation
Evaluates a string that contains an expression that mostly
uses Python constants, arithmetic expressions and the
use of the objects provided in context.
This can be used to e.g. evaluate
an OpenERP domain expression from an untrusted expr.
objects directly provided in context.
This can be used to e.g. evaluate
an OpenERP domain expression from an untrusted source.
>>> safe_eval("__import__('sys').modules")
Traceback (most recent call last):
@@ -212,30 +213,24 @@ def safe_eval(expr, context = None):
"""
if '__subclasses__' in expr:
raise ValueError('expression not allowed (__subclasses__)')
code_obj = compile(expr, '', 'eval')
byte_codes = code_obj.co_code
i = 0
while i < len(byte_codes):
op_code = ord(byte_codes[i])
if op_code not in _SAFE_CODES:
raise ValueError('opcode %byte_codes not allowed' % dis.opname[op_code])
if op_code >= HAVE_ARGUMENT:
i += 3
else:
i += 1
return eval(code_obj, {'__builtins__': {
'True': True,
'False': False,
'None': None,
'str': str,
'globals': locals,
'locals': locals,
'bool': bool,
'dict': dict,
'list': list,
'tuple': tuple,
}
}, context)
if globals_dict is None:
globals_dict = {}
globals_dict.update(
__builtins__ = {
'True': True,
'False': False,
'None': None,
'str': str,
'globals': locals,
'locals': locals,
'bool': bool,
'dict': dict,
'list': list,
'tuple': tuple,
}
)
return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
+1 -1
View File
@@ -7,8 +7,8 @@ import pooler
import netsvc
import misc
from config import config
from tools.safe_eval import safe_eval as eval
import yaml_tag
import yaml
logger_channel = 'tests'
+1
View File
@@ -23,6 +23,7 @@ import sys
import netsvc
import osv as base
import pooler
from tools.safe_eval import safe_eval as eval
class Env(dict):
def __init__(self, cr, uid, model, ids):