1 """Restricted execution facilities.
3 The class RExec exports methods r_exec(), r_eval(), r_execfile(), and
4 r_import(), which correspond roughly to the built-in operations
5 exec, eval(), execfile() and import, but executing the code in an
6 environment that only exposes those built-in operations that are
7 deemed safe. To this end, a modest collection of 'fake' modules is
8 created which mimics the standard modules by the same names. It is a
9 policy decision which built-in modules and operations are made
10 available; this module provides a reasonable default, but derived
11 classes can change the policies e.g. by overriding or extending class
12 variables like ok_builtin_modules or methods like make_sys().
15 - r_open should allow writing tmp dir
16 - r_exec etc. with explicit globals/locals? (Use rexec("exec ... in ...")?)
29 ok_file_methods
= ('fileno', 'flush', 'isatty', 'read', 'readline',
30 'readlines', 'seek', 'tell', 'write', 'writelines')
33 class FileWrapper(FileBase
):
35 # XXX This is just like a Bastion -- should use that!
37 def __init__(self
, f
):
39 for m
in self
.ok_file_methods
:
40 if not hasattr(self
, m
) and hasattr(f
, m
):
41 setattr(self
, m
, getattr(f
, m
))
49 return apply(getattr(self.mod, self.name).%s, args)
52 class FileDelegate(FileBase
):
54 def __init__(self
, mod
, name
):
58 for m
in FileBase
.ok_file_methods
+ ('close',):
59 exec TEMPLATE
% (m
, m
)
62 class RHooks(ihooks
.Hooks
):
64 def __init__(self
, *args
):
65 # Hacks to support both old and new interfaces:
66 # old interface was RHooks(rexec[, verbose])
67 # new interface is RHooks([verbose])
70 if args
and type(args
[-1]) == type(0):
73 if args
and hasattr(args
[0], '__class__'):
77 raise TypeError, "too many arguments"
78 ihooks
.Hooks
.__init
__(self
, verbose
)
81 def set_rexec(self
, rexec
):
82 # Called by RExec instance to complete initialization
85 def is_builtin(self
, name
):
86 return self
.rexec
.is_builtin(name
)
88 def init_builtin(self
, name
):
90 return self
.rexec
.copy_except(m
, ())
92 def init_frozen(self
, name
): raise SystemError, "don't use this"
93 def load_source(self
, *args
): raise SystemError, "don't use this"
94 def load_compiled(self
, *args
): raise SystemError, "don't use this"
95 def load_package(self
, *args
): raise SystemError, "don't use this"
97 def load_dynamic(self
, name
, filename
, file):
98 return self
.rexec
.load_dynamic(name
, filename
, file)
100 def add_module(self
, name
):
101 return self
.rexec
.add_module(name
)
103 def modules_dict(self
):
104 return self
.rexec
.modules
106 def default_path(self
):
107 return self
.rexec
.modules
['sys'].path
110 # XXX Backwards compatibility
111 RModuleLoader
= ihooks
.FancyModuleLoader
112 RModuleImporter
= ihooks
.ModuleImporter
115 class RExec(ihooks
._Verbose
):
117 """Restricted Execution environment."""
119 ok_path
= tuple(sys
.path
) # That's a policy decision
121 ok_builtin_modules
= ('audioop', 'array', 'binascii',
122 'cmath', 'errno', 'imageop',
123 'marshal', 'math', 'md5', 'operator',
124 'parser', 'regex', 'pcre', 'rotor', 'select',
125 'strop', 'struct', 'time')
127 ok_posix_names
= ('error', 'fstat', 'listdir', 'lstat', 'readlink',
128 'stat', 'times', 'uname', 'getpid', 'getppid',
129 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
131 ok_sys_names
= ('ps1', 'ps2', 'copyright', 'version',
132 'platform', 'exit', 'maxint')
134 nok_builtin_names
= ('open', 'reload', '__import__')
136 def __init__(self
, hooks
= None, verbose
= 0):
137 ihooks
._Verbose
.__init
__(self
, verbose
)
138 # XXX There's a circular reference here:
139 self
.hooks
= hooks
or RHooks(verbose
)
140 self
.hooks
.set_rexec(self
)
142 self
.ok_dynamic_modules
= self
.ok_builtin_modules
144 for mname
in self
.ok_builtin_modules
:
145 if mname
in sys
.builtin_module_names
:
147 self
.ok_builtin_modules
= tuple(list)
148 self
.set_trusted_path()
150 self
.make_initial_modules()
151 # make_sys must be last because it adds the already created
152 # modules to its builtin_module_names
154 self
.loader
= RModuleLoader(self
.hooks
, verbose
)
155 self
.importer
= RModuleImporter(self
.loader
, verbose
)
157 def set_trusted_path(self
):
158 # Set the path from which dynamic modules may be loaded.
159 # Those dynamic modules must also occur in ok_builtin_modules
160 self
.trusted_path
= filter(os
.path
.isabs
, sys
.path
)
162 def load_dynamic(self
, name
, filename
, file):
163 if name
not in self
.ok_dynamic_modules
:
164 raise ImportError, "untrusted dynamic module: %s" % name
165 if sys
.modules
.has_key(name
):
166 src
= sys
.modules
[name
]
169 src
= imp
.load_dynamic(name
, filename
, file)
170 dst
= self
.copy_except(src
, [])
173 def make_initial_modules(self
):
179 def is_builtin(self
, mname
):
180 return mname
in self
.ok_builtin_modules
182 # The make_* methods create specific built-in modules
184 def make_builtin(self
):
185 m
= self
.copy_except(__builtin__
, self
.nok_builtin_names
)
186 m
.__import
__ = self
.r_import
187 m
.reload = self
.r_reload
191 m
= self
.add_module('__main__')
193 def make_osname(self
):
195 src
= __import__(osname
)
196 dst
= self
.copy_only(src
, self
.ok_posix_names
)
198 for key
, value
in os
.environ
.items():
202 m
= self
.copy_only(sys
, self
.ok_sys_names
)
203 m
.modules
= self
.modules
204 m
.argv
= ['RESTRICTED']
205 m
.path
= map(None, self
.ok_path
)
206 m
.exc_info
= self
.r_exc_info
207 m
= self
.modules
['sys']
208 l
= self
.modules
.keys() + list(self
.ok_builtin_modules
)
210 m
.builtin_module_names
= tuple(l
)
212 # The copy_* methods copy existing modules with some changes
214 def copy_except(self
, src
, exceptions
):
215 dst
= self
.copy_none(src
)
216 for name
in dir(src
):
217 setattr(dst
, name
, getattr(src
, name
))
218 for name
in exceptions
:
221 except AttributeError:
225 def copy_only(self
, src
, names
):
226 dst
= self
.copy_none(src
)
229 value
= getattr(src
, name
)
230 except AttributeError:
232 setattr(dst
, name
, value
)
235 def copy_none(self
, src
):
236 m
= self
.add_module(src
.__name
__)
237 m
.__doc
__ = src
.__doc
__
240 # Add a module -- return an existing module or create one
242 def add_module(self
, mname
):
243 if self
.modules
.has_key(mname
):
244 return self
.modules
[mname
]
245 self
.modules
[mname
] = m
= self
.hooks
.new_module(mname
)
246 m
.__builtins
__ = self
.modules
['__builtin__']
249 # The r* methods are public interfaces
251 def r_exec(self
, code
):
252 m
= self
.add_module('__main__')
253 exec code
in m
.__dict
__
255 def r_eval(self
, code
):
256 m
= self
.add_module('__main__')
257 return eval(code
, m
.__dict
__)
259 def r_execfile(self
, file):
260 m
= self
.add_module('__main__')
261 return execfile(file, m
.__dict
__)
263 def r_import(self
, mname
, globals={}, locals={}, fromlist
=[]):
264 return self
.importer
.import_module(mname
, globals, locals, fromlist
)
266 def r_reload(self
, m
):
267 return self
.importer
.reload(m
)
269 def r_unload(self
, m
):
270 return self
.importer
.unload(m
)
272 # The s_* methods are similar but also swap std{in,out,err}
274 def make_delegate_files(self
):
275 s
= self
.modules
['sys']
276 self
.delegate_stdin
= FileDelegate(s
, 'stdin')
277 self
.delegate_stdout
= FileDelegate(s
, 'stdout')
278 self
.delegate_stderr
= FileDelegate(s
, 'stderr')
279 self
.restricted_stdin
= FileWrapper(sys
.stdin
)
280 self
.restricted_stdout
= FileWrapper(sys
.stdout
)
281 self
.restricted_stderr
= FileWrapper(sys
.stderr
)
284 if not hasattr(self
, 'save_stdin'):
286 if not hasattr(self
, 'delegate_stdin'):
287 self
.make_delegate_files()
288 s
= self
.modules
['sys']
289 s
.stdin
= self
.restricted_stdin
290 s
.stdout
= self
.restricted_stdout
291 s
.stderr
= self
.restricted_stderr
292 sys
.stdin
= self
.delegate_stdin
293 sys
.stdout
= self
.delegate_stdout
294 sys
.stderr
= self
.delegate_stderr
296 def reset_files(self
):
298 s
= self
.modules
['sys']
299 self
.restricted_stdin
= s
.stdin
300 self
.restricted_stdout
= s
.stdout
301 self
.restricted_stderr
= s
.stderr
304 def save_files(self
):
305 self
.save_stdin
= sys
.stdin
306 self
.save_stdout
= sys
.stdout
307 self
.save_stderr
= sys
.stderr
309 def restore_files(self
):
310 sys
.stdin
= self
.save_stdin
311 sys
.stdout
= self
.save_stdout
312 sys
.stderr
= self
.save_stderr
314 def s_apply(self
, func
, args
=(), kw
=None):
319 r
= apply(func
, args
, kw
)
321 r
= apply(func
, args
)
325 def s_exec(self
, *args
):
326 self
.s_apply(self
.r_exec
, args
)
328 def s_eval(self
, *args
):
329 self
.s_apply(self
.r_eval
, args
)
331 def s_execfile(self
, *args
):
332 self
.s_apply(self
.r_execfile
, args
)
334 def s_import(self
, *args
):
335 self
.s_apply(self
.r_import
, args
)
337 def s_reload(self
, *args
):
338 self
.s_apply(self
.r_reload
, args
)
340 def s_unload(self
, *args
):
341 self
.s_apply(self
.r_unload
, args
)
343 # Restricted open(...)
345 def r_open(self
, file, mode
='r', buf
=-1):
346 if mode
not in ('r', 'rb'):
347 raise IOError, "can't open files for writing in restricted mode"
348 return open(file, mode
, buf
)
350 # Restricted version of sys.exc_info()
352 def r_exc_info(self
):
353 ty
, va
, tr
= sys
.exc_info()
359 import sys
, getopt
, traceback
360 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'vt:')
368 r
= RExec(verbose
=verbose
)
370 r
.ok_builtin_modules
= r
.ok_builtin_modules
+ tuple(trusted
)
372 r
.modules
['sys'].argv
= args
373 r
.modules
['sys'].path
.insert(0, os
.path
.dirname(args
[0]))
375 r
.modules
['sys'].path
.insert(0, "")
377 if args
and args
[0] != '-':
381 print "%s: can't open file %s" % (sys
.argv
[0], `args
[0]`
)
384 print "*** RESTRICTED *** Python", sys
.version
389 s
= raw_input('>>> ')
393 if s
and s
[0] != '#':
395 c
= compile(s
, '<stdin>', 'single')
397 except SystemExit, n
:
400 traceback
.print_exc()
404 c
= compile(text
, fp
.name
, 'exec')
407 except SystemExit, n
:
410 traceback
.print_exc()
414 if __name__
== '__main__':