From d68583a1dd351551059c9ac8d53b537abfe5c5ab Mon Sep 17 00:00:00 2001 From: Jesse Schwartzentruber Date: Thu, 5 Jan 2023 16:52:54 -0500 Subject: [PATCH] Use GetFinalPathNameByHandle for Process.open_files Fixes #1967 --- psutil/_psutil_common.c | 4 - psutil/_psutil_windows.c | 1 - psutil/arch/windows/ntextapi.h | 9 -- psutil/arch/windows/process_handles.c | 203 ++++++-------------------- psutil/arch/windows/process_info.c | 2 +- psutil/arch/windows/process_info.h | 1 + 6 files changed, 43 insertions(+), 177 deletions(-) diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 096e2f373f..7ce43c9253 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -270,10 +270,6 @@ psutil_loadlibs() { "ntdll.dll", "NtSetInformationProcess"); if (! NtSetInformationProcess) return 1; - NtQueryObject = psutil_GetProcAddressFromLib( - "ntdll.dll", "NtQueryObject"); - if (! NtQueryObject) - return 1; RtlIpv4AddressToStringA = psutil_GetProcAddressFromLib( "ntdll.dll", "RtlIpv4AddressToStringA"); if (! RtlIpv4AddressToStringA) diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index 6e51c449dd..117578c66f 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -9,7 +9,6 @@ * other modules: * - NtQuerySystemInformation * - NtQueryInformationProcess - * - NtQueryObject * - NtSuspendProcess * - NtResumeProcess */ diff --git a/psutil/arch/windows/ntextapi.h b/psutil/arch/windows/ntextapi.h index e0662fa044..75c1625ae4 100644 --- a/psutil/arch/windows/ntextapi.h +++ b/psutil/arch/windows/ntextapi.h @@ -660,15 +660,6 @@ ULONGLONG (CALLBACK *_GetTickCount64) ( #define GetTickCount64 _GetTickCount64 -NTSTATUS (NTAPI *_NtQueryObject) ( - HANDLE Handle, - OBJECT_INFORMATION_CLASS ObjectInformationClass, - PVOID ObjectInformation, - ULONG ObjectInformationLength, - PULONG ReturnLength); - -#define NtQueryObject _NtQueryObject - NTSTATUS (WINAPI *_RtlGetVersion) ( PRTL_OSVERSIONINFOW lpVersionInformation ); diff --git a/psutil/arch/windows/process_handles.c b/psutil/arch/windows/process_handles.c index 72e3f4d41e..5d3da84f2c 100644 --- a/psutil/arch/windows/process_handles.c +++ b/psutil/arch/windows/process_handles.c @@ -6,12 +6,6 @@ /* * This module retrieves handles opened by a process. - * We use NtQuerySystemInformation to enumerate them and NtQueryObject - * to obtain the corresponding file name. - * Since NtQueryObject hangs for certain handle types we call it in a - * separate thread which gets killed if it doesn't complete within 100ms. - * This is a limitation of the Windows API and ProcessHacker uses the - * same trick: https://github.com/giampaolo/psutil/pull/597 * * CREDITS: original implementation was written by Jeff Tang. * It was then rewritten by Giampaolo Rodola many years later. @@ -23,12 +17,7 @@ #include #include "../../_psutil_common.h" -#include "process_utils.h" - - -#define THREAD_TIMEOUT 100 // ms -// Global object shared between the 2 threads. -PUNICODE_STRING globalFileName = NULL; +#include "process_info.h" static int @@ -81,151 +70,32 @@ psutil_enum_handles(PSYSTEM_HANDLE_INFORMATION_EX *handles) { } -static int -psutil_get_filename(LPVOID lpvParam) { - HANDLE hFile = *((HANDLE*)lpvParam); - NTSTATUS status; - ULONG bufferSize; - ULONG attempts = 8; - - bufferSize = 0x200; - globalFileName = MALLOC_ZERO(bufferSize); - if (globalFileName == NULL) { - PyErr_NoMemory(); - goto error; - } - - - // Note: also this is supposed to hang, hence why we do it in here. - if (GetFileType(hFile) != FILE_TYPE_DISK) { - SetLastError(0); - globalFileName->Length = 0; - return 0; - } - - // A loop is needed because the I/O subsystem likes to give us the - // wrong return lengths... - do { - status = NtQueryObject( - hFile, - ObjectNameInformation, - globalFileName, - bufferSize, - &bufferSize - ); - if (status == STATUS_BUFFER_OVERFLOW || - status == STATUS_INFO_LENGTH_MISMATCH || - status == STATUS_BUFFER_TOO_SMALL) - { - FREE(globalFileName); - globalFileName = MALLOC_ZERO(bufferSize); - if (globalFileName == NULL) { - PyErr_NoMemory(); - goto error; - } - } - else { - break; - } - } while (--attempts); - - if (! NT_SUCCESS(status)) { - psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation"); - FREE(globalFileName); - globalFileName = NULL; - return 1; - } - - return 0; - -error: - if (globalFileName != NULL) { - FREE(globalFileName); - globalFileName = NULL; - } - return 1; -} - - -static DWORD -psutil_threaded_get_filename(HANDLE hFile) { - DWORD dwWait; - HANDLE hThread; - DWORD threadRetValue; - - hThread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)psutil_get_filename, &hFile, 0, NULL); - if (hThread == NULL) { - PyErr_SetFromOSErrnoWithSyscall("CreateThread"); - return 1; - } - - // Wait for the worker thread to finish. - dwWait = WaitForSingleObject(hThread, THREAD_TIMEOUT); - - // If the thread hangs, kill it and cleanup. - if (dwWait == WAIT_TIMEOUT) { - psutil_debug( - "get handle name thread timed out after %i ms", THREAD_TIMEOUT); - if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall("TerminateThread"); - CloseHandle(hThread); - return 1; - } - CloseHandle(hThread); - return 0; - } - - if (dwWait == WAIT_FAILED) { - psutil_debug("WaitForSingleObject -> WAIT_FAILED"); - if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall( - "WaitForSingleObject -> WAIT_FAILED -> TerminateThread"); - CloseHandle(hThread); - return 1; - } - PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); - CloseHandle(hThread); - return 1; - } - - if (GetExitCodeThread(hThread, &threadRetValue) == 0) { - if (TerminateThread(hThread, 0) == 0) { - PyErr_SetFromOSErrnoWithSyscall( - "GetExitCodeThread (failed) -> TerminateThread"); - CloseHandle(hThread); - return 1; - } - - CloseHandle(hThread); - PyErr_SetFromOSErrnoWithSyscall("GetExitCodeThread"); - return 1; - } - CloseHandle(hThread); - return threadRetValue; -} - - PyObject * psutil_get_open_files(DWORD dwPid, HANDLE hProcess) { + static ULONG fileNameBufferSize = ( + sizeof(WCHAR) * (_MAX_PATH + 1)); PSYSTEM_HANDLE_INFORMATION_EX handlesList = NULL; PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX hHandle = NULL; HANDLE hFile = NULL; ULONG i = 0; BOOLEAN errorOccurred = FALSE; PyObject* py_path = NULL; - PyObject* py_retlist = PyList_New(0);; + PyObject* py_retlist = PyList_New(0); + WCHAR* lpFileName = NULL; + ULONG status = 0; if (!py_retlist) return NULL; - // Due to the use of global variables, ensure only 1 call - // to psutil_get_open_files() is running. - EnterCriticalSection(&PSUTIL_CRITICAL_SECTION); - if (psutil_enum_handles(&handlesList) != 0) goto error; + lpFileName = MALLOC_ZERO(fileNameBufferSize); + if (!lpFileName) { + PyErr_NoMemory(); + goto error; + } + for (i = 0; i < handlesList->NumberOfHandles; i++) { hHandle = &handlesList->Handles[i]; if ((ULONG_PTR)hHandle->UniqueProcessId != dwPid) @@ -243,25 +113,37 @@ psutil_get_open_files(DWORD dwPid, HANDLE hProcess) { continue; } - // This will set *globalFileName* global variable. - if (psutil_threaded_get_filename(hFile) != 0) - goto error; + if (GetFileType(hFile) != FILE_TYPE_DISK) { + goto cleanup_continue; + } - if ((globalFileName != NULL) && (globalFileName->Length > 0)) { - py_path = PyUnicode_FromWideChar(globalFileName->Buffer, - wcslen(globalFileName->Buffer)); - if (! py_path) - goto error; - if (PyList_Append(py_retlist, py_path)) - goto error; - Py_CLEAR(py_path); // also sets to NULL + // Get the filename + if (! GetFinalPathNameByHandleW( + hFile, + lpFileName, + fileNameBufferSize, + VOLUME_NAME_NT)) + { + status = GetLastError(); + // Ignore some normal errors + if (status == ERROR_INVALID_FUNCTION) { + SetLastError(0); + goto cleanup_continue; + } + psutil_convert_winerr(status, "GetFinalPathNameByHandle"); + goto error; } + py_path = PyUnicode_FromWideChar(lpFileName, + wcslen(lpFileName)); + if (! py_path) + goto error; + if (PyList_Append(py_retlist, py_path)) + goto error; + Py_CLEAR(py_path); // also sets to NULL + // Loop cleanup section. - if (globalFileName != NULL) { - FREE(globalFileName); - globalFileName = NULL; - } +cleanup_continue: CloseHandle(hFile); hFile = NULL; } @@ -276,16 +158,13 @@ psutil_get_open_files(DWORD dwPid, HANDLE hProcess) { exit: if (hFile != NULL) CloseHandle(hFile); - if (globalFileName != NULL) { - FREE(globalFileName); - globalFileName = NULL; - } if (py_path != NULL) Py_DECREF(py_path); if (handlesList != NULL) FREE(handlesList); + if (lpFileName != NULL) + FREE(lpFileName); - LeaveCriticalSection(&PSUTIL_CRITICAL_SECTION); if (errorOccurred == TRUE) return NULL; return py_retlist; diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c index 1981d306a6..14a52834da 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -50,7 +50,7 @@ enum psutil_process_data_kind { }; -static void +void psutil_convert_winerr(ULONG err, char* syscall) { char fullmsg[8192]; diff --git a/psutil/arch/windows/process_info.h b/psutil/arch/windows/process_info.h index 5e89ddebdf..946677a0d7 100644 --- a/psutil/arch/windows/process_info.h +++ b/psutil/arch/windows/process_info.h @@ -15,6 +15,7 @@ int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, PVOID *retBuffer); +void psutil_convert_winerr(ULONG err, char* syscall); PyObject* psutil_get_cmdline(DWORD pid, int use_peb); PyObject* psutil_get_cwd(DWORD pid); PyObject* psutil_get_environ(DWORD pid);