Skip to content

Commit 04735de

Browse files
authored
[3.14] gh-149619: Harden _remote_debugging error paths (GH-150349) (#154844)
(cherry picked from commit a5be25d)
1 parent c533f18 commit 04735de

3 files changed

Lines changed: 354 additions & 133 deletions

File tree

Modules/_remote_debugging_module.c

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
// issues.
2929
#include <errno.h>
3030
#include <fcntl.h>
31+
#include <limits.h>
3132
#include <stddef.h>
3233
#include <stdint.h>
3334
#include <stdio.h>
@@ -807,7 +808,7 @@ static int append_awaited_by(RemoteUnwinderObject *unwinder, unsigned long tid,
807808
#define set_exception_cause(unwinder, exc_type, message) \
808809
do { \
809810
assert(PyErr_Occurred() && "function returned -1 without setting exception"); \
810-
if (unwinder->debug) { \
811+
if (unwinder->debug && !_Py_RemoteDebug_HasPermissionError()) { \
811812
_set_debug_exception_cause(exc_type, message); \
812813
} \
813814
} while (0)
@@ -1246,26 +1247,21 @@ read_py_long(
12461247
return -1;
12471248
}
12481249

1249-
// If the long object has inline digits, use them directly
1250-
digit *digits;
1251-
if (size <= _PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS) {
1252-
// For small integers, digits are inline in the long_value.ob_digit array
1253-
digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
1254-
if (!digits) {
1255-
PyErr_NoMemory();
1256-
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for small PyLong");
1257-
return -1;
1258-
}
1259-
memcpy(digits, long_obj + unwinder->debug_offsets.long_object.ob_digit, size * sizeof(digit));
1260-
} else {
1261-
// For larger integers, we need to read the digits separately
1262-
digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
1263-
if (!digits) {
1264-
PyErr_NoMemory();
1265-
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for large PyLong");
1266-
return -1;
1267-
}
1250+
// Calculate how many digits fit inline in our local buffer
1251+
Py_ssize_t ob_digit_offset = unwinder->debug_offsets.long_object.ob_digit;
1252+
Py_ssize_t inline_digits_space = SIZEOF_LONG_OBJ - ob_digit_offset;
1253+
Py_ssize_t max_inline_digits = inline_digits_space / (Py_ssize_t)sizeof(digit);
12681254

1255+
digit *digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
1256+
if (!digits) {
1257+
PyErr_NoMemory();
1258+
set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate digits for PyLong");
1259+
return -1;
1260+
}
1261+
1262+
if (size <= max_inline_digits && size <= _PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS) {
1263+
memcpy(digits, long_obj + ob_digit_offset, size * sizeof(digit));
1264+
} else {
12691265
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
12701266
&unwinder->handle,
12711267
address + (uintptr_t)unwinder->debug_offsets.long_object.ob_digit,
@@ -1278,19 +1274,34 @@ read_py_long(
12781274
}
12791275
}
12801276

1281-
long long value = 0;
1277+
unsigned long limit = negative
1278+
? (unsigned long)LONG_MAX + 1UL
1279+
: (unsigned long)LONG_MAX;
1280+
unsigned long value = 0;
12821281

1283-
// In theory this can overflow, but because of llvm/llvm-project#16778
1284-
// we can't use __builtin_mul_overflow because it fails to link with
1285-
// __muloti4 on aarch64. In practice this is fine because all we're
1286-
// testing here are task numbers that would fit in a single byte.
1287-
for (Py_ssize_t i = 0; i < size; ++i) {
1288-
long long factor = digits[i] * (1UL << (Py_ssize_t)(shift * i));
1289-
value += factor;
1282+
for (Py_ssize_t i = size; i-- > 0;) {
1283+
if (digits[i] >= PyLong_BASE) {
1284+
PyErr_Format(PyExc_RuntimeError,
1285+
"Invalid PyLong digit: %u (base %u)", digits[i], PyLong_BASE);
1286+
set_exception_cause(unwinder, PyExc_RuntimeError,
1287+
"Invalid PyLong digit (corrupted remote memory)");
1288+
goto error;
1289+
}
1290+
if (value > ((limit - (unsigned long)digits[i]) >> shift)) {
1291+
PyErr_SetString(PyExc_OverflowError,
1292+
"Remote PyLong value does not fit in C long");
1293+
set_exception_cause(unwinder, PyExc_OverflowError,
1294+
"Remote PyLong value is too large");
1295+
goto error;
1296+
}
1297+
value = (value << shift) | (unsigned long)digits[i];
12901298
}
12911299
PyMem_RawFree(digits);
12921300
if (negative) {
1293-
value *= -1;
1301+
if (value == (unsigned long)LONG_MAX + 1UL) {
1302+
return LONG_MIN;
1303+
}
1304+
return -(long)value;
12941305
}
12951306
return (long)value;
12961307
error:
@@ -1312,32 +1323,35 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle)
13121323
// On Windows, search for asyncio debug in executable or DLL
13131324
address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio", NULL);
13141325
if (address == 0) {
1315-
// Error out: 'python' substring covers both executable and DLL
1316-
PyObject *exc = PyErr_GetRaisedException();
1317-
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1318-
_PyErr_ChainExceptions1(exc);
1326+
if (!_Py_RemoteDebug_HasPermissionError()) {
1327+
PyObject *exc = PyErr_GetRaisedException();
1328+
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1329+
_PyErr_ChainExceptions1(exc);
1330+
}
13191331
}
13201332
#elif defined(__linux__)
13211333
// On Linux, search for asyncio debug in executable or DLL
13221334
address = search_linux_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
13231335
if (address == 0) {
1324-
// Error out: 'python' substring covers both executable and DLL
1325-
PyObject *exc = PyErr_GetRaisedException();
1326-
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1327-
_PyErr_ChainExceptions1(exc);
1336+
if (!_Py_RemoteDebug_HasPermissionError()) {
1337+
PyObject *exc = PyErr_GetRaisedException();
1338+
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1339+
_PyErr_ChainExceptions1(exc);
1340+
}
13281341
}
13291342
#elif defined(__APPLE__) && TARGET_OS_OSX
13301343
// On macOS, try libpython first, then fall back to python
13311344
address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
1332-
if (address == 0) {
1345+
if (address == 0 && !_Py_RemoteDebug_HasPermissionError()) {
13331346
PyErr_Clear();
13341347
address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython", NULL);
13351348
}
13361349
if (address == 0) {
1337-
// Error out: 'python' substring covers both executable and DLL
1338-
PyObject *exc = PyErr_GetRaisedException();
1339-
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1340-
_PyErr_ChainExceptions1(exc);
1350+
if (!_Py_RemoteDebug_HasPermissionError()) {
1351+
PyObject *exc = PyErr_GetRaisedException();
1352+
PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug section in the process.");
1353+
_PyErr_ChainExceptions1(exc);
1354+
}
13411355
}
13421356
#else
13431357
Py_UNREACHABLE();
@@ -1418,7 +1432,7 @@ parse_task_name(
14181432

14191433
if ((GET_MEMBER(unsigned long, type_obj, unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_LONG_SUBCLASS)) {
14201434
long res = read_py_long(unwinder, task_name_addr);
1421-
if (res == -1) {
1435+
if (res == -1 && PyErr_Occurred()) {
14221436
set_exception_cause(unwinder, PyExc_RuntimeError, "Task name PyLong parsing failed");
14231437
return NULL;
14241438
}
@@ -3124,6 +3138,9 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
31243138
return -1;
31253139
}
31263140
if (async_debug_result < 0) {
3141+
if (_Py_RemoteDebug_HasPermissionError()) {
3142+
return -1;
3143+
}
31273144
PyErr_Clear();
31283145
memset(&self->async_debug_offsets, 0, sizeof(self->async_debug_offsets));
31293146
self->async_debug_offsets_available = 0;

0 commit comments

Comments
 (0)