/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

  1. """Generation of sandboxing stand-alone executable from RPython code.
  2. In place of real calls to any external function, this code builds
  3. trampolines that marshal their input arguments, dump them to STDOUT,
  4. and wait for an answer on STDIN. Enable with 'translate.py --sandbox'.
  5. """
  6. import py
  7. from rpython.rlib import rmarshal, types
  8. from rpython.rlib.signature import signature
  9. # ____________________________________________________________
  10. #
  11. # Sandboxing code generator for external functions
  12. #
  13. from rpython.rlib import rposix
  14. from rpython.rtyper.lltypesystem import lltype, rffi
  15. from rpython.rtyper.llannotation import lltype_to_annotation
  16. from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator
  17. from rpython.tool.ansi_print import AnsiLogger
  18. log = AnsiLogger("sandbox")
  19. # a version of os.read() and os.write() that are not mangled
  20. # by the sandboxing mechanism
  21. ll_read_not_sandboxed = rposix.external('read',
  22. [rffi.INT, rffi.CCHARP, rffi.SIZE_T],
  23. rffi.SIZE_T,
  24. sandboxsafe=True)
  25. ll_write_not_sandboxed = rposix.external('write',
  26. [rffi.INT, rffi.CCHARP, rffi.SIZE_T],
  27. rffi.SIZE_T,
  28. sandboxsafe=True)
  29. @signature(types.int(), types.ptr(rffi.CCHARP.TO), types.int(),
  30. returns=types.none())
  31. def writeall_not_sandboxed(fd, buf, length):
  32. while length > 0:
  33. size = rffi.cast(rffi.SIZE_T, length)
  34. count = rffi.cast(lltype.Signed, ll_write_not_sandboxed(fd, buf, size))
  35. if count <= 0:
  36. raise IOError
  37. length -= count
  38. buf = lltype.direct_ptradd(lltype.direct_arrayitems(buf), count)
  39. buf = rffi.cast(rffi.CCHARP, buf)
  40. class FdLoader(rmarshal.Loader):
  41. def __init__(self, fd):
  42. rmarshal.Loader.__init__(self, "")
  43. self.fd = fd
  44. self.buflen = 4096
  45. def need_more_data(self):
  46. buflen = self.buflen
  47. with lltype.scoped_alloc(rffi.CCHARP.TO, buflen) as buf:
  48. buflen = rffi.cast(rffi.SIZE_T, buflen)
  49. count = ll_read_not_sandboxed(self.fd, buf, buflen)
  50. count = rffi.cast(lltype.Signed, count)
  51. if count <= 0:
  52. raise IOError
  53. self.buf += ''.join([buf[i] for i in range(count)])
  54. self.buflen *= 2
  55. def sandboxed_io(buf):
  56. STDIN = 0
  57. STDOUT = 1
  58. # send the buffer with the marshalled fnname and input arguments to STDOUT
  59. with lltype.scoped_alloc(rffi.CCHARP.TO, len(buf)) as p:
  60. for i in range(len(buf)):
  61. p[i] = buf[i]
  62. writeall_not_sandboxed(STDOUT, p, len(buf))
  63. # build a Loader that will get the answer from STDIN
  64. loader = FdLoader(STDIN)
  65. # check for errors
  66. error = load_int(loader)
  67. if error != 0:
  68. reraise_error(error, loader)
  69. else:
  70. # no exception; the caller will decode the actual result
  71. return loader
  72. def reraise_error(error, loader):
  73. if error == 1:
  74. raise OSError(load_int(loader), "external error")
  75. elif error == 2:
  76. raise IOError
  77. elif error == 3:
  78. raise OverflowError
  79. elif error == 4:
  80. raise ValueError
  81. elif error == 5:
  82. raise ZeroDivisionError
  83. elif error == 6:
  84. raise MemoryError
  85. elif error == 7:
  86. raise KeyError
  87. elif error == 8:
  88. raise IndexError
  89. else:
  90. raise RuntimeError
  91. @signature(types.str(), returns=types.impossible())
  92. def not_implemented_stub(msg):
  93. STDERR = 2
  94. with rffi.scoped_str2charp(msg + '\n') as buf:
  95. writeall_not_sandboxed(STDERR, buf, len(msg) + 1)
  96. raise RuntimeError(msg) # XXX in RPython, the msg is ignored
  97. def make_stub(fnname, msg):
  98. """Build always-raising stub function to replace unsupported external."""
  99. log.WARNING(msg)
  100. def execute(*args):
  101. not_implemented_stub(msg)
  102. execute.__name__ = 'sandboxed_%s' % (fnname,)
  103. return execute
  104. def sig_ll(fnobj):
  105. FUNCTYPE = lltype.typeOf(fnobj)
  106. args_s = [lltype_to_annotation(ARG) for ARG in FUNCTYPE.ARGS]
  107. s_result = lltype_to_annotation(FUNCTYPE.RESULT)
  108. return args_s, s_result
  109. dump_string = rmarshal.get_marshaller(str)
  110. load_int = rmarshal.get_loader(int)
  111. def get_sandbox_stub(fnobj, rtyper):
  112. fnname = fnobj._name
  113. args_s, s_result = sig_ll(fnobj)
  114. msg = "Not implemented: sandboxing for external function '%s'" % (fnname,)
  115. execute = make_stub(fnname, msg)
  116. return _annotate(rtyper, execute, args_s, s_result)
  117. def make_sandbox_trampoline(fnname, args_s, s_result):
  118. """Create a trampoline function with the specified signature.
  119. The trampoline is meant to be used in place of real calls to the external
  120. function named 'fnname'. It marshals its input arguments, dumps them to
  121. STDOUT, and waits for an answer on STDIN.
  122. """
  123. try:
  124. dump_arguments = rmarshal.get_marshaller(tuple(args_s))
  125. load_result = rmarshal.get_loader(s_result)
  126. except (rmarshal.CannotMarshal, rmarshal.CannotUnmarshall) as e:
  127. msg = "Cannot sandbox function '%s': %s" % (fnname, e)
  128. execute = make_stub(fnname, msg)
  129. else:
  130. def execute(*args):
  131. # marshal the function name and input arguments
  132. buf = []
  133. dump_string(buf, fnname)
  134. dump_arguments(buf, args)
  135. # send the buffer and wait for the answer
  136. loader = sandboxed_io(buf)
  137. # decode the answer
  138. result = load_result(loader)
  139. loader.check_finished()
  140. return result
  141. execute.__name__ = 'sandboxed_%s' % (fnname,)
  142. return execute
  143. def _annotate(rtyper, f, args_s, s_result):
  144. ann = MixLevelHelperAnnotator(rtyper)
  145. graph = ann.getgraph(f, args_s, s_result)
  146. ann.finish()
  147. return graph