Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,18 @@ def test_bytearray_api(self):
except OSError:
pass

def test_mod_concurrent_mutation(self):
# Prevent crash in __mod__ when formatting mutates the bytearray.
# Regression test for https://github.com/python/cpython/issues/142557.
fmt = bytearray(b"%a end")

class S:
def __repr__(self):
fmt.clear()
return "E"

self.assertRaises(BufferError, fmt.__mod__, S())

def test_reverse(self):
b = bytearray(b'hello')
self.assertEqual(b.reverse(), None)
Expand Down Expand Up @@ -2092,6 +2104,19 @@ def make_case():
with self.assertRaises(BufferError):
ba.rsplit(evil)

def test_hex_use_after_free(self):
# Prevent UAF in bytearray.hex(sep) with re-entrant sep.__len__.
# Regression test for https://github.com/python/cpython/issues/143195.
ba = bytearray(b'\xAA')

class S(bytes):
def __len__(self):
ba.clear()
return 1

self.assertRaises(BufferError, ba.hex, S(b':'))


class AssortedBytesTest(unittest.TestCase):
#
# Test various combinations of bytes and bytearray
Expand Down
28 changes: 28 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,20 @@ def test_hash_writable(self):
m = self._view(b)
self.assertRaises(ValueError, hash, m)

def test_hash_use_after_free(self):
# Prevent crash in memoryview(v).__hash__ with re-entrant v.__hash__.
# Regression test for https://github.com/python/cpython/issues/142664.
class E(array.array):
def __hash__(self):
mv.release()
self.clear()
return 123

v = E('B', b'A' * 4096)
mv = memoryview(v).toreadonly() # must be read-only for hash()
self.assertRaises(BufferError, hash, mv)
self.assertRaises(BufferError, mv.__hash__)

def test_weakref(self):
# Check memoryviews are weakrefable
for tp in self._types:
Expand Down Expand Up @@ -442,6 +456,20 @@ def test_issue22668(self):
self.assertEqual(c.format, "H")
self.assertEqual(d.format, "H")

def test_hex_use_after_free(self):
# Prevent UAF in memoryview.hex(sep) with re-entrant sep.__len__.
# Regression test for https://github.com/python/cpython/issues/143195.
ba = bytearray(b'A' * 1024)
mv = memoryview(ba)

class S(bytes):
def __len__(self):
mv.release()
ba.clear()
return 1

self.assertRaises(BufferError, mv.hex, S(b':'))


# Variations on source objects for the buffer: bytes-like objects, then arrays
# with itemsize > 1.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix use-after-free crashes in :meth:`bytearray.hex` and :meth:`memoryview.hex`
when the separator's :meth:`~object.__len__` mutates the original object.
Patch by Bénédikt Tran.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a use-after-free crash in :ref:`bytearray.__mod__ <bytes-formatting>` when
the :class:`!bytearray` is mutated while formatting the ``%``-style arguments.
Patch by Bénédikt Tran.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a use-after-free crash in :meth:`memoryview.__hash__ <object.__hash__>`
when the ``__hash__`` method of the referenced object mutates that object or
the view. Patch by Bénédikt Tran.
18 changes: 12 additions & 6 deletions Modules/_remote_debugging/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ _remote_debugging_RemoteUnwinder_get_stats_impl(RemoteUnwinderObject *self)
}

