Skip to content

Commit 1e66eae

Browse files
[3.14] gh-153578: Fix out-of-bounds write in bytearray.extend() with a reentrant __buffer__ (GH-153579) (GH-156008)
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 4a8cfac commit 1e66eae

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
@@ -1670,6 +1670,30 @@ def test_setslice_trap(self):
16701670
b[8:] = b
16711671
self.assertEqual(b, bytearray(list(range(8)) + list(range(256))))
16721672

1673+
def test_setslice_reentrant_resize(self):
1674+
# gh-153578: a buffer argument whose __buffer__ resizes the bytearray
1675+
# while the buffer is being acquired must not leave the slice bounds
1676+
# with lo > hi, which drove a negative-size memmove (an out-of-bounds
1677+
# write) in the setslice path reached through extend().
1678+
class Evil:
1679+
def __init__(self, resize):
1680+
self.resize = resize
1681+
def __buffer__(self, flags):
1682+
self.resize()
1683+
return memoryview(b'ABCDEFGH')
1684+
# clear() during __buffer__: extend appends to the emptied bytearray.
1685+
b = bytearray(b'x' * 100)
1686+
b.extend(Evil(b.clear))
1687+
self.assertEqual(b, b'ABCDEFGH')
1688+
# partial shrink during __buffer__.
1689+
b = bytearray(b'x' * 100)
1690+
b.extend(Evil(lambda: b.__delitem__(slice(30, None))))
1691+
self.assertEqual(b, b'x' * 30 + b'ABCDEFGH')
1692+
# grow during __buffer__: the data lands at the original end.
1693+
b = bytearray(b'x' * 10)
1694+
b.extend(Evil(lambda: b.extend(b'y' * 100)))
1695+
self.assertEqual(b, b'x' * 10 + b'ABCDEFGH' + b'y' * 100)
1696+
16731697
def test_iconcat(self):
16741698
b = bytearray(b"abc")
16751699
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
@@ -668,8 +668,11 @@ bytearray_setslice(PyByteArrayObject *self, Py_ssize_t lo, Py_ssize_t hi,
668668
bytes = vbytes.buf;
669669
}
670670

671+
// gh-153578: __buffer__() may have resized self; re-clamp both bounds.
671672
if (lo < 0)
672673
lo = 0;
674+
else if (lo > Py_SIZE(self))
675+
lo = Py_SIZE(self);
673676
if (hi < lo)
674677
hi = lo;
675678
if (hi > Py_SIZE(self))

0 commit comments

Comments
 (0)