]> mj.ucw.cz Git - pciutils.git/blob - lib/i386-io-windows.h
eff1901ca00f794f3a32f0bbbed2ed4f5c4d409b
[pciutils.git] / lib / i386-io-windows.h
1 /*
2  *      The PCI Library -- Access to i386 I/O ports on Windows
3  *
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>
7  *
8  *      Can be freely distributed and used under the terms of the GNU GPL v2+
9  *
10  *      SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include <windows.h>
14 #include "win32-helpers.h"
15
16 #include "i386-io-access.h"
17
18 /*
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.
29  *
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.
36  */
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)
42 static unsigned int
43 __readeflags(void)
44 {
45   __asm {
46     pushfd
47     pop eax
48   }
49 }
50 #elif defined(__GNUC__)
51 static inline unsigned
52 #ifdef __x86_64__
53 long long
54 #endif
55 int
56 __readeflags(void)
57 {
58   unsigned
59 #ifdef __x86_64__
60   long long
61 #endif
62   int eflags;
63   asm volatile ("pushf\n\tpop %0\n" : "=r" (eflags));
64   return eflags;
65 }
66 #else
67 #error "Unsupported compiler"
68 #endif
69
70 /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */
71 #define read_iopl() ((__readeflags() >> 12) & 0x3)
72
73 /* Unfortunately some toolchains do not provide this constant. */
74 #ifndef SE_IMPERSONATE_NAME
75 #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
76 #endif
77
78 /*
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.
84  */
85 #ifndef NTSTATUS
86 #define NTSTATUS LONG
87 #endif
88 #ifndef STATUS_NOT_IMPLEMENTED
89 #define STATUS_NOT_IMPLEMENTED (NTSTATUS)0xC0000002
90 #endif
91 #ifndef STATUS_PRIVILEGE_NOT_HELD
92 #define STATUS_PRIVILEGE_NOT_HELD (NTSTATUS)0xC0000061
93 #endif
94 #ifndef PROCESSINFOCLASS
95 #define PROCESSINFOCLASS DWORD
96 #endif
97 #ifndef ProcessUserModeIOPL
98 #define ProcessUserModeIOPL 16
99 #endif
100 typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength);
101 typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status);
102
103 /*
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.
107  */
108 static BOOL
109 CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg)
110 {
111   LUID luid_tcb_privilege;
112   LUID luid_impersonate_privilege;
113
114   HANDLE revert_token_tcb_privilege;
115   BOOL revert_only_tcb_privilege;
116
117   HANDLE revert_token_impersonate_privilege;
118   BOOL revert_only_impersonate_privilege;
119
120   BOOL impersonate_privilege_enabled;
121
122   BOOL revert_to_old_token;
123   HANDLE old_token;
124
125   HANDLE lsass_process;
126   HANDLE lsass_token;
127
128   BOOL ret;
129
130   impersonate_privilege_enabled = FALSE;
131   revert_to_old_token = FALSE;
132   lsass_token = NULL;
133   old_token = NULL;
134
135   /* Call supplied function. */
136   ret = Func(Arg);
137   if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
138     goto ret;
139
140   /*
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.
144    */
145
146   if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege))
147     goto err_privilege_not_held;
148
149   /*
150    * If the current thread has already Tcb privilege enabled then there
151    * is some additional unhanded restriction.
152    */
153   if (win32_have_privilege(luid_tcb_privilege))
154     goto err_privilege_not_held;
155
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))
158     {
159       ret = Func(Arg);
160       win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
161       goto ret;
162     }
163
164   /*
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.
168    */
169
170   /*
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.
174    */
175   if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) &&
176       !win32_have_privilege(luid_impersonate_privilege))
177     {
178       /*
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.
185        */
186       if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege))
187         {
188           impersonate_privilege_enabled = TRUE;
189         }
190       else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL))
191         {
192           impersonate_privilege_enabled = TRUE;
193           revert_token_impersonate_privilege = NULL;
194           revert_only_impersonate_privilege = TRUE;
195         }
196       else
197         {
198           goto err_privilege_not_held;
199         }
200
201       /*
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
205        * could now pass.
206        */
207       if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege))
208         {
209           ret = Func(Arg);
210           win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege);
211           goto ret;
212         }
213     }
214
215   /*
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.
219    *
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).
224    */
225
226   lsass_process = win32_find_and_open_process_for_query("lsass.exe");
227   if (!lsass_process)
228     goto err_privilege_not_held;
229
230   /*
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!).
234    */
235   lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE);
236
237   CloseHandle(lsass_process);
238
239   if (!lsass_token)
240     goto err_privilege_not_held;
241
242   /*
243    * After successful open of the primary lsass.exe process access token,
244    * assign its copy for the current thread.
245    */
246   if (!win32_change_token(lsass_token, &old_token))
247     goto err_privilege_not_held;
248
249   revert_to_old_token = TRUE;
250
251   ret = Func(Arg);
252   if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD)
253     goto ret;
254
255   /*
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.
261    */
262   if (win32_have_privilege(luid_tcb_privilege) ||
263       !win32_enable_privilege(luid_tcb_privilege, NULL, NULL))
264     goto err_privilege_not_held;
265
266   ret = Func(Arg);
267   goto ret;
268
269 err_privilege_not_held:
270   SetLastError(ERROR_PRIVILEGE_NOT_HELD);
271   ret = FALSE;
272   goto ret;
273
274 ret:
275   if (revert_to_old_token)
276     win32_revert_to_token(old_token);
277
278   if (impersonate_privilege_enabled)
279     win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege);
280
281   if (lsass_token)
282     CloseHandle(lsass_token);
283
284   return ret;
285 }
286
287 /*
288  * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL
289  * of the current running process to 3.
290  *
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.
295  *
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.
299  */
300 static BOOL
301 SetProcessUserModeIOPLFunc(LPVOID Arg)
302 {
303   RtlNtStatusToDosErrorProt RtlNtStatusToDosErrorPtr = (RtlNtStatusToDosErrorProt)(((LPVOID *)Arg)[1]);
304   NtSetInformationProcessProt NtSetInformationProcessPtr = (NtSetInformationProcessProt)(((LPVOID *)Arg)[0]);
305   NTSTATUS nt_status = NtSetInformationProcessPtr(GetCurrentProcess(), ProcessUserModeIOPL, NULL, 0);
306   if (nt_status >= 0)
307     return TRUE;
308
309   /*
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.
314    */
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);
321   else
322     SetLastError(ERROR_GEN_FAILURE);
323
324   return FALSE;
325 }
326
327 /*
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
331  * privilege.
332  */
333 static BOOL
334 SetProcessUserModeIOPL(VOID)
335 {
336   LPVOID Arg[2];
337   UINT prev_error_mode;
338   HMODULE ntdll;
339   BOOL ret;
340
341   /*
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.
345    */
346   prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS);
347   ntdll = LoadLibrary(TEXT("ntdll.dll"));
348   win32_change_error_mode(prev_error_mode);
349   if (!ntdll)
350     {
351       SetLastError(ERROR_INVALID_FUNCTION);
352       return FALSE;
353     }
354
355   /* Retrieve pointer to NtSetInformationProcess() function. */
356   Arg[0] = (LPVOID)GetProcAddress(ntdll, "NtSetInformationProcess");
357   if (!Arg[0])
358     {
359       FreeLibrary(ntdll);
360       SetLastError(ERROR_INVALID_FUNCTION);
361       return FALSE;
362     }
363
364   /* Retrieve pointer to optional RtlNtStatusToDosError() function, it may be NULL. */
365   Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError");
366
367   /* Call ProcessUserModeIOPL with Tcb privilege. */
368   ret = CallFuncWithTcbPrivilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg);
369
370   FreeLibrary(ntdll);
371
372   if (!ret)
373     return FALSE;
374
375   /*
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.
380    */
381   if (read_iopl() != 3)
382     {
383       SetLastError(ERROR_INVALID_FUNCTION);
384       return FALSE;
385     }
386
387   return TRUE;
388 }
389
390 static int
391 intel_setup_io(struct pci_access *a)
392 {
393 #ifndef _WIN64
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())
396     {
397       a->debug("Detected 16/32-bit non-NT system, skipping NT setup...");
398       return 1;
399     }
400 #endif
401
402   /* Check if we have I/O permission */
403   if (read_iopl() == 3)
404     {
405       a->debug("IOPL is already set to 3, skipping NT setup...");
406       return 1;
407     }
408
409   /* On NT-based systems issue ProcessUserModeIOPL syscall which changes IOPL to 3. */
410   if (!SetProcessUserModeIOPL())
411     {
412       DWORD error = GetLastError();
413       a->debug("NT ProcessUserModeIOPL call failed: %s.", error == ERROR_INVALID_FUNCTION ? "Call is not supported" : win32_strerror(error));
414       return 0;
415     }
416
417   a->debug("NT ProcessUserModeIOPL call succeeded...");
418   return 1;
419 }
420
421 static inline void
422 intel_cleanup_io(struct pci_access *a UNUSED)
423 {
424   /*
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.
428    */
429 }
430
431 static inline void intel_io_lock(void)
432 {
433 }
434
435 static inline void intel_io_unlock(void)
436 {
437 }