aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPali Rohár <pali@kernel.org>2023-05-07 13:32:54 +0200
committerMartin Mares <mj@ucw.cz>2024-02-18 15:48:29 +0100
commitde65a6f92973eb2aeb241f0c46e06e17a57a3c60 (patch)
treedf616a2e5e3d19fe7ddce2cdae7d05a243816d4f
parentf87084246bceb65947d320bd5d53f35d14619b44 (diff)
downloadpciutils-de65a6f92973eb2aeb241f0c46e06e17a57a3c60.tar.gz
windows: Split code for enabling Tcb privilege and calling ProcessUserModeIOPL
Code for enabling Tcb privilege is split from SetProcessUserModeIOPL() into new function CallFuncWithTcbPrivilege().
-rw-r--r--lib/i386-io-windows.h239
1 files changed, 125 insertions, 114 deletions
diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h
index a776bd5..5bb26bb 100644
--- a/lib/i386-io-windows.h
+++ b/lib/i386-io-windows.h
@@ -984,16 +984,13 @@ open_process_token_with_rights(HANDLE process, DWORD rights)
}
/*
- * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via
- * NtSetInformationProcess() call with ProcessUserModeIOPL information class,
- * which is supported by 32-bit Windows NT kernel versions and requires Tcb
- * privilege.
+ * Call supplied function Func with its Arg and if it fails with
+ * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and
+ * call Func with its Arg again.
*/
static BOOL
-SetProcessUserModeIOPL(VOID)
+CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg)
{
- NtSetInformationProcessProt MyNtSetInformationProcess;
-
LUID luid_tcb_privilege;
LUID luid_impersonate_privilege;
@@ -1011,9 +1008,6 @@ SetProcessUserModeIOPL(VOID)
HANDLE lsass_process;
HANDLE lsass_token;
- UINT prev_error_mode;
- NTSTATUS nt_status;
- HMODULE ntdll;
BOOL ret;
impersonate_privilege_enabled = FALSE;
@@ -1021,49 +1015,13 @@ SetProcessUserModeIOPL(VOID)
lsass_token = NULL;
old_token = NULL;
- /* Fast path when ProcessUserModeIOPL was already called. */
- if (read_iopl() == 3)
- return TRUE;
-
- /*
- * Load ntdll.dll library with disabled critical-error-handler message box.
- * It means that NT kernel does not show unwanted GUI message box to user
- * when LoadLibrary() function fails.
- */
- prev_error_mode = change_error_mode(SEM_FAILCRITICALERRORS);
- ntdll = LoadLibrary(TEXT("ntdll.dll"));
- change_error_mode(prev_error_mode);
- if (!ntdll)
- goto err_not_implemented;
-
- /* Retrieve pointer to NtSetInformationProcess() function. */
- MyNtSetInformationProcess = (NtSetInformationProcessProt)(LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
- if (!MyNtSetInformationProcess)
- goto err_not_implemented;
+ /* Call supplied function. */
+ ret = Func(Arg);
+ if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
+ goto ret;
/*
- * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
- * of the current running process to 3.
- *
- * Process handle argument for ProcessUserModeIOPL is ignored and
- * IOPL is always changed for the current running process. So pass
- * GetCurrentProcess() handle for documentation purpose. Process
- * information buffer and length are unused for ProcessUserModeIOPL.
- *
- * ProcessUserModeIOPL may success (return value >= 0) or may fail
- * because it is not implemented or because of missing privilege.
- * Other errors are not defined, so handle them as unknown.
- */
- nt_status = MyNtSetInformationProcess(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
- if (nt_status >= 0)
- goto verify;
- else if (nt_status == STATUS_NOT_IMPLEMENTED)
- goto err_not_implemented;
- else if (nt_status != STATUS_PRIVILEGE_NOT_HELD)
- goto err_unknown;
-
- /*
- * If ProcessUserModeIOPL call failed with STATUS_PRIVILEGE_NOT_HELD
+ * If function call failed with ERROR_PRIVILEGE_NOT_HELD
* error then it means that the current thread token does not have
* Tcb privilege enabled. Try to enable it.
*/
@@ -1078,24 +1036,17 @@ SetProcessUserModeIOPL(VOID)
if (have_privilege(luid_tcb_privilege))
goto err_privilege_not_held;
- /* Try to enable Tcb privilege and try ProcessUserModeIOPL call again. */
+ /* Try to enable Tcb privilege and try function call again. */
if (enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
{
- nt_status = MyNtSetInformationProcess(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
+ ret = Func(Arg);
revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
- if (nt_status >= 0)
- goto verify;
- else if (nt_status == STATUS_NOT_IMPLEMENTED)
- goto err_not_implemented;
- else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
- goto err_privilege_not_held;
- else
- goto err_unknown;
+ goto ret;
}
/*
* If enabling of Tcb privilege failed then it means that current thread
- * does not this privilege. But current process may have it. So try it
+ * does not have this privilege. But current process may have it. So try it
* again with primary process access token.
*/
@@ -1138,16 +1089,9 @@ SetProcessUserModeIOPL(VOID)
*/
if (enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
{
- nt_status = MyNtSetInformationProcess(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
+ ret = Func(Arg);
revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
- if (nt_status >= 0)
- goto verify;
- else if (nt_status == STATUS_NOT_IMPLEMENTED)
- goto err_not_implemented;
- else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
- goto err_privilege_not_held;
- else
- goto err_unknown;
+ goto ret;
}
}
@@ -1187,45 +1131,22 @@ SetProcessUserModeIOPL(VOID)
revert_to_old_token = TRUE;
- nt_status = MyNtSetInformationProcess(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
- if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
- {
- /*
- * Now current thread is not using primary process token anymore
- * but is using custom access token. There is no need to revert
- * enabled Tcb privilege as the whole custom access token would
- * be reverted. So there is no need to setup revert method for
- * enabling privilege.
- */
- if (have_privilege(luid_tcb_privilege) ||
- !enable_privilege(luid_tcb_privilege, NULL, NULL))
- goto err_privilege_not_held;
- nt_status = MyNtSetInformationProcess(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
- }
- if (nt_status >= 0)
- goto verify;
- else if (nt_status == STATUS_NOT_IMPLEMENTED)
- goto err_not_implemented;
- else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
- goto err_privilege_not_held;
- else
- goto err_unknown;
+ ret = Func(Arg);
+ if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
+ goto ret;
-verify:
/*
- * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not
- * implement ProcessUserModeIOPL syscall at all but incorrectly
- * returns success when it is called by user process. So always
- * after this call verify that IOPL is set to 3.
+ * Now current thread is not using primary process token anymore
+ * but is using custom access token. There is no need to revert
+ * enabled Tcb privilege as the whole custom access token would
+ * be reverted. So there is no need to setup revert method for
+ * enabling privilege.
*/
- if (read_iopl() != 3)
- goto err_not_implemented;
- ret = TRUE;
- goto ret;
+ if (have_privilege(luid_tcb_privilege) ||
+ !enable_privilege(luid_tcb_privilege, NULL, NULL))
+ goto err_privilege_not_held;
-err_not_implemented:
- SetLastError(ERROR_INVALID_FUNCTION);
- ret = FALSE;
+ ret = Func(Arg);
goto ret;
err_privilege_not_held:
@@ -1233,11 +1154,6 @@ err_privilege_not_held:
ret = FALSE;
goto ret;
-err_unknown:
- SetLastError(ERROR_GEN_FAILURE);
- ret = FALSE;
- goto ret;
-
ret:
if (revert_to_old_token)
revert_to_token(old_token);
@@ -1248,12 +1164,100 @@ ret:
if (lsass_token)
CloseHandle(lsass_token);
- if (ntdll)
- FreeLibrary(ntdll);
-
return ret;
}
+/*
+ * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
+ * of the current running process to 3.
+ *
+ * Process handle argument for ProcessUserModeIOPL is ignored and
+ * IOPL is always changed for the current running process. So pass
+ * GetCurrentProcess() handle for documentation purpose. Process
+ * information buffer and length are unused for ProcessUserModeIOPL.
+ *
+ * ProcessUserModeIOPL may success (return value >= 0) or may fail
+ * because it is not implemented or because of missing privilege.
+ * Other errors are not defined, so handle them as unknown.
+ */
+static BOOL
+SetProcessUserModeIOPLFunc(LPVOID Arg)
+{
+ NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)Arg;
+ NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
+ if (nt_status >= 0)
+ return TRUE;
+
+ if (nt_status == STATUS_NOT_IMPLEMENTED)
+ SetLastError(ERROR_INVALID_FUNCTION);
+ else if (nt_status == STATUS_PRIVILEGE_NOT_HELD)
+ SetLastError(ERROR_PRIVILEGE_NOT_HELD);
+ else /* TODO: convert NT STATUS to WIN32 ERROR */
+ SetLastError(ERROR_GEN_FAILURE);
+
+ return FALSE;
+}
+
+/*
+ * Set x86 I/O Privilege Level to 3 for the whole current NT process. Do it via
+ * NtSetInformationProcess() call with ProcessUserModeIOPL information class,
+ * which is supported by 32-bit Windows NT kernel versions and requires Tcb
+ * privilege.
+ */
+static BOOL
+SetProcessUserModeIOPL(VOID)
+{
+ NtSetInformationProcessProt NtSetInformationProcessPtr;
+ UINT prev_error_mode;
+ HMODULE ntdll;
+ BOOL ret;
+
+ /*
+ * Load ntdll.dll library with disabled critical-error-handler message box.
+ * It means that NT kernel does not show unwanted GUI message box to user
+ * when LoadLibrary() function fails.
+ */
+ prev_error_mode = change_error_mode(SEM_FAILCRITICALERRORS);
+ ntdll = LoadLibrary(TEXT("ntdll.dll"));
+ change_error_mode(prev_error_mode);
+ if (!ntdll)
+ {
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return FALSE;
+ }
+
+ /* Retrieve pointer to NtSetInformationProcess() function. */
+ NtSetInformationProcessPtr = (NtSetInformationProcessProt)(LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
+ if (!NtSetInformationProcessPtr)
+ {
+ FreeLibrary(ntdll);
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return FALSE;
+ }
+
+ /* Call ProcessUserModeIOPL with Tcb privilege. */
+ ret = CallFuncWithTcbPrivilege(SetProcessUserModeIOPLFunc, (LPVOID)NtSetInformationProcessPtr);
+
+ FreeLibrary(ntdll);
+
+ if (!ret)
+ return FALSE;
+
+ /*
+ * Some Windows NT kernel versions (e.g. Windows 2003 x64) do not
+ * implement ProcessUserModeIOPL syscall at all but incorrectly
+ * returns success when it is called by user process. So always
+ * after this call verify that IOPL is set to 3.
+ */
+ if (read_iopl() != 3)
+ {
+ SetLastError(ERROR_INVALID_FUNCTION);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
static int
intel_setup_io(struct pci_access *a)
{
@@ -1268,6 +1272,13 @@ intel_setup_io(struct pci_access *a)
}
#endif
+ /* Check if we have I/O permission */
+ if (read_iopl() == 3)
+ {
+ a->debug("IOPL is already set to 3, skipping NT setup...");
+ return 1;
+ }
+
/* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */
if (!SetProcessUserModeIOPL())
{