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)&region_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)&region_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]

Reply via email to