From 49474e1e3ac0285462bdc5d255e010ee68c8d79c Mon Sep 17 00:00:00 2001 From: vxiiduu <73044267+vxiiduu@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:16:18 +1000 Subject: [PATCH 1/3] Re-implement win32_getppid efficiently. Use the NtQueryInformationProcess system call to efficiently retrieve the parent process ID in a single step, rather than using the process snapshots API which retrieves large amounts of unnecessary information and is more prone to failure (since it makes heap allocations). --- Modules/posixmodule.c | 73 ++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index fd70b38bddec79..d16d7e9b867a86 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -9115,33 +9115,70 @@ os_setpgrp_impl(PyObject *module) #ifdef HAVE_GETPPID #ifdef MS_WINDOWS -#include +#include + +// The structure definition in winternl.h may be incomplete. +// This structure is the full version from the MSDN documentation. +typedef struct _PROCESS_BASIC_INFORMATION_FULL { + NTSTATUS ExitStatus; + PVOID PebBaseAddress; + ULONG_PTR AffinityMask; + LONG BasePriority; + ULONG_PTR UniqueProcessId; + ULONG_PTR InheritedFromUniqueProcessId; +} PROCESS_BASIC_INFORMATION_FULL; static PyObject* win32_getppid(void) { - DWORD error; - PyObject* result = NULL; - HANDLE process = GetCurrentProcess(); + NTSTATUS Status; + PROCESS_BASIC_INFORMATION_FULL BasicInformation; + HMODULE Ntdll; + NTSTATUS (NTAPI *pNtQueryInformationProcess) (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); - HPSS snapshot = NULL; - error = PssCaptureSnapshot(process, PSS_CAPTURE_NONE, 0, &snapshot); - if (error != ERROR_SUCCESS) { - return PyErr_SetFromWindowsErr(error); - } + // A GetModuleHandle call with NTDLL as the parameter is guaranteed to succeed on + // all versions of Windows. + Ntdll = GetModuleHandleW(L"ntdll.dll"); + pNtQueryInformationProcess = (NTSTATUS (NTAPI *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG)) GetProcAddress(Ntdll, "NtQueryInformationProcess"); - PSS_PROCESS_INFORMATION info; - error = PssQuerySnapshot(snapshot, PSS_QUERY_PROCESS_INFORMATION, &info, - sizeof(info)); - if (error == ERROR_SUCCESS) { - result = PyLong_FromUnsignedLong(info.ParentProcessId); + if (!pNtQueryInformationProcess) { + // This code is never going to be executed. It is just here as insurance. + return PyErr_SetFromWindowsErr(GetLastError()); } - else { - result = PyErr_SetFromWindowsErr(error); + + // The NtQueryInformationProcess system call, the ProcessBasicInformation info class + // and the PROCESS_BASIC_INFORMATION structure are all documented on MSDN. + Status = pNtQueryInformationProcess( + GetCurrentProcess(), + ProcessBasicInformation, + &BasicInformation, + sizeof(BasicInformation), + NULL); + + // If the NtQueryInformationProcess fails for any reason (it should never fail given + // the parameters passed to it), we will convert the returned NTSTATUS error code into + // a normal Win32 error code. + if (!NT_SUCCESS(Status)) { + ULONG ErrorCode; + ULONG (NTAPI *pRtlNtStatusToDosError) (NTSTATUS); + + pRtlNtStatusToDosError = (ULONG (NTAPI *) (NTSTATUS)) GetProcAddress(Ntdll, "RtlNtStatusToDosError"); + + if (!pRtlNtStatusToDosError) { + return PyErr_SetFromWindowsErr(GetLastError()); + } + + // Convert the NTSTATUS error code into a Win32 error code. + ErrorCode = pRtlNtStatusToDosError(Status); + return PyErr_SetFromWindowsErr(ErrorCode); } - PssFreeSnapshot(process, snapshot); - return result; + // Now that we have reached this point, the BasicInformation.InheritedFromUniqueProcessId + // structure member contains a ULONG_PTR which represents the process ID of our parent + // process. This process ID will be correctly returned even if the parent process has + // terminated. + + return PyLong_FromUnsignedLong((ULONG) BasicInformation.InheritedFromUniqueProcessId); } #endif /*MS_WINDOWS*/ From 4bb90fb87788e4ca250d0db31f07d56fb0839cd7 Mon Sep 17 00:00:00 2001 From: vxiiduu <73044267+vxiiduu@users.noreply.github.com> Date: Sat, 2 Mar 2024 00:42:29 +1000 Subject: [PATCH 2/3] Include fallback for win32_getppid, split new implementation into a separate function --- Modules/posixmodule.c | 101 +++++++++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 25 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d16d7e9b867a86..1cd6094046ef31 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -9128,26 +9128,39 @@ typedef struct _PROCESS_BASIC_INFORMATION_FULL { ULONG_PTR InheritedFromUniqueProcessId; } PROCESS_BASIC_INFORMATION_FULL; -static PyObject* -win32_getppid(void) +typedef NTSTATUS (NTAPI *PNT_QUERY_INFORMATION_PROCESS) ( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL); + +// This function returns the process ID of the parent process. +// Returns 0 on failure. +static ULONG +win32_getppid_fast(void) { NTSTATUS Status; - PROCESS_BASIC_INFORMATION_FULL BasicInformation; HMODULE Ntdll; - NTSTATUS (NTAPI *pNtQueryInformationProcess) (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); + PNT_QUERY_INFORMATION_PROCESS pNtQueryInformationProcess; + PROCESS_BASIC_INFORMATION_FULL BasicInformation; + static ULONG CachedParentProcessId = 0; + + if (CachedParentProcessId) { + // No need to query the kernel again. + return CachedParentProcessId; + } - // A GetModuleHandle call with NTDLL as the parameter is guaranteed to succeed on - // all versions of Windows. Ntdll = GetModuleHandleW(L"ntdll.dll"); - pNtQueryInformationProcess = (NTSTATUS (NTAPI *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG)) GetProcAddress(Ntdll, "NtQueryInformationProcess"); + if (!Ntdll) { + return 0; + } + pNtQueryInformationProcess = (PNT_QUERY_INFORMATION_PROCESS) GetProcAddress(Ntdll, "NtQueryInformationProcess"); if (!pNtQueryInformationProcess) { - // This code is never going to be executed. It is just here as insurance. - return PyErr_SetFromWindowsErr(GetLastError()); + return 0; } - // The NtQueryInformationProcess system call, the ProcessBasicInformation info class - // and the PROCESS_BASIC_INFORMATION structure are all documented on MSDN. Status = pNtQueryInformationProcess( GetCurrentProcess(), ProcessBasicInformation, @@ -9155,30 +9168,68 @@ win32_getppid(void) sizeof(BasicInformation), NULL); - // If the NtQueryInformationProcess fails for any reason (it should never fail given - // the parameters passed to it), we will convert the returned NTSTATUS error code into - // a normal Win32 error code. if (!NT_SUCCESS(Status)) { - ULONG ErrorCode; - ULONG (NTAPI *pRtlNtStatusToDosError) (NTSTATUS); + return 0; + } - pRtlNtStatusToDosError = (ULONG (NTAPI *) (NTSTATUS)) GetProcAddress(Ntdll, "RtlNtStatusToDosError"); + // + // Perform sanity check on the parent process ID we received from NtQueryInformationProcess. + // The check covers values which exceed the 32-bit range (if running on x64) as well as + // zero and (ULONG) -1. + // - if (!pRtlNtStatusToDosError) { - return PyErr_SetFromWindowsErr(GetLastError()); - } + if (BasicInformation.InheritedFromUniqueProcessId == 0 || + BasicInformation.InheritedFromUniqueProcessId >= ULONG_MAX) { - // Convert the NTSTATUS error code into a Win32 error code. - ErrorCode = pRtlNtStatusToDosError(Status); - return PyErr_SetFromWindowsErr(ErrorCode); + return 0; } + // // Now that we have reached this point, the BasicInformation.InheritedFromUniqueProcessId // structure member contains a ULONG_PTR which represents the process ID of our parent // process. This process ID will be correctly returned even if the parent process has - // terminated. + // exited or been terminated. + // - return PyLong_FromUnsignedLong((ULONG) BasicInformation.InheritedFromUniqueProcessId); + CachedParentProcessId = (ULONG) BasicInformation.InheritedFromUniqueProcessId; + return CachedParentProcessId; +} + +static PyObject* +win32_getppid(void) +{ + DWORD error; + PyObject* result = NULL; + HANDLE process = GetCurrentProcess(); + HPSS snapshot = NULL; + ULONG pid; + + pid = win32_getppid_fast(); + if (pid != 0) { + return PyLong_FromUnsignedLong(pid); + } + + // + // If failure occurs in win32_getppid_fast(), fall back to using the PSS API. + // + + error = PssCaptureSnapshot(process, PSS_CAPTURE_NONE, 0, &snapshot); + if (error != ERROR_SUCCESS) { + return PyErr_SetFromWindowsErr(error); + } + + PSS_PROCESS_INFORMATION info; + error = PssQuerySnapshot(snapshot, PSS_QUERY_PROCESS_INFORMATION, &info, + sizeof(info)); + if (error == ERROR_SUCCESS) { + result = PyLong_FromUnsignedLong(info.ParentProcessId); + } + else { + result = PyErr_SetFromWindowsErr(error); + } + + PssFreeSnapshot(process, snapshot); + return result; } #endif /*MS_WINDOWS*/ From 615900282da5cf7c970c47ea029823cc0ece13c1 Mon Sep 17 00:00:00 2001 From: vxiiduu <73044267+vxiiduu@users.noreply.github.com> Date: Sat, 2 Mar 2024 01:03:19 +1000 Subject: [PATCH 3/3] Fix a header file that was left out --- Modules/posixmodule.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 1cd6094046ef31..7ebdf1976bb0f9 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -9116,6 +9116,7 @@ os_setpgrp_impl(PyObject *module) #ifdef MS_WINDOWS #include +#include // The structure definition in winternl.h may be incomplete. // This structure is the full version from the MSDN documentation.