Skip to content

Commit 6379b8b

Browse files
miss-islingtontonghuarootserhiy-storchaka
authored
[3.15] gh-153578: Fix out-of-bounds write in bytearray.extend() with a reentrant __buffer__ (GH-153579) (GH-156007)
bytearray.extend() clamped only the high bound of the append range to the current size after acquiring the argument's buffer, so a __buffer__ that shrinks the bytearray left the low bound past the high bound and ran a negative-size memmove. Clamp the low bound too, matching bytearray.__iadd__. (cherry picked from commit e675e37) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 3da25a3 commit 6379b8b

3 files changed

Lines changed: 30 additions & 0 deletions

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,6 +1836,30 @@ def test_setslice_trap(self):
18361836
b[8:] = b
18371837
self.assertEqual(b, bytearray(list(range(8)) + list(range(256))))
18381838

1839+
def test_setslice_reentrant_resize(self):
1840+
# gh-153578: a buffer argument whose __buffer__ resizes the bytearray
1841+
# while the buffer is being acquired must not leave the slice bounds
1842+
# with lo > hi, which drove a negative-size memmove (an out-of-bounds
1843+
# write) in the setslice path reached through extend().
1844+
class Evil:
1845+
def __init__(self, resize):
1846+
self.resize = resize
1847+
def __buffer__(self, flags):
1848+
self.resize()
1849+
return memoryview(b'ABCDEFGH')
1850+
# clear() during __buffer__: extend appends to the emptied bytearray.
1851+
b = bytearray(b'x' * 100)
1852+
b.extend(Evil(b.clear))
1853+
self.assertEqual(b, b'ABCDEFGH')
1854+
# partial shrink during __buffer__.
1855+
b = bytearray(b'x' * 100)
1856+
b.extend(Evil(lambda: b.__delitem__(slice(30, None))))
1857+
self.assertEqual(b, b'x' * 30 + b'ABCDEFGH')
1858+
# grow during __buffer__: the data lands at the original end.
1859+
b = bytearray(b'x' * 10)
1860+
b.extend(Evil(lambda: b.extend(b'y' * 100)))
1861+
self.assertEqual(b, b'x' * 10 + b'ABCDEFGH' + b'y' * 100)
1862+
18391863
def test_iconcat(self):
18401864
b = bytearray(b"abc")
18411865
b1 = b
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an out-of-bounds write in :meth:`bytearray.extend` when the bytearray is
2+
resized while the argument's :meth:`~object.__buffer__` is being acquired, for
3+
example by another thread. Patch by tonghuaroot.

‎Objects/bytearrayobject.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,8 +684,11 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
684684
bytes = vbytes.buf;
685685
}
686686

687+
// gh-153578: __buffer__() may have resized self; re-clamp both bounds.
687688
if (lo < 0)
688689
lo = 0;
690+
else if (lo > Py_SIZE(self))
691+
lo = Py_SIZE(self);
689692
if (hi < lo)
690693
hi = lo;
691694
if (hi > Py_SIZE(self))

0 commit comments

Comments
 (0)