[pypy-commit] pypy py3.6-sandbox-2: merge 8bd962a47352
arigo
pypy.commits at gmail.com
Mon Aug 26 11:27:49 EDT 2019
Author: Armin Rigo <arigo at tunes.org>
Branch: py3.6-sandbox-2
Changeset: r97272:3d2a0a3328e7
Date: 2019-08-26 17:20 +0200
http://bitbucket.org/pypy/pypy/changeset/3d2a0a3328e7/
Log: merge 8bd962a47352
diff --git a/pypy/module/__pypy__/interp_debug.py b/pypy/module/__pypy__/interp_debug.py
--- a/pypy/module/__pypy__/interp_debug.py
+++ b/pypy/module/__pypy__/interp_debug.py
@@ -1,7 +1,13 @@
from pypy.interpreter.gateway import unwrap_spec
from rpython.rlib import debug, jit
from rpython.rlib import rtimer
+from rpython.rlib.objectmodel import sandbox_review
+# In sandbox mode, the debug_start/debug_print functions are disabled,
+# because they could allow the attacker to write arbitrary bytes to stderr
+
+
+ at sandbox_review(abort=True)
@jit.dont_look_inside
@unwrap_spec(category='text', timestamp=bool)
def debug_start(space, category, timestamp=False):
@@ -10,11 +16,13 @@
return space.newint(res)
return space.w_None
+ at sandbox_review(abort=True)
@jit.dont_look_inside
def debug_print(space, args_w):
parts = [space.text_w(space.str(w_item)) for w_item in args_w]
debug.debug_print(' '.join(parts))
+ at sandbox_review(abort=True)
@jit.dont_look_inside
@unwrap_spec(category='text', timestamp=bool)
def debug_stop(space, category, timestamp=False):
@@ -23,6 +31,7 @@
return space.newint(res)
return space.w_None
+ at sandbox_review(abort=True)
@unwrap_spec(category='text')
def debug_print_once(space, category, args_w):
debug_start(space, category)
@@ -34,9 +43,16 @@
def debug_flush(space):
debug.debug_flush()
+
+# In sandbox mode, these two helpers are disabled because they give unlimited
+# access to the real time (if you enable them, note that they use lloperations
+# that must also be white-listed in graphchecker.py)
+
+ at sandbox_review(abort=True)
def debug_read_timestamp(space):
return space.newint(rtimer.read_timestamp())
+ at sandbox_review(abort=True)
def debug_get_timestamp_unit(space):
unit = rtimer.get_timestamp_unit()
if unit == rtimer.UNIT_TSC:
diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py
--- a/pypy/module/array/interp_array.py
+++ b/pypy/module/array/interp_array.py
@@ -1,6 +1,6 @@
from rpython.rlib import jit, rgc, rutf8
from rpython.rlib.buffer import RawBuffer, SubBuffer
-from rpython.rlib.objectmodel import keepalive_until_here
+from rpython.rlib.objectmodel import keepalive_until_here, sandbox_review
from rpython.rlib.rarithmetic import ovfcheck, widen, r_uint
from rpython.rlib.unroll import unrolling_iterable
from rpython.rtyper.annlowlevel import llstr
@@ -179,6 +179,7 @@
if self._buffer:
lltype.free(self._buffer, flavor='raw')
+ @sandbox_review(reviewed=True)
def setlen(self, size, zero=False, overallocate=True):
if self._buffer:
delta_memory_pressure = -self.allocated * self.itemsize
@@ -253,6 +254,7 @@
def _charbuf_stop(self):
keepalive_until_here(self)
+ @sandbox_review(reviewed=True)
def delitem(self, space, i, j):
if i < 0:
i += self.len
@@ -407,6 +409,7 @@
self._charbuf_stop()
return self.space.newbytes(s)
+ @sandbox_review(reviewed=True)
def descr_fromstring(self, space, w_s):
""" fromstring(string)
@@ -565,6 +568,7 @@
w_bytes]),
w_dict])
+ @sandbox_review(reviewed=True)
def descr_copy(self, space):
""" copy(array)
@@ -579,6 +583,7 @@
)
return w_a
+ @sandbox_review(reviewed=True)
def descr_byteswap(self, space):
""" byteswap()
@@ -659,6 +664,7 @@
def descr_iter(self, space):
return space.newseqiter(self)
+ @sandbox_review(reviewed=True)
def descr_add(self, space, w_other):
if (not isinstance(w_other, W_ArrayBase)
or w_other.typecode != self.typecode):
@@ -682,6 +688,7 @@
keepalive_until_here(a)
return a
+ @sandbox_review(reviewed=True)
def descr_inplace_add(self, space, w_other):
if (not isinstance(w_other, W_ArrayBase)
or w_other.typecode != self.typecode):
@@ -700,6 +707,7 @@
keepalive_until_here(w_other)
return self
+ @sandbox_review(reviewed=True)
def _mul_helper(self, space, w_repeat, is_inplace):
try:
repeat = space.getindex_w(w_repeat, space.w_OverflowError)
@@ -1071,6 +1079,7 @@
self.space.newtext(msg))
return result
+ @sandbox_review(reviewed=True)
def fromsequence(self, w_seq):
space = self.space
oldlen = self.len
@@ -1119,6 +1128,7 @@
self._fromiterable(w_seq)
+ @sandbox_review(reviewed=True)
def extend(self, w_iterable, accept_different_array=False):
space = self.space
if isinstance(w_iterable, W_Array):
@@ -1170,6 +1180,7 @@
# interface
+ @sandbox_review(reviewed=True)
def descr_append(self, space, w_x):
x = self.item_w(w_x)
index = self.len
@@ -1179,12 +1190,14 @@
# List interface
+ @sandbox_review(reviewed=True)
def descr_reverse(self, space):
b = self.get_buffer()
for i in range(self.len / 2):
b[i], b[self.len - i - 1] = b[self.len - i - 1], b[i]
keepalive_until_here(self)
+ @sandbox_review(reviewed=True)
def descr_pop(self, space, i):
if i < 0:
i += self.len
@@ -1199,6 +1212,7 @@
self.setlen(self.len - 1)
return w_val
+ @sandbox_review(reviewed=True)
def descr_insert(self, space, idx, w_val):
if idx < 0:
idx += self.len
@@ -1217,6 +1231,7 @@
b[i] = val
keepalive_until_here(self)
+ @sandbox_review(reviewed=True)
def getitem_slice(self, space, w_idx):
start, stop, step, size = space.decode_index4(w_idx, self.len)
w_a = mytype.w_class(self.space)
@@ -1232,6 +1247,7 @@
keepalive_until_here(w_a)
return w_a
+ @sandbox_review(reviewed=True)
def setitem(self, space, w_idx, w_item):
idx, stop, step = space.decode_index(w_idx, self.len)
if step != 0:
@@ -1241,6 +1257,7 @@
self.get_buffer()[idx] = item
keepalive_until_here(self)
+ @sandbox_review(reviewed=True)
def setitem_slice(self, space, w_idx, w_item):
if not isinstance(w_item, W_Array):
raise oefmt(space.w_TypeError,
@@ -1268,6 +1285,7 @@
keepalive_until_here(w_item)
keepalive_until_here(self)
+ @sandbox_review(check_caller=True)
def _repeat_single_item(self, a, start, repeat):
# <a performance hack>
assert isinstance(a, W_Array)
diff --git a/pypy/module/gc/interp_gc.py b/pypy/module/gc/interp_gc.py
--- a/pypy/module/gc/interp_gc.py
+++ b/pypy/module/gc/interp_gc.py
@@ -46,7 +46,8 @@
If they were already enabled, no-op.
If they were disabled even several times, enable them anyway.
"""
- rgc.enable()
+ if not space.config.translation.sandbox: # not available in sandbox
+ rgc.enable()
if not space.user_del_action.enabled_at_app_level:
space.user_del_action.enabled_at_app_level = True
enable_finalizers(space)
@@ -55,7 +56,8 @@
"""Non-recursive version. Disable major collections and finalizers.
Multiple calls to this function are ignored.
"""
- rgc.disable()
+ if not space.config.translation.sandbox: # not available in sandbox
+ rgc.disable()
if space.user_del_action.enabled_at_app_level:
space.user_del_action.enabled_at_app_level = False
disable_finalizers(space)
diff --git a/pypy/module/time/interp_time.py b/pypy/module/time/interp_time.py
--- a/pypy/module/time/interp_time.py
+++ b/pypy/module/time/interp_time.py
@@ -12,6 +12,7 @@
from rpython.rlib.rtime import (GETTIMEOFDAY_NO_TZ, TIMEVAL,
HAVE_GETTIMEOFDAY, HAVE_FTIME)
from rpython.rlib import rposix, rtime
+from rpython.rlib.objectmodel import sandbox_review
from rpython.translator.tool.cbuild import ExternalCompilationInfo
import math
import os
@@ -365,6 +366,7 @@
c_strftime = external('strftime', [rffi.CCHARP, rffi.SIZE_T, rffi.CCHARP, TM_P],
rffi.SIZE_T, sandboxsafe=True)
+ at sandbox_review(reviewed=True)
def _init_timezone(space):
timezone = daylight = altzone = 0
tzname = ["", ""]
@@ -564,6 +566,7 @@
w_obj = space.call_function(w_struct_time, w_time_tuple)
return w_obj
+ at sandbox_review(reviewed=True)
def _gettmarg(space, w_tup, allowNone=True):
if space.is_none(w_tup):
if not allowNone:
@@ -679,6 +682,7 @@
return space.newfloat(_timespec_to_seconds(timespec))
return gettimeofday(space, w_info)
+ at sandbox_review(reviewed=True)
def ctime(space, w_seconds=None):
"""ctime([seconds]) -> string
@@ -725,6 +729,7 @@
return space.mod(space.newtext("%.3s %.3s%3d %.2d:%.2d:%.2d %d"),
space.newtuple(args))
+ at sandbox_review(reviewed=True)
def gmtime(space, w_seconds=None):
"""gmtime([seconds]) -> (tm_year, tm_mon, tm_day, tm_hour, tm_min,
tm_sec, tm_wday, tm_yday, tm_isdst)
@@ -746,6 +751,7 @@
space.newtext(*_get_error_msg()))
return _tm_to_tuple(space, p)
+ at sandbox_review(reviewed=True)
def localtime(space, w_seconds=None):
"""localtime([seconds]) -> (tm_year, tm_mon, tm_day, tm_hour, tm_min,
tm_sec, tm_wday, tm_yday, tm_isdst)
@@ -764,6 +770,7 @@
space.newtext(*_get_error_msg()))
return _tm_to_tuple(space, p)
+ at sandbox_review(reviewed=True)
def mktime(space, w_tup):
"""mktime(tuple) -> floating point number
diff --git a/rpython/memory/gc/incminimark.py b/rpython/memory/gc/incminimark.py
--- a/rpython/memory/gc/incminimark.py
+++ b/rpython/memory/gc/incminimark.py
@@ -1176,6 +1176,8 @@
def unpin(self, obj):
+ if self.safer_variant():
+ out_of_memory("unpin() unexpected")
ll_assert(self._is_pinned(obj),
"unpin: object is already not pinned")
#
@@ -1186,6 +1188,8 @@
return (self.header(obj).tid & GCFLAG_PINNED) != 0
def shrink_array(self, obj, smallerlength):
+ if self.safer_variant(): # no shrinking in the safer variant
+ return False # (because the original 'obj' is kind of broken)
#
# Only objects in the nursery can be "resized". Resizing them
# means recording that they have a smaller size, so that when
diff --git a/rpython/memory/gc/inspector.py b/rpython/memory/gc/inspector.py
--- a/rpython/memory/gc/inspector.py
+++ b/rpython/memory/gc/inspector.py
@@ -89,7 +89,7 @@
raw_os_write = rffi.llexternal(rposix.UNDERSCORE_ON_WIN32 + 'write',
[rffi.INT, llmemory.Address, rffi.SIZE_T],
rffi.SIZE_T,
- sandboxsafe=True, _nowrapper=True)
+ _nowrapper=True)
AddressStack = get_address_stack()
diff --git a/rpython/rlib/buffer.py b/rpython/rlib/buffer.py
--- a/rpython/rlib/buffer.py
+++ b/rpython/rlib/buffer.py
@@ -7,6 +7,7 @@
from rpython.rtyper.lltypesystem.rlist import LIST_OF
from rpython.rtyper.annlowlevel import llstr
from rpython.rlib.objectmodel import specialize, we_are_translated
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rlib import jit
from rpython.rlib.rgc import (resizable_list_supporting_raw_ptr,
nonmoving_raw_ptr_for_resizable_list,
@@ -143,6 +144,7 @@
ptr = self.get_raw_address()
return llop.raw_load(TP, ptr, byte_offset)
+ @sandbox_review(check_caller=True)
@specialize.ll_and_arg(1)
def typed_write(self, TP, byte_offset, value):
"""
@@ -179,6 +181,7 @@
base_ofs = targetcls._get_gc_data_offset()
scale_factor = llmemory.sizeof(lltype.Char)
+ @sandbox_review(check_caller=True)
@specialize.ll_and_arg(1)
def typed_read(self, TP, byte_offset):
if not is_alignment_correct(TP, byte_offset):
@@ -188,6 +191,7 @@
return llop.gc_load_indexed(TP, lldata, byte_offset,
scale_factor, base_ofs)
+ @sandbox_review(check_caller=True)
@specialize.ll_and_arg(1)
def typed_write(self, TP, byte_offset, value):
if self.readonly or not is_alignment_correct(TP, byte_offset):
@@ -362,10 +366,12 @@
ptr = self.buffer.get_raw_address()
return rffi.ptradd(ptr, self.offset)
+ @sandbox_review(check_caller=True)
@specialize.ll_and_arg(1)
def typed_read(self, TP, byte_offset):
return self.buffer.typed_read(TP, byte_offset + self.offset)
+ @sandbox_review(check_caller=True)
@specialize.ll_and_arg(1)
def typed_write(self, TP, byte_offset, value):
return self.buffer.typed_write(TP, byte_offset + self.offset, value)
diff --git a/rpython/rlib/objectmodel.py b/rpython/rlib/objectmodel.py
--- a/rpython/rlib/objectmodel.py
+++ b/rpython/rlib/objectmodel.py
@@ -226,6 +226,38 @@
func._not_rpython_ = True
return func
+def sandbox_review(reviewed=False, check_caller=False, abort=False):
+ """Mark a function as reviewed for sandboxing purposes.
+ This should not be necessary on any function written in "normal" RPython
+ code, but only on functions using some lloperation that is not
+ whitelisted in rpython.translator.sandbox.graphchecker.
+
+ Call this with one of the three flags set to True:
+
+ *reviewed*: This function is fine and any other code can call it.
+ If the function contains external calls, they will still be replaced with
+ stubs using I/O to communicate with the parent process (as long as they
+ are not marked sandboxsafe themselves).
+
+ *check_caller*: This function is fine, but you should still check the
+ callers; they must all have a sandbox_review() as well.
+
+ *abort*: An abort is prepended to the function's code, making the
+ whole process abort if it is called at runtime.
+
+ """
+ assert reviewed + check_caller + abort == 1
+ def wrap(func):
+ assert not hasattr(func, '_sandbox_review_') or abort
+ if reviewed:
+ func._sandbox_review_ = 'reviewed'
+ if check_caller:
+ func._sandbox_review_ = 'check_caller'
+ if abort:
+ func._sandbox_review_ = 'abort'
+ return func
+ return wrap
+
# ____________________________________________________________
@@ -347,6 +379,10 @@
# XXX this can be made more efficient in the future
return bytearray(str(i))
+def sandboxed_translation():
+ config = fetch_translated_config()
+ return config is not None and config.translation.sandbox
+
def fetch_translated_config():
"""Returns the config that is current when translating.
Returns None if not translated.
diff --git a/rpython/rlib/rgc.py b/rpython/rlib/rgc.py
--- a/rpython/rlib/rgc.py
+++ b/rpython/rlib/rgc.py
@@ -6,6 +6,7 @@
from rpython.rlib import jit
from rpython.rlib.objectmodel import we_are_translated, enforceargs, specialize
from rpython.rlib.objectmodel import CDefinedIntSymbolic, not_rpython
+from rpython.rlib.objectmodel import sandbox_review, sandboxed_translation
from rpython.rtyper.extregistry import ExtRegistryEntry
from rpython.rtyper.lltypesystem import lltype, llmemory
@@ -358,14 +359,23 @@
return True
return False
+ at not_rpython
+def _ll_arraycopy_of_nongc_not_for_sandboxed():
+ pass
@jit.oopspec('list.ll_arraycopy(source, dest, source_start, dest_start, length)')
@enforceargs(None, None, int, int, int)
+ at sandbox_review(reviewed=True)
@specialize.ll()
def ll_arraycopy(source, dest, source_start, dest_start, length):
from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.rlib.objectmodel import keepalive_until_here
+ TP = lltype.typeOf(source).TO
+ assert TP == lltype.typeOf(dest).TO
+ if TP._gckind != 'gc' and sandboxed_translation():
+ _ll_arraycopy_of_nongc_not_for_sandboxed()
+
# XXX: Hack to ensure that we get a proper effectinfo.write_descrs_arrays
# and also, maybe, speed up very small cases
if length <= 1:
@@ -379,9 +389,6 @@
assert (source_start + length <= dest_start or
dest_start + length <= source_start)
- TP = lltype.typeOf(source).TO
- assert TP == lltype.typeOf(dest).TO
-
slowpath = False
if must_split_gc_address_space():
slowpath = True
@@ -415,6 +422,7 @@
@jit.oopspec('rgc.ll_shrink_array(p, smallerlength)')
@enforceargs(None, int)
+ at sandbox_review(reviewed=True)
@specialize.ll()
def ll_shrink_array(p, smallerlength):
from rpython.rtyper.lltypesystem.lloperation import llop
@@ -454,6 +462,7 @@
return newp
@jit.dont_look_inside
+ at sandbox_review(reviewed=True)
@specialize.ll()
def ll_arrayclear(p):
# Equivalent to memset(array, 0). Only for GcArray(primitive-type) for now.
@@ -1096,6 +1105,7 @@
hop.exception_cannot_occur()
return hop.genop('gc_gcflag_extra', vlist, resulttype = hop.r_result)
+ at specialize.memo()
def lltype_is_gc(TP):
return getattr(getattr(TP, "TO", None), "_gckind", "?") == 'gc'
@@ -1419,7 +1429,7 @@
return _ResizableListSupportingRawPtr(lst)
def nonmoving_raw_ptr_for_resizable_list(lst):
- if must_split_gc_address_space():
+ if must_split_gc_address_space() or sandboxed_translation():
raise ValueError
return _nonmoving_raw_ptr_for_resizable_list(lst)
@@ -1501,6 +1511,7 @@
@jit.dont_look_inside
+ at sandbox_review(check_caller=True)
def ll_nonmovable_raw_ptr_for_resizable_list(ll_list):
"""
WARNING: dragons ahead.
diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py
--- a/rpython/rlib/rposix.py
+++ b/rpython/rlib/rposix.py
@@ -10,7 +10,8 @@
_CYGWIN, _MACRO_ON_POSIX, UNDERSCORE_ON_WIN32, _WIN32,
_prefer_unicode, _preferred_traits, _preferred_traits2)
from rpython.rlib.objectmodel import (
- specialize, enforceargs, register_replacement_for, NOT_CONSTANT)
+ specialize, enforceargs, register_replacement_for, NOT_CONSTANT,
+ sandbox_review)
from rpython.rlib.rarithmetic import intmask, widen
from rpython.rlib.signature import signature
from rpython.tool.sourcetools import func_renamer
@@ -988,6 +989,7 @@
[rffi.INTP, rffi.VOIDP, rffi.VOIDP, rffi.VOIDP],
rffi.PID_T, _nowrapper = True)
+ at sandbox_review(abort=True)
@replace_os_function('fork')
@jit.dont_look_inside
def fork():
@@ -1017,6 +1019,7 @@
lltype.free(master_p, flavor='raw')
lltype.free(slave_p, flavor='raw')
+ at sandbox_review(abort=True)
@replace_os_function('forkpty')
@jit.dont_look_inside
def forkpty():
@@ -1058,6 +1061,7 @@
[rffi.PID_T, rffi.INTP, rffi.INT], rffi.PID_T,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('waitpid')
def waitpid(pid, options):
status_p = lltype.malloc(rffi.INTP.TO, 1, flavor='raw')
@@ -1743,6 +1747,7 @@
finally:
lltype.free(groups, flavor='raw')
+ at sandbox_review(reviewed=True)
@replace_os_function('setgroups')
def setgroups(gids):
n = len(gids)
diff --git a/rpython/rlib/rstack.py b/rpython/rlib/rstack.py
--- a/rpython/rlib/rstack.py
+++ b/rpython/rlib/rstack.py
@@ -6,6 +6,7 @@
import py
from rpython.rlib.objectmodel import we_are_translated, fetch_translated_config
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rlib.rarithmetic import r_uint
from rpython.rlib import rgc
from rpython.rtyper.lltypesystem import lltype, rffi
@@ -15,7 +16,7 @@
def llexternal(name, args, res, _callable=None):
return rffi.llexternal(name, args, res,
- sandboxsafe=True, _nowrapper=True,
+ sandboxsafe='check_caller', _nowrapper=True,
_callable=_callable)
_stack_get_end = llexternal('LL_stack_get_end', [], lltype.Signed,
@@ -39,6 +40,7 @@
_stack_criticalcode_stop = llexternal('LL_stack_criticalcode_stop', [],
lltype.Void, lambda: None)
+ at sandbox_review(reviewed=True)
def stack_check():
if not we_are_translated():
return
@@ -64,6 +66,7 @@
stack_check._always_inline_ = True
stack_check._dont_insert_stackcheck_ = True
+ at sandbox_review(check_caller=True)
@rgc.no_collect
def stack_check_slowpath(current):
if ord(_stack_too_big_slowpath(current)):
@@ -72,6 +75,7 @@
stack_check_slowpath._dont_inline_ = True
stack_check_slowpath._dont_insert_stackcheck_ = True
+ at sandbox_review(reviewed=True)
def stack_almost_full():
"""Return True if the stack is more than 15/16th full."""
if not we_are_translated():
diff --git a/rpython/rlib/rstruct/standardfmttable.py b/rpython/rlib/rstruct/standardfmttable.py
--- a/rpython/rlib/rstruct/standardfmttable.py
+++ b/rpython/rlib/rstruct/standardfmttable.py
@@ -7,7 +7,7 @@
import struct
-from rpython.rlib.objectmodel import specialize
+from rpython.rlib.objectmodel import specialize, sandbox_review
from rpython.rlib.rarithmetic import r_uint, r_longlong, r_ulonglong
from rpython.rlib.rstruct import ieee
from rpython.rlib.rstruct.error import StructError, StructOverflowError
@@ -30,6 +30,7 @@
Create a fast path packer for TYPE. The packer returns True is it succeded
or False otherwise.
"""
+ @sandbox_review(reviewed=True)
@specialize.argtype(0)
def do_pack_fastpath(fmtiter, value):
size = rffi.sizeof(TYPE)
@@ -39,6 +40,7 @@
raise CannotWrite
#
# typed_write() might raise CannotWrite
+ # (note that we assume the write cannot overflow its buffer)
fmtiter.wbuf.typed_write(TYPE, fmtiter.pos, value)
if not ALLOW_FASTPATH:
# if we are here it means that typed_write did not raise, and thus
@@ -211,6 +213,7 @@
@specialize.memo()
def unpack_fastpath(TYPE):
+ @sandbox_review(reviewed=True)
@specialize.argtype(0)
def do_unpack_fastpath(fmtiter):
size = rffi.sizeof(TYPE)
@@ -289,9 +292,15 @@
# because of alignment issues. So we copy the slice into a new
# string, which is guaranteed to be properly aligned, and read the
# float/double from there
- input = fmtiter.read(size)
- val = StringBuffer(input).typed_read(TYPE, 0)
+ val = read_slowpath(fmtiter)
fmtiter.appendobj(float(val))
+
+ @sandbox_review(reviewed=True)
+ def read_slowpath(fmtiter):
+ size = rffi.sizeof(TYPE)
+ input = fmtiter.read(size)
+ return StringBuffer(input).typed_read(TYPE, 0)
+
return unpack_ieee
@specialize.argtype(0)
diff --git a/rpython/rlib/rthread.py b/rpython/rlib/rthread.py
--- a/rpython/rlib/rthread.py
+++ b/rpython/rlib/rthread.py
@@ -6,6 +6,7 @@
from rpython.rlib.debug import ll_assert
from rpython.rlib.objectmodel import we_are_translated, specialize
from rpython.rlib.objectmodel import CDefinedIntSymbolic, not_rpython
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rtyper.lltypesystem.lloperation import llop
from rpython.rtyper.tool import rffi_platform
from rpython.rtyper.extregistry import ExtRegistryEntry
@@ -225,7 +226,7 @@
get_stacksize = llexternal('RPyThreadGetStackSize', [], lltype.Signed)
set_stacksize = llexternal('RPyThreadSetStackSize', [lltype.Signed],
- lltype.Signed)
+ lltype.Signed, sandboxsafe='abort')
# ____________________________________________________________
#
diff --git a/rpython/rlib/rtime.py b/rpython/rlib/rtime.py
--- a/rpython/rlib/rtime.py
+++ b/rpython/rlib/rtime.py
@@ -8,7 +8,7 @@
from rpython.translator.tool.cbuild import ExternalCompilationInfo
from rpython.rtyper.tool import rffi_platform
from rpython.rtyper.lltypesystem import rffi, lltype
-from rpython.rlib.objectmodel import register_replacement_for
+from rpython.rlib.objectmodel import register_replacement_for, sandbox_review
from rpython.rlib.rarithmetic import intmask, UINT_MAX
from rpython.rlib import rposix
@@ -262,6 +262,7 @@
lltype.Ptr(TIMEVAL)], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_time_function('sleep')
def sleep(secs):
if _WIN32:
diff --git a/rpython/rtyper/lltypesystem/rffi.py b/rpython/rtyper/lltypesystem/rffi.py
--- a/rpython/rtyper/lltypesystem/rffi.py
+++ b/rpython/rtyper/lltypesystem/rffi.py
@@ -9,6 +9,7 @@
from rpython.tool.sourcetools import func_with_new_name
from rpython.rlib.objectmodel import Symbolic, specialize, not_rpython
from rpython.rlib.objectmodel import keepalive_until_here, enforceargs
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rlib import rarithmetic, rgc
from rpython.rtyper.extregistry import ExtRegistryEntry
from rpython.rlib.unroll import unrolling_iterable
@@ -97,6 +98,12 @@
don't bother releasing the GIL. An explicit True or False
overrides this logic.
+ sandboxsafe: if True, the process really calls the C function even if it
+ is sandboxed. If False, it will turn into a stdin/stdout
+ communication with the parent process. If "check_caller",
+ it is like True but we call @sandbox_review(check_caller=True)
+ which means that we need to also check the callers.
+
calling_conv: if 'unknown' or 'win', the C function is not directly seen
by the JIT. If 'c', it can be seen (depending on
releasegil=False). For tests only, or if _nowrapper,
@@ -214,6 +221,8 @@
#
call_external_function = func_with_new_name(call_external_function,
'ccall_' + name)
+ call_external_function = sandbox_review(check_caller=True)(
+ call_external_function)
# don't inline, as a hack to guarantee that no GC pointer is alive
# anywhere in call_external_function
else:
@@ -251,6 +260,8 @@
'ccall_' + name)
call_external_function = jit.dont_look_inside(
call_external_function)
+ call_external_function = sandbox_review(check_caller=True)(
+ call_external_function)
def _oops():
raise AssertionError("can't pass (any more) a unicode string"
@@ -329,8 +340,18 @@
# for debugging, stick ll func ptr to that
wrapper._ptr = funcptr
wrapper = func_with_new_name(wrapper, name)
+ if sandboxsafe == 'check_caller':
+ wrapper = sandbox_review(check_caller=True)(wrapper)
+ elif sandboxsafe == 'abort':
+ wrapper = sandbox_review(abort=True)(wrapper)
+ else:
+ assert isinstance(sandboxsafe, bool)
+ wrapper = sandbox_review(reviewed=True)(wrapper)
return wrapper
+def sandbox_check_type(TYPE):
+ return not isinstance(TYPE, lltype.Primitive) or TYPE == llmemory.Address
+
class CallbackHolder:
def __init__(self):
@@ -792,6 +813,7 @@
lastchar = u'\x00'
# str -> char*
+ @sandbox_review(reviewed=True)
def str2charp(s, track_allocation=True):
""" str -> char*
"""
@@ -806,6 +828,7 @@
return array
str2charp._annenforceargs_ = [strtype, bool]
+ @sandbox_review(reviewed=True)
def free_charp(cp, track_allocation=True):
if track_allocation:
lltype.free(cp, flavor='raw', track_allocation=True)
@@ -838,6 +861,7 @@
# str -> (buf, llobj, flag)
# Can't inline this because of the raw address manipulation.
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
def get_nonmovingbuffer_ll(data):
"""
@@ -891,6 +915,7 @@
get_nonmovingbuffer_ll._annenforceargs_ = [strtype]
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
def get_nonmovingbuffer_ll_final_null(data):
tup = get_nonmovingbuffer_ll(data)
@@ -902,6 +927,7 @@
# args-from-tuple-returned-by-get_nonmoving_buffer() -> None
# Can't inline this because of the raw address manipulation.
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
def free_nonmovingbuffer_ll(buf, llobj, flag):
"""
@@ -918,6 +944,7 @@
# int -> (char*, str, int)
# Can't inline this because of the raw address manipulation.
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
def alloc_buffer(count):
"""
@@ -1096,6 +1123,7 @@
CCHARPP = lltype.Ptr(lltype.Array(CCHARP, hints={'nolength': True}))
CWCHARPP = lltype.Ptr(lltype.Array(CWCHARP, hints={'nolength': True}))
+ at sandbox_review(reviewed=True)
def liststr2charpp(l):
""" list[str] -> char**, NULL terminated
"""
@@ -1241,6 +1269,7 @@
return v_ptr
+ at sandbox_review(check_caller=True)
def structcopy(pdst, psrc):
"""Copy all the fields of the structure given by 'psrc'
into the structure given by 'pdst'.
@@ -1258,6 +1287,7 @@
if name not in padding]
unrollfields = unrolling_iterable(fields)
+ @sandbox_review(check_caller=True)
def copyfn(pdst, psrc):
for name, TYPE in unrollfields:
if isinstance(TYPE, lltype.ContainerType):
@@ -1271,6 +1301,7 @@
_get_structcopy_fn._annspecialcase_ = 'specialize:memo'
+ at sandbox_review(check_caller=True)
def setintfield(pdst, fieldname, value):
"""Maybe temporary: a helper to set an integer field into a structure,
transparently casting between the various integer types.
@@ -1405,14 +1436,14 @@
lltype.Void,
releasegil=False,
calling_conv='c',
- sandboxsafe=True,
+ sandboxsafe='check_caller',
)
c_memset = llexternal("memset",
[VOIDP, lltype.Signed, SIZE_T],
lltype.Void,
releasegil=False,
calling_conv='c',
- sandboxsafe=True,
+ sandboxsafe='check_caller',
)
diff --git a/rpython/rtyper/lltypesystem/rstr.py b/rpython/rtyper/lltypesystem/rstr.py
--- a/rpython/rtyper/lltypesystem/rstr.py
+++ b/rpython/rtyper/lltypesystem/rstr.py
@@ -3,7 +3,8 @@
from rpython.annotator import model as annmodel
from rpython.rlib import jit, types, objectmodel, rgc
from rpython.rlib.objectmodel import (malloc_zero_filled, we_are_translated,
- ll_hash_string, keepalive_until_here, specialize, enforceargs, dont_inline)
+ ll_hash_string, keepalive_until_here, specialize, enforceargs, dont_inline,
+ sandbox_review)
from rpython.rlib.signature import signature
from rpython.rlib.rarithmetic import ovfcheck
from rpython.rtyper.error import TyperError
@@ -59,6 +60,7 @@
llmemory.itemoffsetof(TP.chars, 0) +
llmemory.sizeof(CHAR_TP) * item)
+ @sandbox_review(check_caller=True)
@signature(types.any(), types.any(), types.int(), returns=types.any())
@specialize.arg(0)
def _get_raw_buf(TP, src, ofs):
@@ -75,6 +77,7 @@
_get_raw_buf._always_inline_ = True
@jit.oopspec('stroruni.copy_contents(src, dst, srcstart, dststart, length)')
+ @sandbox_review(reviewed=True)
@signature(types.any(), types.any(), types.int(), types.int(), types.int(), returns=types.none())
def copy_string_contents(src, dst, srcstart, dststart, length):
"""Copies 'length' characters from the 'src' string to the 'dst'
@@ -112,6 +115,7 @@
copy_string_contents = func_with_new_name(copy_string_contents,
'copy_%s_contents' % name)
+ @sandbox_review(check_caller=True)
@jit.oopspec('stroruni.copy_string_to_raw(src, ptrdst, srcstart, length)')
def copy_string_to_raw(src, ptrdst, srcstart, length):
"""
@@ -141,6 +145,7 @@
copy_string_to_raw._always_inline_ = True
copy_string_to_raw = func_with_new_name(copy_string_to_raw, 'copy_%s_to_raw' % name)
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
@signature(types.any(), types.any(), types.int(), types.int(),
returns=types.none())
@@ -1258,6 +1263,7 @@
return hop.gendirectcall(cls.ll_join_strs, size, vtemp)
@staticmethod
+ @sandbox_review(reviewed=True)
@jit.dont_look_inside
def ll_string2list(RESLIST, src):
length = len(src.chars)
diff --git a/rpython/translator/backendopt/all.py b/rpython/translator/backendopt/all.py
--- a/rpython/translator/backendopt/all.py
+++ b/rpython/translator/backendopt/all.py
@@ -113,7 +113,7 @@
if config.profile_based_inline and not secondary:
threshold = config.profile_based_inline_threshold
heuristic = get_function(config.profile_based_inline_heuristic)
- inline.instrument_inline_candidates(graphs, threshold)
+ inline.instrument_inline_candidates(translator, graphs, threshold)
counters = translator.driver_instrument_result(
config.profile_based_inline)
n = len(counters)
diff --git a/rpython/translator/backendopt/inline.py b/rpython/translator/backendopt/inline.py
--- a/rpython/translator/backendopt/inline.py
+++ b/rpython/translator/backendopt/inline.py
@@ -548,7 +548,8 @@
return (0.9999 * measure_median_execution_cost(graph) +
count), True # may be NaN
-def inlinable_static_callers(graphs, store_calls=False, ok_to_call=None):
+def inlinable_static_callers(translator, graphs, store_calls=False,
+ ok_to_call=None):
if ok_to_call is None:
ok_to_call = set(graphs)
result = []
@@ -558,6 +559,7 @@
else:
result.append((parentgraph, graph))
#
+ dont_inline = make_dont_inline_checker(translator)
for parentgraph in graphs:
for block in parentgraph.iterblocks():
for op in block.operations:
@@ -565,13 +567,12 @@
funcobj = op.args[0].value._obj
graph = getattr(funcobj, 'graph', None)
if graph is not None and graph in ok_to_call:
- if getattr(getattr(funcobj, '_callable', None),
- '_dont_inline_', False):
+ if dont_inline(funcobj):
continue
add(parentgraph, block, op, graph)
return result
-def instrument_inline_candidates(graphs, threshold):
+def instrument_inline_candidates(translator, graphs, threshold):
cache = {None: False}
def candidate(graph):
try:
@@ -581,6 +582,7 @@
cache[graph] = res
return res
n = 0
+ dont_inline = make_dont_inline_checker(translator)
for parentgraph in graphs:
for block in parentgraph.iterblocks():
ops = block.operations
@@ -592,8 +594,7 @@
funcobj = op.args[0].value._obj
graph = getattr(funcobj, 'graph', None)
if graph is not None:
- if getattr(getattr(funcobj, '_callable', None),
- '_dont_inline_', False):
+ if dont_inline(funcobj):
continue
if candidate(graph):
tag = Constant('inline', Void)
@@ -610,6 +611,17 @@
return (hasattr(graph, 'func') and
getattr(graph.func, '_always_inline_', None))
+def make_dont_inline_checker(translator):
+ sandbox = translator.config.translation.sandbox
+
+ def dont_inline(funcobj):
+ func = getattr(funcobj, '_callable', None)
+ if sandbox:
+ if hasattr(func, '_sandbox_review_'):
+ return True
+ return getattr(func, '_dont_inline_', False)
+ return dont_inline
+
def auto_inlining(translator, threshold=None,
callgraph=None,
call_count_pred=None,
@@ -621,7 +633,7 @@
callers = {} # {graph: {graphs-that-call-it}}
callees = {} # {graph: {graphs-that-it-calls}}
if callgraph is None:
- callgraph = inlinable_static_callers(translator.graphs)
+ callgraph = inlinable_static_callers(translator, translator.graphs)
for graph1, graph2 in callgraph:
callers.setdefault(graph2, {})[graph1] = True
callees.setdefault(graph1, {})[graph2] = True
@@ -727,7 +739,8 @@
if not hasattr(graph, 'exceptiontransformed')])
else:
ok_to_call = None
- callgraph = inlinable_static_callers(graphs, ok_to_call=ok_to_call)
+ callgraph = inlinable_static_callers(translator, graphs,
+ ok_to_call=ok_to_call)
count = auto_inlining(translator, threshold, callgraph=callgraph,
heuristic=heuristic,
call_count_pred=call_count_pred)
diff --git a/rpython/translator/backendopt/test/test_inline.py b/rpython/translator/backendopt/test/test_inline.py
--- a/rpython/translator/backendopt/test/test_inline.py
+++ b/rpython/translator/backendopt/test/test_inline.py
@@ -100,7 +100,7 @@
call_count_pred = None
if call_count_check:
call_count_pred = lambda lbl: True
- instrument_inline_candidates(t.graphs, threshold)
+ instrument_inline_candidates(t, t.graphs, threshold)
if remove_same_as:
for graph in t.graphs:
diff --git a/rpython/translator/c/genc.py b/rpython/translator/c/genc.py
--- a/rpython/translator/c/genc.py
+++ b/rpython/translator/c/genc.py
@@ -65,6 +65,11 @@
def __init__(self, translator, entrypoint, config, gcpolicy=None,
gchooks=None, secondary_entrypoints=()):
+ #
+ if config.translation.sandbox:
+ assert not config.translation.thread
+ gchooks = None # no custom gc hooks
+ #
self.translator = translator
self.entrypoint = entrypoint
self.entrypoint_name = getattr(self.entrypoint, 'func_name', None)
diff --git a/rpython/translator/c/node.py b/rpython/translator/c/node.py
--- a/rpython/translator/c/node.py
+++ b/rpython/translator/c/node.py
@@ -882,9 +882,9 @@
def new_funcnode(db, T, obj, forcename=None):
from rpython.rtyper.rtyper import llinterp_backend
- if db.sandbox:
- if (getattr(obj, 'external', None) is not None and
- not obj._safe_not_sandboxed):
+ if db.sandbox and getattr(obj, 'external', None) is not None:
+ safe_flag = obj._safe_not_sandboxed
+ if not (safe_flag is True or safe_flag == "check_caller"):
try:
sandbox_mapping = db.sandbox_mapping
except AttributeError:
diff --git a/rpython/translator/driver.py b/rpython/translator/driver.py
--- a/rpython/translator/driver.py
+++ b/rpython/translator/driver.py
@@ -412,6 +412,10 @@
if translator.annotator is not None:
translator.frozen = True
+ if self.config.translation.sandbox:
+ from rpython.translator.sandbox import graphchecker
+ graphchecker.check_all_graphs(self.translator)
+
standalone = self.standalone
get_gchooks = self.extra.get('get_gchooks', lambda: None)
gchooks = get_gchooks()
diff --git a/rpython/translator/sandbox/graphchecker.py b/rpython/translator/sandbox/graphchecker.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/sandbox/graphchecker.py
@@ -0,0 +1,138 @@
+"""Logic to check the operations in all the user graphs.
+This runs at the start of the database-c step, so it excludes the
+graphs produced later, notably for the GC. These are "low-level"
+graphs that are assumed to be safe.
+"""
+
+from rpython.flowspace.model import SpaceOperation, Constant
+from rpython.rtyper.rmodel import inputconst
+from rpython.rtyper.lltypesystem import lltype, llmemory, rstr
+from rpython.rtyper.lltypesystem.lloperation import LL_OPERATIONS
+from rpython.translator.unsimplify import varoftype
+from rpython.tool.ansi_print import AnsiLogger
+
+class UnsafeException(Exception):
+ pass
+
+log = AnsiLogger("sandbox")
+
+safe_operations = set([
+ 'keepalive', 'threadlocalref_get', 'threadlocalref_store',
+ 'malloc', 'malloc_varsize', 'free',
+ 'getfield', 'getarrayitem', 'getinteriorfield', 'raw_load',
+ 'cast_opaque_ptr', 'cast_ptr_to_int',
+ 'gc_thread_run', 'gc_stack_bottom', 'gc_thread_after_fork',
+ 'shrink_array', 'gc_pin', 'gc_unpin', 'gc_can_move', 'gc_id',
+ 'gc_identityhash', 'weakref_create', 'weakref_deref',
+ 'gc_fq_register', 'gc_fq_next_dead',
+ 'gc_set_max_heap_size', 'gc_ignore_finalizer', 'gc_add_memory_pressure',
+ 'gc_writebarrier', 'gc__collect',
+ 'length_of_simple_gcarray_from_opaque',
+ 'debug_fatalerror', 'debug_print_traceback', 'debug_flush',
+ 'hint', 'debug_start', 'debug_stop', 'debug_print', 'debug_offset',
+ 'jit_force_quasi_immutable', 'jit_force_virtual', 'jit_marker',
+ 'jit_is_virtual',
+ ])
+gc_set_operations = set([
+ 'setfield', 'setarrayitem', 'setinteriorfield',
+ ])
+for opname, opdesc in LL_OPERATIONS.items():
+ if opdesc.tryfold:
+ safe_operations.add(opname)
+
+def graph_review(graph):
+ return getattr(getattr(graph, 'func', None), '_sandbox_review_', None)
+
+def make_abort_graph(graph):
+ ll_err = rstr.conststr("reached forbidden function %r" % (graph.name,))
+ c_err = inputconst(lltype.typeOf(ll_err), ll_err)
+ op = SpaceOperation('debug_fatalerror', [c_err], varoftype(lltype.Void))
+ graph.startblock.operations.insert(0, op)
+
+def is_gc_ptr(TYPE):
+ return isinstance(TYPE, lltype.Ptr) and TYPE.TO._gckind == 'gc'
+
+
+class GraphChecker(object):
+
+ def __init__(self, translator):
+ self.translator = translator
+
+ def graph_is_unsafe(self, graph):
+ for block, op in graph.iterblockops():
+ opname = op.opname
+
+ if opname in safe_operations:
+ pass
+
+ elif opname in gc_set_operations:
+ if op.args[0].concretetype.TO._gckind != 'gc':
+ return "non-GC memory write: %r" % (op,)
+
+ elif opname == 'direct_call':
+ c_target = op.args[0]
+ assert isinstance(c_target, Constant)
+ TYPE = lltype.typeOf(c_target.value)
+ assert isinstance(TYPE.TO, lltype.FuncType)
+ obj = c_target.value._obj
+ if hasattr(obj, 'graph'):
+ g2 = obj.graph
+ if graph_review(g2) == 'check_caller':
+ return ("direct_call to a graph with "
+ "check_caller=True: %r" % (op,))
+ elif getattr(obj, '_safe_not_sandboxed', False) is not False:
+ ss = obj._safe_not_sandboxed
+ if ss is not True:
+ return ("direct_call to llfunc with "
+ "sandboxsafe=%r: %r" % (ss, obj))
+ elif getattr(obj, 'external', None) is not None:
+ # either obj._safe_not_sandboxed is True, and then it's
+ # fine; or obj._safe_not_sandboxed is False, and then
+ # this will be transformed into a stdin/stdout stub
+ pass
+ else:
+ # not 'external', but no 'graph' either?
+ return "direct_call to %r" % (obj,)
+
+ elif opname == 'indirect_call':
+ graph_list = op.args[-1].value
+ for g2 in graph_list:
+ if graph_review(g2) == 'check_caller':
+ return ("indirect_call that can go to at least one "
+ "graph with check_caller=True: %r" % (op,))
+
+ elif opname in ('cast_ptr_to_adr', 'force_cast',
+ 'cast_int_to_ptr'):
+ if is_gc_ptr(op.args[0].concretetype):
+ return "argument is a GC ptr: %r" % (opname,)
+ if is_gc_ptr(op.result.concretetype):
+ return "result is a GC ptr: %r" % (opname,)
+
+ else:
+ return "unsupported llop: %r" % (opname,)
+
+ def check(self):
+ unsafe = {}
+ for graph in self.translator.graphs:
+ review = graph_review(graph)
+ if review is not None:
+ if review in ('reviewed', 'check_caller'):
+ continue
+ elif review == 'abort':
+ make_abort_graph(graph)
+ continue
+ else:
+ assert False, repr(review)
+
+ problem = self.graph_is_unsafe(graph)
+ if problem is not None:
+ unsafe[graph] = problem
+ if unsafe:
+ raise UnsafeException(
+ '\n'.join('%r: %s' % kv for kv in unsafe.items()))
+
+
+def check_all_graphs(translator):
+ log("Checking the graphs for sandbox-unsafe operations")
+ checker = GraphChecker(translator)
+ checker.check()
diff --git a/rpython/translator/sandbox/rsandbox.py b/rpython/translator/sandbox/rsandbox.py
--- a/rpython/translator/sandbox/rsandbox.py
+++ b/rpython/translator/sandbox/rsandbox.py
@@ -6,7 +6,7 @@
import py
import sys
-from rpython.rlib import types
+from rpython.rlib import types, debug
from rpython.rlib.objectmodel import specialize
from rpython.rlib.signature import signature
from rpython.rlib.unroll import unrolling_iterable
@@ -20,6 +20,7 @@
from rpython.rtyper.llannotation import lltype_to_annotation
from rpython.rtyper.annlowlevel import MixLevelHelperAnnotator
from rpython.tool.ansi_print import AnsiLogger
+from rpython.translator.sandbox.graphchecker import make_abort_graph
log = AnsiLogger("sandbox")
@@ -99,34 +100,43 @@
lltype.typeOf(rpy_sandbox_arg[arg_kind]).TO.ARGS[0])
for arg_kind in arg_kinds])
- result_func = rpy_sandbox_res[result_kind]
- RESTYPE = FUNCTYPE.RESULT
+ if fnobj._safe_not_sandboxed == 'abort':
- try:
- lst = rtyper._sandboxed_functions
- except AttributeError:
- lst = rtyper._sandboxed_functions = []
- name_and_sig = '%s(%s)%s' % (fnname, ''.join(arg_kinds), result_kind)
- lst.append(name_and_sig)
- log(name_and_sig)
- name_and_sig = rffi.str2charp(name_and_sig, track_allocation=False)
+ msg = "sandboxed subprocess aborts on call to %r" % (fnname,)
+ def execute(*args):
+ debug.fatalerror(msg)
- def execute(*args):
- #
- # serialize the arguments
- i = 0
- for arg_kind, func, ARGTYPE in unroll_args:
- if arg_kind == 'v':
- continue
- func(rffi.cast(ARGTYPE, args[i]))
- i = i + 1
- #
- # send the function name and the arguments and wait for an answer
- result = result_func(name_and_sig)
- #
- # result the answer, if any
- if RESTYPE is not lltype.Void:
- return rffi.cast(RESTYPE, result)
+ else:
+
+ result_func = rpy_sandbox_res[result_kind]
+ RESTYPE = FUNCTYPE.RESULT
+
+ try:
+ lst = rtyper._sandboxed_functions
+ except AttributeError:
+ lst = rtyper._sandboxed_functions = []
+ name_and_sig = '%s(%s)%s' % (fnname, ''.join(arg_kinds), result_kind)
+ lst.append(name_and_sig)
+ log(name_and_sig)
+ name_and_sig = rffi.str2charp(name_and_sig, track_allocation=False)
+
+ def execute(*args):
+ #
+ # serialize the arguments
+ i = 0
+ for arg_kind, func, ARGTYPE in unroll_args:
+ if arg_kind == 'v':
+ continue
+ func(rffi.cast(ARGTYPE, args[i]))
+ i = i + 1
+ #
+ # send the function name and the arguments and wait for an answer
+ result = result_func(name_and_sig)
+ #
+ # result the answer, if any
+ if RESTYPE is not lltype.Void:
+ return rffi.cast(RESTYPE, result)
+ #
execute.__name__ = 'sandboxed_%s' % (fnname,)
#
args_s, s_result = sig_ll(fnobj)
diff --git a/rpython/translator/sandbox/test/test_graphchecker.py b/rpython/translator/sandbox/test/test_graphchecker.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/sandbox/test/test_graphchecker.py
@@ -0,0 +1,128 @@
+from rpython.translator.translator import TranslationContext, graphof
+from rpython.rtyper.lltypesystem import lltype, rffi
+from rpython.rtyper.lltypesystem.lloperation import llop
+from rpython.rlib.objectmodel import sandbox_review
+
+from rpython.translator.sandbox.graphchecker import GraphChecker
+from rpython.translator.sandbox.graphchecker import make_abort_graph
+
+
+class TestGraphIsUnsafe(object):
+
+ def graph_is_unsafe(self, fn, signature=[]):
+ t = TranslationContext()
+ self.t = t
+ t.buildannotator().build_types(fn, signature)
+ t.buildrtyper().specialize()
+ graph = graphof(t, fn)
+
+ checker = GraphChecker(t)
+ return checker.graph_is_unsafe(graph)
+
+ def check_safe(self, fn, signature=[]):
+ result = self.graph_is_unsafe(fn, signature)
+ assert result is None
+
+ def check_unsafe(self, error_substring, fn, signature=[]):
+ result = self.graph_is_unsafe(fn, signature)
+ assert result is not None
+ assert error_substring in result
+
+ def test_simple(self):
+ def f():
+ pass
+ self.check_safe(f)
+
+ def test_unsafe_setfield(self):
+ S = lltype.Struct('S', ('x', lltype.Signed))
+ s = lltype.malloc(S, flavor='raw', immortal=True)
+ def f():
+ s.x = 42
+ self.check_unsafe("non-GC memory write", f)
+
+ def test_unsafe_operation(self):
+ def f():
+ llop.debug_forked(lltype.Void)
+ self.check_unsafe("unsupported llop", f)
+
+ def test_force_cast(self):
+ SRAW = lltype.Struct('SRAW', ('x', lltype.Signed))
+ SGC = lltype.GcStruct('SGC', ('x', lltype.Signed))
+ def f(x):
+ return llop.force_cast(lltype.Signed, x)
+ self.check_safe(f, [float])
+ self.check_safe(f, [lltype.Ptr(SRAW)])
+ self.check_unsafe("argument is a GC ptr", f, [lltype.Ptr(SGC)])
+
+ def test_direct_call_to_check_caller(self):
+ @sandbox_review(check_caller=True)
+ def g():
+ pass
+ def f():
+ g()
+ self.check_unsafe("direct_call to a graph with check_caller=True", f)
+
+ def test_direct_call_to_reviewed(self):
+ @sandbox_review(reviewed=True)
+ def g():
+ pass
+ def f():
+ g()
+ self.check_safe(f)
+
+ def test_direct_call_to_abort(self):
+ @sandbox_review(abort=True)
+ def g():
+ pass
+ def f():
+ g()
+ self.check_safe(f)
+
+ def test_indirect_call_to_check_caller(self):
+ class A:
+ def meth(self, i):
+ pass
+ class B(A):
+ def meth(self, i):
+ pass
+ class C(A):
+ @sandbox_review(check_caller=True)
+ def meth(self, i):
+ pass
+ def f(i):
+ if i > 5:
+ x = B()
+ else:
+ x = C()
+ x.meth(i)
+ self.check_unsafe("indirect_call that can go to at least one "
+ "graph with check_caller=True", f, [int])
+
+ def test_direct_call_external(self):
+ llfn1 = rffi.llexternal("foobar", [], lltype.Void, sandboxsafe=True,
+ _nowrapper=True)
+ self.check_safe(lambda: llfn1())
+ #
+ llfn2 = rffi.llexternal("foobar", [], lltype.Void, sandboxsafe=False,
+ _nowrapper=True)
+ self.check_safe(lambda: llfn2()) # will be turned into an I/O stub
+ #
+ llfn2b = rffi.llexternal("foobar", [], lltype.Void,
+ sandboxsafe="check_caller",
+ _nowrapper=True)
+ self.check_unsafe("direct_call to llfunc with "
+ "sandboxsafe='check_caller'", lambda: llfn2b())
+ #
+ llfn3 = rffi.llexternal("foobar", [], lltype.Void, sandboxsafe=True)
+ self.check_safe(lambda: llfn3())
+ #
+ llfn4 = rffi.llexternal("foobar", [], lltype.Void, sandboxsafe=False)
+ self.check_safe(lambda: llfn4())
+
+ def test_make_abort_graph(self):
+ def dummy():
+ pass
+ self.check_safe(dummy)
+ graph = graphof(self.t, dummy)
+ make_abort_graph(graph)
+ assert graph.startblock.operations[0].opname == 'debug_fatalerror'
More information about the pypy-commit
mailing list