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 'sha', '_sre', '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', 'file', '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
)
158 def set_trusted_path(self
):
159 # Set the path from which dynamic modules may be loaded.
160 # Those dynamic modules must also occur in ok_builtin_modules
161 self
.trusted_path
= filter(os
.path
.isabs
, sys
.path
)
163 def load_dynamic(self
, name
, filename
, file):
164 if name
not in self
.ok_dynamic_modules
:
165 raise ImportError, "untrusted dynamic module: %s" % name
166 if sys
.modules
.has_key(name
):
167 src
= sys
.modules
[name
]
170 src
= imp
.load_dynamic(name
, filename
, file)
171 dst
= self
.copy_except(src
, [])
174 def make_initial_modules(self
):
180 def is_builtin(self
, mname
):
181 return mname
in self
.ok_builtin_modules
183 # The make_* methods create specific built-in modules
185 def make_builtin(self
):
186 m
= self
.copy_except(__builtin__
, self
.nok_builtin_names
)
187 m
.__import
__ = self
.r_import
188 m
.reload = self
.r_reload
189 m
.open = m
.file = self
.r_open
192 m
= self
.add_module('__main__')
194 def make_osname(self
):
196 src
= __import__(osname
)
197 dst
= self
.copy_only(src
, self
.ok_posix_names
)
199 for key
, value
in os
.environ
.items():
203 m
= self
.copy_only(sys
, self
.ok_sys_names
)
204 m
.modules
= self
.modules
205 m
.argv
= ['RESTRICTED']
206 m
.path
= map(None, self
.ok_path
)
207 m
.exc_info
= self
.r_exc_info
208 m
= self
.modules
['sys']
209 l
= self
.modules
.keys() + list(self
.ok_builtin_modules
)
211 m
.builtin_module_names
= tuple(l
)
213 # The copy_* methods copy existing modules with some changes
215 def copy_except(self
, src
, exceptions
):
216 dst
= self
.copy_none(src
)
217 for name
in dir(src
):
218 setattr(dst
, name
, getattr(src
, name
))
219 for name
in exceptions
:
222 except AttributeError:
226 def copy_only(self
, src
, names
):
227 dst
= self
.copy_none(src
)
230 value
= getattr(src
, name
)
231 except AttributeError:
233 setattr(dst
, name
, value
)
236 def copy_none(self
, src
):
237 m
= self
.add_module(src
.__name
__)
238 m
.__doc
__ = src
.__doc
__
241 # Add a module -- return an existing module or create one
243 def add_module(self
, mname
):
244 if self
.modules
.has_key(mname
):
245 return self
.modules
[mname
]
246 self
.modules
[mname
] = m
= self
.hooks
.new_module(mname
)
247 m
.__builtins
__ = self
.modules
['__builtin__']
250 # The r* methods are public interfaces
252 def r_exec(self
, code
):
253 m
= self
.add_module('__main__')
254 exec code
in m
.__dict
__
256 def r_eval(self
, code
):
257 m
= self
.add_module('__main__')
258 return eval(code
, m
.__dict
__)
260 def r_execfile(self
, file):
261 m
= self
.add_module('__main__')
262 execfile(file, m
.__dict
__)
264 def r_import(self
, mname
, globals={}, locals={}, fromlist
=[]):
265 return self
.importer
.import_module(mname
, globals, locals, fromlist
)
267 def r_reload(self
, m
):
268 return self
.importer
.reload(m
)
270 def r_unload(self
, m
):
271 return self
.importer
.unload(m
)
273 # The s_* methods are similar but also swap std{in,out,err}
275 def make_delegate_files(self
):
276 s
= self
.modules
['sys']
277 self
.delegate_stdin
= FileDelegate(s
, 'stdin')
278 self
.delegate_stdout
= FileDelegate(s
, 'stdout')
279 self
.delegate_stderr
= FileDelegate(s
, 'stderr')
280 self
.restricted_stdin
= FileWrapper(sys
.stdin
)
281 self
.restricted_stdout
= FileWrapper(sys
.stdout
)
282 self
.restricted_stderr
= FileWrapper(sys
.stderr
)
285 if not hasattr(self
, 'save_stdin'):
287 if not hasattr(self
, 'delegate_stdin'):
288 self
.make_delegate_files()
289 s
= self
.modules
['sys']
290 s
.stdin
= self
.restricted_stdin
291 s
.stdout
= self
.restricted_stdout
292 s
.stderr
= self
.restricted_stderr
293 sys
.stdin
= self
.delegate_stdin
294 sys
.stdout
= self
.delegate_stdout
295 sys
.stderr
= self
.delegate_stderr
297 def reset_files(self
):
299 s
= self
.modules
['sys']
300 self
.restricted_stdin
= s
.stdin
301 self
.restricted_stdout
= s
.stdout
302 self
.restricted_stderr
= s
.stderr
305 def save_files(self
):
306 self
.save_stdin
= sys
.stdin
307 self
.save_stdout
= sys
.stdout
308 self
.save_stderr
= sys
.stderr
310 def restore_files(self
):
311 sys
.stdin
= self
.save_stdin
312 sys
.stdout
= self
.save_stdout
313 sys
.stderr
= self
.save_stderr
315 def s_apply(self
, func
, args
=(), kw
=None):
320 r
= apply(func
, args
, kw
)
322 r
= apply(func
, args
)
327 def s_exec(self
, *args
):
328 return self
.s_apply(self
.r_exec
, args
)
330 def s_eval(self
, *args
):
331 return self
.s_apply(self
.r_eval
, args
)
333 def s_execfile(self
, *args
):
334 return self
.s_apply(self
.r_execfile
, args
)
336 def s_import(self
, *args
):
337 return self
.s_apply(self
.r_import
, args
)
339 def s_reload(self
, *args
):
340 return self
.s_apply(self
.r_reload
, args
)
342 def s_unload(self
, *args
):
343 return self
.s_apply(self
.r_unload
, args
)
345 # Restricted open(...)
347 def r_open(self
, file, mode
='r', buf
=-1):
348 if mode
not in ('r', 'rb'):
349 raise IOError, "can't open files for writing in restricted mode"
350 return open(file, mode
, buf
)
352 # Restricted version of sys.exc_info()
354 def r_exc_info(self
):
355 ty
, va
, tr
= sys
.exc_info()
361 import getopt
, traceback
362 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'vt:')
370 r
= RExec(verbose
=verbose
)
372 r
.ok_builtin_modules
= r
.ok_builtin_modules
+ tuple(trusted
)
374 r
.modules
['sys'].argv
= args
375 r
.modules
['sys'].path
.insert(0, os
.path
.dirname(args
[0]))
377 r
.modules
['sys'].path
.insert(0, "")
379 if args
and args
[0] != '-':
383 print "%s: can't open file %s" % (sys
.argv
[0], `args
[0]`
)
386 print "*** RESTRICTED *** Python", sys
.version
387 print 'Type "help", "copyright", "credits" or "license" ' \
388 'for more information.'
393 s
= raw_input('>>> ')
397 if s
and s
[0] != '#':
399 c
= compile(s
, '<stdin>', 'single')
401 except SystemExit, n
:
404 traceback
.print_exc()
408 c
= compile(text
, fp
.name
, 'exec')
411 except SystemExit, n
:
414 traceback
.print_exc()
418 if __name__
== '__main__':