https://github.com/python/cpython/commit/04735deeb2dbdc6404eb38544541abf387d71069
commit: 04735deeb2dbdc6404eb38544541abf387d71069
branch: 3.14
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-07-28T23:46:43+01:00
summary:

[3.14] gh-149619: Harden _remote_debugging error paths (GH-150349) (#154844)

(cherry picked from commit a5be25d3bdc1b3cbc9638a3249c0e3db5a97ebc6)

files:
M Modules/_remote_debugging_module.c
M Python/remote_debug.h
M Python/remote_debugging.c

diff --git a/Modules/_remote_debugging_module.c 
b/Modules/_remote_debugging_module.c
index 0b6b9d7f301a98..4fe94d43924231 100644
--- a/Modules/_remote_debugging_module.c
+++ b/Modules/_remote_debugging_module.c
@@ -28,6 +28,7 @@
 // issues.
 #include <errno.h>
 #include <fcntl.h>
+#include <limits.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -807,7 +808,7 @@ static int append_awaited_by(RemoteUnwinderObject 
*unwinder, unsigned long tid,
 #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)
@@ -1246,26 +1247,21 @@ read_py_long(
         return -1;
     }
 
-    // If the long object has inline digits, use them directly
-    digit *digits;
-    if (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 + 
unwinder->debug_offsets.long_object.ob_digit, 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;
-        }
+    // Calculate how many digits fit inline in our local buffer
+    Py_ssize_t ob_digit_offset = unwinder->debug_offsets.long_object.ob_digit;
+    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);
 
+    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) {
+        memcpy(digits, long_obj + ob_digit_offset, size * sizeof(digit));
+    } else {
         bytes_read = _Py_RemoteDebug_PagedReadRemoteMemory(
             &unwinder->handle,
             address + (uintptr_t)unwinder->debug_offsets.long_object.ob_digit,
@@ -1278,19 +1274,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:
@@ -1312,32 +1323,35 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* 
handle)
     // On Windows, search for asyncio debug in executable or DLL
     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__)
     // On Linux, search for asyncio debug in executable or DLL
     address = search_linux_map_for_section(handle, "AsyncioDebug", 
"_asyncio.cpython", 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", 
"_asyncio.cpython", NULL);
-    if (address == 0) {
+    if (address == 0 && !_Py_RemoteDebug_HasPermissionError()) {
         PyErr_Clear();
         address = search_map_for_section(handle, "AsyncioDebug", 
"_asyncio.cpython", 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();
@@ -1418,7 +1432,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;
         }
@@ -3124,6 +3138,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/Python/remote_debug.h b/Python/remote_debug.h
index bacba2ed5f000f..ed0c4e2c85c37d 100644
--- a/Python/remote_debug.h
+++ b/Python/remote_debug.h
@@ -92,7 +92,7 @@ _Py_RemoteDebug_HasPermissionError(void)
 
 #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__); \
@@ -102,6 +102,20 @@ _Py_RemoteDebug_HasPermissionError(void)
         } \
     } 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;
@@ -192,6 +206,20 @@ static mach_port_t pid_to_task(pid_t pid);
 // Initialize the process handle
 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();
+    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);
@@ -204,18 +232,12 @@ _Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, 
pid_t pid) {
         PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | 
PROCESS_QUERY_INFORMATION,
         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();
-    for (int i = 0; i < MAX_PAGES; i++) {
-        handle->pages[i].data = NULL;
-        handle->pages[i].valid = 0;
-    }
     return 0;
 }
 
@@ -380,17 +402,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;
     }
 
@@ -443,26 +467,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;
     }
@@ -491,15 +518,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;
@@ -544,14 +577,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
@@ -575,18 +609,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;
 }
 
@@ -609,25 +657,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;
     }
 
@@ -684,12 +736,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;
@@ -804,9 +867,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;
     }
 
@@ -908,11 +972,21 @@ search_linux_map_for_section(proc_handle_t *handle, const 
char* secname, const c
         }
     }
 
+    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;
     }
 
@@ -924,12 +998,22 @@ search_linux_map_for_section(proc_handle_t *handle, const 
char* secname, const c
 
 #ifdef MS_WINDOWS
 
+static int
+is_process_alive(HANDLE hProcess)
+{
+    DWORD exitCode;
+    if (GetExitCodeProcess(hProcess, &exitCode)) {
+        return exitCode == STILL_ACTIVE;
+    }
+    return 0;
+}
+
 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;
@@ -937,9 +1021,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);
@@ -948,9 +1032,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);
@@ -1008,9 +1092,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);
@@ -1021,17 +1105,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);
@@ -1052,12 +1165,14 @@ _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__)
     // On Linux, search for 'python' in executable or DLL
@@ -1080,17 +1195,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
     Py_UNREACHABLE();
@@ -1109,9 +1226,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;
@@ -1122,6 +1239,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;
@@ -1139,14 +1259,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;
@@ -1158,19 +1287,36 @@ 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)) {
-            PyErr_SetFromWindowsErr(0);
             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)) {
+                _set_errno(ESRCH);
+                PyErr_SetFromErrno(PyExc_OSError);
+                return -1;
+            }
+            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;
@@ -1191,28 +1337,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 (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 (kr) {
@@ -1236,6 +1394,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();
diff --git a/Python/remote_debugging.c b/Python/remote_debugging.c
index 71ffb17ed68b1d..82e58f03966de3 100644
--- a/Python/remote_debugging.c
+++ b/Python/remote_debugging.c
@@ -30,6 +30,9 @@ read_memory(proc_handle_t *handle, uintptr_t remote_address, 
size_t len, void* d
 static int
 write_memory_fallback(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;
@@ -47,10 +50,19 @@ write_memory_fallback(proc_handle_t *handle, uintptr_t 
remote_address, size_t le
 
         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;
@@ -60,12 +72,27 @@ write_memory_fallback(proc_handle_t *handle, uintptr_t 
remote_address, size_t le
 static int
 write_memory(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;
@@ -88,13 +115,26 @@ write_memory(proc_handle_t *handle, uintptr_t 
remote_address, size_t len, const
 
         written = process_vm_writev(handle->pid, local, 1, remote, 1, 0);
         if (written < 0) {
-            if (errno == ENOSYS) {
+            int err = errno;
+            if (err == ENOSYS) {
                 return write_memory_fallback(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(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;
@@ -398,4 +438,3 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char 
*debugger_script_path)
     return rc;
 #endif
 }
-

_______________________________________________
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