Skip to content

Commit 49fe8bd

Browse files
[3.13] gh-153578: Fix out-of-bounds write in bytearray.extend() with a reentrant __buffer__ (GH-153579) (GH-156009)
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>
1 parent e459bd6 commit 49fe8bd

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
@@ -1599,6 +1599,30 @@ def test_setslice_trap(self):
15991599
b[8:] = b
16001600
self.assertEqual(b, bytearray(list(range(8)) + list(range(256))))
16011601

1602+
def test_setslice_reentrant_resize(self):
1603+
# gh-153578: a buffer argument whose __buffer__ resizes the bytearray
1604+
# while the buffer is being acquired must not leave the slice bounds
1605+
# with lo > hi, which drove a negative-size memmove (an out-of-bounds
1606+
# write) in the setslice path reached through extend().
1607+
class Evil:
1608+
def __init__(self, resize):
1609+
self.resize = resize
1610+
def __buffer__(self, flags):
1611+
self.resize()
1612+
return memoryview(b'ABCDEFGH')
1613+
# clear() during __buffer__: extend appends to the emptied bytearray.
1614+
b = bytearray(b'x' * 100)
1615+
b.extend(Evil(b.clear))
1616+
self.assertEqual(b, b'ABCDEFGH')
1617+
# partial shrink during __buffer__.
1618+
b = bytearray(b'x' * 100)
1619+
b.extend(Evil(lambda: b.__delitem__(slice(30, None))))
1620+
self.assertEqual(b, b'x' * 30 + b'ABCDEFGH')
1621+
# grow during __buffer__: the data lands at the original end.
1622+
b = bytearray(b'x' * 10)
1623+
b.extend(Evil(lambda: b.extend(b'y' * 100)))
1624+
self.assertEqual(b, b'x' * 10 + b'ABCDEFGH' + b'y' * 100)
1625+
16021626
def test_iconcat(self):
16031627
b = bytearray(b"abc")
16041628
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
@@ -563,8 +563,11 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
563563
bytes = vbytes.buf;
564564
}
565565

566+
// gh-153578: __buffer__() may have resized self; re-clamp both bounds.
566567
if (lo < 0)
567568
lo = 0;
569+
else if (lo > Py_SIZE(self))
570+
lo = Py_SIZE(self);
568571
if (hi < lo)
569572
hi = lo;
570573
if (hi > Py_SIZE(self))

0 commit comments

Comments
 (0)