[pypy-commit] pypy py3.6-sandbox-2: hg merge sandbox-2
arigo
pypy.commits at gmail.com
Tue Aug 27 06:03:39 EDT 2019
Author: Armin Rigo <arigo at tunes.org>
Branch: py3.6-sandbox-2
Changeset: r97297:dcecc1d7905b
Date: 2019-08-27 10:38 +0200
http://bitbucket.org/pypy/pypy/changeset/dcecc1d7905b/
Log: hg merge sandbox-2
diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py
--- a/pypy/module/__pypy__/interp_time.py
+++ b/pypy/module/__pypy__/interp_time.py
@@ -6,10 +6,12 @@
from rpython.rtyper.lltypesystem import rffi, lltype
from rpython.rlib import rtime
from rpython.rlib.rtime import HAS_CLOCK_GETTIME
+from rpython.rlib.objectmodel import sandbox_review
if HAS_CLOCK_GETTIME:
+ @sandbox_review(reviewed=True)
@unwrap_spec(clk_id="c_int")
def clock_gettime(space, clk_id):
with lltype.scoped_alloc(rtime.TIMESPEC) as tp:
@@ -20,6 +22,7 @@
float(rffi.getintfield(tp, 'c_tv_nsec')) * 0.000000001)
return space.newfloat(t)
+ @sandbox_review(reviewed=True)
@unwrap_spec(clk_id="c_int")
def clock_getres(space, clk_id):
with lltype.scoped_alloc(rtime.TIMESPEC) as tp:
diff --git a/pypy/module/_io/interp_fileio.py b/pypy/module/_io/interp_fileio.py
--- a/pypy/module/_io/interp_fileio.py
+++ b/pypy/module/_io/interp_fileio.py
@@ -458,7 +458,7 @@
length = rwbuffer.getlength()
target_address = lltype.nullptr(rffi.CCHARP.TO)
- if length > 64:
+ if length > 64 and not space.config.translation.sandbox:
try:
target_address = rwbuffer.get_raw_address()
except ValueError:
@@ -480,6 +480,13 @@
else:
# optimized case: reading more than 64 bytes into a rwbuffer
# with a valid raw address
+
+ # XXX note that this is not fully safe, because we don't "lock"
+ # the buffer so we can't in theory pass its raw address to c_read().
+ # Another thread could cause it to be freed in parallel.
+ # Without proper buffer locking, it's not going to be fixed, though.
+ assert not space.config.translation.sandbox
+
while True:
got = c_read(self.fd, target_address, length)
keepalive_until_here(rwbuffer)
diff --git a/pypy/module/posix/interp_posix.py b/pypy/module/posix/interp_posix.py
--- a/pypy/module/posix/interp_posix.py
+++ b/pypy/module/posix/interp_posix.py
@@ -2227,7 +2227,13 @@
except OSError as e:
# 'rurandom' should catch and retry internally if it gets EINTR
# (at least in os.read(), which is probably enough in practice)
- raise wrap_oserror(space, e, eintr_retry=False)
+ #
+ # CPython raises NotImplementedError if /dev/urandom cannot be found.
+ # To maximize compatibility, we should also raise NotImplementedError
+ # and not OSError (although CPython also raises OSError in case it
+ # could open /dev/urandom but there are further problems).
+ raise wrap_oserror(space, e, eintr_retry=False,
+ w_exception_class=space.w_NotImplementedError)
def ctermid(space):
"""ctermid() -> string
diff --git a/rpython/rlib/jit.py b/rpython/rlib/jit.py
--- a/rpython/rlib/jit.py
+++ b/rpython/rlib/jit.py
@@ -3,7 +3,9 @@
import py
from rpython.rlib.nonconst import NonConstant
-from rpython.rlib.objectmodel import CDefinedIntSymbolic, keepalive_until_here, specialize, not_rpython, we_are_translated
+from rpython.rlib.objectmodel import CDefinedIntSymbolic, keepalive_until_here
+from rpython.rlib.objectmodel import specialize, not_rpython, we_are_translated
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rlib.unroll import unrolling_iterable
from rpython.rtyper.extregistry import ExtRegistryEntry
from rpython.tool.sourcetools import rpython_wrapper
@@ -1196,6 +1198,7 @@
def _jit_conditional_call(condition, function, *args):
pass # special-cased below
+ at sandbox_review(reviewed=True) # for the llop.jit_conditional_call
@specialize.call_location()
def conditional_call(condition, function, *args):
"""Does the same as:
@@ -1217,6 +1220,7 @@
def _jit_conditional_call_value(value, function, *args):
return value # special-cased below
+ at sandbox_review(reviewed=True) # for the llop.jit_conditional_call_value
@specialize.call_location()
def conditional_call_elidable(value, function, *args):
"""Does the same as:
diff --git a/rpython/rlib/rarithmetic.py b/rpython/rlib/rarithmetic.py
--- a/rpython/rlib/rarithmetic.py
+++ b/rpython/rlib/rarithmetic.py
@@ -878,9 +878,8 @@
Raises ParseStringOverflowError in case the result does not fit.
"""
from rpython.rlib.rstring import (
- NumberStringParser, ParseStringOverflowError, strip_spaces)
- s = literal = strip_spaces(s)
- p = NumberStringParser(s, literal, base, 'int',
+ NumberStringParser, ParseStringOverflowError)
+ p = NumberStringParser(s, s, base, 'int',
allow_underscores=allow_underscores,
no_implicit_octal=no_implicit_octal)
base = p.base
diff --git a/rpython/rlib/rbigint.py b/rpython/rlib/rbigint.py
--- a/rpython/rlib/rbigint.py
+++ b/rpython/rlib/rbigint.py
@@ -296,14 +296,17 @@
def fromstr(s, base=0, allow_underscores=False):
"""As string_to_int(), but ignores an optional 'l' or 'L' suffix
and returns an rbigint."""
+ from rpython.rlib.rstring import NumberStringParser
from rpython.rlib.rstring import NumberStringParser, \
strip_spaces
- s = literal = strip_spaces(s)
+ s = literal = strip_spaces(s) # XXX could get rid of this slice
+ end = len(s)
if (s.endswith('l') or s.endswith('L')) and base < 22:
# in base 22 and above, 'L' is a valid digit! try: long('L',22)
- s = s[:-1]
+ end -= 1
parser = NumberStringParser(s, literal, base, 'long',
- allow_underscores=allow_underscores)
+ allow_underscores=allow_underscores,
+ end=end)
return rbigint._from_numberstring_parser(parser)
@staticmethod
diff --git a/rpython/rlib/rposix.py b/rpython/rlib/rposix.py
--- a/rpython/rlib/rposix.py
+++ b/rpython/rlib/rposix.py
@@ -395,12 +395,14 @@
save_err=rffi.RFFI_SAVE_ERRNO)
c_open = external(UNDERSCORE_ON_WIN32 + 'open',
[rffi.CCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
# Win32 Unicode functions
c_wopen = external(UNDERSCORE_ON_WIN32 + 'wopen',
[rffi.CWCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
#___________________________________________________________________
# Wrappers around posix functions, that accept either strings, or
@@ -514,6 +516,7 @@
c_close = external(UNDERSCORE_ON_WIN32 + 'close', [rffi.INT], rffi.INT,
releasegil=False, save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('read')
@signature(types.int(), types.int(), returns=types.any())
def read(fd, count):
@@ -525,6 +528,7 @@
got = handle_posix_error('read', c_read(fd, void_buf, count))
return buf.str(got)
+ at sandbox_review(reviewed=True)
@replace_os_function('write')
@signature(types.int(), types.any(), returns=types.any())
def write(fd, data):
@@ -649,13 +653,13 @@
#___________________________________________________________________
c_chdir = external('chdir', [rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_fchdir = external('fchdir', [rffi.INT], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
c_access = external(UNDERSCORE_ON_WIN32 + 'access',
- [rffi.CCHARP, rffi.INT], rffi.INT)
+ [rffi.CCHARP, rffi.INT], rffi.INT, sandboxsafe="nowrite")
c_waccess = external(UNDERSCORE_ON_WIN32 + 'waccess',
- [rffi.CWCHARP, rffi.INT], rffi.INT)
+ [rffi.CWCHARP, rffi.INT], rffi.INT, sandboxsafe="nowrite")
@replace_os_function('chdir')
@specialize.argtype(0)
@@ -753,6 +757,7 @@
[rffi.CWCHARP, rffi.SIZE_T], rffi.CWCHARP,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('getcwd')
def getcwd():
bufsize = 256
@@ -773,6 +778,7 @@
lltype.free(buf, flavor='raw')
return result
+ at sandbox_review(reviewed=True)
@replace_os_function('getcwdu')
def getcwdu():
bufsize = 256
@@ -811,9 +817,11 @@
DIRENT = dirent_config['DIRENT']
DIRENTP = lltype.Ptr(DIRENT)
c_opendir = external('opendir',
- [rffi.CCHARP], DIRP, save_err=rffi.RFFI_SAVE_ERRNO)
+ [rffi.CCHARP], DIRP, save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
c_fdopendir = external('fdopendir',
- [rffi.INT], DIRP, save_err=rffi.RFFI_SAVE_ERRNO)
+ [rffi.INT], DIRP, save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
c_rewinddir = external('rewinddir',
[DIRP], lltype.Void, releasegil=False)
# XXX macro=True is hack to make sure we get the correct kind of
@@ -828,6 +836,7 @@
else:
dirent_config = {}
+ at sandbox_review(reviewed=True)
def _listdir(dirp, rewind=False):
result = []
while True:
@@ -847,6 +856,7 @@
return result
if not _WIN32:
+ @sandbox_review(reviewed=True)
def fdlistdir(dirfd):
"""
Like listdir(), except that the directory is specified as an open
@@ -921,17 +931,17 @@
#___________________________________________________________________
c_execv = external('execv', [rffi.CCHARP, rffi.CCHARPP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_execve = external('execve',
[rffi.CCHARP, rffi.CCHARPP, rffi.CCHARPP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_spawnv = external(UNDERSCORE_ON_WIN32 + 'spawnv',
[rffi.INT, rffi.CCHARP, rffi.CCHARPP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_spawnve = external(UNDERSCORE_ON_WIN32 + 'spawnve',
[rffi.INT, rffi.CCHARP, rffi.CCHARPP, rffi.CCHARPP],
rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
@replace_os_function('execv')
def execv(path, args):
@@ -1006,6 +1016,7 @@
debug.debug_forked(ofs)
return childpid
+ at sandbox_review(reviewed=True)
@replace_os_function('openpty')
@jit.dont_look_inside
def openpty():
@@ -1110,6 +1121,7 @@
c_getloadavg = external('getloadavg',
[rffi.CArrayPtr(lltype.Float), rffi.INT], rffi.INT)
+ at sandbox_review(reviewed=True)
@replace_os_function('getlogin')
def getlogin():
result = c_getlogin()
@@ -1117,6 +1129,7 @@
raise OSError(get_saved_errno(), "getlogin failed")
return rffi.charp2str(result)
+ at sandbox_review(reviewed=True)
@replace_os_function('getloadavg')
def getloadavg():
load = lltype.malloc(rffi.CArrayPtr(lltype.Float).TO, 3, flavor='raw')
@@ -1134,6 +1147,7 @@
[rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('readlink')
def readlink(path):
path = _as_bytes0(path)
@@ -1168,6 +1182,7 @@
releasegil=False,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('ttyname')
def ttyname(fd):
l_name = c_ttyname(fd)
@@ -1178,6 +1193,7 @@
c_strerror = external('strerror', [rffi.INT], rffi.CCHARP,
releasegil=False, sandboxsafe=True)
+ at sandbox_review(reviewed=True)
@replace_os_function('strerror')
def strerror(errnum):
res = c_strerror(errnum)
@@ -1185,20 +1201,20 @@
raise ValueError("os_strerror failed")
return rffi.charp2str(res)
-c_system = external('system', [rffi.CCHARP], rffi.INT)
+c_system = external('system', [rffi.CCHARP], rffi.INT, sandboxsafe="nowrite")
@replace_os_function('system')
def system(command):
return widen(c_system(command))
c_unlink = external('unlink', [rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_mkdir = external('mkdir', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_rmdir = external(UNDERSCORE_ON_WIN32 + 'rmdir', [rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_wrmdir = external(UNDERSCORE_ON_WIN32 + 'wrmdir', [rffi.CWCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
@replace_os_function('unlink')
@specialize.argtype(0)
@@ -1232,11 +1248,11 @@
handle_posix_error('rmdir', c_rmdir(_as_bytes0(path)))
c_chmod = external('chmod', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_fchmod = external('fchmod', [rffi.INT, rffi.MODE_T], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO,)
c_rename = external('rename', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
@replace_os_function('chmod')
@specialize.argtype(0)
@@ -1293,10 +1309,11 @@
#___________________________________________________________________
c_mkfifo = external('mkfifo', [rffi.CCHARP, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_mknod = external('mknod', [rffi.CCHARP, rffi.MODE_T, rffi.INT], rffi.INT,
# # xxx: actually ^^^ dev_t
- macro=_MACRO_ON_POSIX, save_err=rffi.RFFI_SAVE_ERRNO)
+ macro=_MACRO_ON_POSIX, save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
@replace_os_function('mkfifo')
@specialize.argtype(0)
@@ -1337,6 +1354,7 @@
c_pipe2 = external('pipe2', [INT_ARRAY_P, rffi.INT], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('pipe')
def pipe(flags=0):
# 'flags' might be ignored. Check the result.
@@ -1373,6 +1391,7 @@
finally:
lltype.free(filedes, flavor='raw')
+ at sandbox_review(reviewed=True)
def pipe2(flags):
# Only available if there is really a c_pipe2 function.
# No fallback to pipe() if we get ENOSYS.
@@ -1385,9 +1404,9 @@
lltype.free(filedes, flavor='raw')
c_link = external('link', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO,)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_symlink = external('symlink', [rffi.CCHARP, rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
#___________________________________________________________________
@@ -1420,9 +1439,9 @@
return widen(c_umask(newmask))
c_chown = external('chown', [rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_lchown = external('lchown', [rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_fchown = external('fchown', [rffi.INT, rffi.INT, rffi.INT], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
@@ -1581,6 +1600,7 @@
lltype.Ptr(rwin32.FILETIME), lltype.Ptr(rwin32.FILETIME)],
rwin32.BOOL, calling_conv='win')
+ at sandbox_review(reviewed=True)
@replace_os_function('times')
def times():
if not _WIN32:
@@ -1680,12 +1700,14 @@
c_ctermid = external('ctermid', [rffi.CCHARP], rffi.CCHARP)
+ at sandbox_review(reviewed=True)
@replace_os_function('ctermid')
def ctermid():
return rffi.charp2str(c_ctermid(lltype.nullptr(rffi.CCHARP.TO)))
c_tmpnam = external('tmpnam', [rffi.CCHARP], rffi.CCHARP)
+ at sandbox_review(reviewed=True)
@replace_os_function('tmpnam')
def tmpnam():
return rffi.charp2str(c_tmpnam(lltype.nullptr(rffi.CCHARP.TO)))
@@ -1737,8 +1759,10 @@
c_setgroups = external('setgroups', [rffi.SIZE_T, GID_GROUPS_T], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
c_initgroups = external('initgroups', [rffi.CCHARP, GID_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO,
+ sandboxsafe="nowrite")
+ at sandbox_review(reviewed=True)
@replace_os_function('getgroups')
def getgroups():
n = handle_posix_error('getgroups',
@@ -1886,6 +1910,7 @@
c_setresgid = external('setresgid', [GID_T] * 3, rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
+ @sandbox_review(reviewed=True)
@replace_os_function('getresuid')
def getresuid():
out = lltype.malloc(UID_T_P.TO, 3, flavor='raw')
@@ -1898,6 +1923,7 @@
finally:
lltype.free(out, flavor='raw')
+ @sandbox_review(reviewed=True)
@replace_os_function('getresgid')
def getresgid():
out = lltype.malloc(GID_T_P.TO, 3, flavor='raw')
@@ -1956,6 +1982,7 @@
c_chroot = external('chroot', [rffi.CCHARP], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO,
macro=_MACRO_ON_POSIX,
+ sandboxsafe="nowrite",
compilation_info=ExternalCompilationInfo(includes=['unistd.h']))
@replace_os_function('chroot')
@@ -1981,6 +2008,7 @@
compilation_info=CConfig._compilation_info_,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('uname')
def uname():
l_utsbuf = lltype.malloc(UTSNAMEP.TO, flavor='raw')
@@ -2024,7 +2052,8 @@
c_fpathconf = external('fpathconf', [rffi.INT, rffi.INT], rffi.LONG,
save_err=rffi.RFFI_FULL_ERRNO_ZERO)
c_pathconf = external('pathconf', [rffi.CCHARP, rffi.INT], rffi.LONG,
- save_err=rffi.RFFI_FULL_ERRNO_ZERO)
+ save_err=rffi.RFFI_FULL_ERRNO_ZERO,
+ sandboxsafe="nowrite")
c_confstr = external('confstr',
[rffi.INT, rffi.CCHARP, rffi.SIZE_T], rffi.SIZE_T,
save_err=rffi.RFFI_FULL_ERRNO_ZERO)
@@ -2056,6 +2085,7 @@
raise OSError(errno, "pathconf failed")
return res
+ at sandbox_review(reviewed=True)
@replace_os_function('confstr')
def confstr(value):
n = intmask(c_confstr(value, lltype.nullptr(rffi.CCHARP.TO), 0))
@@ -2129,7 +2159,8 @@
if HAVE_FACCESSAT:
c_faccessat = external('faccessat',
- [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT)
+ [rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
+ sandboxsafe="nowrite")
def faccessat(pathname, mode, dir_fd=AT_FDCWD,
effective_ids=False, follow_symlinks=True):
@@ -2147,7 +2178,7 @@
if HAVE_FCHMODAT:
c_fchmodat = external('fchmodat',
[rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO,)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def fchmodat(path, mode, dir_fd=AT_FDCWD, follow_symlinks=True):
if follow_symlinks:
@@ -2160,7 +2191,7 @@
if HAVE_FCHOWNAT:
c_fchownat = external('fchownat',
[rffi.INT, rffi.CCHARP, rffi.INT, rffi.INT, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO,)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def fchownat(path, owner, group, dir_fd=AT_FDCWD,
follow_symlinks=True, empty_path=False):
@@ -2175,7 +2206,7 @@
if HAVE_FEXECVE:
c_fexecve = external('fexecve',
[rffi.INT, rffi.CCHARPP, rffi.CCHARPP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def fexecve(fd, args, env):
envstrs = []
@@ -2196,7 +2227,7 @@
c_linkat = external(
'linkat',
[rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def linkat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD,
follow_symlinks=True):
@@ -2290,7 +2321,7 @@
if HAVE_MKDIRAT:
c_mkdirat = external('mkdirat',
[rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def mkdirat(pathname, mode, dir_fd=AT_FDCWD):
error = c_mkdirat(dir_fd, pathname, mode)
@@ -2299,7 +2330,7 @@
if HAVE_UNLINKAT:
c_unlinkat = external('unlinkat',
[rffi.INT, rffi.CCHARP, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def unlinkat(pathname, dir_fd=AT_FDCWD, removedir=False):
flag = AT_REMOVEDIR if removedir else 0
@@ -2337,7 +2368,7 @@
c_renameat = external(
'renameat',
[rffi.INT, rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def renameat(src, dst, src_dir_fd=AT_FDCWD, dst_dir_fd=AT_FDCWD):
error = c_renameat(src_dir_fd, src, dst_dir_fd, dst)
@@ -2347,7 +2378,7 @@
if HAVE_SYMLINKAT:
c_symlinkat = external('symlinkat',
[rffi.CCHARP, rffi.INT, rffi.CCHARP], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def symlinkat(src, dst, dir_fd=AT_FDCWD):
error = c_symlinkat(src, dir_fd, dst)
@@ -2356,7 +2387,7 @@
if HAVE_OPENAT:
c_openat = external('openat',
[rffi.INT, rffi.CCHARP, rffi.INT, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
@enforceargs(s_Str0, int, int, int, typecheck=False)
def openat(path, flags, mode, dir_fd=AT_FDCWD):
@@ -2366,7 +2397,7 @@
if HAVE_MKFIFOAT:
c_mkfifoat = external('mkfifoat',
[rffi.INT, rffi.CCHARP, rffi.MODE_T], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def mkfifoat(path, mode, dir_fd=AT_FDCWD):
error = c_mkfifoat(dir_fd, path, mode)
@@ -2375,7 +2406,7 @@
if HAVE_MKNODAT:
c_mknodat = external('mknodat',
[rffi.INT, rffi.CCHARP, rffi.MODE_T, rffi.INT], rffi.INT,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
def mknodat(path, mode, device, dir_fd=AT_FDCWD):
error = c_mknodat(dir_fd, path, mode, device)
@@ -2687,29 +2718,29 @@
[rffi.INT, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_setxattr = external('setxattr',
[rffi.CCHARP, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_lsetxattr = external('lsetxattr',
[rffi.CCHARP, rffi.CCHARP, rffi.CCHARP, rffi.SIZE_T, rffi.INT],
rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_fremovexattr = external('fremovexattr',
[rffi.INT, rffi.CCHARP], rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_removexattr = external('removexattr',
[rffi.CCHARP, rffi.CCHARP], rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_lremovexattr = external('lremovexattr',
[rffi.CCHARP, rffi.CCHARP], rffi.INT,
compilation_info=CConfig._compilation_info_,
- save_err=rffi.RFFI_SAVE_ERRNO)
+ save_err=rffi.RFFI_SAVE_ERRNO, sandboxsafe="nowrite")
c_flistxattr = external('flistxattr',
[rffi.INT, rffi.CCHARP, rffi.SIZE_T], rffi.SSIZE_T,
compilation_info=CConfig._compilation_info_,
@@ -2724,6 +2755,7 @@
save_err=rffi.RFFI_SAVE_ERRNO)
buf_sizes = [256, XATTR_SIZE_MAX]
+ @sandbox_review(reviewed=True)
def fgetxattr(fd, name):
for size in buf_sizes:
with rffi.scoped_alloc_buffer(size) as buf:
@@ -2738,6 +2770,7 @@
else:
raise OSError(errno.ERANGE, 'fgetxattr failed')
+ @sandbox_review(reviewed=True)
def getxattr(path, name, follow_symlinks=True):
for size in buf_sizes:
with rffi.scoped_alloc_buffer(size) as buf:
@@ -2783,6 +2816,7 @@
del result[-1]
return result
+ @sandbox_review(reviewed=True)
def flistxattr(fd):
for size in buf_sizes:
with rffi.scoped_alloc_buffer(size) as buf:
@@ -2796,6 +2830,7 @@
else:
raise OSError(errno.ERANGE, 'flistxattr failed')
+ @sandbox_review(reviewed=True)
def listxattr(path, follow_symlinks=True):
for size in buf_sizes:
with rffi.scoped_alloc_buffer(size) as buf:
diff --git a/rpython/rlib/rposix_environ.py b/rpython/rlib/rposix_environ.py
--- a/rpython/rlib/rposix_environ.py
+++ b/rpython/rlib/rposix_environ.py
@@ -2,7 +2,7 @@
import sys
from rpython.annotator import model as annmodel
from rpython.rlib._os_support import _WIN32, StringTraits, UnicodeTraits
-from rpython.rlib.objectmodel import enforceargs
+from rpython.rlib.objectmodel import enforceargs, sandbox_review
# importing rposix here creates a cycle on Windows
from rpython.rtyper.controllerentry import Controller
from rpython.rtyper.lltypesystem import rffi, lltype
@@ -148,6 +148,7 @@
byname, eq = envkeepalive.bywname, u'='
from rpython.rlib.rwin32 import lastSavedWindowsError as last_error
+ @sandbox_review(reviewed=True)
def envitems_llimpl():
environ = get_environ()
result = []
@@ -162,11 +163,13 @@
i += 1
return result
+ @sandbox_review(reviewed=True)
def getenv_llimpl(name):
with traits.scoped_str2charp(name) as l_name:
l_result = getenv(l_name)
return traits.charp2str(l_result) if l_result else None
+ @sandbox_review(reviewed=True)
def putenv_llimpl(name, value):
l_string = traits.str2charp(name + eq + value)
error = rffi.cast(lltype.Signed, putenv(l_string))
@@ -196,6 +199,7 @@
os_unsetenv = llexternal('unsetenv', [rffi.CCHARP], rffi.INT,
save_err=rffi.RFFI_SAVE_ERRNO)
+ @sandbox_review(reviewed=True)
def r_unsetenv(name):
with rffi.scoped_str2charp(name) as l_name:
error = rffi.cast(lltype.Signed, os_unsetenv(l_name))
diff --git a/rpython/rlib/rposix_stat.py b/rpython/rlib/rposix_stat.py
--- a/rpython/rlib/rposix_stat.py
+++ b/rpython/rlib/rposix_stat.py
@@ -18,6 +18,7 @@
from rpython.rlib._os_support import _preferred_traits, string_traits
from rpython.rlib.objectmodel import specialize, we_are_translated, not_rpython
+from rpython.rlib.objectmodel import sandbox_review
from rpython.rtyper.lltypesystem import lltype, rffi
from rpython.translator.tool.cbuild import ExternalCompilationInfo
from rpython.rlib.rarithmetic import intmask
@@ -534,6 +535,7 @@
compilation_info=compilation_info,
save_err=rffi.RFFI_SAVE_ERRNO)
+ at sandbox_review(reviewed=True)
@replace_os_function('fstat')
def fstat(fd):
if not _WIN32:
@@ -574,6 +576,7 @@
finally:
lltype.free(info, flavor='raw')
+ at sandbox_review(reviewed=True)
@replace_os_function('stat')
@specialize.argtype(0)
def stat(path):
@@ -587,6 +590,7 @@
path = traits.as_str0(path)
return win32_xstat(traits, path, traverse=True)
+ at sandbox_review(reviewed=True)
@replace_os_function('lstat')
@specialize.argtype(0)
def lstat(path):
@@ -639,12 +643,14 @@
handle_posix_error('fstatat', error)
return build_stat_result(stresult)
+ at sandbox_review(reviewed=True)
@replace_os_function('fstatvfs')
def fstatvfs(fd):
with lltype.scoped_alloc(STATVFS_STRUCT.TO) as stresult:
handle_posix_error('fstatvfs', c_fstatvfs(fd, stresult))
return build_statvfs_result(stresult)
+ at sandbox_review(reviewed=True)
@replace_os_function('statvfs')
@specialize.argtype(0)
def statvfs(path):
diff --git a/rpython/rlib/rstring.py b/rpython/rlib/rstring.py
--- a/rpython/rlib/rstring.py
+++ b/rpython/rlib/rstring.py
@@ -500,26 +500,34 @@
(self.fname, self.original_base))
def __init__(self, s, literal, base, fname, allow_underscores=False,
- no_implicit_octal=False):
+ no_implicit_octal=False, start=0, end=-1):
self.fname = fname
sign = 1
- if s.startswith('-'):
+ self.s = s
+ self.start = start
+ if end == -1:
+ end = len(s)
+ self.end = end
+ self._strip_spaces()
+ if self._startswith('-'):
sign = -1
- s = strip_spaces(s[1:])
- elif s.startswith('+'):
- s = strip_spaces(s[1:])
+ self.start += 1
+ self._strip_spaces()
+ elif self._startswith('+'):
+ self.start += 1
+ self._strip_spaces()
self.sign = sign
self.original_base = base
self.allow_underscores = allow_underscores
if base == 0:
- if s.startswith('0x') or s.startswith('0X'):
+ if self._startswith('0x') or self._startswith('0X'):
base = 16
- elif s.startswith('0b') or s.startswith('0B'):
+ elif self._startswith('0b') or self._startswith('0B'):
base = 2
- elif s.startswith('0'): # also covers the '0o' case
- if no_implicit_octal and not (s.startswith('0o') or
- s.startswith('0O')):
+ elif self._startswith('0'): # also covers the '0o' case
+ if no_implicit_octal and not (self._startswith('0o') or
+ self._startswith('0O')):
base = 1 # this makes only the digit '0' valid...
else:
base = 8
@@ -530,30 +538,44 @@
self.base = base
# Leading underscores are not allowed
- if s.startswith('_'):
+ if self._startswith('_'):
self.error()
- if base == 16 and (s.startswith('0x') or s.startswith('0X')):
- s = s[2:]
- if base == 8 and (s.startswith('0o') or s.startswith('0O')):
- s = s[2:]
- if base == 2 and (s.startswith('0b') or s.startswith('0B')):
- s = s[2:]
- if not s:
+ if base == 16 and (self._startswith('0x') or self._startswith('0X')):
+ self.start += 2
+ if base == 8 and (self._startswith('0o') or self._startswith('0O')):
+ self.start += 2
+ if base == 2 and (self._startswith('0b') or self._startswith('0B')):
+ self.start += 2
+ if self.start == self.end:
self.error()
- self.s = s
- self.n = len(s)
- self.i = 0
+ self.i = self.start
+
+ def _startswith(self, prefix):
+ return startswith(self.s, prefix, start=self.start, end=self.end)
+
+ def _strip_spaces(self):
+ # XXX this is not locale-dependent
+ p = self.start
+ q = self.end
+ s = self.s
+ while p < q and s[p] in ' \f\n\r\t\v':
+ p += 1
+ while p < q and s[q-1] in ' \f\n\r\t\v':
+ q -= 1
+ assert q >= p
+ self.start = p
+ self.end = q
def rewind(self):
self.i = 0
def next_digit(self): # -1 => exhausted
- if self.i < self.n:
+ if self.i < self.end:
c = self.s[self.i]
if self.allow_underscores and c == '_':
self.i += 1
- if self.i >= self.n:
+ if self.i >= self.end:
self.error()
c = self.s[self.i]
digit = ord(c)
@@ -576,7 +598,7 @@
# After exhausting all n digits in next_digit(), you can walk them
# again in reverse order by calling prev_digit() exactly n times
i = self.i - 1
- assert i >= 0
+ assert i >= self.start
self.i = i
c = self.s[i]
if self.allow_underscores and c == '_':
diff --git a/rpython/rlib/rtime.py b/rpython/rlib/rtime.py
--- a/rpython/rlib/rtime.py
+++ b/rpython/rlib/rtime.py
@@ -236,6 +236,7 @@
diff = a[0] - state.counter_start
return float(diff) / state.divisor
+ at sandbox_review(reviewed=True)
@replace_time_function('clock')
def clock():
if _WIN32:
diff --git a/rpython/rlib/test/test_rarithmetic.py b/rpython/rlib/test/test_rarithmetic.py
--- a/rpython/rlib/test/test_rarithmetic.py
+++ b/rpython/rlib/test/test_rarithmetic.py
@@ -337,6 +337,10 @@
res = self.interpret(f, [123])
assert res == 4 + 2
+ def test_string_to_int_translates(self):
+ def f(s):
+ return string_to_int(str(s))
+ self.interpret(f, [123]) == 123
def test_int_real_union():
from rpython.rtyper.lltypesystem.rffi import r_int_real
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
@@ -102,7 +102,13 @@
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.
+ which means that we need to also check the callers. If
+ "nowrite", we don't need to check the callers. The default
+ of False either implies "check_caller" or "nowrite"
+ depending on whether the function takes and returns pointer
+ arguments or not. Use "nowrite" only if the external
+ function call will only *read* from 'char *' or other data
+ structures passed in.
calling_conv: if 'unknown' or 'win', the C function is not directly seen
by the JIT. If 'c', it can be seen (depending on
@@ -344,13 +350,19 @@
wrapper = sandbox_review(check_caller=True)(wrapper)
elif sandboxsafe == 'abort':
wrapper = sandbox_review(abort=True)(wrapper)
+ elif sandboxsafe == 'nowrite':
+ wrapper = sandbox_review(reviewed=True)(wrapper)
else:
assert isinstance(sandboxsafe, bool)
- wrapper = sandbox_review(reviewed=True)(wrapper)
+ if sandboxsafe or (all(_sandbox_type_safe(ARG) for ARG in args) and
+ _sandbox_type_safe(result)):
+ wrapper = sandbox_review(reviewed=True)(wrapper)
+ else:
+ wrapper = sandbox_review(check_caller=True)(wrapper)
return wrapper
-def sandbox_check_type(TYPE):
- return not isinstance(TYPE, lltype.Primitive) or TYPE == llmemory.Address
+def _sandbox_type_safe(TYPE):
+ return isinstance(TYPE, lltype.Primitive) and TYPE != llmemory.Address
class CallbackHolder:
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(translator, graphs, threshold)
+ inline.instrument_inline_candidates(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,8 +548,7 @@
return (0.9999 * measure_median_execution_cost(graph) +
count), True # may be NaN
-def inlinable_static_callers(translator, graphs, store_calls=False,
- ok_to_call=None):
+def inlinable_static_callers(graphs, store_calls=False, ok_to_call=None):
if ok_to_call is None:
ok_to_call = set(graphs)
result = []
@@ -559,7 +558,6 @@
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:
@@ -567,12 +565,13 @@
funcobj = op.args[0].value._obj
graph = getattr(funcobj, 'graph', None)
if graph is not None and graph in ok_to_call:
- if dont_inline(funcobj):
+ if getattr(getattr(funcobj, '_callable', None),
+ '_dont_inline_', False):
continue
add(parentgraph, block, op, graph)
return result
-def instrument_inline_candidates(translator, graphs, threshold):
+def instrument_inline_candidates(graphs, threshold):
cache = {None: False}
def candidate(graph):
try:
@@ -582,7 +581,6 @@
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
@@ -594,7 +592,8 @@
funcobj = op.args[0].value._obj
graph = getattr(funcobj, 'graph', None)
if graph is not None:
- if dont_inline(funcobj):
+ if getattr(getattr(funcobj, '_callable', None),
+ '_dont_inline_', False):
continue
if candidate(graph):
tag = Constant('inline', Void)
@@ -611,17 +610,6 @@
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,
@@ -633,7 +621,7 @@
callers = {} # {graph: {graphs-that-call-it}}
callees = {} # {graph: {graphs-that-it-calls}}
if callgraph is None:
- callgraph = inlinable_static_callers(translator, translator.graphs)
+ callgraph = inlinable_static_callers(translator.graphs)
for graph1, graph2 in callgraph:
callers.setdefault(graph2, {})[graph1] = True
callees.setdefault(graph1, {})[graph2] = True
@@ -739,8 +727,7 @@
if not hasattr(graph, 'exceptiontransformed')])
else:
ok_to_call = None
- callgraph = inlinable_static_callers(translator, graphs,
- ok_to_call=ok_to_call)
+ callgraph = inlinable_static_callers(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, t.graphs, threshold)
+ instrument_inline_candidates(t.graphs, threshold)
if remove_same_as:
for graph in t.graphs:
diff --git a/rpython/translator/driver.py b/rpython/translator/driver.py
--- a/rpython/translator/driver.py
+++ b/rpython/translator/driver.py
@@ -344,6 +344,12 @@
rtyper = self.translator.buildrtyper()
rtyper.specialize(dont_simplify_again=True)
+ # we do the sandbox review checking here, before inlining graphs
+ # inside each other (and later generating extra graphs for the GC).
+ if self.config.translation.sandbox:
+ from rpython.translator.sandbox import graphchecker
+ graphchecker.check_all_graphs(self.translator)
+
@taskdef([RTYPE], "JIT compiler generation")
def task_pyjitpl_lltype(self):
""" Generate bytecodes for JIT and flow the JIT helper functions
@@ -412,10 +418,6 @@
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
--- a/rpython/translator/sandbox/graphchecker.py
+++ b/rpython/translator/sandbox/graphchecker.py
@@ -2,8 +2,92 @@
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.
+
+Here are again the rules around this check.
+
+- any graph that contains only "safe" lloperations is itself "safe".
+ The "safe" lloperations are the ones marked "tryfold" in
+ rtyper.lltypesystem.lloperation, plus the ones listed explicitly below,
+ plus a few variants of specific operations coded in graph_in_unsafe().
+
+- any graph decorated with @objectmodel.sandbox_review() is "safe".
+ The different flags we can pass to @sandbox_review() are explained next,
+ but the decorated graph is itself always "safe".
+
+- "unsafe" operations are all special rare operations, plus most importantly
+ all *writes* into raw memory. We assume that *reads* from anywhere are
+ OK to ignore: any information that reaches the sandboxed process can be
+ detected and used by anything that runs inside this process (i.e. there
+ is no really "secret" data inside the sandboxed subprocess itself).
+ At worst, random reads will lead to segfaults. But random writes are not
+ safe because that could corrupt memory---e.g. overwrite some GC object
+ header, or even (although I'm not sure how) actually cause the sandboxed
+ process to misbehave in more important ways like doing actual system calls
+ that are supposed to be forbidden.
+
+- the decorator @sandbox_review(check_caller=True) means that the graph is
+ safe, but any call to this graph from somewhere else is an unsafe operation.
+ This forces all callers to also be reviewed and marked with some form of
+ @sandbox_review().
+
+- @sandbox_review(reviewed=True) means that the graph is safe and all
+ calls to this graph are also safe. This should only be used on functions
+ that do internally "unsafe" stuff like writing to raw memory but don't
+ take arguments that could lead them to do bogus things. A typical counter-
+ example is a function that takes a raw pointer and that writes something to
+ it; this should *not* be marked with reviewed=True. On the other hand, many
+ RPython wrappers to external C functions can be reviewed=True because
+ they translate GC-safe information (say an RPython string) to raw memory,
+ do the call, and translate the result back to GC-safe information.
+
+- @sandbox_review(abort=True) is reserved for cases where calling this
+ function at runtime should just immediately abort the subprocess.
+
+Note that all flags above should be considered independently of what the
+actual C function calls are supposed to do. For example, the RPython
+wrapper rposix.system() is something you definitely don't want to allow as-is,
+but the wrapper and the call to the C function are fine. It's up to the
+controlling process to refuse to reply to the system() external call
+(either by having it return ENOSYS or a similar error, or by killing the
+sandboxed process completely).
+
+Like system(), all calls to external C functions are *by default* removed and
+turned into I/O on stdin/stdout, asking the parent controlling process what
+to do. This is controlled in more details by rffi.llexternal(). It takes
+its own argument "sandboxsafe", which can be one of the following values:
+
+- sandboxsafe=False (the default): the external C call is not done but turned
+ into I/O on stdin/stdout. Moreover, *if* the function takes or returns a
+ raw pointer, then it is flagged with @sandbox_review(check_caller=True) to
+ ensure that all callers do something sane with these raw pointers. If
+ the C function only takes and returns integer or float arguments, there is
+ no real need, so in this case we flag @sandbox_review(reviewed=True) instead.
+
+- sandboxsafe=True: means the external call should be done straight from the
+ sandboxed process. Reserved for specific functions like rposix.c_strerror(),
+ or some memory-manipulation functions used by the GC itself.
+
+- sandboxsafe="abort": like @sandbox_review(abort=True).
+
+- sandboxsafe="check_caller": forces @sandbox_review(check_caller=True).
+ Useful for llexternal() functions that appear to return an integer but
+ that's really some address that must be carefully managed.
+
+- sandboxsafe="nowrite": forces @sandbox_review(reviewed=True). This is OK
+ for C functions that have pointer arguments but none of them can point
+ to anything that will be written to (hence the name). The idea is that
+ for the common case of a function that takes a "const char *" argument,
+ we should just mark that function as reviewed=True, because it is safe:
+ the controller process will at most read things from the sandboxed process,
+ namely what the pointer points to, but it should not attempt to do any
+ write into the sandboxed process' memory. Typically the caller itself
+ calls rffi.str2charp() and rffi.free_charp() around the call, but these
+ are also @sandbox_review(reviewed=True) helpers, so such a caller doesn't
+ need to be explicitly reviewed.
+
"""
+
from rpython.flowspace.model import SpaceOperation, Constant
from rpython.rtyper.rmodel import inputconst
from rpython.rtyper.lltypesystem import lltype, llmemory, rstr
@@ -105,7 +189,7 @@
elif opname in ('cast_ptr_to_adr', 'force_cast',
'cast_int_to_ptr'):
if is_gc_ptr(op.result.concretetype):
- return "result is a GC ptr: %r" % (opname,)
+ return "result is a GC ptr: %r" % (op,)
else:
return "unsupported llop: %r" % (opname,)
diff --git a/rpython/translator/sandbox/sandlib.py b/rpython/translator/sandbox/sandlib.py
deleted file mode 100644
--- a/rpython/translator/sandbox/sandlib.py
+++ /dev/null
@@ -1,517 +0,0 @@
-"""
-A Python library to execute and communicate with a subprocess that
-was translated from RPython code with --sandbox. This library is
-for the outer process, which can run CPython or PyPy.
-"""
-
-import sys, os, posixpath, errno, stat, time
-import subprocess
-from rpython.tool.killsubprocess import killsubprocess
-from rpython.translator.sandbox.vfs import UID, GID
-import py
-
-WIN32 = os.name == "nt"
-
-
-def create_log():
- """Make and return a log for the sandbox to use, if needed."""
- from rpython.tool.ansi_print import AnsiLogger
- return AnsiLogger("sandlib")
-
-def write_exception(g, exception, tb=None):
- for i, excclass in EXCEPTION_TABLE:
- if isinstance(exception, excclass):
- write_message(g, i)
- if excclass is OSError:
- error = exception.errno
- if error is None:
- error = errno.EPERM
- write_message(g, error)
- g.flush()
- break
- else:
- # just re-raise the exception
- raise exception.__class__, exception, tb
-
-def shortrepr(x):
- r = repr(x)
- if len(r) >= 80:
- r = r[:20] + '...' + r[-8:]
- return r
-
-def signal_name(n):
- import signal
- for key, value in signal.__dict__.items():
- if key.startswith('SIG') and not key.startswith('SIG_') and value == n:
- return key
- return 'signal %d' % (n,)
-
-
-class SandboxedProc(object):
- """Base class to control a sandboxed subprocess.
- Inherit from this class and implement all the do_xxx() methods
- for the external functions xxx that you want to support.
- """
- debug = False
- log = None
- os_level_sandboxing = False # Linux only: /proc/PID/seccomp
-
- def __init__(self, args, executable=None):
- """'args' should a sequence of argument for the subprocess,
- starting with the full path of the executable.
- """
- self.popen = subprocess.Popen(args, executable=executable,
- bufsize=-1,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- close_fds=False if WIN32 else True,
- env={})
- self.popenlock = None
- self.currenttimeout = None
- self.currentlyidlefrom = None
-
- if self.debug:
- self.log = create_log()
-
- def withlock(self, function, *args, **kwds):
- lock = self.popenlock
- if lock is not None:
- lock.acquire()
- try:
- return function(*args, **kwds)
- finally:
- if lock is not None:
- lock.release()
-
- def settimeout(self, timeout, interrupt_main=False):
- """Start a timeout that will kill the subprocess after the given
- amount of time. Only one timeout can be active at a time.
- """
- import thread
-
- def _waiting_thread():
- while True:
- while self.currentlyidlefrom is not None:
- time.sleep(1) # can't timeout while idle
- t = self.currenttimeout
- if t is None:
- return # cancelled
- delay = t - time.time()
- if delay <= 0.0:
- break # expired!
- time.sleep(min(delay*1.001, 1))
- if self.log:
- self.log.timeout("timeout!")
- self.kill()
- #if interrupt_main:
- # if hasattr(os, 'kill'):
- # import signal
- # os.kill(os.getpid(), signal.SIGINT)
- # else:
- # thread.interrupt_main()
-
- def _settimeout():
- need_new_thread = self.currenttimeout is None
- self.currenttimeout = time.time() + timeout
- if need_new_thread:
- thread.start_new_thread(_waiting_thread, ())
-
- if self.popenlock is None:
- self.popenlock = thread.allocate_lock()
- self.withlock(_settimeout)
-
- def canceltimeout(self):
- """Cancel the current timeout."""
- self.currenttimeout = None
- self.currentlyidlefrom = None
-
- def enter_idle(self):
- self.currentlyidlefrom = time.time()
-
- def leave_idle(self):
- def _postpone_timeout():
- t = self.currentlyidlefrom
- if t is not None and self.currenttimeout is not None:
- self.currenttimeout += time.time() - t
- try:
- self.withlock(_postpone_timeout)
- finally:
- self.currentlyidlefrom = None
-
- def poll(self):
- returncode = self.withlock(self.popen.poll)
- if returncode is not None:
- self.canceltimeout()
- return returncode
-
- def wait(self):
- returncode = self.withlock(self.popen.wait)
- if returncode is not None:
- self.canceltimeout()
- return returncode
-
- def kill(self):
- self.withlock(killsubprocess, self.popen)
-
- def handle_forever(self):
- returncode = self.handle_until_return()
- if returncode != 0:
- raise OSError("the sandboxed subprocess exited with code %d" % (
- returncode,))
-
- def handle_until_return(self):
- child_stdin = self.popen.stdin
- child_stdout = self.popen.stdout
- if self.os_level_sandboxing and sys.platform.startswith('linux'):
- # rationale: we wait until the child process started completely,
- # letting the C library do any system calls it wants for
- # initialization. When the RPython code starts up, it quickly
- # does its first system call. At this point we turn seccomp on.
- import select
- select.select([child_stdout], [], [])
- f = open('/proc/%d/seccomp' % self.popen.pid, 'w')
- print >> f, 1
- f.close()
- while True:
- try:
- fnname = read_message(child_stdout)
- args = read_message(child_stdout)
- except EOFError as e:
- break
- if self.log and not self.is_spam(fnname, *args):
- self.log.call('%s(%s)' % (fnname,
- ', '.join([shortrepr(x) for x in args])))
- try:
- answer, resulttype = self.handle_message(fnname, *args)
- except Exception as e:
- tb = sys.exc_info()[2]
- write_exception(child_stdin, e, tb)
- if self.log:
- if str(e):
- self.log.exception('%s: %s' % (e.__class__.__name__, e))
- else:
- self.log.exception('%s' % (e.__class__.__name__,))
- else:
- if self.log and not self.is_spam(fnname, *args):
- self.log.result(shortrepr(answer))
- try:
- write_message(child_stdin, 0) # error code - 0 for ok
- write_message(child_stdin, answer, resulttype)
- child_stdin.flush()
- except (IOError, OSError):
- # likely cause: subprocess is dead, child_stdin closed
- if self.poll() is not None:
- break
- else:
- raise
- returncode = self.wait()
- return returncode
-
- def is_spam(self, fnname, *args):
- # To hide the spamming amounts of reads and writes to stdin and stdout
- # in interactive sessions
- return (fnname in ('ll_os.ll_os_read', 'll_os.ll_os_write') and
- args[0] in (0, 1, 2))
-
- def handle_message(self, fnname, *args):
- if '__' in fnname:
- raise ValueError("unsafe fnname")
- try:
- handler = getattr(self, 'do_' + fnname.replace('.', '__'))
- except AttributeError:
- raise RuntimeError("no handler for this function")
- resulttype = getattr(handler, 'resulttype', None)
- return handler(*args), resulttype
-
-
-class SimpleIOSandboxedProc(SandboxedProc):
- """Control a sandboxed subprocess which is only allowed to read from
- its stdin and write to its stdout and stderr.
- """
- _input = None
- _output = None
- _error = None
- inputlogfile = None
-
- def communicate(self, input=None):
- """Send data to stdin. Read data from stdout and stderr,
- until end-of-file is reached. Wait for process to terminate.
- """
- import cStringIO
- if input:
- if isinstance(input, str):
- input = cStringIO.StringIO(input)
- self._input = input
- self._output = cStringIO.StringIO()
- self._error = cStringIO.StringIO()
- self.handle_forever()
- output = self._output.getvalue()
- self._output = None
- error = self._error.getvalue()
- self._error = None
- return (output, error)
-
- def interact(self, stdin=None, stdout=None, stderr=None):
- """Interact with the subprocess. By default, stdin, stdout and
- stderr are set to the ones from 'sys'."""
- import sys
- self._input = stdin or sys.stdin
- self._output = stdout or sys.stdout
- self._error = stderr or sys.stderr
- returncode = self.handle_until_return()
- if returncode != 0:
- if os.name == 'posix' and returncode < 0:
- print >> self._error, "[Subprocess killed by %s]" % (
- signal_name(-returncode),)
- else:
- print >> self._error, "[Subprocess exit code: %d]" % (
- returncode,)
- self._input = None
- self._output = None
- self._error = None
- return returncode
-
- def setlogfile(self, filename):
- self.inputlogfile = open(filename, 'a')
-
- def do_ll_os__ll_os_read(self, fd, size):
- if fd == 0:
- if self._input is None:
- return ""
- elif (getattr(self, 'virtual_console_isatty', False) or
- self._input.isatty()):
- # don't wait for all 'size' chars if reading from a tty,
- # to avoid blocking. Instead, stop after reading a line.
-
- # For now, waiting at the interactive console is the
- # only time that counts as idle.
- self.enter_idle()
- try:
- inputdata = self._input.readline(size)
- finally:
- self.leave_idle()
- else:
- inputdata = self._input.read(size)
- if self.inputlogfile is not None:
- self.inputlogfile.write(inputdata)
- return inputdata
- raise OSError("trying to read from fd %d" % (fd,))
-
- def do_ll_os__ll_os_write(self, fd, data):
- if fd == 1:
- self._output.write(data)
- return len(data)
- if fd == 2:
- self._error.write(data)
- return len(data)
- raise OSError("trying to write to fd %d" % (fd,))
-
- # let's allow access to the real time
- def do_ll_time__ll_time_sleep(self, seconds):
- # regularly check for timeouts that could have killed the
- # subprocess
- while seconds > 5.0:
- time.sleep(5.0)
- seconds -= 5.0
- if self.poll() is not None: # subprocess finished?
- return
- time.sleep(seconds)
-
- def do_ll_time__ll_time_time(self):
- return time.time()
-
- def do_ll_time__ll_time_clock(self):
- # measuring the CPU time of the controller process has
- # not much meaning, so let's emulate this and return
- # the real time elapsed since the first call to clock()
- # (this is one of the behaviors allowed by the docs)
- try:
- starttime = self.starttime
- except AttributeError:
- starttime = self.starttime = time.time()
- return time.time() - starttime
-
-class VirtualizedSandboxedProc(SandboxedProc):
- """Control a virtualized sandboxed process, which is given a custom
- view on the filesystem and a custom environment.
- """
- virtual_env = {}
- virtual_cwd = '/tmp'
- virtual_console_isatty = False
- virtual_fd_range = range(3, 50)
-
- def __init__(self, *args, **kwds):
- super(VirtualizedSandboxedProc, self).__init__(*args, **kwds)
- self.virtual_root = self.build_virtual_root()
- self.open_fds = {} # {virtual_fd: (real_file_object, node)}
-
- def build_virtual_root(self):
- raise NotImplementedError("must be overridden")
-
- def do_ll_os__ll_os_envitems(self):
- return self.virtual_env.items()
-
- def do_ll_os__ll_os_getenv(self, name):
- return self.virtual_env.get(name)
-
- def translate_path(self, vpath):
- # XXX this assumes posix vpaths for now, but os-specific real paths
- vpath = posixpath.normpath(posixpath.join(self.virtual_cwd, vpath))
- dirnode = self.virtual_root
- components = [component for component in vpath.split('/')]
- for component in components[:-1]:
- if component:
- dirnode = dirnode.join(component)
- if dirnode.kind != stat.S_IFDIR:
- raise OSError(errno.ENOTDIR, component)
- return dirnode, components[-1]
-
- def get_node(self, vpath):
- dirnode, name = self.translate_path(vpath)
- if name:
- node = dirnode.join(name)
- else:
- node = dirnode
- if self.log:
- self.log.vpath('%r => %r' % (vpath, node))
- return node
-
- def do_ll_os__ll_os_stat(self, vpathname):
- node = self.get_node(vpathname)
- return node.stat()
- do_ll_os__ll_os_stat.resulttype = RESULTTYPE_STATRESULT
-
- do_ll_os__ll_os_lstat = do_ll_os__ll_os_stat
-
- def do_ll_os__ll_os_access(self, vpathname, mode):
- try:
- node = self.get_node(vpathname)
- except OSError as e:
- if e.errno == errno.ENOENT:
- return False
- raise
- return node.access(mode)
-
- def do_ll_os__ll_os_isatty(self, fd):
- return self.virtual_console_isatty and fd in (0, 1, 2)
-
- def allocate_fd(self, f, node=None):
- for fd in self.virtual_fd_range:
- if fd not in self.open_fds:
- self.open_fds[fd] = (f, node)
- return fd
- else:
- raise OSError(errno.EMFILE, "trying to open too many files")
-
- def get_fd(self, fd, throw=True):
- """Get the objects implementing file descriptor `fd`.
-
- Returns a pair, (open file, vfs node)
-
- `throw`: if true, raise OSError for bad fd, else return (None, None).
- """
- try:
- f, node = self.open_fds[fd]
- except KeyError:
- if throw:
- raise OSError(errno.EBADF, "bad file descriptor")
- return None, None
- return f, node
-
- def get_file(self, fd, throw=True):
- """Return the open file for file descriptor `fd`."""
- return self.get_fd(fd, throw)[0]
-
- def do_ll_os__ll_os_open(self, vpathname, flags, mode):
- node = self.get_node(vpathname)
- if flags & (os.O_RDONLY|os.O_WRONLY|os.O_RDWR) != os.O_RDONLY:
- raise OSError(errno.EPERM, "write access denied")
- # all other flags are ignored
- f = node.open()
- return self.allocate_fd(f, node)
-
- def do_ll_os__ll_os_close(self, fd):
- f = self.get_file(fd)
- del self.open_fds[fd]
- f.close()
-
- def do_ll_os__ll_os_read(self, fd, size):
- f = self.get_file(fd, throw=False)
- if f is None:
- return super(VirtualizedSandboxedProc, self).do_ll_os__ll_os_read(
- fd, size)
- else:
- if not (0 <= size <= sys.maxint):
- raise OSError(errno.EINVAL, "invalid read size")
- # don't try to read more than 256KB at once here
- return f.read(min(size, 256*1024))
-
- def do_ll_os__ll_os_fstat(self, fd):
- f, node = self.get_fd(fd)
- return node.stat()
- do_ll_os__ll_os_fstat.resulttype = RESULTTYPE_STATRESULT
-
- def do_ll_os__ll_os_lseek(self, fd, pos, how):
- f = self.get_file(fd)
- f.seek(pos, how)
- return f.tell()
- do_ll_os__ll_os_lseek.resulttype = RESULTTYPE_LONGLONG
-
- def do_ll_os__ll_os_getcwd(self):
- return self.virtual_cwd
-
- def do_ll_os__ll_os_strerror(self, errnum):
- # unsure if this shouldn't be considered safeboxsafe
- return os.strerror(errnum) or ('Unknown error %d' % (errnum,))
-
- def do_ll_os__ll_os_listdir(self, vpathname):
- node = self.get_node(vpathname)
- return node.keys()
-
- def do_ll_os__ll_os_unlink(self, vpathname):
- raise OSError(errno.EPERM, "write access denied")
-
- def do_ll_os__ll_os_mkdir(self, vpathname, mode=None):
- raise OSError(errno.EPERM, "write access denied")
-
- def do_ll_os__ll_os_getuid(self):
- return UID
- do_ll_os__ll_os_geteuid = do_ll_os__ll_os_getuid
-
- def do_ll_os__ll_os_getgid(self):
- return GID
- do_ll_os__ll_os_getegid = do_ll_os__ll_os_getgid
-
-
-class VirtualizedSocketProc(VirtualizedSandboxedProc):
- """ Extends VirtualizedSandboxProc with socket
- options, ie tcp://host:port as args to os.open
- """
- def __init__(self, *args, **kwds):
- super(VirtualizedSocketProc, self).__init__(*args, **kwds)
- self.sockets = {}
-
- def do_ll_os__ll_os_open(self, name, flags, mode):
- if not name.startswith("tcp://"):
- return super(VirtualizedSocketProc, self).do_ll_os__ll_os_open(
- name, flags, mode)
- import socket
- host, port = name[6:].split(":")
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect((host, int(port)))
- fd = self.allocate_fd(sock)
- self.sockets[fd] = True
- return fd
-
- def do_ll_os__ll_os_read(self, fd, size):
- if fd in self.sockets:
- return self.get_file(fd).recv(size)
- return super(VirtualizedSocketProc, self).do_ll_os__ll_os_read(
- fd, size)
-
- def do_ll_os__ll_os_write(self, fd, data):
- if fd in self.sockets:
- return self.get_file(fd).send(data)
- return super(VirtualizedSocketProc, self).do_ll_os__ll_os_write(
- fd, data)
-
diff --git a/rpython/translator/sandbox/test/test_graphchecker.py b/rpython/translator/sandbox/test/test_graphchecker.py
--- a/rpython/translator/sandbox/test/test_graphchecker.py
+++ b/rpython/translator/sandbox/test/test_graphchecker.py
@@ -52,7 +52,11 @@
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)])
+ self.check_safe(f, [lltype.Ptr(SGC)])
+ #
+ def g(x):
+ return llop.force_cast(lltype.Ptr(SGC), x)
+ self.check_unsafe("result is a GC ptr", g, [int])
def test_direct_call_to_check_caller(self):
@sandbox_review(check_caller=True)
diff --git a/rpython/translator/sandbox/vfs.py b/rpython/translator/sandbox/vfs.py
deleted file mode 100644
--- a/rpython/translator/sandbox/vfs.py
+++ /dev/null
@@ -1,137 +0,0 @@
-import os
-import stat, errno
-
-UID = 1000
-GID = 1000
-ATIME = MTIME = CTIME = 0
-INO_COUNTER = 0
-
-
-class FSObject(object):
- read_only = True
-
- def stat(self):
- try:
- st_ino = self._st_ino
- except AttributeError:
- global INO_COUNTER
- INO_COUNTER += 1
- st_ino = self._st_ino = INO_COUNTER
- st_dev = 1
- st_nlink = 1
- st_size = self.getsize()
- st_mode = self.kind
- st_mode |= stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
- if stat.S_ISDIR(self.kind):
- st_mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
- if self.read_only:
- st_uid = 0 # read-only files are virtually owned by root
- st_gid = 0
- else:
- st_uid = UID # read-write files are owned by this virtual user
- st_gid = GID
- st_atime = ATIME
- st_mtime = MTIME
- st_ctime = CTIME
- return os.stat_result(
- (st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid,
- st_size, st_atime, st_mtime, st_ctime))
-
- def access(self, mode):
- s = self.stat()
- e_mode = s.st_mode & stat.S_IRWXO
- if UID == s.st_uid:
- e_mode |= (s.st_mode & stat.S_IRWXU) >> 6
- if GID == s.st_gid:
- e_mode |= (s.st_mode & stat.S_IRWXG) >> 3
- return (e_mode & mode) == mode
-
- def keys(self):
- raise OSError(errno.ENOTDIR, self)
-
- def open(self):
- raise OSError(errno.EACCES, self)
-
- def getsize(self):
- return 0
-
-
-class Dir(FSObject):
- kind = stat.S_IFDIR
- def __init__(self, entries={}):
- self.entries = entries
- def keys(self):
- return self.entries.keys()
- def join(self, name):
- try:
- return self.entries[name]
- except KeyError:
- raise OSError(errno.ENOENT, name)
-
-class RealDir(Dir):
- # If show_dotfiles=False, we pretend that all files whose name starts
- # with '.' simply don't exist. If follow_links=True, then symlinks are
- # transparently followed (they look like a regular file or directory to
- # the sandboxed process). If follow_links=False, the subprocess is
- # not allowed to access them at all. Finally, exclude is a list of
- # file endings that we filter out (note that we also filter out files
- # with the same ending but a different case, to be safe).
- def __init__(self, path, show_dotfiles=False, follow_links=False,
- exclude=[]):
- self.path = path
- self.show_dotfiles = show_dotfiles
- self.follow_links = follow_links
- self.exclude = [excl.lower() for excl in exclude]
- def __repr__(self):
- return '<RealDir %s>' % (self.path,)
- def keys(self):
- names = os.listdir(self.path)
- if not self.show_dotfiles:
- names = [name for name in names if not name.startswith('.')]
- for excl in self.exclude:
- names = [name for name in names if not name.lower().endswith(excl)]
- return names
- def join(self, name):
- if name.startswith('.') and not self.show_dotfiles:
- raise OSError(errno.ENOENT, name)
- for excl in self.exclude:
- if name.lower().endswith(excl):
- raise OSError(errno.ENOENT, name)
- path = os.path.join(self.path, name)
- if self.follow_links:
- st = os.stat(path)
- else:
- st = os.lstat(path)
- if stat.S_ISDIR(st.st_mode):
- return RealDir(path, show_dotfiles = self.show_dotfiles,
- follow_links = self.follow_links,
- exclude = self.exclude)
- elif stat.S_ISREG(st.st_mode):
- return RealFile(path)
- else:
- # don't allow access to symlinks and other special files
- raise OSError(errno.EACCES, path)
-
-class File(FSObject):
- kind = stat.S_IFREG
- def __init__(self, data=''):
- self.data = data
- def getsize(self):
- return len(self.data)
- def open(self):
- import cStringIO
- return cStringIO.StringIO(self.data)
-
-class RealFile(File):
- def __init__(self, path, mode=0):
- self.path = path
- self.kind |= mode
- def __repr__(self):
- return '<RealFile %s>' % (self.path,)
- def getsize(self):
- return os.stat(self.path).st_size
- def open(self):
- try:
- return open(self.path, "rb")
- except IOError as e:
- raise OSError(e.errno, "open failed")
More information about the pypy-commit
mailing list