[pypy-commit] pypy default: Merge nedbat-sandbox branch, with one tweak, don't realy on identity of one,

fijal noreply at buildbot.pypy.org
Sun Dec 11 11:31:09 CET 2011


Author: Maciej Fijalkowski <fijall at gmail.com>
Branch: 
Changeset: r50367:1b1197dcc86d
Date: 2011-12-11 12:30 +0200
http://bitbucket.org/pypy/pypy/changeset/1b1197dcc86d/

Log:	Merge nedbat-sandbox branch, with one tweak, don't realy on identity
	of one, just use object() for identitiy checks.

diff --git a/pypy/translator/sandbox/sandlib.py b/pypy/translator/sandbox/sandlib.py
--- a/pypy/translator/sandbox/sandlib.py
+++ b/pypy/translator/sandbox/sandlib.py
@@ -6,11 +6,10 @@
 
 import py
 import sys, os, posixpath, errno, stat, time
-from pypy.rpython.module.ll_os_stat import s_StatResult
 from pypy.tool.ansi_print import AnsiLog
-from pypy.rlib.rarithmetic import r_longlong
 import subprocess
 from pypy.tool.killsubprocess import killsubprocess
+from pypy.translator.sandbox.vfs import UID, GID
 
 class MyAnsiLog(AnsiLog):
     KW_TO_COLOR = {
@@ -34,6 +33,10 @@
 from pypy.tool.lib_pypy import import_from_lib_pypy
 marshal = import_from_lib_pypy('marshal')
 
+# Non-marshal result types
+RESULTTYPE_STATRESULT = object()
+RESULTTYPE_LONGLONG = object()
+
 def read_message(f, timeout=None):
     # warning: 'timeout' is not really reliable and should only be used
     # for testing.  Also, it doesn't work if the file f does any buffering.
@@ -50,12 +53,30 @@
             marshal.dump(msg, g)
         else:
             marshal.dump(msg, g, 0)
+    elif resulttype is RESULTTYPE_STATRESULT:
+        # Hand-coded marshal for stat results that mimics what rmarshal expects.
+        # marshal.dump(tuple(msg)) would have been too easy. rmarshal insists
+        # on 64-bit ints at places, even when the value fits in 32 bits.
+        import struct
+        st = tuple(msg)
+        fmt = "iIIiiiIfff"
+        buf = []
+        buf.append(struct.pack("<ci", '(', len(st)))
+        for c, v in zip(fmt, st):
+            if c == 'i':
+                buf.append(struct.pack("<ci", c, v))
+            elif c == 'I':
+                buf.append(struct.pack("<cq", c, v))
+            elif c == 'f':
+                fstr = "%g" % v
+                buf.append(struct.pack("<cB", c, len(fstr)))
+                buf.append(fstr)
+        g.write(''.join(buf))
+    elif resulttype is RESULTTYPE_LONGLONG:
+        import struct
+        g.write(struct.pack("<cq", 'I', msg))
     else:
-        # use the exact result type for encoding
-        from pypy.rlib.rmarshal import get_marshaller
-        buf = []
-        get_marshaller(resulttype)(buf, msg)
-        g.write(''.join(buf))
+        raise Exception("Can't marshal: %r (%r)" % (msg, resulttype))
 
 # keep the table in sync with rsandbox.reraise_error()
 EXCEPTION_TABLE = [
@@ -390,7 +411,7 @@
     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}
+        self.open_fds = {}   # {virtual_fd: (real_file_object, node)}
 
     def build_virtual_root(self):
         raise NotImplementedError("must be overridden")
@@ -425,26 +446,39 @@
     def do_ll_os__ll_os_stat(self, vpathname):
         node = self.get_node(vpathname)
         return node.stat()
-    do_ll_os__ll_os_stat.resulttype = s_StatResult
+    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_isatty(self, fd):
         return self.virtual_console_isatty and fd in (0, 1, 2)
 
-    def allocate_fd(self, f):
+    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
+                self.open_fds[fd] = (f, node)
                 return fd
         else:
             raise OSError(errno.EMFILE, "trying to open too many files")
 
-    def get_file(self, fd):
+    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:
-            return self.open_fds[fd]
+            f, node = self.open_fds[fd]
         except KeyError:
-            raise OSError(errno.EBADF, "bad file descriptor")
+            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)
@@ -452,7 +486,7 @@
             raise OSError(errno.EPERM, "write access denied")
         # all other flags are ignored
         f = node.open()
-        return self.allocate_fd(f)
+        return self.allocate_fd(f, node)
 
     def do_ll_os__ll_os_close(self, fd):
         f = self.get_file(fd)
@@ -460,9 +494,8 @@
         f.close()
 
     def do_ll_os__ll_os_read(self, fd, size):
