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, §ion_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