Skip to content

Commit 77e6fdc

Browse files
committed
py/objarray: Fix use-after-free if extending a bytearray from itself.
Two cases, one assigning to a slice. Closes #13283 Second is extending a slice from itself, similar logic. In both cases the problem occurs when m_renew causes realloc to move the buffer, leaving a dangling pointer behind. There are more complex and hard to fix cases when either argument is a memoryview into the buffer, currently resizing to a new address breaks memoryviews into that object. Reproducing this bug and confirming the fix was done by running the unix port under valgrind with GC-aware extensions. Note in default configurations with GIL this bug exists but has no impact (the free buffer won't be reused while the function is still executing, and is no longer referenced after it returns). Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 51d05c4 commit 77e6fdc

File tree

5 files changed

+45
-11
lines changed

5 files changed

+45
-11
lines changed

py/objarray.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,13 @@ static mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) {
424424
if (self->free < len) {
425425
self->items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz);
426426
self->free = 0;
427+
428+
if (self_in == arg_in) {
429+
// Get arg_bufinfo again in case self->items has moved
430+
//
431+
// (Note not possible to handle case that arg_in is a memoryview into self)
432+
mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ);
433+
}
427434
} else {
428435
self->free -= len;
429436
}
@@ -456,7 +463,8 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
456463
#if MICROPY_PY_ARRAY_SLICE_ASSIGN
457464
// Assign
458465
size_t src_len;
459-
void *src_items;
466+
uint8_t *src_items;
467+
size_t src_offs = 0;
460468
size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL);
461469
if (mp_obj_is_obj(value) && MP_OBJ_TYPE_GET_SLOT_OR_NULL(((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type, subscr) == array_subscr) {
462470
// value is array, bytearray or memoryview
@@ -469,7 +477,7 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
469477
src_items = src_slice->items;
470478
#if MICROPY_PY_BUILTINS_MEMORYVIEW
471479
if (mp_obj_is_type(value, &mp_type_memoryview)) {
472-
src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz);
480+
src_offs = src_slice->memview_offset * item_sz;
473481
}
474482
#endif
475483
} else if (mp_obj_is_type(value, &mp_type_bytes)) {
@@ -504,13 +512,17 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
504512
// TODO: alloc policy; at the moment we go conservative
505513
o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz);
506514
o->free = len_adj;
515+
// m_renew may have moved o->items
516+
if (src_items == dest_items) {
517+
src_items = o->items;
518+
}
507519
dest_items = o->items;
508520
}
509521
mp_seq_replace_slice_grow_inplace(dest_items, o->len,
510-
slice.start, slice.stop, src_items, src_len, len_adj, item_sz);
522+
slice.start, slice.stop, src_items + src_offs, src_len, len_adj, item_sz);
511523
} else {
512524
mp_seq_replace_slice_no_grow(dest_items, o->len,
513-
slice.start, slice.stop, src_items, src_len, item_sz);
525+
slice.start, slice.stop, src_items + src_offs, src_len, item_sz);
514526
// Clear "freed" elements at the end of list
515527
// TODO: This is actually only needed for typecode=='O'
516528
mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz);

tests/basics/bytearray_add.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,11 @@
1515

1616
# this inplace add tests the code when the buffer doesn't need to be increased
1717
b = bytearray()
18-
b += b''
18+
b += b""
19+
20+
# extend a bytearray from itself
21+
b = bytearray(b"abcdefgh")
22+
for _ in range(4):
23+
c = bytearray(b) # extra allocation, as above
24+
b.extend(b)
25+
print(b)

tests/basics/bytearray_add_self.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# add a bytearray to itself
2+
#
3+
# This is not supported by CPython as of 3.11.18)
4+
b = bytearray(b"123456789")
5+
for _ in range(4):
6+
c = bytearray(b) # extra allocation increases chance 'b' has to relocate
7+
b += b
8+
print(b)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bytearray(b'123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789')

tests/basics/bytearray_slice_assign.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
l[1:3] = bytearray()
1919
print(l)
2020
l = bytearray(x)
21-
#del l[1:3]
21+
# del l[1:3]
2222
print(l)
2323

2424
l = bytearray(x)
@@ -28,7 +28,7 @@
2828
l[:3] = bytearray()
2929
print(l)
3030
l = bytearray(x)
31-
#del l[:3]
31+
# del l[:3]
3232
print(l)
3333

3434
l = bytearray(x)
@@ -38,7 +38,7 @@
3838
l[:-3] = bytearray()
3939
print(l)
4040
l = bytearray(x)
41-
#del l[:-3]
41+
# del l[:-3]
4242
print(l)
4343

4444
# slice assignment that extends the array
@@ -61,8 +61,14 @@
6161
print(b)
6262

6363
# Growth of bytearray via slice extension
64-
b = bytearray(b'12345678')
65-
b.append(57) # expand and add a bit of unused space at end of the bytearray
64+
b = bytearray(b"12345678")
65+
b.append(57) # expand and add a bit of unused space at end of the bytearray
6666
for i in range(400):
67-
b[-1:] = b'ab' # grow slowly into the unused space
67+
b[-1:] = b"ab" # grow slowly into the unused space
68+
print(len(b), b)
69+
70+
# Growth of bytearray via slice extension from itself
71+
b = bytearray(b"1234567")
72+
for i in range(3):
73+
b[-1:] = b
6874
print(len(b), b)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy