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 ...")?)
30 ok_file_methods
= ('fileno', 'flush', 'isatty', 'read', 'readline',
31 'readlines', 'seek', 'tell', 'write', 'writelines')
34 class FileWrapper(FileBase
):
36 # XXX This is just like a Bastion -- should use that!
38 def __init__(self
, f
):
40 for m
in self
.ok_file_methods
:
41 if not hasattr(self
, m
) and hasattr(f
, m
):
42 setattr(self
, m
, getattr(f
, m
))
50 return apply(getattr(self.mod, self.name).%s, args)
53 class FileDelegate(FileBase
):
55 def __init__(self
, mod
, name
):
59 for m
in FileBase
.ok_file_methods
+ ('close',):
60 exec TEMPLATE
% (m
, m
)
63 class RHooks(ihooks
.Hooks
):
65 def __init__(self
, *args
):
66 # Hacks to support both old and new interfaces:
67 # old interface was RHooks(rexec[, verbose])
68 # new interface is RHooks([verbose])
71 if args
and type(args
[-1]) == type(0):
74 if args
and hasattr(args
[0], '__class__'):
78 raise TypeError, "too many arguments"
79 ihooks
.Hooks
.__init
__(self
, verbose
)
82 def set_rexec(self
, rexec
):
83 # Called by RExec instance to complete initialization
86 def is_builtin(self
, name
):
87 return self
.rexec
.is_builtin(name
)
89 def init_builtin(self
, name
):
91 return self
.rexec
.copy_except(m
, ())
93 def init_frozen(self
, name
): raise SystemError, "don't use this"
94 def load_source(self
, *args
): raise SystemError, "don't use this"
95 def load_compiled(self
, *args
): raise SystemError, "don't use this"
96 def load_package(self
, *args
): raise SystemError, "don't use this"
98 def load_dynamic(self
, name
, filename
, file):
99 return self
.rexec
.load_dynamic(name
, filename
, file)
101 def add_module(self
, name
):
102 return self
.rexec
.add_module(name
)
104 def modules_dict(self
):
105 return self
.rexec
.modules
107 def default_path(self
):
108 return self
.rexec
.modules
['sys'].path
111 # XXX Backwards compatibility
112 RModuleLoader
= ihooks
.FancyModuleLoader
113 RModuleImporter
= ihooks
.ModuleImporter
116 class RExec(ihooks
._Verbose
):
118 """Restricted Execution environment."""
120 ok_path
= tuple(sys
.path
) # That's a policy decision
122 ok_builtin_modules
= ('audioop', 'array', 'binascii',
123 'cmath', 'errno', 'imageop',
124 'marshal', 'math', 'md5', 'operator',
125 'parser', 'regex', 'pcre', 'rotor', 'select',
126 'strop', 'struct', 'time')
128 ok_posix_names
= ('error', 'fstat', 'listdir', 'lstat', 'readlink',
129 'stat', 'times', 'uname', 'getpid', 'getppid',
130 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')
132 ok_sys_names
= ('ps1', 'ps2', 'copyright', 'version',
133 'platform', 'exit', 'maxint')
135 nok_builtin_names
= ('open', 'reload', '__import__')
137 def __init__(self
, hooks
= None, verbose
= 0):
138 ihooks
._Verbose
.__init
__(self
, verbose
)
139 # XXX There's a circular reference here:
140 self
.hooks
= hooks
or RHooks(verbose
)
141 self
.hooks
.set_rexec(self
)
143 self
.ok_dynamic_modules
= self
.ok_builtin_modules
145 for mname
in self
.ok_builtin_modules
:
146 if mname
in sys
.builtin_module_names
:
148 self
.ok_builtin_modules
= tuple(list)
149 self
.set_trusted_path()
151 self
.make_initial_modules()
152 # make_sys must be last because it adds the already created
153 # modules to its builtin_module_names
155 self
.loader
= RModuleLoader(self
.hooks
, verbose
)
156 self
.importer
= RModuleImporter(self
.loader
, verbose
)
157 # but since re isn't normally built-in, we can add it at the end;
158 # we need the imported to be set before this can be imported.
161 def set_trusted_path(self
):
162 # Set the path from which dynamic modules may be loaded.
163 # Those dynamic modules must also occur in ok_builtin_modules
164 self
.trusted_path
= filter(os
.path
.isabs
, sys
.path
)
166 def load_dynamic(self
, name
, filename
, file):
167 if name
not in self
.ok_dynamic_modules
:
168 raise ImportError, "untrusted dynamic module: %s" % name
169 if sys
.modules
.has_key(name
):
170 src
= sys
.modules
[name
]
173 src
= imp
.load_dynamic(name
, filename
, file)
174 dst
= self
.copy_except(src
, [])
177 def make_initial_modules(self
):
183 def is_builtin(self
, mname
):
184 return mname
in self
.ok_builtin_modules
186 # The make_* methods create specific built-in modules
188 def make_builtin(self
):
189 m
= self
.copy_except(__builtin__
, self
.nok_builtin_names
)
190 m
.__import
__ = self
.r_import
191 m
.reload = self
.r_reload
195 m
= self
.add_module('__main__')
197 def make_osname(self
):
199 src
= __import__(osname
)
200 dst
= self
.copy_only(src
, self
.ok_posix_names
)
202 for key
, value
in os
.environ
.items():
206 dst
= self
.add_module("re")
207 src
= self
.r_import("pre")
208 for name
in dir(src
):
209 if name
!= "__name__":
210 setattr(dst
, name
, getattr(src
, name
))
213 m
= self
.copy_only(sys
, self
.ok_sys_names
)
214 m
.modules
= self
.modules
215 m
.argv
= ['RESTRICTED']
216 m
.path
= map(None, self
.ok_path
)
217 m
.exc_info
= self
.r_exc_info
218 m
= self
.modules
['sys']
219 l
= self
.modules
.keys() + list(self
.ok_builtin_modules
)
221 m
.builtin_module_names
= tuple(l
)
223 # The copy_* methods copy existing modules with some changes
225 def copy_except(self
, src
, exceptions
):
226 dst
= self
.copy_none(src
)
227 for name
in dir(src
):
228 setattr(dst
, name
, getattr(src
, name
))
229 for name
in exceptions
:
232 except AttributeError:
236 def copy_only(self
, src
, names
):
237 dst
= self
.copy_none(src
)
240 value
= getattr(src
, name
)
241 except AttributeError:
243 setattr(dst
, name
, value
)
246 def copy_none(self
, src
):
247 m
= self
.add_module(src
.__name
__)
248 m
.__doc
__ = src
.__doc
__
251 # Add a module -- return an existing module or create one
253 def add_module(self
, mname
):
254 if self
.modules
.has_key(mname
):
255 return self
.modules
[mname
]
256 self
.modules
[mname
] = m
= self
.hooks
.new_module(mname
)
257 m
.__builtins
__ = self
.modules
['__builtin__']
260 # The r* methods are public interfaces
262 def r_exec(self
, code
):
263 m
= self
.add_module('__main__')
264 exec code
in m
.__dict
__
266 def r_eval(self
, code
):
267 m
= self
.add_module('__main__')
268 return eval(code
, m
.__dict
__)
270 def r_execfile(self
, file):
271 m
= self
.add_module('__main__')
272 return execfile(file, m
.__dict
__)
274 def r_import(self
, mname
, globals={}, locals={}, fromlist
=[]):
275 return self
.importer
.import_module(mname
, globals, locals, fromlist
)
277 def r_reload(self
, m
):
278 return self
.importer
.reload(m
)
280 def r_unload(self
, m
):
281 return self
.importer
.unload(m
)
283 # The s_* methods are similar but also swap std{in,out,err}
285 def make_delegate_files(self
):
286 s
= self
.modules
['sys']
287 self
.delegate_stdin
= FileDelegate(s
, 'stdin')
288 self
.delegate_stdout
= FileDelegate(s
, 'stdout')
289 self
.delegate_stderr
= FileDelegate(s
, 'stderr')
290 self
.restricted_stdin
= FileWrapper(sys
.stdin
)
291 self
.restricted_stdout
= FileWrapper(sys
.stdout
)
292 self
.restricted_stderr
= FileWrapper(sys
.stderr
)
295 if not hasattr(self
, 'save_stdin'):
297 if not hasattr(self
, 'delegate_stdin'):
298 self
.make_delegate_files()
299 s
= self
.modules
['sys']
300 s
.stdin
= self
.restricted_stdin
301 s
.stdout
= self
.restricted_stdout
302 s
.stderr
= self
.restricted_stderr
303 sys
.stdin
= self
.delegate_stdin
304 sys
.stdout
= self
.delegate_stdout
305 sys
.stderr
= self
.delegate_stderr
307 def reset_files(self
):
309 s
= self
.modules
['sys']
310 self
.restricted_stdin
= s
.stdin
311 self
.restricted_stdout
= s
.stdout
312 self
.restricted_stderr
= s
.stderr
315 def save_files(self
):
316 self
.save_stdin
= sys
.stdin
317 self
.save_stdout
= sys
.stdout
318 self
.save_stderr
= sys
.stderr
320 def restore_files(self
):
321 sys
.stdin
= self
.save_stdin
322 sys
.stdout
= self
.save_stdout
323 sys
.stderr
= self
.save_stderr
325 def s_apply(self
, func
, args
=(), kw
=None):
330 r
= apply(func
, args
, kw
)
332 r
= apply(func
, args
)
336 def s_exec(self
, *args
):
337 self
.s_apply(self
.r_exec
, args
)
339 def s_eval(self
, *args
):
340 self
.s_apply(self
.r_eval
, args
)
342 def s_execfile(self
, *args
):
343 self
.s_apply(self
.r_execfile
, args
)
345 def s_import(self
, *args
):
346 self
.s_apply(self
.r_import
, args
)
348 def s_reload(self
, *args
):
349 self
.s_apply(self
.r_reload
, args
)
351 def s_unload(self
, *args
):
352 self
.s_apply(self
.r_unload
, args
)
354 # Restricted open(...)
356 def r_open(self
, file, mode
='r', buf
=-1):
357 if mode
not in ('r', 'rb'):
358 raise IOError, "can't open files for writing in restricted mode"
359 return open(file, mode
, buf
)
361 # Restricted version of sys.exc_info()
363 def r_exc_info(self
):
364 ty
, va
, tr
= sys
.exc_info()
370 import sys
, getopt
, traceback
371 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'vt:')
379 r
= RExec(verbose
=verbose
)
381 r
.ok_builtin_modules
= r
.ok_builtin_modules
+ tuple(trusted
)
383 r
.modules
['sys'].argv
= args
384 r
.modules
['sys'].path
.insert(0, os
.path
.dirname(args
[0]))
386 r
.modules
['sys'].path
.insert(0, "")
388 if args
and args
[0] != '-':
392 print "%s: can't open file %s" % (sys
.argv
[0], `args
[0]`
)
395 print "*** RESTRICTED *** Python", sys
.version
400 s
= raw_input('>>> ')
404 if s
and s
[0] != '#':
406 c
= compile(s
, '<stdin>', 'single')
408 except SystemExit, n
:
411 traceback
.print_exc()
415 c
= compile(text
, fp
.name
, 'exec')
418 except SystemExit, n
:
421 traceback
.print_exc()
425 if __name__
== '__main__':