/rpython/translator/sandbox/rsandbox.py
https://bitbucket.org/pypy/pypy/ · Python · 172 lines · 169 code · 0 blank · 3 comment · 0 complexity · 4d317d0b395302d6dbd6d956bd6890a4 MD5 · raw file
- """Generation of sandboxing stand-alone executable from RPython code.
- In place of real calls to any external function, this code builds
- trampolines that marshal their input arguments, dump them to STDOUT,
- and wait for an answer on STDIN. Enable with 'translate.py --sandbox'.
- """
- import py
- from rpython.rlib import rmarshal, types
- from rpython.rlib.signature import signature
- # ____________________________________________________________
- #
- # Sandboxing code generator for external functions
- #
- from rpython.rlib import rposix
- from rpython.rtyper.lltypesystem import lltype, rffi
- from rpython.rtyper.llannotation import lltype_to_annotation
- from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator
- from rpython.tool.ansi_print import AnsiLogger
- log = AnsiLogger("sandbox")
- # a version of os.read() and os.write() that are not mangled
- # by the sandboxing mechanism
- ll_read_not_sandboxed = rposix.external('read',
- [rffi.INT, rffi.CCHARP, rffi.SIZE_T],
- rffi.SIZE_T,
- sandboxsafe=True)
- ll_write_not_sandboxed = rposix.external('write',
- [rffi.INT, rffi.CCHARP, rffi.SIZE_T],
- rffi.SIZE_T,
- sandboxsafe=True)
- @signature(types.int(), types.ptr(rffi.CCHARP.TO), types.int(),
- returns=types.none())
- def writeall_not_sandboxed(fd, buf, length):
- while length > 0:
- size = rffi.cast(rffi.SIZE_T, length)
- count = rffi.cast(lltype.Signed, ll_write_not_sandboxed(fd, buf, size))
- if count <= 0:
- raise IOError
- length -= count
- buf = lltype.direct_ptradd(lltype.direct_arrayitems(buf), count)
- buf = rffi.cast(rffi.CCHARP, buf)
- class FdLoader(rmarshal.Loader):
- def __init__(self, fd):
- rmarshal.Loader.__init__(self, "")
- self.fd = fd
- self.buflen = 4096
- def need_more_data(self):
- buflen = self.buflen
- with lltype.scoped_alloc(rffi.CCHARP.TO, buflen) as buf:
- buflen = rffi.cast(rffi.SIZE_T, buflen)
- count = ll_read_not_sandboxed(self.fd, buf, buflen)
- count = rffi.cast(lltype.Signed, count)
- if count <= 0:
- raise IOError
- self.buf += ''.join([buf[i] for i in range(count)])
- self.buflen *= 2
- def sandboxed_io(buf):
- STDIN = 0
- STDOUT = 1
- # send the buffer with the marshalled fnname and input arguments to STDOUT
- with lltype.scoped_alloc(rffi.CCHARP.TO, len(buf)) as p:
- for i in range(len(buf)):
- p[i] = buf[i]
- writeall_not_sandboxed(STDOUT, p, len(buf))
- # build a Loader that will get the answer from STDIN
- loader = FdLoader(STDIN)
- # check for errors
- error = load_int(loader)
- if error != 0:
- reraise_error(error, loader)
- else:
- # no exception; the caller will decode the actual result
- return loader
- def reraise_error(error, loader):
- if error == 1:
- raise OSError(load_int(loader), "external error")
- elif error == 2:
- raise IOError
- elif error == 3:
- raise OverflowError
- elif error == 4:
- raise ValueError
- elif error == 5:
- raise ZeroDivisionError
- elif error == 6:
- raise MemoryError
- elif error == 7:
- raise KeyError
- elif error == 8:
- raise IndexError
- else:
- raise RuntimeError
- @signature(types.str(), returns=types.impossible())
- def not_implemented_stub(msg):
- STDERR = 2
- with rffi.scoped_str2charp(msg + '\n') as buf:
- writeall_not_sandboxed(STDERR, buf, len(msg) + 1)
- raise RuntimeError(msg) # XXX in RPython, the msg is ignored
- def make_stub(fnname, msg):
- """Build always-raising stub function to replace unsupported external."""
- log.WARNING(msg)
- def execute(*args):
- not_implemented_stub(msg)
- execute.__name__ = 'sandboxed_%s' % (fnname,)
- return execute
- def sig_ll(fnobj):
- FUNCTYPE = lltype.typeOf(fnobj)
- args_s = [lltype_to_annotation(ARG) for ARG in FUNCTYPE.ARGS]
- s_result = lltype_to_annotation(FUNCTYPE.RESULT)
- return args_s, s_result
- dump_string = rmarshal.get_marshaller(str)
- load_int = rmarshal.get_loader(int)
- def get_sandbox_stub(fnobj, rtyper):
- fnname = fnobj._name
- args_s, s_result = sig_ll(fnobj)
- msg = "Not implemented: sandboxing for external function '%s'" % (fnname,)
- execute = make_stub(fnname, msg)
- return _annotate(rtyper, execute, args_s, s_result)
- def make_sandbox_trampoline(fnname, args_s, s_result):
- """Create a trampoline function with the specified signature.
- The trampoline is meant to be used in place of real calls to the external
- function named 'fnname'. It marshals its input arguments, dumps them to
- STDOUT, and waits for an answer on STDIN.
- """
- try:
- dump_arguments = rmarshal.get_marshaller(tuple(args_s))
- load_result = rmarshal.get_loader(s_result)
- except (rmarshal.CannotMarshal, rmarshal.CannotUnmarshall) as e:
- msg = "Cannot sandbox function '%s': %s" % (fnname, e)
- execute = make_stub(fnname, msg)
- else:
- def execute(*args):
- # marshal the function name and input arguments
- buf = []
- dump_string(buf, fnname)
- dump_arguments(buf, args)
- # send the buffer and wait for the answer
- loader = sandboxed_io(buf)
- # decode the answer
- result = load_result(loader)
- loader.check_finished()
- return result
- execute.__name__ = 'sandboxed_%s' % (fnname,)
- return execute
- def _annotate(rtyper, f, args_s, s_result):
- ann = MixLevelHelperAnnotator(rtyper)
- graph = ann.getgraph(f, args_s, s_result)
- ann.finish()
- return graph