VirtualBox

source: vbox/trunk/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp@ 62457

Last change on this file since 62457 was 62457, checked in by vboxsync, 8 years ago

5.0,4.3,trunk: build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 99.2 KB
Line 
1/* $Id: SUPHardenedVerifyProcess-win.cpp 62457 2016-07-22 16:06:22Z vboxsync $ */
2/** @file
3 * VirtualBox Support Library/Driver - Hardened Process Verification, Windows.
4 */
5
6/*
7 * Copyright (C) 2006-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#ifdef IN_RING0
32# define IPRT_NT_MAP_TO_ZW
33# include <iprt/nt/nt.h>
34# include <ntimage.h>
35#else
36# include <iprt/nt/nt-and-windows.h>
37#endif
38
39#include <VBox/sup.h>
40#include <VBox/err.h>
41#include <iprt/alloca.h>
42#include <iprt/ctype.h>
43#include <iprt/param.h>
44#include <iprt/string.h>
45#include <iprt/zero.h>
46
47#ifdef IN_RING0
48# include "SUPDrvInternal.h"
49#else
50# include "SUPLibInternal.h"
51#endif
52#include "win/SUPHardenedVerify-win.h"
53
54
55/*********************************************************************************************************************************
56* Structures and Typedefs *
57*********************************************************************************************************************************/
58/**
59 * Virtual address space region.
60 */
61typedef struct SUPHNTVPREGION
62{
63 /** The RVA of the region. */
64 uint32_t uRva;
65 /** The size of the region. */
66 uint32_t cb;
67 /** The protection of the region. */
68 uint32_t fProt;
69} SUPHNTVPREGION;
70/** Pointer to a virtual address space region. */
71typedef SUPHNTVPREGION *PSUPHNTVPREGION;
72
73/**
74 * Virtual address space image information.
75 */
76typedef struct SUPHNTVPIMAGE
77{
78 /** The base address of the image. */
79 uintptr_t uImageBase;
80 /** The size of the image mapping. */
81 uintptr_t cbImage;
82
83 /** The name from the allowed lists. */
84 const char *pszName;
85 /** Name structure for NtQueryVirtualMemory/MemorySectionName. */
86 struct
87 {
88 /** The full unicode name. */
89 UNICODE_STRING UniStr;
90 /** Buffer space. */
91 WCHAR awcBuffer[260];
92 } Name;
93
94 /** The number of mapping regions. */
95 uint32_t cRegions;
96 /** Mapping regions. */
97 SUPHNTVPREGION aRegions[16];
98
99 /** The image characteristics from the FileHeader. */
100 uint16_t fImageCharecteristics;
101 /** The DLL characteristics from the OptionalHeader. */
102 uint16_t fDllCharecteristics;
103
104 /** Set if this is the DLL. */
105 bool fDll;
106 /** Set if the image is NTDLL an the verficiation code needs to watch out for
107 * the NtCreateSection patch. */
108 bool fNtCreateSectionPatch;
109 /** Whether the API set schema hack needs to be applied when verifying memory
110 * content. The hack means that we only check if the 1st section is mapped. */
111 bool fApiSetSchemaOnlySection1;
112 /** This may be a 32-bit resource DLL. */
113 bool f32bitResourceDll;
114
115 /** Pointer to the loader cache entry for the image. */
116 PSUPHNTLDRCACHEENTRY pCacheEntry;
117#ifdef IN_RING0
118 /** In ring-0 we don't currently cache images, so put it here. */
119 SUPHNTLDRCACHEENTRY CacheEntry;
120#endif
121} SUPHNTVPIMAGE;
122/** Pointer to image info from the virtual address space scan. */
123typedef SUPHNTVPIMAGE *PSUPHNTVPIMAGE;
124
125/**
126 * Virtual address space scanning state.
127 */
128typedef struct SUPHNTVPSTATE
129{
130 /** Type of verification to perform. */
131 SUPHARDNTVPKIND enmKind;
132 /** Combination of SUPHARDNTVP_F_XXX. */
133 uint32_t fFlags;
134 /** The result. */
135 int rcResult;
136 /** Number of fixes we've done.
137 * Only applicable in the purification modes. */
138 uint32_t cFixes;
139 /** Number of images in aImages. */
140 uint32_t cImages;
141 /** The index of the last image we looked up. */
142 uint32_t iImageHint;
143 /** The process handle. */
144 HANDLE hProcess;
145 /** Images found in the process.
146 * The array is large enough to hold the executable, all allowed DLLs, and one
147 * more so we can get the image name of the first unwanted DLL. */
148 SUPHNTVPIMAGE aImages[1 + 6 + 1
149#ifdef VBOX_PERMIT_VERIFIER_DLL
150 + 1
151#endif
152#ifdef VBOX_PERMIT_MORE
153 + 5
154#endif
155#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
156 + 16
157#endif
158 ];
159 /** Memory compare scratch buffer.*/
160 uint8_t abMemory[_4K];
161 /** File compare scratch buffer.*/
162 uint8_t abFile[_4K];
163 /** Section headers for use when comparing file and loaded image. */
164 IMAGE_SECTION_HEADER aSecHdrs[16];
165 /** Pointer to the error info. */
166 PRTERRINFO pErrInfo;
167} SUPHNTVPSTATE;
168/** Pointer to stat information of a virtual address space scan. */
169typedef SUPHNTVPSTATE *PSUPHNTVPSTATE;
170
171
172/*********************************************************************************************************************************
173* Global Variables *
174*********************************************************************************************************************************/
175/**
176 * System DLLs allowed to be loaded into the process.
177 * @remarks supHardNtVpCheckDlls assumes these are lower case.
178 */
179static const char *g_apszSupNtVpAllowedDlls[] =
180{
181 "ntdll.dll",
182 "kernel32.dll",
183 "kernelbase.dll",
184 "apphelp.dll",
185 "apisetschema.dll",
186#ifdef VBOX_PERMIT_VERIFIER_DLL
187 "verifier.dll",
188#endif
189#ifdef VBOX_PERMIT_MORE
190# define VBOX_PERMIT_MORE_FIRST_IDX 5
191 "sfc.dll",
192 "sfc_os.dll",
193 "user32.dll",
194 "acres.dll",
195 "acgenral.dll",
196#endif
197#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
198 "psapi.dll",
199 "msvcrt.dll",
200 "advapi32.dll",
201 "sechost.dll",
202 "rpcrt4.dll",
203 "SamplingRuntime.dll",
204#endif
205};
206
207/**
208 * VBox executables allowed to start VMs.
209 * @remarks Remember to keep in sync with g_aSupInstallFiles in
210 * SUPR3HardenedVerify.cpp.
211 */
212static const char *g_apszSupNtVpAllowedVmExes[] =
213{
214 "VBoxHeadless.exe",
215 "VirtualBox.exe",
216 "VBoxSDL.exe",
217 "VBoxNetDHCP.exe",
218 "VBoxNetNAT.exe",
219 "VBoxVMMPreload.exe",
220
221 "tstMicro.exe",
222 "tstPDMAsyncCompletion.exe",
223 "tstPDMAsyncCompletionStress.exe",
224 "tstVMM.exe",
225 "tstVMREQ.exe",
226 "tstCFGM.exe",
227 "tstGIP-2.exe",
228 "tstIntNet-1.exe",
229 "tstMMHyperHeap.exe",
230 "tstRTR0ThreadPreemptionDriver.exe",
231 "tstRTR0MemUserKernelDriver.exe",
232 "tstRTR0SemMutexDriver.exe",
233 "tstRTR0TimerDriver.exe",
234 "tstSSM.exe",
235};
236
237/** Pointer to NtQueryVirtualMemory. Initialized by SUPDrv-win.cpp in
238 * ring-0, in ring-3 it's just a slightly confusing define. */
239#ifdef IN_RING0
240PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory = NULL;
241#else
242# define g_pfnNtQueryVirtualMemory NtQueryVirtualMemory
243#endif
244
245#ifdef IN_RING3
246/** The number of valid entries in the loader cache. */
247static uint32_t g_cSupNtVpLdrCacheEntries = 0;
248/** The loader cache entries. */
249static SUPHNTLDRCACHEENTRY g_aSupNtVpLdrCacheEntries[RT_ELEMENTS(g_apszSupNtVpAllowedDlls) + 1 + 3];
250#endif
251
252
253/**
254 * Fills in error information.
255 *
256 * @returns @a rc.
257 * @param pErrInfo Pointer to the extended error info structure.
258 * Can be NULL.
259 * @param rc The status to return.
260 * @param pszMsg The format string for the message.
261 * @param ... The arguments for the format string.
262 */
263static int supHardNtVpSetInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...)
264{
265 va_list va;
266#ifdef IN_RING3
267 va_start(va, pszMsg);
268 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
269 va_end(va);
270#endif
271
272 va_start(va, pszMsg);
273 RTErrInfoSetV(pErrInfo, rc, pszMsg, va);
274 va_end(va);
275
276 return rc;
277}
278
279
280/**
281 * Adds error information.
282 *
283 * @returns @a rc.
284 * @param pErrInfo Pointer to the extended error info structure
285 * which may contain some details already. Can be
286 * NULL.
287 * @param rc The status to return.
288 * @param pszMsg The format string for the message.
289 * @param ... The arguments for the format string.
290 */
291static int supHardNtVpAddInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...)
292{
293 va_list va;
294#ifdef IN_RING3
295 va_start(va, pszMsg);
296 if (pErrInfo && pErrInfo->pszMsg)
297 supR3HardenedError(rc, false /*fFatal*/, "%N - %s\n", pszMsg, &va, pErrInfo->pszMsg);
298 else
299 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
300 va_end(va);
301#endif
302
303 va_start(va, pszMsg);
304 RTErrInfoAddV(pErrInfo, rc, pszMsg, va);
305 va_end(va);
306
307 return rc;
308}
309
310
311/**
312 * Fills in error information.
313 *
314 * @returns @a rc.
315 * @param pThis The process validator instance.
316 * @param rc The status to return.
317 * @param pszMsg The format string for the message.
318 * @param ... The arguments for the format string.
319 */
320static int supHardNtVpSetInfo2(PSUPHNTVPSTATE pThis, int rc, const char *pszMsg, ...)
321{
322 va_list va;
323#ifdef IN_RING3
324 va_start(va, pszMsg);
325 supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va);
326 va_end(va);
327#endif
328
329 va_start(va, pszMsg);
330#ifdef IN_RING0
331 RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va);
332 pThis->rcResult = rc;
333#else
334 if (RT_SUCCESS(pThis->rcResult))
335 {
336 RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va);
337 pThis->rcResult = rc;
338 }
339 else
340 {
341 RTErrInfoAddF(pThis->pErrInfo, rc, " \n[rc=%d] ", rc);
342 RTErrInfoAddV(pThis->pErrInfo, rc, pszMsg, va);
343 }
344#endif
345 va_end(va);
346
347 return pThis->rcResult;
348}
349
350
351static int supHardNtVpReadImage(PSUPHNTVPIMAGE pImage, uint64_t off, void *pvBuf, size_t cbRead)
352{
353 return pImage->pCacheEntry->pNtViRdr->Core.pfnRead(&pImage->pCacheEntry->pNtViRdr->Core, pvBuf, cbRead, off);
354}
355
356
357static NTSTATUS supHardNtVpReadMem(HANDLE hProcess, uintptr_t uPtr, void *pvBuf, size_t cbRead)
358{
359#ifdef IN_RING0
360 /* ASSUMES hProcess is the current process. */
361 /** @todo use MmCopyVirtualMemory where available! */
362 int rc = RTR0MemUserCopyFrom(pvBuf, uPtr, cbRead);
363 if (RT_SUCCESS(rc))
364 return STATUS_SUCCESS;
365 return STATUS_ACCESS_DENIED;
366#else
367 SIZE_T cbIgn;
368 NTSTATUS rcNt = NtReadVirtualMemory(hProcess, (PVOID)uPtr, pvBuf, cbRead, &cbIgn);
369 if (NT_SUCCESS(rcNt) && cbIgn != cbRead)
370 rcNt = STATUS_IO_DEVICE_ERROR;
371 return rcNt;
372#endif
373}
374
375
376#ifdef IN_RING3
377static NTSTATUS supHardNtVpFileMemRestore(PSUPHNTVPSTATE pThis, PVOID pvRestoreAddr, uint8_t const *pbFile, uint32_t cbToRestore,
378 uint32_t fCorrectProtection)
379{
380 PVOID pvProt = pvRestoreAddr;
381 SIZE_T cbProt = cbToRestore;
382 ULONG fOldProt = 0;
383 NTSTATUS rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_READWRITE, &fOldProt);
384 if (NT_SUCCESS(rcNt))
385 {
386 SIZE_T cbIgnored;
387 rcNt = NtWriteVirtualMemory(pThis->hProcess, pvRestoreAddr, pbFile, cbToRestore, &cbIgnored);
388
389 pvProt = pvRestoreAddr;
390 cbProt = cbToRestore;
391 NTSTATUS rcNt2 = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fCorrectProtection, &fOldProt);
392 if (NT_SUCCESS(rcNt))
393 rcNt = rcNt2;
394 }
395 pThis->cFixes++;
396 return rcNt;
397}
398#endif /* IN_RING3 */
399
400
401typedef struct SUPHNTVPSKIPAREA
402{
403 uint32_t uRva;
404 uint32_t cb;
405} SUPHNTVPSKIPAREA;
406typedef SUPHNTVPSKIPAREA *PSUPHNTVPSKIPAREA;
407
408static int supHardNtVpFileMemCompareSection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage,
409 uint32_t uRva, uint32_t cb, const uint8_t *pbFile,
410 int32_t iSh, PSUPHNTVPSKIPAREA paSkipAreas, uint32_t cSkipAreas,
411 uint32_t fCorrectProtection)
412{
413 AssertCompileAdjacentMembers(SUPHNTVPSTATE, abMemory, abFile); /* Use both the memory and file buffers here. Parfait might hate me for this... */
414 uint32_t const cbMemory = sizeof(pThis->abMemory) + sizeof(pThis->abFile);
415 uint8_t * const pbMemory = &pThis->abMemory[0];
416
417 while (cb > 0)
418 {
419 uint32_t cbThis = RT_MIN(cb, cbMemory);
420
421 /* Clipping. */
422 uint32_t uNextRva = uRva + cbThis;
423 if (cSkipAreas)
424 {
425 uint32_t uRvaEnd = uNextRva;
426 uint32_t i = cSkipAreas;
427 while (i-- > 0)
428 {
429 uint32_t uSkipEnd = paSkipAreas[i].uRva + paSkipAreas[i].cb;
430 if ( uRva < uSkipEnd
431 && uRvaEnd > paSkipAreas[i].uRva)
432 {
433 if (uRva < paSkipAreas[i].uRva)
434 {
435 cbThis = paSkipAreas[i].uRva - uRva;
436 uRvaEnd = paSkipAreas[i].uRva;
437 uNextRva = uSkipEnd;
438 }
439 else if (uRvaEnd >= uSkipEnd)
440 {
441 cbThis -= uSkipEnd - uRva;
442 pbFile += uSkipEnd - uRva;
443 uRva = uSkipEnd;
444 }
445 else
446 {
447 uNextRva = uSkipEnd;
448 cbThis = 0;
449 break;
450 }
451 }
452 }
453 }
454
455 /* Read the memory. */
456 NTSTATUS rcNt = supHardNtVpReadMem(pThis->hProcess, pImage->uImageBase + uRva, pbMemory, cbThis);
457 if (!NT_SUCCESS(rcNt))
458 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_READ_ERROR,
459 "%s: Error reading %#x bytes at %p (rva %#x, #%u, %.8s) from memory: %#x",
460 pImage->pszName, cbThis, pImage->uImageBase + uRva, uRva, iSh + 1,
461 iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers", rcNt);
462
463 /* Do the compare. */
464 if (memcmp(pbFile, pbMemory, cbThis) != 0)
465 {
466 const char *pachSectNm = iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers";
467 SUP_DPRINTF(("%s: Differences in section #%u (%s) between file and memory:\n", pImage->pszName, iSh + 1, pachSectNm));
468
469 uint32_t off = 0;
470 while (off < cbThis && pbFile[off] == pbMemory[off])
471 off++;
472 SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
473 pImage->uImageBase + uRva + off, uRva + off, pbFile[off], pbMemory[off]));
474 uint32_t offLast = off;
475 uint32_t cDiffs = 1;
476 for (uint32_t off2 = off + 1; off2 < cbThis; off2++)
477 if (pbFile[off2] != pbMemory[off2])
478 {
479 SUP_DPRINTF((" %p / %#09x: %02x != %02x\n",
480 pImage->uImageBase + uRva + off2, uRva + off2, pbFile[off2], pbMemory[off2]));
481 cDiffs++;
482 offLast = off2;
483 }
484
485#ifdef IN_RING3
486 if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION
487 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION)
488 {
489 PVOID pvRestoreAddr = (uint8_t *)pImage->uImageBase + uRva;
490 rcNt = supHardNtVpFileMemRestore(pThis, pvRestoreAddr, pbFile, cbThis, fCorrectProtection);
491 if (NT_SUCCESS(rcNt))
492 SUP_DPRINTF((" Restored %#x bytes of original file content at %p\n", cbThis, pvRestoreAddr));
493 else
494 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH,
495 "%s: Failed to restore %#x bytes at %p (%#x, #%u, %s): %#x (cDiffs=%#x, first=%#x)",
496 pImage->pszName, cbThis, pvRestoreAddr, uRva, iSh + 1, pachSectNm, rcNt,
497 cDiffs, uRva + off);
498 }
499 else
500#endif /* IN_RING3 */
501 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH,
502 "%s: %u differences between %#x and %#x in #%u (%.8s), first: %02x != %02x",
503 pImage->pszName, cDiffs, uRva + off, uRva + offLast, iSh + 1,
504 pachSectNm, pbFile[off], pbMemory[off]);
505 }
506
507 /* Advance. The clipping makes it a little bit complicated. */
508 cbThis = uNextRva - uRva;
509 if (cbThis >= cb)
510 break;
511 cb -= cbThis;
512 pbFile += cbThis;
513 uRva = uNextRva;
514 }
515 return VINF_SUCCESS;
516}
517
518
519
520static int supHardNtVpCheckSectionProtection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage,
521 uint32_t uRva, uint32_t cb, uint32_t fProt)
522{
523 uint32_t const cbOrg = cb;
524 if (!cb)
525 return VINF_SUCCESS;
526 if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION
527 || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION)
528 return VINF_SUCCESS;
529
530 for (uint32_t i = 0; i < pImage->cRegions; i++)
531 {
532 uint32_t offRegion = uRva - pImage->aRegions[i].uRva;
533 if (offRegion < pImage->aRegions[i].cb)
534 {
535 uint32_t cbLeft = pImage->aRegions[i].cb - offRegion;
536 if ( pImage->aRegions[i].fProt != fProt
537 && ( fProt != PAGE_READWRITE
538 || pImage->aRegions[i].fProt != PAGE_WRITECOPY))
539 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SECTION_PROTECTION_MISMATCH,
540 "%s: RVA range %#x-%#x protection is %#x, expected %#x. (cb=%#x)",
541 pImage->pszName, uRva, uRva + cbLeft - 1, pImage->aRegions[i].fProt, fProt, cb);
542 if (cbLeft >= cb)
543 return VINF_SUCCESS;
544 cb -= cbLeft;
545 uRva += cbLeft;
546
547#if 0 /* This shouldn't ever be necessary. */
548 if ( i + 1 < pImage->cRegions
549 && uRva < pImage->aRegions[i + 1].uRva)
550 {
551 cbLeft = pImage->aRegions[i + 1].uRva - uRva;
552 if (cbLeft >= cb)
553 return VINF_SUCCESS;
554 cb -= cbLeft;
555 uRva += cbLeft;
556 }
557#endif
558 }
559 }
560
561 return supHardNtVpSetInfo2(pThis, cbOrg == cb ? VERR_SUP_VP_SECTION_NOT_MAPPED : VERR_SUP_VP_SECTION_NOT_FULLY_MAPPED,
562 "%s: RVA range %#x-%#x is not mapped?", pImage->pszName, uRva, uRva + cb - 1);
563}
564
565
566DECLINLINE(bool) supHardNtVpIsModuleNameMatch(PSUPHNTVPIMAGE pImage, const char *pszModule)
567{
568 if (pImage->fDll)
569 {
570 const char *pszImageNm = pImage->pszName;
571 for (;;)
572 {
573 char chLeft = *pszImageNm++;
574 char chRight = *pszModule++;
575 if (chLeft != chRight)
576 {
577 Assert(chLeft == RT_C_TO_LOWER(chLeft));
578 if (chLeft != RT_C_TO_LOWER(chRight))
579 {
580 if ( chRight == '\0'
581 && chLeft == '.'
582 && pszImageNm[0] == 'd'
583 && pszImageNm[1] == 'l'
584 && pszImageNm[2] == 'l'
585 && pszImageNm[3] == '\0')
586 return true;
587 break;
588 }
589 }
590
591 if (chLeft == '\0')
592 return true;
593 }
594 }
595
596 return false;
597}
598
599
600/**
601 * Worker for supHardNtVpGetImport that looks up a module in the module table.
602 *
603 * @returns Pointer to the module if found, NULL if not found.
604 * @param pThis The process validator instance.
605 * @param pszModule The name of the module we're looking for.
606 */
607static PSUPHNTVPIMAGE supHardNtVpFindModule(PSUPHNTVPSTATE pThis, const char *pszModule)
608{
609 /*
610 * Check out the hint first.
611 */
612 if ( pThis->iImageHint < pThis->cImages
613 && supHardNtVpIsModuleNameMatch(&pThis->aImages[pThis->iImageHint], pszModule))
614 return &pThis->aImages[pThis->iImageHint];
615
616 /*
617 * Linear array search next.
618 */
619 uint32_t i = pThis->cImages;
620 while (i-- > 0)
621 if (supHardNtVpIsModuleNameMatch(&pThis->aImages[i], pszModule))
622 {
623 pThis->iImageHint = i;
624 return &pThis->aImages[i];
625 }
626
627 /* No cigar. */
628 return NULL;
629}
630
631
632/**
633 * @callback_method_impl{FNRTLDRIMPORT}
634 */
635static DECLCALLBACK(int) supHardNtVpGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol,
636 PRTLDRADDR pValue, void *pvUser)
637{
638 /*SUP_DPRINTF(("supHardNtVpGetImport: %s / %#x / %s.\n", pszModule, uSymbol, pszSymbol));*/
639 PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)pvUser;
640
641 int rc = VERR_MODULE_NOT_FOUND;
642 PSUPHNTVPIMAGE pImage = supHardNtVpFindModule(pThis, pszModule);
643 if (pImage)
644 {
645 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
646 pImage->uImageBase, uSymbol, pszSymbol, pValue);
647 if (RT_SUCCESS(rc))
648 return rc;
649 }
650 /*
651 * API set hacks.
652 */
653 else if (!RTStrNICmp(pszModule, RT_STR_TUPLE("api-ms-win-")))
654 {
655 static const char * const s_apszDlls[] = { "ntdll.dll", "kernelbase.dll", "kernel32.dll" };
656 for (uint32_t i = 0; i < RT_ELEMENTS(s_apszDlls); i++)
657 {
658 pImage = supHardNtVpFindModule(pThis, s_apszDlls[i]);
659 if (pImage)
660 {
661 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
662 pImage->uImageBase, uSymbol, pszSymbol, pValue);
663 if (RT_SUCCESS(rc))
664 return rc;
665 if (rc != VERR_SYMBOL_NOT_FOUND)
666 break;
667 }
668 }
669 }
670
671 /*
672 * Deal with forwarders.
673 * ASSUMES no forwarders thru any api-ms-win-core-*.dll.
674 * ASSUMES forwarders are resolved after one redirection.
675 */
676 if (rc == VERR_LDR_FORWARDER)
677 {
678 size_t cbInfo = RT_MIN((uint32_t)*pValue, sizeof(RTLDRIMPORTINFO) + 32);
679 PRTLDRIMPORTINFO pInfo = (PRTLDRIMPORTINFO)alloca(cbInfo);
680 rc = RTLdrQueryForwarderInfo(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
681 uSymbol, pszSymbol, pInfo, cbInfo);
682 if (RT_SUCCESS(rc))
683 {
684 rc = VERR_MODULE_NOT_FOUND;
685 pImage = supHardNtVpFindModule(pThis, pInfo->szModule);
686 if (pImage)
687 {
688 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits,
689 pImage->uImageBase, pInfo->iOrdinal, pInfo->pszSymbol, pValue);
690 if (RT_SUCCESS(rc))
691 return rc;
692
693 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol '%s' in '%s' (forwarded from %s / %s): %Rrc\n",
694 pInfo->pszSymbol, pInfo->szModule, pszModule, pszSymbol, rc));
695 if (rc == VERR_LDR_FORWARDER)
696 rc = VERR_LDR_FORWARDER_CHAIN_TOO_LONG;
697 }
698 else
699 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find forwarder module '%s' (%#x / %s; originally %s / %#x / %s): %Rrc\n",
700 pInfo->szModule, pInfo->iOrdinal, pInfo->pszSymbol, pszModule, uSymbol, pszSymbol, rc));
701 }
702 else
703 SUP_DPRINTF(("supHardNtVpGetImport: RTLdrQueryForwarderInfo failed on symbol %#x/'%s' in '%s': %Rrc\n",
704 uSymbol, pszSymbol, pszModule, rc));
705 }
706 else
707 SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol %#x / '%s' in '%s': %Rrc\n",
708 uSymbol, pszSymbol, pszModule, rc));
709 return rc;
710}
711
712
713/**
714 * Compares process memory with the disk content.
715 *
716 * @returns VBox status code.
717 * @param pThis The process scanning state structure (for the
718 * two scratch buffers).
719 * @param pImage The image data collected during the address
720 * space scan.
721 * @param hProcess Handle to the process.
722 * @param pErrInfo Pointer to error info structure. Optional.
723 */
724static int supHardNtVpVerifyImageMemoryCompare(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, HANDLE hProcess, PRTERRINFO pErrInfo)
725{
726 /*
727 * Read and find the file headers.
728 */
729 int rc = supHardNtVpReadImage(pImage, 0 /*off*/, pThis->abFile, sizeof(pThis->abFile));
730 if (RT_FAILURE(rc))
731 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_HDR_READ_ERROR,
732 "%s: Error reading image header: %Rrc", pImage->pszName, rc);
733
734 uint32_t offNtHdrs = 0;
735 PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pThis->abFile[0];
736 if (pDosHdr->e_magic == IMAGE_DOS_SIGNATURE)
737 {
738 offNtHdrs = pDosHdr->e_lfanew;
739 if (offNtHdrs > 512 || offNtHdrs < sizeof(*pDosHdr))
740 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_MZ_OFFSET,
741 "%s: Unexpected e_lfanew value: %#x", pImage->pszName, offNtHdrs);
742 }
743 PIMAGE_NT_HEADERS pNtHdrs = (PIMAGE_NT_HEADERS)&pThis->abFile[offNtHdrs];
744 PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)pNtHdrs;
745 if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
746 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIGNATURE,
747 "%s: No PE signature at %#x: %#x", pImage->pszName, offNtHdrs, pNtHdrs->Signature);
748
749 /*
750 * Do basic header validation.
751 */
752#ifdef RT_ARCH_AMD64
753 if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 && !pImage->f32bitResourceDll)
754#else
755 if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_I386)
756#endif
757 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_IMAGE_MACHINE,
758 "%s: Unexpected machine: %#x", pImage->pszName, pNtHdrs->FileHeader.Machine);
759 bool const fIs32Bit = pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386;
760
761 if (pNtHdrs->FileHeader.SizeOfOptionalHeader != (fIs32Bit ? sizeof(IMAGE_OPTIONAL_HEADER32) : sizeof(IMAGE_OPTIONAL_HEADER64)))
762 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
763 "%s: Unexpected optional header size: %#x",
764 pImage->pszName, pNtHdrs->FileHeader.SizeOfOptionalHeader);
765
766 if (pNtHdrs->OptionalHeader.Magic != (fIs32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC))
767 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
768 "%s: Unexpected optional header magic: %#x", pImage->pszName, pNtHdrs->OptionalHeader.Magic);
769
770 uint32_t cDirs = (fIs32Bit ? pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes : pNtHdrs->OptionalHeader.NumberOfRvaAndSizes);
771 if (cDirs != IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
772 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER,
773 "%s: Unexpected data dirs: %#x", pImage->pszName, cDirs);
774
775 /*
776 * Before we start comparing things, store what we need to know from the headers.
777 */
778 uint32_t const cSections = pNtHdrs->FileHeader.NumberOfSections;
779 if (cSections > RT_ELEMENTS(pThis->aSecHdrs))
780 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_SECTIONS,
781 "%s: Too many section headers: %#x", pImage->pszName, cSections);
782 suplibHardenedMemCopy(pThis->aSecHdrs, (fIs32Bit ? (void *)(pNtHdrs32 + 1) : (void *)(pNtHdrs + 1)),
783 cSections * sizeof(IMAGE_SECTION_HEADER));
784
785 uintptr_t const uImageBase = fIs32Bit ? pNtHdrs32->OptionalHeader.ImageBase : pNtHdrs->OptionalHeader.ImageBase;
786 if (uImageBase & PAGE_OFFSET_MASK)
787 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_BASE,
788 "%s: Invalid image base: %p", pImage->pszName, uImageBase);
789
790 uint32_t const cbImage = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfImage : pNtHdrs->OptionalHeader.SizeOfImage;
791 if (RT_ALIGN_32(pImage->cbImage, PAGE_SIZE) != RT_ALIGN_32(cbImage, PAGE_SIZE) && !pImage->fApiSetSchemaOnlySection1)
792 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
793 "%s: SizeOfImage (%#x) isn't close enough to the mapping size (%#x)",
794 pImage->pszName, cbImage, pImage->cbImage);
795 if (cbImage != RTLdrSize(pImage->pCacheEntry->hLdrMod))
796 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE,
797 "%s: SizeOfImage (%#x) differs from what RTLdrSize returns (%#zx)",
798 pImage->pszName, cbImage, RTLdrSize(pImage->pCacheEntry->hLdrMod));
799
800 uint32_t const cbSectAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.SectionAlignment : pNtHdrs->OptionalHeader.SectionAlignment;
801 if ( !RT_IS_POWER_OF_TWO(cbSectAlign)
802 || cbSectAlign < PAGE_SIZE
803 || cbSectAlign > (pImage->fApiSetSchemaOnlySection1 ? _64K : (uint32_t)PAGE_SIZE) )
804 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_ALIGNMENT_VALUE,
805 "%s: Unexpected SectionAlignment value: %#x", pImage->pszName, cbSectAlign);
806
807 uint32_t const cbFileAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.FileAlignment : pNtHdrs->OptionalHeader.FileAlignment;
808 if (!RT_IS_POWER_OF_TWO(cbFileAlign) || cbFileAlign < 512 || cbFileAlign > PAGE_SIZE || cbFileAlign > cbSectAlign)
809 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_FILE_ALIGNMENT_VALUE,
810 "%s: Unexpected FileAlignment value: %#x (cbSectAlign=%#x)",
811 pImage->pszName, cbFileAlign, cbSectAlign);
812
813 uint32_t const cbHeaders = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfHeaders : pNtHdrs->OptionalHeader.SizeOfHeaders;
814 uint32_t const cbMinHdrs = offNtHdrs + (fIs32Bit ? sizeof(*pNtHdrs32) : sizeof(*pNtHdrs) )
815 + sizeof(IMAGE_SECTION_HEADER) * cSections;
816 if (cbHeaders < cbMinHdrs)
817 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS,
818 "%s: Headers are too small: %#x < %#x (cSections=%#x)",
819 pImage->pszName, cbHeaders, cbMinHdrs, cSections);
820 uint32_t const cbHdrsFile = RT_ALIGN_32(cbHeaders, cbFileAlign);
821 if (cbHdrsFile > sizeof(pThis->abFile))
822 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SIZE_OF_HEADERS,
823 "%s: Headers are larger than expected: %#x/%#x (expected max %zx)",
824 pImage->pszName, cbHeaders, cbHdrsFile, sizeof(pThis->abFile));
825
826 /*
827 * Save some header fields we might be using later on.
828 */
829 pImage->fImageCharecteristics = pNtHdrs->FileHeader.Characteristics;
830 pImage->fDllCharecteristics = fIs32Bit ? pNtHdrs32->OptionalHeader.DllCharacteristics : pNtHdrs->OptionalHeader.DllCharacteristics;
831
832 /*
833 * Correct the apisetschema image base, size and region rva.
834 */
835 if (pImage->fApiSetSchemaOnlySection1)
836 {
837 pImage->uImageBase -= pThis->aSecHdrs[0].VirtualAddress;
838 pImage->cbImage += pThis->aSecHdrs[0].VirtualAddress;
839 pImage->aRegions[0].uRva = pThis->aSecHdrs[0].VirtualAddress;
840 }
841
842 /*
843 * Get relocated bits.
844 */
845 uint8_t *pbBits;
846 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
847 rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, NULL /*pfnGetImport*/, pThis,
848 pThis->pErrInfo);
849 else
850 rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, supHardNtVpGetImport, pThis,
851 pThis->pErrInfo);
852 if (RT_FAILURE(rc))
853 return rc;
854
855 /* XP SP3 does not set ImageBase to load address. It fixes up the image on load time though. */
856 if (g_uNtVerCombined >= SUP_NT_VER_VISTA)
857 {
858 if (fIs32Bit)
859 ((PIMAGE_NT_HEADERS32)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = (uint32_t)pImage->uImageBase;
860 else
861 ((PIMAGE_NT_HEADERS)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = pImage->uImageBase;
862 }
863
864 /*
865 * Figure out areas we should skip during comparison.
866 */
867 uint32_t cSkipAreas = 0;
868 SUPHNTVPSKIPAREA aSkipAreas[5];
869 if (pImage->fNtCreateSectionPatch)
870 {
871 RTLDRADDR uValue;
872 if (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY)
873 {
874 /* Ignore our NtCreateSection hack. */
875 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "NtCreateSection", &uValue);
876 if (RT_FAILURE(rc))
877 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'NtCreateSection': %Rrc", pImage->pszName, rc);
878 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
879 aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12;
880
881 /* Ignore our LdrLoadDll hack. */
882 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrLoadDll", &uValue);
883 if (RT_FAILURE(rc))
884 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrLoadDll': %Rrc", pImage->pszName, rc);
885 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
886 aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12;
887 }
888
889 /* Ignore our patched LdrInitializeThunk hack. */
890 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrInitializeThunk", &uValue);
891 if (RT_FAILURE(rc))
892 return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrInitializeThunk': %Rrc", pImage->pszName, rc);
893 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
894 aSkipAreas[cSkipAreas++].cb = 14;
895
896 /* LdrSystemDllInitBlock is filled in by the kernel. It mainly contains addresses of 32-bit ntdll method for wow64. */
897 rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrSystemDllInitBlock", &uValue);
898 if (RT_SUCCESS(rc))
899 {
900 aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue;
901 aSkipAreas[cSkipAreas++].cb = RT_MAX(pbBits[(uint32_t)uValue], 0x50);
902 }
903
904 Assert(cSkipAreas <= RT_ELEMENTS(aSkipAreas));
905 }
906
907 /*
908 * Compare the file header with the loaded bits. The loader will fiddle
909 * with image base, changing it to the actual load address.
910 */
911 if (!pImage->fApiSetSchemaOnlySection1)
912 {
913 rc = supHardNtVpFileMemCompareSection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, pbBits, -1, NULL, 0, PAGE_READONLY);
914 if (RT_FAILURE(rc))
915 return rc;
916
917 rc = supHardNtVpCheckSectionProtection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, PAGE_READONLY);
918 if (RT_FAILURE(rc))
919 return rc;
920 }
921
922 /*
923 * Validate sections:
924 * - Check them against the mapping regions.
925 * - Check section bits according to enmKind.
926 */
927 uint32_t fPrevProt = PAGE_READONLY;
928 uint32_t uRva = cbHdrsFile;
929 for (uint32_t i = 0; i < cSections; i++)
930 {
931 /* Validate the section. */
932 uint32_t uSectRva = pThis->aSecHdrs[i].VirtualAddress;
933 if (uSectRva < uRva || uSectRva > cbImage || RT_ALIGN_32(uSectRva, cbSectAlign) != uSectRva)
934 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_RVA,
935 "%s: Section %u: Invalid virtual address: %#x (uRva=%#x, cbImage=%#x, cbSectAlign=%#x)",
936 pImage->pszName, i, uSectRva, uRva, cbImage, cbSectAlign);
937 uint32_t cbMap = pThis->aSecHdrs[i].Misc.VirtualSize;
938 if (cbMap > cbImage || uRva + cbMap > cbImage)
939 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_VIRTUAL_SIZE,
940 "%s: Section %u: Invalid virtual size: %#x (uSectRva=%#x, uRva=%#x, cbImage=%#x)",
941 pImage->pszName, i, cbMap, uSectRva, uRva, cbImage);
942 uint32_t cbFile = pThis->aSecHdrs[i].SizeOfRawData;
943 if (cbFile != RT_ALIGN_32(cbFile, cbFileAlign) || cbFile > RT_ALIGN_32(cbMap, cbSectAlign))
944 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_FILE_SIZE,
945 "%s: Section %u: Invalid file size: %#x (cbMap=%#x, uSectRva=%#x)",
946 pImage->pszName, i, cbFile, cbMap, uSectRva);
947
948 /* Validate the protection and bits. */
949 if (!pImage->fApiSetSchemaOnlySection1 || i == 0)
950 {
951 uint32_t fProt;
952 switch (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE))
953 {
954 case IMAGE_SCN_MEM_READ:
955 fProt = PAGE_READONLY;
956 break;
957 case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE:
958 fProt = PAGE_READWRITE;
959 if ( pThis->enmKind != SUPHARDNTVPKIND_VERIFY_ONLY
960 && pThis->enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION
961 && !suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll. Changed by proc init. */
962 fProt = PAGE_READONLY;
963 break;
964 case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE:
965 fProt = PAGE_EXECUTE_READ;
966 break;
967 case IMAGE_SCN_MEM_EXECUTE:
968 fProt = PAGE_EXECUTE;
969 break;
970 case IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE:
971 /* Only the executable is allowed to have this section,
972 and it's protected after we're done patching. */
973 if (!pImage->fDll)
974 {
975 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
976 fProt = PAGE_EXECUTE_READWRITE;
977 else
978 fProt = PAGE_EXECUTE_READ;
979 break;
980 }
981 default:
982 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_SECTION_FLAGS,
983 "%s: Section %u: Unexpected characteristics: %#x (uSectRva=%#x, cbMap=%#x)",
984 pImage->pszName, i, pThis->aSecHdrs[i].Characteristics, uSectRva, cbMap);
985 }
986
987 /* The section bits. Child purification verifies all, normal
988 verification verifies all except where the executable is
989 concerned (due to opening vboxdrv during early process init). */
990 if ( ( (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE))
991 && !(pThis->aSecHdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE))
992 || (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) == IMAGE_SCN_MEM_READ
993 || (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY && pImage->fDll)
994 || pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
995 {
996 rc = VINF_SUCCESS;
997 if (uRva < uSectRva && !pImage->fApiSetSchemaOnlySection1) /* Any gap worth checking? */
998 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uRva, uSectRva - uRva, pbBits + uRva,
999 i - 1, NULL, 0, fPrevProt);
1000 if (RT_SUCCESS(rc))
1001 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva, cbMap, pbBits + uSectRva,
1002 i, aSkipAreas, cSkipAreas, fProt);
1003 if (RT_SUCCESS(rc))
1004 {
1005 uint32_t cbMapAligned = i + 1 < cSections && !pImage->fApiSetSchemaOnlySection1
1006 ? RT_ALIGN_32(cbMap, cbSectAlign) : RT_ALIGN_32(cbMap, PAGE_SIZE);
1007 if (cbMapAligned > cbMap)
1008 rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva + cbMap, cbMapAligned - cbMap,
1009 g_abRTZeroPage, i, NULL, 0, fProt);
1010 }
1011 if (RT_FAILURE(rc))
1012 return rc;
1013 }
1014
1015 /* The protection (must be checked afterwards!). */
1016 rc = supHardNtVpCheckSectionProtection(pThis, pImage, uSectRva, RT_ALIGN_32(cbMap, PAGE_SIZE), fProt);
1017 if (RT_FAILURE(rc))
1018 return rc;
1019
1020 fPrevProt = fProt;
1021 }
1022
1023 /* Advance the RVA. */
1024 uRva = uSectRva + RT_ALIGN_32(cbMap, cbSectAlign);
1025 }
1026
1027 return VINF_SUCCESS;
1028}
1029
1030
1031/**
1032 * Verifies the signature of the given image on disk, then checks if the memory
1033 * mapping matches what we verified.
1034 *
1035 * @returns VBox status code.
1036 * @param pThis The process scanning state structure (for the
1037 * two scratch buffers).
1038 * @param pImage The image data collected during the address
1039 * space scan.
1040 * @param hProcess Handle to the process.
1041 */
1042static int supHardNtVpVerifyImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, HANDLE hProcess)
1043{
1044 /*
1045 * Validate the file signature first, then do the memory compare.
1046 */
1047 int rc;
1048 if ( pImage->pCacheEntry != NULL
1049 && pImage->pCacheEntry->hLdrMod != NIL_RTLDRMOD)
1050 {
1051 rc = supHardNtLdrCacheEntryVerify(pImage->pCacheEntry, pImage->Name.UniStr.Buffer, pThis->pErrInfo);
1052 if (RT_SUCCESS(rc))
1053 rc = supHardNtVpVerifyImageMemoryCompare(pThis, pImage, hProcess, pThis->pErrInfo);
1054 }
1055 else
1056 rc = supHardNtVpSetInfo2(pThis, VERR_OPEN_FAILED, "pCacheEntry/hLdrMod is NIL! Impossible!");
1057 return rc;
1058}
1059
1060
1061/**
1062 * Verifies that there is only one thread in the process.
1063 *
1064 * @returns VBox status code.
1065 * @param hProcess The process.
1066 * @param hThread The thread.
1067 * @param pErrInfo Pointer to error info structure. Optional.
1068 */
1069DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo)
1070{
1071 /*
1072 * Use the ThreadAmILastThread request to check that there is only one
1073 * thread in the process.
1074 * Seems this isn't entirely reliable when hThread isn't the current thread?
1075 */
1076 ULONG cbIgn = 0;
1077 ULONG fAmI = 0;
1078 NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn);
1079 if (!NT_SUCCESS(rcNt))
1080 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_THREAD_ERROR,
1081 "NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt);
1082 if (!fAmI)
1083 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_THREAD_NOT_ALONE,
1084 "More than one thread in process");
1085
1086 /** @todo Would be nice to verify the relation ship between hProcess and hThread
1087 * as well... */
1088 return VINF_SUCCESS;
1089}
1090
1091
1092/**
1093 * Verifies that there isn't a debugger attached to the process.
1094 *
1095 * @returns VBox status code.
1096 * @param hProcess The process.
1097 * @param pErrInfo Pointer to error info structure. Optional.
1098 */
1099DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo)
1100{
1101#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS
1102 /*
1103 * Use the ProcessDebugPort request to check there is no debugger
1104 * currently attached to the process.
1105 */
1106 ULONG cbIgn = 0;
1107 uintptr_t uPtr = ~(uintptr_t)0;
1108 NTSTATUS rcNt = NtQueryInformationProcess(hProcess,
1109 ProcessDebugPort,
1110 &uPtr, sizeof(uPtr), &cbIgn);
1111 if (!NT_SUCCESS(rcNt))
1112 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_DBG_PORT_ERROR,
1113 "NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt);
1114 if (uPtr != 0)
1115 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_DEBUGGED,
1116 "Debugger attached (%#zx)", uPtr);
1117#endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */
1118 return VINF_SUCCESS;
1119}
1120
1121
1122/**
1123 * Matches two UNICODE_STRING structures in a case sensitive fashion.
1124 *
1125 * @returns true if equal, false if not.
1126 * @param pUniStr1 The first unicode string.
1127 * @param pUniStr2 The first unicode string.
1128 */
1129static bool supHardNtVpAreUniStringsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2)
1130{
1131 if (pUniStr1->Length != pUniStr2->Length)
1132 return false;
1133 return suplibHardenedMemComp(pUniStr1->Buffer, pUniStr2->Buffer, pUniStr1->Length) == 0;
1134}
1135
1136
1137/**
1138 * Performs a case insensitive comparison of an ASCII and an UTF-16 file name.
1139 *
1140 * @returns true / false
1141 * @param pszName1 The ASCII name.
1142 * @param pwszName2 The UTF-16 name.
1143 */
1144static bool supHardNtVpAreNamesEqual(const char *pszName1, PCRTUTF16 pwszName2)
1145{
1146 for (;;)
1147 {
1148 char ch1 = *pszName1++;
1149 RTUTF16 wc2 = *pwszName2++;
1150 if (ch1 != wc2)
1151 {
1152 ch1 = RT_C_TO_LOWER(ch1);
1153 wc2 = wc2 < 0x80 ? RT_C_TO_LOWER(wc2) : wc2;
1154 if (ch1 != wc2)
1155 return false;
1156 }
1157 if (!ch1)
1158 return true;
1159 }
1160}
1161
1162
1163/**
1164 * Records an additional memory region for an image.
1165 *
1166 * May trash pThis->abMemory.
1167 *
1168 * @returns VBox status code.
1169 * @retval VINF_OBJECT_DESTROYED if we've unmapped the image (child
1170 * purification only).
1171 * @param pThis The process scanning state structure.
1172 * @param pImage The new image structure. Only the unicode name
1173 * buffer is valid (it's zero-terminated).
1174 * @param pMemInfo The memory information for the image.
1175 */
1176static int supHardNtVpNewImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo)
1177{
1178 /*
1179 * If the filename or path contains short names, we have to get the long
1180 * path so that we will recognize the DLLs and their location.
1181 */
1182 PUNICODE_STRING pLongName = &pImage->Name.UniStr;
1183 if (RTNtPathFindPossible8dot3Name(pLongName->Buffer))
1184 {
1185 AssertCompile(sizeof(pThis->abMemory) > sizeof(pImage->Name));
1186 PUNICODE_STRING pTmp = (PUNICODE_STRING)pThis->abMemory;
1187 pTmp->MaximumLength = (USHORT)RT_MIN(_64K - 1, sizeof(pThis->abMemory) - sizeof(*pTmp)) - sizeof(RTUTF16);
1188 pTmp->Length = pImage->Name.UniStr.Length;
1189 pTmp->Buffer = (PRTUTF16)(pTmp + 1);
1190 memcpy(pTmp->Buffer, pLongName->Buffer, pLongName->Length + sizeof(RTUTF16));
1191
1192 RTNtPathExpand8dot3Path(pTmp, false /*fPathOnly*/);
1193 Assert(pTmp->Buffer[pTmp->Length / sizeof(RTUTF16)] == '\0');
1194
1195 pLongName = pTmp;
1196 }
1197
1198 /*
1199 * Extract the final component.
1200 */
1201 RTUTF16 wc;
1202 unsigned cwcDirName = pLongName->Length / sizeof(WCHAR);
1203 PCRTUTF16 pwcDirName = &pLongName->Buffer[cwcDirName];
1204 PCRTUTF16 pwszFilename = &pLongName->Buffer[cwcDirName];
1205 while ( cwcDirName > 0
1206 && (wc = pwszFilename[-1]) != '\\'
1207 && wc != '/'
1208 && wc != ':')
1209 {
1210 pwszFilename--;
1211 cwcDirName--;
1212 }
1213 if (!*pwszFilename)
1214 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_IMAGE_MAPPING_NAME,
1215 "Empty filename (len=%u) for image at %p.", pLongName->Length, pMemInfo->BaseAddress);
1216
1217 /*
1218 * Drop trailing slashes from the directory name.
1219 */
1220 while ( cwcDirName > 0
1221 && ( pLongName->Buffer[cwcDirName - 1] == '\\'
1222 || pLongName->Buffer[cwcDirName - 1] == '/'))
1223 cwcDirName--;
1224
1225 /*
1226 * Match it against known DLLs.
1227 */
1228 pImage->pszName = NULL;
1229 for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls); i++)
1230 if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedDlls[i], pwszFilename))
1231 {
1232 pImage->pszName = g_apszSupNtVpAllowedDlls[i];
1233 pImage->fDll = true;
1234
1235#ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
1236 /* The directory name must match the one we've got for System32. */
1237 if ( ( cwcDirName * sizeof(WCHAR) != g_System32NtPath.UniStr.Length
1238 || suplibHardenedMemComp(pLongName->Buffer, g_System32NtPath.UniStr.Buffer, cwcDirName * sizeof(WCHAR)) )
1239# ifdef VBOX_PERMIT_MORE
1240 && ( pImage->pszName[0] != 'a'
1241 || pImage->pszName[1] != 'c'
1242 || !supHardViIsAppPatchDir(pLongName->Buffer, pLongName->Length / sizeof(WCHAR)) )
1243# endif
1244 )
1245 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NON_SYSTEM32_DLL,
1246 "Expected %ls to be loaded from %ls.",
1247 pLongName->Buffer, g_System32NtPath.UniStr.Buffer);
1248# ifdef VBOX_PERMIT_MORE
1249 if (g_uNtVerCombined < SUP_NT_VER_W70 && i >= VBOX_PERMIT_MORE_FIRST_IDX)
1250 pImage->pszName = NULL; /* hard limit: user32.dll is unwanted prior to w7. */
1251# endif
1252
1253#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
1254 break;
1255 }
1256 if (!pImage->pszName)
1257 {
1258 /*
1259 * Not a known DLL, is it a known executable?
1260 */
1261 for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedVmExes); i++)
1262 if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedVmExes[i], pwszFilename))
1263 {
1264 pImage->pszName = g_apszSupNtVpAllowedVmExes[i];
1265 pImage->fDll = false;
1266 break;
1267 }
1268 }
1269 if (!pImage->pszName)
1270 {
1271 /*
1272 * Unknown image.
1273 *
1274 * If we're cleaning up a child process, we can unmap the offending
1275 * DLL... Might have interesting side effects, or at least interesting
1276 * as in "may you live in interesting times".
1277 */
1278#ifdef IN_RING3
1279 if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress
1280 && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
1281 {
1282 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping image mem at %p (%p LB %#zx) - '%ls'\n",
1283 pMemInfo->AllocationBase, pMemInfo->BaseAddress, pMemInfo->RegionSize, pwszFilename));
1284 NTSTATUS rcNt = NtUnmapViewOfSection(pThis->hProcess, pMemInfo->AllocationBase);
1285 if (NT_SUCCESS(rcNt))
1286 return VINF_OBJECT_DESTROYED;
1287 pThis->cFixes++;
1288 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: NtUnmapViewOfSection(,%p) failed: %#x\n", pMemInfo->AllocationBase, rcNt));
1289 }
1290#endif
1291 /*
1292 * Special error message if we can.
1293 */
1294 if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress
1295 && ( supHardNtVpAreNamesEqual("sysfer.dll", pwszFilename)
1296 || supHardNtVpAreNamesEqual("sysfer32.dll", pwszFilename)
1297 || supHardNtVpAreNamesEqual("sysfer64.dll", pwszFilename)
1298 || supHardNtVpAreNamesEqual("sysfrethunk.dll", pwszFilename)) )
1299 {
1300 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SYSFER_DLL,
1301 "Found %ls at %p - This is probably part of Symantec Endpoint Protection. \n"
1302 "You or your admin need to add and exception to the Application and Device Control (ADC) "
1303 "component (or disable it) to prevent ADC from injecting itself into the VirtualBox VM processes. "
1304 "See http://www.symantec.com/connect/articles/creating-application-control-exclusions-symantec-endpoint-protection-121"
1305 , pLongName->Buffer, pMemInfo->BaseAddress);
1306 return pThis->rcResult = VERR_SUP_VP_SYSFER_DLL; /* Try make sure this is what the user sees first! */
1307 }
1308 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE,
1309 "Unknown image file %ls at %p.", pLongName->Buffer, pMemInfo->BaseAddress);
1310 }
1311
1312 /*
1313 * Checks for multiple mappings of the same DLL but with different image file paths.
1314 */
1315 uint32_t i = pThis->cImages;
1316 while (i-- > 1)
1317 if (pImage->pszName == pThis->aImages[i].pszName)
1318 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING,
1319 "Duplicate image entries for %s: %ls and %ls",
1320 pImage->pszName, pImage->Name.UniStr.Buffer, pThis->aImages[i].Name.UniStr.Buffer);
1321
1322 /*
1323 * Since it's a new image, we expect to be at the start of the mapping now.
1324 */
1325 if (pMemInfo->AllocationBase != pMemInfo->BaseAddress)
1326 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_MAPPING_BASE_ERROR,
1327 "Invalid AllocationBase/BaseAddress for %s: %p vs %p.",
1328 pImage->pszName, pMemInfo->AllocationBase, pMemInfo->BaseAddress);
1329
1330 /*
1331 * Check for size/rva overflow.
1332 */
1333 if (pMemInfo->RegionSize >= _2G)
1334 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION,
1335 "Region 0 of image %s is too large: %p.", pImage->pszName, pMemInfo->RegionSize);
1336
1337 /*
1338 * Fill in details from the memory info.
1339 */
1340 pImage->uImageBase = (uintptr_t)pMemInfo->AllocationBase;
1341 pImage->cbImage = pMemInfo->RegionSize;
1342 pImage->pCacheEntry= NULL;
1343 pImage->cRegions = 1;
1344 pImage->aRegions[0].uRva = 0;
1345 pImage->aRegions[0].cb = (uint32_t)pMemInfo->RegionSize;
1346 pImage->aRegions[0].fProt = pMemInfo->Protect;
1347
1348 if (suplibHardenedStrCmp(pImage->pszName, "ntdll.dll") == 0)
1349 pImage->fNtCreateSectionPatch = true;
1350 else if (suplibHardenedStrCmp(pImage->pszName, "apisetschema.dll") == 0)
1351 pImage->fApiSetSchemaOnlySection1 = true; /** @todo Check the ApiSetMap field in the PEB. */
1352#ifdef VBOX_PERMIT_MORE
1353 else if (suplibHardenedStrCmp(pImage->pszName, "acres.dll") == 0)
1354 pImage->f32bitResourceDll = true;
1355#endif
1356
1357 return VINF_SUCCESS;
1358}
1359
1360
1361/**
1362 * Records an additional memory region for an image.
1363 *
1364 * @returns VBox status code.
1365 * @param pThis The process scanning state structure.
1366 * @param pImage The image.
1367 * @param pMemInfo The memory information for the region.
1368 */
1369static int supHardNtVpAddRegion(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo)
1370{
1371 /*
1372 * Make sure the base address matches.
1373 */
1374 if (pImage->uImageBase != (uintptr_t)pMemInfo->AllocationBase)
1375 return supHardNtVpSetInfo2(pThis, VERR_SUPLIB_NT_PROCESS_UNTRUSTED_3,
1376 "Base address mismatch for %s: have %p, found %p for region %p LB %#zx.",
1377 pImage->pszName, pImage->uImageBase, pMemInfo->AllocationBase,
1378 pMemInfo->BaseAddress, pMemInfo->RegionSize);
1379
1380 /*
1381 * Check for size and rva overflows.
1382 */
1383 uintptr_t uRva = (uintptr_t)pMemInfo->BaseAddress - pImage->uImageBase;
1384 if (pMemInfo->RegionSize >= _2G)
1385 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_LARGE_REGION,
1386 "Region %u of image %s is too large: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva);
1387 if (uRva >= _2G)
1388 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_HIGH_REGION_RVA,
1389 "Region %u of image %s is too high: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva);
1390
1391
1392 /*
1393 * Record the region.
1394 */
1395 uint32_t iRegion = pImage->cRegions;
1396 if (iRegion + 1 >= RT_ELEMENTS(pImage->aRegions))
1397 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_IMAGE_REGIONS,
1398 "Too many regions for %s.", pImage->pszName);
1399 pImage->aRegions[iRegion].uRva = (uint32_t)uRva;
1400 pImage->aRegions[iRegion].cb = (uint32_t)pMemInfo->RegionSize;
1401 pImage->aRegions[iRegion].fProt = pMemInfo->Protect;
1402 pImage->cbImage = pImage->aRegions[iRegion].uRva + pImage->aRegions[iRegion].cb;
1403 pImage->cRegions++;
1404 pImage->fApiSetSchemaOnlySection1 = false;
1405
1406 return VINF_SUCCESS;
1407}
1408
1409
1410#ifdef IN_RING3
1411/**
1412 * Frees (or replaces) executable memory of allocation type private.
1413 *
1414 * @returns True if nothing really bad happen, false if to quit ASAP because we
1415 * killed the process being scanned.
1416 * @param pThis The process scanning state structure. Details
1417 * about images are added to this.
1418 * @param hProcess The process to verify.
1419 * @param pMemInfo The information we've got on this private
1420 * executable memory.
1421 */
1422static bool supHardNtVpFreeOrReplacePrivateExecMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess,
1423 MEMORY_BASIC_INFORMATION const *pMemInfo)
1424{
1425 NTSTATUS rcNt;
1426
1427 /*
1428 * Try figure the entire allocation size. Free/Alloc may fail otherwise.
1429 */
1430 PVOID pvFree = pMemInfo->AllocationBase;
1431 SIZE_T cbFree = pMemInfo->RegionSize + ((uintptr_t)pMemInfo->BaseAddress - (uintptr_t)pMemInfo->AllocationBase);
1432 for (;;)
1433 {
1434 SIZE_T cbActual = 0;
1435 MEMORY_BASIC_INFORMATION MemInfo2 = { 0, 0, 0, 0, 0, 0, 0 };
1436 uintptr_t uPtrNext = (uintptr_t)pvFree + cbFree;
1437 rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1438 (void const *)uPtrNext,
1439 MemoryBasicInformation,
1440 &MemInfo2,
1441 sizeof(MemInfo2),
1442 &cbActual);
1443 if (!NT_SUCCESS(rcNt))
1444 break;
1445 if (pMemInfo->AllocationBase != MemInfo2.AllocationBase)
1446 break;
1447 if (MemInfo2.RegionSize == 0)
1448 break;
1449 cbFree += MemInfo2.RegionSize;
1450 }
1451 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: %s exec mem at %p (LB %#zx, %p LB %#zx)\n",
1452 pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW ? "Replacing" : "Freeing",
1453 pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize));
1454
1455 /*
1456 * In the BSOD workaround mode, we need to make a copy of the memory before
1457 * freeing it.
1458 */
1459 uintptr_t uCopySrc = (uintptr_t)pvFree;
1460 size_t cbCopy = 0;
1461 void *pvCopy = NULL;
1462 if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)
1463 {
1464 cbCopy = cbFree;
1465 pvCopy = RTMemAllocZ(cbCopy);
1466 if (!pvCopy)
1467 {
1468 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "RTMemAllocZ(%#zx) failed", cbCopy);
1469 return true;
1470 }
1471
1472 rcNt = supHardNtVpReadMem(hProcess, uCopySrc, pvCopy, cbCopy);
1473 if (!NT_SUCCESS(rcNt))
1474 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1475 "Error reading data from original alloc: %#x (%p LB %#zx)", rcNt, uCopySrc, cbCopy, rcNt);
1476 supR3HardenedLogFlush();
1477 }
1478
1479 /*
1480 * Free the memory.
1481 */
1482 for (uint32_t i = 0; i < 10; i++)
1483 {
1484 PVOID pvFreeInOut = pvFree;
1485 SIZE_T cbFreeInOut = 0;
1486 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1487 if (NT_SUCCESS(rcNt))
1488 {
1489 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 succeeded: %#x [%p/%p LB 0/%#zx]\n",
1490 rcNt, pvFree, pvFreeInOut, cbFreeInOut));
1491 supR3HardenedLogFlush();
1492 }
1493 else
1494 {
1495 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 failed: %#x [%p LB 0]\n", rcNt, pvFree));
1496 supR3HardenedLogFlush();
1497 pvFreeInOut = pvFree;
1498 cbFreeInOut = cbFree;
1499 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1500 if (NT_SUCCESS(rcNt))
1501 {
1502 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 succeeded: %#x [%p/%p LB %#zx/%#zx]\n",
1503 rcNt, pvFree, pvFreeInOut, cbFree, cbFreeInOut));
1504 supR3HardenedLogFlush();
1505 }
1506 else
1507 {
1508 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 failed: %#x [%p LB %#zx]\n",
1509 rcNt, pvFree, cbFree));
1510 supR3HardenedLogFlush();
1511 pvFreeInOut = pMemInfo->BaseAddress;
1512 cbFreeInOut = pMemInfo->RegionSize;
1513 rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE);
1514 if (NT_SUCCESS(rcNt))
1515 {
1516 pvFree = pMemInfo->BaseAddress;
1517 cbFree = pMemInfo->RegionSize;
1518 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #3 succeeded [%p LB %#zx]\n",
1519 pvFree, cbFree));
1520 supR3HardenedLogFlush();
1521 }
1522 else
1523 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED,
1524 "NtFreeVirtualMemory [%p LB %#zx and %p LB %#zx] failed: %#x",
1525 pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt);
1526 }
1527 }
1528
1529 /*
1530 * Query the region again, redo the free operation if there's still memory there.
1531 */
1532 if (!NT_SUCCESS(rcNt))
1533 break;
1534 SIZE_T cbActual = 0;
1535 MEMORY_BASIC_INFORMATION MemInfo3 = { 0, 0, 0, 0, 0, 0, 0 };
1536 NTSTATUS rcNt2 = g_pfnNtQueryVirtualMemory(hProcess, pvFree, MemoryBasicInformation,
1537 &MemInfo3, sizeof(MemInfo3), &cbActual);
1538 if (!NT_SUCCESS(rcNt2))
1539 break;
1540 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: QVM after free %u: [%p]/%p LB %#zx s=%#x ap=%#x rp=%#p\n",
1541 i, MemInfo3.AllocationBase, MemInfo3.BaseAddress, MemInfo3.RegionSize, MemInfo3.State,
1542 MemInfo3.AllocationProtect, MemInfo3.Protect));
1543 supR3HardenedLogFlush();
1544 if (MemInfo3.State == MEM_FREE || !(pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW))
1545 break;
1546 NtYieldExecution();
1547 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Retrying free...\n"));
1548 supR3HardenedLogFlush();
1549 }
1550
1551 /*
1552 * Restore memory as non-executable - Kludge for Trend Micro sakfile.sys
1553 * and Digital Guardian dgmaster.sys BSODs.
1554 */
1555 if (NT_SUCCESS(rcNt) && (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW))
1556 {
1557 PVOID pvAlloc = pvFree;
1558 SIZE_T cbAlloc = cbFree;
1559 rcNt = NtAllocateVirtualMemory(hProcess, &pvAlloc, 0, &cbAlloc, MEM_COMMIT, PAGE_READWRITE);
1560 if (!NT_SUCCESS(rcNt))
1561 {
1562 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1563 "NtAllocateVirtualMemory (%p LB %#zx) failed with rcNt=%#x allocating "
1564 "replacement memory for working around buggy protection software. "
1565 "See VBoxStartup.log for more details",
1566 pvAlloc, cbFree, rcNt);
1567 supR3HardenedLogFlush();
1568 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1569 return false;
1570 }
1571
1572 if ( (uintptr_t)pvFree < (uintptr_t)pvAlloc
1573 || (uintptr_t)pvFree + cbFree > (uintptr_t)pvAlloc + cbFree)
1574 {
1575 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED,
1576 "We wanted NtAllocateVirtualMemory to get us %p LB %#zx, but it returned %p LB %#zx.",
1577 pMemInfo->BaseAddress, pMemInfo->RegionSize, pvFree, cbFree, rcNt);
1578 supR3HardenedLogFlush();
1579 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1580 return false;
1581 }
1582
1583 /*
1584 * Copy what we can, considering the 2nd free attempt.
1585 */
1586 uint8_t *pbDst = (uint8_t *)pvFree;
1587 size_t cbDst = cbFree;
1588 uint8_t *pbSrc = (uint8_t *)pvCopy;
1589 size_t cbSrc = cbCopy;
1590 if ((uintptr_t)pbDst != uCopySrc)
1591 {
1592 if ((uintptr_t)pbDst > uCopySrc)
1593 {
1594 uintptr_t cbAdj = (uintptr_t)pbDst - uCopySrc;
1595 pbSrc += cbAdj;
1596 cbSrc -= cbAdj;
1597 }
1598 else
1599 {
1600 uintptr_t cbAdj = uCopySrc - (uintptr_t)pbDst;
1601 pbDst += cbAdj;
1602 cbDst -= cbAdj;
1603 }
1604 }
1605 if (cbSrc > cbDst)
1606 cbSrc = cbDst;
1607
1608 SIZE_T cbWritten;
1609 rcNt = NtWriteVirtualMemory(hProcess, pbDst, pbSrc, cbSrc, &cbWritten);
1610 if (NT_SUCCESS(rcNt))
1611 {
1612 SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Restored the exec memory as non-exec.\n"));
1613 supR3HardenedLogFlush();
1614 }
1615 else
1616 {
1617 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED,
1618 "NtWriteVirtualMemory (%p LB %#zx) failed: %#x",
1619 pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt);
1620 supR3HardenedLogFlush();
1621 NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED);
1622 return false;
1623 }
1624 }
1625 if (pvCopy)
1626 RTMemFree(pvCopy);
1627 return true;
1628}
1629#endif /* IN_RING3 */
1630
1631
1632/**
1633 * Scans the virtual memory of the process.
1634 *
1635 * This collects the locations of DLLs and the EXE, and verifies that executable
1636 * memory is only associated with these. May trash pThis->abMemory.
1637 *
1638 * @returns VBox status code.
1639 * @param pThis The process scanning state structure. Details
1640 * about images are added to this.
1641 * @param hProcess The process to verify.
1642 */
1643static int supHardNtVpScanVirtualMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess)
1644{
1645 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: enmKind=%s\n",
1646 pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY ? "VERIFY_ONLY" :
1647 pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION ? "CHILD_PURIFICATION" : "SELF_PURIFICATION"));
1648
1649 uint32_t cXpExceptions = 0;
1650 uintptr_t cbAdvance = 0;
1651 uintptr_t uPtrWhere = 0;
1652#ifdef VBOX_PERMIT_VERIFIER_DLL
1653 for (uint32_t i = 0; i < 10240; i++)
1654#else
1655 for (uint32_t i = 0; i < 1024; i++)
1656#endif
1657 {
1658 SIZE_T cbActual = 0;
1659 MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 };
1660 NTSTATUS rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1661 (void const *)uPtrWhere,
1662 MemoryBasicInformation,
1663 &MemInfo,
1664 sizeof(MemInfo),
1665 &cbActual);
1666 if (!NT_SUCCESS(rcNt))
1667 {
1668 if (rcNt == STATUS_INVALID_PARAMETER)
1669 return pThis->rcResult;
1670 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_ERROR,
1671 "NtQueryVirtualMemory failed for %p: %#x", uPtrWhere, rcNt);
1672 }
1673
1674 /*
1675 * Record images.
1676 */
1677 if ( MemInfo.Type == SEC_IMAGE
1678 || MemInfo.Type == SEC_PROTECTED_IMAGE
1679 || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE))
1680 {
1681 uint32_t iImg = pThis->cImages;
1682 rcNt = g_pfnNtQueryVirtualMemory(hProcess,
1683 (void const *)uPtrWhere,
1684 MemorySectionName,
1685 &pThis->aImages[iImg].Name,
1686 sizeof(pThis->aImages[iImg].Name) - sizeof(WCHAR),
1687 &cbActual);
1688 if (!NT_SUCCESS(rcNt))
1689 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_NM_ERROR,
1690 "NtQueryVirtualMemory/MemorySectionName failed for %p: %#x", uPtrWhere, rcNt);
1691 pThis->aImages[iImg].Name.UniStr.Buffer[pThis->aImages[iImg].Name.UniStr.Length / sizeof(WCHAR)] = '\0';
1692 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
1693 ? " *%p-%p %#06x/%#06x %#09x %ls\n"
1694 : " %p-%p %#06x/%#06x %#09x %ls\n",
1695 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect,
1696 MemInfo.AllocationProtect, MemInfo.Type, pThis->aImages[iImg].Name.UniStr.Buffer));
1697
1698 /* New or existing image? */
1699 bool fNew = true;
1700 uint32_t iSearch = iImg;
1701 while (iSearch-- > 0)
1702 if (supHardNtVpAreUniStringsEqual(&pThis->aImages[iSearch].Name.UniStr, &pThis->aImages[iImg].Name.UniStr))
1703 {
1704 int rc = supHardNtVpAddRegion(pThis, &pThis->aImages[iSearch], &MemInfo);
1705 if (RT_FAILURE(rc))
1706 return rc;
1707 fNew = false;
1708 break;
1709 }
1710 else if (pThis->aImages[iSearch].uImageBase == (uintptr_t)MemInfo.AllocationBase)
1711 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_MAPPING_NAME_CHANGED,
1712 "Unexpected base address match");
1713
1714 if (fNew)
1715 {
1716 int rc = supHardNtVpNewImage(pThis, &pThis->aImages[iImg], &MemInfo);
1717 if (RT_SUCCESS(rc))
1718 {
1719 if (rc != VINF_OBJECT_DESTROYED)
1720 {
1721 pThis->cImages++;
1722 if (pThis->cImages >= RT_ELEMENTS(pThis->aImages))
1723 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_DLLS_LOADED,
1724 "Internal error: aImages is full.\n");
1725 }
1726 }
1727#ifdef IN_RING3 /* Continue and add more information if unknown DLLs are found. */
1728 else if (rc != VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE && rc != VERR_SUP_VP_NON_SYSTEM32_DLL)
1729 return rc;
1730#else
1731 else
1732 return rc;
1733#endif
1734 }
1735 }
1736 /*
1737 * XP, W2K3: Ignore the CSRSS read-only region as best we can.
1738 */
1739 else if ( (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
1740 == PAGE_EXECUTE_READ
1741 && cXpExceptions == 0
1742 && (uintptr_t)MemInfo.BaseAddress >= UINT32_C(0x78000000)
1743 /* && MemInfo.BaseAddress == pPeb->ReadOnlySharedMemoryBase */
1744 && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 0) )
1745 {
1746 cXpExceptions++;
1747 SUP_DPRINTF((" %p-%p %#06x/%#06x %#09x XP CSRSS read-only region\n", MemInfo.BaseAddress,
1748 (uintptr_t)MemInfo.BaseAddress - MemInfo.RegionSize - 1, MemInfo.Protect,
1749 MemInfo.AllocationProtect, MemInfo.Type));
1750 }
1751 /*
1752 * Executable memory?
1753 */
1754#ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING
1755 else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))
1756 {
1757 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
1758 ? " *%p-%p %#06x/%#06x %#09x !!\n"
1759 : " %p-%p %#06x/%#06x %#09x !!\n",
1760 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress - MemInfo.RegionSize - 1,
1761 MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type));
1762# ifdef IN_RING3
1763 if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
1764 {
1765 /*
1766 * Free any private executable memory (sysplant.sys allocates executable memory).
1767 */
1768 if (MemInfo.Type == MEM_PRIVATE)
1769 {
1770 if (!supHardNtVpFreeOrReplacePrivateExecMemory(pThis, hProcess, &MemInfo))
1771 break;
1772 }
1773 /*
1774 * Unmap mapped memory, failing that, drop exec privileges.
1775 */
1776 else if (MemInfo.Type == MEM_MAPPED)
1777 {
1778 SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping exec mem at %p (%p/%p LB %#zx)\n",
1779 uPtrWhere, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize));
1780 rcNt = NtUnmapViewOfSection(hProcess, MemInfo.AllocationBase);
1781 if (!NT_SUCCESS(rcNt))
1782 {
1783 PVOID pvCopy = MemInfo.BaseAddress;
1784 SIZE_T cbCopy = MemInfo.RegionSize;
1785 NTSTATUS rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_NOACCESS, NULL);
1786 if (!NT_SUCCESS(rcNt2))
1787 rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_READONLY, NULL);
1788 if (!NT_SUCCESS(rcNt2))
1789 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNMAP_AND_PROTECT_FAILED,
1790 "NtUnmapViewOfSection (%p/%p LB %#zx) failed: %#x (%#x)",
1791 MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize, rcNt, rcNt2);
1792 }
1793 }
1794 else
1795 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNKOWN_MEM_TYPE,
1796 "Unknown executable memory type %#x at %p/%p LB %#zx",
1797 MemInfo.Type, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize);
1798 pThis->cFixes++;
1799 }
1800 else
1801# endif /* IN_RING3 */
1802 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_EXEC_MEMORY,
1803 "Found executable memory at %p (%p LB %#zx): type=%#x prot=%#x state=%#x aprot=%#x abase=%p",
1804 uPtrWhere, MemInfo.BaseAddress, MemInfo.RegionSize, MemInfo.Type, MemInfo.Protect,
1805 MemInfo.State, MemInfo.AllocationBase, MemInfo.AllocationProtect);
1806
1807# ifndef IN_RING3
1808 if (RT_FAILURE(pThis->rcResult))
1809 return pThis->rcResult;
1810# endif
1811 /* Continue add more information about the problematic process. */
1812 }
1813#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */
1814 else
1815 SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress
1816 ? " *%p-%p %#06x/%#06x %#09x\n"
1817 : " %p-%p %#06x/%#06x %#09x\n",
1818 MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress - MemInfo.RegionSize - 1,
1819 MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type));
1820
1821 /*
1822 * Advance.
1823 */
1824 cbAdvance = MemInfo.RegionSize;
1825 if (uPtrWhere + cbAdvance <= uPtrWhere)
1826 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EMPTY_REGION_TOO_LARGE,
1827 "Empty region at %p.", uPtrWhere);
1828 uPtrWhere += MemInfo.RegionSize;
1829 }
1830
1831 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_MEMORY_REGIONS,
1832 "Too many virtual memory regions.\n");
1833}
1834
1835
1836/**
1837 * Verifies the loader image, i.e. check cryptographic signatures if present.
1838 *
1839 * @returns VBox status code.
1840 * @param pEntry The loader cache entry.
1841 * @param pwszName The filename to use in error messages.
1842 * @param pErrInfo Where to return extened error information.
1843 */
1844DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo)
1845{
1846 int rc = VINF_SUCCESS;
1847 if (!pEntry->fVerified)
1848 {
1849 rc = supHardenedWinVerifyImageByLdrMod(pEntry->hLdrMod, pwszName, pEntry->pNtViRdr,
1850 false /*fAvoidWinVerifyTrust*/, NULL /*pfWinVerifyTrust*/, pErrInfo);
1851 pEntry->fVerified = RT_SUCCESS(rc);
1852 }
1853 return rc;
1854}
1855
1856
1857/**
1858 * Allocates a image bits buffer and calls RTLdrGetBits on them.
1859 *
1860 * An assumption here is that there won't ever be concurrent use of the cache.
1861 * It's currently 104% single threaded, non-reentrant. Thus, we can't reuse the
1862 * pbBits allocation.
1863 *
1864 * @returns VBox status code
1865 * @param pEntry The loader cache entry.
1866 * @param ppbBits Where to return the pointer to the allocation.
1867 * @param uBaseAddress The image base address, see RTLdrGetBits.
1868 * @param pfnGetImport Import getter, see RTLdrGetBits.
1869 * @param pvUser The user argument for @a pfnGetImport.
1870 * @param pErrInfo Where to return extened error information.
1871 */
1872DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits,
1873 RTLDRADDR uBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser,
1874 PRTERRINFO pErrInfo)
1875{
1876 int rc;
1877
1878 /*
1879 * First time around we have to allocate memory before we can get the image bits.
1880 */
1881 if (!pEntry->pbBits)
1882 {
1883 size_t cbBits = RTLdrSize(pEntry->hLdrMod);
1884 if (cbBits >= _1M*32U)
1885 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_TOO_BIG, "Image %s is too large: %zu bytes (%#zx).",
1886 pEntry->pszName, cbBits, cbBits);
1887
1888 pEntry->pbBits = (uint8_t *)RTMemAllocZ(cbBits);
1889 if (!pEntry->pbBits)
1890 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes for image %s.",
1891 cbBits, pEntry->pszName);
1892
1893 pEntry->fValidBits = false; /* paranoia */
1894
1895 rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser);
1896 if (RT_FAILURE(rc))
1897 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
1898 pEntry->pszName, rc);
1899 pEntry->uImageBase = uBaseAddress;
1900 pEntry->fValidBits = pfnGetImport == NULL;
1901
1902 }
1903 /*
1904 * Cache hit? No?
1905 *
1906 * Note! We cannot currently cache image bits for images with imports as we
1907 * don't control the way they're resolved. Fortunately, NTDLL and
1908 * the VM process images all have no imports.
1909 */
1910 else if ( !pEntry->fValidBits
1911 || pEntry->uImageBase != uBaseAddress
1912 || pfnGetImport)
1913 {
1914 pEntry->fValidBits = false;
1915
1916 rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser);
1917 if (RT_FAILURE(rc))
1918 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc",
1919 pEntry->pszName, rc);
1920 pEntry->uImageBase = uBaseAddress;
1921 pEntry->fValidBits = pfnGetImport == NULL;
1922 }
1923
1924 *ppbBits = pEntry->pbBits;
1925 return VINF_SUCCESS;
1926}
1927
1928
1929/**
1930 * Frees all resources associated with a cache entry and wipes the members
1931 * clean.
1932 *
1933 * @param pEntry The entry to delete.
1934 */
1935static void supHardNTLdrCacheDeleteEntry(PSUPHNTLDRCACHEENTRY pEntry)
1936{
1937 if (pEntry->pbBits)
1938 {
1939 RTMemFree(pEntry->pbBits);
1940 pEntry->pbBits = NULL;
1941 }
1942
1943 if (pEntry->hLdrMod != NIL_RTLDRMOD)
1944 {
1945 RTLdrClose(pEntry->hLdrMod);
1946 pEntry->hLdrMod = NIL_RTLDRMOD;
1947 pEntry->pNtViRdr = NULL;
1948 }
1949 else if (pEntry->pNtViRdr)
1950 {
1951 pEntry->pNtViRdr->Core.pfnDestroy(&pEntry->pNtViRdr->Core);
1952 pEntry->pNtViRdr = NULL;
1953 }
1954
1955 if (pEntry->hFile)
1956 {
1957 NtClose(pEntry->hFile);
1958 pEntry->hFile = NULL;
1959 }
1960
1961 pEntry->pszName = NULL;
1962 pEntry->fVerified = false;
1963 pEntry->fValidBits = false;
1964 pEntry->uImageBase = 0;
1965}
1966
1967#ifdef IN_RING3
1968
1969/**
1970 * Flushes the cache.
1971 *
1972 * This is called from one of two points in the hardened main code, first is
1973 * after respawning and the second is when we open the vboxdrv device for
1974 * unrestricted access.
1975 */
1976DECLHIDDEN(void) supR3HardenedWinFlushLoaderCache(void)
1977{
1978 uint32_t i = g_cSupNtVpLdrCacheEntries;
1979 while (i-- > 0)
1980 supHardNTLdrCacheDeleteEntry(&g_aSupNtVpLdrCacheEntries[i]);
1981 g_cSupNtVpLdrCacheEntries = 0;
1982}
1983
1984
1985/**
1986 * Searches the cache for a loader image.
1987 *
1988 * @returns Pointer to the cache entry if found, NULL if not.
1989 * @param pszName The name (from g_apszSupNtVpAllowedVmExes or
1990 * g_apszSupNtVpAllowedDlls).
1991 */
1992static PSUPHNTLDRCACHEENTRY supHardNtLdrCacheLookupEntry(const char *pszName)
1993{
1994 /*
1995 * Since the caller is supplying us a pszName from one of the two tables,
1996 * we can dispense with string compare and simply compare string pointers.
1997 */
1998 uint32_t i = g_cSupNtVpLdrCacheEntries;
1999 while (i-- > 0)
2000 if (g_aSupNtVpLdrCacheEntries[i].pszName == pszName)
2001 return &g_aSupNtVpLdrCacheEntries[i];
2002 return NULL;
2003}
2004
2005#endif /* IN_RING3 */
2006
2007static int supHardNtLdrCacheNewEntry(PSUPHNTLDRCACHEENTRY pEntry, const char *pszName, PUNICODE_STRING pUniStrPath,
2008 bool fDll, bool f32bitResourceDll, PRTERRINFO pErrInfo)
2009{
2010 /*
2011 * Open the image file.
2012 */
2013 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
2014 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
2015
2016 OBJECT_ATTRIBUTES ObjAttr;
2017 InitializeObjectAttributes(&ObjAttr, pUniStrPath, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
2018#ifdef IN_RING0
2019 ObjAttr.Attributes |= OBJ_KERNEL_HANDLE;
2020#endif
2021
2022 NTSTATUS rcNt = NtCreateFile(&hFile,
2023 GENERIC_READ | SYNCHRONIZE,
2024 &ObjAttr,
2025 &Ios,
2026 NULL /* Allocation Size*/,
2027 FILE_ATTRIBUTE_NORMAL,
2028 FILE_SHARE_READ,
2029 FILE_OPEN,
2030 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
2031 NULL /*EaBuffer*/,
2032 0 /*EaLength*/);
2033 if (NT_SUCCESS(rcNt))
2034 rcNt = Ios.Status;
2035 if (!NT_SUCCESS(rcNt))
2036 return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_FILE_OPEN_ERROR,
2037 "Error opening image for scanning: %#x (name %ls)", rcNt, pUniStrPath->Buffer);
2038
2039 /*
2040 * Figure out validation flags we'll be using and create the reader
2041 * for this image.
2042 */
2043 uint32_t fFlags = fDll
2044 ? SUPHNTVI_F_TRUSTED_INSTALLER_OWNER | SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION
2045 : SUPHNTVI_F_REQUIRE_BUILD_CERT;
2046 if (f32bitResourceDll)
2047 fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE;
2048
2049 PSUPHNTVIRDR pNtViRdr;
2050 int rc = supHardNtViRdrCreate(hFile, pUniStrPath->Buffer, fFlags, &pNtViRdr);
2051 if (RT_FAILURE(rc))
2052 {
2053 NtClose(hFile);
2054 return rc;
2055 }
2056
2057 /*
2058 * Finally, open the image with the loader
2059 */
2060 RTLDRMOD hLdrMod;
2061 RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST;
2062 if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE)
2063 enmArch = RTLDRARCH_WHATEVER;
2064 rc = RTLdrOpenWithReader(&pNtViRdr->Core, RTLDR_O_FOR_VALIDATION, enmArch, &hLdrMod, pErrInfo);
2065 if (RT_FAILURE(rc))
2066 return supHardNtVpAddInfo1(pErrInfo, rc, "RTLdrOpenWithReader failed: %Rrc (Image='%ls').",
2067 rc, pUniStrPath->Buffer);
2068
2069 /*
2070 * Fill in the cache entry.
2071 */
2072 pEntry->pszName = pszName;
2073 pEntry->hLdrMod = hLdrMod;
2074 pEntry->pNtViRdr = pNtViRdr;
2075 pEntry->hFile = hFile;
2076 pEntry->pbBits = NULL;
2077 pEntry->fVerified = false;
2078 pEntry->fValidBits = false;
2079 pEntry->uImageBase = ~(uintptr_t)0;
2080
2081#ifdef IN_SUP_HARDENED_R3
2082 /*
2083 * Log the image timestamp when in the hardened exe.
2084 */
2085 uint64_t uTimestamp = 0;
2086 rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uint64_t));
2087 SUP_DPRINTF(("%s: timestamp %#llx (rc=%Rrc)\n", pszName, uTimestamp, rc));
2088#endif
2089
2090 return VINF_SUCCESS;
2091}
2092
2093#ifdef IN_RING3
2094/**
2095 * Opens a loader cache entry.
2096 *
2097 * Currently this is only used by the import code for getting NTDLL.
2098 *
2099 * @returns VBox status code.
2100 * @param pszName The DLL name. Must be one from the
2101 * g_apszSupNtVpAllowedDlls array.
2102 * @param ppEntry Where to return the entry we've opened/found.
2103 * @param pErrInfo Optional buffer where to return additional error
2104 * information.
2105 */
2106DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo)
2107{
2108 /*
2109 * Locate the dll.
2110 */
2111 uint32_t i = 0;
2112 while ( i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls)
2113 && strcmp(pszName, g_apszSupNtVpAllowedDlls[i]))
2114 i++;
2115 if (i >= RT_ELEMENTS(g_apszSupNtVpAllowedDlls))
2116 return VERR_FILE_NOT_FOUND;
2117 pszName = g_apszSupNtVpAllowedDlls[i];
2118
2119 /*
2120 * Try the cache.
2121 */
2122 *ppEntry = supHardNtLdrCacheLookupEntry(pszName);
2123 if (*ppEntry)
2124 return VINF_SUCCESS;
2125
2126 /*
2127 * Not in the cache, so open it.
2128 * Note! We cannot assume that g_System32NtPath has been initialized at this point.
2129 */
2130 if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries))
2131 return VERR_INTERNAL_ERROR_3;
2132
2133 static WCHAR s_wszSystem32[] = L"\\SystemRoot\\System32\\";
2134 WCHAR wszPath[64];
2135 memcpy(wszPath, s_wszSystem32, sizeof(s_wszSystem32));
2136 RTUtf16CatAscii(wszPath, sizeof(wszPath), pszName);
2137
2138 UNICODE_STRING UniStr;
2139 UniStr.Buffer = wszPath;
2140 UniStr.Length = (USHORT)(RTUtf16Len(wszPath) * sizeof(WCHAR));
2141 UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR);
2142
2143 int rc = supHardNtLdrCacheNewEntry(&g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries], pszName, &UniStr,
2144 true /*fDll*/, false /*f32bitResourceDll*/, pErrInfo);
2145 if (RT_SUCCESS(rc))
2146 {
2147 *ppEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries];
2148 g_cSupNtVpLdrCacheEntries++;
2149 return VINF_SUCCESS;
2150 }
2151 return rc;
2152}
2153#endif /* IN_RING3 */
2154
2155
2156/**
2157 * Opens all the images with the IPRT loader, setting both, hFile, pNtViRdr and
2158 * hLdrMod for each image.
2159 *
2160 * @returns VBox status code.
2161 * @param pThis The process scanning state structure.
2162 */
2163static int supHardNtVpOpenImages(PSUPHNTVPSTATE pThis)
2164{
2165 unsigned i = pThis->cImages;
2166 while (i-- > 0)
2167 {
2168 PSUPHNTVPIMAGE pImage = &pThis->aImages[i];
2169
2170#ifdef IN_RING3
2171 /*
2172 * Try the cache first.
2173 */
2174 pImage->pCacheEntry = supHardNtLdrCacheLookupEntry(pImage->pszName);
2175 if (pImage->pCacheEntry)
2176 continue;
2177
2178 /*
2179 * Not in the cache, so load it into the cache.
2180 */
2181 if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries))
2182 return supHardNtVpSetInfo2(pThis, VERR_INTERNAL_ERROR_3, "Loader cache overflow.");
2183 pImage->pCacheEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries];
2184#else
2185 /*
2186 * In ring-0 we don't have a cache at the moment (resource reasons), so
2187 * we have a static cache entry in each image structure that we use instead.
2188 */
2189 pImage->pCacheEntry = &pImage->CacheEntry;
2190#endif
2191
2192 int rc = supHardNtLdrCacheNewEntry(pImage->pCacheEntry, pImage->pszName, &pImage->Name.UniStr,
2193 pImage->fDll, pImage->f32bitResourceDll, pThis->pErrInfo);
2194 if (RT_FAILURE(rc))
2195 return rc;
2196#ifdef IN_RING3
2197 g_cSupNtVpLdrCacheEntries++;
2198#endif
2199 }
2200
2201 return VINF_SUCCESS;
2202}
2203
2204
2205/**
2206 * Check the integrity of the executable of the process.
2207 *
2208 * @returns VBox status code.
2209 * @param pThis The process scanning state structure. Details
2210 * about images are added to this.
2211 * @param hProcess The process to verify.
2212 */
2213static int supHardNtVpCheckExe(PSUPHNTVPSTATE pThis, HANDLE hProcess)
2214{
2215 /*
2216 * Make sure there is exactly one executable image.
2217 */
2218 unsigned cExecs = 0;
2219 unsigned iExe = ~0U;
2220 unsigned i = pThis->cImages;
2221 while (i-- > 0)
2222 {
2223 if (!pThis->aImages[i].fDll)
2224 {
2225 cExecs++;
2226 iExe = i;
2227 }
2228 }
2229 if (cExecs == 0)
2230 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_FOUND_NO_EXE_MAPPING,
2231 "No executable mapping found in the virtual address space.");
2232 if (cExecs != 1)
2233 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FOUND_MORE_THAN_ONE_EXE_MAPPING,
2234 "Found more than one executable mapping in the virtual address space.");
2235 PSUPHNTVPIMAGE pImage = &pThis->aImages[iExe];
2236
2237 /*
2238 * Check that it matches the executable image of the process.
2239 */
2240 int rc;
2241 ULONG cbUniStr = sizeof(UNICODE_STRING) + RTPATH_MAX * sizeof(RTUTF16);
2242 PUNICODE_STRING pUniStr = (PUNICODE_STRING)RTMemAllocZ(cbUniStr);
2243 if (!pUniStr)
2244 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY,
2245 "Error allocating %zu bytes for process name.", cbUniStr);
2246 ULONG cbIgn = 0;
2247 NTSTATUS rcNt = NtQueryInformationProcess(hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn);
2248 if (NT_SUCCESS(rcNt))
2249 {
2250 if (supHardNtVpAreUniStringsEqual(pUniStr, &pImage->Name.UniStr))
2251 rc = VINF_SUCCESS;
2252 else
2253 {
2254 pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0';
2255 rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_VS_PROC_NAME_MISMATCH,
2256 "Process image name does not match the exectuable we found: %ls vs %ls.",
2257 pUniStr->Buffer, pImage->Name.UniStr.Buffer);
2258 }
2259 }
2260 else
2261 rc = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_NM_ERROR,
2262 "NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt);
2263 RTMemFree(pUniStr);
2264 if (RT_FAILURE(rc))
2265 return rc;
2266
2267 /*
2268 * Validate the signing of the executable image.
2269 * This will load the fDllCharecteristics and fImageCharecteristics members we use below.
2270 */
2271 rc = supHardNtVpVerifyImage(pThis, pImage, hProcess);
2272 if (RT_FAILURE(rc))
2273 return rc;
2274
2275 /*
2276 * Check linking requirements.
2277 * This query is only available using the current process pseudo handle on
2278 * older windows versions. The cut-off seems to be Vista.
2279 */
2280 SECTION_IMAGE_INFORMATION ImageInfo;
2281 rcNt = NtQueryInformationProcess(hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL);
2282 if (!NT_SUCCESS(rcNt))
2283 {
2284 if ( rcNt == STATUS_INVALID_PARAMETER
2285 && g_uNtVerCombined < SUP_NT_VER_VISTA
2286 && hProcess != NtCurrentProcess() )
2287 return VINF_SUCCESS;
2288 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_IMG_INFO_ERROR,
2289 "NtQueryInformationProcess/ProcessImageInformation failed: %#x hProcess=%#x", rcNt, hProcess);
2290 }
2291 if ( !(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY))
2292 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_FORCE_INTEGRITY,
2293 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY to be set.",
2294 ImageInfo.DllCharacteristics);
2295 if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE))
2296 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_DYNAMIC_BASE,
2297 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE to be set.",
2298 ImageInfo.DllCharacteristics);
2299 if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT))
2300 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EXE_MISSING_NX_COMPAT,
2301 "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_NX_COMPAT to be set.",
2302 ImageInfo.DllCharacteristics);
2303
2304 if (pImage->fDllCharecteristics != ImageInfo.DllCharacteristics)
2305 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH,
2306 "EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.",
2307 ImageInfo.DllCharacteristics, pImage->fDllCharecteristics);
2308
2309 if (pImage->fImageCharecteristics != ImageInfo.ImageCharacteristics)
2310 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH,
2311 "EXE Info.ImageCharacteristics=%#x fImageCharecteristics=%#x.",
2312 ImageInfo.ImageCharacteristics, pImage->fImageCharecteristics);
2313
2314 return VINF_SUCCESS;
2315}
2316
2317
2318/**
2319 * Check the integrity of the DLLs found in the process.
2320 *
2321 * @returns VBox status code.
2322 * @param pThis The process scanning state structure. Details
2323 * about images are added to this.
2324 * @param hProcess The process to verify.
2325 */
2326static int supHardNtVpCheckDlls(PSUPHNTVPSTATE pThis, HANDLE hProcess)
2327{
2328 /*
2329 * Check for duplicate entries (paranoia).
2330 */
2331 uint32_t i = pThis->cImages;
2332 while (i-- > 1)
2333 {
2334 const char *pszName = pThis->aImages[i].pszName;
2335 uint32_t j = i;
2336 while (j-- > 0)
2337 if (pThis->aImages[j].pszName == pszName)
2338 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING,
2339 "Duplicate image entries for %s: %ls and %ls",
2340 pszName, pThis->aImages[i].Name.UniStr.Buffer, pThis->aImages[j].Name.UniStr.Buffer);
2341 }
2342
2343 /*
2344 * Check that both ntdll and kernel32 are present.
2345 * ASSUMES the entries in g_apszSupNtVpAllowedDlls are all lower case.
2346 */
2347 uint32_t iNtDll = UINT32_MAX;
2348 uint32_t iKernel32 = UINT32_MAX;
2349 i = pThis->cImages;
2350 while (i-- > 0)
2351 if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "ntdll.dll") == 0)
2352 iNtDll = i;
2353 else if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "kernel32.dll") == 0)
2354 iKernel32 = i;
2355 if (iNtDll == UINT32_MAX)
2356 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_NTDLL_MAPPING,
2357 "The process has no NTDLL.DLL.");
2358 if (iKernel32 == UINT32_MAX && pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION)
2359 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_KERNEL32_MAPPING,
2360 "The process has no KERNEL32.DLL.");
2361 else if (iKernel32 != UINT32_MAX && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION)
2362 return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_KERNEL32_ALREADY_MAPPED,
2363 "The process already has KERNEL32.DLL loaded.");
2364
2365 /*
2366 * Verify that the DLLs are correctly signed (by MS).
2367 */
2368 i = pThis->cImages;
2369 while (i-- > 0)
2370 {
2371 int rc = supHardNtVpVerifyImage(pThis, &pThis->aImages[i], hProcess);
2372 if (RT_FAILURE(rc))
2373 return rc;
2374 }
2375
2376 return VINF_SUCCESS;
2377}
2378
2379
2380/**
2381 * Verifies the given process.
2382 *
2383 * The following requirements are checked:
2384 * - The process only has one thread, the calling thread.
2385 * - The process has no debugger attached.
2386 * - The executable image of the process is verified to be signed with
2387 * certificate known to this code at build time.
2388 * - The executable image is one of a predefined set.
2389 * - The process has only a very limited set of system DLLs loaded.
2390 * - The system DLLs signatures check out fine.
2391 * - The only executable memory in the process belongs to the system DLLs and
2392 * the executable image.
2393 *
2394 * @returns VBox status code.
2395 * @param hProcess The process to verify.
2396 * @param hThread A thread in the process (the caller).
2397 * @param enmKind The kind of process verification to perform.
2398 * @param fFlags Valid combination of SUPHARDNTVP_F_XXX flags.
2399 * @param pErrInfo Pointer to error info structure. Optional.
2400 * @param pcFixes Where to return the number of fixes made during
2401 * purification. Optional.
2402 */
2403DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, uint32_t fFlags,
2404 uint32_t *pcFixes, PRTERRINFO pErrInfo)
2405{
2406 if (pcFixes)
2407 *pcFixes = 0;
2408
2409 /*
2410 * Some basic checks regarding threads and debuggers. We don't need
2411 * allocate any state memory for these.
2412 */
2413 int rc = VINF_SUCCESS;
2414 if (enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION)
2415 rc = supHardNtVpThread(hProcess, hThread, pErrInfo);
2416 if (RT_SUCCESS(rc))
2417 rc = supHardNtVpDebugger(hProcess, pErrInfo);
2418 if (RT_SUCCESS(rc))
2419 {
2420 /*
2421 * Allocate and initialize memory for the state.
2422 */
2423 PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)RTMemAllocZ(sizeof(*pThis));
2424 if (pThis)
2425 {
2426 pThis->enmKind = enmKind;
2427 pThis->fFlags = fFlags;
2428 pThis->rcResult = VINF_SUCCESS;
2429 pThis->hProcess = hProcess;
2430 pThis->pErrInfo = pErrInfo;
2431
2432 /*
2433 * Perform the verification.
2434 */
2435 rc = supHardNtVpScanVirtualMemory(pThis, hProcess);
2436 if (RT_SUCCESS(rc))
2437 rc = supHardNtVpOpenImages(pThis);
2438 if (RT_SUCCESS(rc))
2439 rc = supHardNtVpCheckExe(pThis, hProcess);
2440 if (RT_SUCCESS(rc))
2441 rc = supHardNtVpCheckDlls(pThis, hProcess);
2442
2443 if (pcFixes)
2444 *pcFixes = pThis->cFixes;
2445
2446 /*
2447 * Clean up the state.
2448 */
2449#ifdef IN_RING0
2450 for (uint32_t i = 0; i < pThis->cImages; i++)
2451 supHardNTLdrCacheDeleteEntry(&pThis->aImages[i].CacheEntry);
2452#endif
2453 RTMemFree(pThis);
2454 }
2455 else
2456 rc = supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY_STATE,
2457 "Failed to allocate %zu bytes for state structures.", sizeof(*pThis));
2458 }
2459 return rc;
2460}
2461
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette