1 /*
2 * The PCI Library -- Win32 helper functions
3 *
4 * Copyright (c) 2023 Pali Rohár <[email protected]>
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
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 /* Unfortunately some toolchains do not provide these constants. */
28 #ifndef SE_DACL_AUTO_INHERIT_REQ
29 #define SE_DACL_AUTO_INHERIT_REQ 0x0100
30 #endif
31 #ifndef SE_SACL_AUTO_INHERIT_REQ
32 #define SE_SACL_AUTO_INHERIT_REQ 0x0200
33 #endif
34 #ifndef SE_DACL_AUTO_INHERITED
35 #define SE_DACL_AUTO_INHERITED 0x0400
36 #endif
37 #ifndef SE_SACL_AUTO_INHERITED
38 #define SE_SACL_AUTO_INHERITED 0x0800
39 #endif
40
41 /*
42 * These psapi functions are available in kernel32.dll library with K32 prefix
43 * on Windows 7 and higher systems. On older Windows systems these functions are
44 * available in psapi.dll libary without K32 prefix. So resolve pointers to
45 * these functions dynamically at runtime from the available system library.
46 * Function GetProcessImageFileNameW() is not available on Windows 2000 and
47 * older systems.
48 */
49 typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded);
50 typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize);
51 typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize);
52
53 /*
54 * These aclapi function is available in advapi.dll library on Windows 2000
55 * and higher systems.
56 */
57 typedef BOOL (WINAPI *SetSecurityDescriptorControlProt)(PSECURITY_DESCRIPTOR pSecurityDescriptor, SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest, SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet);
58
59 /*
60 * This errhandlingapi function is available in kernel32.dll library on
61 * Windows 7 and higher systems.
62 */
63 typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode);
64
65
66 static DWORD
format_message_from_system(DWORD win32_error_id,DWORD lang_id,LPSTR buffer,DWORD size)67 format_message_from_system(DWORD win32_error_id, DWORD lang_id, LPSTR buffer, DWORD size)
68 {
69 return FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, win32_error_id, lang_id, buffer, size, NULL);
70 }
71
72 const char *
win32_strerror(DWORD win32_error_id)73 win32_strerror(DWORD win32_error_id)
74 {
75 /*
76 * Use static buffer which is large enough.
77 * Hopefully no Win32 API error message string is longer than 4 kB.
78 */
79 static char buffer[4096];
80 DWORD len;
81
82 /*
83 * If it is possible show error messages in US English language.
84 * International Windows editions do not have to provide error
85 * messages in English language, so fallback to the language
86 * which system provides (neutral).
87 */
88 len = format_message_from_system(win32_error_id, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer));
89 if (!len)
90 len = format_message_from_system(win32_error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer));
91
92 /* FormatMessage() automatically appends ".\r\n" to the error message. */
93 if (len && buffer[len-1] == '\n')
94 buffer[--len] = '\0';
95 if (len && buffer[len-1] == '\r')
96 buffer[--len] = '\0';
97 if (len && buffer[len-1] == '.')
98 buffer[--len] = '\0';
99
100 if (!len)
101 sprintf(buffer, "Unknown Win32 error %lu", win32_error_id);
102
103 return buffer;
104 }
105
106 BOOL
win32_is_non_nt_system(void)107 win32_is_non_nt_system(void)
108 {
109 OSVERSIONINFOA version;
110 version.dwOSVersionInfoSize = sizeof(version);
111 return GetVersionExA(&version) && version.dwPlatformId < VER_PLATFORM_WIN32_NT;
112 }
113
114 BOOL
win32_is_32bit_on_64bit_system(void)115 win32_is_32bit_on_64bit_system(void)
116 {
117 BOOL (WINAPI *MyIsWow64Process)(HANDLE, PBOOL);
118 HMODULE kernel32;
119 BOOL is_wow64;
120
121 /*
122 * Check for 64-bit system via IsWow64Process() function exported
123 * from 32-bit kernel32.dll library available on the 64-bit systems.
124 * Resolve pointer to this function at runtime as this code path is
125 * primary running on 32-bit systems where are not available 64-bit
126 * functions.
127 */
128
129 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
130 if (!kernel32)
131 return FALSE;
132
133 MyIsWow64Process = (void *)GetProcAddress(kernel32, "IsWow64Process");
134 if (!MyIsWow64Process)
135 return FALSE;
136
137 if (!MyIsWow64Process(GetCurrentProcess(), &is_wow64))
138 return FALSE;
139
140 return is_wow64;
141 }
142
143 BOOL
win32_is_32bit_on_win8_64bit_system(void)144 win32_is_32bit_on_win8_64bit_system(void)
145 {
146 #ifdef _WIN64
147 return FALSE;
148 #else
149 OSVERSIONINFOA version;
150
151 /* Check for Windows 8 (NT 6.2). */
152 version.dwOSVersionInfoSize = sizeof(version);
153 if (!GetVersionExA(&version) ||
154 version.dwPlatformId != VER_PLATFORM_WIN32_NT ||
155 version.dwMajorVersion < 6 ||
156 (version.dwMajorVersion == 6 && version.dwMinorVersion < 2))
157 return FALSE;
158
159 return win32_is_32bit_on_64bit_system();
160 #endif
161 }
162
163 /*
164 * Change error mode of the current thread. If it is not possible then change
165 * error mode of the whole process. Always returns previous error mode.
166 */
167 UINT
win32_change_error_mode(UINT new_mode)168 win32_change_error_mode(UINT new_mode)
169 {
170 SetThreadErrorModeProt MySetThreadErrorMode = NULL;
171 HMODULE kernel32;
172 DWORD old_mode;
173
174 /*
175 * Function SetThreadErrorMode() was introduced in Windows 7, so use
176 * GetProcAddress() for compatibility with older systems.
177 */
178 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
179 if (kernel32)
180 MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode");
181
182 if (MySetThreadErrorMode &&
183 MySetThreadErrorMode(new_mode, &old_mode))
184 return old_mode;
185
186 /*
187 * Fallback to function SetErrorMode() which modifies error mode of the
188 * whole process and returns old mode.
189 */
190 return SetErrorMode(new_mode);
191 }
192
193 /*
194 * Check if the current thread has particular privilege in current active access
195 * token. Case when it not possible to determinate it (e.g. current thread does
196 * not have permission to open its own current active access token) is evaluated
197 * as thread does not have that privilege.
198 */
199 BOOL
win32_have_privilege(LUID luid_privilege)200 win32_have_privilege(LUID luid_privilege)
201 {
202 PRIVILEGE_SET priv;
203 HANDLE token;
204 BOOL ret;
205
206 /*
207 * If the current thread does not have active access token then thread
208 * uses primary process access token for all permission checks.
209 */
210 if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
211 (GetLastError() != ERROR_NO_TOKEN ||
212 !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
213 return FALSE;
214
215 priv.PrivilegeCount = 1;
216 priv.Control = PRIVILEGE_SET_ALL_NECESSARY;
217 priv.Privilege[0].Luid = luid_privilege;
218 priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
219
220 if (!PrivilegeCheck(token, &priv, &ret))
221 return FALSE;
222
223 return ret;
224 }
225
226 /*
227 * Enable or disable particular privilege in specified access token.
228 *
229 * Note that it is not possible to disable privilege in access token with
230 * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check
231 * this case and incorrectly returns no error even when disabling failed.
232 * Rationale for this decision: Simplification of this function as WinAPI
233 * call AdjustTokenPrivileges() does not signal error in this case too.
234 */
235 static BOOL
set_privilege(HANDLE token,LUID luid_privilege,BOOL enable)236 set_privilege(HANDLE token, LUID luid_privilege, BOOL enable)
237 {
238 TOKEN_PRIVILEGES token_privileges;
239
240 token_privileges.PrivilegeCount = 1;
241 token_privileges.Privileges[0].Luid = luid_privilege;
242 token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
243
244 /*
245 * WinAPI function AdjustTokenPrivileges() success also when not all
246 * privileges were enabled. It is always required to check for failure
247 * via GetLastError() call. AdjustTokenPrivileges() always sets error
248 * also when it success, as opposite to other WinAPI functions.
249 */
250 if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) ||
251 GetLastError() != ERROR_SUCCESS)
252 return FALSE;
253
254 return TRUE;
255 }
256
257 /*
258 * Change access token for the current thread to new specified access token.
259 * Previously active access token is stored in old_token variable and can be
260 * used for reverting to this access token. It is set to NULL if the current
261 * thread previously used primary process access token.
262 */
263 BOOL
win32_change_token(HANDLE new_token,HANDLE * old_token)264 win32_change_token(HANDLE new_token, HANDLE *old_token)
265 {
266 HANDLE token;
267
268 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
269 {
270 if (GetLastError() != ERROR_NO_TOKEN)
271 return FALSE;
272 token = NULL;
273 }
274
275 if (!ImpersonateLoggedOnUser(new_token))
276 {
277 if (token)
278 CloseHandle(token);
279 return FALSE;
280 }
281
282 *old_token = token;
283 return TRUE;
284 }
285
286 /*
287 * Change access token for the current thread to the primary process access
288 * token. This function fails also when the current thread already uses primary
289 * process access token.
290 */
291 static BOOL
change_token_to_primary(HANDLE * old_token)292 change_token_to_primary(HANDLE *old_token)
293 {
294 HANDLE token;
295
296 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token))
297 return FALSE;
298
299 RevertToSelf();
300
301 *old_token = token;
302 return TRUE;
303 }
304
305 /*
306 * Revert to the specified access token for the current thread. When access
307 * token is specified as NULL then revert to the primary process access token.
308 * Use to revert after win32_change_token() or change_token_to_primary() call.
309 */
310 VOID
win32_revert_to_token(HANDLE token)311 win32_revert_to_token(HANDLE token)
312 {
313 /*
314 * If SetThreadToken() call fails then there is no option to revert to
315 * the specified previous thread access token. So in this case revert to
316 * the primary process access token.
317 */
318 if (!token || !SetThreadToken(NULL, token))
319 RevertToSelf();
320 if (token)
321 CloseHandle(token);
322 }
323
324 /*
325 * Enable particular privilege for the current thread. And set method how to
326 * revert this privilege (if to revert whole token or only privilege).
327 */
328 BOOL
win32_enable_privilege(LUID luid_privilege,HANDLE * revert_token,BOOL * revert_only_privilege)329 win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege)
330 {
331 HANDLE thread_token;
332 HANDLE new_token;
333
334 if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token))
335 {
336 if (set_privilege(thread_token, luid_privilege, TRUE))
337 {
338 /*
339 * Indicate that correct revert method is just to
340 * disable privilege in access token.
341 */
342 if (revert_token && revert_only_privilege)
343 {
344 *revert_token = thread_token;
345 *revert_only_privilege = TRUE;
346 }
347 else
348 {
349 CloseHandle(thread_token);
350 }
351 return TRUE;
352 }
353 CloseHandle(thread_token);
354 /*
355 * If enabling privilege failed then try to enable it via
356 * primary process access token.
357 */
358 }
359
360 /*
361 * If the current thread has already active thread access token then
362 * open it with just impersonate right as it would be used only for
363 * future revert.
364 */
365 if (revert_token && revert_only_privilege)
366 {
367 if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token))
368 {
369 if (GetLastError() != ERROR_NO_TOKEN)
370 return FALSE;
371 thread_token = NULL;
372 }
373
374 /*
375 * If current thread has no access token (and uses primary
376 * process access token) or it does not have permission to
377 * adjust privileges or it does not have specified privilege
378 * then create a copy of the primary process access token,
379 * assign it for the current thread (= impersonate self)
380 * and then try adjusting privilege again.
381 */
382 if (!ImpersonateSelf(SecurityImpersonation))
383 {
384 if (thread_token)
385 CloseHandle(thread_token);
386 return FALSE;
387 }
388 }
389
390 if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token))
391 {
392 /* thread_token is set only when we were asked for revert method. */
393 if (revert_token && revert_only_privilege)
394 win32_revert_to_token(thread_token);
395 return FALSE;
396 }
397
398 if (!set_privilege(new_token, luid_privilege, TRUE))
399 {
400 CloseHandle(new_token);
401 /* thread_token is set only when we were asked for revert method. */
402 if (revert_token && revert_only_privilege)
403 win32_revert_to_token(thread_token);
404 return FALSE;
405 }
406
407 /*
408 * Indicate that correct revert method is to change to the previous
409 * access token. Either to the primary process access token or to the
410 * previous thread access token.
411 */
412 if (revert_token && revert_only_privilege)
413 {
414 *revert_token = thread_token;
415 *revert_only_privilege = FALSE;
416 }
417 return TRUE;
418 }
419
420 /*
421 * Revert particular privilege for the current thread was previously enabled by
422 * win32_enable_privilege() call. Either disable privilege in specified access token
423 * or revert to previous access token.
424 */
425 VOID
win32_revert_privilege(LUID luid_privilege,HANDLE revert_token,BOOL revert_only_privilege)426 win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege)
427 {
428 if (revert_only_privilege)
429 {
430 set_privilege(revert_token, luid_privilege, FALSE);
431 CloseHandle(revert_token);
432 }
433 else
434 {
435 win32_revert_to_token(revert_token);
436 }
437 }
438
439 /*
440 * Return owner of the access token used by the current thread. Buffer for
441 * returned owner needs to be released by LocalFree() call.
442 */
443 static TOKEN_OWNER *
get_current_token_owner(VOID)444 get_current_token_owner(VOID)
445 {
446 HANDLE token;
447 DWORD length;
448 TOKEN_OWNER *owner;
449
450 /*
451 * If the current thread does not have active access token then thread
452 * uses primary process access token for all permission checks.
453 */
454 if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) &&
455 (GetLastError() != ERROR_NO_TOKEN ||
456 !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)))
457 return NULL;
458
459 if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) &&
460 GetLastError() != ERROR_INSUFFICIENT_BUFFER)
461 {
462 CloseHandle(token);
463 return NULL;
464 }
465
466 retry:
467 owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length);
468 if (!owner)
469 {
470 CloseHandle(token);
471 return NULL;
472 }
473
474 if (!GetTokenInformation(token, TokenOwner, owner, length, &length))
475 {
476 /*
477 * Length of token owner (SID) buffer between two get calls may
478 * changes (e.g. by another thread of process), so retry.
479 */
480 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
481 {
482 LocalFree(owner);
483 goto retry;
484 }
485 LocalFree(owner);
486 CloseHandle(token);
487 return NULL;
488 }
489
490 CloseHandle(token);
491 return owner;
492 }
493
494 /*
495 * Create a new security descriptor in absolute form from relative form.
496 * Newly created security descriptor in absolute form is stored in linear buffer.
497 */
498 static PSECURITY_DESCRIPTOR
create_relsd_from_abssd(PSECURITY_DESCRIPTOR rel_security_descriptor)499 create_relsd_from_abssd(PSECURITY_DESCRIPTOR rel_security_descriptor)
500 {
501 PBYTE abs_security_descriptor_buffer;
502 DWORD abs_security_descriptor_size=0, abs_dacl_size=0, abs_sacl_size=0, abs_owner_size=0, abs_primary_group_size=0;
503
504 if (!MakeAbsoluteSD(rel_security_descriptor,
505 NULL, &abs_security_descriptor_size,
506 NULL, &abs_dacl_size,
507 NULL, &abs_sacl_size,
508 NULL, &abs_owner_size,
509 NULL, &abs_primary_group_size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
510 return NULL;
511
512 abs_security_descriptor_buffer = (PBYTE)LocalAlloc(LPTR, abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size+abs_primary_group_size);
513 if (!abs_security_descriptor_buffer)
514 return NULL;
515
516 if (!MakeAbsoluteSD(rel_security_descriptor,
517 (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer, &abs_security_descriptor_size,
518 (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size), &abs_dacl_size,
519 (PACL)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size), &abs_sacl_size,
520 (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size), &abs_owner_size,
521 (PSID)(abs_security_descriptor_buffer+abs_security_descriptor_size+abs_dacl_size+abs_sacl_size+abs_owner_size), &abs_primary_group_size))
522 return NULL;
523
524 return (PSECURITY_DESCRIPTOR)abs_security_descriptor_buffer;
525 }
526
527 /*
528 * Prepare security descriptor obtained by GetKernelObjectSecurity() so it can be
529 * passed to SetKernelObjectSecurity() as identity operation. It modifies control
530 * flags of security descriptor, which is needed for Windows 2000 and new.
531 */
532 static BOOL
prepare_security_descriptor_for_set_operation(PSECURITY_DESCRIPTOR security_descriptor)533 prepare_security_descriptor_for_set_operation(PSECURITY_DESCRIPTOR security_descriptor)
534 {
535 SetSecurityDescriptorControlProt MySetSecurityDescriptorControl;
536 SECURITY_DESCRIPTOR_CONTROL bits_mask;
537 SECURITY_DESCRIPTOR_CONTROL bits_set;
538 SECURITY_DESCRIPTOR_CONTROL control;
539 OSVERSIONINFO version;
540 HMODULE advapi32;
541 DWORD revision;
542
543 /*
544 * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are flags introduced in
545 * Windows 2000 to control client-side automatic inheritance (client - user
546 * process - is responsible for propagating inherited ACEs to subobjects).
547 * To prevent applications which do not understand client-side automatic
548 * inheritance (applications created prior Windows 2000 or which use low
549 * level API like SetKernelObjectSecurity()) to unintentionally set those
550 * SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED control flags when
551 * coping them from other security descriptor.
552 *
553 * As we are not modifying existing ACEs, we are compatible with Windows 2000
554 * client-side automatic inheritance model and therefore prepare security
555 * descriptor for SetKernelObjectSecurity() to not clear existing automatic
556 * inheritance control flags.
557 *
558 * Control flags SE_DACL_AUTO_INHERITED and SE_SACL_AUTO_INHERITED are set
559 * into security object only when they are set together with set-only flags
560 * SE_DACL_AUTO_INHERIT_REQ and SE_SACL_AUTO_INHERIT_REQ. Those flags are
561 * never received by GetKernelObjectSecurity() and are just commands for
562 * SetKernelObjectSecurity() how to interpret SE_DACL_AUTO_INHERITED and
563 * SE_SACL_AUTO_INHERITED flags.
564 *
565 * Function symbol SetSecurityDescriptorControl is not available in the
566 * older versions of advapi32.dll library, so resolve it at runtime.
567 */
568
569 version.dwOSVersionInfoSize = sizeof(version);
570 if (!GetVersionEx(&version) ||
571 version.dwPlatformId != VER_PLATFORM_WIN32_NT ||
572 version.dwMajorVersion < 5)
573 return TRUE;
574
575 if (!GetSecurityDescriptorControl(security_descriptor, &control, &revision))
576 return FALSE;
577
578 bits_mask = 0;
579 bits_set = 0;
580
581 if (control & SE_DACL_AUTO_INHERITED)
582 {
583 bits_mask |= SE_DACL_AUTO_INHERIT_REQ;
584 bits_set |= SE_DACL_AUTO_INHERIT_REQ;
585 }
586
587 if (control & SE_SACL_AUTO_INHERITED)
588 {
589 bits_mask |= SE_SACL_AUTO_INHERIT_REQ;
590 bits_set |= SE_SACL_AUTO_INHERIT_REQ;
591 }
592
593 if (!bits_mask)
594 return TRUE;
595
596 advapi32 = GetModuleHandle(TEXT("advapi32.dll"));
597 if (!advapi32)
598 return FALSE;
599
600 MySetSecurityDescriptorControl = (SetSecurityDescriptorControlProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityDescriptorControl");
601 if (!MySetSecurityDescriptorControl)
602 return FALSE;
603
604 if (!MySetSecurityDescriptorControl(security_descriptor, bits_mask, bits_set))
605 return FALSE;
606
607 return TRUE;
608 }
609
610 /*
611 * Grant particular permissions in the primary access token of the specified
612 * process for the owner of current thread token and set old DACL of the
613 * process access token for reverting permissions. Security descriptor is
614 * just memory buffer for old DACL.
615 */
616 static BOOL
grant_process_token_dacl_permissions(HANDLE process,DWORD permissions,HANDLE * token,PSECURITY_DESCRIPTOR * old_security_descriptor)617 grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PSECURITY_DESCRIPTOR *old_security_descriptor)
618 {
619 TOKEN_OWNER *owner;
620 PACL old_dacl;
621 BOOL old_dacl_present;
622 BOOL old_dacl_defaulted;
623 PACL new_dacl;
624 WORD new_dacl_size;
625 PSECURITY_DESCRIPTOR new_security_descriptor;
626 DWORD length;
627
628 owner = get_current_token_owner();
629 if (!owner)
630 return FALSE;
631
632 /*
633 * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION)
634 * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION).
635 */
636 if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token))
637 {
638 LocalFree(owner);
639 return FALSE;
640 }
641
642 if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, NULL, 0, &length) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
643 {
644 LocalFree(owner);
645 CloseHandle(*token);
646 return FALSE;
647 }
648
649 retry:
650 *old_security_descriptor = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, length);
651 if (!*old_security_descriptor)
652 {
653 LocalFree(owner);
654 CloseHandle(*token);
655 return FALSE;
656 }
657
658 if (!GetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, *old_security_descriptor, length, &length))
659 {
660 /*
661 * Length of the security descriptor between two get calls
662 * may changes (e.g. by another thread of process), so retry.
663 */
664 if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
665 {
666 LocalFree(*old_security_descriptor);
667 goto retry;
668 }
669 LocalFree(*old_security_descriptor);
670 LocalFree(owner);
671 CloseHandle(*token);
672 return FALSE;
673 }
674
675 if (!prepare_security_descriptor_for_set_operation(*old_security_descriptor))
676 {
677 LocalFree(*old_security_descriptor);
678 LocalFree(owner);
679 CloseHandle(*token);
680 return FALSE;
681 }
682
683 /* Retrieve the current DACL from security descriptor including present and defaulted properties. */
684 if (!GetSecurityDescriptorDacl(*old_security_descriptor, &old_dacl_present, &old_dacl, &old_dacl_defaulted))
685 {
686 LocalFree(*old_security_descriptor);
687 LocalFree(owner);
688 CloseHandle(*token);
689 return FALSE;
690 }
691
692 /*
693 * If DACL is not present then system grants full access to everyone. It this
694 * case do not modify DACL as it just adds one ACL allow rule for us, which
695 * automatically disallow access to anybody else which had access before.
696 */
697 if (!old_dacl_present || !old_dacl)
698 {
699 LocalFree(*old_security_descriptor);
700 LocalFree(owner);
701 *old_security_descriptor = NULL;
702 return TRUE;
703 }
704
705 /* Create new DACL which would be copy of the current old one. */
706 new_dacl_size = old_dacl->AclSize + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(owner->Owner) - sizeof(DWORD);
707 new_dacl = (PACL)LocalAlloc(LPTR, new_dacl_size);
708 if (!new_dacl)
709 {
710 LocalFree(*old_security_descriptor);
711 LocalFree(owner);
712 CloseHandle(*token);
713 return FALSE;
714 }
715
716 /*
717 * Initialize new DACL structure to the same format as was the old one.
718 * Set new explicit access for the owner of the current thread access
719 * token with non-inherited granting access to specified permissions.
720 * This permission is added in the first ACE, so has the highest priority.
721 */
722 if (!InitializeAcl(new_dacl, new_dacl_size, old_dacl->AclRevision) ||
723 !AddAccessAllowedAce(new_dacl, ACL_REVISION2, permissions, owner->Owner))
724 {
725 LocalFree(new_dacl);
726 LocalFree(*old_security_descriptor);
727 LocalFree(owner);
728 CloseHandle(*token);
729 return FALSE;
730 }
731
732 /*
733 * Now (after setting our new permissions) append all ACE entries from the
734 * old DACL to the new DACL, which preserve all other existing permissions.
735 */
736 if (old_dacl->AceCount > 0)
737 {
738 WORD ace_index;
739 LPVOID ace;
740
741 for (ace_index = 0; ace_index < old_dacl->AceCount; ace_index++)
742 {
743 if (!GetAce(old_dacl, ace_index, &ace) ||
744 !AddAce(new_dacl, old_dacl->AclRevision, MAXDWORD, ace, ((PACE_HEADER)ace)->AceSize))
745 {
746 LocalFree(new_dacl);
747 LocalFree(*old_security_descriptor);
748 LocalFree(owner);
749 CloseHandle(*token);
750 return FALSE;
751 }
752 }
753 }
754
755 /*
756 * Create copy of the old security descriptor, so we can modify its DACL.
757 * Function SetSecurityDescriptorDacl() works only with security descriptors
758 * in absolute format. So use our helper function create_relsd_from_abssd()
759 * for converting security descriptor from relative format (which is returned
760 * by GetKernelObjectSecurity() function) to the absolute format.
761 */
762 new_security_descriptor = create_relsd_from_abssd(*old_security_descriptor);
763 if (!new_security_descriptor)
764 {
765 LocalFree(new_dacl);
766 LocalFree(*old_security_descriptor);
767 LocalFree(owner);
768 CloseHandle(*token);
769 return FALSE;
770 }
771
772 /*
773 * In the new security descriptor replace old DACL by the new DACL (which has
774 * new permissions) and then set this new security descriptor to the token,
775 * so token would have new access permissions.
776 */
777 if (!SetSecurityDescriptorDacl(new_security_descriptor, TRUE, new_dacl, FALSE) ||
778 !SetKernelObjectSecurity(*token, DACL_SECURITY_INFORMATION, new_security_descriptor))
779 {
780 LocalFree(new_security_descriptor);
781 LocalFree(new_dacl);
782 LocalFree(*old_security_descriptor);
783 LocalFree(owner);
784 CloseHandle(*token);
785 return FALSE;
786 }
787
788 LocalFree(new_security_descriptor);
789 LocalFree(new_dacl);
790 LocalFree(owner);
791 return TRUE;
792 }
793
794 /*
795 * Revert particular granted permissions in specified access token done by
796 * grant_process_token_dacl_permissions() call.
797 */
798 static VOID
revert_token_dacl_permissions(HANDLE token,PSECURITY_DESCRIPTOR old_security_descriptor)799 revert_token_dacl_permissions(HANDLE token, PSECURITY_DESCRIPTOR old_security_descriptor)
800 {
801 SetKernelObjectSecurity(token, DACL_SECURITY_INFORMATION, old_security_descriptor);
802 LocalFree(old_security_descriptor);
803 CloseHandle(token);
804 }
805
806 /*
807 * Open process handle specified by the process id with the query right and
808 * optionally also with vm read right.
809 */
810 static HANDLE
open_process_for_query(DWORD pid,BOOL with_vm_read)811 open_process_for_query(DWORD pid, BOOL with_vm_read)
812 {
813 BOOL revert_only_privilege;
814 LUID luid_debug_privilege;
815 OSVERSIONINFO version;
816 DWORD process_right;
817 HANDLE revert_token;
818 HANDLE process;
819
820 /*
821 * Some processes on Windows Vista and higher systems can be opened only
822 * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough
823 * for accessing primary process token. But this right is not supported
824 * on older pre-Vista systems. When the current thread on these older
825 * systems does not have Debug privilege then OpenProcess() fails with
826 * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then
827 * OpenProcess() success and returns handle to requested process.
828 * Problem is that this handle does not have PROCESS_QUERY_INFORMATION
829 * right and so cannot be used for accessing primary process token
830 * on those older systems. Moreover it has zero rights and therefore
831 * such handle is fully useless. So never try to use open process with
832 * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than
833 * Windows Vista (NT 6.0).
834 */
835 version.dwOSVersionInfoSize = sizeof(version);
836 if (GetVersionEx(&version) &&
837 version.dwPlatformId == VER_PLATFORM_WIN32_NT &&
838 version.dwMajorVersion >= 6)
839 process_right = PROCESS_QUERY_LIMITED_INFORMATION;
840 else
841 process_right = PROCESS_QUERY_INFORMATION;
842
843 if (with_vm_read)
844 process_right |= PROCESS_VM_READ;
845
846 process = OpenProcess(process_right, FALSE, pid);
847 if (process)
848 return process;
849
850 /*
851 * It is possible to open only processes to which owner of the current
852 * thread access token has permissions. For opening other processing it
853 * is required to have Debug privilege enabled. By default local
854 * administrators have this privilege, but it is disabled. So try to
855 * enable it and then try to open process again.
856 */
857
858 if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege))
859 return NULL;
860
861 if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege))
862 return NULL;
863
864 process = OpenProcess(process_right, FALSE, pid);
865
866 win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege);
867
868 return process;
869 }
870
871 /*
872 * Check if process image path name (wide string) matches exe file name
873 * (7-bit ASCII string). Do case-insensitive string comparison. Process
874 * image path name can be in any namespace format (DOS, Win32, UNC, ...).
875 */
876 static BOOL
check_process_name(LPCWSTR path,DWORD path_length,LPCSTR exe_file)877 check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file)
878 {
879 DWORD exe_file_length;
880 WCHAR c1;
881 UCHAR c2;
882 DWORD i;
883
884 exe_file_length = 0;
885 while (exe_file[exe_file_length] != '\0')
886 exe_file_length++;
887
888 /* Path must have backslash before exe file name. */
889 if (exe_file_length >= path_length ||
890 path[path_length-exe_file_length-1] != L'\\')
891 return FALSE;
892
893 for (i = 0; i < exe_file_length; i++)
894 {
895 c1 = path[path_length-exe_file_length+i];
896 c2 = exe_file[i];
897 /*
898 * Input string for comparison is 7-bit ASCII and file name part
899 * of path must not contain backslash as it is path separator.
900 */
901 if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\')
902 return FALSE;
903 if (c1 >= L'a' && c1 <= L'z')
904 c1 -= L'a' - L'A';
905 if (c2 >= 'a' && c2 <= 'z')
906 c2 -= 'a' - 'A';
907 if (c1 != c2)
908 return FALSE;
909 }
910
911 return TRUE;
912 }
913
914 /* Open process handle with the query right specified by process exe file. */
915 HANDLE
win32_find_and_open_process_for_query(LPCSTR exe_file)916 win32_find_and_open_process_for_query(LPCSTR exe_file)
917 {
918 GetProcessImageFileNameWProt MyGetProcessImageFileNameW;
919 GetModuleFileNameExWProt MyGetModuleFileNameExW;
920 EnumProcessesProt MyEnumProcesses;
921 HMODULE kernel32, psapi;
922 UINT prev_error_mode;
923 DWORD partial_retry;
924 BOOL found_process;
925 DWORD size, length;
926 DWORD *processes;
927 HANDLE process;
928 LPWSTR path;
929 DWORD error;
930 DWORD count;
931 DWORD i;
932
933 psapi = NULL;
934 kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
935 if (!kernel32)
936 return NULL;
937
938 /*
939 * On Windows 7 and higher systems these functions are available in
940 * kernel32.dll library with K32 prefix.
941 */
942 MyGetModuleFileNameExW = NULL;
943 MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW");
944 MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses");
945 if (!MyGetProcessImageFileNameW || !MyEnumProcesses)
946 {
947 /*
948 * On older NT-based systems these functions are available in
949 * psapi.dll library without K32 prefix.
950 */
951 prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
952 psapi = LoadLibrary(TEXT("psapi.dll"));
953 win32_change_error_mode(prev_error_mode);
954
955 if (!psapi)
956 return NULL;
957
958 /*
959 * Function GetProcessImageFileNameW() is available in
960 * Windows XP and higher systems. On older versions is
961 * available function GetModuleFileNameExW().
962 */
963 MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW");
964 MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW");
965 MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses");
966 if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses)
967 {
968 FreeLibrary(psapi);
969 return NULL;
970 }
971 }
972
973 /* Make initial buffer size for 1024 processes. */
974 size = 1024 * sizeof(*processes);
975
976 retry:
977 processes = (DWORD *)LocalAlloc(LPTR, size);
978 if (!processes)
979 {
980 if (psapi)
981 FreeLibrary(psapi);
982 return NULL;
983 }
984
985 if (!MyEnumProcesses(processes, size, &length))
986 {
987 LocalFree(processes);
988 if (psapi)
989 FreeLibrary(psapi);
990 return NULL;
991 }
992 else if (size == length)
993 {
994 /*
995 * There is no indication given when the buffer is too small to
996 * store all process identifiers. Therefore if returned length
997 * is same as buffer size there can be more processes. Call
998 * again with larger buffer.
999 */
1000 LocalFree(processes);
1001 size *= 2;
1002 goto retry;
1003 }
1004
1005 process = NULL;
1006 count = length / sizeof(*processes);
1007
1008 for (i = 0; i < count; i++)
1009 {
1010 /* Skip System Idle Process. */
1011 if (processes[i] == 0)
1012 continue;
1013
1014 /*
1015 * Function GetModuleFileNameExW() requires additional
1016 * PROCESS_VM_READ right as opposite to function
1017 * GetProcessImageFileNameW() which does not need it.
1018 */
1019 process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE);
1020 if (!process)
1021 continue;
1022
1023 /*
1024 * Set initial buffer size to 256 (wide) characters.
1025 * Final path length on the modern NT-based systems can be also larger.
1026 */
1027 size = 256;
1028 found_process = FALSE;
1029 partial_retry = 0;
1030
1031 retry_path:
1032 path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path));
1033 if (!path)
1034 goto end_path;
1035
1036 if (MyGetProcessImageFileNameW)
1037 length = MyGetProcessImageFileNameW(process, path, size);
1038 else
1039 length = MyGetModuleFileNameExW(process, NULL, path, size);
1040
1041 error = GetLastError();
1042
1043 /*
1044 * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY
1045 * when remote process is in the middle of updating its module table.
1046 * Sleep 10 ms and try again, max 10 attempts.
1047 */
1048 if (!MyGetProcessImageFileNameW)
1049 {
1050 if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10)
1051 {
1052 Sleep(10);
1053 goto retry_path;
1054 }
1055 partial_retry = 0;
1056 }
1057
1058 /*
1059 * When buffer is too small then function GetModuleFileNameEx() returns
1060 * its size argument on older systems (Windows XP) or its size minus
1061 * argument one on new systems (Windows 10) without signalling any error.
1062 * Function GetProcessImageFileNameW() on the other hand returns zero
1063 * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these
1064 * cases call function again with larger buffer.
1065 */
1066
1067 if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER)
1068 goto end_path;
1069
1070 if ((MyGetProcessImageFileNameW && length == 0) ||
1071 (!MyGetProcessImageFileNameW && (length == size || length == size-1)))
1072 {
1073 LocalFree(path);
1074 size *= 2;
1075 goto retry_path;
1076 }
1077
1078 if (length && check_process_name(path, length, exe_file))
1079 found_process = TRUE;
1080
1081 end_path:
1082 if (path)
1083 {
1084 LocalFree(path);
1085 path = NULL;
1086 }
1087
1088 if (found_process)
1089 break;
1090
1091 CloseHandle(process);
1092 process = NULL;
1093 }
1094
1095 LocalFree(processes);
1096
1097 if (psapi)
1098 FreeLibrary(psapi);
1099
1100 return process;
1101 }
1102
1103 /*
1104 * Try to open primary access token of the particular process with specified
1105 * rights. Before opening access token try to adjust DACL permissions of the
1106 * primary process access token, so following open does not fail on error
1107 * related to no open permissions. Revert DACL permissions after open attempt.
1108 * As following steps are not atomic, try to execute them more times in case
1109 * of possible race conditions caused by other threads or processes.
1110 */
1111 static HANDLE
try_grant_permissions_and_open_process_token(HANDLE process,DWORD rights)1112 try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights)
1113 {
1114 PSECURITY_DESCRIPTOR old_security_descriptor;
1115 HANDLE grant_token;
1116 HANDLE token;
1117 DWORD retry;
1118 DWORD error;
1119
1120 /*
1121 * This code is not atomic. Between grant and open calls can other
1122 * thread or process change or revert permissions. So try to execute
1123 * it more times.
1124 */
1125 for (retry = 0; retry < 10; retry++)
1126 {
1127 if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_security_descriptor))
1128 return NULL;
1129 if (!OpenProcessToken(process, rights, &token))
1130 {
1131 token = NULL;
1132 error = GetLastError();
1133 }
1134 if (old_security_descriptor)
1135 revert_token_dacl_permissions(grant_token, old_security_descriptor);
1136 if (token)
1137 return token;
1138 else if (error != ERROR_ACCESS_DENIED)
1139 return NULL;
1140 }
1141
1142 return NULL;
1143 }
1144
1145 /*
1146 * Open primary access token of particular process handle with specified rights.
1147 * If permissions for specified rights are missing then try to grant them.
1148 */
1149 HANDLE
win32_open_process_token_with_rights(HANDLE process,DWORD rights)1150 win32_open_process_token_with_rights(HANDLE process, DWORD rights)
1151 {
1152 HANDLE old_token;
1153 HANDLE token;
1154
1155 /* First try to open primary access token of process handle directly. */
1156 if (OpenProcessToken(process, rights, &token))
1157 return token;
1158
1159 /*
1160 * If opening failed then it means that owner of the current thread
1161 * access token does not have permission for it. Try it again with
1162 * primary process access token.
1163 */
1164 if (change_token_to_primary(&old_token))
1165 {
1166 if (!OpenProcessToken(process, rights, &token))
1167 token = NULL;
1168 win32_revert_to_token(old_token);
1169 if (token)
1170 return token;
1171 }
1172
1173 /*
1174 * If opening is still failing then try to grant specified permissions
1175 * for the current thread and try to open it again.
1176 */
1177 token = try_grant_permissions_and_open_process_token(process, rights);
1178 if (token)
1179 return token;
1180
1181 /*
1182 * And if it is still failing then try it again with granting
1183 * permissions for the primary process token of the current process.
1184 */
1185 if (change_token_to_primary(&old_token))
1186 {
1187 token = try_grant_permissions_and_open_process_token(process, rights);
1188 win32_revert_to_token(old_token);
1189 if (token)
1190 return token;
1191 }
1192
1193 /*
1194 * TODO: Sorry, no other option for now...
1195 * It could be possible to use Take Ownership Name privilege to
1196 * temporary change token owner of specified process to the owner of
1197 * the current thread token, grant permissions for current thread in
1198 * that process token, change ownership back to original one, open
1199 * that process token and revert granted permissions. But this is
1200 * not implemented yet.
1201 */
1202 return NULL;
1203 }
1204
1205 /*
1206 * Call supplied function with its argument and if it fails with
1207 * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and
1208 * call function with its argument again.
1209 */
1210 BOOL
win32_call_func_with_tcb_privilege(BOOL (* function)(LPVOID),LPVOID argument)1211 win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument)
1212 {
1213 LUID luid_tcb_privilege;
1214 LUID luid_impersonate_privilege;
1215
1216 HANDLE revert_token_tcb_privilege;
1217 BOOL revert_only_tcb_privilege;
1218
1219 HANDLE revert_token_impersonate_privilege;
1220 BOOL revert_only_impersonate_privilege;
1221
1222 BOOL impersonate_privilege_enabled;
1223
1224 BOOL revert_to_old_token;
1225 HANDLE old_token;
1226
1227 HANDLE lsass_process;
1228 HANDLE lsass_token;
1229
1230 DWORD error;
1231 BOOL ret;
1232
1233 impersonate_privilege_enabled = FALSE;
1234 revert_to_old_token = FALSE;
1235 lsass_token = NULL;
1236 old_token = NULL;
1237
1238 /* Call supplied function. */
1239 ret = function(argument);
1240 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1241 goto ret;
1242
1243 /*
1244 * If function call failed with ERROR_PRIVILEGE_NOT_HELD
1245 * error then it means that the current thread token does not have
1246 * Tcb privilege enabled. Try to enable it.
1247 */
1248
1249 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege))
1250 goto err_privilege_not_held;
1251
1252 /*
1253 * If the current thread has already Tcb privilege enabled then there
1254 * is some additional unhanded restriction.
1255 */
1256 if (win32_have_privilege(luid_tcb_privilege))
1257 goto err_privilege_not_held;
1258
1259 /* Try to enable Tcb privilege and try function call again. */
1260 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
1261 {
1262 ret = function(argument);
1263 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1264 goto ret;
1265 }
1266
1267 /*
1268 * If enabling of Tcb privilege failed then it means that current thread
1269 * does not have this privilege. But current process may have it. So try it
1270 * again with primary process access token.
1271 */
1272
1273 /*
1274 * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then
1275 * all future actions in this function require this Impersonate privilege.
1276 * So try to enable it in case it is currently disabled.
1277 */
1278 if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) &&
1279 !win32_have_privilege(luid_impersonate_privilege))
1280 {
1281 /*
1282 * If current thread does not have Impersonate privilege enabled
1283 * then first try to enable it just for the current thread. If
1284 * it is not possible to enable it just for the current thread
1285 * then try it to enable globally for whole process (which
1286 * affects all process threads). Both actions will be reverted
1287 * at the end of this function.
1288 */
1289 if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege))
1290 {
1291 impersonate_privilege_enabled = TRUE;
1292 }
1293 else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL))
1294 {
1295 impersonate_privilege_enabled = TRUE;
1296 revert_token_impersonate_privilege = NULL;
1297 revert_only_impersonate_privilege = TRUE;
1298 }
1299 else
1300 {
1301 goto err_privilege_not_held;
1302 }
1303
1304 /*
1305 * Now when Impersonate privilege is enabled, try to enable Tcb
1306 * privilege again. Enabling other privileges for the current
1307 * thread requires Impersonate privilege, so enabling Tcb again
1308 * could now pass.
1309 */
1310 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
1311 {
1312 ret = function(argument);
1313 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
1314 goto ret;
1315 }
1316 }
1317
1318 /*
1319 * If enabling Tcb privilege failed then it means that the current
1320 * thread access token does not have this privilege or does not
1321 * have permission to adjust privileges.
1322 *
1323 * Try to use more privileged token from Local Security Authority
1324 * Subsystem Service process (lsass.exe) which has Tcb privilege.
1325 * Retrieving this more privileged token is possible for local
1326 * administrators (unless it was disabled by local administrators).
1327 */
1328
1329 lsass_process = win32_find_and_open_process_for_query("lsass.exe");
1330 if (!lsass_process)
1331 goto err_privilege_not_held;
1332
1333 /*
1334 * Open primary lsass.exe process access token with query and duplicate
1335 * rights. Just these two rights are required for impersonating other
1336 * primary process token (impersonate right is really not required!).
1337 */
1338 lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE);
1339
1340 CloseHandle(lsass_process);
1341
1342 if (!lsass_token)
1343 goto err_privilege_not_held;
1344
1345 /*
1346 * After successful open of the primary lsass.exe process access token,
1347 * assign its copy for the current thread.
1348 */
1349 if (!win32_change_token(lsass_token, &old_token))
1350 goto err_privilege_not_held;
1351
1352 revert_to_old_token = TRUE;
1353
1354 ret = function(argument);
1355 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
1356 goto ret;
1357
1358 /*
1359 * Now current thread is not using primary process token anymore
1360 * but is using custom access token. There is no need to revert
1361 * enabled Tcb privilege as the whole custom access token would
1362 * be reverted. So there is no need to setup revert method for
1363 * enabling privilege.
1364 */
1365 if (win32_have_privilege(luid_tcb_privilege) ||
1366 !win32_enable_privilege(luid_tcb_privilege, NULL, NULL))
1367 goto err_privilege_not_held;
1368
1369 ret = function(argument);
1370 goto ret;
1371
1372 err_privilege_not_held:
1373 SetLastError(ERROR_PRIVILEGE_NOT_HELD);
1374 ret = FALSE;
1375 goto ret;
1376
1377 ret:
1378 error = GetLastError();
1379
1380 if (revert_to_old_token)
1381 win32_revert_to_token(old_token);
1382
1383 if (impersonate_privilege_enabled)
1384 win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege);
1385
1386 if (lsass_token)
1387 CloseHandle(lsass_token);
1388
1389 SetLastError(error);
1390
1391 return ret;
1392 }
1393