From: Pali Rohár Date: Sun, 30 Nov 2025 15:28:46 +0000 (+0100) Subject: windows: Simplify win32_find_and_open_process_for_query() X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b5ea805b8d17edc9b22d13f88fb3e24b5db3050;p=thirdparty%2Fpciutils.git windows: Simplify win32_find_and_open_process_for_query() Retrieve information about all processes in system via NtQuerySystemInformation(SystemProcessInformation) call. This method returns both process id and process name. It is supported on all Windows NT based systems. This replaces complicated code which uses different WinAPI functions for specific Windows versions. --- diff --git a/lib/win32-helpers.c b/lib/win32-helpers.c index 24a731d..de52398 100644 --- a/lib/win32-helpers.c +++ b/lib/win32-helpers.c @@ -92,18 +92,6 @@ __inline struct _TEB *NtCurrentTeb(void) { __asm mov eax, fs:[PcTeb] } #define TEB_HARD_ERROR_MODE_OFFSET 0x16B0 #endif -/* - * These psapi functions are available in kernel32.dll library with K32 prefix - * on Windows 7 and higher systems. On older Windows systems these functions are - * available in psapi.dll libary without K32 prefix. So resolve pointers to - * these functions dynamically at runtime from the available system library. - * Function GetProcessImageFileNameW() is not available on Windows 2000 and - * older systems. - */ -typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded); -typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize); -typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize); - /* * These aclapi function is available in advapi.dll library on Windows 2000 * and higher systems. @@ -123,6 +111,37 @@ typedef UINT (WINAPI *GetErrorModeProt)(VOID); typedef DWORD (WINAPI *GetThreadErrorModeProt)(VOID); typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode); +/* + * This NtQuerySystemInformation() function and SystemProcessInformation class + * is available in all Windows NT based systems. But their definitions are in + * any standard WinAPI header file. + */ +#ifndef NTSTATUS +#define NTSTATUS LONG +#endif +#ifndef STATUS_INFO_LENGTH_MISMATCH +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004) +#endif +#ifndef SYSTEM_INFORMATION_CLASS +#define SYSTEM_INFORMATION_CLASS DWORD +#endif +#ifndef SystemProcessInformation +#define SystemProcessInformation 5 +#endif +typedef struct { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER Reserved[6]; + struct { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } ImageName; + LONG BasePriority; + HANDLE UniqueProcessId; +} MY_SYSTEM_PROCESS_INFORMATION; +typedef NTSTATUS (NTAPI *NtQuerySystemInformationProt)(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength); + static DWORD format_message_from_system(DWORD win32_error_id, DWORD lang_id, LPSTR buffer, DWORD size) @@ -1132,7 +1151,7 @@ revert_token_dacl_permissions(HANDLE token, PSECURITY_DESCRIPTOR old_security_de * optionally also with vm read right. */ static HANDLE -open_process_for_query(DWORD pid, BOOL with_vm_read) +open_process_for_query(DWORD pid) { BOOL revert_only_privilege; LUID luid_debug_privilege; @@ -1160,9 +1179,6 @@ open_process_for_query(DWORD pid, BOOL with_vm_read) else process_right = PROCESS_QUERY_INFORMATION; - if (with_vm_read) - process_right |= PROCESS_VM_READ; - process = OpenProcess(process_right, FALSE, pid); if (process) return process; @@ -1189,42 +1205,32 @@ open_process_for_query(DWORD pid, BOOL with_vm_read) } /* - * Check if process image path name (wide string) matches exe file name - * (7-bit ASCII string). Do case-insensitive string comparison. Process - * image path name can be in any namespace format (DOS, Win32, UNC, ...). + * Check if the non-nul-term wide string of the process image file name + * (base name with extension) matches the narrow nul-term string of the + * exe file name (base name with extension). Do case-insensitive string + * comparison because process image name is uppercase on older Windows + * versions. */ static BOOL -check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) +check_process_name(LPCWSTR image_name, DWORD image_name_byte_length, LPCSTR exe_file) { - DWORD exe_file_length; + DWORD exe_file_length = strlen(exe_file); WCHAR c1; UCHAR c2; DWORD i; - exe_file_length = 0; - while (exe_file[exe_file_length] != '\0') - exe_file_length++; - - /* Path must have backslash before exe file name. */ - if (exe_file_length >= path_length || - path[path_length-exe_file_length-1] != L'\\') + if (image_name_byte_length / sizeof(WCHAR) != exe_file_length) return FALSE; for (i = 0; i < exe_file_length; i++) { - c1 = path[path_length-exe_file_length+i]; + c1 = image_name[i]; c2 = exe_file[i]; - /* - * Input string for comparison is 7-bit ASCII and file name part - * of path must not contain backslash as it is path separator. - */ - if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\') - return FALSE; if (c1 >= L'a' && c1 <= L'z') c1 -= L'a' - L'A'; if (c2 >= 'a' && c2 <= 'z') c2 -= 'a' - 'A'; - if (c1 != c2) + if (c1 != c2 || c2 >= 0x80) /* exe_file must be 7-bit ASCII */ return FALSE; } @@ -1235,188 +1241,70 @@ check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) HANDLE win32_find_and_open_process_for_query(LPCSTR exe_file) { - GetProcessImageFileNameWProt MyGetProcessImageFileNameW; - GetModuleFileNameExWProt MyGetModuleFileNameExW; - EnumProcessesProt MyEnumProcesses; - HMODULE kernel32, psapi; - UINT prev_error_mode; - DWORD partial_retry; - BOOL found_process; - DWORD size, length; - DWORD *processes; + HMODULE ntdll; + NtQuerySystemInformationProt MyNtQuerySystemInformation; + MY_SYSTEM_PROCESS_INFORMATION *info; + NTSTATUS status; + BYTE *buffer; + DWORD size; HANDLE process; - LPWSTR path; - DWORD error; - DWORD count; - DWORD i; - psapi = NULL; - kernel32 = GetModuleHandle(TEXT("kernel32.dll")); - if (!kernel32) + /* Ntdll.dll is loaded into every process on all NT systems. */ + ntdll = GetModuleHandle(TEXT("ntdll.dll")); + if (!ntdll) + return NULL; + + MyNtQuerySystemInformation = (LPVOID)GetProcAddress(ntdll, "NtQuerySystemInformation"); + if (!MyNtQuerySystemInformation) return NULL; /* - * On Windows 7 and higher systems these functions are available in - * kernel32.dll library with K32 prefix. + * Retrieve information about all processes in system via + * NtQuerySystemInformation(SystemProcessInformation) call. + * This method returns both process id and process name. + * It is supported on all Windows NT based systems. */ - MyGetModuleFileNameExW = NULL; - MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(void(*)(void))GetProcAddress(kernel32, "K32GetProcessImageFileNameW"); - MyEnumProcesses = (EnumProcessesProt)(void(*)(void))GetProcAddress(kernel32, "K32EnumProcesses"); - if (!MyGetProcessImageFileNameW || !MyEnumProcesses) - { - /* - * On older NT-based systems these functions are available in - * psapi.dll library without K32 prefix. - */ - prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX, TRUE); - psapi = LoadLibrary(TEXT("psapi.dll")); - win32_change_error_mode(prev_error_mode, FALSE); - - if (!psapi) - return NULL; - - /* - * Function GetProcessImageFileNameW() is available in - * Windows XP and higher systems. On older versions is - * available function GetModuleFileNameExW(). - */ - MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(void(*)(void))GetProcAddress(psapi, "GetProcessImageFileNameW"); - MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(void(*)(void))GetProcAddress(psapi, "GetModuleFileNameExW"); - MyEnumProcesses = (EnumProcessesProt)(void(*)(void))GetProcAddress(psapi, "EnumProcesses"); - if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses) - { - FreeLibrary(psapi); - return NULL; - } - } - - /* Make initial buffer size for 1024 processes. */ - size = 1024 * sizeof(*processes); - + size = 4096; retry: - processes = (DWORD *)LocalAlloc(LPTR, size); - if (!processes) + buffer = (BYTE *)LocalAlloc(LPTR, size); + if (!buffer) + return NULL; + status = MyNtQuerySystemInformation(SystemProcessInformation, buffer, size, NULL); + if (status == STATUS_INFO_LENGTH_MISMATCH) { - if (psapi) - FreeLibrary(psapi); - return NULL; + LocalFree(buffer); + size *= 2; + goto retry; } - - if (!MyEnumProcesses(processes, size, &length)) + if (status < 0) { - LocalFree(processes); - if (psapi) - FreeLibrary(psapi); + LocalFree(buffer); return NULL; } - else if (size == length) - { - /* - * There is no indication given when the buffer is too small to - * store all process identifiers. Therefore if returned length - * is same as buffer size there can be more processes. Call - * again with larger buffer. - */ - LocalFree(processes); - size *= 2; - goto retry; - } process = NULL; - count = length / sizeof(*processes); - - for (i = 0; i < count; i++) + info = (MY_SYSTEM_PROCESS_INFORMATION *)buffer; + while (1) { - /* Skip System Idle Process. */ - if (processes[i] == 0) - continue; - /* - * Function GetModuleFileNameExW() requires additional - * PROCESS_VM_READ right as opposite to function - * GetProcessImageFileNameW() which does not need it. + * ImageName is just file base name with extension, not the full path. + * For inaccessible and system processes it can be an empty string. + * Note that ImageName.Length is length without nul term in bytes + * and ImageName.Buffer is the wide string. */ - process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE); - if (!process) - continue; - - /* - * Set initial buffer size to 256 (wide) characters. - * Final path length on the modern NT-based systems can be also larger. - */ - size = 256; - found_process = FALSE; - partial_retry = 0; - -retry_path: - path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path)); - if (!path) - goto end_path; - - if (MyGetProcessImageFileNameW) - length = MyGetProcessImageFileNameW(process, path, size); - else - length = MyGetModuleFileNameExW(process, NULL, path, size); - - error = GetLastError(); - - /* - * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY - * when remote process is in the middle of updating its module table. - * Sleep 10 ms and try again, max 10 attempts. - */ - if (!MyGetProcessImageFileNameW) - { - if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10) - { - Sleep(10); - goto retry_path; - } - partial_retry = 0; - } - - /* - * When buffer is too small then function GetModuleFileNameEx() returns - * its size argument on older systems (Windows XP) or its size minus - * argument one on new systems (Windows 10) without signalling any error. - * Function GetProcessImageFileNameW() on the other hand returns zero - * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these - * cases call function again with larger buffer. - */ - - if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER) - goto end_path; - - if ((MyGetProcessImageFileNameW && length == 0) || - (!MyGetProcessImageFileNameW && (length == size || length == size-1))) + if (check_process_name(info->ImageName.Buffer, info->ImageName.Length, exe_file)) { - LocalFree(path); - size *= 2; - goto retry_path; - } - - if (length && check_process_name(path, length, exe_file)) - found_process = TRUE; - -end_path: - if (path) - { - LocalFree(path); - path = NULL; + process = open_process_for_query((DWORD)info->UniqueProcessId); + break; } - if (found_process) + if (info->NextEntryOffset == 0) break; - CloseHandle(process); - process = NULL; + info = (MY_SYSTEM_PROCESS_INFORMATION *)((BYTE *)info + info->NextEntryOffset); } - LocalFree(processes); - - if (psapi) - FreeLibrary(psapi); - + LocalFree(buffer); return process; }