Skip to content
Open
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
29 changes: 29 additions & 0 deletions Lib/test/test_external_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,35 @@ def {func_name}():
)
self.assertEqual(target.filename, script_name)

@skip_if_not_supported
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
"Test only runs on Linux with process_vm_readv support",
)
def test_remote_stack_trace_invalid_unicode_name(self):
import_module("_testinternalcapi")

script = textwrap.dedent("""\
import os
import _remote_debugging
import _testinternalcapi

def func():
return _remote_debugging.RemoteUnwinder(os.getpid()).get_stack_trace()

name = _testinternalcapi.new_invalid_str()
func.__code__ = func.__code__.replace(co_qualname=name)
print(func())
""")
result = subprocess.run(
[sys.executable, "-c", script],
capture_output=True,
text=True,
timeout=SHORT_TIMEOUT,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("<unknown function>", result.stdout)

@skip_if_not_supported
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the sampling profiler crashing when a string read from the target process
contains code points above ``U+10FFFF``.
67 changes: 41 additions & 26 deletions Modules/_remote_debugging/object_reading.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
******************************************************************************/

#include "_remote_debugging.h"
#include "internal/pycore_unicodeobject.h" // _Py_MAX_UNICODE
#include <limits.h>

/* ============================================================================
Expand Down Expand Up @@ -91,23 +92,15 @@ read_py_str(
}

int kind = (int)state.kind;
Py_UCS4 max_char;
switch (kind) {
case PyUnicode_1BYTE_KIND:
max_char = state.ascii ? 0x7F : 0xFF;
break;
case PyUnicode_2BYTE_KIND:
max_char = 0xFFFF;
break;
case PyUnicode_4BYTE_KIND:
max_char = 0x10FFFF;
break;
default:
PyErr_Format(PyExc_RuntimeError,
"Invalid Unicode kind %d at 0x%lx", kind, address);
set_exception_cause(unwinder, PyExc_RuntimeError,
"Invalid kind in remote Unicode object");
return NULL;
if (kind != PyUnicode_1BYTE_KIND
&& kind != PyUnicode_2BYTE_KIND
&& kind != PyUnicode_4BYTE_KIND)
{
PyErr_Format(PyExc_RuntimeError,
"Invalid Unicode kind %d at 0x%lx", kind, address);
set_exception_cause(unwinder, PyExc_RuntimeError,
"Invalid kind in remote Unicode object");
return NULL;
}

size_t header_size = state.ascii
Expand All @@ -127,16 +120,16 @@ read_py_str(
return NULL;
}

PyObject *result = PyUnicode_New(len, max_char);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to allocate PyUnicode for remote string");
return NULL;
}
if (nbytes == 0) {
return result;
return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
}

void *data = PyUnicode_DATA(result);
char *data = PyMem_Malloc(nbytes);
if (data == NULL) {
PyErr_NoMemory();
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate buffer for remote string");
return NULL;
}

// Reuse data already present in the header read; only round-trip for
// whatever spills past it.
Expand All @@ -153,14 +146,36 @@ read_py_str(
&unwinder->handle,
address + header_size + inline_bytes,
nbytes - inline_bytes,
(char *)data + inline_bytes);
data + inline_bytes);
if (res < 0) {
Py_DECREF(result);
PyMem_Free(data);
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read string data from remote memory");
return NULL;
}
}

if (kind == PyUnicode_4BYTE_KIND) {
Py_UCS4 *ucs4 = (Py_UCS4 *)data;
for (Py_ssize_t i = 0; i < len; i++) {
Py_UCS4 ch = ucs4[i];
if (ch > _Py_MAX_UNICODE) {
PyMem_Free(data);
PyErr_Format(PyExc_ValueError,
"character U+%x is not in range [U+0000; U+%x]",
ch, _Py_MAX_UNICODE);
set_exception_cause(unwinder, PyExc_ValueError,
"Unicode string contains invalid character");
return NULL;
}
}
}

PyObject *result = PyUnicode_FromKindAndData(kind, data, len);
Comment thread
StanFromIreland marked this conversation as resolved.
PyMem_Free(data);
if (result == NULL) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Invalid character data in remote Unicode object");
return NULL;
}
return result;
}

Expand Down
14 changes: 14 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,19 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg)
return _PyUnicode_TransformDecimalAndSpaceToASCII(arg);
}

/* Return a str holding code points above U+10FFFF. */
static PyObject *
new_invalid_str(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *str = PyUnicode_New(2, 0x10FFFF);
if (str == NULL) {
return NULL;
}
PyUnicode_4BYTE_DATA(str)[0] = 0x110000;
PyUnicode_4BYTE_DATA(str)[1] = 0x110000;
return str;
}

static PyObject *
test_pyobject_is_freed(const char *test_name, PyObject *op)
{
Expand Down Expand Up @@ -3395,6 +3408,7 @@ static PyMethodDef module_functions[] = {
{"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"new_invalid_str", new_invalid_str, METH_NOARGS},
{"check_pyobject_forbidden_bytes_is_freed",
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
Expand Down
Loading