xref: /pciutils/lib/physmem-windows.c (revision ea02a2ff)
1 /*
2  *      The PCI Library -- Physical memory mapping for Windows systems
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 "internal.h"
12 
13 #include <windows.h>
14 #include <errno.h>
15 #include <stdlib.h>
16 
17 #include "physmem.h"
18 #include "win32-helpers.h"
19 
20 #ifndef NTSTATUS
21 #define NTSTATUS LONG
22 #endif
23 #ifndef STATUS_INVALID_HANDLE
24 #define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008)
25 #endif
26 #ifndef STATUS_INVALID_PARAMETER
27 #define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D)
28 #endif
29 #ifndef STATUS_CONFLICTING_ADDRESSES
30 #define STATUS_CONFLICTING_ADDRESSES ((NTSTATUS)0xC0000018)
31 #endif
32 #ifndef STATUS_NOT_MAPPED_VIEW
33 #define STATUS_NOT_MAPPED_VIEW ((NTSTATUS)0xC0000019)
34 #endif
35 #ifndef STATUS_INVALID_VIEW_SIZE
36 #define STATUS_INVALID_VIEW_SIZE ((NTSTATUS)0xC000001F)
37 #endif
38 #ifndef STATUS_ACCESS_DENIED
39 #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022)
40 #endif
41 #ifndef STATUS_OBJECT_NAME_NOT_FOUND
42 #define STATUS_OBJECT_NAME_NOT_FOUND ((NTSTATUS)0xC0000034)
43 #endif
44 #ifndef STATUS_INVALID_PAGE_PROTECTION
45 #define STATUS_INVALID_PAGE_PROTECTION ((NTSTATUS)0xC0000045)
46 #endif
47 #ifndef STATUS_SECTION_PROTECTION
48 #define STATUS_SECTION_PROTECTION ((NTSTATUS)0xC000004E)
49 #endif
50 #ifndef STATUS_INSUFFICIENT_RESOURCES
51 #define STATUS_INSUFFICIENT_RESOURCES ((NTSTATUS)0xC000009A)
52 #endif
53 #ifndef STATUS_INVALID_PARAMETER_3
54 #define STATUS_INVALID_PARAMETER_3 ((NTSTATUS)0xC00000F1)
55 #endif
56 #ifndef STATUS_INVALID_PARAMETER_4
57 #define STATUS_INVALID_PARAMETER_4 ((NTSTATUS)0xC00000F2)
58 #endif
59 #ifndef STATUS_INVALID_PARAMETER_5
60 #define STATUS_INVALID_PARAMETER_5 ((NTSTATUS)0xC00000F3)
61 #endif
62 #ifndef STATUS_INVALID_PARAMETER_8
63 #define STATUS_INVALID_PARAMETER_8 ((NTSTATUS)0xC00000F6)
64 #endif
65 #ifndef STATUS_INVALID_PARAMETER_9
66 #define STATUS_INVALID_PARAMETER_9 ((NTSTATUS)0xC00000F7)
67 #endif
68 #ifndef STATUS_MAPPED_ALIGNMENT
69 #define STATUS_MAPPED_ALIGNMENT ((NTSTATUS)0xC0000220)
70 #endif
71 
72 #ifndef OBJ_CASE_INSENSITIVE
73 #define OBJ_CASE_INSENSITIVE 0x00000040L
74 #endif
75 
76 #ifndef SECTION_INHERIT
77 #define SECTION_INHERIT ULONG
78 #endif
79 #ifndef ViewUnmap
80 #define ViewUnmap ((SECTION_INHERIT)2)
81 #endif
82 
83 #ifndef IMAGE_NT_OPTIONAL_HDR_MAGIC
84 #ifdef _WIN64
85 #define IMAGE_NT_OPTIONAL_HDR_MAGIC 0x20b
86 #else
87 #define IMAGE_NT_OPTIONAL_HDR_MAGIC 0x10b
88 #endif
89 #endif
90 
91 #ifndef EOVERFLOW
92 #define EOVERFLOW 132
93 #endif
94 
95 #if _WIN32_WINNT < 0x0500
96 typedef ULONG ULONG_PTR;
97 typedef ULONG_PTR SIZE_T, *PSIZE_T;
98 #endif
99 
100 #ifndef __UNICODE_STRING_DEFINED
101 #define __UNICODE_STRING_DEFINED
102 typedef struct _UNICODE_STRING {
103   USHORT Length;
104   USHORT MaximumLength;
105   PWSTR  Buffer;
106 } UNICODE_STRING, *PUNICODE_STRING;
107 #endif
108 
109 #ifndef __OBJECT_ATTRIBUTES_DEFINED
110 #define __OBJECT_ATTRIBUTES_DEFINED
111 typedef struct _OBJECT_ATTRIBUTES {
112   ULONG Length;
113   HANDLE RootDirectory;
114   PUNICODE_STRING ObjectName;
115   ULONG Attributes;
116   PVOID SecurityDescriptor;
117   PVOID SecurityQualityOfService;
118 } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
119 #endif
120 
121 #ifndef InitializeObjectAttributes
122 #define InitializeObjectAttributes(p, n, a, r, s) \
123 { \
124   (p)->Length = sizeof(OBJECT_ATTRIBUTES); \
125   (p)->RootDirectory = (r); \
126   (p)->Attributes = (a); \
127   (p)->ObjectName = (n); \
128   (p)->SecurityDescriptor = (s); \
129   (p)->SecurityQualityOfService = NULL; \
130 }
131 #endif
132 
133 #ifndef RtlInitUnicodeString
134 #define RtlInitUnicodeString(d, s) \
135 { \
136   (d)->Length = wcslen(s) * sizeof(WCHAR); \
137   (d)->MaximumLength = (d)->Length + sizeof(WCHAR); \
138   (d)->Buffer = (PWCHAR)(s); \
139 }
140 #endif
141 
142 #define VWIN32_DEVICE_ID 0x0002A /* from vmm.h */
143 #define WIN32_SERVICE_ID(device, function) (((device) << 16) | (function))
144 #define VWIN32_Int31Dispatch WIN32_SERVICE_ID(VWIN32_DEVICE_ID, 0x29)
145 #define DPMI_PHYSICAL_ADDRESS_MAPPING 0x0800
146 
147 struct physmem {
148   HANDLE section_handle;
149   NTSTATUS (NTAPI *NtOpenSection)(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes);
150   NTSTATUS (NTAPI *NtMapViewOfSection)(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect);
151   NTSTATUS (NTAPI *NtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress);
152   ULONG (NTAPI *RtlNtStatusToDosError)(NTSTATUS Status);
153 #if defined(__i386__) || defined(_M_IX86)
154   DWORD (WINAPI *VxDCall2)(DWORD Service, DWORD Arg1, DWORD Arg2);
155   LPVOID w32skrnl_dpmi_lcall_ptr;
156   DWORD base_addr_offset;
157 #endif
158 };
159 
160 #if defined(__i386__) || defined(_M_IX86)
161 
162 static BOOL
w32skrnl_physical_address_mapping(struct physmem * physmem,DWORD phys_addr,DWORD size,DWORD * virt_addr)163 w32skrnl_physical_address_mapping(struct physmem *physmem, DWORD phys_addr, DWORD size, DWORD *virt_addr)
164 {
165   DWORD address_hi = phys_addr >> 16;
166   DWORD address_lo = phys_addr & 0xffff;
167   DWORD size_hi = size >> 16;
168   DWORD size_lo = size & 0xffff;
169   BYTE failed;
170 
171   /*
172    * Physical address mapping via w32skrnl.dll on Windows maps physical memory
173    * and translates it to the virtual space of the current process memory.
174    * Works only for aligned address / length and first 1 MB cannot be mapped
175    * by this method. Expect that first 1 MB is already 1:1 mapped by the OS.
176    * So accept request for physical memory range which is whole below 1 MB
177    * without error and return virtual address same as the physical one.
178    */
179   if (phys_addr < 1*1024*1024UL)
180     {
181       if ((u64)phys_addr + size > 1*1024*1024UL)
182         {
183           errno = ENXIO;
184           return FALSE;
185         }
186 
187       *virt_addr = phys_addr;
188       return TRUE;
189     }
190 
191   /*
192    * Unfortunately w32skrnl.dll provides only 48-bit fword pointer to physical
193    * address mapping function and such pointer type is not supported by GCC,
194    * nor by MSVC and therefore it is not possible call this function in C code.
195    * So call this function with all parameters passed via inline assembly.
196    */
197 #if defined(__GNUC__)
198   asm volatile (
199     "stc\n\t"
200     "lcall *(%3)\n\t"
201     "setc %0\n\t"
202       : "=qm" (failed), "+b" (address_hi), "+c" (address_lo)
203       : "r" (physmem->w32skrnl_dpmi_lcall_ptr), "a" (DPMI_PHYSICAL_ADDRESS_MAPPING), "S" (size_hi), "D" (size_lo)
204       : "cc", "memory"
205   );
206 #elif defined(_MSC_VER)
207   __asm {
208     mov esi, size_hi
209     mov edi, size_lo
210     mov ebx, address_hi
211     mov ecx, address_lo
212     mov eax, DPMI_PHYSICAL_ADDRESS_MAPPING
213     stc
214     mov edx, physmem
215     mov edx, [edx]physmem.w32skrnl_dpmi_lcall_ptr
216     call fword ptr [edx]
217     setc failed
218     mov address_hi, ebx
219     mov address_lo, ecx
220   }
221 #else
222 #error "Unsupported compiler"
223 #endif
224 
225   /*
226    * Windows does not provide any error code when this function call fails.
227    * So set errno just to the generic EACCES value.
228    */
229   if (failed)
230     {
231       errno = EACCES;
232       return FALSE;
233     }
234 
235   *virt_addr = ((address_hi & 0xffff) << 16) | (address_lo & 0xffff);
236   return TRUE;
237 }
238 
239 #if defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || (__GNUC__ > 4)) && (__GNUC__ <= 11)
240 /*
241  * GCC versions 4.8 - 11 are buggy and throw error "'asm' operand has impossible
242  * constraints" for inline assembly when optimizations (O1+) are enabled. So for
243  * these GCC versions disable buggy optimizations by enforcing O0 optimize flag
244  * affecting just this one function.
245  */
246 __attribute__((optimize("O0")))
247 #endif
248 static BOOL
vdxcall_physical_address_mapping(struct physmem * physmem,DWORD phys_addr,DWORD size,DWORD * virt_addr)249 vdxcall_physical_address_mapping(struct physmem *physmem, DWORD phys_addr, DWORD size, DWORD *virt_addr)
250 {
251   DWORD address_hi = phys_addr >> 16;
252   DWORD address_lo = phys_addr & 0xffff;
253   DWORD size_hi = size >> 16;
254   DWORD size_lo = size & 0xffff;
255   BYTE failed;
256 
257   /*
258    * Physical address mapping via VxDCall2() on Windows maps physical memory
259    * and translates it to the virtual space of the current process memory.
260    * There are no restrictions for aligning or physical address ranges.
261    * Works with any (unaligned) address or length, including low 1MB range.
262    */
263 
264   /*
265    * Function VxDCall2() has strange calling convention. First 3 arguments are
266    * passed on stack, which callee pops (same as stdcall convention) but rest
267    * of the function arguments are passed in ESI, EDI and EBX registers. And
268    * return value is in carry flag (CF) and AX, BX and CX registers. GCC and
269    * neither MSVC do not support this strange calling convention, so call this
270    * function via inline assembly.
271    *
272    * Pseudocode with stdcall calling convention of that function looks like:
273    * ESI = size_hi
274    * EDI = size_lo
275    * EBX = address_hi
276    * VxDCall2(VWIN32_Int31Dispatch, DPMI_PHYSICAL_ADDRESS_MAPPING, address_lo)
277    * failed = CF
278    * address_hi = BX (if not failed)
279    * address_lo = CX (if not failed)
280    */
281 
282 #if defined(__GNUC__)
283   asm volatile (
284     "pushl %6\n\t"
285     "pushl %5\n\t"
286     "pushl %4\n\t"
287     "stc\n\t"
288     "call *%P3\n\t"
289     "setc %0\n\t"
290       : "=qm" (failed), "+b" (address_hi), "=c" (address_lo)
291       : "rmi" (physmem->VxDCall2), "rmi" (VWIN32_Int31Dispatch), "rmi" (DPMI_PHYSICAL_ADDRESS_MAPPING),
292         "rmi" (address_lo), "S" (size_hi), "D" (size_lo)
293 /* Specify all call clobbered scratch registers for stdcall calling convention. */
294       : "eax", "edx",
295 /*
296  * Since GCC version 4.9.0 it is possible to specify x87 registers as clobbering
297  * if they are not disabled by -mno-80387 switch (which defines _SOFT_FLOAT).
298  */
299 #if ((__GNUC__ == 4 && __GNUC_MINOR__ >= 9) || (__GNUC__ > 4)) && !defined(_SOFT_FLOAT)
300         "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)",
301 #endif
302 #ifdef __MMX__
303         "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6",
304 #endif
305 #ifdef __SSE__
306         "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
307 #endif
308         "cc", "memory"
309   );
310 #elif defined(_MSC_VER)
311   __asm {
312     mov esi, size_hi
313     mov edi, size_lo
314     mov ebx, address_hi
315     push address_lo
316     push DPMI_PHYSICAL_ADDRESS_MAPPING
317     push VWIN32_Int31Dispatch
318     stc
319     mov eax, physmem
320     call [eax]physmem.VxDCall2
321     setc failed
322     mov address_hi, ebx
323     mov address_lo, ecx
324   }
325 #else
326 #error "Unsupported compiler"
327 #endif
328 
329   /*
330    * Windows does not provide any error code when this function call fails.
331    * So set errno just to the generic EACCES value.
332    */
333   if (failed)
334     {
335       errno = EACCES;
336       return FALSE;
337     }
338 
339   *virt_addr = ((address_hi & 0xffff) << 16) | (address_lo & 0xffff);
340   return TRUE;
341 }
342 
343 static BOOL
win32_get_physmem_offset(DWORD * offset)344 win32_get_physmem_offset(DWORD *offset)
345 {
346   WORD DSsel;
347   LDT_ENTRY DSentry;
348 
349   /*
350    * Read DS selector. For this purpose there is WinAPI function and when called
351    * as GetThreadContext(GetCurrentThread(), ...) with CONTEXT_SEGMENTS param,
352    * it fills SegDs value. But on some Windows versions, GetThreadContext() can
353    * be called only for threads attached to debugger. Hence we cannot use it for
354    * our current thread. So instead read DS selector directly from ds register
355    * via inline assembly code.
356    */
357 #if defined(__GNUC__)
358   asm ("movw %%ds, %w0" : "=rm" (DSsel));
359 #elif defined(_MSC_VER)
360   __asm { mov DSsel, ds }
361 #else
362 #error "Unsupported compiler"
363 #endif
364 
365   if (!GetThreadSelectorEntry(GetCurrentThread(), DSsel, &DSentry))
366     return FALSE;
367 
368   *offset = DSentry.BaseLow | (DSentry.HighWord.Bytes.BaseMid << 0x10) | (DSentry.HighWord.Bytes.BaseHi << 0x18);
369   return TRUE;
370 }
371 
372 static BYTE *
win32_get_baseaddr_from_hmodule(HMODULE module)373 win32_get_baseaddr_from_hmodule(HMODULE module)
374 {
375   WORD (WINAPI *ImteFromHModule)(HMODULE);
376   BYTE *(WINAPI *BaseAddrFromImte)(WORD);
377   HMODULE w32skrnl;
378   WORD imte;
379 
380   if ((GetVersion() & 0xC0000000) != 0x80000000)
381     return (BYTE *)module;
382 
383   w32skrnl = GetModuleHandleA("w32skrnl.dll");
384   if (!w32skrnl)
385     return NULL;
386 
387   ImteFromHModule = (LPVOID)GetProcAddress(w32skrnl, "_ImteFromHModule@4");
388   BaseAddrFromImte = (LPVOID)GetProcAddress(w32skrnl, "_BaseAddrFromImte@4");
389   if (!ImteFromHModule || !BaseAddrFromImte)
390     return NULL;
391 
392   imte = ImteFromHModule(module);
393   if (imte == 0xffff)
394     return NULL;
395 
396   return BaseAddrFromImte(imte);
397 }
398 
399 static FARPROC
win32_get_proc_address_by_ordinal(HMODULE module,DWORD ordinal,BOOL must_be_without_name)400 win32_get_proc_address_by_ordinal(HMODULE module, DWORD ordinal, BOOL must_be_without_name)
401 {
402   BYTE *baseaddr;
403   IMAGE_DOS_HEADER *dos_header;
404   IMAGE_NT_HEADERS *nt_header;
405   DWORD export_dir_offset, export_dir_size;
406   IMAGE_EXPORT_DIRECTORY *export_dir;
407   DWORD base_ordinal, func_count;
408   DWORD *func_addrs;
409   FARPROC func_ptr;
410   DWORD names_count, i;
411   USHORT *names_idxs;
412   UINT prev_error_mode;
413   char module_name[MAX_PATH];
414   DWORD module_name_len;
415   char *export_name;
416   char *endptr;
417   long num;
418 
419   baseaddr = win32_get_baseaddr_from_hmodule(module);
420   if (!baseaddr)
421     return NULL;
422 
423   dos_header = (IMAGE_DOS_HEADER *)baseaddr;
424   if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
425     return NULL;
426 
427   nt_header = (IMAGE_NT_HEADERS *)((BYTE *)dos_header + dos_header->e_lfanew);
428   if (nt_header->Signature != IMAGE_NT_SIGNATURE)
429     return NULL;
430 
431   if (nt_header->FileHeader.SizeOfOptionalHeader < offsetof(IMAGE_OPTIONAL_HEADER, DataDirectory))
432     return NULL;
433 
434   if (nt_header->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
435     return NULL;
436 
437   if (nt_header->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT)
438     return NULL;
439 
440   export_dir_offset = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
441   export_dir_size = nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
442 
443   if (!export_dir_offset || !export_dir_size)
444     return NULL;
445 
446   export_dir = (IMAGE_EXPORT_DIRECTORY *)(baseaddr + export_dir_offset);
447   base_ordinal = export_dir->Base;
448   func_count = export_dir->NumberOfFunctions;
449   func_addrs = (DWORD *)(baseaddr + (DWORD)export_dir->AddressOfFunctions);
450 
451   if (ordinal < base_ordinal || ordinal - base_ordinal > func_count)
452     return NULL;
453 
454   if (must_be_without_name)
455     {
456       /* Check that function with ordinal number does not have any name. */
457       names_count = export_dir->NumberOfNames;
458       names_idxs = (USHORT *)(baseaddr + (DWORD)export_dir->AddressOfNameOrdinals);
459       for (i = 0; i < names_count; i++)
460         {
461           if (names_idxs[i] == ordinal - base_ordinal)
462             return NULL;
463         }
464     }
465 
466   func_ptr = (FARPROC)(baseaddr + func_addrs[ordinal - base_ordinal]);
467   if ((BYTE *)func_ptr >= (BYTE *)export_dir && (BYTE *)func_ptr < (BYTE *)export_dir + export_dir_size)
468     {
469       /*
470        * We need to locate the _last_ dot character (separator of library name
471        * and export symbol name) because library name may contain dot character
472        * (used when specifying file name with explicit extension). For example
473        * wine is using this kind of strange symbol redirection to different
474        * library with non-standard file name extension (different than .dll).
475        */
476       export_name = strrchr((char *)func_ptr, '.');
477       if (!export_name)
478         return NULL;
479       export_name++;
480 
481       module_name_len = export_name - 1 - (char *)func_ptr;
482       if (module_name_len >= sizeof(module_name))
483         return NULL;
484 
485       memcpy(module_name, func_ptr, module_name_len);
486       module_name[module_name_len] = 0;
487 
488       prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
489       module = LoadLibraryA(module_name);
490       win32_change_error_mode(prev_error_mode);
491       if (!module)
492         {
493           FreeLibrary(module);
494           return NULL;
495         }
496 
497       if (*export_name == '#')
498         {
499           export_name++;
500           errno = 0;
501           num = strtol(export_name, &endptr, 10);
502           if (*export_name < '0' || *export_name > '9' || errno || *endptr || num < 0 || (unsigned long)num >= ((DWORD)-1)/2)
503             {
504               FreeLibrary(module);
505               return NULL;
506             }
507           ordinal = num;
508           func_ptr = win32_get_proc_address_by_ordinal(module, ordinal, FALSE);
509         }
510       else
511         {
512           func_ptr = GetProcAddress(module, export_name);
513         }
514 
515       if (!func_ptr)
516         FreeLibrary(module);
517     }
518 
519   return func_ptr;
520 }
521 
522 static int
init_physmem_w32skrnl(struct physmem * physmem,struct pci_access * a)523 init_physmem_w32skrnl(struct physmem *physmem, struct pci_access *a)
524 {
525   HMODULE w32skrnl;
526   LPVOID (WINAPI *GetThunkBuff)(VOID);
527   OSVERSIONINFOA version;
528   LPVOID buf_ptr;
529 
530   a->debug("resolving DPMI function via GetThunkBuff() function from w32skrnl.dll...");
531   w32skrnl = GetModuleHandleA("w32skrnl.dll");
532   if (!w32skrnl)
533     {
534       a->debug("failed: library not present.");
535       errno = ENOENT;
536       return 0;
537     }
538 
539   GetThunkBuff = (LPVOID)GetProcAddress(w32skrnl, "_GetThunkBuff@0");
540   if (!GetThunkBuff)
541     {
542       a->debug("failed: symbol not found.");
543       errno = ENOENT;
544       return 0;
545     }
546 
547   version.dwOSVersionInfoSize = sizeof(version);
548   if (!GetVersionExA(&version))
549     {
550       a->debug("failed: cannot detect version.");
551       errno = EINVAL;
552       return 0;
553     }
554 
555   /* Versions before 1.1 (1.1.88) are not supported. */
556   if (version.dwMajorVersion < 1 || (version.dwMajorVersion == 1 && version.dwMinorVersion < 1))
557     {
558       a->debug("failed: found old incompatible version.");
559       errno = ENOENT;
560       return 0;
561     }
562 
563   if (!win32_get_physmem_offset(&physmem->base_addr_offset))
564     {
565       a->debug("failed: cannot retrieve physical address offset: %s.", win32_strerror(GetLastError()));
566       errno = EINVAL;
567       return 0;
568     }
569 
570   buf_ptr = GetThunkBuff();
571   if (!buf_ptr)
572     {
573       a->debug("failed: cannot retrieve DPMI function pointer.");
574       errno = EINVAL;
575       return 0;
576     }
577 
578   /*
579    * Versions 1.1 (1.1.88) - 1.15 (1.15.103) have DPMI function at offset 0xa0.
580    * Versions 1.15a (1.15.111) - 1.30c (1.30.172) have DPMI function at offset 0xa4.
581    */
582   if (version.dwMajorVersion > 1 ||
583       (version.dwMajorVersion == 1 && version.dwMinorVersion > 15) ||
584       (version.dwMajorVersion == 1 && version.dwMinorVersion == 15 && version.dwBuildNumber >= 111))
585     physmem->w32skrnl_dpmi_lcall_ptr = (LPVOID)((BYTE *)buf_ptr + 0xa4);
586   else
587     physmem->w32skrnl_dpmi_lcall_ptr = (LPVOID)((BYTE *)buf_ptr + 0xa0);
588 
589   a->debug("success.");
590   return 1;
591 }
592 
593 static int
init_physmem_vxdcall(struct physmem * physmem,struct pci_access * a)594 init_physmem_vxdcall(struct physmem *physmem, struct pci_access *a)
595 {
596   HMODULE kernel32;
597   BOOL success;
598   DWORD addr;
599 
600   a->debug("resolving VxDCall2() function from kernel32.dll...");
601   kernel32 = GetModuleHandleA("kernel32.dll");
602   if (!kernel32)
603     {
604       a->debug("failed: library not present.");
605       errno = ENOENT;
606       return 0;
607     }
608 
609   /*
610    * New Windows versions do not export VxDCall2 symbol by name anymore,
611    * so try also locating this symbol by its ordinal number, which is 3.
612    * Old Windows versions prevents using GetProcAddress() for locating
613    * kernel32.dll symbol by ordinal number, so use our own custom function.
614    * When locating via ordinal number, check that this ordinal number does
615    * not have any name assigned (to ensure that it is really VxDCall2).
616    */
617   physmem->VxDCall2 = (LPVOID)GetProcAddress(kernel32, "VxDCall2");
618   if (!physmem->VxDCall2)
619     physmem->VxDCall2 = (LPVOID)win32_get_proc_address_by_ordinal(kernel32, 3, TRUE);
620 
621   if (!physmem->VxDCall2)
622     {
623       a->debug("failed: symbol not found.");
624       errno = ENOENT;
625       return 0;
626     }
627 
628   /*
629    * Wine implementation of VxDCall2() does not support physical address
630    * mapping but returns success with virtual address same as passed physical
631    * address. Detect this broken wine behavior by trying to map zero address
632    * of zero range. Broken wine implementation returns NULL pointer. This
633    * prevents accessing unmapped memory or dereferencing NULL pointer.
634    */
635   success = vdxcall_physical_address_mapping(physmem, 0x0, 0x0, &addr);
636   if (success && addr == 0x0)
637     {
638       a->debug("failed: physical address mapping via VxDCall2() is broken.");
639       physmem->VxDCall2 = NULL;
640       errno = EINVAL;
641       return 0;
642     }
643   else if (!success)
644     {
645       a->debug("failed: physical address mapping via VxDCall2() is unsupported.");
646       physmem->VxDCall2 = NULL;
647       errno = ENOENT;
648       return 0;
649     }
650 
651   /* Retrieve base address - offset for all addresses returned by VxDCall2(). */
652   if (!win32_get_physmem_offset(&physmem->base_addr_offset))
653     {
654       a->debug("failed: cannot retrieve physical address offset: %s.", win32_strerror(GetLastError()));
655       physmem->VxDCall2 = NULL;
656       errno = EINVAL;
657       return 0;
658     }
659 
660   a->debug("success.");
661   return 1;
662 }
663 
664 #endif
665 
666 static int
init_physmem_ntdll(struct physmem * physmem,struct pci_access * a,const char * filename,int w)667 init_physmem_ntdll(struct physmem *physmem, struct pci_access *a, const char *filename, int w)
668 {
669   wchar_t *wide_filename;
670   UNICODE_STRING unicode_filename;
671   OBJECT_ATTRIBUTES attributes;
672   NTSTATUS status;
673   HMODULE ntdll;
674   int len;
675 
676   a->debug("resolving section functions from ntdll.dll...");
677   ntdll = GetModuleHandle(TEXT("ntdll.dll"));
678   if (!ntdll)
679     {
680       a->debug("failed: library ntdll.dll is not present.");
681       errno = ENOENT;
682       return 0;
683     }
684 
685   physmem->RtlNtStatusToDosError = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError");
686 
687   physmem->NtOpenSection = (LPVOID)GetProcAddress(ntdll, "NtOpenSection");
688   if (!physmem->NtOpenSection)
689     {
690       a->debug("failed: function NtOpenSection() not found.");
691       errno = ENOENT;
692       return 0;
693     }
694 
695   physmem->NtMapViewOfSection = (LPVOID)GetProcAddress(ntdll, "NtMapViewOfSection");
696   if (!physmem->NtMapViewOfSection)
697     {
698       a->debug("failed: function NtMapViewOfSection() not found.");
699       errno = ENOENT;
700       return 0;
701     }
702 
703   physmem->NtUnmapViewOfSection = (LPVOID)GetProcAddress(ntdll, "NtUnmapViewOfSection");
704   if (!physmem->NtUnmapViewOfSection)
705     {
706       a->debug("failed: function NtUnmapViewOfSection() not found.");
707       errno = ENOENT;
708       return 0;
709     }
710 
711   a->debug("success.");
712 
713   /*
714    * Note: It is not possible to use WinAPI function OpenFileMappingA() because
715    * it takes path relative to the NT base path \\Sessions\\X\\BaseNamedObjects\\
716    * and so it does not support to open sections outside that NT directory.
717    * NtOpenSection() does not have this restriction and supports specifying any
718    * path, including path in absolute format. Unfortunately NtOpenSection()
719    * takes path in UNICODE_STRING structure, unlike OpenFileMappingA() which
720    * takes path in 8-bit char*. So first it is needed to do conversion from
721    * char* string to wchar_t* string via function MultiByteToWideChar() and
722    * then fill UNICODE_STRING structure from that wchar_t* string via function
723    * RtlInitUnicodeString().
724    */
725 
726   len = MultiByteToWideChar(CP_ACP, 0, filename, -1, NULL, 0);
727   if (len <= 0)
728     {
729       a->debug("Option devmem.path '%s' is invalid multibyte string.", filename);
730       errno = EINVAL;
731       return 0;
732     }
733 
734   wide_filename = pci_malloc(a, len * sizeof(wchar_t));
735   len = MultiByteToWideChar(CP_ACP, 0, filename, -1, wide_filename, len);
736   if (len <= 0)
737     {
738       a->debug("Option devmem.path '%s' is invalid multibyte string.", filename);
739       pci_mfree(wide_filename);
740       errno = EINVAL;
741       return 0;
742     }
743 
744   RtlInitUnicodeString(&unicode_filename, wide_filename);
745   InitializeObjectAttributes(&attributes, &unicode_filename, OBJ_CASE_INSENSITIVE, NULL, NULL);
746 
747   a->debug("trying to open NT Section %s in %s mode...", filename, w ? "read/write" : "read-only");
748   physmem->section_handle = INVALID_HANDLE_VALUE;
749   status = physmem->NtOpenSection(&physmem->section_handle, SECTION_MAP_READ | (w ? SECTION_MAP_WRITE : 0), &attributes);
750 
751   pci_mfree(wide_filename);
752 
753   if (status < 0 || physmem->section_handle == INVALID_HANDLE_VALUE)
754     {
755       physmem->section_handle = INVALID_HANDLE_VALUE;
756       if (status == 0)
757         a->debug("failed.");
758       else if (physmem->RtlNtStatusToDosError)
759         a->debug("failed: %s (0x%lx).", win32_strerror(physmem->RtlNtStatusToDosError(status)), status);
760       else
761         a->debug("failed: 0x%lx.", status);
762       switch (status)
763         {
764         case STATUS_INVALID_PARAMETER: /* SectionHandle or ObjectAttributes parameter is invalid */
765           errno = EINVAL;
766           break;
767         case STATUS_OBJECT_NAME_NOT_FOUND: /* Section name in ObjectAttributes.ObjectName does not exist */
768           errno = ENOENT;
769           break;
770         case STATUS_ACCESS_DENIED: /* No permission to access Section name in ObjectAttributes.ObjectName */
771           errno = EACCES;
772           break;
773         default: /* Other unspecified error */
774           errno = EINVAL;
775           break;
776         }
777       return 0;
778     }
779 
780   a->debug("success.");
781   return 1;
782 }
783 
784 void
physmem_init_config(struct pci_access * a)785 physmem_init_config(struct pci_access *a)
786 {
787   pci_define_param(a, "devmem.path", PCI_PATH_DEVMEM_DEVICE, "NT path to the PhysicalMemory NT Section"
788 #if defined(__i386__) || defined(_M_IX86)
789     " or \"vxdcall\" or \"w32skrnl\""
790 #endif
791   );
792 }
793 
794 int
physmem_access(struct pci_access * a,int w)795 physmem_access(struct pci_access *a, int w)
796 {
797   struct physmem *physmem = physmem_open(a, w);
798   if (!physmem)
799     return -1;
800   physmem_close(physmem);
801   return 0;
802 }
803 
804 struct physmem *
physmem_open(struct pci_access * a,int w)805 physmem_open(struct pci_access *a, int w)
806 {
807   const char *devmem = pci_get_param(a, "devmem.path");
808 #if defined(__i386__) || defined(_M_IX86)
809   int force_vxdcall = strcmp(devmem, "vxdcall") == 0;
810   int force_w32skrnl = strcmp(devmem, "w32skrnl") == 0;
811 #endif
812   struct physmem *physmem = pci_malloc(a, sizeof(*physmem));
813 
814   memset(physmem, 0, sizeof(*physmem));
815   physmem->section_handle = INVALID_HANDLE_VALUE;
816 
817   errno = ENOENT;
818 
819   if (
820 #if defined(__i386__) || defined(_M_IX86)
821       !force_vxdcall && !force_w32skrnl &&
822 #endif
823       init_physmem_ntdll(physmem, a, devmem, w))
824     return physmem;
825 
826 #if defined(__i386__) || defined(_M_IX86)
827   if (!force_w32skrnl && init_physmem_vxdcall(physmem, a))
828     return physmem;
829 
830   if (!force_vxdcall && init_physmem_w32skrnl(physmem, a))
831     return physmem;
832 #endif
833 
834   a->debug("no windows method for physical memory access.");
835   pci_mfree(physmem);
836   return NULL;
837 }
838 
839 void
physmem_close(struct physmem * physmem)840 physmem_close(struct physmem *physmem)
841 {
842   if (physmem->section_handle != INVALID_HANDLE_VALUE)
843     CloseHandle(physmem->section_handle);
844   pci_mfree(physmem);
845 }
846 
847 long
physmem_get_pagesize(struct physmem * physmem UNUSED)848 physmem_get_pagesize(struct physmem *physmem UNUSED)
849 {
850   SYSTEM_INFO system_info;
851   system_info.dwPageSize = 0;
852   GetSystemInfo(&system_info);
853   return system_info.dwPageSize;
854 }
855 
856 void *
physmem_map(struct physmem * physmem,u64 addr,size_t length,int w)857 physmem_map(struct physmem *physmem, u64 addr, size_t length, int w)
858 {
859   LARGE_INTEGER section_offset;
860   NTSTATUS status;
861   SIZE_T view_size;
862   VOID *ptr;
863 
864   if (physmem->section_handle != INVALID_HANDLE_VALUE)
865     {
866       /*
867        * Note: Do not use WinAPI function MapViewOfFile() because it makes memory
868        * mapping available also for all child processes that are spawned in the
869        * future. NtMapViewOfSection() allows to specify ViewUnmap parameter which
870        * creates mapping just for this process and not for future child processes.
871        * For security reasons we do not want this physical address mapping to be
872        * present also in future spawned processes.
873        */
874       ptr = NULL;
875       section_offset.QuadPart = addr;
876       view_size = length;
877       status = physmem->NtMapViewOfSection(physmem->section_handle, GetCurrentProcess(), &ptr, 0, 0, &section_offset, &view_size, ViewUnmap, 0, w ? PAGE_READWRITE : PAGE_READONLY);
878       if (status < 0)
879         {
880           switch (status)
881             {
882             case STATUS_INVALID_HANDLE: /* Invalid SectionHandle (physmem->section_handle) */
883             case STATUS_INVALID_PARAMETER_3: /* Invalid BaseAddress parameter (&ptr) */
884             case STATUS_CONFLICTING_ADDRESSES: /* Invalid value of BaseAddress pointer (ptr) */
885             case STATUS_MAPPED_ALIGNMENT: /* Invalid value of BaseAddress pointer (ptr) or SectionOffset (section_offset) */
886             case STATUS_INVALID_PARAMETER_4: /* Invalid ZeroBits parameter (0) */
887             case STATUS_INVALID_PARAMETER_5: /* Invalid CommitSize parameter (0) */
888             case STATUS_INVALID_PARAMETER_8: /* Invalid InheritDisposition parameter (ViewUnmap) */
889             case STATUS_INVALID_PARAMETER_9: /* Invalid AllocationType parameter (0) */
890             case STATUS_SECTION_PROTECTION: /* Invalid Protect parameter (based on w) */
891             case STATUS_INVALID_PAGE_PROTECTION: /* Invalid Protect parameter (based on w) */
892               errno = EINVAL;
893               break;
894             case STATUS_INVALID_VIEW_SIZE: /* Invalid SectionOffset / ViewSize range (section_offset, view_size) */
895               errno = ENXIO;
896               break;
897             case STATUS_INSUFFICIENT_RESOURCES: /* Quota limit exceeded */
898             case STATUS_NO_MEMORY: /* Memory limit exceeded */
899               errno = ENOMEM;
900               break;
901             case STATUS_ACCESS_DENIED: /* No permission to create mapping */
902               errno = EPERM;
903               break;
904             default: /* Other unspecified error */
905               errno = EACCES;
906               break;
907             }
908           return (void *)-1;
909         }
910 
911       return ptr;
912     }
913 #if defined(__i386__) || defined(_M_IX86)
914   else if (physmem->VxDCall2 || physmem->w32skrnl_dpmi_lcall_ptr)
915     {
916       BOOL success;
917       DWORD virt;
918 
919       /*
920        * These two methods support mapping only the first 4 GB of physical memory
921        * and mapped memory is always read/write. There is no way to create
922        * read-only mapping, so argument "w" is ignored.
923        */
924       if (addr >= 0xffffffffUL || addr + length > 0xffffffffUL)
925         {
926           errno = EOVERFLOW;
927           return (void *)-1;
928         }
929 
930       if (physmem->VxDCall2)
931         success = vdxcall_physical_address_mapping(physmem, (DWORD)addr, length, &virt);
932       else
933         success = w32skrnl_physical_address_mapping(physmem, (DWORD)addr, length, &virt);
934 
935       /* Both above functions set errno on failure. */
936       if (!success)
937         return (void *)-1;
938 
939       /* Virtual address from our view is calculated from the base offset. */
940       ptr = (VOID *)(virt - physmem->base_addr_offset);
941       return ptr;
942     }
943 #endif
944 
945 
946   /* invalid physmem parameter */
947   errno = EBADF;
948   return (void *)-1;
949 }
950 
951 int
physmem_unmap(struct physmem * physmem,void * ptr,size_t length)952 physmem_unmap(struct physmem *physmem, void *ptr, size_t length)
953 {
954   long pagesize = physmem_get_pagesize(physmem);
955   MEMORY_BASIC_INFORMATION info;
956   NTSTATUS status;
957 
958   if (physmem->section_handle != INVALID_HANDLE_VALUE)
959     {
960       /*
961        * NtUnmapViewOfSection() unmaps entire memory range previously mapped by
962        * NtMapViewOfSection(). The specified ptr (BaseAddress) does not have to
963        * point to the beginning of the mapped memory range.
964        *
965        * So verify that the ptr argument is the beginning of the mapped range
966        * and length argument is the length of mapped range.
967        */
968 
969       if (VirtualQuery(ptr, &info, sizeof(info)) != sizeof(info))
970         {
971           errno = EINVAL;
972           return -1;
973         }
974 
975       /* RegionSize is already page aligned, but length does not have to be. */
976       if (info.AllocationBase != ptr || info.RegionSize != ((length + pagesize-1) & ~(pagesize-1)))
977         {
978           errno = EINVAL;
979           return -1;
980         }
981 
982       status = physmem->NtUnmapViewOfSection(GetCurrentProcess(), ptr);
983       if (status < 0)
984         {
985           switch (status)
986             {
987             case STATUS_NOT_MAPPED_VIEW: /* BaseAddress parameter (ptr) not mapped */
988               errno = EINVAL;
989               break;
990             case STATUS_ACCESS_DENIED: /* No permission to unmap BaseAddress (ptr) */
991               errno = EPERM;
992               break;
993             default: /* Other unspecified error */
994               errno = EACCES;
995               break;
996             }
997           return -1;
998         }
999 
1000       return 0;
1001     }
1002 #if defined(__i386__) || defined(_M_IX86)
1003   else if (physmem->VxDCall2 || physmem->w32skrnl_dpmi_lcall_ptr)
1004     {
1005       /* There is no way to unmap physical memory mapped by these methods. */
1006       errno = ENOSYS;
1007       return -1;
1008     }
1009 #endif
1010 
1011   /* invalid physmem parameter */
1012   errno = EBADF;
1013   return -1;
1014 }
1015