https://github.com/python/cpython/commit/d5381e18b85c6781e4961e5d98e2ef23781f654c
commit: d5381e18b85c6781e4961e5d98e2ef23781f654c
branch: 3.15
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-05-25T23:02:37Z
summary:
[3.15] gh-149619: Harden _remote_debugging error paths (GH-150349) (#150435)
(cherry picked from commit a5be25d3bdc1b3cbc9638a3249c0e3db5a97ebc6)
files:
M Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
M Modules/_remote_debugging/_remote_debugging.h
M Modules/_remote_debugging/asyncio.c
M Modules/_remote_debugging/binary_io_reader.c
M Modules/_remote_debugging/binary_io_writer.c
M Modules/_remote_debugging/code_objects.c
M Modules/_remote_debugging/module.c
M Modules/_remote_debugging/object_reading.c
M Modules/_remote_debugging/subprocess.c
M Modules/_remote_debugging/threads.c
M Python/remote_debug.h
diff --git
a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
index 1fbb4e2d6c6fbb..5efc60a9211175 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
@@ -975,7 +975,11 @@ def
test_writer_total_samples_after_close_returns_zero(self):
class TestBinaryFormatValidation(BinaryFormatTestBase):
"""Tests for malformed binary files."""
+ HDR_OFF_SAMPLES = 28
HDR_OFF_THREADS = 32
+ HDR_OFF_STR_TABLE = 36
+ HDR_OFF_FRAME_TABLE = 44
+ FILE_HEADER_PLACEHOLDER_SIZE = 64
def test_replay_rejects_more_threads_than_declared(self):
"""Replay rejects files with more unique threads than the header
declares."""
@@ -1000,6 +1004,43 @@ def test_replay_rejects_more_threads_than_declared(self):
"threads than declared in header (declared 1, found at least
2)",
)
+ def test_replay_rejects_sample_count_mismatch(self):
+ """Replay rejects files whose decoded samples disagree with the
header."""
+ samples = [[make_interpreter(0, [
+ make_thread(1, [make_frame("sample.py", 10, "sample")])
+ ])]]
+ filename = self.create_binary_file(samples, compression="none")
+
+ with open(filename, "r+b") as raw:
+ raw.seek(self.HDR_OFF_SAMPLES)
+ raw.write(struct.pack("=I", 2))
+
+ with BinaryReader(filename) as reader:
+ self.assertEqual(reader.get_info()["sample_count"], 2)
+ with self.assertRaises(ValueError) as cm:
+ reader.replay_samples(RawCollector())
+ self.assertEqual(
+ str(cm.exception),
+ "Sample count mismatch: header declares 2 samples "
+ "but replay decoded 1",
+ )
+
+ def test_replay_rejects_trailing_partial_sample_header(self):
+ """Replay rejects partial sample bytes instead of silently stopping."""
+ filename = self.create_binary_file([], compression="none")
+ sample_data_end = self.FILE_HEADER_PLACEHOLDER_SIZE + 1
+
+ with open(filename, "r+b") as raw:
+ raw.seek(self.HDR_OFF_STR_TABLE)
+ raw.write(struct.pack("=Q", sample_data_end))
+ raw.seek(self.HDR_OFF_FRAME_TABLE)
+ raw.write(struct.pack("=Q", sample_data_end))
+
+ with BinaryReader(filename) as reader:
+ with self.assertRaises(ValueError) as cm:
+ reader.replay_samples(RawCollector())
+ self.assertEqual(str(cm.exception), "Truncated sample data: 1
trailing bytes")
+
class TestBinaryEncodings(BinaryFormatTestBase):
"""Tests specifically targeting different stack encodings."""
diff --git a/Modules/_remote_debugging/_remote_debugging.h
b/Modules/_remote_debugging/_remote_debugging.h
index d91ce54a18c813..635e6e208902af 100644
--- a/Modules/_remote_debugging/_remote_debugging.h
+++ b/Modules/_remote_debugging/_remote_debugging.h
@@ -180,7 +180,7 @@ typedef enum _WIN32_THREADSTATE {
#define set_exception_cause(unwinder, exc_type, message)
\
do {
\
assert(PyErr_Occurred() && "function returned -1 without setting
exception"); \
- if (unwinder->debug) {
\
+ if (unwinder->debug && !_Py_RemoteDebug_HasPermissionError()) {
\
_set_debug_exception_cause(exc_type, message);
\
}
\
} while (0)
diff --git a/Modules/_remote_debugging/asyncio.c
b/Modules/_remote_debugging/asyncio.c
index fc7487d4044bfb..44a9a3cbce0061 100644
--- a/Modules/_remote_debugging/asyncio.c
+++ b/Modules/_remote_debugging/asyncio.c
@@ -22,35 +22,38 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t*
handle)
address = search_windows_map_for_section(handle, "AsyncioD", L"_asyncio",
NULL);
if (address == 0) {
- // Error out: 'python' substring covers both executable and DLL
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug
section in the process.");
- _PyErr_ChainExceptions1(exc);
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the
AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
}
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
// On Linux, search for asyncio debug in executable or DLL
address = search_linux_map_for_section(handle, "AsyncioDebug", "python",
NULL);
if (address == 0) {
- // Error out: 'python' substring covers both executable and DLL
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug
section in the process.");
- _PyErr_ChainExceptions1(exc);
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the
AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
}
#elif defined(__APPLE__) && TARGET_OS_OSX
// On macOS, try libpython first, then fall back to python
address = search_map_for_section(handle, "AsyncioDebug", "libpython",
NULL);
- if (address == 0) {
+ if (address == 0 && !_Py_RemoteDebug_HasPermissionError()) {
PyErr_Clear();
address = search_map_for_section(handle, "AsyncioDebug", "python",
NULL);
}
if (address == 0) {
- // Error out: 'python' substring covers both executable and DLL
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_SetString(PyExc_RuntimeError, "Failed to find the AsyncioDebug
section in the process.");
- _PyErr_ChainExceptions1(exc);
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_SetString(PyExc_RuntimeError, "Failed to find the
AsyncioDebug section in the process.");
+ _PyErr_ChainExceptions1(exc);
+ }
}
#else
Py_UNREACHABLE();
@@ -96,10 +99,12 @@ ensure_async_debug_offsets(RemoteUnwinderObject *unwinder)
return -1;
}
if (result < 0) {
- PyErr_Clear();
- PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not
available");
- set_exception_cause(unwinder, PyExc_RuntimeError,
- "AsyncioDebug section unavailable - asyncio module may not be
loaded in target process");
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not
available");
+ set_exception_cause(unwinder, PyExc_RuntimeError,
+ "AsyncioDebug section unavailable - asyncio module may not be
loaded in target process");
+ }
return -1;
}
@@ -218,7 +223,7 @@ parse_task_name(
if ((GET_MEMBER(unsigned long, type_obj,
unwinder->debug_offsets.type_object.tp_flags) & Py_TPFLAGS_LONG_SUBCLASS)) {
long res = read_py_long(unwinder, task_name_addr);
- if (res == -1) {
+ if (res == -1 && PyErr_Occurred()) {
set_exception_cause(unwinder, PyExc_RuntimeError, "Task name
PyLong parsing failed");
return NULL;
}
diff --git a/Modules/_remote_debugging/binary_io_reader.c
b/Modules/_remote_debugging/binary_io_reader.c
index 972b197cfbad86..ce1c3d232c94e0 100644
--- a/Modules/_remote_debugging/binary_io_reader.c
+++ b/Modules/_remote_debugging/binary_io_reader.c
@@ -380,7 +380,22 @@ binary_reader_open(PyObject *path)
Py_fclose(fp);
goto error;
}
+ if (st.st_size < 0) {
+ PyErr_SetString(PyExc_IOError, "Invalid negative file size");
+ Py_fclose(fp);
+ goto error;
+ }
+ if ((uintmax_t)st.st_size > SIZE_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "File is too large to map");
+ Py_fclose(fp);
+ goto error;
+ }
reader->mapped_size = st.st_size;
+ if (reader->mapped_size == 0) {
+ PyErr_SetString(PyExc_ValueError, "File too small for header");
+ Py_fclose(fp);
+ goto error;
+ }
/* Map the file into memory.
* MAP_POPULATE (Linux-only) pre-faults all pages at mmap time, which:
@@ -424,7 +439,10 @@ binary_reader_open(PyObject *path)
}
#endif
- (void)Py_fclose(fp);
+ if (Py_fclose(fp) != 0) {
+ PyErr_SetFromErrno(PyExc_IOError);
+ goto error;
+ }
uint8_t *data = reader->mapped_data;
size_t file_size = reader->mapped_size;
@@ -444,7 +462,15 @@ binary_reader_open(PyObject *path)
PyErr_SetFromErrno(PyExc_IOError);
goto error;
}
+ if ((uint64_t)file_size_off > SIZE_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "File is too large to read");
+ goto error;
+ }
reader->file_size = (size_t)file_size_off;
+ if (reader->file_size == 0) {
+ PyErr_SetString(PyExc_ValueError, "File too small for header");
+ goto error;
+ }
if (FSEEK64(reader->fp, 0, SEEK_SET) != 0) {
PyErr_SetFromErrno(PyExc_IOError);
goto error;
@@ -456,8 +482,18 @@ binary_reader_open(PyObject *path)
goto error;
}
- if (fread(reader->file_data, 1, reader->file_size, reader->fp) !=
reader->file_size) {
- PyErr_SetFromErrno(PyExc_IOError);
+ size_t nread = fread(reader->file_data, 1, reader->file_size, reader->fp);
+ if (nread != reader->file_size) {
+ int err = errno;
+ if (ferror(reader->fp) && err != 0) {
+ errno = err;
+ PyErr_SetFromErrno(PyExc_IOError);
+ }
+ else {
+ PyErr_Format(PyExc_ValueError,
+ "Unexpected end of file: read %zu of %zu bytes",
+ nread, reader->file_size);
+ }
goto error;
}
@@ -944,10 +980,16 @@ invoke_progress_callback(PyObject *callback, Py_ssize_t
current, uint32_t total)
Py_ssize_t
binary_reader_replay(BinaryReader *reader, PyObject *collector, PyObject
*progress_callback)
{
- if (!PyObject_HasAttrString(collector, "collect")) {
+ PyObject *collect_method;
+ int has_collect = PyObject_GetOptionalAttrString(collector, "collect",
&collect_method);
+ if (has_collect < 0) {
+ return -1;
+ }
+ if (has_collect == 0) {
PyErr_SetString(PyExc_TypeError, "Collector must have a collect()
method");
return -1;
}
+ Py_DECREF(collect_method);
/* Get module state for struct sequence types */
PyObject *module = PyImport_ImportModule("_remote_debugging");
@@ -973,7 +1015,10 @@ binary_reader_replay(BinaryReader *reader, PyObject
*collector, PyObject *progre
while (offset < reader->sample_data_size) {
/* Read thread_id (8 bytes) + interpreter_id (4 bytes) + encoding byte
*/
if (reader->sample_data_size - offset < SAMPLE_HEADER_FIXED_SIZE) {
- break; /* End of data */
+ PyErr_Format(PyExc_ValueError,
+ "Truncated sample data: %zu trailing bytes",
+ reader->sample_data_size - offset);
+ return -1;
}
/* Use memcpy to avoid strict aliasing violations, then byte-swap if
needed */
@@ -1019,6 +1064,11 @@ binary_reader_replay(BinaryReader *reader, PyObject
*collector, PyObject *progre
count, max_possible_samples);
return -1;
}
+ if ((uint64_t)count > (uint64_t)PY_SSIZE_T_MAX -
(uint64_t)replayed) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Sample count exceeds Py_ssize_t maximum");
+ return -1;
+ }
reader->stats.repeat_records++;
reader->stats.repeat_samples += count;
@@ -1149,6 +1199,11 @@ binary_reader_replay(BinaryReader *reader, PyObject
*collector, PyObject *progre
return -1;
}
Py_DECREF(timestamps_list);
+ if (replayed == PY_SSIZE_T_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Sample count exceeds Py_ssize_t maximum");
+ return -1;
+ }
replayed++;
reader->stats.total_samples++;
break;
@@ -1167,6 +1222,13 @@ binary_reader_replay(BinaryReader *reader, PyObject
*collector, PyObject *progre
}
}
+ if ((uint64_t)replayed != reader->sample_count) {
+ PyErr_Format(PyExc_ValueError,
+ "Sample count mismatch: header declares %u samples but replay
decoded %zd",
+ reader->sample_count, replayed);
+ return -1;
+ }
+
/* Final progress callback at 100% */
if (invoke_progress_callback(progress_callback, replayed,
reader->sample_count) < 0) {
return -1;
diff --git a/Modules/_remote_debugging/binary_io_writer.c
b/Modules/_remote_debugging/binary_io_writer.c
index c31ed7d746466f..341f9f7dc8ac45 100644
--- a/Modules/_remote_debugging/binary_io_writer.c
+++ b/Modules/_remote_debugging/binary_io_writer.c
@@ -108,7 +108,15 @@ fwrite_checked_allow_threads(const void *data, size_t
size, FILE *fp)
written = fwrite(data, 1, size, fp);
Py_END_ALLOW_THREADS
if (written != size) {
- PyErr_SetFromErrno(PyExc_IOError);
+ int err = errno;
+ if (ferror(fp) && err != 0) {
+ errno = err;
+ PyErr_SetFromErrno(PyExc_IOError);
+ }
+ else {
+ PyErr_Format(PyExc_IOError,
+ "short write: wrote %zu of %zu bytes", written, size);
+ }
return -1;
}
return 0;
@@ -366,6 +374,11 @@ writer_intern_string(BinaryWriter *writer, PyObject
*string, uint32_t *index)
return 0;
}
+ if (writer->string_count >= UINT32_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many strings for binary format");
+ return -1;
+ }
if (writer->string_count >= writer->string_capacity) {
if (grow_parallel_arrays((void **)&writer->strings,
(void **)&writer->string_lengths,
@@ -380,6 +393,12 @@ writer_intern_string(BinaryWriter *writer, PyObject
*string, uint32_t *index)
if (!str_data) {
return -1;
}
+ if ((uintmax_t)str_len > UINT32_MAX) {
+ PyErr_Format(PyExc_OverflowError,
+ "string length %zd exceeds binary format maximum %u",
+ str_len, UINT32_MAX);
+ return -1;
+ }
char *str_copy = PyMem_Malloc(str_len + 1);
if (!str_copy) {
@@ -422,6 +441,11 @@ writer_intern_frame(BinaryWriter *writer, const FrameEntry
*entry, uint32_t *ind
return 0;
}
+ if (writer->frame_count >= UINT32_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many frames for binary format");
+ return -1;
+ }
if (GROW_ARRAY(writer->frame_entries, writer->frame_count,
writer->frame_capacity, FrameEntry) < 0) {
return -1;
@@ -466,6 +490,11 @@ writer_get_or_create_thread_entry(BinaryWriter *writer,
uint64_t thread_id,
}
}
+ if (writer->thread_count >= UINT32_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many threads for binary format");
+ return NULL;
+ }
if (writer->thread_count >= writer->thread_capacity) {
ThreadEntry *new_entries = grow_array(writer->thread_entries,
&writer->thread_capacity,
@@ -600,6 +629,11 @@ flush_pending_rle(BinaryWriter *writer, ThreadEntry *entry)
if (!entry->has_pending_rle || entry->pending_rle_count == 0) {
return 0;
}
+ if (entry->pending_rle_count > UINT32_MAX - writer->total_samples) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many samples for binary format");
+ return -1;
+ }
/* Write RLE record:
* [thread_id: 8] [interpreter_id: 4] [STACK_REPEAT: 1] [count: varint]
@@ -644,6 +678,12 @@ write_sample_with_encoding(BinaryWriter *writer,
ThreadEntry *entry,
const uint32_t *frame_indices, size_t stack_depth,
size_t shared_count, size_t pop_count, size_t
push_count)
{
+ if (writer->total_samples == UINT32_MAX) {
+ PyErr_SetString(PyExc_OverflowError,
+ "too many samples for binary format");
+ return -1;
+ }
+
/* Header: thread_id(8) + interpreter_id(4) + encoding(1) + delta(varint)
+ status(1) */
uint8_t header_buf[SAMPLE_HEADER_MAX_SIZE];
memcpy(header_buf + SMP_OFF_THREAD_ID, &entry->thread_id,
SMP_SIZE_THREAD_ID);
diff --git a/Modules/_remote_debugging/code_objects.c
b/Modules/_remote_debugging/code_objects.c
index 3af58f2b3c379e..ab889a130ee4e7 100644
--- a/Modules/_remote_debugging/code_objects.c
+++ b/Modules/_remote_debugging/code_objects.c
@@ -47,7 +47,6 @@ cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t
code_addr, uintptr_t
// Read the TLBC array pointer
if (read_ptr(unwinder, tlbc_array_addr, &tlbc_array_ptr) != 0) {
- PyErr_SetString(PyExc_RuntimeError, "Failed to read TLBC array
pointer");
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC
array pointer");
return 0; // Read error
}
@@ -61,7 +60,6 @@ cache_tlbc_array(RemoteUnwinderObject *unwinder, uintptr_t
code_addr, uintptr_t
// Read the TLBC array size
Py_ssize_t tlbc_size;
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle,
tlbc_array_ptr, sizeof(tlbc_size), &tlbc_size) != 0) {
- PyErr_SetString(PyExc_RuntimeError, "Failed to read TLBC array size");
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read TLBC
array size");
return 0; // Read error
}
diff --git a/Modules/_remote_debugging/module.c
b/Modules/_remote_debugging/module.c
index 3e60a7c2f794ad..984213d1881752 100644
--- a/Modules/_remote_debugging/module.c
+++ b/Modules/_remote_debugging/module.c
@@ -411,6 +411,9 @@
_remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
return -1;
}
if (async_debug_result < 0) {
+ if (_Py_RemoteDebug_HasPermissionError()) {
+ return -1;
+ }
PyErr_Clear();
memset(&self->async_debug_offsets, 0,
sizeof(self->async_debug_offsets));
self->async_debug_offsets_available = 0;
diff --git a/Modules/_remote_debugging/object_reading.c
b/Modules/_remote_debugging/object_reading.c
index b63b103a2617ac..1cea96a2151fcc 100644
--- a/Modules/_remote_debugging/object_reading.c
+++ b/Modules/_remote_debugging/object_reading.c
@@ -6,6 +6,7 @@
******************************************************************************/
#include "_remote_debugging.h"
+#include <limits.h>
/* ============================================================================
* MEMORY READING FUNCTIONS
@@ -264,26 +265,16 @@ read_py_long(
Py_ssize_t inline_digits_space = SIZEOF_LONG_OBJ - ob_digit_offset;
Py_ssize_t max_inline_digits = inline_digits_space /
(Py_ssize_t)sizeof(digit);
- // If the long object has inline digits that fit in our buffer, use them
directly
- digit *digits;
+ digit *digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
+ if (!digits) {
+ PyErr_NoMemory();
+ set_exception_cause(unwinder, PyExc_MemoryError, "Failed to allocate
digits for PyLong");
+ return -1;
+ }
+
if (size <= max_inline_digits && size <= _PY_NSMALLNEGINTS +
_PY_NSMALLPOSINTS) {
- // For small integers, digits are inline in the long_value.ob_digit
array
- digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
- if (!digits) {
- PyErr_NoMemory();
- set_exception_cause(unwinder, PyExc_MemoryError, "Failed to
allocate digits for small PyLong");
- return -1;
- }
memcpy(digits, long_obj + ob_digit_offset, size * sizeof(digit));
} else {
- // For larger integers, we need to read the digits separately
- digits = (digit *)PyMem_RawMalloc(size * sizeof(digit));
- if (!digits) {
- PyErr_NoMemory();
- set_exception_cause(unwinder, PyExc_MemoryError, "Failed to
allocate digits for large PyLong");
- return -1;
- }
-
bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
address + (uintptr_t)unwinder->debug_offsets.long_object.ob_digit,
@@ -296,19 +287,34 @@ read_py_long(
}
}
- long long value = 0;
+ unsigned long limit = negative
+ ? (unsigned long)LONG_MAX + 1UL
+ : (unsigned long)LONG_MAX;
+ unsigned long value = 0;
- // In theory this can overflow, but because of llvm/llvm-project#16778
- // we can't use __builtin_mul_overflow because it fails to link with
- // __muloti4 on aarch64. In practice this is fine because all we're
- // testing here are task numbers that would fit in a single byte.
- for (Py_ssize_t i = 0; i < size; ++i) {
- long long factor = digits[i] * (1UL << (Py_ssize_t)(shift * i));
- value += factor;
+ for (Py_ssize_t i = size; i-- > 0;) {
+ if (digits[i] >= PyLong_BASE) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Invalid PyLong digit: %u (base %u)", digits[i], PyLong_BASE);
+ set_exception_cause(unwinder, PyExc_RuntimeError,
+ "Invalid PyLong digit (corrupted remote memory)");
+ goto error;
+ }
+ if (value > ((limit - (unsigned long)digits[i]) >> shift)) {
+ PyErr_SetString(PyExc_OverflowError,
+ "Remote PyLong value does not fit in C long");
+ set_exception_cause(unwinder, PyExc_OverflowError,
+ "Remote PyLong value is too large");
+ goto error;
+ }
+ value = (value << shift) | (unsigned long)digits[i];
}
PyMem_RawFree(digits);
if (negative) {
- value *= -1;
+ if (value == (unsigned long)LONG_MAX + 1UL) {
+ return LONG_MIN;
+ }
+ return -(long)value;
}
return (long)value;
error:
diff --git a/Modules/_remote_debugging/subprocess.c
b/Modules/_remote_debugging/subprocess.c
index 1b16dd8343f2a5..cdad75e318be91 100644
--- a/Modules/_remote_debugging/subprocess.c
+++ b/Modules/_remote_debugging/subprocess.c
@@ -223,8 +223,19 @@ get_child_pids_platform(pid_t target_pid, int recursive,
pid_array_t *result)
}
/* Single pass: collect PIDs and their PPIDs together */
- struct dirent *entry;
- while ((entry = readdir(proc_dir)) != NULL) {
+ for (;;) {
+ errno = 0;
+ struct dirent *entry = readdir(proc_dir);
+ if (entry == NULL) {
+ if (errno != 0) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, "/proc",
+ "Failed to read process directory '/proc': %s",
+ strerror(err));
+ goto done;
+ }
+ break;
+ }
/* Skip non-numeric entries (also skips . and ..) */
if (entry->d_name[0] < '1' || entry->d_name[0] > '9') {
continue;
@@ -245,7 +256,14 @@ get_child_pids_platform(pid_t target_pid, int recursive,
pid_array_t *result)
}
}
- closedir(proc_dir);
+ if (closedir(proc_dir) != 0) {
+ int err = errno;
+ proc_dir = NULL;
+ _set_debug_oserror_from_errno_with_filename(err, "/proc",
+ "Failed to close process directory '/proc': %s",
+ strerror(err));
+ goto done;
+ }
proc_dir = NULL;
if (find_children_bfs(target_pid, recursive,
@@ -358,7 +376,8 @@ get_child_pids_platform(pid_t target_pid, int recursive,
pid_array_t *result)
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
- PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
goto done;
}
@@ -373,13 +392,23 @@ get_child_pids_platform(pid_t target_pid, int recursive,
pid_array_t *result)
/* Single pass: collect PIDs and PPIDs together */
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
- if (Process32First(snapshot, &pe)) {
- do {
- if (pid_array_append(&all_pids, (pid_t)pe.th32ProcessID) < 0 ||
- pid_array_append(&ppids, (pid_t)pe.th32ParentProcessID) < 0) {
- goto done;
- }
- } while (Process32Next(snapshot, &pe));
+ if (!Process32First(snapshot, &pe)) {
+ DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
+ goto done;
+ }
+
+ do {
+ if (pid_array_append(&all_pids, (pid_t)pe.th32ProcessID) < 0 ||
+ pid_array_append(&ppids, (pid_t)pe.th32ParentProcessID) < 0) {
+ goto done;
+ }
+ } while (Process32Next(snapshot, &pe));
+
+ DWORD error = GetLastError();
+ if (error != ERROR_NO_MORE_FILES) {
+ PyErr_SetFromWindowsErr(error);
+ goto done;
}
CloseHandle(snapshot);
diff --git a/Modules/_remote_debugging/threads.c
b/Modules/_remote_debugging/threads.c
index ae120a26d5f4ec..5176c4cf0671bb 100644
--- a/Modules/_remote_debugging/threads.c
+++ b/Modules/_remote_debugging/threads.c
@@ -658,8 +658,7 @@ read_thread_ids(RemoteUnwinderObject *unwinder,
_Py_RemoteDebug_ThreadsState *st
DIR *dir = opendir(task_path);
if (dir == NULL) {
- st->tids = NULL;
- st->count = 0;
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
if (errno == ENOENT || errno == ESRCH) {
PyErr_Format(PyExc_ProcessLookupError,
"Process %d has terminated", unwinder->handle.pid);
@@ -671,8 +670,21 @@ read_thread_ids(RemoteUnwinderObject *unwinder,
_Py_RemoteDebug_ThreadsState *st
st->count = 0;
- struct dirent *entry;
- while ((entry = readdir(dir)) != NULL) {
+ for (;;) {
+ errno = 0;
+ struct dirent *entry = readdir(dir);
+ if (entry == NULL) {
+ if (errno != 0) {
+ int err = errno;
+ closedir(dir);
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
+ _set_debug_oserror_from_errno_with_filename(err, task_path,
+ "Failed to read process task directory '%s': %s",
+ task_path, strerror(err));
+ return -1;
+ }
+ break;
+ }
if (entry->d_name[0] < '1' || entry->d_name[0] > '9') {
continue;
}
@@ -686,8 +698,7 @@ read_thread_ids(RemoteUnwinderObject *unwinder,
_Py_RemoteDebug_ThreadsState *st
pid_t *new_tids = PyMem_RawRealloc(unwinder->thread_tids, new_cap
* sizeof(pid_t));
if (new_tids == NULL) {
closedir(dir);
- st->tids = NULL;
- st->count = 0;
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
PyErr_NoMemory();
return -1;
}
@@ -697,8 +708,15 @@ read_thread_ids(RemoteUnwinderObject *unwinder,
_Py_RemoteDebug_ThreadsState *st
unwinder->thread_tids[st->count++] = (pid_t)tid;
}
+ if (closedir(dir) != 0) {
+ int err = errno;
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
+ _set_debug_oserror_from_errno_with_filename(err, task_path,
+ "Failed to close process task directory '%s': %s",
+ task_path, strerror(err));
+ return -1;
+ }
st->tids = unwinder->thread_tids;
- closedir(dir);
return 0;
}
@@ -711,28 +729,30 @@ detach_threads(_Py_RemoteDebug_ThreadsState *st, size_t
up_to)
}
static int
-seize_thread(pid_t tid)
+seize_thread(pid_t tid, int *err)
{
if (ptrace(PTRACE_SEIZE, tid, NULL, 0) == 0) {
return 0;
}
- if (errno == ESRCH) {
+ *err = errno;
+ if (*err == ESRCH) {
return 1; // Thread gone, skip
}
- if (errno == EPERM) {
+ if (*err == EPERM) {
// Thread may have exited, be in a special state, or already be traced.
// Skip rather than fail - this avoids endless retry loops when
// threads transiently become inaccessible.
return 1;
}
- if (errno == EINVAL || errno == EIO) {
+ if (*err == EINVAL || *err == EIO) {
// Fallback for older kernels
if (ptrace(PTRACE_ATTACH, tid, NULL, NULL) == 0) {
int status;
waitpid(tid, &status, __WALL);
return 0;
}
- if (errno == ESRCH || errno == EPERM) {
+ *err = errno;
+ if (*err == ESRCH || *err == EPERM) {
return 1; // Thread gone or inaccessible
}
}
@@ -746,39 +766,50 @@ _Py_RemoteDebug_StopAllThreads(RemoteUnwinderObject
*unwinder, _Py_RemoteDebug_T
return -1;
}
- for (size_t i = 0; i < st->count; i++) {
+ size_t n_tids = st->count;
+ size_t seized = 0;
+ for (size_t i = 0; i < n_tids; i++) {
pid_t tid = st->tids[i];
- int ret = seize_thread(tid);
+ int err = 0;
+ int ret = seize_thread(tid, &err);
if (ret == 1) {
continue; // Thread gone, skip
}
if (ret < 0) {
- detach_threads(st, i);
- PyErr_Format(PyExc_RuntimeError, "Failed to seize thread %d: %s",
tid, strerror(errno));
- st->tids = NULL;
- st->count = 0;
+ detach_threads(st, seized);
+ _set_debug_oserror_from_errno(err,
+ "Failed to seize thread %d: %s", tid, strerror(err));
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
return -1;
}
+ st->tids[seized++] = tid;
- if (ptrace(PTRACE_INTERRUPT, tid, NULL, NULL) == -1 && errno != ESRCH)
{
- detach_threads(st, i + 1);
- PyErr_Format(PyExc_RuntimeError, "Failed to interrupt thread %d:
%s", tid, strerror(errno));
- st->tids = NULL;
- st->count = 0;
- return -1;
+ if (ptrace(PTRACE_INTERRUPT, tid, NULL, NULL) == -1) {
+ err = errno;
+ if (err != ESRCH) {
+ detach_threads(st, seized);
+ _set_debug_oserror_from_errno(err,
+ "Failed to interrupt thread %d: %s", tid, strerror(err));
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
+ return -1;
+ }
}
int status;
- if (waitpid(tid, &status, __WALL) == -1 && errno != ECHILD && errno !=
ESRCH) {
- detach_threads(st, i + 1);
- PyErr_Format(PyExc_RuntimeError, "waitpid failed for thread %d:
%s", tid, strerror(errno));
- st->tids = NULL;
- st->count = 0;
- return -1;
+ if (waitpid(tid, &status, __WALL) == -1) {
+ err = errno;
+ if (err != ECHILD && err != ESRCH) {
+ detach_threads(st, seized);
+ _set_debug_oserror_from_errno(err,
+ "waitpid failed for thread %d: %s", tid, strerror(err));
+ _Py_RemoteDebug_InitThreadsState(unwinder, st);
+ return -1;
+ }
}
}
+ st->count = seized;
return 0;
}
diff --git a/Python/remote_debug.h b/Python/remote_debug.h
index 53bbd571ad3cef..6fecc23502b46e 100644
--- a/Python/remote_debug.h
+++ b/Python/remote_debug.h
@@ -100,9 +100,16 @@ extern "C" {
# define HAVE_PROCESS_VM_READV 0
#endif
+static inline int
+_Py_RemoteDebug_HasPermissionError(void)
+{
+ return PyErr_Occurred()
+ && PyErr_ExceptionMatches(PyExc_PermissionError);
+}
+
#define _set_debug_exception_cause(exception, format, ...) \
do { \
- if (!PyErr_ExceptionMatches(PyExc_PermissionError)) { \
+ if (!_Py_RemoteDebug_HasPermissionError()) { \
PyThreadState *tstate = _PyThreadState_GET(); \
if (!_PyErr_Occurred(tstate)) { \
_PyErr_Format(tstate, exception, format, ##__VA_ARGS__); \
@@ -112,6 +119,20 @@ extern "C" {
} \
} while (0)
+#define _set_debug_oserror_from_errno(err, format, ...) \
+ do { \
+ errno = (err); \
+ PyErr_SetFromErrno(PyExc_OSError); \
+ _set_debug_exception_cause(PyExc_OSError, format, ##__VA_ARGS__); \
+ } while (0)
+
+#define _set_debug_oserror_from_errno_with_filename(err, filename, format,
...) \
+ do { \
+ errno = (err); \
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename); \
+ _set_debug_exception_cause(PyExc_OSError, format, ##__VA_ARGS__); \
+ } while (0)
+
static inline size_t
get_page_size(void) {
size_t page_size = 0;
@@ -170,7 +191,7 @@ _Py_RemoteDebug_ValidatePyRuntimeCookie(proc_handle_t
*handle, uintptr_t address
}
char buf[sizeof(_Py_Debug_Cookie) - 1];
if (_Py_RemoteDebug_ReadRemoteMemory(handle, address, sizeof(buf), buf) !=
0) {
- if (!PyErr_ExceptionMatches(PyExc_PermissionError)) {
+ if (!_Py_RemoteDebug_HasPermissionError()) {
PyErr_Clear();
}
return 0;
@@ -207,6 +228,21 @@ static mach_port_t pid_to_task(pid_t pid);
// Initialize the process handle
UNUSED static int
_Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
+ handle->pid = 0;
+#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
+ handle->task = 0;
+#elif defined(MS_WINDOWS)
+ handle->hProcess = NULL;
+#elif defined(__linux__)
+ handle->memfd = -1;
+#endif
+ handle->page_size = get_page_size();
+ handle->page_cache_count = 0;
+ for (int i = 0; i < MAX_PAGES; i++) {
+ handle->pages[i].data = NULL;
+ handle->pages[i].valid = 0;
+ }
+
handle->pid = pid;
#if defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
handle->task = pid_to_task(handle->pid);
@@ -219,19 +255,12 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle,
pid_t pid) {
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION |
PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME,
FALSE, pid);
if (handle->hProcess == NULL) {
- PyErr_SetFromWindowsErr(0);
+ DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
_set_debug_exception_cause(PyExc_RuntimeError, "Failed to initialize
Windows process handle");
return -1;
}
-#elif defined(__linux__)
- handle->memfd = -1;
#endif
- handle->page_size = get_page_size();
- handle->page_cache_count = 0;
- for (int i = 0; i < MAX_PAGES; i++) {
- handle->pages[i].data = NULL;
- handle->pages[i].valid = 0;
- }
return 0;
}
@@ -396,17 +425,19 @@ return_section_address_fat(
size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64);
if (sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0) != 0) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno(err,
"Failed to determine CPU type via sysctlbyname "
"for fat binary analysis at 0x%lx: %s",
- base, strerror(errno));
+ base, strerror(err));
return 0;
}
if (sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0)
!= 0) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno(err,
"Failed to determine CPU ABI capability via sysctlbyname "
"for fat binary analysis at 0x%lx: %s",
- base, strerror(errno));
+ base, strerror(err));
return 0;
}
@@ -459,26 +490,29 @@ search_section_in_file(const char* secname, char* path,
uintptr_t base, mach_vm_
{
int fd = open(path, O_RDONLY);
if (fd == -1) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, path,
"Cannot open binary file '%s' for section '%s' search: %s",
- path, secname, strerror(errno));
+ path, secname, strerror(err));
return 0;
}
struct stat fs;
if (fstat(fd, &fs) == -1) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, path,
"Cannot get file size for binary '%s' during section '%s' search:
%s",
- path, secname, strerror(errno));
+ path, secname, strerror(err));
close(fd);
return 0;
}
void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, path,
"Cannot memory map binary file '%s' (size: %lld bytes) for section
'%s' search: %s",
- path, (long long)fs.st_size, secname, strerror(errno));
+ path, (long long)fs.st_size, secname, strerror(err));
close(fd);
return 0;
}
@@ -507,15 +541,21 @@ search_section_in_file(const char* secname, char* path,
uintptr_t base, mach_vm_
}
if (munmap(map, fs.st_size) != 0) {
- PyErr_Format(PyExc_OSError,
- "Failed to unmap binary file '%s' (size: %lld bytes): %s",
- path, (long long)fs.st_size, strerror(errno));
+ if (!PyErr_Occurred()) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, path,
+ "Failed to unmap binary file '%s' (size: %lld bytes): %s",
+ path, (long long)fs.st_size, strerror(err));
+ }
result = 0;
}
if (close(fd) != 0) {
- PyErr_Format(PyExc_OSError,
- "Failed to close binary file '%s': %s",
- path, strerror(errno));
+ if (!PyErr_Occurred()) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, path,
+ "Failed to close binary file '%s': %s",
+ path, strerror(err));
+ }
result = 0;
}
return result;
@@ -560,14 +600,15 @@ search_map_for_section(proc_handle_t *handle, const char*
secname, const char* s
char map_filename[MAXPATHLEN + 1];
- while (mach_vm_region(
- proc_ref,
- &address,
- &size,
- VM_REGION_BASIC_INFO_64,
- (vm_region_info_t)®ion_info,
- &count,
- &object_name) == KERN_SUCCESS)
+ kern_return_t kr;
+ while ((kr = mach_vm_region(
+ proc_ref,
+ &address,
+ &size,
+ VM_REGION_BASIC_INFO_64,
+ (vm_region_info_t)®ion_info,
+ &count,
+ &object_name)) == KERN_SUCCESS)
{
if ((region_info.protection & VM_PROT_READ) == 0
@@ -591,18 +632,32 @@ search_map_for_section(proc_handle_t *handle, const char*
secname, const char* s
}
if (strncmp(filename, substr, strlen(substr)) == 0) {
+ PyErr_Clear();
uintptr_t result = search_section_in_file(
secname, map_filename, address, size, proc_ref);
- if (result != 0
- && (validator == NULL || validator(handle, result)))
- {
- return result;
+ if (result != 0) {
+ if (validator == NULL || validator(handle, result)) {
+ return result;
+ }
+ if (_Py_RemoteDebug_HasPermissionError()) {
+ return 0;
+ }
+ }
+ else if (_Py_RemoteDebug_HasPermissionError()) {
+ return 0;
}
}
address += size;
}
+ if (kr != KERN_INVALID_ADDRESS && !PyErr_Occurred()) {
+ PyErr_Format(PyExc_RuntimeError,
+ "mach_vm_region failed while searching PID %d for section '%s' "
+ "(kern_return_t: %d)",
+ handle->pid, secname, kr);
+ }
+
return 0;
}
@@ -625,25 +680,29 @@ search_elf_file_for_section(
int fd = open(elf_file, O_RDONLY);
if (fd < 0) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, elf_file,
"Cannot open ELF file '%s' for section '%s' search: %s",
- elf_file, secname, strerror(errno));
+ elf_file, secname, strerror(err));
goto exit;
}
struct stat file_stats;
if (fstat(fd, &file_stats) != 0) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, elf_file,
"Cannot get file size for ELF file '%s' during section '%s'
search: %s",
- elf_file, secname, strerror(errno));
+ elf_file, secname, strerror(err));
goto exit;
}
file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd,
0);
if (file_memory == MAP_FAILED) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, elf_file,
"Cannot memory map ELF file '%s' (size: %lld bytes) for section
'%s' search: %s",
- elf_file, (long long)file_stats.st_size, secname, strerror(errno));
+ elf_file, (long long)file_stats.st_size, secname, strerror(err));
+ file_memory = NULL;
goto exit;
}
@@ -700,12 +759,23 @@ search_elf_file_for_section(
exit:
if (file_memory != NULL) {
- munmap(file_memory, file_stats.st_size);
+ if (munmap(file_memory, file_stats.st_size) != 0) {
+ if (!PyErr_Occurred()) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, elf_file,
+ "Failed to unmap ELF file '%s' (size: %lld bytes): %s",
+ elf_file, (long long)file_stats.st_size, strerror(err));
+ }
+ result = 0;
+ }
}
if (fd >= 0 && close(fd) != 0) {
- PyErr_Format(PyExc_OSError,
- "Failed to close ELF file '%s': %s",
- elf_file, strerror(errno));
+ if (!PyErr_Occurred()) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, elf_file,
+ "Failed to close ELF file '%s': %s",
+ elf_file, strerror(err));
+ }
result = 0;
}
return result;
@@ -720,9 +790,10 @@ search_linux_map_for_section(proc_handle_t *handle, const
char* secname, const c
FILE* maps_file = fopen(maps_file_path, "r");
if (maps_file == NULL) {
- PyErr_Format(PyExc_OSError,
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, maps_file_path,
"Cannot open process memory map file '%s' for PID %d section
search: %s",
- maps_file_path, handle->pid, strerror(errno));
+ maps_file_path, handle->pid, strerror(err));
return 0;
}
@@ -787,26 +858,39 @@ search_linux_map_for_section(proc_handle_t *handle, const
char* secname, const c
}
if (strstr(filename, substr)) {
- if (PyErr_ExceptionMatches(PyExc_PermissionError)) {
- retval = 0;
- break;
- }
PyErr_Clear();
retval = search_elf_file_for_section(handle, secname, start, path);
- if (retval
- && (validator == NULL || validator(handle, retval)))
- {
+ if (retval) {
+ if (validator == NULL || validator(handle, retval)) {
+ break;
+ }
+ if (_Py_RemoteDebug_HasPermissionError()) {
+ retval = 0;
+ break;
+ }
+ }
+ else if (_Py_RemoteDebug_HasPermissionError()) {
break;
}
retval = 0;
}
}
+ if (retval == 0 && !PyErr_Occurred() && ferror(maps_file)) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, maps_file_path,
+ "Failed to read process map file '%s' for PID %d section search:
%s",
+ maps_file_path, handle->pid, strerror(err));
+ }
+
PyMem_Free(line);
if (fclose(maps_file) != 0) {
- PyErr_Format(PyExc_OSError,
- "Failed to close process map file '%s': %s",
- maps_file_path, strerror(errno));
+ if (!PyErr_Occurred()) {
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, maps_file_path,
+ "Failed to close process map file '%s': %s",
+ maps_file_path, strerror(err));
+ }
retval = 0;
}
@@ -829,9 +913,9 @@ static int is_process_alive(HANDLE hProcess) {
static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const
char* secname) {
HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
- PyErr_SetFromWindowsErr(0);
DWORD error = GetLastError();
- PyErr_Format(PyExc_OSError,
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
"Cannot open PE file for section '%s' analysis (error %lu)",
secname, error);
return NULL;
@@ -839,9 +923,9 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE*
remote_base, const char*
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);
if (!hMap) {
- PyErr_SetFromWindowsErr(0);
DWORD error = GetLastError();
- PyErr_Format(PyExc_OSError,
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
"Cannot create file mapping for PE file section '%s' analysis
(error %lu)",
secname, error);
CloseHandle(hFile);
@@ -850,9 +934,9 @@ static void* analyze_pe(const wchar_t* mod_path, BYTE*
remote_base, const char*
BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (!mapView) {
- PyErr_SetFromWindowsErr(0);
DWORD error = GetLastError();
- PyErr_Format(PyExc_OSError,
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
"Cannot map view of PE file for section '%s' analysis (error %lu)",
secname, error);
CloseHandle(hMap);
@@ -910,9 +994,9 @@ search_windows_map_for_section(proc_handle_t* handle, const
char* secname, const
} while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() ==
ERROR_BAD_LENGTH);
if (hProcSnap == INVALID_HANDLE_VALUE) {
- PyErr_SetFromWindowsErr(0);
DWORD error = GetLastError();
- PyErr_Format(PyExc_PermissionError,
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
"Unable to create module snapshot for PID %d section '%s' "
"search (error %lu). Check permissions or PID validity",
handle->pid, secname, error);
@@ -923,17 +1007,46 @@ search_windows_map_for_section(proc_handle_t* handle,
const char* secname, const
moduleEntry.dwSize = sizeof(moduleEntry);
void* runtime_addr = NULL;
- for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule;
hasModule = Module32NextW(hProcSnap, &moduleEntry)) {
+ if (!Module32FirstW(hProcSnap, &moduleEntry)) {
+ DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
+ "Unable to enumerate modules for PID %d section '%s' "
+ "search (error %lu)",
+ handle->pid, secname, error);
+ CloseHandle(hProcSnap);
+ return 0;
+ }
+
+ do {
// Look for either python executable or DLL
if (wcsstr(moduleEntry.szModule, substr)) {
+ PyErr_Clear();
void *candidate = analyze_pe(moduleEntry.szExePath,
moduleEntry.modBaseAddr, secname);
- if (candidate != NULL
- && (validator == NULL || validator(handle,
(uintptr_t)candidate)))
- {
- runtime_addr = candidate;
+ if (candidate != NULL) {
+ if (validator == NULL || validator(handle,
(uintptr_t)candidate)) {
+ runtime_addr = candidate;
+ break;
+ }
+ if (_Py_RemoteDebug_HasPermissionError()) {
+ break;
+ }
+ }
+ else if (_Py_RemoteDebug_HasPermissionError()) {
break;
}
}
+ } while (Module32NextW(hProcSnap, &moduleEntry));
+
+ if (runtime_addr == NULL && !PyErr_Occurred()) {
+ DWORD error = GetLastError();
+ if (error != ERROR_NO_MORE_FILES) {
+ PyErr_SetFromWindowsErr(error);
+ _set_debug_exception_cause(PyExc_OSError,
+ "Module enumeration failed for PID %d section '%s' "
+ "search (error %lu)",
+ handle->pid, secname, error);
+ }
}
CloseHandle(hProcSnap);
@@ -954,19 +1067,21 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t*
handle)
address = search_windows_map_for_section(handle, "PyRuntime", L"python",
_Py_RemoteDebug_ValidatePyRuntimeCookie);
if (address == 0) {
- // Error out: 'python' substring covers both executable and DLL
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_Format(PyExc_RuntimeError,
- "Failed to find the PyRuntime section in process %d on Windows
platform",
- handle->pid);
- _PyErr_ChainExceptions1(exc);
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ // Error out: 'python' substring covers both executable and DLL
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to find the PyRuntime section in process %d on Windows
platform",
+ handle->pid);
+ _PyErr_ChainExceptions1(exc);
+ }
}
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
// On Linux, search for 'python' in executable or DLL
address = search_linux_map_for_section(handle, "PyRuntime", "python",
_Py_RemoteDebug_ValidatePyRuntimeCookie);
if (address == 0) {
- if (!PyErr_ExceptionMatches(PyExc_PermissionError)) {
+ if (!_Py_RemoteDebug_HasPermissionError()) {
// Error out: 'python' substring covers both executable and DLL
PyObject *exc = PyErr_GetRaisedException();
PyErr_Format(PyExc_RuntimeError,
@@ -982,17 +1097,19 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t*
handle)
PyErr_Clear();
address = search_map_for_section(handle, "PyRuntime", *candidate,
_Py_RemoteDebug_ValidatePyRuntimeCookie);
- if (address != 0) {
+ if (address != 0 || _Py_RemoteDebug_HasPermissionError()) {
break;
}
}
if (address == 0) {
- PyObject *exc = PyErr_GetRaisedException();
- PyErr_Format(PyExc_RuntimeError,
- "Failed to find the PyRuntime section in process %d "
- "on macOS platform (tried both libpython and python)",
- handle->pid);
- _PyErr_ChainExceptions1(exc);
+ if (!_Py_RemoteDebug_HasPermissionError()) {
+ PyObject *exc = PyErr_GetRaisedException();
+ PyErr_Format(PyExc_RuntimeError,
+ "Failed to find the PyRuntime section in process %d "
+ "on macOS platform (tried both libpython and python)",
+ handle->pid);
+ _PyErr_ChainExceptions1(exc);
+ }
}
#else
_set_debug_exception_cause(PyExc_RuntimeError,
@@ -1013,9 +1130,9 @@ open_proc_mem_fd(proc_handle_t *handle)
handle->memfd = open(mem_file_path, O_RDWR);
if (handle->memfd == -1) {
- PyErr_SetFromErrno(PyExc_OSError);
- _set_debug_exception_cause(PyExc_OSError,
- "failed to open file %s: %s", mem_file_path, strerror(errno));
+ int err = errno;
+ _set_debug_oserror_from_errno_with_filename(err, mem_file_path,
+ "failed to open file %s: %s", mem_file_path, strerror(err));
return -1;
}
return 0;
@@ -1026,6 +1143,9 @@ open_proc_mem_fd(proc_handle_t *handle)
static int
read_remote_memory_fallback(proc_handle_t *handle, uintptr_t remote_address,
size_t len, void* dst)
{
+ if (len == 0) {
+ return 0;
+ }
if (handle->memfd == -1) {
if (open_proc_mem_fd(handle) < 0) {
return -1;
@@ -1043,14 +1163,23 @@ read_remote_memory_fallback(proc_handle_t *handle,
uintptr_t remote_address, siz
read_bytes = preadv(handle->memfd, local, 1, offset);
if (read_bytes < 0) {
+ int err = errno;
+ errno = err;
PyErr_SetFromErrno(PyExc_OSError);
_set_debug_exception_cause(PyExc_OSError,
"preadv failed for PID %d at address 0x%lx "
"(size %zu, partial read %zd bytes): %s",
- handle->pid, remote_address + result, len - result, result,
strerror(errno));
+ handle->pid, remote_address + result, len - result, result,
strerror(err));
return -1;
}
+ if (read_bytes == 0) {
+ PyErr_Format(PyExc_OSError,
+ "preadv returned 0 bytes for PID %d at address 0x%lx "
+ "(size %zu, partial read %zd bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += read_bytes;
} while ((size_t)read_bytes != local[0].iov_len);
return 0;
@@ -1062,11 +1191,15 @@ read_remote_memory_fallback(proc_handle_t *handle,
uintptr_t remote_address, siz
static int
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t
remote_address, size_t len, void* dst)
{
+ if (len == 0) {
+ return 0;
+ }
#ifdef MS_WINDOWS
SIZE_T read_bytes = 0;
SIZE_T result = 0;
do {
if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address +
result), (char*)dst + result, len - result, &read_bytes)) {
+ DWORD error = GetLastError();
// Check if the process is still alive: we need to be able to tell
our caller
// that the process is dead and not just that the read failed.
if (!is_process_alive(handle->hProcess)) {
@@ -1074,14 +1207,20 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle,
uintptr_t remote_address
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
- PyErr_SetFromWindowsErr(0);
- DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
_set_debug_exception_cause(PyExc_OSError,
"ReadProcessMemory failed for PID %d at address 0x%lx "
"(size %zu, partial read %zu bytes): Windows error %lu",
handle->pid, remote_address + result, len - result, result,
error);
return -1;
}
+ if (read_bytes == 0) {
+ PyErr_Format(PyExc_OSError,
+ "ReadProcessMemory returned 0 bytes for PID %d at address
0x%lx "
+ "(size %zu, partial read %zu bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += read_bytes;
} while (result < len);
return 0;
@@ -1102,31 +1241,40 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle,
uintptr_t remote_address
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
if (read_bytes < 0) {
- if (errno == ENOSYS) {
+ int err = errno;
+ if (err == ENOSYS) {
return read_remote_memory_fallback(handle, remote_address,
len, dst);
}
+ errno = err;
PyErr_SetFromErrno(PyExc_OSError);
- if (errno == ESRCH) {
+ if (err == ESRCH) {
return -1;
}
_set_debug_exception_cause(PyExc_OSError,
"process_vm_readv failed for PID %d at address 0x%lx "
"(size %zu, partial read %zd bytes): %s",
- handle->pid, remote_address + result, len - result, result,
strerror(errno));
+ handle->pid, remote_address + result, len - result, result,
strerror(err));
return -1;
}
+ if (read_bytes == 0) {
+ PyErr_Format(PyExc_OSError,
+ "process_vm_readv returned 0 bytes for PID %d at address 0x%lx
"
+ "(size %zu, partial read %zd bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += read_bytes;
} while ((size_t)read_bytes != local[0].iov_len);
return 0;
#elif defined(__APPLE__) && defined(TARGET_OS_OSX) && TARGET_OS_OSX
- Py_ssize_t result = -1;
+ mach_vm_size_t bytes_read = 0;
kern_return_t kr = mach_vm_read_overwrite(
handle->task,
(mach_vm_address_t)remote_address,
len,
(mach_vm_address_t)dst,
- (mach_vm_size_t*)&result);
+ &bytes_read);
if (kr != KERN_SUCCESS) {
switch (err_get_code(kr)) {
@@ -1170,6 +1318,13 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle,
uintptr_t remote_address
}
return -1;
}
+ if (bytes_read != (mach_vm_size_t)len) {
+ PyErr_Format(PyExc_OSError,
+ "mach_vm_read_overwrite read %llu of %zu bytes for PID %d at "
+ "address 0x%lx",
+ (unsigned long long)bytes_read, len, handle->pid, remote_address);
+ return -1;
+ }
return 0;
#else
Py_UNREACHABLE();
@@ -1181,6 +1336,9 @@ _Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle,
uintptr_t remote_address
static int
_Py_RemoteDebug_WriteRemoteMemoryFallback(proc_handle_t *handle, uintptr_t
remote_address, size_t len, const void* src)
{
+ if (len == 0) {
+ return 0;
+ }
if (handle->memfd == -1) {
if (open_proc_mem_fd(handle) < 0) {
return -1;
@@ -1198,10 +1356,19 @@ _Py_RemoteDebug_WriteRemoteMemoryFallback(proc_handle_t
*handle, uintptr_t remot
written = pwritev(handle->memfd, local, 1, offset);
if (written < 0) {
+ int err = errno;
+ errno = err;
PyErr_SetFromErrno(PyExc_OSError);
return -1;
}
+ if (written == 0) {
+ PyErr_Format(PyExc_OSError,
+ "pwritev wrote 0 bytes for PID %d at address 0x%lx "
+ "(size %zu, partial write %zd bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += written;
} while ((size_t)written != local[0].iov_len);
return 0;
@@ -1212,19 +1379,29 @@ _Py_RemoteDebug_WriteRemoteMemoryFallback(proc_handle_t
*handle, uintptr_t remot
UNUSED static int
_Py_RemoteDebug_WriteRemoteMemory(proc_handle_t *handle, uintptr_t
remote_address, size_t len, const void* src)
{
+ if (len == 0) {
+ return 0;
+ }
#ifdef MS_WINDOWS
SIZE_T written = 0;
SIZE_T result = 0;
do {
if (!WriteProcessMemory(handle->hProcess, (LPVOID)(remote_address +
result), (const char*)src + result, len - result, &written)) {
- PyErr_SetFromWindowsErr(0);
DWORD error = GetLastError();
+ PyErr_SetFromWindowsErr(error);
_set_debug_exception_cause(PyExc_OSError,
"WriteProcessMemory failed for PID %d at address 0x%lx "
"(size %zu, partial write %zu bytes): Windows error %lu",
handle->pid, remote_address + result, len - result, result,
error);
return -1;
}
+ if (written == 0) {
+ PyErr_Format(PyExc_OSError,
+ "WriteProcessMemory wrote 0 bytes for PID %d at address 0x%lx "
+ "(size %zu, partial write %zu bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += written;
} while (result < len);
return 0;
@@ -1245,17 +1422,26 @@ _Py_RemoteDebug_WriteRemoteMemory(proc_handle_t
*handle, uintptr_t remote_addres
written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
if (written < 0) {
- if (errno == ENOSYS) {
+ int err = errno;
+ if (err == ENOSYS) {
return _Py_RemoteDebug_WriteRemoteMemoryFallback(handle,
remote_address, len, src);
}
+ errno = err;
PyErr_SetFromErrno(PyExc_OSError);
_set_debug_exception_cause(PyExc_OSError,
"process_vm_writev failed for PID %d at address 0x%lx "
"(size %zu, partial write %zd bytes): %s",
- handle->pid, remote_address + result, len - result, result,
strerror(errno));
+ handle->pid, remote_address + result, len - result, result,
strerror(err));
return -1;
}
+ if (written == 0) {
+ PyErr_Format(PyExc_OSError,
+ "process_vm_writev wrote 0 bytes for PID %d at address 0x%lx "
+ "(size %zu, partial write %zd bytes)",
+ handle->pid, remote_address + result, len - result, result);
+ return -1;
+ }
result += written;
} while ((size_t)written != local[0].iov_len);
return 0;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]