| /* |
| * The PCI Library -- Win32 helper functions |
| * |
| * Copyright (c) 2023 Pali Rohár <pali@kernel.org> |
| * |
| * Can be freely distributed and used under the terms of the GNU GPL v2+ |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| #include <windows.h> |
| |
| #include <stdio.h> /* for sprintf() */ |
| |
| #include "win32-helpers.h" |
| |
| /* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */ |
| #ifndef PROCESS_QUERY_LIMITED_INFORMATION |
| #define PROCESS_QUERY_LIMITED_INFORMATION 0x1000 |
| #endif |
| |
| /* Unfortunately some toolchains do not provide this constant. */ |
| #ifndef SE_IMPERSONATE_NAME |
| #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege") |
| #endif |
| |
| /* Unfortunately some toolchains do not provide these constants. */ |
| #ifndef SE_DACL_AUTO_INHERIT_REQ |
| #define SE_DACL_AUTO_INHERIT_REQ 0x0100 |
| #endif |
| #ifndef SE_SACL_AUTO_INHERIT_REQ |
| #define SE_SACL_AUTO_INHERIT_REQ 0x0200 |
| #endif |
| #ifndef SE_DACL_AUTO_INHERITED |
| #define SE_DACL_AUTO_INHERITED 0x0400 |
| #endif |
| #ifndef SE_SACL_AUTO_INHERITED |
| #define SE_SACL_AUTO_INHERITED 0x0800 |
| #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. |
| */ |
| typedef BOOL (WINAPI *SetSecurityDescriptorControlProt)(PSECURITY_DESCRIPTOR pSecurityDescriptor, SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest, SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet); |
| |
| /* |
| * This errhandlingapi function is available in kernel32.dll library on |
| * Windows 7 and higher systems. |
| */ |
| typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode); |
| |
| |
| static DWORD |
| format_message_from_system(DWORD win32_error_id, DWORD lang_id, LPSTR buffer, DWORD size) |
| { |
| return FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, lang_id, buffer, size, NULL); |
| } |
| |
| const char * |
| win32_strerror(DWORD win32_error_id) |
| { |
| /* |
| * Use static buffer which is large enough. |
| * Hopefully no Win32 API error message string is longer than 4 kB. |
| */ |
| static char buffer[4096]; |
| DWORD len; |
| |
| /* |
| * If it is possible show error messages in US English language. |
| * International Windows editions do not have to provide error |
| * messages in English language, so fallback to the language |
| * which system provides (neutral). |
| */ |
| len = format_message_from_system(win32_error_id, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer)); |
| if (!len) |
| len = format_message_from_system(win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer)); |
| |
| /* FormatMessage() automatically appends ".\r\n" to the error message. */ |
| if (len && buffer[len-1] == '\n') |
| buffer[--len] = '\0'; |
| if (len && buffer[len-1] == '\r') |
| buffer[--len] = '\0'; |
| if (len && buffer[len-1] == '.') |
| buffer[--len] = '\0'; |
| |
| if (!len) |
| sprintf(buffer, "Unknown Win32 error %lu", win32_error_id); |
| |
| return buffer; |
| } |
| |
| BOOL |
| win32_is_non_nt_system(void) |
| { |
| OSVERSIONINFOA version; |
| version.dwOSVersionInfoSize = sizeof(version); |
| return GetVersionExA(&version) && version.dwPlatformId < VER_PLATFORM_WIN32_NT; |
| } |
| |
| BOOL |
| win32_is_32bit_on_64bit_system(void) |
| { |
| BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL); |
| HMODULE kernel32; |
| BOOL is_wow64; |
| |
| /* |
| * Check for 64-bit system via IsWow64Process() function exported |
| * from 32-bit kernel32.dll library available on the 64-bit systems. |
| * Resolve pointer to this function at runtime as this code path is |
| * primary running on 32-bit systems where are not available 64-bit |
| * functions. |
| */ |
| |
| kernel32 = GetModuleHandle(TEXT("kernel32.dll")); |
| if (!kernel32) |
| return FALSE; |
| |
| MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process"); |
| if (!MyIsWow64Process) |
| return FALSE; |
| |
| if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64)) |
| return FALSE; |
| |
| return is_wow64; |
| } |
| |
| BOOL |
| win32_is_32bit_on_win8_64bit_system(void) |
| { |
| #ifdef _WIN64 |
| return FALSE; |
| #else |
| OSVERSIONINFOA version; |
| |
| /* Check for Windows 8 (NT 6.2). */ |
| version.dwOSVersionInfoSize = sizeof(version); |
| if (!GetVersionExA(&version) || |
| version.dwPlatformId != VER_PLATFORM_WIN32_NT || |
| version.dwMajorVersion < 6 || |
| (version.dwMajorVersion == 6 && version.dwMinorVersion < 2)) |
| return FALSE; |
| |
| return win32_is_32bit_on_64bit_system(); |
| #endif |
| } |
| |
| /* |
| * Change error mode of the current thread. If it is not possible then change |
| * error mode of the whole process. Always returns previous error mode. |
| */ |
| UINT |
| win32_change_error_mode(UINT new_mode) |
| { |
| SetThreadErrorModeProt MySetThreadErrorMode = NULL; |
| HMODULE kernel32; |
| DWORD old_mode; |
| |
| /* |
| * Function SetThreadErrorMode() was introduced in Windows 7, so use |
| * GetProcAddress() for compatibility with older systems. |
| */ |
| kernel32 = GetModuleHandle(TEXT("kernel32.dll")); |
| if (kernel32) |
| MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode"); |
| |
| if (MySetThreadErrorMode && |
| MySetThreadErrorMode(new_mode, &old_mode)) |
| return old_mode; |
| |
| /* |
| * Fallback to function SetErrorMode() which modifies error mode of the |
| * whole process and returns old mode. |
| */ |
| return SetErrorMode(new_mode); |
| } |
| |
| /* |
| * Check if the current thread has particular privilege in current active access |
| * token. Case when it not possible to determinate it (e.g. current thread does |
| * not have permission to open its own current active access token) is evaluated |
| * as thread does not have that privilege. |
| */ |
| BOOL |
| win32_have_privilege(LUID luid_privilege) |
| { |
| PRIVILEGE_SET priv; |
| HANDLE token; |
| BOOL ret; |
| |
| /* |
| * If the current thread does not have active access token then thread |
| * uses primary process access token for all permission checks. |
| */ |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && |
| (GetLastError() != ERROR_NO_TOKEN || |
| !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) |
| return FALSE; |
| |
| priv.PrivilegeCount = 1; |
| priv.Control = PRIVILEGE_SET_ALL_NECESSARY; |
| priv.Privilege[0].Luid = luid_privilege; |
| priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; |
| |
| if (!PrivilegeCheck(token, &priv, &ret)) |
| return FALSE; |
| |
| return ret; |
| } |
| |
| /* |
| * Enable or disable particular privilege in specified access token. |
| * |
| * Note that it is not possible to disable privilege in access token with |
| * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check |
| * this case and incorrectly returns no error even when disabling failed. |
| * Rationale for this decision: Simplification of this function as WinAPI |
| * call AdjustTokenPrivileges() does not signal error in this case too. |
| */ |
| static BOOL |
| set_privilege(HANDLE token, LUID luid_privilege, BOOL enable) |
| { |
| TOKEN_PRIVILEGES token_privileges; |
| |
| token_privileges.PrivilegeCount = 1; |
| token_privileges.Privileges[0].Luid = luid_privilege; |
| token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; |
| |
| /* |
| * WinAPI function AdjustTokenPrivileges() success also when not all |
| * privileges were enabled. It is always required to check for failure |
| * via GetLastError() call. AdjustTokenPrivileges() always sets error |
| * also when it success, as opposite to other WinAPI functions. |
| */ |
| if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) || |
| GetLastError() != ERROR_SUCCESS) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* |
| * Change access token for the current thread to new specified access token. |
| * Previously active access token is stored in old_token variable and can be |
| * used for reverting to this access token. It is set to NULL if the current |
| * thread previously used primary process access token. |
| */ |
| BOOL |
| win32_change_token(HANDLE new_token, HANDLE *old_token) |
| { |
| HANDLE token; |
| |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) |
| { |
| if (GetLastError() != ERROR_NO_TOKEN) |
| return FALSE; |
| token = NULL; |
| } |
| |
| if (!ImpersonateLoggedOnUser(new_token)) |
| { |
| if (token) |
| CloseHandle(token); |
| return FALSE; |
| } |
| |
| *old_token = token; |
| return TRUE; |
| } |
| |
| /* |
| * Change access token for the current thread to the primary process access |
| * token. This function fails also when the current thread already uses primary |
| * process access token. |
| */ |
| static BOOL |
| change_token_to_primary(HANDLE *old_token) |
| { |
| HANDLE token; |
| |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) |
| return FALSE; |
| |
| RevertToSelf(); |
| |
| *old_token = token; |
| return TRUE; |
| } |
| |
| /* |
| * Revert to the specified access token for the current thread. When access |
| * token is specified as NULL then revert to the primary process access token. |
| * Use to revert after win32_change_token() or change_token_to_primary() call. |
| */ |
| VOID |
| win32_revert_to_token(HANDLE token) |
| { |
| /* |
| * If SetThreadToken() call fails then there is no option to revert to |
| * the specified previous thread access token. So in this case revert to |
| * the primary process access token. |
| */ |
| if (!token || !SetThreadToken(NULL, token)) |
| RevertToSelf(); |
| if (token) |
| CloseHandle(token); |
| } |
| |
| /* |
| * Enable particular privilege for the current thread. And set method how to |
| * revert this privilege (if to revert whole token or only privilege). |
| */ |
| BOOL |
| win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege) |
| { |
| HANDLE thread_token; |
| HANDLE new_token; |
| |
| if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token)) |
| { |
| if (set_privilege(thread_token, luid_privilege, TRUE)) |
| { |
| /* |
| * Indicate that correct revert method is just to |
| * disable privilege in access token. |
| */ |
| if (revert_token && revert_only_privilege) |
| { |
| *revert_token = thread_token; |
| *revert_only_privilege = TRUE; |
| } |
| else |
| { |
| CloseHandle(thread_token); |
| } |
| return TRUE; |
| } |
| CloseHandle(thread_token); |
| /* |
| * If enabling privilege failed then try to enable it via |
| * primary process access token. |
| */ |
| } |
| |
| /* |
| * If the current thread has already active thread access token then |
| * open it with just impersonate right as it would be used only for |
| * future revert. |
| */ |
| if (revert_token && revert_only_privilege) |
| { |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token)) |
| { |
| if (GetLastError() != ERROR_NO_TOKEN) |
| return FALSE; |
| thread_token = NULL; |
| } |
| |
| /* |
| * If current thread has no access token (and uses primary |
| * process access token) or it does not have permission to |
| * adjust privileges or it does not have specified privilege |
| * then create a copy of the primary process access token, |
| * assign it for the current thread (= impersonate self) |
| * and then try adjusting privilege again. |
| */ |
| if (!ImpersonateSelf(SecurityImpersonation)) |
| { |
| if (thread_token) |
| CloseHandle(thread_token); |
| return FALSE; |
| } |
| } |
| |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token)) |
| { |
| /* thread_token is set only when we were asked for revert method. */ |
| if (revert_token && revert_only_privilege) |
| win32_revert_to_token(thread_token); |
| return FALSE; |
| } |
| |
| if (!set_privilege(new_token, luid_privilege, TRUE)) |
| { |
| CloseHandle(new_token); |
| /* thread_token is set only when we were asked for revert method. */ |
| if (revert_token && revert_only_privilege) |
| win32_revert_to_token(thread_token); |
| return FALSE; |
| } |
| |
| /* |
| * Indicate that correct revert method is to change to the previous |
| * access token. Either to the primary process access token or to the |
| * previous thread access token. |
| */ |
| if (revert_token && revert_only_privilege) |
| { |
| *revert_token = thread_token; |
| *revert_only_privilege = FALSE; |
| } |
| return TRUE; |
| } |
| |
| /* |
| * Revert particular privilege for the current thread was previously enabled by |
| * win32_enable_privilege() call. Either disable privilege in specified access token |
| * or revert to previous access token. |
| */ |
| VOID |
| win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege) |
| { |
| if (revert_only_privilege) |
| { |
| set_privilege(revert_token, luid_privilege, FALSE); |
| CloseHandle(revert_token); |
| } |
| else |
| { |
| win32_revert_to_token(revert_token); |
| } |
| } |
| |
| /* |
| * Return owner of the access token used by the current thread. Buffer for |
| * returned owner needs to be released by LocalFree() call. |
| */ |
| static TOKEN_OWNER * |
| get_current_token_owner(VOID) |
| { |
| HANDLE token; |
| DWORD length; |
| TOKEN_OWNER *owner; |
| |
| /* |
| * If the current thread does not have active access token then thread |
| * uses primary process access token for all permission checks. |
| */ |
| if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && |
| (GetLastError() != ERROR_NO_TOKEN || |
| !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) |
| return NULL; |
| |
| if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) && |
| GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
| { |
| CloseHandle(token); |
| return NULL; |
| } |
| |
| retry: |
| owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length); |
| if (!owner) |
| { |
| CloseHandle(token); |
| return NULL; |
| } |
| |
| if (!GetTokenInformation(token, TokenOwner, owner, length, &length)) |
| { |
| /* |
| * Length of token owner (SID) buffer between two get calls may |
| * changes (e.g. by another thread of process), so retry. |
| */ |
| if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) |
| { |
| LocalFree(owner); |
| goto retry; |
| } |
| LocalFree(owner); |
| CloseHandle(token); |
| return NULL; |
| } |
| |
| CloseHandle(token); |
| return owner; |
| } |
| |
| /* |
| * Create a new security descriptor in absolute form from relative form. |
| * Newly created security descriptor in absolute form is stored in linear buffer. |
| */ |
| static PSECURITY_DESCRIPTOR |
| create_relsd_from_abssd(PSECURITY_DESCRIPTOR rel_security_descriptor) |
| { |
| PBYTE abs_security_descriptor_buffer; |
| DWORD abs_security_descriptor_size=0, abs_dacl_size=0, abs_sacl_size=0, abs_owner_size=0, abs_primary_group_size=0; |
| |
| if (!MakeAbsoluteSD(rel_security_descriptor, |
| NULL, &abs_security_descriptor_size, |
| NULL, &abs_dacl_size, |
| NULL, &abs_sacl_size, |
| NULL, &abs_owner_size, |
| NULL, &abs_primary_group_size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
| return NULL; |
| |
| abs_security_descriptor_buffer = (PBYTE)LocalAlloc(LPTR, abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size+abs_primary_group_size); |
| if (!abs_security_descriptor_buffer) |
| return NULL; |
| |
| if (!MakeAbsoluteSD(rel_security_descriptor, |
| (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer, &abs_security_descriptor_size, |
| (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size), &abs_dacl_size, |
| (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size), &abs_sacl_size, |
| (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size), &abs_owner_size, |
| (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size), &abs_primary_group_size)) |
| return NULL; |
| |
| return (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer; |
| } |
| |
| /* |
| * Prepare security descriptor obtained by GetKernelObjectSecurity() so it can be |
| * passed to SetKernelObjectSecurity() as identity operation. It modifies control |
| * flags of security descriptor, which is needed for Windows 2000 and new. |
| */ |
| static BOOL |
| prepare_security_descriptor_for_set_operation(PSECURITY_DESCRIPTOR security_descriptor) |
| { |
| SetSecurityDescriptorControlProt MySetSecurityDescriptorControl; |
| SECURITY_DESCRIPTOR_CONTROL bits_mask; |
| SECURITY_DESCRIPTOR_CONTROL bits_set; |
| SECURITY_DESCRIPTOR_CONTROL control; |
| OSVERSIONINFO version; |
| HMODULE advapi32; |
| DWORD revision; |
| |
| /* |
| * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are flags introduced in |
| * Windows 2000 to control client-side automatic inheritance (client - user |
| * process - is responsible for propagating inherited ACEs to subobjects). |
| * To prevent applications which do not understand client-side automatic |
| * inheritance (applications created prior Windows 2000 or which use low |
| * level API like SetKernelObjectSecurity()) to unintentionally set those |
| * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED control flags when |
| * coping them from other security descriptor. |
| * |
| * As we are not modifying existing ACEs, we are compatible with Windows 2000 |
| * client-side automatic inheritance model and therefore prepare security |
| * descriptor for SetKernelObjectSecurity() to not clear existing automatic |
| * inheritance control flags. |
| * |
| * Control flags SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are set |
| * into security object only when they are set together with set-only flags |
| * SE_DACL_AUTO_INHERIT_REQ and SE_SACL_AUTO_INHERIT_REQ. Those flags are |
| * never received by GetKernelObjectSecurity() and are just commands for |
| * SetKernelObjectSecurity() how to interpret SE_DACL_AUTO_INHERITED and |
| * SE_SACL_AUTO_INHERITED flags. |
| * |
| * Function symbol SetSecurityDescriptorControl is not available in the |
| * older versions of advapi32.dll library, so resolve it at runtime. |
| */ |
| |
| version.dwOSVersionInfoSize = sizeof(version); |
| if (!GetVersionEx(&version) || |
| version.dwPlatformId != VER_PLATFORM_WIN32_NT || |
| version.dwMajorVersion < 5) |
| return TRUE; |
| |
| if (!GetSecurityDescriptorControl(security_descriptor, &control, &revision)) |
| return FALSE; |
| |
| bits_mask = 0; |
| bits_set = 0; |
| |
| if (control & SE_DACL_AUTO_INHERITED) |
| { |
| bits_mask |= SE_DACL_AUTO_INHERIT_REQ; |
| bits_set |= SE_DACL_AUTO_INHERIT_REQ; |
| } |
| |
| if (control & SE_SACL_AUTO_INHERITED) |
| { |
| bits_mask |= SE_SACL_AUTO_INHERIT_REQ; |
| bits_set |= SE_SACL_AUTO_INHERIT_REQ; |
| } |
| |
| if (!bits_mask) |
| return TRUE; |
| |
| advapi32 = GetModuleHandle(TEXT("advapi32.dll")); |
| if (!advapi32) |
| return FALSE; |
| |
| MySetSecurityDescriptorControl = (SetSecurityDescriptorControlProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityDescriptorControl"); |
| if (!MySetSecurityDescriptorControl) |
| return FALSE; |
| |
| if (!MySetSecurityDescriptorControl(security_descriptor, bits_mask, bits_set)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* |
| * Grant particular permissions in the primary access token of the specified |
| * process for the owner of current thread token and set old DACL of the |
| * process access token for reverting permissions. Security descriptor is |
| * just memory buffer for old DACL. |
| */ |
| static BOOL |
| grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PSECURITY_DESCRIPTOR *old_security_descriptor) |
| { |
| TOKEN_OWNER *owner; |
| PACL old_dacl; |
| BOOL old_dacl_present; |
| BOOL old_dacl_defaulted; |
| PACL new_dacl; |
| WORD new_dacl_size; |
| PSECURITY_DESCRIPTOR new_security_descriptor; |
| DWORD length; |
| |
| owner = get_current_token_owner(); |
| if (!owner) |
| return FALSE; |
| |
| /* |
| * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION) |
| * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION). |
| */ |
| if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token)) |
| { |
| LocalFree(owner); |
| return FALSE; |
| } |
| |
| if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, NULL, 0, &length) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) |
| { |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| retry: |
| *old_security_descriptor = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, length); |
| if (!*old_security_descriptor) |
| { |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, *old_security_descriptor, length, &length)) |
| { |
| /* |
| * Length of the security descriptor between two get calls |
| * may changes (e.g. by another thread of process), so retry. |
| */ |
| if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) |
| { |
| LocalFree(*old_security_descriptor); |
| goto retry; |
| } |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| if (!prepare_security_descriptor_for_set_operation(*old_security_descriptor)) |
| { |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| /* Retrieve the current DACL from security descriptor including present and defaulted properties. */ |
| if (!GetSecurityDescriptorDacl(*old_security_descriptor, &old_dacl_present, &old_dacl, &old_dacl_defaulted)) |
| { |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| /* |
| * If DACL is not present then system grants full access to everyone. It this |
| * case do not modify DACL as it just adds one ACL allow rule for us, which |
| * automatically disallow access to anybody else which had access before. |
| */ |
| if (!old_dacl_present || !old_dacl) |
| { |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| *old_security_descriptor = NULL; |
| return TRUE; |
| } |
| |
| /* Create new DACL which would be copy of the current old one. */ |
| new_dacl_size = old_dacl->AclSize + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(owner->Owner) - sizeof(DWORD); |
| new_dacl = (PACL)LocalAlloc(LPTR, new_dacl_size); |
| if (!new_dacl) |
| { |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| /* |
| * Initialize new DACL structure to the same format as was the old one. |
| * Set new explicit access for the owner of the current thread access |
| * token with non-inherited granting access to specified permissions. |
| * This permission is added in the first ACE, so has the highest priority. |
| */ |
| if (!InitializeAcl(new_dacl, new_dacl_size, old_dacl->AclRevision) || |
| !AddAccessAllowedAce(new_dacl, ACL_REVISION2, permissions, owner->Owner)) |
| { |
| LocalFree(new_dacl); |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| /* |
| * Now (after setting our new permissions) append all ACE entries from the |
| * old DACL to the new DACL, which preserve all other existing permissions. |
| */ |
| if (old_dacl->AceCount > 0) |
| { |
| WORD ace_index; |
| LPVOID ace; |
| |
| for (ace_index = 0; ace_index < old_dacl->AceCount; ace_index++) |
| { |
| if (!GetAce(old_dacl, ace_index, &ace) || |
| !AddAce(new_dacl, old_dacl->AclRevision, MAXDWORD, ace, ((PACE_HEADER)ace)->AceSize)) |
| { |
| LocalFree(new_dacl); |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| } |
| } |
| |
| /* |
| * Create copy of the old security descriptor, so we can modify its DACL. |
| * Function SetSecurityDescriptorDacl() works only with security descriptors |
| * in absolute format. So use our helper function create_relsd_from_abssd() |
| * for converting security descriptor from relative format (which is returned |
| * by GetKernelObjectSecurity() function) to the absolute format. |
| */ |
| new_security_descriptor = create_relsd_from_abssd(*old_security_descriptor); |
| if (!new_security_descriptor) |
| { |
| LocalFree(new_dacl); |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| /* |
| * In the new security descriptor replace old DACL by the new DACL (which has |
| * new permissions) and then set this new security descriptor to the token, |
| * so token would have new access permissions. |
| */ |
| if (!SetSecurityDescriptorDacl(new_security_descriptor, TRUE, new_dacl, FALSE) || |
| !SetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, new_security_descriptor)) |
| { |
| LocalFree(new_security_descriptor); |
| LocalFree(new_dacl); |
| LocalFree(*old_security_descriptor); |
| LocalFree(owner); |
| CloseHandle(*token); |
| return FALSE; |
| } |
| |
| LocalFree(new_security_descriptor); |
| LocalFree(new_dacl); |
| LocalFree(owner); |
| return TRUE; |
| } |
| |
| /* |
| * Revert particular granted permissions in specified access token done by |
| * grant_process_token_dacl_permissions() call. |
| */ |
| static VOID |
| revert_token_dacl_permissions(HANDLE token, PSECURITY_DESCRIPTOR old_security_descriptor) |
| { |
| SetKernelObjectSecurity(token, DACL_SECURITY_INFORMATION, old_security_descriptor); |
| LocalFree(old_security_descriptor); |
| CloseHandle(token); |
| } |
| |
| /* |
| * Open process handle specified by the process id with the query right and |
| * optionally also with vm read right. |
| */ |
| static HANDLE |
| open_process_for_query(DWORD pid, BOOL with_vm_read) |
| { |
| BOOL revert_only_privilege; |
| LUID luid_debug_privilege; |
| OSVERSIONINFO version; |
| DWORD process_right; |
| HANDLE revert_token; |
| HANDLE process; |
| |
| /* |
| * Some processes on Windows Vista and higher systems can be opened only |
| * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough |
| * for accessing primary process token. But this right is not supported |
| * on older pre-Vista systems. When the current thread on these older |
| * systems does not have Debug privilege then OpenProcess() fails with |
| * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then |
| * OpenProcess() success and returns handle to requested process. |
| * Problem is that this handle does not have PROCESS_QUERY_INFORMATION |
| * right and so cannot be used for accessing primary process token |
| * on those older systems. Moreover it has zero rights and therefore |
| * such handle is fully useless. So never try to use open process with |
| * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than |
| * Windows Vista (NT 6.0). |
| */ |
| version.dwOSVersionInfoSize = sizeof(version); |
| if (GetVersionEx(&version) && |
| version.dwPlatformId == VER_PLATFORM_WIN32_NT && |
| version.dwMajorVersion >= 6) |
| process_right = PROCESS_QUERY_LIMITED_INFORMATION; |
| 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; |
| |
| /* |
| * It is possible to open only processes to which owner of the current |
| * thread access token has permissions. For opening other processing it |
| * is required to have Debug privilege enabled. By default local |
| * administrators have this privilege, but it is disabled. So try to |
| * enable it and then try to open process again. |
| */ |
| |
| if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) |
| return NULL; |
| |
| if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) |
| return NULL; |
| |
| process = OpenProcess(process_right, FALSE, pid); |
| |
| win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); |
| |
| return process; |
| } |
| |
| /* |
| * 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, ...). |
| */ |
| static BOOL |
| check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) |
| { |
| DWORD exe_file_length; |
| 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'\\') |
| return FALSE; |
| |
| for (i = 0; i < exe_file_length; i++) |
| { |
| c1 = path[path_length-exe_file_length+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) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Open process handle with the query right specified by process 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; |
| HANDLE process; |
| LPWSTR path; |
| DWORD error; |
| DWORD count; |
| DWORD i; |
| |
| psapi = NULL; |
| kernel32 = GetModuleHandle(TEXT("kernel32.dll")); |
| if (!kernel32) |
| return NULL; |
| |
| /* |
| * On Windows 7 and higher systems these functions are available in |
| * kernel32.dll library with K32 prefix. |
| */ |
| MyGetModuleFileNameExW = NULL; |
| MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW"); |
| MyEnumProcesses = (EnumProcessesProt)(LPVOID)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); |
| psapi = LoadLibrary(TEXT("psapi.dll")); |
| win32_change_error_mode(prev_error_mode); |
| |
| if (!psapi) |
| return NULL; |
| |
| /* |
| * Function GetProcessImageFileNameW() is available in |
| * Windows XP and higher systems. On older versions is |
| * available function GetModuleFileNameExW(). |
| */ |
| MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW"); |
| MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW"); |
| MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses"); |
| if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses) |
| { |
| FreeLibrary(psapi); |
| return NULL; |
| } |
| } |
| |
| /* Make initial buffer size for 1024 processes. */ |
| size = 1024 * sizeof(*processes); |
| |
| retry: |
| processes = (DWORD *)LocalAlloc(LPTR, size); |
| if (!processes) |
| { |
| if (psapi) |
| FreeLibrary(psapi); |
| return NULL; |
| } |
| |
| if (!MyEnumProcesses(processes, size, &length)) |
| { |
| LocalFree(processes); |
| if (psapi) |
| FreeLibrary(psapi); |
| 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++) |
| { |
| /* 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. |
| */ |
| 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))) |
| { |
| 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; |
| } |
| |
| if (found_process) |
| break; |
| |
| CloseHandle(process); |
| process = NULL; |
| } |
| |
| LocalFree(processes); |
| |
| if (psapi) |
| FreeLibrary(psapi); |
| |
| return process; |
| } |
| |
| /* |
| * Try to open primary access token of the particular process with specified |
| * rights. Before opening access token try to adjust DACL permissions of the |
| * primary process access token, so following open does not fail on error |
| * related to no open permissions. Revert DACL permissions after open attempt. |
| * As following steps are not atomic, try to execute them more times in case |
| * of possible race conditions caused by other threads or processes. |
| */ |
| static HANDLE |
| try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights) |
| { |
| PSECURITY_DESCRIPTOR old_security_descriptor; |
| HANDLE grant_token; |
| HANDLE token; |
| DWORD retry; |
| DWORD error; |
| |
| /* |
| * This code is not atomic. Between grant and open calls can other |
| * thread or process change or revert permissions. So try to execute |
| * it more times. |
| */ |
| for (retry = 0; retry < 10; retry++) |
| { |
| if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_security_descriptor)) |
| return NULL; |
| if (!OpenProcessToken(process, rights, &token)) |
| { |
| token = NULL; |
| error = GetLastError(); |
| } |
| if (old_security_descriptor) |
| revert_token_dacl_permissions(grant_token, old_security_descriptor); |
| if (token) |
| return token; |
| else if (error != ERROR_ACCESS_DENIED) |
| return NULL; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Open primary access token of particular process handle with specified rights. |
| * If permissions for specified rights are missing then try to grant them. |
| */ |
| HANDLE |
| win32_open_process_token_with_rights(HANDLE process, DWORD rights) |
| { |
| HANDLE old_token; |
| HANDLE token; |
| |
| /* First try to open primary access token of process handle directly. */ |
| if (OpenProcessToken(process, rights, &token)) |
| return token; |
| |
| /* |
| * If opening failed then it means that owner of the current thread |
| * access token does not have permission for it. Try it again with |
| * primary process access token. |
| */ |
| if (change_token_to_primary(&old_token)) |
| { |
| if (!OpenProcessToken(process, rights, &token)) |
| token = NULL; |
| win32_revert_to_token(old_token); |
| if (token) |
| return token; |
| } |
| |
| /* |
| * If opening is still failing then try to grant specified permissions |
| * for the current thread and try to open it again. |
| */ |
| token = try_grant_permissions_and_open_process_token(process, rights); |
| if (token) |
| return token; |
| |
| /* |
| * And if it is still failing then try it again with granting |
| * permissions for the primary process token of the current process. |
| */ |
| if (change_token_to_primary(&old_token)) |
| { |
| token = try_grant_permissions_and_open_process_token(process, rights); |
| win32_revert_to_token(old_token); |
| if (token) |
| return token; |
| } |
| |
| /* |
| * TODO: Sorry, no other option for now... |
| * It could be possible to use Take Ownership Name privilege to |
| * temporary change token owner of specified process to the owner of |
| * the current thread token, grant permissions for current thread in |
| * that process token, change ownership back to original one, open |
| * that process token and revert granted permissions. But this is |
| * not implemented yet. |
| */ |
| return NULL; |
| } |
| |
| /* |
| * Call supplied function with its argument and if it fails with |
| * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and |
| * call function with its argument again. |
| */ |
| BOOL |
| win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument) |
| { |
| LUID luid_tcb_privilege; |
| LUID luid_impersonate_privilege; |
| |
| HANDLE revert_token_tcb_privilege; |
| BOOL revert_only_tcb_privilege; |
| |
| HANDLE revert_token_impersonate_privilege; |
| BOOL revert_only_impersonate_privilege; |
| |
| BOOL impersonate_privilege_enabled; |
| |
| BOOL revert_to_old_token; |
| HANDLE old_token; |
| |
| HANDLE lsass_process; |
| HANDLE lsass_token; |
| |
| DWORD error; |
| BOOL ret; |
| |
| impersonate_privilege_enabled = FALSE; |
| revert_to_old_token = FALSE; |
| lsass_token = NULL; |
| old_token = NULL; |
| |
| /* Call supplied function. */ |
| ret = function(argument); |
| if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) |
| goto ret; |
| |
| /* |
| * If function call failed with ERROR_PRIVILEGE_NOT_HELD |
| * error then it means that the current thread token does not have |
| * Tcb privilege enabled. Try to enable it. |
| */ |
| |
| if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege)) |
| goto err_privilege_not_held; |
| |
| /* |
| * If the current thread has already Tcb privilege enabled then there |
| * is some additional unhanded restriction. |
| */ |
| if (win32_have_privilege(luid_tcb_privilege)) |
| goto err_privilege_not_held; |
| |
| /* Try to enable Tcb privilege and try function call again. */ |
| if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) |
| { |
| ret = function(argument); |
| win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); |
| goto ret; |
| } |
| |
| /* |
| * If enabling of Tcb privilege failed then it means that current thread |
| * does not have this privilege. But current process may have it. So try it |
| * again with primary process access token. |
| */ |
| |
| /* |
| * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then |
| * all future actions in this function require this Impersonate privilege. |
| * So try to enable it in case it is currently disabled. |
| */ |
| if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) && |
| !win32_have_privilege(luid_impersonate_privilege)) |
| { |
| /* |
| * If current thread does not have Impersonate privilege enabled |
| * then first try to enable it just for the current thread. If |
| * it is not possible to enable it just for the current thread |
| * then try it to enable globally for whole process (which |
| * affects all process threads). Both actions will be reverted |
| * at the end of this function. |
| */ |
| if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) |
| { |
| impersonate_privilege_enabled = TRUE; |
| } |
| else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL)) |
| { |
| impersonate_privilege_enabled = TRUE; |
| revert_token_impersonate_privilege = NULL; |
| revert_only_impersonate_privilege = TRUE; |
| } |
| else |
| { |
| goto err_privilege_not_held; |
| } |
| |
| /* |
| * Now when Impersonate privilege is enabled, try to enable Tcb |
| * privilege again. Enabling other privileges for the current |
| * thread requires Impersonate privilege, so enabling Tcb again |
| * could now pass. |
| */ |
| if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) |
| { |
| ret = function(argument); |
| win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); |
| goto ret; |
| } |
| } |
| |
| /* |
| * If enabling Tcb privilege failed then it means that the current |
| * thread access token does not have this privilege or does not |
| * have permission to adjust privileges. |
| * |
| * Try to use more privileged token from Local Security Authority |
| * Subsystem Service process (lsass.exe) which has Tcb privilege. |
| * Retrieving this more privileged token is possible for local |
| * administrators (unless it was disabled by local administrators). |
| */ |
| |
| lsass_process = win32_find_and_open_process_for_query("lsass.exe"); |
| if (!lsass_process) |
| goto err_privilege_not_held; |
| |
| /* |
| * Open primary lsass.exe process access token with query and duplicate |
| * rights. Just these two rights are required for impersonating other |
| * primary process token (impersonate right is really not required!). |
| */ |
| lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); |
| |
| CloseHandle(lsass_process); |
| |
| if (!lsass_token) |
| goto err_privilege_not_held; |
| |
| /* |
| * After successful open of the primary lsass.exe process access token, |
| * assign its copy for the current thread. |
| */ |
| if (!win32_change_token(lsass_token, &old_token)) |
| goto err_privilege_not_held; |
| |
| revert_to_old_token = TRUE; |
| |
| ret = function(argument); |
| if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) |
| goto ret; |
| |
| /* |
| * Now current thread is not using primary process token anymore |
| * but is using custom access token. There is no need to revert |
| * enabled Tcb privilege as the whole custom access token would |
| * be reverted. So there is no need to setup revert method for |
| * enabling privilege. |
| */ |
| if (win32_have_privilege(luid_tcb_privilege) || |
| !win32_enable_privilege(luid_tcb_privilege, NULL, NULL)) |
| goto err_privilege_not_held; |
| |
| ret = function(argument); |
| goto ret; |
| |
| err_privilege_not_held: |
| SetLastError(ERROR_PRIVILEGE_NOT_HELD); |
| ret = FALSE; |
| goto ret; |
| |
| ret: |
| error = GetLastError(); |
| |
| if (revert_to_old_token) |
| win32_revert_to_token(old_token); |
| |
| if (impersonate_privilege_enabled) |
| win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); |
| |
| if (lsass_token) |
| CloseHandle(lsass_token); |
| |
| SetLastError(error); |
| |
| return ret; |
| } |