-        try:
-            f = self.open_fds[fd]
-        except KeyError:
+        f = self.get_file(fd, throw=False)
+        if f is None:
             return super(VirtualizedSandboxedProc, self).do_ll_os__ll_os_read(
                 fd, size)
         else:
@@ -471,11 +504,16 @@
             # 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 = r_longlong
+    do_ll_os__ll_os_lseek.resulttype = RESULTTYPE_LONGLONG
 
     def do_ll_os__ll_os_getcwd(self):
         return self.virtual_cwd
@@ -488,6 +526,14 @@
         node = self.get_node(vpathname)
         return node.keys()
 
+    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
@@ -511,13 +557,13 @@
 
     def do_ll_os__ll_os_read(self, fd, size):
         if fd in self.sockets:
-            return self.open_fds[fd].recv(size)
+            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.open_fds[fd].send(data)
+            return self.get_file(fd).send(data)
         return super(VirtualizedSocketProc, self).do_ll_os__ll_os_write(
             fd, data)
 
diff --git a/pypy/translator/sandbox/test/test_sandbox.py b/pypy/translator/sandbox/test/test_sandbox.py
--- a/pypy/translator/sandbox/test/test_sandbox.py
+++ b/pypy/translator/sandbox/test/test_sandbox.py
@@ -80,7 +80,7 @@
     assert tail == ""
 
 def test_stat_ftruncate():
-    from pypy.rpython.module.ll_os_stat import s_StatResult
+    from pypy.translator.sandbox.sandlib import RESULTTYPE_STATRESULT
     from pypy.rlib.rarithmetic import r_longlong
     r0x12380000007 = r_longlong(0x12380000007)
 
@@ -93,7 +93,7 @@
     g, f = os.popen2(exe, "t", 0)
     st = os.stat_result((55, 0, 0, 0, 0, 0, 0x12380000007, 0, 0, 0))
     expect(f, g, "ll_os.ll_os_stat", ("somewhere",), st,
-           resulttype = s_StatResult)
+           resulttype = RESULTTYPE_STATRESULT)
     expect(f, g, "ll_os.ll_os_ftruncate", (55, 0x12380000007), None)
     g.close()
     tail = f.read()
diff --git a/pypy/translator/sandbox/test/test_sandlib.py b/pypy/translator/sandbox/test/test_sandlib.py
--- a/pypy/translator/sandbox/test/test_sandlib.py
+++ b/pypy/translator/sandbox/test/test_sandlib.py
@@ -1,14 +1,17 @@
 import py
-import os, StringIO
+import errno, os, StringIO
 from pypy.tool.sourcetools import func_with_new_name
 from pypy.rpython.lltypesystem import rffi
 from pypy.translator.sandbox.sandlib import SandboxedProc
 from pypy.translator.sandbox.sandlib import SimpleIOSandboxedProc
+from pypy.translator.sandbox.sandlib import VirtualizedSandboxedProc
 from pypy.translator.sandbox.sandlib import VirtualizedSocketProc
 from pypy.translator.sandbox.test.test_sandbox import compile
+from pypy.translator.sandbox.vfs import Dir, File, RealDir, RealFile
 
 
-class MySandboxedProc(SandboxedProc):
+class MockSandboxedProc(SandboxedProc):
+    """A sandbox process wrapper that replays expected syscalls."""
 
     def __init__(self, args, expected):
         SandboxedProc.__init__(self, args)
@@ -48,7 +51,7 @@
         return 0
     exe = compile(entry_point)
 