/*[clinic input]
@permit_long_docstring_body
@critical_section
_remote_debugging.RemoteUnwinder.pause_threads

Expand All @@ -963,7 +964,7 @@ Returns True if threads were successfully paused, False if they were already pau

static PyObject *
_remote_debugging_RemoteUnwinder_pause_threads_impl(RemoteUnwinderObject *self)
/*[clinic end generated code: output=aaf2bdc0a725750c input=78601c60dbc245fe]*/
/*[clinic end generated code: output=aaf2bdc0a725750c input=d8a266f19a81c67e]*/
{
#ifdef Py_REMOTE_DEBUG_SUPPORTS_BLOCKING
if (self->threads_stopped) {
Expand All @@ -985,6 +986,7 @@ _remote_debugging_RemoteUnwinder_pause_threads_impl(RemoteUnwinderObject *self)
}

/*[clinic input]
@permit_long_docstring_body
@critical_section
_remote_debugging.RemoteUnwinder.resume_threads

Expand All @@ -997,7 +999,7 @@ Returns True if threads were successfully resumed, False if they were not paused

static PyObject *
_remote_debugging_RemoteUnwinder_resume_threads_impl(RemoteUnwinderObject *self)
/*[clinic end generated code: output=8d6781ea37095536 input=67ca813bd804289e]*/
/*[clinic end generated code: output=8d6781ea37095536 input=16baaaab007f4259]*/
{
#ifdef Py_REMOTE_DEBUG_SUPPORTS_BLOCKING
if (!self->threads_stopped) {
Expand Down Expand Up @@ -1261,6 +1263,7 @@ class _remote_debugging.BinaryWriter "BinaryWriterObject *" "&PyBinaryWriter_Typ
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e948838b90a2003c]*/

/*[clinic input]
@permit_long_docstring_body
_remote_debugging.BinaryWriter.__init__
filename: str
sample_interval_us: unsigned_long_long
Expand All @@ -1285,7 +1288,7 @@ _remote_debugging_BinaryWriter___init___impl(BinaryWriterObject *self,
unsigned long long sample_interval_us,
unsigned long long start_time_us,
int compression)
/*[clinic end generated code: output=014c0306f1bacf4b input=57497fe3cb9214a6]*/
/*[clinic end generated code: output=014c0306f1bacf4b input=3bdf01c1cc2f5a1d]*/
{
if (self->writer) {
binary_writer_destroy(self->writer);
Expand All @@ -1300,6 +1303,7 @@ _remote_debugging_BinaryWriter___init___impl(BinaryWriterObject *self,
}

/*[clinic input]
@permit_long_docstring_body
_remote_debugging.BinaryWriter.write_sample
stack_frames: object
timestamp_us: unsigned_long_long
Expand All @@ -1315,7 +1319,7 @@ static PyObject *
_remote_debugging_BinaryWriter_write_sample_impl(BinaryWriterObject *self,
PyObject *stack_frames,
unsigned long long timestamp_us)
/*[clinic end generated code: output=24d5b86679b4128f input=dce3148417482624]*/
/*[clinic end generated code: output=24d5b86679b4128f input=4e6d832d360bea46]*/
{
if (!self->writer) {
PyErr_SetString(PyExc_ValueError, "Writer is closed");
Expand Down Expand Up @@ -1422,6 +1426,7 @@ _remote_debugging_BinaryWriter___exit___impl(BinaryWriterObject *self,
}

/*[clinic input]
@permit_long_docstring_body
_remote_debugging.BinaryWriter.get_stats

Get encoding statistics for the writer.
Expand All @@ -1432,7 +1437,7 @@ record counts, frames written/saved, and compression ratio.

static PyObject *
_remote_debugging_BinaryWriter_get_stats_impl(BinaryWriterObject *self)
/*[clinic end generated code: output=06522cd52544df89 input=82968491b53ad277]*/
/*[clinic end generated code: output=06522cd52544df89 input=837c874ffdebd24c]*/
{
if (!self->writer) {
PyErr_SetString(PyExc_ValueError, "Writer is closed");
Expand Down Expand Up @@ -1754,6 +1759,7 @@ _remote_debugging_zstd_available_impl(PyObject *module)
* ============================================================================ */

/*[clinic input]
@permit_long_docstring_body
_remote_debugging.get_child_pids

pid: int
Expand All @@ -1779,7 +1785,7 @@ Child processes may exit or new ones may be created after the list is returned.
static PyObject *
_remote_debugging_get_child_pids_impl(PyObject *module, int pid,
int recursive)
/*[clinic end generated code: output=1ae2289c6b953e4b input=3395cbe7f17066c9]*/
/*[clinic end generated code: output=1ae2289c6b953e4b input=19d8d5d6e2b59e6e]*/
{
return enumerate_child_pids((pid_t)pid, recursive);
}
Expand Down
18 changes: 16 additions & 2 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2664,7 +2664,13 @@ bytearray_hex_impl(PyByteArrayObject *self, PyObject *sep, int bytes_per_sep)
{
char* argbuf = PyByteArray_AS_STRING(self);
Py_ssize_t arglen = PyByteArray_GET_SIZE(self);
return _Py_strhex_with_sep(argbuf, arglen, sep, bytes_per_sep);
// Prevent 'self' from being freed if computing len(sep) mutates 'self'
// in _Py_strhex_with_sep().
// See: https://github.com/python/cpython/issues/143195.
self->ob_exports++;
PyObject *res = _Py_strhex_with_sep(argbuf, arglen, sep, bytes_per_sep);
self->ob_exports--;
return res;
}

static PyObject *
Expand Down Expand Up @@ -2837,7 +2843,15 @@ bytearray_mod_lock_held(PyObject *v, PyObject *w)
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(v);
if (!PyByteArray_Check(v))
Py_RETURN_NOTIMPLEMENTED;
return _PyBytes_FormatEx(PyByteArray_AS_STRING(v), PyByteArray_GET_SIZE(v), w, 1);

PyByteArrayObject *self = _PyByteArray_CAST(v);
/* Increase exports to prevent bytearray storage from changing during op. */
self->ob_exports++;
PyObject *res = _PyBytes_FormatEx(
PyByteArray_AS_STRING(v), PyByteArray_GET_SIZE(v), w, 1
);
self->ob_exports--;
return res;
}

static PyObject *
Expand Down
21 changes: 17 additions & 4 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2349,7 +2349,13 @@ memoryview_hex_impl(PyMemoryViewObject *self, PyObject *sep,
CHECK_RELEASED(self);

if (MV_C_CONTIGUOUS(self->flags)) {
return _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep);
// Prevent 'self' from being freed if computing len(sep) mutates 'self'
// in _Py_strhex_with_sep().
// See: https://github.com/python/cpython/issues/143195.
self->exports++;
PyObject *ret = _Py_strhex_with_sep(src->buf, src->len, sep, bytes_per_sep);
self->exports--;
return ret;
}

PyBytesWriter *writer = PyBytesWriter_Create(src->len);
Expand Down Expand Up @@ -3225,9 +3231,16 @@ memory_hash(PyObject *_self)
"memoryview: hashing is restricted to formats 'B', 'b' or 'c'");
return -1;
}
if (view->obj != NULL && PyObject_Hash(view->obj) == -1) {
/* Keep the original error message */
return -1;
if (view->obj != NULL) {
// Prevent 'self' from being freed when computing the item's hash.
// See https://github.com/python/cpython/issues/142664.
self->exports++;
Py_hash_t h = PyObject_Hash(view->obj);
self->exports--;
if (h == -1) {
/* Keep the original error message */
return -1;
}
}

if (!MV_C_CONTIGUOUS(self->flags)) {
Expand Down
4 changes: 3 additions & 1 deletion Python/remote_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ static void
_Py_RemoteDebug_FreePageCache(proc_handle_t *handle)
{
for (int i = 0; i < MAX_PAGES; i++) {
PyMem_RawFree(handle->pages[i].data);
if (handle->pages[i].data) {
PyMem_RawFree(handle->pages[i].data);
}
handle->pages[i].data = NULL;
handle->pages[i].valid = 0;
}
Expand Down
Loading