/* $Id: SUPR3HardenedNoCrt-win.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */ /** @file * VirtualBox Support Library - Hardened main(), windows bits. */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #ifndef PROCESS_SET_LIMITED_INFORMATION # define PROCESS_SET_LIMITED_INFORMATION 0x2000 #endif #include #include #include #include #include #include #include #include #include #include #include #include "SUPLibInternal.h" #include "win/SUPHardenedVerify-win.h" /* * assert.cpp */ RTDATADECL(char) g_szRTAssertMsg1[1024]; RTDATADECL(char) g_szRTAssertMsg2[4096]; RTDATADECL(const char * volatile) g_pszRTAssertExpr; RTDATADECL(const char * volatile) g_pszRTAssertFile; RTDATADECL(uint32_t volatile) g_u32RTAssertLine; RTDATADECL(const char * volatile) g_pszRTAssertFunction; RTDECL(bool) RTAssertMayPanic(void) { return true; } RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) { /* * Fill in the globals. */ g_pszRTAssertExpr = pszExpr; g_pszRTAssertFile = pszFile; g_pszRTAssertFunction = pszFunction; g_u32RTAssertLine = uLine; RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), "\n!!Assertion Failed!!\n" "Expression: %s\n" "Location : %s(%d) %s\n", pszExpr, pszFile, uLine, pszFunction); } RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) { RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, va); if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN) supR3HardenedFatalMsg(g_pszRTAssertExpr, kSupInitOp_Misc, VERR_INTERNAL_ERROR, "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); else supR3HardenedError(VERR_INTERNAL_ERROR, false/*fFatal*/, "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); } /* * Memory allocator. */ /** The handle of the heap we're using. */ static HANDLE g_hSupR3HardenedHeap = NULL; /** Number of heaps used during early process init. */ static uint32_t g_cSupR3HardenedEarlyHeaps = 0; /** Early process init heaps. */ static struct { /** The heap handle. */ RTHEAPSIMPLE hHeap; /** The heap block pointer. */ void *pvBlock; /** The size of the heap block. */ size_t cbBlock; /** Number of active allocations on this heap. */ size_t cAllocations; } g_aSupR3HardenedEarlyHeaps[8]; static uint32_t supR3HardenedEarlyFind(void *pv) RT_NOTHROW_DEF { uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; while (iHeap-- > 0) if ((uintptr_t)pv - (uintptr_t)g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock < g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock) return iHeap; return UINT32_MAX; } static void supR3HardenedEarlyCompact(void) RT_NOTHROW_DEF { uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; while (iHeap-- > 0) if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations == 0) { PVOID pvMem = g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock; SIZE_T cbMem = g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock; if (iHeap + 1 < g_cSupR3HardenedEarlyHeaps) g_aSupR3HardenedEarlyHeaps[iHeap] = g_aSupR3HardenedEarlyHeaps[g_cSupR3HardenedEarlyHeaps - 1]; g_cSupR3HardenedEarlyHeaps--; NTSTATUS rcNt = NtFreeVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, MEM_RELEASE); Assert(NT_SUCCESS(rcNt)); RT_NOREF_PV(rcNt); SUP_DPRINTF(("supR3HardenedEarlyCompact: Removed heap %#u (%#p LB %#zx)\n", iHeap, pvMem, cbMem)); } } static void *supR3HardenedEarlyAlloc(size_t cb, bool fZero) RT_NOTHROW_DEF { /* * Try allocate on existing heaps. */ void *pv; uint32_t iHeap = 0; while (iHeap < g_cSupR3HardenedEarlyHeaps) { if (fZero) pv = RTHeapSimpleAllocZ(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); else pv = RTHeapSimpleAlloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); if (pv) { g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations++; #ifdef SUPR3HARDENED_EARLY_HEAP_TRACE SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); #endif return pv; } iHeap++; } /* * Add another heap. */ if (iHeap == RT_ELEMENTS(g_aSupR3HardenedEarlyHeaps)) supR3HardenedFatal("Early heap table is full (cb=%#zx).\n", cb); SIZE_T cbBlock = iHeap == 0 ? _1M : g_aSupR3HardenedEarlyHeaps[iHeap - 1].cbBlock * 2; while (cbBlock <= cb * 2) cbBlock *= 2; PVOID pvBlock = NULL; NTSTATUS rcNt = NtAllocateVirtualMemory(NtCurrentProcess(), &pvBlock, 0 /*ZeroBits*/, &cbBlock, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(rcNt)) supR3HardenedFatal("NtAllocateVirtualMemory(,,,%#zx,,) failed: rcNt=%#x\n", cbBlock, rcNt); SUP_DPRINTF(("New simple heap: #%u %p LB %#zx (for %zu allocation)\n", iHeap, pvBlock, cbBlock, cb)); RTHEAPSIMPLE hHeap; int rc = RTHeapSimpleInit(&hHeap, pvBlock, cbBlock); if (RT_FAILURE(rc)) supR3HardenedFatal("RTHeapSimpleInit(,%p,%#zx) failed: rc=%#x\n", pvBlock, cbBlock, rc); if (fZero) pv = RTHeapSimpleAllocZ(hHeap, cb, 0); else pv = RTHeapSimpleAlloc(hHeap, cb, 0); if (!pv) supR3HardenedFatal("RTHeapSimpleAlloc[Z] failed allocating %#zx bytes on a %#zu heap.\n", cb, cbBlock); g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock = pvBlock; g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock = cbBlock; g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations = 1; g_aSupR3HardenedEarlyHeaps[iHeap].hHeap = hHeap; Assert(g_cSupR3HardenedEarlyHeaps == iHeap); g_cSupR3HardenedEarlyHeaps = iHeap + 1; #ifdef SUPR3HARDENED_EARLY_HEAP_TRACE SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); #endif return pv; } /** * Lazy heap initialization function. * * @returns Heap handle. */ static HANDLE supR3HardenedHeapInit(void) RT_NOTHROW_DEF { Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED); HANDLE hHeap = RtlCreateHeap(HEAP_GROWABLE | HEAP_CLASS_PRIVATE, NULL /*HeapBase*/, 0 /*ReserveSize*/, 0 /*CommitSize*/, NULL /*Lock*/, NULL /*Parameters*/); if (hHeap) { g_hSupR3HardenedHeap = hHeap; return hHeap; } supR3HardenedFatal("RtlCreateHeap failed.\n"); /* not reached */ } /** * Compacts the heaps before enter wait for parent/child. */ DECLHIDDEN(void) supR3HardenedWinCompactHeaps(void) { if (g_hSupR3HardenedHeap) RtlCompactHeap(g_hSupR3HardenedHeap, 0 /*dwFlags*/); RtlCompactHeap(GetProcessHeap(), 0 /*dwFlags*/); supR3HardenedEarlyCompact(); } #undef RTMemTmpAllocTag RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF { return RTMemAllocTag(cb, pszTag); } #undef RTMemTmpAllocZTag RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF { return RTMemAllocZTag(cb, pszTag); } #undef RTMemTmpFree RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW_DEF { RTMemFree(pv); } #undef RTMemAllocTag RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF { RT_NOREF1(pszTag); HANDLE hHeap = g_hSupR3HardenedHeap; if (!hHeap) { if ( g_fSupEarlyProcessInit && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) return supR3HardenedEarlyAlloc(cb, false /*fZero*/); hHeap = supR3HardenedHeapInit(); } void *pv = RtlAllocateHeap(hHeap, 0 /*fFlags*/, cb); if (!pv) supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); return pv; } #undef RTMemAllocZTag RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF { RT_NOREF1(pszTag); HANDLE hHeap = g_hSupR3HardenedHeap; if (!hHeap) { if ( g_fSupEarlyProcessInit && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) return supR3HardenedEarlyAlloc(cb, true /*fZero*/); hHeap = supR3HardenedHeapInit(); } void *pv = RtlAllocateHeap(hHeap, HEAP_ZERO_MEMORY, cb); if (!pv) supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); return pv; } #undef RTMemAllocVarTag RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF { size_t cbAligned; if (cbUnaligned >= 16) cbAligned = RT_ALIGN_Z(cbUnaligned, 16); else cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); return RTMemAllocTag(cbAligned, pszTag); } #undef RTMemAllocZVarTag RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF { size_t cbAligned; if (cbUnaligned >= 16) cbAligned = RT_ALIGN_Z(cbUnaligned, 16); else cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); return RTMemAllocZTag(cbAligned, pszTag); } #undef RTMemReallocTag RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF { if (!pvOld) return RTMemAllocZTag(cbNew, pszTag); void *pv; if (g_fSupEarlyProcessInit) { uint32_t iHeap = supR3HardenedEarlyFind(pvOld); if (iHeap != UINT32_MAX) { #if 0 /* RTHeapSimpleRealloc is not implemented */ /* If this is before we can use a regular heap, we try resize within the simple heap. (There are a lot of array growing in the ASN.1 code.) */ if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) { pv = RTHeapSimpleRealloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld, cbNew, 0); if (pv) { # ifdef SUPR3HARDENED_EARLY_HEAP_TRACE SUP_DPRINTF(("Early heap: %p LB %#zx, was %p - realloc\n", pvNew, cbNew, pvOld)); # endif return pv; } } #endif /* Either we can't reallocate it on the same simple heap, or we're past hardened main and wish to migrate everything over on the real heap. */ size_t cbOld = RTHeapSimpleSize(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); pv = RTMemAllocTag(cbNew, pszTag); if (pv) { memcpy(pv, pvOld, RT_MIN(cbOld, cbNew)); RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) supR3HardenedEarlyCompact(); } # ifdef SUPR3HARDENED_EARLY_HEAP_TRACE SUP_DPRINTF(("Early heap: %p LB %#zx, was %p %LB %#zx - realloc\n", pv, cbNew, pvOld, cbOld)); # endif return pv; } Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); } /* Allocate from the regular heap. */ HANDLE hHeap = g_hSupR3HardenedHeap; Assert(hHeap != NULL); pv = RtlReAllocateHeap(hHeap, 0 /*dwFlags*/, pvOld, cbNew); if (!pv) supR3HardenedFatal("RtlReAllocateHeap failed to allocate %zu bytes.\n", cbNew); return pv; } #undef RTMemFree RTDECL(void) RTMemFree(void *pv) RT_NO_THROW_DEF { if (pv) { if (g_fSupEarlyProcessInit) { uint32_t iHeap = supR3HardenedEarlyFind(pv); if (iHeap != UINT32_MAX) { #ifdef SUPR3HARDENED_EARLY_HEAP_TRACE SUP_DPRINTF(("Early heap: %p - free\n", pv)); #endif RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pv); if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) supR3HardenedEarlyCompact(); return; } Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); } HANDLE hHeap = g_hSupR3HardenedHeap; Assert(hHeap != NULL); RtlFreeHeap(hHeap, 0 /* dwFlags*/, pv); } } /* * Simplified version of RTMemWipeThoroughly that avoids dragging in the * random number code. */ RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF { size_t cPasses = RT_MIN(cMinPasses, 6); static const uint32_t s_aPatterns[] = { 0x00, 0xaa, 0x55, 0xff, 0xf0, 0x0f, 0xcc, 0x3c, 0xc3 }; uint32_t iPattern = 0; do { memset(pv, s_aPatterns[iPattern], cb); iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); ASMMemoryFence(); memset(pv, s_aPatterns[iPattern], cb); iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); ASMMemoryFence(); memset(pv, s_aPatterns[iPattern], cb); iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); ASMMemoryFence(); } while (cPasses-- > 0); memset(pv, 0xff, cb); ASMMemoryFence(); } /* * path-win.cpp */ RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cbPath) { int rc; if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) /** @todo Rainy day: improve this by checking the process parameter block * (needs to be normalized). */ rc = RTStrCopy(pszPath, cbPath, "C:\\"); else { /* * GetCurrentDirectory may in some cases omit the drive letter, according * to MSDN, thus the GetFullPathName call. */ RTUTF16 wszCurPath[RTPATH_MAX]; if (GetCurrentDirectoryW(RTPATH_MAX, wszCurPath)) { RTUTF16 wszFullPath[RTPATH_MAX]; if (GetFullPathNameW(wszCurPath, RTPATH_MAX, wszFullPath, NULL)) rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cbPath, NULL); else rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); } else rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); } return rc; }