]> mj.ucw.cz Git - pciutils.git/blob - lib/win32-helpers.c
windows: Move win32_call_func_with_tcb_privilege() from i386-io-windows.h to win32...
[pciutils.git] / lib / win32-helpers.c
1 /*
2  *      The PCI Library -- Win32 helper functions
3  *
4  *      Copyright (c) 2023 Pali Rohár <pali@kernel.org>
5  *
6  *      Can be freely distributed and used under the terms of the GNU GPL v2+
7  *
8  *      SPDX-License-Identifier: GPL-2.0-or-later
9  */
10
11 #include <windows.h>
12 #include <aclapi.h>
13 #include <stdio.h> /* for sprintf() */
14
15 #include "win32-helpers.h"
16
17 /* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */
18 #ifndef PROCESS_QUERY_LIMITED_INFORMATION
19 #define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
20 #endif
21
22 /* Unfortunately some toolchains do not provide this constant. */
23 #ifndef SE_IMPERSONATE_NAME
24 #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
25 #endif
26
27 /*
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
33  * older systems.
34  */
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);
38
39 /*
40  * These aclapi functions are available in advapi.dll library on Windows NT 4.0
41  * and higher systems.
42  */
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);
46
47 /*
48  * This errhandlingapi function is available in kernel32.dll library on
49  * Windows 7 and higher systems.
50  */
51 typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode);
52
53
54 const char *
55 win32_strerror(DWORD win32_error_id)
56 {
57   /*
58    * Use static buffer which is large enough.
59    * Hopefully no Win32 API error message string is longer than 4 kB.
60    */
61   static char buffer[4096];
62   DWORD len;
63
64   len = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL);
65
66   /* FormatMessage() automatically appends ".\r\n" to the error message. */
67   if (len && buffer[len-1] == '\n')
68     buffer[--len] = '\0';
69   if (len && buffer[len-1] == '\r')
70     buffer[--len] = '\0';
71   if (len && buffer[len-1] == '.')
72     buffer[--len] = '\0';
73
74   if (!len)
75     sprintf(buffer, "Unknown Win32 error %lu", win32_error_id);
76
77   return buffer;
78 }
79
80 BOOL
81 win32_is_non_nt_system(void)
82 {
83   OSVERSIONINFOA version;
84   version.dwOSVersionInfoSize = sizeof(version);
85   return GetVersionExA(&version) && version.dwPlatformId < VER_PLATFORM_WIN32_NT;
86 }
87
88 BOOL
89 win32_is_32bit_on_64bit_system(void)
90 {
91   BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL);
92   HMODULE kernel32;
93   BOOL is_wow64;
94
95   /*
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
100    * functions.
101    */
102
103   kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
104   if (!kernel32)
105     return FALSE;
106
107   MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process");
108   if (!MyIsWow64Process)
109     return FALSE;
110
111   if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64))
112     return FALSE;
113
114   return is_wow64;
115 }
116
117 BOOL
118 win32_is_32bit_on_win8_64bit_system(void)
119 {
120 #ifdef _WIN64
121   return FALSE;
122 #else
123   OSVERSIONINFOA version;
124
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))
131     return FALSE;
132
133   return win32_is_32bit_on_64bit_system();
134 #endif
135 }
136
137 /*
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.
140  */
141 UINT
142 win32_change_error_mode(UINT new_mode)
143 {
144   SetThreadErrorModeProt MySetThreadErrorMode = NULL;
145   HMODULE kernel32;
146   DWORD old_mode;
147
148   /*
149    * Function SetThreadErrorMode() was introduced in Windows 7, so use
150    * GetProcAddress() for compatibility with older systems.
151    */
152   kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
153   if (kernel32)
154     MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode");
155
156   if (MySetThreadErrorMode &&
157       MySetThreadErrorMode(new_mode, &old_mode))
158     return old_mode;
159
160   /*
161    * Fallback to function SetErrorMode() which modifies error mode of the
162    * whole process and returns old mode.
163    */
164   return SetErrorMode(new_mode);
165 }
166
167 /*
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.
172  */
173 BOOL
174 win32_have_privilege(LUID luid_privilege)
175 {
176   PRIVILEGE_SET priv;
177   HANDLE token;
178   BOOL ret;
179
180   /*
181    * If the current thread does not have active access token then thread
182    * uses primary process access token for all permission checks.
183    */
184   if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
185       (GetLastError() != ERROR_NO_TOKEN ||
186        !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
187     return FALSE;
188
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;
193
194   if (!PrivilegeCheck(token, &priv, &ret))
195     return FALSE;
196
197   return ret;
198 }
199
200 /*
201  * Enable or disable particular privilege in specified access token.
202  *
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.
208  */
209 static BOOL
210 set_privilege(HANDLE token, LUID luid_privilege, BOOL enable)
211 {
212   TOKEN_PRIVILEGES token_privileges;
213
214   token_privileges.PrivilegeCount = 1;
215   token_privileges.Privileges[0].Luid = luid_privilege;
216   token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
217
218   /*
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.
223    */
224   if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) ||
225       GetLastError() != ERROR_SUCCESS)
226     return FALSE;
227
228   return TRUE;
229 }
230
231 /*
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.
236  */
237 BOOL
238 win32_change_token(HANDLE new_token, HANDLE *old_token)
239 {
240   HANDLE token;
241
242   if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
243     {
244       if (GetLastError() != ERROR_NO_TOKEN)
245         return FALSE;
246       token = NULL;
247     }
248
249   if (!ImpersonateLoggedOnUser(new_token))
250     {
251       if (token)
252         CloseHandle(token);
253       return FALSE;
254     }
255
256   *old_token = token;
257   return TRUE;
258 }
259
260 /*
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.
264  */
265 static BOOL
266 change_token_to_primary(HANDLE *old_token)
267 {
268   HANDLE token;
269
270   if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
271     return FALSE;
272
273   RevertToSelf();
274
275   *old_token = token;
276   return TRUE;
277 }
278
279 /*
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.
283  */
284 VOID
285 win32_revert_to_token(HANDLE token)
286 {
287   /*
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.
291    */
292   if (!token || !SetThreadToken(NULL, token))
293     RevertToSelf();
294   if (token)
295     CloseHandle(token);
296 }
297
298 /*
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).
301  */
302 BOOL
303 win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege)
304 {
305   HANDLE thread_token;
306   HANDLE new_token;
307
308   if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token))
309     {
310       if (set_privilege(thread_token, luid_privilege, TRUE))
311         {
312           /*
313            * Indicate that correct revert method is just to
314            * disable privilege in access token.
315            */
316           if (revert_token && revert_only_privilege)
317             {
318               *revert_token = thread_token;
319               *revert_only_privilege = TRUE;
320             }
321           else
322             {
323               CloseHandle(thread_token);
324             }
325           return TRUE;
326         }
327       CloseHandle(thread_token);
328       /*
329        * If enabling privilege failed then try to enable it via
330        * primary process access token.
331        */
332     }
333
334   /*
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
337    * future revert.
338    */
339   if (revert_token && revert_only_privilege)
340     {
341       if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token))
342         {
343           if (GetLastError() != ERROR_NO_TOKEN)
344             return FALSE;
345           thread_token = NULL;
346         }
347
348       /*
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.
355        */
356       if (!ImpersonateSelf(SecurityImpersonation))
357         {
358           if (thread_token)
359             CloseHandle(thread_token);
360           return FALSE;
361         }
362     }
363
364   if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token))
365     {
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);
369       return FALSE;
370     }
371
372   if (!set_privilege(new_token, luid_privilege, TRUE))
373     {
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);
378       return FALSE;
379     }
380
381   /*
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.
385    */
386   if (revert_token && revert_only_privilege)
387     {
388       *revert_token = thread_token;
389       *revert_only_privilege = FALSE;
390     }
391   return TRUE;
392 }
393
394 /*
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.
398  */
399 VOID
400 win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege)
401 {
402   if (revert_only_privilege)
403     {
404       set_privilege(revert_token, luid_privilege, FALSE);
405       CloseHandle(revert_token);
406     }
407   else
408     {
409       win32_revert_to_token(revert_token);
410     }
411 }
412
413 /*
414  * Return owner of the access token used by the current thread. Buffer for
415  * returned owner needs to be released by LocalFree() call.
416  */
417 static TOKEN_OWNER *
418 get_current_token_owner(VOID)
419 {
420   HANDLE token;
421   DWORD length;
422   TOKEN_OWNER *owner;
423
424   /*
425    * If the current thread does not have active access token then thread
426    * uses primary process access token for all permission checks.
427    */
428   if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
429       (GetLastError() != ERROR_NO_TOKEN ||
430        !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
431     return NULL;
432
433   if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) &&
434       GetLastError() != ERROR_INSUFFICIENT_BUFFER)
435     {
436       CloseHandle(token);
437       return NULL;
438     }
439
440 retry:
441   owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length);
442   if (!owner)
443     {
444       CloseHandle(token);
445       return NULL;
446     }
447
448   if (!GetTokenInformation(token, TokenOwner, owner, length, &length))
449     {
450       /*
451        * Length of token owner (SID) buffer between two get calls may
452        * changes (e.g. by another thread of process), so retry.
453        */
454       if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
455         {
456           LocalFree(owner);
457           goto retry;
458         }
459       LocalFree(owner);
460       CloseHandle(token);
461       return NULL;
462     }
463
464   CloseHandle(token);
465   return owner;
466 }
467
468 /*
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.
473  */
474 static BOOL
475 grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PACL *old_dacl, PSECURITY_DESCRIPTOR *security_descriptor)
476 {
477   GetSecurityInfoProt MyGetSecurityInfo;
478   SetSecurityInfoProt MySetSecurityInfo;
479   SetEntriesInAclProt MySetEntriesInAcl;
480   EXPLICIT_ACCESS explicit_access;
481   TOKEN_OWNER *owner;
482   HMODULE advapi32;
483   PACL new_dacl;
484
485   /*
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.
489    */
490   advapi32 = GetModuleHandle(TEXT("advapi32.dll"));
491   if (!advapi32)
492     return FALSE;
493
494   /*
495    * It does not matter if SetEntriesInAclA() or SetEntriesInAclW() is
496    * called as no string is passed to SetEntriesInAcl function.
497    */
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)
502     return FALSE;
503
504   owner = get_current_token_owner();
505   if (!owner)
506     return FALSE;
507
508   /*
509    * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION)
510    * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION).
511    */
512   if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token))
513     {
514       LocalFree(owner);
515       return FALSE;
516     }
517
518   if (MyGetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL, security_descriptor) != ERROR_SUCCESS)
519     {
520       LocalFree(owner);
521       CloseHandle(*token);
522       return FALSE;
523     }
524
525   /*
526    * Set new explicit access for the owner of the current thread access
527    * token with non-inherited granting access to specified permissions.
528    */
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;
536   /*
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.
540    */
541   explicit_access.Trustee.ptstrName = (PVOID)owner->Owner;
542
543   if (MySetEntriesInAcl(1, &explicit_access, *old_dacl, &new_dacl) != ERROR_SUCCESS)
544     {
545       LocalFree(*security_descriptor);
546       LocalFree(owner);
547       CloseHandle(*token);
548       return FALSE;
549     }
550
551   if (MySetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS)
552     {
553       LocalFree(new_dacl);
554       LocalFree(*security_descriptor);
555       LocalFree(owner);
556       CloseHandle(*token);
557       return FALSE;
558     }
559
560   LocalFree(new_dacl);
561   LocalFree(owner);
562   return TRUE;
563 }
564
565 /*
566  * Revert particular granted permissions in specified access token done by
567  * grant_process_token_dacl_permissions() call.
568  */
569 static VOID
570 revert_token_dacl_permissions(HANDLE token, PACL old_dacl, PSECURITY_DESCRIPTOR security_descriptor)
571 {
572   SetSecurityInfoProt MySetSecurityInfo;
573   HMODULE advapi32;
574
575   /*
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.
579    */
580   advapi32 = GetModuleHandle(TEXT("advapi32.dll"));
581   if (advapi32)
582     {
583       MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo");
584       MySetSecurityInfo(token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL);
585     }
586
587   LocalFree(security_descriptor);
588   CloseHandle(token);
589 }
590
591 /*
592  * Open process handle specified by the process id with the query right and
593  * optionally also with vm read right.
594  */
595 static HANDLE
596 open_process_for_query(DWORD pid, BOOL with_vm_read)
597 {
598   BOOL revert_only_privilege;
599   LUID luid_debug_privilege;
600   OSVERSIONINFO version;
601   DWORD process_right;
602   HANDLE revert_token;
603   HANDLE process;
604
605   /*
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).
619    */
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;
625   else
626     process_right = PROCESS_QUERY_INFORMATION;
627
628   if (with_vm_read)
629     process_right |= PROCESS_VM_READ;
630
631   process = OpenProcess(process_right, FALSE, pid);
632   if (process)
633     return process;
634
635   /*
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.
641    */
642
643   if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege))
644     return NULL;
645
646   if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege))
647     return NULL;
648
649   process = OpenProcess(process_right, FALSE, pid);
650
651   win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege);
652
653   return process;
654 }
655
656 /*
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, ...).
660  */
661 static BOOL
662 check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file)
663 {
664   DWORD exe_file_length;
665   WCHAR c1;
666   UCHAR c2;
667   DWORD i;
668
669   exe_file_length = 0;
670   while (exe_file[exe_file_length] != '\0')
671     exe_file_length++;
672
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'\\')
676     return FALSE;
677
678   for (i = 0; i < exe_file_length; i++)
679     {
680       c1 = path[path_length-exe_file_length+i];
681       c2 = exe_file[i];
682       /*
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.
685        */
686       if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\')
687         return FALSE;
688       if (c1 >= L'a' && c1 <= L'z')
689         c1 -= L'a' - L'A';
690       if (c2 >= 'a' && c2 <= 'z')
691         c2 -= 'a' - 'A';
692       if (c1 != c2)
693         return FALSE;
694     }
695
696   return TRUE;
697 }
698
699 /* Open process handle with the query right specified by process exe file. */
700 HANDLE
701 win32_find_and_open_process_for_query(LPCSTR exe_file)
702 {
703   GetProcessImageFileNameWProt MyGetProcessImageFileNameW;
704   GetModuleFileNameExWProt MyGetModuleFileNameExW;
705   EnumProcessesProt MyEnumProcesses;
706   HMODULE kernel32, psapi;
707   UINT prev_error_mode;
708   DWORD partial_retry;
709   BOOL found_process;
710   DWORD size, length;
711   DWORD *processes;
712   HANDLE process;
713   LPWSTR path;
714   DWORD error;
715   DWORD count;
716   DWORD i;
717
718   psapi = NULL;
719   kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
720   if (!kernel32)
721     return NULL;
722
723   /*
724    * On Windows 7 and higher systems these functions are available in
725    * kernel32.dll library with K32 prefix.
726    */
727   MyGetModuleFileNameExW = NULL;
728   MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW");
729   MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses");
730   if (!MyGetProcessImageFileNameW || !MyEnumProcesses)
731     {
732       /*
733        * On older NT-based systems these functions are available in
734        * psapi.dll library without K32 prefix.
735        */
736       prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
737       psapi = LoadLibrary(TEXT("psapi.dll"));
738       win32_change_error_mode(prev_error_mode);
739
740       if (!psapi)
741         return NULL;
742
743       /*
744        * Function GetProcessImageFileNameW() is available in
745        * Windows XP and higher systems. On older versions is
746        * available function GetModuleFileNameExW().
747        */
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)
752         {
753           FreeLibrary(psapi);
754           return NULL;
755         }
756     }
757
758   /* Make initial buffer size for 1024 processes. */
759   size = 1024 * sizeof(*processes);
760
761 retry:
762   processes = (DWORD *)LocalAlloc(LPTR, size);
763   if (!processes)
764     {
765       if (psapi)
766         FreeLibrary(psapi);
767       return NULL;
768     }
769
770   if (!MyEnumProcesses(processes, size, &length))
771     {
772       LocalFree(processes);
773       if (psapi)
774         FreeLibrary(psapi);
775       return NULL;
776     }
777   else if (size == length)
778     {
779       /*
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.
784        */
785       LocalFree(processes);
786       size *= 2;
787       goto retry;
788     }
789
790   process = NULL;
791   count = length / sizeof(*processes);
792
793   for (i = 0; i < count; i++)
794     {
795       /* Skip System Idle Process. */
796       if (processes[i] == 0)
797         continue;
798
799       /*
800        * Function GetModuleFileNameExW() requires additional
801        * PROCESS_VM_READ right as opposite to function
802        * GetProcessImageFileNameW() which does not need it.
803        */
804       process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE);
805       if (!process)
806         continue;
807
808       /*
809        * Set initial buffer size to 256 (wide) characters.
810        * Final path length on the modern NT-based systems can be also larger.
811        */
812       size = 256;
813       found_process = FALSE;
814       partial_retry = 0;
815
816 retry_path:
817       path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path));
818       if (!path)
819         goto end_path;
820
821       if (MyGetProcessImageFileNameW)
822         length = MyGetProcessImageFileNameW(process, path, size);
823       else
824         length = MyGetModuleFileNameExW(process, NULL, path, size);
825
826       error = GetLastError();
827
828       /*
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.
832        */
833       if (!MyGetProcessImageFileNameW)
834         {
835           if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10)
836             {
837               Sleep(10);
838               goto retry_path;
839             }
840           partial_retry = 0;
841         }
842
843       /*
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.
850        */
851
852       if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER)
853         goto end_path;
854
855       if ((MyGetProcessImageFileNameW && length == 0) ||
856           (!MyGetProcessImageFileNameW && (length == size || length == size-1)))
857         {
858           LocalFree(path);
859           size *= 2;
860           goto retry_path;
861         }
862
863       if (length && check_process_name(path, length, exe_file))
864         found_process = TRUE;
865
866 end_path:
867       if (path)
868         {
869           LocalFree(path);
870           path = NULL;
871         }
872
873       if (found_process)
874         break;
875
876       CloseHandle(process);
877       process = NULL;
878     }
879
880   LocalFree(processes);
881
882   if (psapi)
883     FreeLibrary(psapi);
884
885   return process;
886 }
887
888 /*
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.
895  */
896 static HANDLE
897 try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights)
898 {
899   PSECURITY_DESCRIPTOR security_descriptor;
900   HANDLE grant_token;
901   PACL old_dacl;
902   HANDLE token;
903   DWORD retry;
904   DWORD error;
905
906   /*
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
909    * it more times.
910    */
911   for (retry = 0; retry < 10; retry++)
912     {
913       if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_dacl, &security_descriptor))
914         return NULL;
915       if (!OpenProcessToken(process, rights, &token))
916         {
917           token = NULL;
918           error = GetLastError();
919         }
920       revert_token_dacl_permissions(grant_token, old_dacl, security_descriptor);
921       if (token)
922         return token;
923       else if (error != ERROR_ACCESS_DENIED)
924         return NULL;
925     }
926
927   return NULL;
928 }
929
930 /*
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.
933  */
934 HANDLE
935 win32_open_process_token_with_rights(HANDLE process, DWORD rights)
936 {
937   HANDLE old_token;
938   HANDLE token;
939
940   /* First try to open primary access token of process handle directly. */
941   if (OpenProcessToken(process, rights, &token))
942     return token;
943
944   /*
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.
948    */
949   if (change_token_to_primary(&old_token))
950     {
951       if (!OpenProcessToken(process, rights, &token))
952         token = NULL;
953       win32_revert_to_token(old_token);
954       if (token)
955         return token;
956     }
957
958   /*
959    * If opening is still failing then try to grant specified permissions
960    * for the current thread and try to open it again.
961    */
962   token = try_grant_permissions_and_open_process_token(process, rights);
963   if (token)
964     return token;
965
966   /*
967    * And if it is still failing then try it again with granting
968    * permissions for the primary process token of the current process.
969    */
970   if (change_token_to_primary(&old_token))
971     {
972       token = try_grant_permissions_and_open_process_token(process, rights);
973       win32_revert_to_token(old_token);
974       if (token)
975         return token;
976     }
977
978   /*
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.
986    */
987   return NULL;
988 }
989
990 /*
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.
994  */
995 BOOL
996 win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument)
997 {
998   LUID luid_tcb_privilege;
999   LUID luid_impersonate_privilege;
1000
1001   HANDLE revert_token_tcb_privilege;
1002   BOOL revert_only_tcb_privilege;
1003
1004   HANDLE revert_token_impersonate_privilege;
1005   BOOL revert_only_impersonate_privilege;
1006
1007   BOOL impersonate_privilege_enabled;
1008
1009   BOOL revert_to_old_token;
1010   HANDLE old_token;
1011
1012   HANDLE lsass_process;
1013   HANDLE lsass_token;
1014
1015   BOOL ret;
1016
1017   impersonate_privilege_enabled = FALSE;
1018   revert_to_old_token = FALSE;
1019   lsass_token = NULL;
1020   old_token = NULL;
1021
1022   /* Call supplied function. */
1023   ret = function(argument);
1024   if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1025     goto ret;
1026
1027   /*
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.
1031    */
1032
1033   if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege))
1034     goto err_privilege_not_held;
1035
1036   /*
1037    * If the current thread has already Tcb privilege enabled then there
1038    * is some additional unhanded restriction.
1039    */
1040   if (win32_have_privilege(luid_tcb_privilege))
1041     goto err_privilege_not_held;
1042
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))
1045     {
1046       ret = function(argument);
1047       win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1048       goto ret;
1049     }
1050
1051   /*
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.
1055    */
1056
1057   /*
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.
1061    */
1062   if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) &&
1063       !win32_have_privilege(luid_impersonate_privilege))
1064     {
1065       /*
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.
1072        */
1073       if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege))
1074         {
1075           impersonate_privilege_enabled = TRUE;
1076         }
1077       else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL))
1078         {
1079           impersonate_privilege_enabled = TRUE;
1080           revert_token_impersonate_privilege = NULL;
1081           revert_only_impersonate_privilege = TRUE;
1082         }
1083       else
1084         {
1085           goto err_privilege_not_held;
1086         }
1087
1088       /*
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
1092        * could now pass.
1093        */
1094       if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
1095         {
1096           ret = function(argument);
1097           win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1098           goto ret;
1099         }
1100     }
1101
1102   /*
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.
1106    *
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).
1111    */
1112
1113   lsass_process = win32_find_and_open_process_for_query("lsass.exe");
1114   if (!lsass_process)
1115     goto err_privilege_not_held;
1116
1117   /*
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!).
1121    */
1122   lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE);
1123
1124   CloseHandle(lsass_process);
1125
1126   if (!lsass_token)
1127     goto err_privilege_not_held;
1128
1129   /*
1130    * After successful open of the primary lsass.exe process access token,
1131    * assign its copy for the current thread.
1132    */
1133   if (!win32_change_token(lsass_token, &old_token))
1134     goto err_privilege_not_held;
1135
1136   revert_to_old_token = TRUE;
1137
1138   ret = function(argument);
1139   if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1140     goto ret;
1141
1142   /*
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.
1148    */
1149   if (win32_have_privilege(luid_tcb_privilege) ||
1150       !win32_enable_privilege(luid_tcb_privilege, NULL, NULL))
1151     goto err_privilege_not_held;
1152
1153   ret = function(argument);
1154   goto ret;
1155
1156 err_privilege_not_held:
1157   SetLastError(ERROR_PRIVILEGE_NOT_HELD);
1158   ret = FALSE;
1159   goto ret;
1160
1161 ret:
1162   if (revert_to_old_token)
1163     win32_revert_to_token(old_token);
1164
1165   if (impersonate_privilege_enabled)
1166     win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege);
1167
1168   if (lsass_token)
1169     CloseHandle(lsass_token);
1170
1171   return ret;
1172 }