2 * The PCI Library -- Access to i386 I/O ports on Windows
4 * Copyright (c) 2004 Alexander Stock <stock.alexander@gmx.de>
5 * Copyright (c) 2006 Martin Mares <mj@ucw.cz>
6 * Copyright (c) 2021 Pali Rohár <pali@kernel.org>
8 * Can be freely distributed and used under the terms of the GNU GPL v2+
10 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include "win32-helpers.h"
16 #include "i386-io-access.h"
19 * Define __readeflags() for MSVC and GCC compilers.
20 * MSVC since version 14.00 included in WDK 6001 and since version 15.00
21 * included in VS 2008 provides __readeflags() intrinsic for both 32 and 64-bit
22 * modes. WDK 6001 defines macro __BUILDMACHINE__ to value WinDDK. VS 2008 does
23 * not define this macro at all. MSVC throws error if name of user defined
24 * function conflicts with some MSVC intrinsic.
25 * MSVC supports inline assembly via __asm keyword in 32-bit mode only.
26 * GCC version 4.9.0 and higher provides __builtin_ia32_readeflags_uXX()
27 * builtin for XX-mode. This builtin is also available as __readeflags()
28 * function indirectly via <x86intrin.h> header file.
30 * CAVEAT: Semicolon in MSVC __asm block means start of the comment, and not
31 * end of the __asm statement, like it is for all other C statements. Also
32 * function which uses MSVC inline assembly cannot be inlined to another function
33 * (compiler reports a warning about it, not a fatal error). So we add explicit
34 * curly brackets for __asm blocks, remove misleading semicolons and do not
35 * declare functions as inline.
37 #if defined(_MSC_VER) && (_MSC_VER >= 1500 || (_MSC_VER >= 1400 && defined(__BUILDMACHINE__)))
38 #pragma intrinsic(__readeflags)
39 #elif defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4))
40 #include <x86intrin.h>
41 #elif defined(_MSC_VER) && defined(_M_IX86)
50 #elif defined(__GNUC__)
51 static inline unsigned
63 asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags));
67 #error "Unsupported compiler"
70 /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */
71 #define read_iopl() ((__readeflags() >> 12) & 0x3)
73 /* Unfortunately some toolchains do not provide this constant. */
74 #ifndef SE_IMPERSONATE_NAME
75 #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
79 * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL
80 * constant and all other helpers for its usage are not specified in any
81 * standard WinAPI header file. So define all of required constants and types.
82 * Function NtSetInformationProcess() is available in ntdll.dll library on all
83 * Windows systems but marked as it can be removed in some future version.
88 #ifndef STATUS_NOT_IMPLEMENTED
89 #define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002
91 #ifndef STATUS_PRIVILEGE_NOT_HELD
92 #define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061
94 #ifndef PROCESSINFOCLASS
95 #define PROCESSINFOCLASS DWORD
97 #ifndef ProcessUserModeIOPL
98 #define ProcessUserModeIOPL 16
100 typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength);
101 typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status);
104 * Call supplied function Func with its Arg and if it fails with
105 * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and
106 * call Func with its Arg again.
109 CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg)
111 LUID luid_tcb_privilege;
112 LUID luid_impersonate_privilege;
114 HANDLE revert_token_tcb_privilege;
115 BOOL revert_only_tcb_privilege;
117 HANDLE revert_token_impersonate_privilege;
118 BOOL revert_only_impersonate_privilege;
120 BOOL impersonate_privilege_enabled;
122 BOOL revert_to_old_token;
125 HANDLE lsass_process;
130 impersonate_privilege_enabled = FALSE;
131 revert_to_old_token = FALSE;
135 /* Call supplied function. */
137 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
141 * If function call failed with ERROR_PRIVILEGE_NOT_HELD
142 * error then it means that the current thread token does not have
143 * Tcb privilege enabled. Try to enable it.
146 if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege))
147 goto err_privilege_not_held;
150 * If the current thread has already Tcb privilege enabled then there
151 * is some additional unhanded restriction.
153 if (win32_have_privilege(luid_tcb_privilege))
154 goto err_privilege_not_held;
156 /* Try to enable Tcb privilege and try function call again. */
157 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
160 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
165 * If enabling of Tcb privilege failed then it means that current thread
166 * does not have this privilege. But current process may have it. So try it
167 * again with primary process access token.
171 * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then
172 * all future actions in this function require this Impersonate privilege.
173 * So try to enable it in case it is currently disabled.
175 if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) &&
176 !win32_have_privilege(luid_impersonate_privilege))
179 * If current thread does not have Impersonate privilege enabled
180 * then first try to enable it just for the current thread. If
181 * it is not possible to enable it just for the current thread
182 * then try it to enable globally for whole process (which
183 * affects all process threads). Both actions will be reverted
184 * at the end of this function.
186 if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege))
188 impersonate_privilege_enabled = TRUE;
190 else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL))
192 impersonate_privilege_enabled = TRUE;
193 revert_token_impersonate_privilege = NULL;
194 revert_only_impersonate_privilege = TRUE;
198 goto err_privilege_not_held;
202 * Now when Impersonate privilege is enabled, try to enable Tcb
203 * privilege again. Enabling other privileges for the current
204 * thread requires Impersonate privilege, so enabling Tcb again
207 if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
210 win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
216 * If enabling Tcb privilege failed then it means that the current
217 * thread access token does not have this privilege or does not
218 * have permission to adjust privileges.
220 * Try to use more privileged token from Local Security Authority
221 * Subsystem Service process (lsass.exe) which has Tcb privilege.
222 * Retrieving this more privileged token is possible for local
223 * administrators (unless it was disabled by local administrators).
226 lsass_process = win32_find_and_open_process_for_query("lsass.exe");
228 goto err_privilege_not_held;
231 * Open primary lsass.exe process access token with query and duplicate
232 * rights. Just these two rights are required for impersonating other
233 * primary process token (impersonate right is really not required!).
235 lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE);
237 CloseHandle(lsass_process);
240 goto err_privilege_not_held;
243 * After successful open of the primary lsass.exe process access token,
244 * assign its copy for the current thread.
246 if (!win32_change_token(lsass_token, &old_token))
247 goto err_privilege_not_held;
249 revert_to_old_token = TRUE;
252 if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
256 * Now current thread is not using primary process token anymore
257 * but is using custom access token. There is no need to revert
258 * enabled Tcb privilege as the whole custom access token would
259 * be reverted. So there is no need to setup revert method for
260 * enabling privilege.
262 if (win32_have_privilege(luid_tcb_privilege) ||
263 !win32_enable_privilege(luid_tcb_privilege, NULL, NULL))
264 goto err_privilege_not_held;
269 err_privilege_not_held:
270 SetLastError(ERROR_PRIVILEGE_NOT_HELD);
275 if (revert_to_old_token)
276 win32_revert_to_token(old_token);
278 if (impersonate_privilege_enabled)
279 win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege);
282 CloseHandle(lsass_token);
288 * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
289 * of the current running process to 3.
291 * Process handle argument for ProcessUserModeIOPL is ignored and
292 * IOPL is always changed for the current running process. So pass
293 * GetCurrentProcess() handle for documentation purpose. Process
294 * information buffer and length are unused for ProcessUserModeIOPL.
296 * ProcessUserModeIOPL may success (return value >= 0) or may fail
297 * because it is not implemented or because of missing privilege.
298 * Other errors are not defined, so handle them as unknown.
301 SetProcessUserModeIOPLFunc(LPVOID Arg)
303 RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]);
304 NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]);
305 NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
310 * If we have optional RtlNtStatusToDosError() function then use it for
311 * translating NT status to Win32 error. If we do not have it then translate
312 * two important status codes which we use later STATUS_NOT_IMPLEMENTED and
313 * STATUS_PRIVILEGE_NOT_HELD.
315 if (RtlNtStatusToDosErrorPtr)
316 SetLastError(RtlNtStatusToDosErrorPtr(nt_status));
317 else if (nt_status == STATUS_NOT_IMPLEMENTED)
318 SetLastError(ERROR_INVALID_FUNCTION);
319 else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
320 SetLastError(ERROR_PRIVILEGE_NOT_HELD);
322 SetLastError(ERROR_GEN_FAILURE);
328 * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via
329 * NtSetInformationProcess() call with ProcessUserModeIOPL information class,
330 * which is supported by 32-bit Windows NT kernel versions and requires Tcb
334 SetProcessUserModeIOPL(VOID)
337 UINT prev_error_mode;
342 * Load ntdll.dll library with disabled critical-error-handler message box.
343 * It means that NT kernel does not show unwanted GUI message box to user
344 * when LoadLibrary() function fails.
346 prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
347 ntdll = LoadLibrary(TEXT("ntdll.dll"));
348 win32_change_error_mode(prev_error_mode);
351 SetLastError(ERROR_INVALID_FUNCTION);
355 /* Retrieve pointer to NtSetInformationProcess() function. */
356 Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
360 SetLastError(ERROR_INVALID_FUNCTION);
364 /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */
365 Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError");
367 /* Call ProcessUserModeIOPL with Tcb privilege. */
368 ret = CallFuncWithTcbPrivilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg);
376 * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not
377 * implement ProcessUserModeIOPL syscall at all but incorrectly
378 * returns success when it is called by user process. So always
379 * after this call verify that IOPL is set to 3.
381 if (read_iopl() != 3)
383 SetLastError(ERROR_INVALID_FUNCTION);
391 intel_setup_io(struct pci_access *a)
394 /* 16/32-bit non-NT systems allow applications to access PCI I/O ports without any special setup. */
395 if (win32_is_non_nt_system())
397 a->debug("Detected 16/32-bit non-NT system, skipping NT setup...");
402 /* Check if we have I/O permission */
403 if (read_iopl() == 3)
405 a->debug("IOPL is already set to 3, skipping NT setup...");
409 /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */
410 if (!SetProcessUserModeIOPL())
412 DWORD error = GetLastError();
413 a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error));
417 a->debug("NT ProcessUserModeIOPL call succeeded...");
422 intel_cleanup_io(struct pci_access *a UNUSED)
425 * 16/32-bit non-NT systems do not use any special setup and on NT-based
426 * systems ProcessUserModeIOPL permanently changes IOPL to 3 for the current
427 * NT process, no revert for current process is possible.
431 static inline void intel_io_lock(void)
435 static inline void intel_io_unlock(void)