2 * The PCI Library -- Win32 helper functions
4 * Copyright (c) 2023 Pali Rohár <pali@kernel.org>
6 * Can be freely distributed and used under the terms of the GNU GPL v2+
8 * SPDX-License-Identifier: GPL-2.0-or-later
13 #include <stdio.h> /* for sprintf() */
15 #include "win32-helpers.h"
17 /* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */
18 #ifndef PROCESS_QUERY_LIMITED_INFORMATION
19 #define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
22 /* Unfortunately some toolchains do not provide this constant. */
23 #ifndef SE_IMPERSONATE_NAME
24 #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
28 * These psapi functions are available in kernel32.dll library with K32 prefix
29 * on Windows 7 and higher systems. On older Windows systems these functions are
30 * available in psapi.dll libary without K32 prefix. So resolve pointers to
31 * these functions dynamically at runtime from the available system library.
32 * Function GetProcessImageFileNameW() is not available on Windows 2000 and
35 typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded);
36 typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize);
37 typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize);
40 * These aclapi functions are available in advapi.dll library on Windows NT 4.0
43 typedef DWORD (WINAPI *GetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID *ppsidOwner, PSID *ppsidGroup, PACL *ppDacl, PACL *ppSacl, PSECURITY_DESCRIPTOR *ppSecurityDescriptor);
44 typedef DWORD (WINAPI *SetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID psidOwner, PSID psidGroup, PACL pDacl, PACL pSacl);
45 typedef DWORD (WINAPI *SetEntriesInAclProt)(ULONG cCountOfExplicitEntries, PEXPLICIT_ACCESS pListOfExplicitEntries, PACL OldAcl, PACL *NewAcl);
48 * This errhandlingapi function is available in kernel32.dll library on
49 * Windows 7 and higher systems.
51 typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode);
55 win32_strerror(DWORD win32_error_id)
58 * Use static buffer which is large enough.
59 * Hopefully no Win32 API error message string is longer than 4 kB.
61 static char buffer[4096];
64 len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL);
66 /* FormatMessage() automatically appends ".\r\n" to the error message. */
67 if (len && buffer[len-1] == '\n')
69 if (len && buffer[len-1] == '\r')
71 if (len && buffer[len-1] == '.')
75 sprintf(buffer, "Unknown Win32 error %lu", win32_error_id);
81 win32_is_non_nt_system(void)
83 OSVERSIONINFOA version;
84 version.dwOSVersionInfoSize = sizeof(version);
85 return GetVersionExA(&version) && version.dwPlatformId < VER_PLATFORM_WIN32_NT;
89 win32_is_32bit_on_64bit_system(void)
91 BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL);
96 * Check for 64-bit system via IsWow64Process() function exported
97 * from 32-bit kernel32.dll library available on the 64-bit systems.
98 * Resolve pointer to this function at runtime as this code path is
99 * primary running on 32-bit systems where are not available 64-bit
103 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
107 MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process");
108 if (!MyIsWow64Process)
111 if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64))
118 win32_is_32bit_on_win8_64bit_system(void)
123 OSVERSIONINFOA version;
125 /* Check for Windows 8 (NT 6.2). */
126 version.dwOSVersionInfoSize = sizeof(version);
127 if (!GetVersionExA(&version) ||
128 version.dwPlatformId != VER_PLATFORM_WIN32_NT ||
129 version.dwMajorVersion < 6 ||
130 (version.dwMajorVersion == 6 && version.dwMinorVersion < 2))
133 return win32_is_32bit_on_64bit_system();
138 * Change error mode of the current thread. If it is not possible then change
139 * error mode of the whole process. Always returns previous error mode.
142 win32_change_error_mode(UINT new_mode)
144 SetThreadErrorModeProt MySetThreadErrorMode = NULL;
149 * Function SetThreadErrorMode() was introduced in Windows 7, so use
150 * GetProcAddress() for compatibility with older systems.
152 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
154 MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode");
156 if (MySetThreadErrorMode &&
157 MySetThreadErrorMode(new_mode, &old_mode))
161 * Fallback to function SetErrorMode() which modifies error mode of the
162 * whole process and returns old mode.
164 return SetErrorMode(new_mode);
168 * Check if the current thread has particular privilege in current active access
169 * token. Case when it not possible to determinate it (e.g. current thread does
170 * not have permission to open its own current active access token) is evaluated
171 * as thread does not have that privilege.
174 win32_have_privilege(LUID luid_privilege)
181 * If the current thread does not have active access token then thread
182 * uses primary process access token for all permission checks.
184 if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
185 (GetLastError() != ERROR_NO_TOKEN ||
186 !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
189 priv.PrivilegeCount = 1;
190 priv.Control = PRIVILEGE_SET_ALL_NECESSARY;
191 priv.Privilege[0].Luid = luid_privilege;
192 priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
194 if (!PrivilegeCheck(token, &priv, &ret))
201 * Enable or disable particular privilege in specified access token.
203 * Note that it is not possible to disable privilege in access token with
204 * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check
205 * this case and incorrectly returns no error even when disabling failed.
206 * Rationale for this decision: Simplification of this function as WinAPI
207 * call AdjustTokenPrivileges() does not signal error in this case too.
210 set_privilege(HANDLE token, LUID luid_privilege, BOOL enable)
212 TOKEN_PRIVILEGES token_privileges;
214 token_privileges.PrivilegeCount = 1;
215 token_privileges.Privileges[0].Luid = luid_privilege;
216 token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
219 * WinAPI function AdjustTokenPrivileges() success also when not all
220 * privileges were enabled. It is always required to check for failure
221 * via GetLastError() call. AdjustTokenPrivileges() always sets error
222 * also when it success, as opposite to other WinAPI functions.
224 if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) ||
225 GetLastError() != ERROR_SUCCESS)
232 * Change access token for the current thread to new specified access token.
233 * Previously active access token is stored in old_token variable and can be
234 * used for reverting to this access token. It is set to NULL if the current
235 * thread previously used primary process access token.
238 win32_change_token(HANDLE new_token, HANDLE *old_token)
242 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
244 if (GetLastError() != ERROR_NO_TOKEN)
249 if (!ImpersonateLoggedOnUser(new_token))
261 * Change access token for the current thread to the primary process access
262 * token. This function fails also when the current thread already uses primary
263 * process access token.
266 change_token_to_primary(HANDLE *old_token)
270 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
280 * Revert to the specified access token for the current thread. When access
281 * token is specified as NULL then revert to the primary process access token.
282 * Use to revert after win32_change_token() or change_token_to_primary() call.
285 win32_revert_to_token(HANDLE token)
288 * If SetThreadToken() call fails then there is no option to revert to
289 * the specified previous thread access token. So in this case revert to
290 * the primary process access token.
292 if (!token || !SetThreadToken(NULL, token))
299 * Enable particular privilege for the current thread. And set method how to
300 * revert this privilege (if to revert whole token or only privilege).
303 win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege)
308 if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token))
310 if (set_privilege(thread_token, luid_privilege, TRUE))
313 * Indicate that correct revert method is just to
314 * disable privilege in access token.
316 if (revert_token && revert_only_privilege)
318 *revert_token = thread_token;
319 *revert_only_privilege = TRUE;
323 CloseHandle(thread_token);
327 CloseHandle(thread_token);
329 * If enabling privilege failed then try to enable it via
330 * primary process access token.
335 * If the current thread has already active thread access token then
336 * open it with just impersonate right as it would be used only for
339 if (revert_token && revert_only_privilege)
341 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token))
343 if (GetLastError() != ERROR_NO_TOKEN)
349 * If current thread has no access token (and uses primary
350 * process access token) or it does not have permission to
351 * adjust privileges or it does not have specified privilege
352 * then create a copy of the primary process access token,
353 * assign it for the current thread (= impersonate self)
354 * and then try adjusting privilege again.
356 if (!ImpersonateSelf(SecurityImpersonation))
359 CloseHandle(thread_token);
364 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token))
366 /* thread_token is set only when we were asked for revert method. */
367 if (revert_token && revert_only_privilege)
368 win32_revert_to_token(thread_token);
372 if (!set_privilege(new_token, luid_privilege, TRUE))
374 CloseHandle(new_token);
375 /* thread_token is set only when we were asked for revert method. */
376 if (revert_token && revert_only_privilege)
377 win32_revert_to_token(thread_token);
382 * Indicate that correct revert method is to change to the previous
383 * access token. Either to the primary process access token or to the
384 * previous thread access token.
386 if (revert_token && revert_only_privilege)
388 *revert_token = thread_token;
389 *revert_only_privilege = FALSE;
395 * Revert particular privilege for the current thread was previously enabled by
396 * win32_enable_privilege() call. Either disable privilege in specified access token
397 * or revert to previous access token.
400 win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege)
402 if (revert_only_privilege)
404 set_privilege(revert_token, luid_privilege, FALSE);
405 CloseHandle(revert_token);
409 win32_revert_to_token(revert_token);
414 * Return owner of the access token used by the current thread. Buffer for
415 * returned owner needs to be released by LocalFree() call.
418 get_current_token_owner(VOID)
425 * If the current thread does not have active access token then thread
426 * uses primary process access token for all permission checks.
428 if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
429 (GetLastError() != ERROR_NO_TOKEN ||
430 !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
433 if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) &&
434 GetLastError() != ERROR_INSUFFICIENT_BUFFER)
441 owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length);
448 if (!GetTokenInformation(token, TokenOwner, owner, length, &length))
451 * Length of token owner (SID) buffer between two get calls may
452 * changes (e.g. by another thread of process), so retry.
454 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
469 * Grant particular permissions in the primary access token of the specified
470 * process for the owner of current thread token and set old DACL of the
471 * process access token for reverting permissions. Security descriptor is
472 * just memory buffer for old DACL.
475 grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PACL *old_dacl, PSECURITY_DESCRIPTOR *security_descriptor)
477 GetSecurityInfoProt MyGetSecurityInfo;
478 SetSecurityInfoProt MySetSecurityInfo;
479 SetEntriesInAclProt MySetEntriesInAcl;
480 EXPLICIT_ACCESS explicit_access;
486 * This source file already uses advapi32.dll library, so it is
487 * linked to executable and automatically loaded when starting
488 * current running process.
490 advapi32 = GetModuleHandle(TEXT("advapi32.dll"));
495 * It does not matter if SetEntriesInAclA() or SetEntriesInAclW() is
496 * called as no string is passed to SetEntriesInAcl function.
498 MyGetSecurityInfo = (GetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "GetSecurityInfo");
499 MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo");
500 MySetEntriesInAcl = (SetEntriesInAclProt)(LPVOID)GetProcAddress(advapi32, "SetEntriesInAclA");
501 if (!MyGetSecurityInfo || !MySetSecurityInfo || !MySetEntriesInAcl)
504 owner = get_current_token_owner();
509 * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION)
510 * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION).
512 if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token))
518 if (MyGetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL, security_descriptor) != ERROR_SUCCESS)
526 * Set new explicit access for the owner of the current thread access
527 * token with non-inherited granting access to specified permissions.
529 explicit_access.grfAccessPermissions = permissions;
530 explicit_access.grfAccessMode = GRANT_ACCESS;
531 explicit_access.grfInheritance = NO_PROPAGATE_INHERIT_ACE;
532 explicit_access.Trustee.pMultipleTrustee = NULL;
533 explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
534 explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
535 explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER;
537 * Unfortunately i586-mingw32msvc toolchain does not have pSid pointer
538 * member in Trustee union. So assign owner SID to ptstrName pointer
539 * member which aliases with pSid pointer member in the same union.
541 explicit_access.Trustee.ptstrName = (PVOID)owner->Owner;
543 if (MySetEntriesInAcl(1, &explicit_access, *old_dacl, &new_dacl) != ERROR_SUCCESS)
545 LocalFree(*security_descriptor);
551 if (MySetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS)
554 LocalFree(*security_descriptor);
566 * Revert particular granted permissions in specified access token done by
567 * grant_process_token_dacl_permissions() call.
570 revert_token_dacl_permissions(HANDLE token, PACL old_dacl, PSECURITY_DESCRIPTOR security_descriptor)
572 SetSecurityInfoProt MySetSecurityInfo;
576 * This source file already uses advapi32.dll library, so it is
577 * linked to executable and automatically loaded when starting
578 * current running process.
580 advapi32 = GetModuleHandle(TEXT("advapi32.dll"));
583 MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo");
584 MySetSecurityInfo(token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL);
587 LocalFree(security_descriptor);
592 * Open process handle specified by the process id with the query right and
593 * optionally also with vm read right.
596 open_process_for_query(DWORD pid, BOOL with_vm_read)
598 BOOL revert_only_privilege;
599 LUID luid_debug_privilege;
600 OSVERSIONINFO version;
606 * Some processes on Windows Vista and higher systems can be opened only
607 * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough
608 * for accessing primary process token. But this right is not supported
609 * on older pre-Vista systems. When the current thread on these older
610 * systems does not have Debug privilege then OpenProcess() fails with
611 * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then
612 * OpenProcess() success and returns handle to requested process.
613 * Problem is that this handle does not have PROCESS_QUERY_INFORMATION
614 * right and so cannot be used for accessing primary process token
615 * on those older systems. Moreover it has zero rights and therefore
616 * such handle is fully useless. So never try to use open process with
617 * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than
618 * Windows Vista (NT 6.0).
620 version.dwOSVersionInfoSize = sizeof(version);
621 if (GetVersionEx(&version) &&
622 version.dwPlatformId == VER_PLATFORM_WIN32_NT &&
623 version.dwMajorVersion >= 6)
624 process_right = PROCESS_QUERY_LIMITED_INFORMATION;
626 process_right = PROCESS_QUERY_INFORMATION;
629 process_right |= PROCESS_VM_READ;
631 process = OpenProcess(process_right, FALSE, pid);
636 * It is possible to open only processes to which owner of the current
637 * thread access token has permissions. For opening other processing it
638 * is required to have Debug privilege enabled. By default local
639 * administrators have this privilege, but it is disabled. So try to
640 * enable it and then try to open process again.
643 if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege))
646 if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege))
649 process = OpenProcess(process_right, FALSE, pid);
651 win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege);
657 * Check if process image path name (wide string) matches exe file name
658 * (7-bit ASCII string). Do case-insensitive string comparison. Process
659 * image path name can be in any namespace format (DOS, Win32, UNC, ...).
662 check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file)
664 DWORD exe_file_length;
670 while (exe_file[exe_file_length] != '\0')
673 /* Path must have backslash before exe file name. */
674 if (exe_file_length >= path_length ||
675 path[path_length-exe_file_length-1] != L'\\')
678 for (i = 0; i < exe_file_length; i++)
680 c1 = path[path_length-exe_file_length+i];
683 * Input string for comparison is 7-bit ASCII and file name part
684 * of path must not contain backslash as it is path separator.
686 if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\')
688 if (c1 >= L'a' && c1 <= L'z')
690 if (c2 >= 'a' && c2 <= 'z')
699 /* Open process handle with the query right specified by process exe file. */
701 win32_find_and_open_process_for_query(LPCSTR exe_file)
703 GetProcessImageFileNameWProt MyGetProcessImageFileNameW;
704 GetModuleFileNameExWProt MyGetModuleFileNameExW;
705 EnumProcessesProt MyEnumProcesses;
706 HMODULE kernel32, psapi;
707 UINT prev_error_mode;
719 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
724 * On Windows 7 and higher systems these functions are available in
725 * kernel32.dll library with K32 prefix.
727 MyGetModuleFileNameExW = NULL;
728 MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW");
729 MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses");
730 if (!MyGetProcessImageFileNameW || !MyEnumProcesses)
733 * On older NT-based systems these functions are available in
734 * psapi.dll library without K32 prefix.
736 prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
737 psapi = LoadLibrary(TEXT("psapi.dll"));
738 win32_change_error_mode(prev_error_mode);
744 * Function GetProcessImageFileNameW() is available in
745 * Windows XP and higher systems. On older versions is
746 * available function GetModuleFileNameExW().
748 MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW");
749 MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW");
750 MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses");
751 if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses)
758 /* Make initial buffer size for 1024 processes. */
759 size = 1024 * sizeof(*processes);
762 processes = (DWORD *)LocalAlloc(LPTR, size);
770 if (!MyEnumProcesses(processes, size, &length))
772 LocalFree(processes);
777 else if (size == length)
780 * There is no indication given when the buffer is too small to
781 * store all process identifiers. Therefore if returned length
782 * is same as buffer size there can be more processes. Call
783 * again with larger buffer.
785 LocalFree(processes);
791 count = length / sizeof(*processes);
793 for (i = 0; i < count; i++)
795 /* Skip System Idle Process. */
796 if (processes[i] == 0)
800 * Function GetModuleFileNameExW() requires additional
801 * PROCESS_VM_READ right as opposite to function
802 * GetProcessImageFileNameW() which does not need it.
804 process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE);
809 * Set initial buffer size to 256 (wide) characters.
810 * Final path length on the modern NT-based systems can be also larger.
813 found_process = FALSE;
817 path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path));
821 if (MyGetProcessImageFileNameW)
822 length = MyGetProcessImageFileNameW(process, path, size);
824 length = MyGetModuleFileNameExW(process, NULL, path, size);
826 error = GetLastError();
829 * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY
830 * when remote process is in the middle of updating its module table.
831 * Sleep 10 ms and try again, max 10 attempts.
833 if (!MyGetProcessImageFileNameW)
835 if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10)
844 * When buffer is too small then function GetModuleFileNameEx() returns
845 * its size argument on older systems (Windows XP) or its size minus
846 * argument one on new systems (Windows 10) without signalling any error.
847 * Function GetProcessImageFileNameW() on the other hand returns zero
848 * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these
849 * cases call function again with larger buffer.
852 if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER)
855 if ((MyGetProcessImageFileNameW && length == 0) ||
856 (!MyGetProcessImageFileNameW && (length == size || length == size-1)))
863 if (length && check_process_name(path, length, exe_file))
864 found_process = TRUE;
876 CloseHandle(process);
880 LocalFree(processes);
889 * Try to open primary access token of the particular process with specified
890 * rights. Before opening access token try to adjust DACL permissions of the
891 * primary process access token, so following open does not fail on error
892 * related to no open permissions. Revert DACL permissions after open attempt.
893 * As following steps are not atomic, try to execute them more times in case
894 * of possible race conditions caused by other threads or processes.
897 try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights)
899 PSECURITY_DESCRIPTOR security_descriptor;
907 * This code is not atomic. Between grant and open calls can other
908 * thread or process change or revert permissions. So try to execute
911 for (retry = 0; retry < 10; retry++)
913 if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_dacl, &security_descriptor))
915 if (!OpenProcessToken(process, rights, &token))
918 error = GetLastError();
920 revert_token_dacl_permissions(grant_token, old_dacl, security_descriptor);
923 else if (error != ERROR_ACCESS_DENIED)
931 * Open primary access token of particular process handle with specified rights.
932 * If permissions for specified rights are missing then try to grant them.
935 win32_open_process_token_with_rights(HANDLE process, DWORD rights)
940 /* First try to open primary access token of process handle directly. */
941 if (OpenProcessToken(process, rights, &token))
945 * If opening failed then it means that owner of the current thread
946 * access token does not have permission for it. Try it again with
947 * primary process access token.
949 if (change_token_to_primary(&old_token))
951 if (!OpenProcessToken(process, rights, &token))
953 win32_revert_to_token(old_token);
959 * If opening is still failing then try to grant specified permissions
960 * for the current thread and try to open it again.
962 token = try_grant_permissions_and_open_process_token(process, rights);
967 * And if it is still failing then try it again with granting
968 * permissions for the primary process token of the current process.
970 if (change_token_to_primary(&old_token))
972 token = try_grant_permissions_and_open_process_token(process, rights);
973 win32_revert_to_token(old_token);
979 * TODO: Sorry, no other option for now...
980 * It could be possible to use Take Ownership Name privilege to
981 * temporary change token owner of specified process to the owner of
982 * the current thread token, grant permissions for current thread in
983 * that process token, change ownership back to original one, open
984 * that process token and revert granted permissions. But this is
985 * not implemented yet.
991 * Call supplied function with its argument and if it fails with
992 * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and
993 * call function with its argument again.
996 win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument)
998 LUID luid_tcb_privilege;
999 LUID luid_impersonate_privilege;
1001 HANDLE revert_token_tcb_privilege;
1002 BOOL revert_only_tcb_privilege;
1004 HANDLE revert_token_impersonate_privilege;
1005 BOOL revert_only_impersonate_privilege;
1007 BOOL impersonate_privilege_enabled;
1009 BOOL revert_to_old_token;
1012 HANDLE lsass_process;
1017 impersonate_privilege_enabled = FALSE;
1018 revert_to_old_token = FALSE;
1022 /* Call supplied function. */
1023 ret = function(argument);
1024 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1028 * If function call failed with ERROR_PRIVILEGE_NOT_HELD
1029 * error then it means that the current thread token does not have
1030 * Tcb privilege enabled. Try to enable it.
1033 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege))
1034 goto err_privilege_not_held;
1037 * If the current thread has already Tcb privilege enabled then there
1038 * is some additional unhanded restriction.
1040 if (win32_have_privilege(luid_tcb_privilege))
1041 goto err_privilege_not_held;
1043 /* Try to enable Tcb privilege and try function call again. */
1044 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
1046 ret = function(argument);
1047 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1052 * If enabling of Tcb privilege failed then it means that current thread
1053 * does not have this privilege. But current process may have it. So try it
1054 * again with primary process access token.
1058 * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then
1059 * all future actions in this function require this Impersonate privilege.
1060 * So try to enable it in case it is currently disabled.
1062 if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) &&
1063 !win32_have_privilege(luid_impersonate_privilege))
1066 * If current thread does not have Impersonate privilege enabled
1067 * then first try to enable it just for the current thread. If
1068 * it is not possible to enable it just for the current thread
1069 * then try it to enable globally for whole process (which
1070 * affects all process threads). Both actions will be reverted
1071 * at the end of this function.
1073 if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege))
1075 impersonate_privilege_enabled = TRUE;
1077 else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL))
1079 impersonate_privilege_enabled = TRUE;
1080 revert_token_impersonate_privilege = NULL;
1081 revert_only_impersonate_privilege = TRUE;
1085 goto err_privilege_not_held;
1089 * Now when Impersonate privilege is enabled, try to enable Tcb
1090 * privilege again. Enabling other privileges for the current
1091 * thread requires Impersonate privilege, so enabling Tcb again
1094 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
1096 ret = function(argument);
1097 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1103 * If enabling Tcb privilege failed then it means that the current
1104 * thread access token does not have this privilege or does not
1105 * have permission to adjust privileges.
1107 * Try to use more privileged token from Local Security Authority
1108 * Subsystem Service process (lsass.exe) which has Tcb privilege.
1109 * Retrieving this more privileged token is possible for local
1110 * administrators (unless it was disabled by local administrators).
1113 lsass_process = win32_find_and_open_process_for_query("lsass.exe");
1115 goto err_privilege_not_held;
1118 * Open primary lsass.exe process access token with query and duplicate
1119 * rights. Just these two rights are required for impersonating other
1120 * primary process token (impersonate right is really not required!).
1122 lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE);
1124 CloseHandle(lsass_process);
1127 goto err_privilege_not_held;
1130 * After successful open of the primary lsass.exe process access token,
1131 * assign its copy for the current thread.
1133 if (!win32_change_token(lsass_token, &old_token))
1134 goto err_privilege_not_held;
1136 revert_to_old_token = TRUE;
1138 ret = function(argument);
1139 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1143 * Now current thread is not using primary process token anymore
1144 * but is using custom access token. There is no need to revert
1145 * enabled Tcb privilege as the whole custom access token would
1146 * be reverted. So there is no need to setup revert method for
1147 * enabling privilege.
1149 if (win32_have_privilege(luid_tcb_privilege) ||
1150 !win32_enable_privilege(luid_tcb_privilege, NULL, NULL))
1151 goto err_privilege_not_held;
1153 ret = function(argument);
1156 err_privilege_not_held:
1157 SetLastError(ERROR_PRIVILEGE_NOT_HELD);
1162 if (revert_to_old_token)
1163 win32_revert_to_token(old_token);
1165 if (impersonate_privilege_enabled)
1166 win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege);
1169 CloseHandle(lsass_token);