1# Copyright (C) 2013 Jeremy S. Sanders 2# Email: Jeremy Sanders <jeremy@jeremysanders.net> 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License along 15# with this program; if not, write to the Free Software Foundation, Inc., 16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17############################################################################### 18 19""" 20'Safe' python code evaluation 21 22The idea is to examine the compiled ast tree and chack for invalid 23entries 24""" 25 26# don't do this as it affects imported files 27# from __future__ import division 28import ast 29 30from ..compat import cstr, cpy3, cbuiltins 31from .. import qtall as qt 32 33def _(text, disambiguation=None, context='SafeEval'): 34 """Translate text.""" 35 return qt.QCoreApplication.translate(context, text, disambiguation) 36 37# blacklist of nodes 38forbidden_nodes = set(( 39 ast.Global, 40 ast.Import, 41 ast.ImportFrom, 42 )) 43 44if hasattr(ast, 'Exec'): 45 forbidden_nodes.add(ast.Exec) 46 47# whitelist of allowed builtins 48allowed_builtins = frozenset(( 49 'ArithmeticError', 50 'AttributeError', 51 'BaseException', 52 'Exception', 53 'False', 54 'FloatingPointError', 55 'IndexError', 56 'KeyError', 57 'NameError', 58 'None', 59 'OverflowError', 60 'RuntimeError', 61 'StandardError', 62 'StopIteration', 63 'True', 64 'TypeError', 65 'ValueError', 66 'ZeroDivisionError', 67 'abs', 68 'all', 69 'any', 70 'apply', 71 'basestring', 72 'bin', 73 'bool', 74 'bytes', 75 'callable', 76 'chr', 77 'cmp', 78 'complex', 79 'dict', 80 'divmod', 81 'enumerate', 82 'filter', 83 'float', 84 'format', 85 'frozenset', 86 'hash', 87 'hex', 88 'id', 89 'int', 90 'isinstance', 91 'issubclass', 92 'iter', 93 'len', 94 'list', 95 'long', 96 'map', 97 'max', 98 'min', 99 'next', 100 'object', 101 'oct', 102 'ord', 103 'pow', 104 'print', 105 'property', 106 'range', 107 'reduce', 108 'repr', 109 'reversed', 110 'round', 111 'set', 112 'slice', 113 'sorted', 114 'str', 115 'sum', 116 'tuple', 117 'unichr', 118 'unicode', 119 'xrange', 120 'zip' 121 )) 122 123numpy_forbidden = set(( 124 'frombuffer', 125 'fromfile', 126 'getbuffer', 127 'getbufsize', 128 'load', 129 'loads', 130 'loadtxt', 131 'ndfromtxt', 132 'newbuffer', 133 'pkgload', 134 'recfromcsv', 135 'recfromtxt', 136 'save', 137 'savetxt', 138 'savez', 139 'savez_compressed', 140 'setbufsize', 141 'seterr', 142 'seterrcall', 143 'seterrobj', 144 )) 145 146# blacklist using whitelist above 147forbidden_builtins = ( set(cbuiltins.__dict__.keys()) - allowed_builtins | 148 numpy_forbidden ) 149 150class SafeEvalException(Exception): 151 """Raised by safety errors in code.""" 152 pass 153 154class CheckNodeVisitor(ast.NodeVisitor): 155 """Visit ast nodes to look for unsafe entries.""" 156 157 def generic_visit(self, node): 158 if type(node) in forbidden_nodes: 159 raise SafeEvalException(_("%s not safe") % type(node)) 160 ast.NodeVisitor.generic_visit(self, node) 161 162 def visit_Name(self, name): 163 if name.id[:2] == '__' or name.id in forbidden_builtins: 164 raise SafeEvalException( 165 _('Access to special names not allowed: "%s"') % name.id) 166 self.generic_visit(name) 167 168 def visit_Call(self, call): 169 if not hasattr(call.func, 'id'): 170 raise SafeEvalException(_("Function has no identifier")) 171 172 if call.func.id[:2] == '__' or call.func.id in forbidden_builtins: 173 raise SafeEvalException( 174 _('Access to special functions not allowed: "%s"') % 175 call.func.id) 176 self.generic_visit(call) 177 178 def visit_Attribute(self, attr): 179 if not hasattr(attr, 'attr'): 180 raise SafeEvalException(_('Access denied to attribute')) 181 if ( attr.attr[:2] == '__' or attr.attr[:5] == 'func_' or 182 attr.attr[:3] == 'im_' or attr.attr[:3] == 'tb_' ): 183 raise SafeEvalException( 184 _('Access to special attributes not allowed: "%s"') % 185 attr.attr) 186 self.generic_visit(attr) 187 188def compileChecked(code, mode='eval', filename='<string>', 189 ignoresecurity=False): 190 """Compile code, checking for security errors. 191 192 Returns a compiled code object. 193 mode = 'exec' or 'eval' 194 """ 195 196 # python2 needs filename encoded 197 if not cpy3: 198 filename = filename.encode('utf-8') 199 200 try: 201 tree = ast.parse(code, filename, mode) 202 except Exception as e: 203 raise ValueError(_('Unable to parse file: %s') % cstr(e)) 204 205 if not ignoresecurity: 206 visitor = CheckNodeVisitor() 207 visitor.visit(tree) 208 209 compiled = compile(tree, filename, mode) 210 211 return compiled 212