2
1

0004-py-objarray-fix-use-after-free-if-extending-a-bytearray-from-itself.patch 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. From 4bed614e707c0644c06e117f848fa12605c711cd Mon Sep 17 00:00:00 2001
  2. From: Angus Gratton <angus@redyak.com.au>
  3. Date: Tue, 13 Feb 2024 09:24:36 +1100
  4. Subject: [PATCH] py/objarray: Fix use-after-free if extending a bytearray from
  5. itself.
  6. Two cases, one assigning to a slice.
  7. Closes https://github.com/micropython/micropython/issues/13283
  8. Second is extending a slice from itself, similar logic.
  9. In both cases the problem occurs when m_renew causes realloc to move the
  10. buffer, leaving a dangling pointer behind.
  11. There are more complex and hard to fix cases when either argument is a
  12. memoryview into the buffer, currently resizing to a new address breaks
  13. memoryviews into that object.
  14. Reproducing this bug and confirming the fix was done by running the unix
  15. port under valgrind with GC-aware extensions.
  16. Note in default configurations with GIL this bug exists but has no impact
  17. (the free buffer won't be reused while the function is still executing, and
  18. is no longer referenced after it returns).
  19. Signed-off-by: Angus Gratton <angus@redyak.com.au>
  20. CVE: CVE-2024-8947
  21. Upstream: https://github.com/micropython/micropython/commit/4bed614e707c0644c06e117f848fa12605c711cd
  22. Signed-off-by: Thomas Perale <thomas.perale@mind.be>
  23. ---
  24. py/objarray.c | 20 ++++++++++++++++----
  25. tests/basics/bytearray_add.py | 9 ++++++++-
  26. tests/basics/bytearray_add_self.py | 8 ++++++++
  27. tests/basics/bytearray_add_self.py.exp | 1 +
  28. tests/basics/bytearray_slice_assign.py | 18 ++++++++++++------
  29. 5 files changed, 45 insertions(+), 11 deletions(-)
  30. create mode 100644 tests/basics/bytearray_add_self.py
  31. create mode 100644 tests/basics/bytearray_add_self.py.exp
  32. diff --git a/py/objarray.c b/py/objarray.c
  33. index 1fff234822521..803af2cd270c7 100644
  34. --- a/py/objarray.c
  35. +++ b/py/objarray.c
  36. @@ -424,6 +424,13 @@ static mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) {
  37. if (self->free < len) {
  38. self->items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz);
  39. self->free = 0;
  40. +
  41. + if (self_in == arg_in) {
  42. + // Get arg_bufinfo again in case self->items has moved
  43. + //
  44. + // (Note not possible to handle case that arg_in is a memoryview into self)
  45. + mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ);
  46. + }
  47. } else {
  48. self->free -= len;
  49. }
  50. @@ -456,7 +463,8 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
  51. #if MICROPY_PY_ARRAY_SLICE_ASSIGN
  52. // Assign
  53. size_t src_len;
  54. - void *src_items;
  55. + uint8_t *src_items;
  56. + size_t src_offs = 0;
  57. size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL);
  58. 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) {
  59. // value is array, bytearray or memoryview
  60. @@ -469,7 +477,7 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
  61. src_items = src_slice->items;
  62. #if MICROPY_PY_BUILTINS_MEMORYVIEW
  63. if (mp_obj_is_type(value, &mp_type_memoryview)) {
  64. - src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz);
  65. + src_offs = src_slice->memview_offset * item_sz;
  66. }
  67. #endif
  68. } else if (mp_obj_is_type(value, &mp_type_bytes)) {
  69. @@ -504,13 +512,17 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
  70. // TODO: alloc policy; at the moment we go conservative
  71. o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz);
  72. o->free = len_adj;
  73. + // m_renew may have moved o->items
  74. + if (src_items == dest_items) {
  75. + src_items = o->items;
  76. + }
  77. dest_items = o->items;
  78. }
  79. mp_seq_replace_slice_grow_inplace(dest_items, o->len,
  80. - slice.start, slice.stop, src_items, src_len, len_adj, item_sz);
  81. + slice.start, slice.stop, src_items + src_offs, src_len, len_adj, item_sz);
  82. } else {
  83. mp_seq_replace_slice_no_grow(dest_items, o->len,
  84. - slice.start, slice.stop, src_items, src_len, item_sz);
  85. + slice.start, slice.stop, src_items + src_offs, src_len, item_sz);
  86. // Clear "freed" elements at the end of list
  87. // TODO: This is actually only needed for typecode=='O'
  88. mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz);
  89. diff --git a/tests/basics/bytearray_add.py b/tests/basics/bytearray_add.py
  90. index a7e2b57374255..1f30a3b740e95 100644
  91. --- a/tests/basics/bytearray_add.py
  92. +++ b/tests/basics/bytearray_add.py
  93. @@ -15,4 +15,11 @@
  94. # this inplace add tests the code when the buffer doesn't need to be increased
  95. b = bytearray()
  96. -b += b''
  97. +b += b""
  98. +
  99. +# extend a bytearray from itself
  100. +b = bytearray(b"abcdefgh")
  101. +for _ in range(4):
  102. + c = bytearray(b) # extra allocation, as above
  103. + b.extend(b)
  104. +print(b)
  105. diff --git a/tests/basics/bytearray_add_self.py b/tests/basics/bytearray_add_self.py
  106. new file mode 100644
  107. index 0000000000000..94ae8689fd16c
  108. --- /dev/null
  109. +++ b/tests/basics/bytearray_add_self.py
  110. @@ -0,0 +1,8 @@
  111. +# add a bytearray to itself
  112. +# This is not supported by CPython as of 3.11.18.
  113. +
  114. +b = bytearray(b"123456789")
  115. +for _ in range(4):
  116. + c = bytearray(b) # extra allocation increases chance 'b' has to relocate
  117. + b += b
  118. +print(b)
  119. diff --git a/tests/basics/bytearray_add_self.py.exp b/tests/basics/bytearray_add_self.py.exp
  120. new file mode 100644
  121. index 0000000000000..5ef948157ac0f
  122. --- /dev/null
  123. +++ b/tests/basics/bytearray_add_self.py.exp
  124. @@ -0,0 +1 @@
  125. +bytearray(b'123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789')
  126. diff --git a/tests/basics/bytearray_slice_assign.py b/tests/basics/bytearray_slice_assign.py
  127. index fa7878e10ddbb..4de0819042a13 100644
  128. --- a/tests/basics/bytearray_slice_assign.py
  129. +++ b/tests/basics/bytearray_slice_assign.py
  130. @@ -18,7 +18,7 @@
  131. l[1:3] = bytearray()
  132. print(l)
  133. l = bytearray(x)
  134. -#del l[1:3]
  135. +# del l[1:3]
  136. print(l)
  137. l = bytearray(x)
  138. @@ -28,7 +28,7 @@
  139. l[:3] = bytearray()
  140. print(l)
  141. l = bytearray(x)
  142. -#del l[:3]
  143. +# del l[:3]
  144. print(l)
  145. l = bytearray(x)
  146. @@ -38,7 +38,7 @@
  147. l[:-3] = bytearray()
  148. print(l)
  149. l = bytearray(x)
  150. -#del l[:-3]
  151. +# del l[:-3]
  152. print(l)
  153. # slice assignment that extends the array
  154. @@ -61,8 +61,14 @@
  155. print(b)
  156. # Growth of bytearray via slice extension
  157. -b = bytearray(b'12345678')
  158. -b.append(57) # expand and add a bit of unused space at end of the bytearray
  159. +b = bytearray(b"12345678")
  160. +b.append(57) # expand and add a bit of unused space at end of the bytearray
  161. for i in range(400):
  162. - b[-1:] = b'ab' # grow slowly into the unused space
  163. + b[-1:] = b"ab" # grow slowly into the unused space
  164. +print(len(b), b)
  165. +
  166. +# Growth of bytearray via slice extension from itself
  167. +b = bytearray(b"1234567")
  168. +for i in range(3):
  169. + b[-1:] = b
  170. print(len(b), b)