[pypy-commit] pypy merge-2.7.2: The underlying stream of a BufferedIO can call arbitrary code,

amauryfa noreply at buildbot.pypy.org
Mon Jan 23 22:14:20 CET 2012


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: merge-2.7.2
Changeset: r51696:624143b36c8d
Date: 2012-01-23 22:13 +0100
http://bitbucket.org/pypy/pypy/changeset/624143b36c8d/

Log:	The underlying stream of a BufferedIO can call arbitrary code, or
	yield to some signal handler; protect against reentrant code.

diff --git a/pypy/module/_io/interp_bufferedio.py b/pypy/module/_io/interp_bufferedio.py
--- a/pypy/module/_io/interp_bufferedio.py
+++ b/pypy/module/_io/interp_bufferedio.py
@@ -11,6 +11,7 @@
     W_IOBase, DEFAULT_BUFFER_SIZE, convert_size,
     check_readable_w, check_writable_w, check_seekable_w)
 from pypy.module._io.interp_io import W_BlockingIOError
+from pypy.module.thread import ll_thread
 import errno
 
 STATE_ZERO, STATE_OK, STATE_DETACHED = range(3)
@@ -28,6 +29,29 @@
         return False
 
 
+class TryLock(object):
+    "A Lock that raises RuntimeError when acquired twice by the same thread"
+    def __init__(self, space):
+        ## XXX cannot free a Lock?
+        ## if self.lock:
+        ##     self.lock.free()
+        self.lock = space.allocate_lock()
+        self.owner = 0
+        self.operr = OperationError(space.w_RuntimeError,
+                                    space.wrap("reentrant call"))
+
+    def __enter__(self):
+        if not self.lock.acquire(False):
+            if self.owner == ll_thread.get_ident():
+                raise self.operr
+            self.lock.acquire(True)
+        self.owner = ll_thread.get_ident()
+    
+    def __exit__(self,*args):
+        self.owner = 0
+        self.lock.release()
+
+
 class BlockingIOError(Exception):
     pass
 
@@ -130,10 +154,7 @@
 
         self.buffer = ['\0'] * self.buffer_size
 
-        ## XXX cannot free a Lock?
-        ## if self.lock:
-        ##     self.lock.free()
-        self.lock = space.allocate_lock()
+        self.lock = TryLock(space)
 
         try:
             self._raw_tell(space)
diff --git a/pypy/module/_io/test/test_bufferedio.py b/pypy/module/_io/test/test_bufferedio.py
--- a/pypy/module/_io/test/test_bufferedio.py
+++ b/pypy/module/_io/test/test_bufferedio.py
@@ -1,6 +1,9 @@
 from pypy.conftest import gettestobjspace, option
 from pypy.interpreter.gateway import interp2app
 from pypy.tool.udir import udir
+from pypy.module._io import interp_bufferedio
+from pypy.interpreter.error import OperationError
+import py.test
 
 class AppTestBufferedReader:
     spaceconfig = dict(usemodules=['_io'])
@@ -217,7 +220,7 @@
 
 class AppTestBufferedWriter:
     def setup_class(cls):
-        cls.space = gettestobjspace(usemodules=['_io'])
+        cls.space = gettestobjspace(usemodules=['_io', 'thread'])
         tmpfile = udir.join('tmpfile')
         cls.w_tmpfile = cls.space.wrap(str(tmpfile))
         if option.runappdirect:
@@ -425,6 +428,22 @@
         bufio.flush()
         assert rawio.count == 3
 
+    def test_reentrant_write(self):
+        import thread  # Reentrant-safe is only enabled with threads
+        import _io, errno
+        class MockRawIO(_io._RawIOBase):
+            def writable(self):
+                return True
+            def write(self, data):
+                bufio.write("something else")
+                return len(data)
+
+        rawio = MockRawIO()
+        bufio = _io.BufferedWriter(rawio)
+        bufio.write("test")
+        exc = raises(RuntimeError, bufio.flush)
+        assert "reentrant" in str(exc.value)  # And not e.g. recursion limit.
+
 class AppTestBufferedRWPair:
     def test_pair(self):
         import _io
@@ -494,3 +513,15 @@
                 expected[j] = 2
                 expected[i] = 1
                 assert raw.getvalue() == str(expected)
+        
+
+class TestNonReentrantLock:
+    def test_trylock(self):
+        space = gettestobjspace(usemodules=['thread'])
+        lock = interp_bufferedio.TryLock(space)
+        with lock:
+            pass
+        with lock:
+            exc = py.test.raises(OperationError, "with lock: pass")
+        assert exc.value.match(space, space.w_RuntimeError)
+


More information about the pypy-commit mailing list