-    proc = MySandboxedProc([exe, 'x1', 'y2'], expected = [
+    proc = MockSandboxedProc([exe, 'x1', 'y2'], expected = [
         ("open", ("/tmp/foobar", os.O_RDONLY, 0777), 77),
         ("read", (77, 123), "he\x00llo"),
         ("write", (77, "world\x00!\x00"), 42),
@@ -69,7 +72,7 @@
         return n
     exe = compile(entry_point)
 
-    proc = MySandboxedProc([exe, 'spam', 'egg'], expected = [
+    proc = MockSandboxedProc([exe, 'spam', 'egg'], expected = [
         ("foobar", ("spam",), 2),
         ("foobar", ("egg",), 0),
         ])
@@ -122,9 +125,140 @@
         return 0
     exe = compile(entry_point)
 
-    proc = MySandboxedProc([exe], expected = [
+    proc = MockSandboxedProc([exe], expected = [
         ("open", ("/tmp/foobar", os.O_RDONLY, 0777), OSError(-42, "baz")),
         ("close", (-42,), None),
         ])
     proc.handle_forever()
     assert proc.seen == len(proc.expected)
+
+
+class SandboxedProcWithFiles(VirtualizedSandboxedProc, SimpleIOSandboxedProc):
+    """A sandboxed process with a simple virtualized filesystem.
+
+    For testing file operations.
+
+    """
+    def build_virtual_root(self):
+        return Dir({
+            'hi.txt': File("Hello, world!\n"),
+            'this.pyc': RealFile(__file__),
+             })
+
+def test_too_many_opens():
+    def entry_point(argv):
+        try:
+            open_files = []
+            for i in range(500):
+                fd = os.open('/hi.txt', os.O_RDONLY, 0777)
+                open_files.append(fd)
+                txt = os.read(fd, 100)
+                if txt != "Hello, world!\n":
+                    print "Wrong content: %s" % txt
+        except OSError, e:
+            # We expect to get EMFILE, for opening too many files.
+            if e.errno != errno.EMFILE:
+                print "OSError: %s!" % (e.errno,)
+        else:
+            print "We opened 500 fake files! Shouldn't have been able to."
+
+        for fd in open_files:
+            os.close(fd)
+
+        try:
+            open_files = []
+            for i in range(500):
+                fd = os.open('/this.pyc', os.O_RDONLY, 0777)
+                open_files.append(fd)
+        except OSError, e:
+            # We expect to get EMFILE, for opening too many files.
+            if e.errno != errno.EMFILE:
+                print "OSError: %s!" % (e.errno,)
+        else:
+            print "We opened 500 real files! Shouldn't have been able to."
+
+        print "All ok!"
+        return 0
+    exe = compile(entry_point)
+
+    proc = SandboxedProcWithFiles([exe])
+    output, error = proc.communicate("")
+    assert output == "All ok!\n"
+    assert error == ""
+
+def test_fstat():
+    def compare(a, b, i):
+        if a != b:
+            print "stat and fstat differ @%d: %s != %s" % (i, a, b)
+
+    def entry_point(argv):
+        try:
+            # Open a file, and compare stat and fstat
+            fd = os.open('/hi.txt', os.O_RDONLY, 0777)
+            st = os.stat('/hi.txt')
+            fs = os.fstat(fd)
+            # RPython requires the index for stat to be a constant.. :(
+            compare(st[0], fs[0], 0)
+            compare(st[1], fs[1], 1)
+            compare(st[2], fs[2], 2)
+            compare(st[3], fs[3], 3)
+            compare(st[4], fs[4], 4)
+            compare(st[5], fs[5], 5)
+            compare(st[6], fs[6], 6)
+            compare(st[7], fs[7], 7)
+            compare(st[8], fs[8], 8)
+            compare(st[9], fs[9], 9)
+        except OSError, e:
+            print "OSError: %s" % (e.errno,)
+        print "All ok!"
+        return 0
+    exe = compile(entry_point)
+
+    proc = SandboxedProcWithFiles([exe])
+    output, error = proc.communicate("")
+    assert output == "All ok!\n"
+    assert error == ""
+
+def test_lseek():
+    def char_should_be(c, should):
+        if c != should:
+            print "Wrong char: '%s' should be '%s'" % (c, should)
+
+    def entry_point(argv):
+        fd = os.open('/hi.txt', os.O_RDONLY, 0777)
+        char_should_be(os.read(fd, 1), "H")
+        new = os.lseek(fd, 3, os.SEEK_CUR)
+        if new != 4:
+            print "Wrong offset, %d should be 4" % new
+        char_should_be(os.read(fd, 1), "o")
+        new = os.lseek(fd, -3, os.SEEK_END)
+        if new != 11:
+            print "Wrong offset, %d should be 11" % new
+        char_should_be(os.read(fd, 1), "d")
+        new = os.lseek(fd, 7, os.SEEK_SET)
+        if new != 7:
+            print "Wrong offset, %d should be 7" % new
+        char_should_be(os.read(fd, 1), "w")
+        print "All ok!"
+        return 0
+    exe = compile(entry_point)
+
+    proc = SandboxedProcWithFiles([exe])
+    output, error = proc.communicate("")
+    assert output == "All ok!\n"
+    assert error == ""
+
+def test_getuid():
+    def entry_point(argv):
+        import os
+        print "uid is %s" % os.getuid()
+        print "euid is %s" % os.geteuid()
+        print "gid is %s" % os.getgid()
+        print "egid is %s" % os.getegid()
+        return 0
+    exe = compile(entry_point)
+
+    proc = SandboxedProcWithFiles([exe])
+    output, error = proc.communicate("")
+    assert output == "uid is 1000\neuid is 1000\ngid is 1000\negid is 1000\n"
+    assert error == ""


More information about the pypy-commit mailing list