/* $Id: SUPDrv-win.cpp $ */ /** @file * VBoxDrv - The VirtualBox Support Driver - Windows NT specifics. */ /* * Copyright (C) 2006-2022 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 * *********************************************************************************************************************************/ #ifndef IPRT_NT_MAP_TO_ZW # define IPRT_NT_MAP_TO_ZW #endif #define LOG_GROUP LOG_GROUP_SUP_DRV #include "../SUPDrvInternal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef VBOX_WITH_HARDENING # include "SUPHardenedVerify-win.h" #endif /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The support service name. */ #define SERVICE_NAME "VBoxDrv" /** The Pool tag (VBox). */ #define SUPDRV_NT_POOL_TAG 'xoBV' /** NT device name for user access. */ #define DEVICE_NAME_NT_USR L"\\Device\\VBoxDrvU" #ifdef VBOX_WITH_HARDENING /** Macro for checking for deflecting calls to the stub device. */ # define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) \ do { if ((a_pDevObj) == g_pDevObjStub) \ return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ } while (0) /** Macro for checking for deflecting calls to the stub and error info * devices. */ # define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) \ do { if ((a_pDevObj) == g_pDevObjStub || (a_pDevObj) == g_pDevObjErrorInfo) \ return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ } while (0) #else # define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) do {} while (0) # define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) do {} while (0) #endif /** Enables the fast I/O control code path. */ #define VBOXDRV_WITH_FAST_IO /** Enables generating UID from NT SIDs so the GMM can share free memory * among VMs running as the same user. */ #define VBOXDRV_WITH_SID_TO_UID_MAPPING /* Missing if we're compiling against older WDKs. */ #ifndef NonPagedPoolNx # define NonPagedPoolNx ((POOL_TYPE)512) #endif /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING /** * SID to User ID mapping. * * This is used to generate a RTUID value for a NT security identifier. * Normally, the UID is the hash of the SID string, but due to collisions it may * differ. See g_NtUserIdHashTree and g_NtUserIdUidTree. */ typedef struct SUPDRVNTUSERID { /** Hash tree node, key: RTStrHash1 of szSid. */ AVLLU32NODECORE HashCore; /** UID three node, key: The UID. */ AVLU32NODECORE UidCore; /** Reference counter. */ uint32_t volatile cRefs; /** The length of the SID string. */ uint16_t cchSid; /** The SID string for the user. */ char szSid[RT_FLEXIBLE_ARRAY]; } SUPDRVNTUSERID; /** Pointer to a SID to UID mapping. */ typedef SUPDRVNTUSERID *PSUPDRVNTUSERID; #endif /** * Device extension used by VBoxDrvU. */ typedef struct SUPDRVDEVEXTUSR { /** Global cookie (same location as in SUPDRVDEVEXT, different value). */ uint32_t u32Cookie; /** Pointer to the main driver extension. */ PSUPDRVDEVEXT pMainDrvExt; } SUPDRVDEVEXTUSR; AssertCompileMembersAtSameOffset(SUPDRVDEVEXT, u32Cookie, SUPDRVDEVEXTUSR, u32Cookie); /** Pointer to the VBoxDrvU device extension. */ typedef SUPDRVDEVEXTUSR *PSUPDRVDEVEXTUSR; /** Value of SUPDRVDEVEXTUSR::u32Cookie. */ #define SUPDRVDEVEXTUSR_COOKIE UINT32_C(0x12345678) /** Get the main device extension. */ #define SUPDRVNT_GET_DEVEXT(pDevObj) \ ( pDevObj != g_pDevObjUsr \ ? (PSUPDRVDEVEXT)pDevObj->DeviceExtension \ : ((PSUPDRVDEVEXTUSR)pDevObj->DeviceExtension)->pMainDrvExt ) #ifdef VBOX_WITH_HARDENING /** * Device extension used by VBoxDrvStub. */ typedef struct SUPDRVDEVEXTSTUB { /** Common header. */ SUPDRVDEVEXTUSR Common; } SUPDRVDEVEXTSTUB; /** Pointer to the VBoxDrvStub device extension. */ typedef SUPDRVDEVEXTSTUB *PSUPDRVDEVEXTSTUB; /** Value of SUPDRVDEVEXTSTUB::Common.u32Cookie. */ #define SUPDRVDEVEXTSTUB_COOKIE UINT32_C(0x90abcdef) /** * Device extension used by VBoxDrvErrorInfo. */ typedef struct SUPDRVDEVEXTERRORINFO { /** Common header. */ SUPDRVDEVEXTUSR Common; } SUPDRVDEVEXTERRORINFO; /** Pointer to the VBoxDrvErrorInfo device extension. */ typedef SUPDRVDEVEXTERRORINFO *PSUPDRVDEVEXTERRORINFO; /** Value of SUPDRVDEVEXTERRORINFO::Common.u32Cookie. */ #define SUPDRVDEVEXTERRORINFO_COOKIE UINT32_C(0xBadC0ca0) /** * Error info for a failed VBoxDrv or VBoxDrvStub open attempt. */ typedef struct SUPDRVNTERRORINFO { /** The list entry (in g_ErrorInfoHead). */ RTLISTNODE ListEntry; /** The ID of the process this error info belongs to. */ HANDLE hProcessId; /** The ID of the thread owning this info. */ HANDLE hThreadId; /** Milliseconds createion timestamp (for cleaning up). */ uint64_t uCreatedMsTs; /** Number of bytes of valid info. */ uint32_t cchErrorInfo; /** The error info. */ char szErrorInfo[16384 - sizeof(RTLISTNODE) - sizeof(HANDLE)*2 - sizeof(uint64_t) - sizeof(uint32_t) - 0x20]; } SUPDRVNTERRORINFO; /** Pointer to error info. */ typedef SUPDRVNTERRORINFO *PSUPDRVNTERRORINFO; /** * The kind of process we're protecting. */ typedef enum SUPDRVNTPROTECTKIND { kSupDrvNtProtectKind_Invalid = 0, /** Stub process protection while performing process verification. * Next: StubSpawning (or free) */ kSupDrvNtProtectKind_StubUnverified, /** Stub process protection before it creates the VM process. * Next: StubParent, StubDead. */ kSupDrvNtProtectKind_StubSpawning, /** Stub process protection while having a VM process as child. * Next: StubDead */ kSupDrvNtProtectKind_StubParent, /** Dead stub process. */ kSupDrvNtProtectKind_StubDead, /** Potential VM process. * Next: VmProcessConfirmed, VmProcessDead. */ kSupDrvNtProtectKind_VmProcessUnconfirmed, /** Confirmed VM process. * Next: VmProcessDead. */ kSupDrvNtProtectKind_VmProcessConfirmed, /** Dead VM process. */ kSupDrvNtProtectKind_VmProcessDead, /** End of valid protection kinds. */ kSupDrvNtProtectKind_End } SUPDRVNTPROTECTKIND; /** * A NT process protection structure. */ typedef struct SUPDRVNTPROTECT { /** The AVL node core structure. The process ID is the pid. */ AVLPVNODECORE AvlCore; /** Magic value (SUPDRVNTPROTECT_MAGIC). */ uint32_t volatile u32Magic; /** Reference counter. */ uint32_t volatile cRefs; /** The kind of process we're protecting. */ SUPDRVNTPROTECTKIND volatile enmProcessKind; /** Whether this structure is in the tree. */ bool fInTree : 1; /** 7,: Hack to allow the supid themes service duplicate handle privileges to * our process. */ bool fThemesFirstProcessCreateHandle : 1; /** Vista, 7 & 8: Hack to allow more rights to the handle returned by * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ bool fFirstProcessCreateHandle : 1; /** Vista, 7 & 8: Hack to allow more rights to the handle returned by * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ bool fFirstThreadCreateHandle : 1; /** 8.1: Hack to allow more rights to the handle returned by * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ bool fCsrssFirstProcessCreateHandle : 1; /** Vista, 7 & 8: Hack to allow more rights to the handle duplicated by CSRSS * during process creation. Only applicable to VmProcessUnconfirmed. On * 32-bit systems we allow two as ZoneAlarm's system call hooks has been * observed to do some seemingly unnecessary duplication work. */ int32_t volatile cCsrssFirstProcessDuplicateHandle; /** The parent PID for VM processes, otherwise NULL. */ HANDLE hParentPid; /** The TID of the thread opening VBoxDrv or VBoxDrvStub, NULL if not opened. */ HANDLE hOpenTid; /** The PID of the CSRSS process associated with this process. */ HANDLE hCsrssPid; /** Pointer to the CSRSS process structure (referenced). */ PEPROCESS pCsrssProcess; /** State dependent data. */ union { /** A stub process in the StubParent state will keep a reference to a child * while it's in the VmProcessUnconfirmed state so that it can be cleaned up * correctly if things doesn't work out. */ struct SUPDRVNTPROTECT *pChild; /** A process in the VmProcessUnconfirmed state will keep a weak * reference to the parent's protection structure so it can clean up the pChild * reference the parent has to it. */ struct SUPDRVNTPROTECT *pParent; } u; } SUPDRVNTPROTECT; /** Pointer to a NT process protection record. */ typedef SUPDRVNTPROTECT *PSUPDRVNTPROTECT; /** The SUPDRVNTPROTECT::u32Magic value (Robert A. Heinlein). */ # define SUPDRVNTPROTECT_MAGIC UINT32_C(0x19070707) /** The SUPDRVNTPROTECT::u32Magic value of a dead structure. */ # define SUPDRVNTPROTECT_MAGIC_DEAD UINT32_C(0x19880508) /** Pointer to ObGetObjectType. */ typedef POBJECT_TYPE (NTAPI *PFNOBGETOBJECTTYPE)(PVOID); /** Pointer to ObRegisterCallbacks. */ typedef NTSTATUS (NTAPI *PFNOBREGISTERCALLBACKS)(POB_CALLBACK_REGISTRATION, PVOID *); /** Pointer to ObUnregisterCallbacks. */ typedef VOID (NTAPI *PFNOBUNREGISTERCALLBACKS)(PVOID); /** Pointer to PsSetCreateProcessNotifyRoutineEx. */ typedef NTSTATUS (NTAPI *PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)(PCREATE_PROCESS_NOTIFY_ROUTINE_EX, BOOLEAN); /** Pointer to PsReferenceProcessFilePointer. */ typedef NTSTATUS (NTAPI *PFNPSREFERENCEPROCESSFILEPOINTER)(PEPROCESS, PFILE_OBJECT *); /** Pointer to PsIsProtectedProcessLight. */ typedef BOOLEAN (NTAPI *PFNPSISPROTECTEDPROCESSLIGHT)(PEPROCESS); /** Pointer to ZwAlpcCreatePort. */ typedef NTSTATUS (NTAPI *PFNZWALPCCREATEPORT)(PHANDLE, POBJECT_ATTRIBUTES, struct _ALPC_PORT_ATTRIBUTES *); #endif /* VBOX_WITH_HARDENINIG */ /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj); static NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp); static NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp); static NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp); #ifdef VBOXDRV_WITH_FAST_IO static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, PVOID pvOutput, ULONG cbOutput, ULONG uCmd, PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj); #endif static NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack); static NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); static VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1, PVOID pArgument2); static NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp); static NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp); static NTSTATUS VBoxDrvNtErr2NtStatus(int rc); #ifdef VBOX_WITH_HARDENING static NTSTATUS supdrvNtProtectInit(void); static void supdrvNtProtectTerm(void); static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, SUPDRVNTPROTECTKIND enmProcessKind, bool fLink); static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect); static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid); static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect); static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect); static bool supdrvNtIsDebuggerAttached(void); static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId); #endif /********************************************************************************************************************************* * Exported Functions * *********************************************************************************************************************************/ RT_C_DECLS_BEGIN NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); RT_C_DECLS_END /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** The non-paged pool type to use, NonPagedPool or NonPagedPoolNx. */ static POOL_TYPE g_enmNonPagedPoolType = NonPagedPool; /** Pointer to the system device instance. */ static PDEVICE_OBJECT g_pDevObjSys = NULL; /** Pointer to the user device instance. */ static PDEVICE_OBJECT g_pDevObjUsr = NULL; #ifdef VBOXDRV_WITH_FAST_IO /** Fast I/O dispatch table. */ static FAST_IO_DISPATCH const g_VBoxDrvFastIoDispatch = { /* .SizeOfFastIoDispatch = */ sizeof(g_VBoxDrvFastIoDispatch), /* .FastIoCheckIfPossible = */ NULL, /* .FastIoRead = */ NULL, /* .FastIoWrite = */ NULL, /* .FastIoQueryBasicInfo = */ NULL, /* .FastIoQueryStandardInfo = */ NULL, /* .FastIoLock = */ NULL, /* .FastIoUnlockSingle = */ NULL, /* .FastIoUnlockAll = */ NULL, /* .FastIoUnlockAllByKey = */ NULL, /* .FastIoDeviceControl = */ VBoxDrvNtFastIoDeviceControl, /* .AcquireFileForNtCreateSection = */ NULL, /* .ReleaseFileForNtCreateSection = */ NULL, /* .FastIoDetachDevice = */ NULL, /* .FastIoQueryNetworkOpenInfo = */ NULL, /* .AcquireForModWrite = */ NULL, /* .MdlRead = */ NULL, /* .MdlReadComplete = */ NULL, /* .PrepareMdlWrite = */ NULL, /* .MdlWriteComplete = */ NULL, /* .FastIoReadCompressed = */ NULL, /* .FastIoWriteCompressed = */ NULL, /* .MdlReadCompleteCompressed = */ NULL, /* .MdlWriteCompleteCompressed = */ NULL, /* .FastIoQueryOpen = */ NULL, /* .ReleaseForModWrite = */ NULL, /* .AcquireForCcFlush = */ NULL, /* .ReleaseForCcFlush = */ NULL, }; #endif /* VBOXDRV_WITH_FAST_IO */ /** Default ZERO value. */ static ULONG g_fOptDefaultZero = 0; /** Registry values. * We wrap these in a struct to ensure they are followed by a little zero * padding in order to limit the chance of trouble on unpatched systems. */ struct { /** The ForceAsync registry value. */ ULONG fOptForceAsyncTsc; /** Padding. */ uint64_t au64Padding[2]; } g_Options = { FALSE, 0, 0 }; /** Registry query table for RtlQueryRegistryValues. */ static RTL_QUERY_REGISTRY_TABLE g_aRegValues[] = { { /* .QueryRoutine = */ NULL, /* .Flags = */ RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, /* .Name = */ L"ForceAsyncTsc", /* .EntryContext = */ &g_Options.fOptForceAsyncTsc, /* .DefaultType = */ (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, /* .DefaultData = */ &g_fOptDefaultZero, /* .DefaultLength = */ sizeof(g_fOptDefaultZero), }, { NULL, 0, NULL, NULL, 0, NULL, 0 } /* terminator entry. */ }; /** Pointer to KeQueryMaximumGroupCount. */ static PFNKEQUERYMAXIMUMGROUPCOUNT g_pfnKeQueryMaximumGroupCount = NULL; /** Pointer to KeGetProcessorIndexFromNumber. */ static PFNKEGETPROCESSORINDEXFROMNUMBER g_pfnKeGetProcessorIndexFromNumber = NULL; /** Pointer to KeGetProcessorNumberFromIndex. */ static PFNKEGETPROCESSORNUMBERFROMINDEX g_pfnKeGetProcessorNumberFromIndex = NULL; #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING /** Spinlock protecting g_NtUserIdHashTree and g_NtUserIdUidTree. */ static RTSPINLOCK g_hNtUserIdLock = NIL_RTSPINLOCK; /** AVL tree of SUPDRVNTUSERID structures by hash value. */ static PAVLLU32NODECORE g_NtUserIdHashTree = NULL; /** AVL tree of SUPDRVNTUSERID structures by UID. */ static PAVLU32NODECORE g_NtUserIdUidTree = NULL; #endif #ifdef VBOX_WITH_HARDENING /** Pointer to the stub device instance. */ static PDEVICE_OBJECT g_pDevObjStub = NULL; /** Spinlock protecting g_NtProtectTree as well as the releasing of protection * structures. */ static RTSPINLOCK g_hNtProtectLock = NIL_RTSPINLOCK; /** AVL tree of SUPDRVNTPROTECT structures. */ static AVLPVTREE g_NtProtectTree = NULL; /** Cookie returned by ObRegisterCallbacks for the callbacks. */ static PVOID g_pvObCallbacksCookie = NULL; /** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ uint32_t g_uNtVerCombined = 0; /** Pointer to ObGetObjectType if available.. */ static PFNOBGETOBJECTTYPE g_pfnObGetObjectType = NULL; /** Pointer to ObRegisterCallbacks if available.. */ static PFNOBREGISTERCALLBACKS g_pfnObRegisterCallbacks = NULL; /** Pointer to ObUnregisterCallbacks if available.. */ static PFNOBUNREGISTERCALLBACKS g_pfnObUnRegisterCallbacks = NULL; /** Pointer to PsSetCreateProcessNotifyRoutineEx if available.. */ static PFNPSSETCREATEPROCESSNOTIFYROUTINEEX g_pfnPsSetCreateProcessNotifyRoutineEx = NULL; /** Pointer to PsReferenceProcessFilePointer if available. */ static PFNPSREFERENCEPROCESSFILEPOINTER g_pfnPsReferenceProcessFilePointer = NULL; /** Pointer to PsIsProtectedProcessLight. */ static PFNPSISPROTECTEDPROCESSLIGHT g_pfnPsIsProtectedProcessLight = NULL; /** Pointer to ZwAlpcCreatePort. */ static PFNZWALPCCREATEPORT g_pfnZwAlpcCreatePort = NULL; # ifdef RT_ARCH_AMD64 extern "C" { /** Pointer to KiServiceLinkage (used to fake missing ZwQueryVirtualMemory on * XP64 / W2K3-64). */ PFNRT g_pfnKiServiceLinkage = NULL; /** Pointer to KiServiceInternal (used to fake missing ZwQueryVirtualMemory on * XP64 / W2K3-64) */ PFNRT g_pfnKiServiceInternal = NULL; } # endif /** The primary ALPC port object type. (LpcPortObjectType at init time.) */ static POBJECT_TYPE g_pAlpcPortObjectType1 = NULL; /** The secondary ALPC port object type. (Sampled at runtime.) */ static POBJECT_TYPE volatile g_pAlpcPortObjectType2 = NULL; /** Pointer to the error information device instance. */ static PDEVICE_OBJECT g_pDevObjErrorInfo = NULL; /** Fast mutex semaphore protecting the error info list. */ static RTSEMMUTEX g_hErrorInfoLock = NIL_RTSEMMUTEX; /** Head of the error info (SUPDRVNTERRORINFO). */ static RTLISTANCHOR g_ErrorInfoHead; #endif /** * Takes care of creating the devices and their symbolic links. * * @returns NT status code. * @param pDrvObj Pointer to driver object. */ static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) { /* * System device. */ UNICODE_STRING DevName; RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_SYS); NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXT), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjSys); if (NT_SUCCESS(rcNt)) { /* * User device. */ RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_USR); rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTUSR), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjUsr); if (NT_SUCCESS(rcNt)) { PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; pDevExtUsr->pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; pDevExtUsr->u32Cookie = SUPDRVDEVEXTUSR_COOKIE; #ifdef VBOX_WITH_HARDENING /* * Hardened stub device. */ RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_STUB); rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTSTUB), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjStub); if (NT_SUCCESS(rcNt)) { if (NT_SUCCESS(rcNt)) { PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; pDevExtStub->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; pDevExtStub->Common.u32Cookie = SUPDRVDEVEXTSTUB_COOKIE; /* * Hardened error information device. */ RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_ERROR_INFO); rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTERRORINFO), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjErrorInfo); if (NT_SUCCESS(rcNt)) { g_pDevObjErrorInfo->Flags |= DO_BUFFERED_IO; if (NT_SUCCESS(rcNt)) { PSUPDRVDEVEXTERRORINFO pDevExtErrInf = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; pDevExtErrInf->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; pDevExtErrInf->Common.u32Cookie = SUPDRVDEVEXTERRORINFO_COOKIE; #endif /* Done. */ return rcNt; #ifdef VBOX_WITH_HARDENING } /* Bail out. */ IoDeleteDevice(g_pDevObjErrorInfo); g_pDevObjErrorInfo = NULL; } } /* Bail out. */ IoDeleteDevice(g_pDevObjStub); g_pDevObjUsr = NULL; } IoDeleteDevice(g_pDevObjUsr); g_pDevObjUsr = NULL; #endif } IoDeleteDevice(g_pDevObjSys); g_pDevObjSys = NULL; } return rcNt; } /** * Destroys the devices and links created by vboxdrvNtCreateDevices. */ static void vboxdrvNtDestroyDevices(void) { if (g_pDevObjUsr) { PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; pDevExtUsr->pMainDrvExt = NULL; } #ifdef VBOX_WITH_HARDENING if (g_pDevObjStub) { PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; pDevExtStub->Common.pMainDrvExt = NULL; } if (g_pDevObjErrorInfo) { PSUPDRVDEVEXTERRORINFO pDevExtErrorInfo = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; pDevExtErrorInfo->Common.pMainDrvExt = NULL; } #endif #ifdef VBOX_WITH_HARDENING IoDeleteDevice(g_pDevObjErrorInfo); g_pDevObjErrorInfo = NULL; IoDeleteDevice(g_pDevObjStub); g_pDevObjStub = NULL; #endif IoDeleteDevice(g_pDevObjUsr); g_pDevObjUsr = NULL; IoDeleteDevice(g_pDevObjSys); g_pDevObjSys = NULL; } /** * Driver entry point. * * @returns appropriate status code. * @param pDrvObj Pointer to driver object. * @param pRegPath Registry base path. */ NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) { RT_NOREF1(pRegPath); /* * Sanity checks. */ #ifdef VBOXDRV_WITH_FAST_IO if (g_VBoxDrvFastIoDispatch.FastIoDeviceControl != VBoxDrvNtFastIoDeviceControl) { DbgPrint("VBoxDrv: FastIoDeviceControl=%p instead of %p\n", g_VBoxDrvFastIoDispatch.FastIoDeviceControl, VBoxDrvNtFastIoDeviceControl); return STATUS_INTERNAL_ERROR; } #endif /* * Figure out if we can use NonPagedPoolNx or not. */ ULONG ulMajorVersion, ulMinorVersion, ulBuildNumber; PsGetVersion(&ulMajorVersion, &ulMinorVersion, &ulBuildNumber, NULL); if (ulMajorVersion > 6 || (ulMajorVersion == 6 && ulMinorVersion >= 2)) /* >= 6.2 (W8)*/ g_enmNonPagedPoolType = NonPagedPoolNx; /* * Query options first so any overflows on unpatched machines will do less * harm (see MS11-011 / 2393802 / 2011-03-18). * * Unfortunately, pRegPath isn't documented as zero terminated, even if it * quite likely always is, so we have to make a copy here. */ NTSTATUS rcNt; PWSTR pwszCopy = (PWSTR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, pRegPath->Length + sizeof(WCHAR), 'VBox'); if (pwszCopy) { memcpy(pwszCopy, pRegPath->Buffer, pRegPath->Length); pwszCopy[pRegPath->Length / sizeof(WCHAR)] = '\0'; rcNt = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, pwszCopy, g_aRegValues, NULL /*pvContext*/, NULL /*pvEnv*/); ExFreePoolWithTag(pwszCopy, 'VBox'); /* Probably safe to ignore rcNt here. */ } /* * Resolve methods we want but isn't available everywhere. */ UNICODE_STRING RoutineName; RtlInitUnicodeString(&RoutineName, L"KeQueryMaximumGroupCount"); g_pfnKeQueryMaximumGroupCount = (PFNKEQUERYMAXIMUMGROUPCOUNT)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"KeGetProcessorIndexFromNumber"); g_pfnKeGetProcessorIndexFromNumber = (PFNKEGETPROCESSORINDEXFROMNUMBER)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"KeGetProcessorNumberFromIndex"); g_pfnKeGetProcessorNumberFromIndex = (PFNKEGETPROCESSORNUMBERFROMINDEX)MmGetSystemRoutineAddress(&RoutineName); Assert( (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeGetProcessorIndexFromNumber != NULL) && (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeQueryMaximumGroupCount != NULL)); /* all or nothing. */ /* * Initialize the runtime (IPRT). */ int vrc = RTR0Init(0); if (RT_SUCCESS(vrc)) { Log(("VBoxDrv::DriverEntry\n")); #ifdef VBOX_WITH_HARDENING /* * Initialize process protection. */ rcNt = supdrvNtProtectInit(); if (NT_SUCCESS(rcNt)) #endif { #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING /* * Create the spinlock for the SID -> UID mappings. */ vrc = RTSpinlockCreate(&g_hNtUserIdLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtUserId"); if (RT_SUCCESS(vrc)) #endif { /* * Create device. * (That means creating a device object and a symbolic link so the DOS * subsystems (OS/2, win32, ++) can access the device.) */ rcNt = vboxdrvNtCreateDevices(pDrvObj); if (NT_SUCCESS(rcNt)) { /* * Initialize the device extension. */ PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; memset(pDevExt, 0, sizeof(*pDevExt)); vrc = supdrvInitDevExt(pDevExt, sizeof(SUPDRVSESSION)); if (!vrc) { /* * Setup the driver entry points in pDrvObj. */ pDrvObj->DriverUnload = VBoxDrvNtUnload; pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxDrvNtCreate; pDrvObj->MajorFunction[IRP_MJ_CLEANUP] = VBoxDrvNtCleanup; pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxDrvNtClose; pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxDrvNtDeviceControl; pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxDrvNtInternalDeviceControl; pDrvObj->MajorFunction[IRP_MJ_READ] = VBoxDrvNtRead; pDrvObj->MajorFunction[IRP_MJ_WRITE] = VBoxDrvNtNotSupportedStub; #ifdef VBOXDRV_WITH_FAST_IO /* Fast I/O to speed up guest execution roundtrips. */ pDrvObj->FastIoDispatch = (PFAST_IO_DISPATCH)&g_VBoxDrvFastIoDispatch; #endif /* * Register ourselves for power state changes. We don't * currently care if this fails. */ UNICODE_STRING CallbackName; RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); OBJECT_ATTRIBUTES Attr; InitializeObjectAttributes(&Attr, &CallbackName, OBJ_CASE_INSENSITIVE, NULL, NULL); rcNt = ExCreateCallback(&pDevExt->pObjPowerCallback, &Attr, TRUE, TRUE); if (rcNt == STATUS_SUCCESS) pDevExt->hPowerCallback = ExRegisterCallback(pDevExt->pObjPowerCallback, VBoxPowerDispatchCallback, g_pDevObjSys); /* * Done! Returning success! */ Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); return STATUS_SUCCESS; } /* * Failed. Clean up. */ Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); rcNt = VBoxDrvNtErr2NtStatus(vrc); vboxdrvNtDestroyDevices(); } #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING RTSpinlockDestroy(g_hNtUserIdLock); g_hNtUserIdLock = NIL_RTSPINLOCK; #endif } #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING else rcNt = VBoxDrvNtErr2NtStatus(vrc); #endif #ifdef VBOX_WITH_HARDENING supdrvNtProtectTerm(); #endif } RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); RTR0Term(); } else { Log(("RTR0Init failed with vrc=%d!\n", vrc)); rcNt = VBoxDrvNtErr2NtStatus(vrc); } if (NT_SUCCESS(rcNt)) rcNt = STATUS_INVALID_PARAMETER; return rcNt; } /** * Unload the driver. * * @param pDrvObj Driver object. */ void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) { PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; Log(("VBoxDrvNtUnload at irql %d\n", KeGetCurrentIrql())); /* Clean up the power callback registration. */ if (pDevExt->hPowerCallback) ExUnregisterCallback(pDevExt->hPowerCallback); if (pDevExt->pObjPowerCallback) ObDereferenceObject(pDevExt->pObjPowerCallback); /* * We ASSUME that it's not possible to unload a driver with open handles. */ supdrvDeleteDevExt(pDevExt); #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING RTSpinlockDestroy(g_hNtUserIdLock); g_hNtUserIdLock = NIL_RTSPINLOCK; #endif #ifdef VBOX_WITH_HARDENING supdrvNtProtectTerm(); #endif RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); RTR0Term(); vboxdrvNtDestroyDevices(); NOREF(pDrvObj); } #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING /** * Worker for supdrvNtUserIdMakeForSession. */ static bool supdrvNtUserIdMakeUid(PSUPDRVNTUSERID pNtUserId) { pNtUserId->UidCore.Key = pNtUserId->HashCore.Key; for (uint32_t cTries = 0; cTries < _4K; cTries++) { bool fRc = RTAvlU32Insert(&g_NtUserIdUidTree, &pNtUserId->UidCore); if (fRc) return true; pNtUserId->UidCore.Key += pNtUserId->cchSid | 1; } return false; } /** * Try create a RTUID value for the session. * * @returns VBox status code. * @param pSession The session to try set SUPDRVSESSION::Uid for. */ static int supdrvNtUserIdMakeForSession(PSUPDRVSESSION pSession) { /* * Get the current security context and query the User SID for it. */ SECURITY_SUBJECT_CONTEXT Ctx = { NULL, SecurityIdentification, NULL, NULL }; SeCaptureSubjectContext(&Ctx); int rc; TOKEN_USER *pTokenUser = NULL; NTSTATUS rcNt = SeQueryInformationToken(SeQuerySubjectContextToken(&Ctx) /* or always PrimaryToken?*/, TokenUser, (PVOID *)&pTokenUser); if (NT_SUCCESS(rcNt)) { /* * Convert the user SID to a string to make it easier to handle, then prepare * a user ID entry for it as that way we can combine lookup and insertion and * avoid needing to deal with races. */ UNICODE_STRING UniStr = RTNT_NULL_UNISTR(); rcNt = RtlConvertSidToUnicodeString(&UniStr, pTokenUser->User.Sid, TRUE /*AllocateDesitnationString*/); if (NT_SUCCESS(rcNt)) { size_t cchSid = 0; rc = RTUtf16CalcUtf8LenEx(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &cchSid); if (RT_SUCCESS(rc)) { PSUPDRVNTUSERID const pNtUserIdNew = (PSUPDRVNTUSERID)RTMemAlloc(RT_UOFFSETOF_DYN(SUPDRVNTUSERID, szSid[cchSid + 1])); if (pNtUserIdNew) { char *pszSid = pNtUserIdNew->szSid; rc = RTUtf16ToUtf8Ex(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &pszSid, cchSid + 1, NULL); if (RT_SUCCESS(rc)) { pNtUserIdNew->HashCore.Key = RTStrHash1(pNtUserIdNew->szSid); pNtUserIdNew->cchSid = (uint16_t)cchSid; pNtUserIdNew->cRefs = 1; Log5Func(("pNtUserId=%p cchSid=%u hash=%#x '%s'\n", pNtUserIdNew, cchSid, pNtUserIdNew->HashCore.Key, pszSid)); /* * Do the lookup / insert. */ RTSpinlockAcquire(g_hNtUserIdLock); AssertCompileMemberOffset(SUPDRVNTUSERID, HashCore, 0); PSUPDRVNTUSERID pNtUserId = (PSUPDRVNTUSERID)RTAvllU32Get(&g_NtUserIdHashTree, pNtUserIdNew->HashCore.Key); if (pNtUserId) { /* Match the strings till we reach the end of the collision list. */ PSUPDRVNTUSERID const pNtUserIdHead = pNtUserId; while ( pNtUserId && ( pNtUserId->cchSid != cchSid || memcmp(pNtUserId->szSid, pNtUserId->szSid, cchSid) != 0)) pNtUserId = (PSUPDRVNTUSERID)pNtUserId->HashCore.pList; if (pNtUserId) { /* Found matching: Retain reference and free the new entry we prepared. */ uint32_t const cRefs = ASMAtomicIncU32(&pNtUserId->cRefs); Assert(cRefs < _16K); RT_NOREF(cRefs); RTSpinlockRelease(g_hNtUserIdLock); Log5Func(("Using %p / %#x instead\n", pNtUserId, pNtUserId->UidCore.Key)); } else { /* No match: Try insert prepared entry after the head node. */ if (supdrvNtUserIdMakeUid(pNtUserIdNew)) { pNtUserIdNew->HashCore.pList = pNtUserIdHead->HashCore.pList; pNtUserIdHead->HashCore.pList = &pNtUserIdNew->HashCore; pNtUserId = pNtUserIdNew; } RTSpinlockRelease(g_hNtUserIdLock); if (pNtUserId) Log5Func(("Using %p / %#x (the prepared one)\n", pNtUserId, pNtUserId->UidCore.Key)); else LogRelFunc(("supdrvNtUserIdMakeForSession: failed to insert new\n")); } } else { /* No matching hash: Try insert the prepared entry. */ pNtUserIdNew->UidCore.Key = pNtUserIdNew->HashCore.Key; if (supdrvNtUserIdMakeUid(pNtUserIdNew)) { RTAvllU32Insert(&g_NtUserIdHashTree, &pNtUserIdNew->HashCore); pNtUserId = pNtUserIdNew; } RTSpinlockRelease(g_hNtUserIdLock); if (pNtUserId) Log5Func(("Using %p / %#x (the prepared one, no conflict)\n", pNtUserId, pNtUserId->UidCore.Key)); else LogRelFunc(("failed to insert!! WTF!?!\n")); } if (pNtUserId != pNtUserIdNew) RTMemFree(pNtUserIdNew); /* * Update the session info. */ pSession->pNtUserId = pNtUserId; pSession->Uid = pNtUserId ? (RTUID)pNtUserId->UidCore.Key : NIL_RTUID; } else RTMemFree(pNtUserIdNew); } else rc = VERR_NO_MEMORY; } RtlFreeUnicodeString(&UniStr); } else { rc = RTErrConvertFromNtStatus(rcNt); LogFunc(("RtlConvertSidToUnicodeString failed: %#x / %Rrc\n", rcNt, rc)); } ExFreePool(pTokenUser); } else { rc = RTErrConvertFromNtStatus(rcNt); LogFunc(("SeQueryInformationToken failed: %#x / %Rrc\n", rcNt, rc)); } SeReleaseSubjectContext(&Ctx); return rc; } /** * Releases a reference to @a pNtUserId. * * @param pNtUserId The NT user ID entry to release. */ static void supdrvNtUserIdRelease(PSUPDRVNTUSERID pNtUserId) { if (pNtUserId) { uint32_t const cRefs = ASMAtomicDecU32(&pNtUserId->cRefs); Log5Func(("%p / %#x: cRefs=%d\n", pNtUserId, pNtUserId->cRefs)); Assert(cRefs < _8K); if (cRefs == 0) { RTSpinlockAcquire(g_hNtUserIdLock); if (pNtUserId->cRefs == 0) { PAVLLU32NODECORE pAssert1 = RTAvllU32RemoveNode(&g_NtUserIdHashTree, &pNtUserId->HashCore); PAVLU32NODECORE pAssert2 = RTAvlU32Remove(&g_NtUserIdUidTree, pNtUserId->UidCore.Key); RTSpinlockRelease(g_hNtUserIdLock); Assert(pAssert1 == &pNtUserId->HashCore); Assert(pAssert2 == &pNtUserId->UidCore); RT_NOREF(pAssert1, pAssert2); RTMemFree(pNtUserId); } else RTSpinlockRelease(g_hNtUserIdLock); } } } #endif /* VBOXDRV_WITH_SID_TO_UID_MAPPING */ /** * For simplifying request completion into a simple return statement, extended * version. * * @returns rcNt * @param rcNt The status code. * @param uInfo Extra info value. * @param pIrp The IRP. */ DECLINLINE(NTSTATUS) supdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp) { pIrp->IoStatus.Status = rcNt; pIrp->IoStatus.Information = uInfo; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return rcNt; } /** * For simplifying request completion into a simple return statement. * * @returns rcNt * @param rcNt The status code. * @param pIrp The IRP. */ DECLINLINE(NTSTATUS) supdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp) { return supdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp); } /** * Create (i.e. Open) file entry point. * * @param pDevObj Device object. * @param pIrp Request packet. */ NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) { Log(("VBoxDrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack->FileObject; PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); /* * We are not remotely similar to a directory... * (But this is possible.) */ if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) return supdrvNtCompleteRequest(STATUS_NOT_A_DIRECTORY, pIrp); /* * Don't create a session for kernel clients, they'll close the handle * immediately and work with the file object via * VBoxDrvNtInternalDeviceControl. The first request will be one to * create a session. */ NTSTATUS rcNt; if (pIrp->RequestorMode == KernelMode) { if (pDevObj == g_pDevObjSys) return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); rcNt = STATUS_ACCESS_DENIED; } #ifdef VBOX_WITH_HARDENING /* * Anyone can open the error device. */ else if (pDevObj == g_pDevObjErrorInfo) { pFileObj->FsContext = NULL; return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); } #endif else { #if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) /* * Make sure no debuggers are attached to non-user processes. */ if ( pDevObj != g_pDevObjUsr && supdrvNtIsDebuggerAttached()) { LogRel(("vboxdrv: Process %p is being debugged, access to vboxdrv / vboxdrvu declined.\n", PsGetProcessId(PsGetCurrentProcess()))); rcNt = STATUS_TRUST_FAILURE; } else #endif { int rc = VINF_SUCCESS; #ifdef VBOX_WITH_HARDENING /* * Access to the stub device is only granted to processes which * passes verification. * * Note! The stub device has no need for a SUPDRVSESSION structure, * so the it uses the SUPDRVNTPROTECT directly instead. */ if (pDevObj == g_pDevObjStub) { PSUPDRVNTPROTECT pNtProtect = NULL; rc = supdrvNtProtectCreate(&pNtProtect, PsGetProcessId(PsGetCurrentProcess()), kSupDrvNtProtectKind_StubUnverified, true /*fLink*/); if (RT_SUCCESS(rc)) { rc = supdrvNtProtectFindAssociatedCsrss(pNtProtect); if (RT_SUCCESS(rc)) rc = supdrvNtProtectVerifyProcess(pNtProtect); if (RT_SUCCESS(rc)) { pFileObj->FsContext = pNtProtect; /* Keeps reference. */ return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); } supdrvNtProtectRelease(pNtProtect); } LogRel(("vboxdrv: Declined %p access to VBoxDrvStub: rc=%d\n", PsGetProcessId(PsGetCurrentProcess()), rc)); } /* * Unrestricted access is only granted to a process in the * VmProcessUnconfirmed state that checks out correctly and is * allowed to transition to VmProcessConfirmed. Again, only one * session per process. */ else if (pDevObj != g_pDevObjUsr) { PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(PsGetCurrentProcess())); if (pNtProtect) { if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) { rc = supdrvNtProtectVerifyProcess(pNtProtect); if (RT_SUCCESS(rc)) { /* Create a session. */ PSUPDRVSESSION pSession; rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, &pSession); if (RT_SUCCESS(rc)) { #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING rc = supdrvNtUserIdMakeForSession(pSession); if (RT_SUCCESS(rc)) #endif rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); supdrvSessionRelease(pSession); if (RT_SUCCESS(rc)) { pSession->pNtProtect = pNtProtect; /* Keeps reference. */ return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); } } /* No second attempt. */ RTSpinlockAcquire(g_hNtProtectLock); if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; RTSpinlockRelease(g_hNtProtectLock); LogRel(("vboxdrv: supdrvCreateSession failed for process %p: rc=%d.\n", PsGetProcessId(PsGetCurrentProcess()), rc)); } else LogRel(("vboxdrv: Process %p failed process verification: rc=%d.\n", PsGetProcessId(PsGetCurrentProcess()), rc)); } else { LogRel(("vboxdrv: %p is not a budding VM process (enmProcessKind=%d).\n", PsGetProcessId(PsGetCurrentProcess()), pNtProtect->enmProcessKind)); rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_2; } supdrvNtProtectRelease(pNtProtect); } else { LogRel(("vboxdrv: %p is not a budding VM process.\n", PsGetProcessId(PsGetCurrentProcess()))); rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_1; } } /* * Call common code to create an unprivileged session. */ else { PSUPDRVSESSION pSession; rc = supdrvCreateSession(pDevExt, true /*fUser*/, false /*fUnrestricted*/, &pSession); if (RT_SUCCESS(rc)) { #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING rc = supdrvNtUserIdMakeForSession(pSession); if (RT_SUCCESS(rc)) #endif rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); supdrvSessionRelease(pSession); if (RT_SUCCESS(rc)) { pFileObj->FsContext = pSession; /* Keeps reference. No race. */ pSession->pNtProtect = NULL; return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); } } } #else /* !VBOX_WITH_HARDENING */ /* * Call common code to create a session. */ pFileObj->FsContext = NULL; PSUPDRVSESSION pSession; rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, &pSession); if (RT_SUCCESS(rc)) { # ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING rc = supdrvNtUserIdMakeForSession(pSession); if (RT_SUCCESS(rc)) # endif rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); supdrvSessionRelease(pSession); if (RT_SUCCESS(rc)) return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); } #endif /* !VBOX_WITH_HARDENING */ /* bail out */ rcNt = VBoxDrvNtErr2NtStatus(rc); } } Assert(!NT_SUCCESS(rcNt)); pFileObj->FsContext = NULL; return supdrvNtCompleteRequest(rcNt, pIrp); /* Note. the IoStatus is completely ignored on error. */ } /** * Clean up file handle entry point. * * This is called when the last handle reference is released, or something like * that. In the case of IoGetDeviceObjectPointer, this is called as it closes * the handle, however it will go on using the file object afterwards... * * @param pDevObj Device object. * @param pIrp Request packet. */ NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp) { PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack->FileObject; #ifdef VBOX_WITH_HARDENING if (pDevObj == g_pDevObjStub) { PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); if (pNtProtect) { supdrvNtProtectRelease(pNtProtect); pFileObj->FsContext = NULL; } } else if (pDevObj == g_pDevObjErrorInfo) supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); else #endif { PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), (PSUPDRVSESSION *)&pFileObj->FsContext); Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); if (pSession) { supdrvSessionHashTabRemove(pDevExt, pSession, NULL); supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ } } return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); } /** * Close file entry point. * * @param pDevObj Device object. * @param pIrp Request packet. */ NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) { PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack->FileObject; #ifdef VBOX_WITH_HARDENING if (pDevObj == g_pDevObjStub) { PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; Log(("VBoxDrvNtClose: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); if (pNtProtect) { supdrvNtProtectRelease(pNtProtect); pFileObj->FsContext = NULL; } } else if (pDevObj == g_pDevObjErrorInfo) supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); else #endif { PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), (PSUPDRVSESSION *)&pFileObj->FsContext); Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); if (pSession) { supdrvSessionHashTabRemove(pDevExt, pSession, NULL); supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ } } return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); } #ifdef VBOXDRV_WITH_FAST_IO /** * Fast I/O device control callback. * * This performs no buffering, neither on the way in or out. * * @returns TRUE if handled, FALSE if the normal I/O control routine should be * called. * @param pFileObj The file object. * @param fWait Whether it's a blocking call * @param pvInput The input buffer as specified by the user. * @param cbInput The size of the input buffer. * @param pvOutput The output buffer as specfied by the user. * @param cbOutput The size of the output buffer. * @param uCmd The I/O command/function being invoked. * @param pIoStatus Where to return the status of the operation. * @param pDevObj The device object.. */ static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, PVOID pvOutput, ULONG cbOutput, ULONG uCmd, PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj) { RT_NOREF1(fWait); /* * Only the normal devices, not the stub or error info ones. */ if (pDevObj != g_pDevObjSys && pDevObj != g_pDevObjUsr) { pIoStatus->Status = STATUS_NOT_SUPPORTED; pIoStatus->Information = 0; return TRUE; } /* * Check the input a little bit and get a the session references. */ PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), (PSUPDRVSESSION *)&pFileObj->FsContext); if (!pSession) { pIoStatus->Status = STATUS_TRUST_FAILURE; pIoStatus->Information = 0; return TRUE; } if (pSession->fUnrestricted) { #if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) if (supdrvNtIsDebuggerAttached()) { pIoStatus->Status = STATUS_TRUST_FAILURE; pIoStatus->Information = 0; supdrvSessionRelease(pSession); return TRUE; } #endif /* * Deal with the 2-3 high-speed IOCtl that takes their arguments from * the session and iCmd, and does not return anything. */ if ( (uCmd & 3) == METHOD_NEITHER && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) { int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, (unsigned)(uintptr_t)pvOutput/* VMCPU id */, pDevExt, pSession); pIoStatus->Status = RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; pIoStatus->Information = 0; /* Could be used to pass rc if we liked. */ supdrvSessionRelease(pSession); return TRUE; } } /* * The normal path. */ NTSTATUS rcNt; unsigned cbOut = 0; int rc = 0; Log2(("VBoxDrvNtFastIoDeviceControl(%p): ioctl=%#x pvIn=%p cbIn=%#x pvOut=%p cbOut=%#x pSession=%p\n", pDevExt, uCmd, pvInput, cbInput, pvOutput, cbOutput, pSession)); # ifdef RT_ARCH_AMD64 /* Don't allow 32-bit processes to do any I/O controls. */ if (!IoIs32bitProcess(NULL)) # endif { /* * In this fast I/O device control path we have to do our own buffering. */ /* Verify that the I/O control function matches our pattern. */ if ((uCmd & 0x3) == METHOD_BUFFERED) { /* Get the header so we can validate it a little bit against the parameters before allocating any memory kernel for the reqest. */ SUPREQHDR Hdr; if (cbInput >= sizeof(Hdr) && cbOutput >= sizeof(Hdr)) { __try { RtlCopyMemory(&Hdr, pvInput, sizeof(Hdr)); rcNt = STATUS_SUCCESS; } __except(EXCEPTION_EXECUTE_HANDLER) { rcNt = GetExceptionCode(); Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ } } else { Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ rcNt = STATUS_INVALID_PARAMETER; } if (NT_SUCCESS(rcNt)) { /* Verify that the sizes in the request header are correct. */ ULONG cbBuf = RT_MAX(cbInput, cbOutput); if ( cbInput == Hdr.cbIn && cbOutput == Hdr.cbOut && cbBuf < _1M*16) { /* Allocate a buffer and copy all the input into it. */ PSUPREQHDR pHdr = (PSUPREQHDR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, cbBuf, 'VBox'); if (pHdr) { __try { RtlCopyMemory(pHdr, pvInput, cbInput); if (cbInput < cbBuf) RtlZeroMemory((uint8_t *)pHdr + cbInput, cbBuf - cbInput); if (!memcmp(pHdr, &Hdr, sizeof(Hdr))) rcNt = STATUS_SUCCESS; else rcNt = STATUS_INVALID_PARAMETER; } __except(EXCEPTION_EXECUTE_HANDLER) { rcNt = GetExceptionCode(); } if (NT_SUCCESS(rcNt)) { /* * Now call the common code to do the real work. */ rc = supdrvIOCtl(uCmd, pDevExt, pSession, pHdr, cbBuf); if (RT_SUCCESS(rc)) { /* * Copy back the result. */ cbOut = pHdr->cbOut; if (cbOut > cbOutput) { cbOut = cbOutput; OSDBGPRINT(("VBoxDrvNtFastIoDeviceControl: too much output! %#x > %#x; uCmd=%#x!\n", pHdr->cbOut, cbOut, uCmd)); } if (cbOut) { __try { RtlCopyMemory(pvOutput, pHdr, cbOut); rcNt = STATUS_SUCCESS; } __except(EXCEPTION_EXECUTE_HANDLER) { rcNt = GetExceptionCode(); } } else rcNt = STATUS_SUCCESS; } else if (rc == VERR_INVALID_PARAMETER) rcNt = STATUS_INVALID_PARAMETER; else rcNt = STATUS_NOT_SUPPORTED; Log2(("VBoxDrvNtFastIoDeviceControl: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); } else Log(("VBoxDrvNtFastIoDeviceControl: Error reading %u bytes of user memory at %p (uCmd=%#x)\n", cbInput, pvInput, uCmd)); ExFreePoolWithTag(pHdr, 'VBox'); } else rcNt = STATUS_NO_MEMORY; } else { Log(("VBoxDrvNtFastIoDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", uCmd, Hdr.cbIn, Hdr.cbOut, cbInput, cbOutput)); rcNt = STATUS_INVALID_PARAMETER; } } } else { Log(("VBoxDrvNtFastIoDeviceControl: not buffered request (%#x) - not supported\n", uCmd)); rcNt = STATUS_NOT_SUPPORTED; } } # ifdef RT_ARCH_AMD64 else { Log(("VBoxDrvNtFastIoDeviceControl: WOW64 req - not supported\n")); rcNt = STATUS_NOT_SUPPORTED; } # endif /* complete the request. */ pIoStatus->Status = rcNt; pIoStatus->Information = cbOut; supdrvSessionRelease(pSession); return TRUE; /* handled. */ } #endif /* VBOXDRV_WITH_FAST_IO */ /** * Device I/O Control entry point. * * @param pDevObj Device object. * @param pIrp Request packet. */ NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) { VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), (PSUPDRVSESSION *)&pStack->FileObject->FsContext); if (!RT_VALID_PTR(pSession)) return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); /* * Deal with the 2-3 high-speed IOCtl that takes their arguments from * the session and iCmd, and does not return anything. */ if (pSession->fUnrestricted) { #if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) if (supdrvNtIsDebuggerAttached()) { supdrvSessionRelease(pSession); return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); } #endif ULONG uCmd = pStack->Parameters.DeviceIoControl.IoControlCode; if ( (uCmd & 3) == METHOD_NEITHER && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) { int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, pDevExt, pSession); /* Complete the I/O request. */ supdrvSessionRelease(pSession); return supdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); } } return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack); } /** * Worker for VBoxDrvNtDeviceControl that takes the slow IOCtl functions. * * @returns NT status code. * * @param pDevExt Device extension. * @param pSession The session. * @param pIrp Request packet. * @param pStack The stack location containing the DeviceControl parameters. */ static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack) { NTSTATUS rcNt; uint32_t cbOut = 0; int rc = 0; Log2(("VBoxDrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); #ifdef RT_ARCH_AMD64 /* Don't allow 32-bit processes to do any I/O controls. */ if (!IoIs32bitProcess(pIrp)) #endif { /* Verify that it's a buffered CTL. */ if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) { /* Verify that the sizes in the request header are correct. */ PSUPREQHDR pHdr = (PSUPREQHDR)pIrp->AssociatedIrp.SystemBuffer; if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut) { /* Zero extra output bytes to make sure we don't leak anything. */ if (pHdr->cbIn < pHdr->cbOut) RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn); /* * Do the job. */ rc = supdrvIOCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr, RT_MAX(pHdr->cbIn, pHdr->cbOut)); if (!rc) { rcNt = STATUS_SUCCESS; cbOut = pHdr->cbOut; if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength) { cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength; OSDBGPRINT(("VBoxDrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n", pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode)); } } else rcNt = STATUS_INVALID_PARAMETER; Log2(("VBoxDrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); } else { Log(("VBoxDrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", pStack->Parameters.DeviceIoControl.IoControlCode, pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0, pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0, pStack->Parameters.DeviceIoControl.InputBufferLength, pStack->Parameters.DeviceIoControl.OutputBufferLength)); rcNt = STATUS_INVALID_PARAMETER; } } else { Log(("VBoxDrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n", pStack->Parameters.DeviceIoControl.IoControlCode)); rcNt = STATUS_NOT_SUPPORTED; } } #ifdef RT_ARCH_AMD64 else { Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n")); rcNt = STATUS_NOT_SUPPORTED; } #endif /* complete the request. */ pIrp->IoStatus.Status = rcNt; pIrp->IoStatus.Information = cbOut; supdrvSessionRelease(pSession); IoCompleteRequest(pIrp, IO_NO_INCREMENT); return rcNt; } /** * Internal Device I/O Control entry point, used for IDC. * * @param pDevObj Device object. * @param pIrp Request packet. */ NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) { VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack ? pStack->FileObject : NULL; PSUPDRVSESSION pSession = pFileObj ? (PSUPDRVSESSION)pFileObj->FsContext : NULL; NTSTATUS rcNt; unsigned cbOut = 0; int rc = 0; Log2(("VBoxDrvNtInternalDeviceControl(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); /* Verify that it's a buffered CTL. */ if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) { /* Verify the pDevExt in the session. */ if ( pStack->Parameters.DeviceIoControl.IoControlCode != SUPDRV_IDC_REQ_CONNECT ? RT_VALID_PTR(pSession) && pSession->pDevExt == pDevExt : !pSession ) { /* Verify that the size in the request header is correct. */ PSUPDRVIDCREQHDR pHdr = (PSUPDRVIDCREQHDR)pIrp->AssociatedIrp.SystemBuffer; if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cb && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cb) { /* * Call the generic code. * * Note! Connect and disconnect requires some extra attention * in order to get the session handling right. */ if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) pFileObj->FsContext = NULL; rc = supdrvIDC(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr); if (!rc) { if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_CONNECT) pFileObj->FsContext = ((PSUPDRVIDCREQCONNECT)pHdr)->u.Out.pSession; rcNt = STATUS_SUCCESS; cbOut = pHdr->cb; } else { rcNt = STATUS_INVALID_PARAMETER; if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) pFileObj->FsContext = pSession; } Log2(("VBoxDrvNtInternalDeviceControl: returns %#x/rc=%#x\n", rcNt, rc)); } else { Log(("VBoxDrvNtInternalDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx Irp=%#lx/%#lx!\n", pStack->Parameters.DeviceIoControl.IoControlCode, pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cb : 0, pStack->Parameters.DeviceIoControl.InputBufferLength, pStack->Parameters.DeviceIoControl.OutputBufferLength)); rcNt = STATUS_INVALID_PARAMETER; } } else rcNt = STATUS_NOT_SUPPORTED; } else { Log(("VBoxDrvNtInternalDeviceControl: not buffered request (%#x) - not supported\n", pStack->Parameters.DeviceIoControl.IoControlCode)); rcNt = STATUS_NOT_SUPPORTED; } /* complete the request. */ pIrp->IoStatus.Status = rcNt; pIrp->IoStatus.Information = cbOut; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return rcNt; } /** * Implementation of the read major function for VBoxDrvErrorInfo. * * This is a stub function for the other devices. * * @returns NT status code. * @param pDevObj The device object. * @param pIrp The I/O request packet. */ NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) { Log(("VBoxDrvNtRead\n")); RT_NOREF1(pDevObj); NTSTATUS rcNt; pIrp->IoStatus.Information = 0; #ifdef VBOX_WITH_HARDENING /* * VBoxDrvErrorInfo? */ if (pDevObj == g_pDevObjErrorInfo) { PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); if ( pStack && (pIrp->Flags & IRP_BUFFERED_IO)) { /* * Look up the process error information. */ HANDLE hCurThreadId = PsGetCurrentThreadId(); HANDLE hCurProcessId = PsGetCurrentProcessId(); int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); if (RT_SUCCESS(rc)) { PSUPDRVNTERRORINFO pMatch = NULL; PSUPDRVNTERRORINFO pCur; RTListForEach(&g_ErrorInfoHead, pCur, SUPDRVNTERRORINFO, ListEntry) { if ( pCur->hProcessId == hCurProcessId && pCur->hThreadId == hCurThreadId) { pMatch = pCur; break; } } /* * Did we find error info and is the caller requesting data within it? * If so, check the destination buffer and copy the data into it. */ if ( pMatch && pStack->Parameters.Read.ByteOffset.QuadPart < pMatch->cchErrorInfo && pStack->Parameters.Read.ByteOffset.QuadPart >= 0) { PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; if (pvDstBuf) { uint32_t offRead = (uint32_t)pStack->Parameters.Read.ByteOffset.QuadPart; uint32_t cbToRead = pMatch->cchErrorInfo - offRead; if (cbToRead < pStack->Parameters.Read.Length) RT_BZERO((uint8_t *)pvDstBuf + cbToRead, pStack->Parameters.Read.Length - cbToRead); else cbToRead = pStack->Parameters.Read.Length; memcpy(pvDstBuf, &pMatch->szErrorInfo[offRead], cbToRead); pIrp->IoStatus.Information = cbToRead; rcNt = STATUS_SUCCESS; } else rcNt = STATUS_INVALID_ADDRESS; } /* * End of file. Free the info. */ else if (pMatch) { RTListNodeRemove(&pMatch->ListEntry); RTMemFree(pMatch); rcNt = STATUS_END_OF_FILE; } /* * We found no error info. Return EOF. */ else rcNt = STATUS_END_OF_FILE; RTSemMutexRelease(g_hErrorInfoLock); } else rcNt = STATUS_UNSUCCESSFUL; /* Paranoia: Clear the buffer on failure. */ if (!NT_SUCCESS(rcNt)) { PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; if ( pvDstBuf && pStack->Parameters.Read.Length) RT_BZERO(pvDstBuf, pStack->Parameters.Read.Length); } } else rcNt = STATUS_INVALID_PARAMETER; } else #endif /* VBOX_WITH_HARDENING */ { /* * Stub. */ rcNt = STATUS_NOT_SUPPORTED; } /* * Complete the request. */ pIrp->IoStatus.Status = rcNt; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return rcNt; } /** * Stub function for functions we don't implemented. * * @returns STATUS_NOT_SUPPORTED * @param pDevObj Device object. * @param pIrp IRP. */ NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp) { Log(("VBoxDrvNtNotSupportedStub\n")); NOREF(pDevObj); pIrp->IoStatus.Information = 0; pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_NOT_SUPPORTED; } /** * ExRegisterCallback handler for power events * * @param pCallbackContext User supplied parameter (pDevObj) * @param pvArgument1 First argument * @param pvArgument2 Second argument */ VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pvArgument1, PVOID pvArgument2) { /*PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pCallbackContext;*/ RT_NOREF1(pCallbackContext); Log(("VBoxPowerDispatchCallback: %x %x\n", pvArgument1, pvArgument2)); /* Power change imminent? */ if ((uintptr_t)pvArgument1 == PO_CB_SYSTEM_STATE_LOCK) { if (pvArgument2 == NULL) Log(("VBoxPowerDispatchCallback: about to go into suspend mode!\n")); else Log(("VBoxPowerDispatchCallback: resumed!\n")); /* Inform any clients that have registered themselves with IPRT. */ RTPowerSignalEvent(pvArgument2 == NULL ? RTPOWEREVENT_SUSPEND : RTPOWEREVENT_RESUME); } } /** * Called to clean up the session structure before it's freed. * * @param pDevExt The device globals. * @param pSession The session that's being cleaned up. */ void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) { #ifdef VBOX_WITH_HARDENING if (pSession->pNtProtect) { supdrvNtProtectRelease(pSession->pNtProtect); pSession->pNtProtect = NULL; } RT_NOREF1(pDevExt); #else RT_NOREF2(pDevExt, pSession); #endif #ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING if (pSession->pNtUserId) { supdrvNtUserIdRelease(pSession->pNtUserId); pSession->pNtUserId = NULL; } #endif } void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) { NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); } void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) { NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); } size_t VBOXCALL supdrvOSGipGetGroupTableSize(PSUPDRVDEVEXT pDevExt) { NOREF(pDevExt); uint32_t cMaxCpus = RTMpGetCount(); uint32_t cGroups = RTMpGetMaxCpuGroupCount(); return cGroups * RT_UOFFSETOF(SUPGIPCPUGROUP, aiCpuSetIdxs) + RT_SIZEOFMEMB(SUPGIPCPUGROUP, aiCpuSetIdxs[0]) * cMaxCpus; } int VBOXCALL supdrvOSInitGipGroupTable(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, size_t cbGipCpuGroups) { Assert(cbGipCpuGroups > 0); NOREF(cbGipCpuGroups); NOREF(pDevExt); unsigned const cGroups = RTMpGetMaxCpuGroupCount(); AssertReturn(cGroups > 0 && cGroups < RT_ELEMENTS(pGip->aoffCpuGroup), VERR_INTERNAL_ERROR_2); pGip->cPossibleCpuGroups = cGroups; PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)&pGip->aCPUs[pGip->cCpus]; for (uint32_t idxGroup = 0; idxGroup < cGroups; idxGroup++) { uint32_t cActive = 0; uint32_t const cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); uint32_t const cbNeeded = RT_UOFFSETOF_DYN(SUPGIPCPUGROUP, aiCpuSetIdxs[cMax]); uintptr_t const offGroup = (uintptr_t)pGroup - (uintptr_t)pGip; AssertReturn(cbNeeded <= cbGipCpuGroups, VERR_INTERNAL_ERROR_3); AssertReturn(cActive <= cMax, VERR_INTERNAL_ERROR_4); AssertReturn(offGroup == (uint32_t)offGroup, VERR_INTERNAL_ERROR_5); pGip->aoffCpuGroup[idxGroup] = offGroup; pGroup->cMembers = cActive; pGroup->cMaxMembers = cMax; for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) { pGroup->aiCpuSetIdxs[idxMember] = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); Assert((unsigned)pGroup->aiCpuSetIdxs[idxMember] < pGip->cPossibleCpus); } /* advance. */ cbGipCpuGroups -= cbNeeded; pGroup = (PSUPGIPCPUGROUP)&pGroup->aiCpuSetIdxs[cMax]; } return VINF_SUCCESS; } void VBOXCALL supdrvOSGipInitGroupBitsForCpu(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pGipCpu) { NOREF(pDevExt); /* * Translate the CPU index into a group and member. */ PROCESSOR_NUMBER ProcNum = { 0, (UCHAR)pGipCpu->iCpuSet, 0 }; if (g_pfnKeGetProcessorNumberFromIndex) { NTSTATUS rcNt = g_pfnKeGetProcessorNumberFromIndex(pGipCpu->iCpuSet, &ProcNum); if (NT_SUCCESS(rcNt)) Assert(ProcNum.Group < g_pfnKeQueryMaximumGroupCount()); else { AssertFailed(); ProcNum.Group = 0; ProcNum.Number = pGipCpu->iCpuSet; } } pGipCpu->iCpuGroup = ProcNum.Group; pGipCpu->iCpuGroupMember = ProcNum.Number; /* * Update the group info. Just do this wholesale for now (doesn't scale well). */ for (uint32_t idxGroup = 0; idxGroup < pGip->cPossibleCpuGroups; idxGroup++) { uint32_t offGroup = pGip->aoffCpuGroup[idxGroup]; if (offGroup != UINT32_MAX) { PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offGroup); uint32_t cActive = 0; uint32_t cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); AssertStmt(cMax == pGroup->cMaxMembers, cMax = pGroup->cMaxMembers); AssertStmt(cActive <= cMax, cActive = cMax); if (pGroup->cMembers != cActive) ASMAtomicWriteU16(&pGroup->cMembers, cActive); for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) { int idxCpuSet = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); AssertMsg((unsigned)idxCpuSet < pGip->cPossibleCpus, ("%d vs %d for %u.%u\n", idxCpuSet, pGip->cPossibleCpus, idxGroup, idxMember)); if (pGroup->aiCpuSetIdxs[idxMember] != idxCpuSet) ASMAtomicWriteS16(&pGroup->aiCpuSetIdxs[idxMember], idxCpuSet); } } } } /** * Initializes any OS specific object creator fields. */ void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) { NOREF(pObj); NOREF(pSession); } /** * Checks if the session can access the object. * * @returns true if a decision has been made. * @returns false if the default access policy should be applied. * * @param pObj The object in question. * @param pSession The session wanting to access the object. * @param pszObjName The object name, can be NULL. * @param prc Where to store the result when returning true. */ bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) { NOREF(pObj); NOREF(pSession); NOREF(pszObjName); NOREF(prc); return false; } /** * Force async tsc mode (stub). */ bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) { RT_NOREF1(pDevExt); return g_Options.fOptForceAsyncTsc != 0; } /** * Whether the host takes CPUs offline during a suspend/resume operation. */ bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) { return false; } /** * Whether the hardware TSC has been synchronized by the OS. */ bool VBOXCALL supdrvOSAreTscDeltasInSync(void) { /* If IPRT didn't find KeIpiGenericCall we pretend windows(, the firmware, or whoever) always configures TSCs perfectly. */ return !RTMpOnPairIsConcurrentExecSupported(); } #define MY_SystemLoadGdiDriverInSystemSpaceInformation 54 #define MY_SystemUnloadGdiDriverInformation 27 typedef struct MYSYSTEMGDIDRIVERINFO { UNICODE_STRING Name; /**< In: image file name. */ PVOID ImageAddress; /**< Out: the load address. */ PVOID SectionPointer; /**< Out: section object. */ PVOID EntryPointer; /**< Out: entry point address. */ PVOID ExportSectionPointer; /**< Out: export directory/section. */ ULONG ImageLength; /**< Out: SizeOfImage. */ } MYSYSTEMGDIDRIVERINFO; extern "C" __declspec(dllimport) NTSTATUS NTAPI ZwSetSystemInformation(ULONG, PVOID, ULONG); int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) { pImage->pvNtSectionObj = NULL; pImage->hMemLock = NIL_RTR0MEMOBJ; #ifdef VBOX_WITHOUT_NATIVE_R0_LOADER # ifndef RT_ARCH_X86 # error "VBOX_WITHOUT_NATIVE_R0_LOADER is only safe on x86." # endif NOREF(pDevExt); NOREF(pszFilename); NOREF(pImage); return VERR_NOT_SUPPORTED; #else /* * Convert the filename from DOS UTF-8 to NT UTF-16. */ size_t cwcFilename; int rc = RTStrCalcUtf16LenEx(pszFilename, RTSTR_MAX, &cwcFilename); if (RT_FAILURE(rc)) return rc; PRTUTF16 pwcsFilename = (PRTUTF16)RTMemTmpAlloc((4 + cwcFilename + 1) * sizeof(RTUTF16)); if (!pwcsFilename) return VERR_NO_TMP_MEMORY; pwcsFilename[0] = '\\'; pwcsFilename[1] = '?'; pwcsFilename[2] = '?'; pwcsFilename[3] = '\\'; PRTUTF16 pwcsTmp = &pwcsFilename[4]; rc = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwcsTmp, cwcFilename + 1, NULL); if (RT_SUCCESS(rc)) { /* * Try load it. */ MYSYSTEMGDIDRIVERINFO Info; RtlInitUnicodeString(&Info.Name, pwcsFilename); Info.ImageAddress = NULL; Info.SectionPointer = NULL; Info.EntryPointer = NULL; Info.ExportSectionPointer = NULL; Info.ImageLength = 0; NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemLoadGdiDriverInSystemSpaceInformation, &Info, sizeof(Info)); if (NT_SUCCESS(rcNt)) { pImage->pvImage = Info.ImageAddress; pImage->pvNtSectionObj = Info.SectionPointer; Log(("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer)); # ifdef DEBUG_bird SUPR0Printf("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer); # endif if (pImage->cbImageBits == Info.ImageLength) { /* * Lock down the entire image, just to be on the safe side. */ rc = RTR0MemObjLockKernel(&pImage->hMemLock, pImage->pvImage, pImage->cbImageBits, RTMEM_PROT_READ); if (RT_FAILURE(rc)) { pImage->hMemLock = NIL_RTR0MEMOBJ; supdrvOSLdrUnload(pDevExt, pImage); } } else { supdrvOSLdrUnload(pDevExt, pImage); rc = VERR_LDR_MISMATCH_NATIVE; } } else { Log(("rcNt=%#x '%ls'\n", rcNt, pwcsFilename)); SUPR0Printf("VBoxDrv: rcNt=%x '%ws'\n", rcNt, pwcsFilename); switch (rcNt) { case /* 0xc0000003 */ STATUS_INVALID_INFO_CLASS: # ifdef RT_ARCH_AMD64 /* Unwind will crash and BSOD, so no fallback here! */ rc = VERR_NOT_IMPLEMENTED; # else /* * Use the old way of loading the modules. * * Note! We do *NOT* try class 26 because it will probably * not work correctly on terminal servers and such. */ rc = VERR_NOT_SUPPORTED; # endif break; case /* 0xc0000034 */ STATUS_OBJECT_NAME_NOT_FOUND: rc = VERR_MODULE_NOT_FOUND; break; case /* 0xC0000263 */ STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: rc = VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND; break; case /* 0xC0000428 */ STATUS_INVALID_IMAGE_HASH: rc = VERR_LDR_IMAGE_HASH; break; case /* 0xC000010E */ STATUS_IMAGE_ALREADY_LOADED: Log(("WARNING: see @bugref{4853} for cause of this failure on Windows 7 x64\n")); rc = VERR_ALREADY_LOADED; break; default: rc = VERR_LDR_GENERAL_FAILURE; break; } pImage->pvNtSectionObj = NULL; } } RTMemTmpFree(pwcsFilename); NOREF(pDevExt); return rc; #endif } void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) { NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); } void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) { NOREF(pDevExt); NOREF(pImage); } /** * Common worker for supdrvOSLdrQuerySymbol and supdrvOSLdrValidatePointer. * * @note Similar code in rtR0DbgKrnlNtParseModule. */ static int supdrvOSLdrValidatePointerOrQuerySymbol(PSUPDRVLDRIMAGE pImage, void *pv, const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) { AssertReturn(pImage->pvNtSectionObj, VERR_INVALID_STATE); Assert(pszSymbol || !ppvSymbol); /* * Locate the export directory in the loaded image. */ uint8_t const *pbMapping = (uint8_t const *)pImage->pvImage; uint32_t const cbMapping = pImage->cbImageBits; uint32_t const uRvaToValidate = (uint32_t)((uintptr_t)pv - (uintptr_t)pbMapping); AssertReturn(uRvaToValidate < cbMapping || ppvSymbol, VERR_INTERNAL_ERROR_3); uint32_t const offNtHdrs = *(uint16_t *)pbMapping == IMAGE_DOS_SIGNATURE ? ((IMAGE_DOS_HEADER const *)pbMapping)->e_lfanew : 0; AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < cbMapping, VERR_INTERNAL_ERROR_5); IMAGE_NT_HEADERS const *pNtHdrs = (IMAGE_NT_HEADERS const *)((uintptr_t)pbMapping + offNtHdrs); AssertLogRelReturn(pNtHdrs->Signature == IMAGE_NT_SIGNATURE, VERR_INVALID_EXE_SIGNATURE); AssertLogRelReturn(pNtHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, VERR_BAD_EXE_FORMAT); AssertLogRelReturn(pNtHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES, VERR_BAD_EXE_FORMAT); uint32_t const offEndSectHdrs = offNtHdrs + sizeof(*pNtHdrs) + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); AssertReturn(offEndSectHdrs < cbMapping, VERR_BAD_EXE_FORMAT); /* * Find the export directory. */ IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (!ExpDir.Size) { SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; } AssertReturn( ExpDir.Size >= sizeof(IMAGE_EXPORT_DIRECTORY) && ExpDir.VirtualAddress >= offEndSectHdrs && ExpDir.VirtualAddress < cbMapping && ExpDir.VirtualAddress + ExpDir.Size <= cbMapping, VERR_BAD_EXE_FORMAT); IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pbMapping[ExpDir.VirtualAddress]; uint32_t const cNamedExports = pExpDir->NumberOfNames; AssertReturn(cNamedExports < _1M, VERR_BAD_EXE_FORMAT); AssertReturn(pExpDir->NumberOfFunctions < _1M, VERR_BAD_EXE_FORMAT); if (pExpDir->NumberOfFunctions == 0 || cNamedExports == 0) { SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; } uint32_t const cExports = RT_MAX(cNamedExports, pExpDir->NumberOfFunctions); AssertReturn( pExpDir->AddressOfFunctions >= offEndSectHdrs && pExpDir->AddressOfFunctions < cbMapping && pExpDir->AddressOfFunctions + cExports * sizeof(uint32_t) <= cbMapping, VERR_BAD_EXE_FORMAT); uint32_t const * const paoffExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfFunctions]; AssertReturn( pExpDir->AddressOfNames >= offEndSectHdrs && pExpDir->AddressOfNames < cbMapping && pExpDir->AddressOfNames + cNamedExports * sizeof(uint32_t) <= cbMapping, VERR_BAD_EXE_FORMAT); uint32_t const * const paoffNamedExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfNames]; AssertReturn( pExpDir->AddressOfNameOrdinals >= offEndSectHdrs && pExpDir->AddressOfNameOrdinals < cbMapping && pExpDir->AddressOfNameOrdinals + cNamedExports * sizeof(uint32_t) <= cbMapping, VERR_BAD_EXE_FORMAT); uint16_t const * const pau16NameOrdinals = (uint16_t const *)&pbMapping[pExpDir->AddressOfNameOrdinals]; /* * Validate the entrypoint RVA by scanning the export table. */ uint32_t iExportOrdinal = UINT32_MAX; if (!ppvSymbol) { for (uint32_t i = 0; i < cExports; i++) if (paoffExports[i] == uRvaToValidate) { iExportOrdinal = i; break; } if (iExportOrdinal == UINT32_MAX) { SUPR0Printf("SUPDrv: No export with rva %#x (%s) in %s!\n", uRvaToValidate, pszSymbol, pImage->szName); return VERR_NOT_FOUND; } } /* * Can we validate the symbol name too or should we find a name? * If so, just do a linear search. */ if (pszSymbol && (RT_C_IS_UPPER(*pszSymbol) || ppvSymbol)) { for (uint32_t i = 0; i < cNamedExports; i++) { uint32_t const offName = paoffNamedExports[i]; AssertReturn(offName < cbMapping, VERR_BAD_EXE_FORMAT); uint32_t const cchMaxName = cbMapping - offName; const char * const pszName = (const char *)&pbMapping[offName]; const char * const pszEnd = (const char *)memchr(pszName, '\0', cchMaxName); AssertReturn(pszEnd, VERR_BAD_EXE_FORMAT); if ( cchSymbol == (size_t)(pszEnd - pszName) && memcmp(pszName, pszSymbol, cchSymbol) == 0) { if (ppvSymbol) { iExportOrdinal = pau16NameOrdinals[i]; if ( iExportOrdinal < cExports && paoffExports[iExportOrdinal] < cbMapping) { *ppvSymbol = (void *)(paoffExports[iExportOrdinal] + pbMapping); return VINF_SUCCESS; } } else if (pau16NameOrdinals[i] == iExportOrdinal) return VINF_SUCCESS; else SUPR0Printf("SUPDrv: Different exports found for %s and rva %#x in %s: %#x vs %#x\n", pszSymbol, uRvaToValidate, pImage->szName, pau16NameOrdinals[i], iExportOrdinal); return VERR_LDR_BAD_FIXUP; } } if (!ppvSymbol) SUPR0Printf("SUPDrv: No export named %s (%#x) in %s!\n", pszSymbol, uRvaToValidate, pImage->szName); return VERR_SYMBOL_NOT_FOUND; } return VINF_SUCCESS; } int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, const uint8_t *pbImageBits, const char *pszSymbol) { RT_NOREF(pDevExt, pbImageBits); return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, pv, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0, NULL); } int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) { RT_NOREF(pDevExt); AssertReturn(ppvSymbol, VERR_INVALID_PARAMETER); AssertReturn(pszSymbol, VERR_INVALID_PARAMETER); return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, NULL, pszSymbol, cchSymbol, ppvSymbol); } /** * memcmp + errormsg + log. * * @returns Same as memcmp. * @param pImage The image. * @param pbImageBits The image bits ring-3 uploads. * @param uRva The RVA to start comparing at. * @param cb The number of bytes to compare. * @param pReq The load request. */ static int supdrvNtCompare(PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, uint32_t uRva, uint32_t cb, PSUPLDRLOAD pReq) { int iDiff = memcmp((uint8_t const *)pImage->pvImage + uRva, pbImageBits + uRva, cb); if (iDiff) { uint32_t cbLeft = cb; const uint8_t *pbNativeBits = (const uint8_t *)pImage->pvImage; for (size_t off = uRva; cbLeft > 0; off++, cbLeft--) if (pbNativeBits[off] != pbImageBits[off]) { /* Note! We need to copy image bits into a temporary stack buffer here as we'd otherwise risk overwriting them while formatting the error message. */ uint8_t abBytes[64]; memcpy(abBytes, &pbImageBits[off], RT_MIN(64, cbLeft)); supdrvLdrLoadError(VERR_LDR_MISMATCH_NATIVE, pReq, "Mismatch at %#x (%p) of %s loaded at %p:\n" "ntld: %.*Rhxs\n" "iprt: %.*Rhxs", off, &pbNativeBits[off], pImage->szName, pImage->pvImage, RT_MIN(64, cbLeft), &pbNativeBits[off], RT_MIN(64, cbLeft), &abBytes[0]); SUPR0Printf("VBoxDrv: %s", pReq->u.Out.szError); break; } } return iDiff; } /** Image compare exclusion regions. */ typedef struct SUPDRVNTEXCLREGIONS { /** Number of regions. */ uint32_t cRegions; /** The regions. */ struct SUPDRVNTEXCLREGION { uint32_t uRva; uint32_t cb; } aRegions[20]; } SUPDRVNTEXCLREGIONS; /** * Adds an exclusion region to the collection. */ static bool supdrvNtAddExclRegion(SUPDRVNTEXCLREGIONS *pRegions, uint32_t uRvaRegion, uint32_t cbRegion) { uint32_t const cRegions = pRegions->cRegions; AssertReturn(cRegions + 1 <= RT_ELEMENTS(pRegions->aRegions), false); uint32_t i = 0; for (; i < cRegions; i++) if (uRvaRegion < pRegions->aRegions[i].uRva) break; if (i != cRegions) memmove(&pRegions->aRegions[i + 1], &pRegions->aRegions[i], (cRegions - i) * sizeof(pRegions->aRegions[0])); pRegions->aRegions[i].uRva = uRvaRegion; pRegions->aRegions[i].cb = cbRegion; pRegions->cRegions++; return true; } int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) { NOREF(pDevExt); if (pImage->pvNtSectionObj) { /* * Usually, the entire image matches exactly. */ if (!memcmp(pImage->pvImage, pbImageBits, pImage->cbImageBits)) return VINF_SUCCESS; /* * On Windows 10 the ImageBase member of the optional header is sometimes * updated with the actual load address and sometimes not. * On older windows versions (builds <= 9200?), a user mode address is * sometimes found in the image base field after upgrading to VC++ 14.2. */ uint32_t const offNtHdrs = *(uint16_t *)pbImageBits == IMAGE_DOS_SIGNATURE ? ((IMAGE_DOS_HEADER const *)pbImageBits)->e_lfanew : 0; AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < pImage->cbImageBits, VERR_INTERNAL_ERROR_5); IMAGE_NT_HEADERS const *pNtHdrsIprt = (IMAGE_NT_HEADERS const *)(pbImageBits + offNtHdrs); IMAGE_NT_HEADERS const *pNtHdrsNtLd = (IMAGE_NT_HEADERS const *)((uintptr_t)pImage->pvImage + offNtHdrs); uint32_t const offImageBase = offNtHdrs + RT_UOFFSETOF(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); uint32_t const cbImageBase = RT_SIZEOFMEMB(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); if ( pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase && pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC && !memcmp(pImage->pvImage, pbImageBits, offImageBase) && !memcmp((uint8_t const *)pImage->pvImage + offImageBase + cbImageBase, pbImageBits + offImageBase + cbImageBase, pImage->cbImageBits - offImageBase - cbImageBase)) return VINF_SUCCESS; /* * On Windows Server 2003 (sp2 x86) both import thunk tables are fixed * up and we typically get a mismatch in the INIT section. * * So, lets see if everything matches when excluding the * OriginalFirstThunk tables and (maybe) the ImageBase member. * For simplicity the max number of exclusion regions is set to 16. */ if ( pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC && pNtHdrsIprt->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size >= sizeof(IMAGE_IMPORT_DESCRIPTOR) && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress > sizeof(IMAGE_NT_HEADERS) && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < pImage->cbImageBits && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size >= sizeof(IMAGE_LOAD_CONFIG_DIRECTORY) && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress > sizeof(IMAGE_NT_HEADERS) && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress < pImage->cbImageBits) { SUPDRVNTEXCLREGIONS ExcludeRegions; ExcludeRegions.cRegions = 0; /* ImageBase: */ if (pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase) supdrvNtAddExclRegion(&ExcludeRegions, offImageBase, cbImageBase); /* Imports: */ uint32_t cImpsLeft = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); uint32_t offImps = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; AssertLogRelReturn(offImps + cImpsLeft * sizeof(IMAGE_IMPORT_DESCRIPTOR) <= pImage->cbImageBits, VERR_INTERNAL_ERROR_3); IMAGE_IMPORT_DESCRIPTOR const *pImp = (IMAGE_IMPORT_DESCRIPTOR const *)(pbImageBits + offImps); while ( cImpsLeft-- > 0 && ExcludeRegions.cRegions < RT_ELEMENTS(ExcludeRegions.aRegions)) { uint32_t uRvaThunk = pImp->OriginalFirstThunk; if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA) && uRvaThunk != pImp->FirstThunk) { /* Find the size of the thunk table. */ IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); uint32_t cThunks = 0; while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) cThunks++; supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); } #if 0 /* Useful for VMMR0 hacking, not for production use. See also SUPDrvLdr.cpp. */ /* Exclude the other thunk table if ntoskrnl.exe. */ uint32_t uRvaName = pImp->Name; if ( uRvaName > sizeof(IMAGE_NT_HEADERS) && uRvaName < pImage->cbImageBits - sizeof("ntoskrnl.exe") && memcmp(&pbImageBits[uRvaName], RT_STR_TUPLE("ntoskrnl.exe")) == 0) { uRvaThunk = pImp->FirstThunk; if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA)) { /* Find the size of the thunk table. */ IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); uint32_t cThunks = 0; while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) cThunks++; supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); } } #endif /* advance */ pImp++; } /* Exclude the security cookie if present. */ uint32_t const cbCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size; uint32_t const offCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress; IMAGE_LOAD_CONFIG_DIRECTORY const * const pCfg = (IMAGE_LOAD_CONFIG_DIRECTORY const *)&pbImageBits[offCfg]; if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) && pCfg->SecurityCookie != NULL) supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->SecurityCookie - (uintptr_t)pImage->pvImage, sizeof(void *)); /* Also exclude the GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer pointer variables. */ if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFCheckFunctionPointer) && pCfg->GuardCFCheckFunctionPointer != NULL) supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFDispatchFunctionPointer) && pCfg->GuardCFDispatchFunctionPointer != NULL) supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); /* Ditto for the XFG variants: */ if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGCheckFunctionPointer) && pCfg->GuardXFGCheckFunctionPointer != NULL) supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGDispatchFunctionPointer) && pCfg->GuardXFGDispatchFunctionPointer != NULL) supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); /** @todo What about GuardRFVerifyStackPointerFunctionPointer and * GuardRFFailureRoutineFunctionPointer? Ignore for now as the compiler we're * using (19.26.28805) sets them to zero from what I can tell. */ /* * Ok, do the comparison. */ int iDiff = 0; uint32_t uRvaNext = 0; for (unsigned i = 0; !iDiff && i < ExcludeRegions.cRegions; i++) { if (uRvaNext < ExcludeRegions.aRegions[i].uRva) iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, ExcludeRegions.aRegions[i].uRva - uRvaNext, pReq); uRvaNext = ExcludeRegions.aRegions[i].uRva + ExcludeRegions.aRegions[i].cb; } if (!iDiff && uRvaNext < pImage->cbImageBits) iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, pImage->cbImageBits - uRvaNext, pReq); if (!iDiff) { /* * If there is a cookie init export, call it. * * This typically just does: * __security_cookie = (rdtsc ^ &__security_cookie) & 0xffffffffffff; * __security_cookie_complement = ~__security_cookie; */ PFNRT pfnModuleInitSecurityCookie = NULL; int rcSym = supdrvOSLdrQuerySymbol(pDevExt, pImage, RT_STR_TUPLE("ModuleInitSecurityCookie"), (void **)&pfnModuleInitSecurityCookie); if (RT_SUCCESS(rcSym) && pfnModuleInitSecurityCookie) pfnModuleInitSecurityCookie(); return VINF_SUCCESS; } } else supdrvNtCompare(pImage, pbImageBits, 0, pImage->cbImageBits, pReq); return VERR_LDR_MISMATCH_NATIVE; } return supdrvLdrLoadError(VERR_INTERNAL_ERROR_4, pReq, "No NT section object! Impossible!"); } void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) { if (pImage->pvNtSectionObj) { if (pImage->hMemLock != NIL_RTR0MEMOBJ) { RTR0MemObjFree(pImage->hMemLock, false /*fFreeMappings*/); pImage->hMemLock = NIL_RTR0MEMOBJ; } NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemUnloadGdiDriverInformation, &pImage->pvNtSectionObj, sizeof(pImage->pvNtSectionObj)); if (rcNt != STATUS_SUCCESS) SUPR0Printf("VBoxDrv: failed to unload '%s', rcNt=%#x\n", pImage->szName, rcNt); pImage->pvNtSectionObj = NULL; } NOREF(pDevExt); } void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) { RT_NOREF(pDevExt, pImage); AssertFailed(); } void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) { RT_NOREF(pDevExt, pImage); AssertFailed(); } #ifdef SUPDRV_WITH_MSR_PROBER #if 1 /** @todo make this selectable. */ # define AMD_MSR_PASSCODE 0x9c5a203a #else # define ASMRdMsrEx(a, b, c) ASMRdMsr(a) # define ASMWrMsrEx(a, b, c) ASMWrMsr(a,c) #endif /** * Argument package used by supdrvOSMsrProberRead and supdrvOSMsrProberWrite. */ typedef struct SUPDRVNTMSPROBERARGS { uint32_t uMsr; uint64_t uValue; bool fGp; } SUPDRVNTMSPROBERARGS; /** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberRead.} */ static DECLCALLBACK(void) supdrvNtMsProberReadOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) { /* * rdmsr and wrmsr faults can be caught even with interrupts disabled. * (At least on 32-bit XP.) */ SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); RTCCUINTREG fOldFlags = ASMIntDisableFlags(); __try { pArgs->uValue = ASMRdMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE); pArgs->fGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { pArgs->fGp = true; pArgs->uValue = 0; } ASMSetFlags(fOldFlags); } int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) { SUPDRVNTMSPROBERARGS Args; Args.uMsr = uMsr; Args.uValue = 0; Args.fGp = true; if (idCpu == NIL_RTCPUID) supdrvNtMsProberReadOnCpu(idCpu, &Args, NULL); else { int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberReadOnCpu, &Args, NULL); if (RT_FAILURE(rc)) return rc; } if (Args.fGp) return VERR_ACCESS_DENIED; *puValue = Args.uValue; return VINF_SUCCESS; } /** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberWrite.} */ static DECLCALLBACK(void) supdrvNtMsProberWriteOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) { /* * rdmsr and wrmsr faults can be caught even with interrupts disabled. * (At least on 32-bit XP.) */ SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); RTCCUINTREG fOldFlags = ASMIntDisableFlags(); __try { ASMWrMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE, pArgs->uValue); pArgs->fGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { pArgs->fGp = true; } ASMSetFlags(fOldFlags); } int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) { SUPDRVNTMSPROBERARGS Args; Args.uMsr = uMsr; Args.uValue = uValue; Args.fGp = true; if (idCpu == NIL_RTCPUID) supdrvNtMsProberWriteOnCpu(idCpu, &Args, NULL); else { int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberWriteOnCpu, &Args, NULL); if (RT_FAILURE(rc)) return rc; } if (Args.fGp) return VERR_ACCESS_DENIED; return VINF_SUCCESS; } /** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberModify.} */ static DECLCALLBACK(void) supdrvNtMsProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) { PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; register uint32_t uMsr = pReq->u.In.uMsr; bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; uint64_t uBefore = 0; uint64_t uWritten = 0; uint64_t uAfter = 0; bool fBeforeGp = true; bool fModifyGp = true; bool fAfterGp = true; bool fRestoreGp = true; RTCCUINTREG fOldFlags; RT_NOREF2(idCpu, pvUser2); /* * Do the job. */ fOldFlags = ASMIntDisableFlags(); ASMCompilerBarrier(); /* paranoia */ if (!fFaster) ASMWriteBackAndInvalidateCaches(); __try { uBefore = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); fBeforeGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { fBeforeGp = true; } if (!fBeforeGp) { register uint64_t uRestore = uBefore; /* Modify. */ uWritten = uRestore; uWritten &= pReq->u.In.uArgs.Modify.fAndMask; uWritten |= pReq->u.In.uArgs.Modify.fOrMask; __try { ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uWritten); fModifyGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { fModifyGp = true; } /* Read modified value. */ __try { uAfter = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); fAfterGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { fAfterGp = true; } /* Restore original value. */ __try { ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uRestore); fRestoreGp = false; } __except(EXCEPTION_EXECUTE_HANDLER) { fRestoreGp = true; } /* Invalid everything we can. */ if (!fFaster) { ASMWriteBackAndInvalidateCaches(); ASMReloadCR3(); ASMNopPause(); } } ASMCompilerBarrier(); /* paranoia */ ASMSetFlags(fOldFlags); /* * Write out the results. */ pReq->u.Out.uResults.Modify.uBefore = uBefore; pReq->u.Out.uResults.Modify.uWritten = uWritten; pReq->u.Out.uResults.Modify.uAfter = uAfter; pReq->u.Out.uResults.Modify.fBeforeGp = fBeforeGp; pReq->u.Out.uResults.Modify.fModifyGp = fModifyGp; pReq->u.Out.uResults.Modify.fAfterGp = fAfterGp; pReq->u.Out.uResults.Modify.fRestoreGp = fRestoreGp; RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); } int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) { if (idCpu == NIL_RTCPUID) { supdrvNtMsProberModifyOnCpu(idCpu, pReq, NULL); return VINF_SUCCESS; } return RTMpOnSpecific(idCpu, supdrvNtMsProberModifyOnCpu, pReq, NULL); } #endif /* SUPDRV_WITH_MSR_PROBER */ /** * Converts an IPRT error code to an nt status code. * * @returns corresponding nt status code. * @param rc IPRT error status code. */ static NTSTATUS VBoxDrvNtErr2NtStatus(int rc) { switch (rc) { case VINF_SUCCESS: return STATUS_SUCCESS; case VERR_GENERAL_FAILURE: return STATUS_NOT_SUPPORTED; case VERR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; case VERR_INVALID_MAGIC: return STATUS_UNKNOWN_REVISION; case VERR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; case VERR_INVALID_POINTER: return STATUS_INVALID_ADDRESS; case VERR_LOCK_FAILED: return STATUS_NOT_LOCKED; case VERR_ALREADY_LOADED: return STATUS_IMAGE_ALREADY_LOADED; case VERR_PERMISSION_DENIED: return STATUS_ACCESS_DENIED; case VERR_VERSION_MISMATCH: return STATUS_REVISION_MISMATCH; } if (rc < 0) { if (((uint32_t)rc & UINT32_C(0xffff0000)) == UINT32_C(0xffff0000)) return (NTSTATUS)( ((uint32_t)rc & UINT32_C(0xffff)) | SUP_NT_STATUS_BASE ); } return STATUS_UNSUCCESSFUL; } SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) { char szMsg[384]; size_t cch = RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); szMsg[sizeof(szMsg) - 1] = '\0'; RTLogWriteDebugger(szMsg, cch); return 0; } SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) { return 0; } SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) { RT_NOREF(fCtxHook); return false; } SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) { RT_NOREF(fCtxHook); } SUPR0DECL(int) SUPR0IoCtlSetupForHandle(PSUPDRVSESSION pSession, intptr_t hHandle, uint32_t fFlags, PSUPR0IOCTLCTX *ppCtx) { /* * Validate input. */ AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); *ppCtx = NULL; AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); AssertReturn(!fFlags, VERR_INVALID_FLAGS); /* * Turn the partition handle into a file object and related device object * so that we can issue direct I/O control calls to the pair later. */ PFILE_OBJECT pFileObject = NULL; OBJECT_HANDLE_INFORMATION HandleInfo = { 0, 0 }; NTSTATUS rcNt = ObReferenceObjectByHandle((HANDLE)hHandle, /*FILE_WRITE_DATA*/0, *IoFileObjectType, UserMode, (void **)&pFileObject, &HandleInfo); if (!NT_SUCCESS(rcNt)) return RTErrConvertFromNtStatus(rcNt); AssertPtrReturn(pFileObject, VERR_INTERNAL_ERROR_3); PDEVICE_OBJECT pDevObject = IoGetRelatedDeviceObject(pFileObject); AssertMsgReturnStmt(RT_VALID_PTR(pDevObject), ("pDevObject=%p\n", pDevObject), ObDereferenceObject(pFileObject), VERR_INTERNAL_ERROR_2); /* * Allocate a context structure and fill it in. */ PSUPR0IOCTLCTX pCtx = (PSUPR0IOCTLCTX)RTMemAllocZ(sizeof(*pCtx)); if (pCtx) { pCtx->u32Magic = SUPR0IOCTLCTX_MAGIC; pCtx->cRefs = 1; pCtx->pFileObject = pFileObject; pCtx->pDeviceObject = pDevObject; PDRIVER_OBJECT pDrvObject = pDevObject->DriverObject; if ( RT_VALID_PTR(pDrvObject->FastIoDispatch) && RT_VALID_PTR(pDrvObject->FastIoDispatch->FastIoDeviceControl)) pCtx->pfnFastIoDeviceControl = pDrvObject->FastIoDispatch->FastIoDeviceControl; else pCtx->pfnFastIoDeviceControl = NULL; *ppCtx = pCtx; return VINF_SUCCESS; } ObDereferenceObject(pFileObject); return VERR_NO_MEMORY; } /** * I/O control destructor for NT. * * @param pCtx The context to destroy. */ static void supdrvNtIoCtlContextDestroy(PSUPR0IOCTLCTX pCtx) { PFILE_OBJECT pFileObject = pCtx->pFileObject; pCtx->pfnFastIoDeviceControl = NULL; pCtx->pFileObject = NULL; pCtx->pDeviceObject = NULL; ASMAtomicWriteU32(&pCtx->u32Magic, ~SUPR0IOCTLCTX_MAGIC); if (RT_VALID_PTR(pFileObject)) ObDereferenceObject(pFileObject); RTMemFree(pCtx); } SUPR0DECL(int) SUPR0IoCtlCleanup(PSUPR0IOCTLCTX pCtx) { if (pCtx != NULL) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); uint32_t cRefs = ASMAtomicDecU32(&pCtx->cRefs); Assert(cRefs < _4K); if (cRefs == 0) supdrvNtIoCtlContextDestroy(pCtx); } return VINF_SUCCESS; } SUPR0DECL(int) SUPR0IoCtlPerform(PSUPR0IOCTLCTX pCtx, uintptr_t uFunction, void *pvInput, RTR3PTR pvInputUser, size_t cbInput, void *pvOutput, RTR3PTR pvOutputUser, size_t cbOutput, int32_t *piNativeRc) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); /* Reference the context. */ uint32_t cRefs = ASMAtomicIncU32(&pCtx->cRefs); Assert(cRefs > 1 && cRefs < _4K); /* * Try fast I/O control path first. */ IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; if (pCtx->pfnFastIoDeviceControl) { /* Must pass user addresses here as that's what's being expected. */ BOOLEAN fHandled = pCtx->pfnFastIoDeviceControl(pCtx->pFileObject, TRUE /*Wait*/, (void *)pvInputUser, (ULONG)cbInput, (void *)pvOutputUser, (ULONG)cbOutput, uFunction, &Ios, pCtx->pDeviceObject); if (fHandled) { /* Relase the context. */ cRefs = ASMAtomicDecU32(&pCtx->cRefs); Assert(cRefs < _4K); if (cRefs == 0) supdrvNtIoCtlContextDestroy(pCtx); /* Set/convert status and return. */ if (piNativeRc) { *piNativeRc = Ios.Status; return VINF_SUCCESS; } if (NT_SUCCESS(Ios.Status)) return VINF_SUCCESS; return RTErrConvertFromNtStatus(Ios.Status); } /* * Fall back on IRP if not handled. * * Note! Perhaps we should rather fail, because VID.SYS will crash getting * the partition ID with the code below. It tries to zero the output * buffer as if it were as system buffer... */ RTNT_IO_STATUS_BLOCK_REINIT(&Ios); } /* * For directly accessed buffers we must supply user mode addresses or * we'll fail ProbeForWrite validation. */ switch (uFunction & 3) { case METHOD_BUFFERED: /* For buffered accesses, we can supply kernel buffers. */ break; case METHOD_IN_DIRECT: pvInput = (void *)pvInputUser; break; case METHOD_NEITHER: pvInput = (void *)pvInputUser; RT_FALL_THRU(); case METHOD_OUT_DIRECT: pvOutput = (void *)pvOutputUser; break; } /* * Build the request. */ int rc; KEVENT Event; KeInitializeEvent(&Event, NotificationEvent, FALSE); PIRP pIrp = IoBuildDeviceIoControlRequest(uFunction, pCtx->pDeviceObject, pvInput, (ULONG)cbInput, pvOutput, (ULONG)cbOutput, FALSE /* InternalDeviceControl */, &Event, &Ios); if (pIrp) { IoGetNextIrpStackLocation(pIrp)->FileObject = pCtx->pFileObject; /* * Make the call. */ NTSTATUS rcNt = IoCallDriver(pCtx->pDeviceObject, pIrp); if (rcNt == STATUS_PENDING) { rcNt = KeWaitForSingleObject(&Event, /* Object */ Executive, /* WaitReason */ KernelMode, /* WaitMode */ FALSE, /* Alertable */ NULL); /* TimeOut */ AssertMsg(rcNt == STATUS_SUCCESS, ("rcNt=%#x\n", rcNt)); rcNt = Ios.Status; } else if (NT_SUCCESS(rcNt) && Ios.Status != STATUS_SUCCESS) rcNt = Ios.Status; /* Set/convert return code. */ if (piNativeRc) { *piNativeRc = rcNt; rc = VINF_SUCCESS; } else if (NT_SUCCESS(rcNt)) rc = VINF_SUCCESS; else rc = RTErrConvertFromNtStatus(rcNt); } else { if (piNativeRc) *piNativeRc = STATUS_NO_MEMORY; rc = VERR_NO_MEMORY; } /* Relase the context. */ cRefs = ASMAtomicDecU32(&pCtx->cRefs); Assert(cRefs < _4K); if (cRefs == 0) supdrvNtIoCtlContextDestroy(pCtx); return rc; } #ifdef VBOX_WITH_HARDENING /** @name Identifying Special Processes: CSRSS.EXE * @{ */ /** * Checks if the process is a system32 process by the given name. * * @returns true / false. * @param pProcess The process to check. * @param pszName The lower case process name (no path!). */ static bool supdrvNtProtectIsSystem32ProcessMatch(PEPROCESS pProcess, const char *pszName) { Assert(strlen(pszName) < 16); /* see buffer below */ /* * This test works on XP+. */ const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); if (!pszImageFile) return false; if (RTStrICmp(pszImageFile, pszName) != 0) return false; /* * This test requires a Vista+ API. */ if (g_pfnPsReferenceProcessFilePointer) { PFILE_OBJECT pFile = NULL; NTSTATUS rcNt = g_pfnPsReferenceProcessFilePointer(pProcess, &pFile); if (!NT_SUCCESS(rcNt)) return false; union { OBJECT_NAME_INFORMATION Info; uint8_t abBuffer[sizeof(g_System32NtPath) + 16 * sizeof(WCHAR)]; } Buf; ULONG cbIgn; rcNt = ObQueryNameString(pFile, &Buf.Info, sizeof(Buf) - sizeof(WCHAR), &cbIgn); ObDereferenceObject(pFile); if (!NT_SUCCESS(rcNt)) return false; /* Terminate the name. */ PRTUTF16 pwszName = Buf.Info.Name.Buffer; pwszName[Buf.Info.Name.Length / sizeof(RTUTF16)] = '\0'; /* Match the name against the system32 directory path. */ uint32_t cbSystem32 = g_System32NtPath.UniStr.Length; if (Buf.Info.Name.Length < cbSystem32) return false; if (memcmp(pwszName, g_System32NtPath.UniStr.Buffer, cbSystem32)) return false; pwszName += cbSystem32 / sizeof(RTUTF16); if (*pwszName++ != '\\') return false; /* Compare the name. */ const char *pszRight = pszName; for (;;) { WCHAR wchLeft = *pwszName++; char chRight = *pszRight++; Assert(chRight == RT_C_TO_LOWER(chRight)); if ( wchLeft != chRight && RT_C_TO_LOWER(wchLeft) != chRight) return false; if (!chRight) break; } } return true; } /** * Checks if the current process is likely to be CSRSS. * * @returns true/false. * @param pProcess The process. */ static bool supdrvNtProtectIsCsrssByProcess(PEPROCESS pProcess) { /* * On Windows 8.1 CSRSS.EXE is a protected process. */ if (g_pfnPsIsProtectedProcessLight) { if (!g_pfnPsIsProtectedProcessLight(pProcess)) return false; } /* * The name tests. */ if (!supdrvNtProtectIsSystem32ProcessMatch(pProcess, "csrss.exe")) return false; /** @todo Could extend the CSRSS.EXE check with that the TokenUser of the * current process must be "NT AUTHORITY\SYSTEM" (S-1-5-18). */ return true; } /** * Helper for supdrvNtProtectGetAlpcPortObjectType that tries out a name. * * @returns true if done, false if not. * @param pwszPortNm The port path. * @param ppObjType The object type return variable, updated when * returning true. */ static bool supdrvNtProtectGetAlpcPortObjectType2(PCRTUTF16 pwszPortNm, POBJECT_TYPE *ppObjType) { bool fDone = false; UNICODE_STRING UniStrPortNm; UniStrPortNm.Buffer = (WCHAR *)pwszPortNm; UniStrPortNm.Length = (USHORT)(RTUtf16Len(pwszPortNm) * sizeof(WCHAR)); UniStrPortNm.MaximumLength = UniStrPortNm.Length + sizeof(WCHAR); OBJECT_ATTRIBUTES ObjAttr; InitializeObjectAttributes(&ObjAttr, &UniStrPortNm, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); HANDLE hPort; NTSTATUS rcNt = g_pfnZwAlpcCreatePort(&hPort, &ObjAttr, NULL /*pPortAttribs*/); if (NT_SUCCESS(rcNt)) { PVOID pvObject; rcNt = ObReferenceObjectByHandle(hPort, 0 /*DesiredAccess*/, NULL /*pObjectType*/, KernelMode, &pvObject, NULL /*pHandleInfo*/); if (NT_SUCCESS(rcNt)) { POBJECT_TYPE pObjType = g_pfnObGetObjectType(pvObject); if (pObjType) { SUPR0Printf("vboxdrv: ALPC Port Object Type %p (vs %p)\n", pObjType, *ppObjType); *ppObjType = pObjType; fDone = true; } ObDereferenceObject(pvObject); } NtClose(hPort); } return fDone; } /** * Attempts to retrieve the ALPC Port object type. * * We've had at least three reports that using LpcPortObjectType when trying to * get at the ApiPort object results in STATUS_OBJECT_TYPE_MISMATCH errors. * It's not known who has modified LpcPortObjectType or AlpcPortObjectType (not * exported) so that it differs from the actual ApiPort type, or maybe this * unknown entity is intercepting our attempt to reference the port and * tries to mislead us. The paranoid explanataion is of course that some evil * root kit like software is messing with the OS, however, it's possible that * this is valid kernel behavior that 99.8% of our users and 100% of the * developers are not triggering for some reason. * * The code here creates an ALPC port object and gets it's type. It will cache * the result in g_pAlpcPortObjectType2 on success. * * @returns Object type. * @param uSessionId The session id. * @param pszSessionId The session id formatted as a string. */ static POBJECT_TYPE supdrvNtProtectGetAlpcPortObjectType(uint32_t uSessionId, const char *pszSessionId) { POBJECT_TYPE pObjType = *LpcPortObjectType; if ( g_pfnZwAlpcCreatePort && g_pfnObGetObjectType) { int rc; ssize_t cchTmp; NOREF(cchTmp); char szTmp[16]; RTUTF16 wszPortNm[128]; size_t offRand; /* * First attempt is in the session directory. */ rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\VBoxDrv-"); cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); offRand = RTUtf16Len(wszPortNm); cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); AssertRCSuccess(rc); bool fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); if (!fDone) { wszPortNm[offRand] = '\0'; cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); AssertRCSuccess(rc); fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); } if (!fDone) { /* * Try base names. */ if (uSessionId == 0) rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); else { rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); } cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); offRand = RTUtf16Len(wszPortNm); cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); AssertRCSuccess(rc); fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); if (!fDone) { wszPortNm[offRand] = '\0'; cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); AssertRCSuccess(rc); fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); } } /* Cache the result in g_pAlpcPortObjectType2. */ if ( g_pAlpcPortObjectType2 == NULL && pObjType != g_pAlpcPortObjectType1 && fDone) g_pAlpcPortObjectType2 = pObjType; } return pObjType; } /** * Called in the context of VBoxDrvNtCreate to determin the CSRSS for the * current process. * * The Client/Server Runtime Subsystem (CSRSS) process needs to be allowed some * additional access right so we need to make 101% sure we correctly identify * the CSRSS process a process is associated with. * * @returns IPRT status code. * @param pNtProtect The NT protected process structure. The * hCsrssPid member will be updated on success. */ static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect) { Assert(pNtProtect->AvlCore.Key == PsGetCurrentProcessId()); Assert(pNtProtect->pCsrssProcess == NULL); Assert(pNtProtect->hCsrssPid == NULL); /* * We'll try use the ApiPort LPC object for the session we're in to track * down the CSRSS process. So, we start by constructing a path to it. */ int rc; uint32_t uSessionId = PsGetProcessSessionId(PsGetCurrentProcess()); char szSessionId[16]; WCHAR wszApiPort[48]; if (uSessionId == 0) { szSessionId[0] = '0'; szSessionId[1] = '\0'; rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); } else { ssize_t cchTmp = RTStrFormatU32(szSessionId, sizeof(szSessionId), uSessionId, 10, 0, 0, 0); AssertReturn(cchTmp > 0, (int)cchTmp); rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Sessions\\"); if (RT_SUCCESS(rc)) rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), szSessionId); if (RT_SUCCESS(rc)) rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); } AssertRCReturn(rc, rc); UNICODE_STRING ApiPortStr; ApiPortStr.Buffer = wszApiPort; ApiPortStr.Length = (USHORT)(RTUtf16Len(wszApiPort) * sizeof(RTUTF16)); ApiPortStr.MaximumLength = ApiPortStr.Length + sizeof(RTUTF16); /* * The object cannot be opened, but we can reference it by name. */ void *pvApiPortObj = NULL; NTSTATUS rcNt = ObReferenceObjectByName(&ApiPortStr, 0, NULL /*pAccessState*/, STANDARD_RIGHTS_READ, g_pAlpcPortObjectType1, KernelMode, NULL /*pvParseContext*/, &pvApiPortObj); if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH && g_pAlpcPortObjectType2 != NULL) rcNt = ObReferenceObjectByName(&ApiPortStr, 0, NULL /*pAccessState*/, STANDARD_RIGHTS_READ, g_pAlpcPortObjectType2, KernelMode, NULL /*pvParseContext*/, &pvApiPortObj); if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH && g_pfnObGetObjectType && g_pfnZwAlpcCreatePort) rcNt = ObReferenceObjectByName(&ApiPortStr, 0, NULL /*pAccessState*/, STANDARD_RIGHTS_READ, supdrvNtProtectGetAlpcPortObjectType(uSessionId, szSessionId), KernelMode, NULL /*pvParseContext*/, &pvApiPortObj); if (!NT_SUCCESS(rcNt)) { SUPR0Printf("vboxdrv: Error opening '%ls': %#x\n", wszApiPort, rcNt); return rcNt == STATUS_OBJECT_TYPE_MISMATCH ? VERR_SUPDRV_APIPORT_OPEN_ERROR_TYPE : VERR_SUPDRV_APIPORT_OPEN_ERROR; } /* * Query the processes in the system so we can locate CSRSS.EXE candidates. * Note! Attempts at using SystemSessionProcessInformation failed with * STATUS_ACCESS_VIOLATION. * Note! The 32 bytes on the size of to counteract the allocation header * that rtR0MemAllocEx slaps on everything. */ ULONG cbNeeded = _64K - 32; uint32_t cbBuf; uint8_t *pbBuf = NULL; do { cbBuf = RT_ALIGN(cbNeeded + _4K, _64K) - 32; pbBuf = (uint8_t *)RTMemAlloc(cbBuf); if (!pbBuf) break; cbNeeded = 0; #if 0 /* doesn't work. */ SYSTEM_SESSION_PROCESS_INFORMATION Req; Req.SessionId = uSessionId; Req.BufferLength = cbBuf; Req.Buffer = pbBuf; rcNt = NtQuerySystemInformation(SystemSessionProcessInformation, &Req, sizeof(Req), &cbNeeded); #else rcNt = NtQuerySystemInformation(SystemProcessInformation, pbBuf, cbBuf, &cbNeeded); #endif if (NT_SUCCESS(rcNt)) break; RTMemFree(pbBuf); pbBuf = NULL; } while ( rcNt == STATUS_INFO_LENGTH_MISMATCH && cbNeeded > cbBuf && cbNeeded < 32U*_1M); if ( pbBuf && NT_SUCCESS(rcNt) && cbNeeded >= sizeof(SYSTEM_PROCESS_INFORMATION)) { /* * Walk the returned data and look for the process associated with the * ApiPort object. The ApiPort object keeps the EPROCESS address of * the owner process (i.e. CSRSS) relatively early in the structure. On * 64-bit windows 8.1 it's at offset 0x18. So, obtain the EPROCESS * pointer to likely CSRSS processes and check for a match in the first * 0x40 bytes of the ApiPort object. */ rc = VERR_SUPDRV_CSRSS_NOT_FOUND; for (uint32_t offBuf = 0; offBuf <= cbNeeded - sizeof(SYSTEM_PROCESS_INFORMATION);) { PRTNT_SYSTEM_PROCESS_INFORMATION pProcInfo = (PRTNT_SYSTEM_PROCESS_INFORMATION)&pbBuf[offBuf]; if ( pProcInfo->ProcessName.Length == 9 * sizeof(WCHAR) && pProcInfo->NumberOfThreads > 2 /* Very low guess. */ && pProcInfo->HandleCount > 32 /* Very low guess, I hope. */ && (uintptr_t)pProcInfo->ProcessName.Buffer - (uintptr_t)pbBuf < cbNeeded && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[0]) == 'c' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[1]) == 's' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[2]) == 'r' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[3]) == 's' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[4]) == 's' && pProcInfo->ProcessName.Buffer[5] == '.' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[6]) == 'e' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[7]) == 'x' && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[8]) == 'e' ) { /* Get the process structure and perform some more thorough process checks. */ PEPROCESS pProcess; rcNt = PsLookupProcessByProcessId(pProcInfo->UniqueProcessId, &pProcess); if (NT_SUCCESS(rcNt)) { if (supdrvNtProtectIsCsrssByProcess(pProcess)) { if (PsGetProcessSessionId(pProcess) == uSessionId) { /* Final test, check the ApiPort. Note! The old LPC (pre Vista) objects has the PID much earlier in the structure. Might be worth looking for it instead. */ bool fThatsIt = false; __try { PEPROCESS *ppPortProc = (PEPROCESS *)pvApiPortObj; uint32_t cTests = g_uNtVerCombined >= SUP_NT_VER_VISTA ? 16 : 38; /* ALPC since Vista. */ do { fThatsIt = *ppPortProc == pProcess; ppPortProc++; } while (!fThatsIt && --cTests > 0); } __except(EXCEPTION_EXECUTE_HANDLER) { fThatsIt = false; } if (fThatsIt) { /* Ok, we found it! Keep the process structure reference as well as the PID so we can safely identify it later on. */ pNtProtect->hCsrssPid = pProcInfo->UniqueProcessId; pNtProtect->pCsrssProcess = pProcess; rc = VINF_SUCCESS; break; } } } ObDereferenceObject(pProcess); } } /* Advance. */ if (!pProcInfo->NextEntryOffset) break; offBuf += pProcInfo->NextEntryOffset; } } else rc = VERR_SUPDRV_SESSION_PROCESS_ENUM_ERROR; RTMemFree(pbBuf); ObDereferenceObject(pvApiPortObj); return rc; } /** * Checks that the given process is the CSRSS process associated with protected * process. * * @returns true / false. * @param pNtProtect The NT protection structure. * @param pCsrss The process structure of the alleged CSRSS.EXE * process. */ static bool supdrvNtProtectIsAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pCsrss) { if (pNtProtect->pCsrssProcess == pCsrss) { if (pNtProtect->hCsrssPid == PsGetProcessId(pCsrss)) { return true; } } return false; } /** * Checks if the given process is the stupid themes service. * * The caller does some screening of access masks and what not. We do the rest. * * @returns true / false. * @param pNtProtect The NT protection structure. * @param pAnnoyingProcess The process structure of an process that might * happen to be the annoying themes process. */ static bool supdrvNtProtectIsFrigginThemesService(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pAnnoyingProcess) { RT_NOREF1(pNtProtect); /* * Check the process name. */ if (!supdrvNtProtectIsSystem32ProcessMatch(pAnnoyingProcess, "svchost.exe")) return false; /** @todo Come up with more checks. */ return true; } #ifdef VBOX_WITHOUT_DEBUGGER_CHECKS /** * Checks if the given process is one of the whitelisted debuggers. * * @returns true / false. * @param pProcess The process to check. */ static bool supdrvNtProtectIsWhitelistedDebugger(PEPROCESS pProcess) { const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); if (!pszImageFile) return false; if (pszImageFile[0] == 'w' || pszImageFile[0] == 'W') { if (RTStrICmp(pszImageFile, "windbg.exe") == 0) return true; if (RTStrICmp(pszImageFile, "werfault.exe") == 0) return true; if (RTStrICmp(pszImageFile, "werfaultsecure.exe") == 0) return true; } else if (pszImageFile[0] == 'd' || pszImageFile[0] == 'D') { if (RTStrICmp(pszImageFile, "drwtsn32.exe") == 0) return true; if (RTStrICmp(pszImageFile, "dwwin.exe") == 0) return true; } return false; } #endif /* VBOX_WITHOUT_DEBUGGER_CHECKS */ /** @} */ /** @name Process Creation Callbacks. * @{ */ /** * Cleans up VBoxDrv or VBoxDrvStub error info not collected by the dead process. * * @param hProcessId The ID of the dead process. */ static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId) { int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); if (RT_SUCCESS(rc)) { PSUPDRVNTERRORINFO pCur, pNext; RTListForEachSafe(&g_ErrorInfoHead, pCur, pNext, SUPDRVNTERRORINFO, ListEntry) { if (pCur->hProcessId == hProcessId) { RTListNodeRemove(&pCur->ListEntry); RTMemFree(pCur); } } RTSemMutexRelease(g_hErrorInfoLock); } } /** * Common worker used by the process creation hooks as well as the process * handle creation hooks to check if a VM process is being created. * * @returns true if likely to be a VM process, false if not. * @param pNtStub The NT protection structure for the possible * stub process. * @param hParentPid The parent pid. * @param hChildPid The child pid. */ static bool supdrvNtProtectIsSpawningStubProcess(PSUPDRVNTPROTECT pNtStub, HANDLE hParentPid, HANDLE hChildPid) { bool fRc = false; if (pNtStub->AvlCore.Key == hParentPid) /* paranoia */ { if (pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) { /* Compare short names. */ PEPROCESS pStubProcess; NTSTATUS rcNt = PsLookupProcessByProcessId(hParentPid, &pStubProcess); if (NT_SUCCESS(rcNt)) { PEPROCESS pChildProcess; rcNt = PsLookupProcessByProcessId(hChildPid, &pChildProcess); if (NT_SUCCESS(rcNt)) { const char *pszStub = (const char *)PsGetProcessImageFileName(pStubProcess); const char *pszChild = (const char *)PsGetProcessImageFileName(pChildProcess); fRc = pszStub != NULL && pszChild != NULL && strcmp(pszStub, pszChild) == 0; /** @todo check that the full image names matches. */ ObDereferenceObject(pChildProcess); } ObDereferenceObject(pStubProcess); } } } return fRc; } /** * Common code used by the notifies to protect a child process. * * @returns VBox status code. * @param pNtStub The NT protect structure for the parent. * @param hChildPid The child pid. */ static int supdrvNtProtectProtectNewStubChild(PSUPDRVNTPROTECT pNtParent, HANDLE hChildPid) { /* * Create a child protection struction. */ PSUPDRVNTPROTECT pNtChild; int rc = supdrvNtProtectCreate(&pNtChild, hChildPid, kSupDrvNtProtectKind_VmProcessUnconfirmed, false /*fLink*/); if (RT_SUCCESS(rc)) { pNtChild->fFirstProcessCreateHandle = true; pNtChild->fFirstThreadCreateHandle = true; pNtChild->fCsrssFirstProcessCreateHandle = true; pNtChild->cCsrssFirstProcessDuplicateHandle = ARCH_BITS == 32 ? 2 : 1; pNtChild->fThemesFirstProcessCreateHandle = true; pNtChild->hParentPid = pNtParent->AvlCore.Key; pNtChild->hCsrssPid = pNtParent->hCsrssPid; pNtChild->pCsrssProcess = pNtParent->pCsrssProcess; if (pNtChild->pCsrssProcess) ObReferenceObject(pNtChild->pCsrssProcess); /* * Take the spinlock, recheck parent conditions and link things. */ RTSpinlockAcquire(g_hNtProtectLock); if (pNtParent->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) { bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtChild->AvlCore); if (fSuccess) { pNtChild->fInTree = true; pNtParent->u.pChild = pNtChild; /* Parent keeps the initial reference. */ pNtParent->enmProcessKind = kSupDrvNtProtectKind_StubParent; pNtChild->u.pParent = pNtParent; RTSpinlockRelease(g_hNtProtectLock); return VINF_SUCCESS; } rc = VERR_INTERNAL_ERROR_2; } else rc = VERR_WRONG_ORDER; pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; RTSpinlockRelease(g_hNtProtectLock); supdrvNtProtectRelease(pNtChild); } return rc; } /** * Common process termination code. * * Transitions protected process to the dead states, protecting against handle * PID reuse (esp. with unconfirmed VM processes) and handle cleanup issues. * * @param hDeadPid The PID of the dead process. */ static void supdrvNtProtectUnprotectDeadProcess(HANDLE hDeadPid) { PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hDeadPid); if (pNtProtect) { PSUPDRVNTPROTECT pNtChild = NULL; RTSpinlockAcquire(g_hNtProtectLock); /* * If this is an unconfirmed VM process, we must release the reference * the parent structure holds. */ if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) { PSUPDRVNTPROTECT pNtParent = pNtProtect->u.pParent; AssertRelease(pNtParent); AssertRelease(pNtParent->u.pChild == pNtProtect); pNtParent->u.pChild = NULL; pNtProtect->u.pParent = NULL; pNtChild = pNtProtect; } /* * If this is a stub exitting before the VM process gets confirmed, * release the protection of the potential VM process as this is not * the prescribed behavior. */ else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent && pNtProtect->u.pChild) { pNtChild = pNtProtect->u.pChild; pNtProtect->u.pChild = NULL; pNtChild->u.pParent = NULL; pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; } /* * Transition it to the dead state to prevent it from opening the * support driver again or be posthumously abused as a vm process parent. */ if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubSpawning || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) pNtProtect->enmProcessKind = kSupDrvNtProtectKind_StubDead; RTSpinlockRelease(g_hNtProtectLock); supdrvNtProtectRelease(pNtProtect); supdrvNtProtectRelease(pNtChild); /* * Do session cleanups. */ AssertReturnVoid((HANDLE)(uintptr_t)RTProcSelf() == hDeadPid); if (g_pDevObjSys) { PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, (RTPROCESS)(uintptr_t)hDeadPid, RTR0ProcHandleSelf(), NULL); if (pSession) { supdrvSessionHashTabRemove(pDevExt, pSession, NULL); supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ } } } } /** * Common worker for the process creation callback that verifies a new child * being created by the handle creation callback code. * * @param pNtStub The parent. * @param pNtVm The child. * @param fCallerChecks The result of any additional tests the caller made. * This is in order to avoid duplicating the failure * path code. */ static void supdrvNtProtectVerifyNewChildProtection(PSUPDRVNTPROTECT pNtStub, PSUPDRVNTPROTECT pNtVm, bool fCallerChecks) { if ( fCallerChecks && pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubParent && pNtVm->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed && pNtVm->u.pParent == pNtStub && pNtStub->u.pChild == pNtVm) { /* Fine, reset the CSRSS hack (fixes ViRobot APT Shield 2.0 issue). */ pNtVm->fFirstProcessCreateHandle = true; return; } LogRel(("vboxdrv: Misdetected vm stub; hParentPid=%p hChildPid=%p\n", pNtStub->AvlCore.Key, pNtVm->AvlCore.Key)); if (pNtStub->enmProcessKind != kSupDrvNtProtectKind_VmProcessConfirmed) supdrvNtProtectUnprotectDeadProcess(pNtVm->AvlCore.Key); } /** * Old style callback (since forever). * * @param hParentPid The parent PID. * @param hNewPid The PID of the new child. * @param fCreated TRUE if it's a creation notification, * FALSE if termination. * @remarks ASSUMES this arrives before the handle creation callback. */ static VOID __stdcall supdrvNtProtectCallback_ProcessCreateNotify(HANDLE hParentPid, HANDLE hNewPid, BOOLEAN fCreated) { /* * Is it a new process that needs protection? */ if (fCreated) { PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); if (pNtStub) { PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); if (!pNtVm) { if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hNewPid)) supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); } else { supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, true); supdrvNtProtectRelease(pNtVm); } supdrvNtProtectRelease(pNtStub); } } /* * Process termination, do clean ups. */ else { supdrvNtProtectUnprotectDeadProcess(hNewPid); supdrvNtErrorInfoCleanupProcess(hNewPid); } } /** * New style callback (Vista SP1+ / w2k8). * * @param pNewProcess The new process. * @param hNewPid The PID of the new process. * @param pInfo Process creation details. NULL if process * termination notification. * @remarks ASSUMES this arrives before the handle creation callback. */ static VOID __stdcall supdrvNtProtectCallback_ProcessCreateNotifyEx(PEPROCESS pNewProcess, HANDLE hNewPid, PPS_CREATE_NOTIFY_INFO pInfo) { RT_NOREF1(pNewProcess); /* * Is it a new process that needs protection? */ if (pInfo) { PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(pInfo->CreatingThreadId.UniqueProcess); Log(("vboxdrv/NewProcessEx: ctx=%04zx/%p pid=%04zx ppid=%04zx ctor=%04zx/%04zx rcNt=%#x %.*ls\n", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), hNewPid, pInfo->ParentProcessId, pInfo->CreatingThreadId.UniqueProcess, pInfo->CreatingThreadId.UniqueThread, pInfo->CreationStatus, pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? (size_t)pInfo->ImageFileName->Length / 2 : 0, pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? pInfo->ImageFileName->Buffer : NULL)); if (pNtStub) { PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); if (!pNtVm) { /* Parent must be creator. */ if (pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId) { if (supdrvNtProtectIsSpawningStubProcess(pNtStub, pInfo->ParentProcessId, hNewPid)) supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); } } else { /* Parent must be creator (as above). */ supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId); supdrvNtProtectRelease(pNtVm); } supdrvNtProtectRelease(pNtStub); } } /* * Process termination, do clean ups. */ else { supdrvNtProtectUnprotectDeadProcess(hNewPid); supdrvNtErrorInfoCleanupProcess(hNewPid); } } /** @} */ /** @name Process Handle Callbacks. * @{ */ /** Process rights that we allow for handles to stub and VM processes. */ # define SUPDRV_NT_ALLOW_PROCESS_RIGHTS \ ( PROCESS_TERMINATE \ | PROCESS_VM_READ \ | PROCESS_QUERY_INFORMATION \ | PROCESS_QUERY_LIMITED_INFORMATION \ | PROCESS_SUSPEND_RESUME \ | DELETE \ | READ_CONTROL \ | SYNCHRONIZE) /** Evil process rights. */ # define SUPDRV_NT_EVIL_PROCESS_RIGHTS \ ( PROCESS_CREATE_THREAD \ | PROCESS_SET_SESSIONID /*?*/ \ | PROCESS_VM_OPERATION \ | PROCESS_VM_WRITE \ | PROCESS_DUP_HANDLE \ | PROCESS_CREATE_PROCESS /*?*/ \ | PROCESS_SET_QUOTA /*?*/ \ | PROCESS_SET_INFORMATION \ | PROCESS_SET_LIMITED_INFORMATION /*?*/ \ | 0) AssertCompile((SUPDRV_NT_ALLOW_PROCESS_RIGHTS & SUPDRV_NT_EVIL_PROCESS_RIGHTS) == 0); static OB_PREOP_CALLBACK_STATUS __stdcall supdrvNtProtectCallback_ProcessHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) { Assert(pvUser == NULL); RT_NOREF1(pvUser); Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); Assert(pOpInfo->ObjectType == *PsProcessType); /* * Protected? Kludge required for NtOpenProcess calls comming in before * the create process hook triggers on Windows 8.1 (possibly others too). */ HANDLE hObjPid = PsGetProcessId((PEPROCESS)pOpInfo->Object); PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hObjPid); if (!pNtProtect) { HANDLE hParentPid = PsGetProcessInheritedFromUniqueProcessId((PEPROCESS)pOpInfo->Object); PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); if (pNtStub) { if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hObjPid)) { supdrvNtProtectProtectNewStubChild(pNtStub, hObjPid); pNtProtect = supdrvNtProtectLookup(hObjPid); } supdrvNtProtectRelease(pNtStub); } } pOpInfo->CallContext = pNtProtect; /* Just for reference. */ if (pNtProtect) { /* * Ok, it's a protected process. Strip rights as required or possible. */ static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOW_PROCESS_RIGHTS; if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) { /* Don't restrict the process accessing itself. */ if ((PEPROCESS)pOpInfo->Object == PsGetCurrentProcess()) { pOpInfo->CallContext = NULL; /* don't assert */ pNtProtect->fFirstProcessCreateHandle = false; Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); } #ifdef VBOX_WITHOUT_DEBUGGER_CHECKS /* Allow debuggers full access. */ else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) { pOpInfo->CallContext = NULL; /* don't assert */ pNtProtect->fFirstProcessCreateHandle = false; Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); } #endif else { ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->CreateHandleInformation.DesiredAccess; /* Special case 1 on Vista, 7 & 8: The CreateProcess code passes the handle over to CSRSS.EXE and the code inBaseSrvCreateProcess will duplicate the handle with 0x1fffff as access mask. NtDuplicateObject will fail this call before it ever gets down here. Special case 2 on 8.1: The CreateProcess code requires additional rights for something, we'll drop these in the stub code. */ if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed && pNtProtect->fFirstProcessCreateHandle && pOpInfo->KernelHandle == 0 && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) && ExGetPreviousMode() != KernelMode) { if ( !pOpInfo->KernelHandle && fDesiredAccess == s_fCsrssStupidDesires) { if (g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3)) fAllowedRights |= s_fCsrssStupidDesires; else fAllowedRights = fAllowedRights | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_SET_INFORMATION | PROCESS_SET_LIMITED_INFORMATION | 0; pOpInfo->CallContext = NULL; /* don't assert this. */ } pNtProtect->fFirstProcessCreateHandle = false; } /* Special case 3 on 8.1: The interaction between the CreateProcess code and CSRSS.EXE has changed to the better with Windows 8.1. CSRSS.EXE no longer duplicates the process (thread too) handle, but opens it, thus allowing us to do our job. */ if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed && pNtProtect->fCsrssFirstProcessCreateHandle && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) { pNtProtect->fCsrssFirstProcessCreateHandle = false; if (fDesiredAccess == s_fCsrssStupidDesires) { /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, PROCESS_CREATE_PROCESS */ fAllowedRights = fAllowedRights | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE /* Needed for CreateProcess/VBoxTestOGL. */ | 0; pOpInfo->CallContext = NULL; /* don't assert this. */ } } /* Special case 4, Windows 7, Vista, possibly 8, but not 8.1: The Themes service requires PROCESS_DUP_HANDLE access to our process or we won't get any menus and dialogs will be half unreadable. This is _very_ unfortunate and more work will go into making this more secure. */ if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0) && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) && fDesiredAccess == 0x1478 /* 6.1.7600.16385 (win7_rtm.090713-1255) */ && pNtProtect->fThemesFirstProcessCreateHandle && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsFrigginThemesService(pNtProtect, PsGetCurrentProcess()) ) { pNtProtect->fThemesFirstProcessCreateHandle = true; /* Only once! */ fAllowedRights |= PROCESS_DUP_HANDLE; pOpInfo->CallContext = NULL; /* don't assert this. */ } /* Special case 6a, Windows 10+: AudioDG.exe opens the process with the PROCESS_SET_LIMITED_INFORMATION right. It seems like it need it for some myserious and weirdly placed cpu set management of our process. I'd love to understand what that's all about... Currently playing safe and only grand this right, however limited, to audiodg.exe. */ if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ ) && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) { fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; pOpInfo->CallContext = NULL; /* don't assert this. */ } Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p/pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), fDesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, fDesiredAccess & fAllowedRights, PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode() )); pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; } } else { /* Don't restrict the process accessing itself. */ if ( (PEPROCESS)pOpInfo->Object == PsGetCurrentProcess() && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pOpInfo->Object) { Log(("vboxdrv/ProcessHandlePre: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); pOpInfo->CallContext = NULL; /* don't assert */ } else { ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess; /* Special case 5 on Vista, 7 & 8: This is the CSRSS.EXE end of special case #1. */ if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed && pNtProtect->cCsrssFirstProcessDuplicateHandle > 0 && pOpInfo->KernelHandle == 0 && fDesiredAccess == s_fCsrssStupidDesires && pNtProtect->hParentPid == PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess) && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() && ExGetPreviousMode() == UserMode && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess())) { if (ASMAtomicDecS32(&pNtProtect->cCsrssFirstProcessDuplicateHandle) >= 0) { /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, PROCESS_CREATE_PROCESS, PROCESS_DUP_HANDLE */ fAllowedRights = fAllowedRights | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE /* Needed for launching VBoxTestOGL. */ | 0; pOpInfo->CallContext = NULL; /* don't assert this. */ } } /* Special case 6b, Windows 10+: AudioDG.exe duplicates the handle it opened above. */ if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ ) && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) { fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; pOpInfo->CallContext = NULL; /* don't assert this. */ } Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, fDesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; } } supdrvNtProtectRelease(pNtProtect); } return OB_PREOP_SUCCESS; } static VOID __stdcall supdrvNtProtectCallback_ProcessHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) { Assert(pvUser == NULL); RT_NOREF1(pvUser); Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); Assert(pOpInfo->ObjectType == *PsProcessType); if ( pOpInfo->CallContext && NT_SUCCESS(pOpInfo->ReturnStatus)) { ACCESS_MASK const fGrantedAccess = pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE ? pOpInfo->Parameters->CreateHandleInformation.GrantedAccess : pOpInfo->Parameters->DuplicateHandleInformation.GrantedAccess; AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOW_PROCESS_RIGHTS | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ | PROCESS_UNKNOWN_4000 /* Seen set on win 8.1 */ /*| PROCESS_UNKNOWN_8000 */ ) ) || pOpInfo->KernelHandle, ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", fGrantedAccess, SUPDRV_NT_ALLOW_PROCESS_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOW_PROCESS_RIGHTS)); } } # undef SUPDRV_NT_ALLOW_PROCESS_RIGHTS /** @} */ /** @name Thread Handle Callbacks * @{ */ /* From ntifs.h */ extern "C" NTKERNELAPI PEPROCESS __stdcall IoThreadToProcess(PETHREAD); /** Thread rights that we allow for handles to stub and VM processes. */ # define SUPDRV_NT_ALLOWED_THREAD_RIGHTS \ ( THREAD_TERMINATE \ | THREAD_GET_CONTEXT \ | THREAD_QUERY_INFORMATION \ | THREAD_QUERY_LIMITED_INFORMATION \ | DELETE \ | READ_CONTROL \ | SYNCHRONIZE) /** @todo consider THREAD_SET_LIMITED_INFORMATION & THREAD_RESUME */ /** Evil thread rights. * @remarks THREAD_RESUME is not included as it seems to be forced upon us by * Windows 8.1, at least for some processes. We dont' actively * allow it though, just tollerate it when forced to. */ # define SUPDRV_NT_EVIL_THREAD_RIGHTS \ ( THREAD_SUSPEND_RESUME \ | THREAD_SET_CONTEXT \ | THREAD_SET_INFORMATION \ | THREAD_SET_LIMITED_INFORMATION /*?*/ \ | THREAD_SET_THREAD_TOKEN /*?*/ \ | THREAD_IMPERSONATE /*?*/ \ | THREAD_DIRECT_IMPERSONATION /*?*/ \ /*| THREAD_RESUME - see remarks. */ \ | 0) AssertCompile((SUPDRV_NT_EVIL_THREAD_RIGHTS & SUPDRV_NT_ALLOWED_THREAD_RIGHTS) == 0); static OB_PREOP_CALLBACK_STATUS __stdcall supdrvNtProtectCallback_ThreadHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) { Assert(pvUser == NULL); RT_NOREF1(pvUser); Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); Assert(pOpInfo->ObjectType == *PsThreadType); PEPROCESS pProcess = IoThreadToProcess((PETHREAD)pOpInfo->Object); PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(pProcess)); pOpInfo->CallContext = pNtProtect; /* Just for reference. */ if (pNtProtect) { static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOWED_THREAD_RIGHTS; if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) { /* Don't restrict the process accessing its own threads. */ if (pProcess == PsGetCurrentProcess()) { Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] self\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind)); pOpInfo->CallContext = NULL; /* don't assert */ pNtProtect->fFirstThreadCreateHandle = false; } #ifdef VBOX_WITHOUT_DEBUGGER_CHECKS /* Allow debuggers full access. */ else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) { Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); pOpInfo->CallContext = NULL; /* don't assert */ } #endif else { /* Special case 1 on Vista, 7, 8: The CreateProcess code passes the handle over to CSRSS.EXE and the code inBaseSrvCreateProcess will duplicate the handle with 0x1fffff as access mask. NtDuplicateObject will fail this call before it ever gets down here. */ if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed && pNtProtect->fFirstThreadCreateHandle && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) ) { if ( !pOpInfo->KernelHandle && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == s_fCsrssStupidDesires) { fAllowedRights |= s_fCsrssStupidDesires; pOpInfo->CallContext = NULL; /* don't assert this. */ } pNtProtect->fFirstThreadCreateHandle = false; } /* Special case 2 on 8.1, possibly also Vista, 7, 8: When creating a process like VBoxTestOGL from the VM process, CSRSS.EXE will try talk to the calling thread and, it appears, impersonate it. We unfortunately need to allow this or there will be no 3D support. Typical DbgPrint: "SXS: BasepCreateActCtx() Calling csrss server failed. Status = 0xc00000a5" */ SUPDRVNTPROTECTKIND enmProcessKind; if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) { fAllowedRights |= THREAD_IMPERSONATE; fAllowedRights |= THREAD_DIRECT_IMPERSONATION; //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one pOpInfo->CallContext = NULL; /* don't assert this. */ } Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode())); pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; } } else { /* Don't restrict the process accessing its own threads. */ if ( pProcess == PsGetCurrentProcess() && (PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pProcess) { Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] self\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, PsGetProcessImageFileName(PsGetCurrentProcess()) )); pOpInfo->CallContext = NULL; /* don't assert */ } else { /* Special case 3 on Vista, 7, 8: This is the follow up to special case 1. */ SUPDRVNTPROTECTKIND enmProcessKind; if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() && pOpInfo->KernelHandle == 0 && ExGetPreviousMode() == UserMode && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) { fAllowedRights |= THREAD_IMPERSONATE; fAllowedRights |= THREAD_DIRECT_IMPERSONATION; //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one pOpInfo->CallContext = NULL; /* don't assert this. */ } Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s\n", pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess & fAllowedRights, PsGetProcessImageFileName(PsGetCurrentProcess()) )); pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; } } supdrvNtProtectRelease(pNtProtect); } return OB_PREOP_SUCCESS; } static VOID __stdcall supdrvNtProtectCallback_ThreadHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) { Assert(pvUser == NULL); RT_NOREF1(pvUser); Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); Assert(pOpInfo->ObjectType == *PsThreadType); if ( pOpInfo->CallContext && NT_SUCCESS(pOpInfo->ReturnStatus)) { ACCESS_MASK const fGrantedAccess = pOpInfo->Parameters->CreateHandleInformation.GrantedAccess; AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOWED_THREAD_RIGHTS | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ | THREAD_RESUME /* This seems to be force upon us too with 8.1. */ ) ) || pOpInfo->KernelHandle, ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", fGrantedAccess, SUPDRV_NT_ALLOWED_THREAD_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOWED_THREAD_RIGHTS)); } } # undef SUPDRV_NT_ALLOWED_THREAD_RIGHTS /** @} */ /** * Creates a new process protection structure. * * @returns VBox status code. * @param ppNtProtect Where to return the pointer to the structure * on success. * @param hPid The process ID of the process to protect. * @param enmProcessKind The kind of process we're protecting. * @param fLink Whether to link the structure into the tree. */ static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, SUPDRVNTPROTECTKIND enmProcessKind, bool fLink) { AssertReturn(g_hNtProtectLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)RTMemAllocZ(sizeof(*pNtProtect)); if (!pNtProtect) return VERR_NO_MEMORY; pNtProtect->AvlCore.Key = hPid; pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC; pNtProtect->cRefs = 1; pNtProtect->enmProcessKind = enmProcessKind; pNtProtect->hParentPid = NULL; pNtProtect->hOpenTid = NULL; pNtProtect->hCsrssPid = NULL; pNtProtect->pCsrssProcess = NULL; if (fLink) { RTSpinlockAcquire(g_hNtProtectLock); bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtProtect->AvlCore); pNtProtect->fInTree = fSuccess; RTSpinlockRelease(g_hNtProtectLock); if (!fSuccess) { /* Duplicate entry, fail. */ pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC_DEAD; LogRel(("supdrvNtProtectCreate: Duplicate (%#x).\n", pNtProtect->AvlCore.Key)); RTMemFree(pNtProtect); return VERR_DUPLICATE; } } *ppNtProtect = pNtProtect; return VINF_SUCCESS; } /** * Releases a reference to a NT protection structure. * * @param pNtProtect The NT protection structure. */ static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect) { if (!pNtProtect) return; AssertReturnVoid(pNtProtect->u32Magic == SUPDRVNTPROTECT_MAGIC); RTSpinlockAcquire(g_hNtProtectLock); uint32_t cRefs = ASMAtomicDecU32(&pNtProtect->cRefs); if (cRefs != 0) RTSpinlockRelease(g_hNtProtectLock); else { /* * That was the last reference. Remove it from the tree, invalidate it * and free the resources associated with it. Also, release any * child/parent references related to this protection structure. */ ASMAtomicWriteU32(&pNtProtect->u32Magic, SUPDRVNTPROTECT_MAGIC_DEAD); if (pNtProtect->fInTree) { PSUPDRVNTPROTECT pRemoved = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pNtProtect->AvlCore.Key); Assert(pRemoved == pNtProtect); RT_NOREF_PV(pRemoved); pNtProtect->fInTree = false; } PSUPDRVNTPROTECT pChild = NULL; if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent) { pChild = pNtProtect->u.pChild; if (pChild) { pNtProtect->u.pChild = NULL; pChild->u.pParent = NULL; pChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; uint32_t cChildRefs = ASMAtomicDecU32(&pChild->cRefs); if (!cChildRefs) { Assert(pChild->fInTree); if (pChild->fInTree) { PSUPDRVNTPROTECT pRemovedChild = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pChild->AvlCore.Key); Assert(pRemovedChild == pChild); RT_NOREF_PV(pRemovedChild); pChild->fInTree = false; } } else pChild = NULL; } } else AssertRelease(pNtProtect->enmProcessKind != kSupDrvNtProtectKind_VmProcessUnconfirmed); RTSpinlockRelease(g_hNtProtectLock); if (pNtProtect->pCsrssProcess) { ObDereferenceObject(pNtProtect->pCsrssProcess); pNtProtect->pCsrssProcess = NULL; } RTMemFree(pNtProtect); if (pChild) RTMemFree(pChild); } } /** * Looks up a PID in the NT protect tree. * * @returns Pointer to a NT protection structure (with a referenced) on success, * NULL if not found. * @param hPid The process ID. */ static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid) { RTSpinlockAcquire(g_hNtProtectLock); PSUPDRVNTPROTECT pFound = (PSUPDRVNTPROTECT)RTAvlPVGet(&g_NtProtectTree, hPid); if (pFound) ASMAtomicIncU32(&pFound->cRefs); RTSpinlockRelease(g_hNtProtectLock); return pFound; } /** * Validates a few facts about the stub process when the VM process opens * vboxdrv. * * This makes sure the stub process is still around and that it has neither * debugger nor extra threads in it. * * @returns VBox status code. * @param pNtProtect The unconfirmed VM process currently trying to * open vboxdrv. * @param pErrInfo Additional error information. */ static int supdrvNtProtectVerifyStubForVmProcess(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) { /* * Grab a reference to the parent stub process. */ SUPDRVNTPROTECTKIND enmStub = kSupDrvNtProtectKind_Invalid; PSUPDRVNTPROTECT pNtStub = NULL; RTSpinlockAcquire(g_hNtProtectLock); if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) { pNtStub = pNtProtect->u.pParent; /* weak reference. */ if (pNtStub) { enmStub = pNtStub->enmProcessKind; if (enmStub == kSupDrvNtProtectKind_StubParent) { uint32_t cRefs = ASMAtomicIncU32(&pNtStub->cRefs); Assert(cRefs > 0 && cRefs < 1024); RT_NOREF_PV(cRefs); } else pNtStub = NULL; } } RTSpinlockRelease(g_hNtProtectLock); /* * We require the stub process to be present. */ if (!pNtStub) return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, "Missing stub process (enmStub=%d).", enmStub); /* * Open the parent process and thread so we can check for debuggers and unwanted threads. */ int rc; PEPROCESS pStubProcess; NTSTATUS rcNt = PsLookupProcessByProcessId(pNtStub->AvlCore.Key, &pStubProcess); if (NT_SUCCESS(rcNt)) { HANDLE hStubProcess; rcNt = ObOpenObjectByPointer(pStubProcess, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, 0 /*DesiredAccess*/, *PsProcessType, KernelMode, &hStubProcess); if (NT_SUCCESS(rcNt)) { PETHREAD pStubThread; rcNt = PsLookupThreadByThreadId(pNtStub->hOpenTid, &pStubThread); if (NT_SUCCESS(rcNt)) { HANDLE hStubThread; rcNt = ObOpenObjectByPointer(pStubThread, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, 0 /*DesiredAccess*/, *PsThreadType, KernelMode, &hStubThread); if (NT_SUCCESS(rcNt)) { /* * Do some simple sanity checking. */ rc = supHardNtVpDebugger(hStubProcess, pErrInfo); if (RT_SUCCESS(rc)) rc = supHardNtVpThread(hStubProcess, hStubThread, pErrInfo); /* Clean up. */ rcNt = NtClose(hStubThread); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); } else rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_OPEN_ERROR, "Error opening stub thread %p (tid %p, pid %p): %#x", pStubThread, pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); } else rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_NOT_FOUND, "Failed to locate thread %p in %p: %#x", pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); rcNt = NtClose(hStubProcess); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); } else rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_OPEN_ERROR, "Error opening stub process %p (pid %p): %#x", pStubProcess, pNtStub->AvlCore.Key, rcNt); ObDereferenceObject(pStubProcess); } else rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, "Failed to locate stub process %p: %#x", pNtStub->AvlCore.Key, rcNt); supdrvNtProtectRelease(pNtStub); return rc; } static const char *supdrvNtProtectHandleTypeIndexToName(ULONG idxType, char *pszName, size_t cbName) { /* * Query the object types. */ uint32_t cbBuf = _8K; uint8_t *pbBuf = (uint8_t *)RTMemAllocZ(_8K); ULONG cbNeeded = cbBuf; NTSTATUS rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); while (rcNt == STATUS_INFO_LENGTH_MISMATCH) { cbBuf = RT_ALIGN_32(cbNeeded + 256, _64K); RTMemFree(pbBuf); pbBuf = (uint8_t *)RTMemAllocZ(cbBuf); if (pbBuf) rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); else break; } if (NT_SUCCESS(rcNt)) { Assert(cbNeeded <= cbBuf); POBJECT_TYPES_INFORMATION pObjTypes = (OBJECT_TYPES_INFORMATION *)pbBuf; POBJECT_TYPE_INFORMATION pCurType = &pObjTypes->FirstType; ULONG cLeft = pObjTypes->NumberOfTypes; while (cLeft-- > 0 && (uintptr_t)&pCurType[1] - (uintptr_t)pbBuf < cbNeeded) { if (pCurType->TypeIndex == idxType) { PCRTUTF16 const pwszSrc = pCurType->TypeName.Buffer; AssertBreak(pwszSrc); size_t idxName = pCurType->TypeName.Length / sizeof(RTUTF16); AssertBreak(idxName > 0); AssertBreak(idxName < 128); if (idxName >= cbName) idxName = cbName - 1; pszName[idxName] = '\0'; while (idxName-- > 0) pszName[idxName] = (char )pwszSrc[idxName]; RTMemFree(pbBuf); return pszName; } /* next */ pCurType = (POBJECT_TYPE_INFORMATION)( (uintptr_t)pCurType->TypeName.Buffer + RT_ALIGN_32(pCurType->TypeName.MaximumLength, sizeof(uintptr_t))); } } RTMemFree(pbBuf); return "unknown"; } /** * Worker for supdrvNtProtectVerifyProcess that verifies the handles to a VM * process and its thread. * * @returns VBox status code. * @param pNtProtect The NT protect structure for getting information * about special processes. * @param pErrInfo Where to return additional error details. */ static int supdrvNtProtectRestrictHandlesToProcessAndThread(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) { /* * What to protect. */ PEPROCESS pProtectedProcess = PsGetCurrentProcess(); HANDLE hProtectedPid = PsGetProcessId(pProtectedProcess); PETHREAD pProtectedThread = PsGetCurrentThread(); AssertReturn(pNtProtect->AvlCore.Key == hProtectedPid, VERR_INTERNAL_ERROR_5); /* * Take a snapshot of all the handles in the system. * Note! The 32 bytes on the size of to counteract the allocation header * that rtR0MemAllocEx slaps on everything. */ uint32_t cbBuf = _256K - 32; uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf); ULONG cbNeeded = cbBuf; NTSTATUS rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); if (!NT_SUCCESS(rcNt)) { while ( rcNt == STATUS_INFO_LENGTH_MISMATCH && cbNeeded > cbBuf && cbBuf <= 32U*_1M) { cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K) - 32; RTMemFree(pbBuf); pbBuf = (uint8_t *)RTMemAlloc(cbBuf); if (!pbBuf) return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Error allocating %zu bytes for querying handles.", cbBuf); rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); } if (!NT_SUCCESS(rcNt)) { RTMemFree(pbBuf); return RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), "NtQuerySystemInformation/SystemExtendedHandleInformation failed: %#x\n", rcNt); } } /* * Walk the information and look for handles to the two objects we're protecting. */ int rc = VINF_SUCCESS; # ifdef VBOX_WITHOUT_DEBUGGER_CHECKS HANDLE idLastDebugger = (HANDLE)~(uintptr_t)0; # endif uint32_t cCsrssProcessHandles = 0; uint32_t cSystemProcessHandles = 0; uint32_t cEvilProcessHandles = 0; uint32_t cBenignProcessHandles = 0; uint32_t cCsrssThreadHandles = 0; uint32_t cEvilThreadHandles = 0; uint32_t cBenignThreadHandles = 0; uint32_t cEvilInheritableHandles = 0; uint32_t cBenignInheritableHandles = 0; char szTmpName[32]; SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; ULONG_PTR i = pInfo->NumberOfHandles; AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded); while (i-- > 0) { const char *pszType; SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; if (pHandleInfo->Object == pProtectedProcess) { /* Handles within the protected process are fine. */ if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_PROCESS_RIGHTS) || pHandleInfo->UniqueProcessId == hProtectedPid) { cBenignProcessHandles++; continue; } /* CSRSS is allowed to have one evil process handle. See the special cases in the hook code. */ if ( cCsrssProcessHandles < 1 && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) { cCsrssProcessHandles++; continue; } /* The system process is allowed having two open process handle in Windows 8.1 and later, and one in earlier. This is probably a little overly paranoid as I think we can safely trust the system process... */ if ( cSystemProcessHandles < (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) ? UINT32_C(2) : UINT32_C(1)) && pHandleInfo->UniqueProcessId == PsGetProcessId(PsInitialSystemProcess)) { cSystemProcessHandles++; continue; } cEvilProcessHandles++; pszType = "process"; } else if (pHandleInfo->Object == pProtectedThread) { /* Handles within the protected process is fine. */ if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_THREAD_RIGHTS) || pHandleInfo->UniqueProcessId == hProtectedPid) { cBenignThreadHandles++; continue; } /* CSRSS is allowed to have one evil handle to the primary thread for LPC purposes. See the hook for special case. */ if ( cCsrssThreadHandles < 1 && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) { cCsrssThreadHandles++; continue; } cEvilThreadHandles++; pszType = "thread"; } else if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) && pHandleInfo->UniqueProcessId == hProtectedPid) { /* No handles should be marked inheritable, except files and two events. Handles to NT 'directory' objects are especially evil, because of KnownDlls faking. See bugref{10294} for details. Correlating the ObjectTypeIndex to a type is complicated, so instead we try referecing the handle and check the type that way. So, only file and events objects are allowed to be marked inheritable at the moment. Add more in whitelist fashion if needed. */ void *pvObject = NULL; rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *IoFileObjectType, KernelMode, &pvObject, NULL); if (rcNt == STATUS_OBJECT_TYPE_MISMATCH) rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *ExEventObjectType, KernelMode, &pvObject, NULL); if (NT_SUCCESS(rcNt)) { ObDereferenceObject(pvObject); cBenignInheritableHandles++; continue; } if (rcNt != STATUS_OBJECT_TYPE_MISMATCH) { cBenignInheritableHandles++; continue; } cEvilInheritableHandles++; pszType = supdrvNtProtectHandleTypeIndexToName(pHandleInfo->ObjectTypeIndex, szTmpName, sizeof(szTmpName)); } else continue; # ifdef VBOX_WITHOUT_DEBUGGER_CHECKS /* Ignore whitelisted debuggers. */ if (pHandleInfo->UniqueProcessId == idLastDebugger) continue; PEPROCESS pDbgProc; rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pDbgProc); if (NT_SUCCESS(rcNt)) { bool fIsDebugger = supdrvNtProtectIsWhitelistedDebugger(pDbgProc); ObDereferenceObject(pDbgProc); if (fIsDebugger) { idLastDebugger = pHandleInfo->UniqueProcessId; continue; } } # endif /* Found evil handle. Currently ignoring on pre-Vista. */ # ifndef VBOX_WITH_VISTA_NO_SP if ( g_uNtVerCombined >= SUP_NT_VER_VISTA # else if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0) # endif || g_pfnObRegisterCallbacks) { LogRel(("vboxdrv: Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)\n", pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex)); rc = RTErrInfoAddF(pErrInfo, VERR_SUPDRV_HARDENING_EVIL_HANDLE, *pErrInfo->pszMsg ? "\nFound evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)" : "Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)", pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex); /* Try add the process name. */ PEPROCESS pOffendingProcess; rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pOffendingProcess); if (NT_SUCCESS(rcNt)) { const char *pszName = (const char *)PsGetProcessImageFileName(pOffendingProcess); if (pszName && *pszName) rc = RTErrInfoAddF(pErrInfo, rc, " [%s]", pszName); ObDereferenceObject(pOffendingProcess); } } } RTMemFree(pbBuf); return rc; } /** * Checks if the current process checks out as a VM process stub. * * @returns VBox status code. * @param pNtProtect The NT protect structure. This is upgraded to a * final protection kind (state) on success. */ static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect) { AssertReturn(PsGetProcessId(PsGetCurrentProcess()) == pNtProtect->AvlCore.Key, VERR_INTERNAL_ERROR_3); /* * Do the verification. The handle restriction checks are only preformed * on VM processes. */ int rc = VINF_SUCCESS; PSUPDRVNTERRORINFO pErrorInfo = (PSUPDRVNTERRORINFO)RTMemAllocZ(sizeof(*pErrorInfo)); if (RT_SUCCESS(rc)) { pErrorInfo->hProcessId = PsGetCurrentProcessId(); pErrorInfo->hThreadId = PsGetCurrentThreadId(); RTERRINFO ErrInfo; RTErrInfoInit(&ErrInfo, pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo)); if (pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) rc = supdrvNtProtectRestrictHandlesToProcessAndThread(pNtProtect, &ErrInfo); if (RT_SUCCESS(rc)) { rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_VERIFY_ONLY, 0 /*fFlags*/, NULL /*pcFixes*/, &ErrInfo); if (RT_SUCCESS(rc) && pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) rc = supdrvNtProtectVerifyStubForVmProcess(pNtProtect, &ErrInfo); } } else rc = VERR_NO_MEMORY; /* * Upgrade and return. */ HANDLE hOpenTid = PsGetCurrentThreadId(); RTSpinlockAcquire(g_hNtProtectLock); /* Stub process verficiation is pretty much straight forward. */ if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) { pNtProtect->enmProcessKind = RT_SUCCESS(rc) ? kSupDrvNtProtectKind_StubSpawning : kSupDrvNtProtectKind_StubDead; pNtProtect->hOpenTid = hOpenTid; } /* The VM process verification is a little bit more complicated because we need to drop the parent process reference as well. */ else if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) { AssertRelease(pNtProtect->cRefs >= 2); /* Parent + Caller */ PSUPDRVNTPROTECT pParent = pNtProtect->u.pParent; AssertRelease(pParent); AssertRelease(pParent->u.pParent == pNtProtect); AssertRelease(pParent->enmProcessKind == kSupDrvNtProtectKind_StubParent); pParent->u.pParent = NULL; pNtProtect->u.pParent = NULL; ASMAtomicDecU32(&pNtProtect->cRefs); if (RT_SUCCESS(rc)) { pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessConfirmed; pNtProtect->hOpenTid = hOpenTid; } else pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; } /* Since the stub and VM processes are only supposed to have one thread, we're not supposed to be subject to any races from within the processes. There is a race between VM process verification and the stub process exiting, though. We require the stub process to be alive until the new VM process has made it thru the validation. So, when the stub terminates the notification handler will change the state of both stub and VM process to dead. Also, I'm not entirely certain where the process termination notification is triggered from, so that can theorically create a race in both cases. */ else { AssertReleaseMsg( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubDead || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessDead, ("enmProcessKind=%d rc=%Rrc\n", pNtProtect->enmProcessKind, rc)); if (RT_SUCCESS(rc)) rc = VERR_INVALID_STATE; /* There should be no races here. */ } RTSpinlockRelease(g_hNtProtectLock); /* * Free error info on success, keep it on failure. */ if (RT_SUCCESS(rc)) RTMemFree(pErrorInfo); else if (pErrorInfo) { pErrorInfo->cchErrorInfo = (uint32_t)strlen(pErrorInfo->szErrorInfo); if (!pErrorInfo->cchErrorInfo) pErrorInfo->cchErrorInfo = (uint32_t)RTStrPrintf(pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo), "supdrvNtProtectVerifyProcess: rc=%d", rc); RTLogWriteDebugger(pErrorInfo->szErrorInfo, pErrorInfo->cchErrorInfo); int rc2 = RTSemMutexRequest(g_hErrorInfoLock, RT_INDEFINITE_WAIT); if (RT_SUCCESS(rc2)) { pErrorInfo->uCreatedMsTs = RTTimeMilliTS(); /* Free old entries. */ PSUPDRVNTERRORINFO pCur; while ( (pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL && (int64_t)(pErrorInfo->uCreatedMsTs - pCur->uCreatedMsTs) > 60000 /*60sec*/) { RTListNodeRemove(&pCur->ListEntry); RTMemFree(pCur); } /* Insert our new entry. */ RTListAppend(&g_ErrorInfoHead, &pErrorInfo->ListEntry); RTSemMutexRelease(g_hErrorInfoLock); } else RTMemFree(pErrorInfo); } return rc; } # ifndef VBOX_WITHOUT_DEBUGGER_CHECKS /** * Checks if the current process is being debugged. * @return @c true if debugged, @c false if not. */ static bool supdrvNtIsDebuggerAttached(void) { return PsIsProcessBeingDebugged(PsGetCurrentProcess()) != FALSE; } # endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ /** * Terminates the hardening bits. */ static void supdrvNtProtectTerm(void) { /* * Stop intercepting process and thread handle creation calls. */ if (g_pvObCallbacksCookie) { g_pfnObUnRegisterCallbacks(g_pvObCallbacksCookie); g_pvObCallbacksCookie = NULL; } /* * Stop intercepting process creation and termination notifications. */ NTSTATUS rcNt; if (g_pfnPsSetCreateProcessNotifyRoutineEx) rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); else rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); AssertMsg(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); Assert(g_NtProtectTree == NULL); /* * Clean up globals. */ RTSpinlockDestroy(g_hNtProtectLock); g_NtProtectTree = NIL_RTSPINLOCK; RTSemMutexDestroy(g_hErrorInfoLock); g_hErrorInfoLock = NIL_RTSEMMUTEX; PSUPDRVNTERRORINFO pCur; while ((pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL) { RTListNodeRemove(&pCur->ListEntry); RTMemFree(pCur); } supHardenedWinTermImageVerifier(); } # ifdef RT_ARCH_X86 DECLASM(void) supdrvNtQueryVirtualMemory_0xAF(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB0(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB1(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB2(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB3(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB4(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB5(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB6(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB7(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB8(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xB9(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xBA(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xBB(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xBC(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xBD(void); DECLASM(void) supdrvNtQueryVirtualMemory_0xBE(void); # elif defined(RT_ARCH_AMD64) DECLASM(void) supdrvNtQueryVirtualMemory_0x1F(void); DECLASM(void) supdrvNtQueryVirtualMemory_0x20(void); DECLASM(void) supdrvNtQueryVirtualMemory_0x21(void); DECLASM(void) supdrvNtQueryVirtualMemory_0x22(void); DECLASM(void) supdrvNtQueryVirtualMemory_0x23(void); extern "C" NTSYSAPI NTSTATUS NTAPI ZwRequestWaitReplyPort(HANDLE, PVOID, PVOID); # endif /** * Initalizes the hardening bits. * * @returns NT status code. */ static NTSTATUS supdrvNtProtectInit(void) { /* * Initialize the globals. */ /* The NT version. */ ULONG uMajor, uMinor, uBuild; PsGetVersion(&uMajor, &uMinor, &uBuild, NULL); g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(uMajor, uMinor, uBuild, 0, 0); /* Resolve methods we want but isn't available everywhere. */ UNICODE_STRING RoutineName; RtlInitUnicodeString(&RoutineName, L"ObGetObjectType"); g_pfnObGetObjectType = (PFNOBGETOBJECTTYPE)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"ObRegisterCallbacks"); g_pfnObRegisterCallbacks = (PFNOBREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"ObUnRegisterCallbacks"); g_pfnObUnRegisterCallbacks = (PFNOBUNREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"PsSetCreateProcessNotifyRoutineEx"); g_pfnPsSetCreateProcessNotifyRoutineEx = (PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"PsReferenceProcessFilePointer"); g_pfnPsReferenceProcessFilePointer = (PFNPSREFERENCEPROCESSFILEPOINTER)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"PsIsProtectedProcessLight"); g_pfnPsIsProtectedProcessLight = (PFNPSISPROTECTEDPROCESSLIGHT)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"ZwAlpcCreatePort"); g_pfnZwAlpcCreatePort = (PFNZWALPCCREATEPORT)MmGetSystemRoutineAddress(&RoutineName); RtlInitUnicodeString(&RoutineName, L"ZwQueryVirtualMemory"); /* Yes, using Zw version here. */ g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)MmGetSystemRoutineAddress(&RoutineName); if (!g_pfnNtQueryVirtualMemory && g_uNtVerCombined < SUP_NT_VER_VISTA) { /* XP & W2K3 doesn't have this function exported, so we've cooked up a few alternative in the assembly helper file that uses the code in ZwReadFile with a different eax value. We figure the syscall number by inspecting ZwQueryVolumeInformationFile as it's the next number. */ # ifdef RT_ARCH_X86 uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwQueryVolumeInformationFile; if (*pbCode == 0xb8) /* mov eax, dword */ switch (*(uint32_t const *)&pbCode[1]) { case 0xb0: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xAF; break; /* just in case */ case 0xb1: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB0; break; /* just in case */ case 0xb2: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB1; break; /* just in case */ case 0xb3: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* XP SP3 */ case 0xb4: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* just in case */ case 0xb5: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB3; break; /* just in case */ case 0xb6: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB4; break; /* just in case */ case 0xb7: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB5; break; /* just in case */ case 0xb8: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB6; break; /* just in case */ case 0xb9: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB7; break; /* just in case */ case 0xba: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB8; break; /* just in case */ case 0xbb: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBA; break; /* W2K3 R2 SP2 */ case 0xbc: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBB; break; /* just in case */ case 0xbd: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBC; break; /* just in case */ case 0xbe: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBD; break; /* just in case */ case 0xbf: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBE; break; /* just in case */ } # elif defined(RT_ARCH_AMD64) uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwRequestWaitReplyPort; if ( pbCode[ 0] == 0x48 /* mov rax, rsp */ && pbCode[ 1] == 0x8b && pbCode[ 2] == 0xc4 && pbCode[ 3] == 0xfa /* cli */ && pbCode[ 4] == 0x48 /* sub rsp, 10h */ && pbCode[ 5] == 0x83 && pbCode[ 6] == 0xec && pbCode[ 7] == 0x10 && pbCode[ 8] == 0x50 /* push rax */ && pbCode[ 9] == 0x9c /* pushfq */ && pbCode[10] == 0x6a /* push 10 */ && pbCode[11] == 0x10 && pbCode[12] == 0x48 /* lea rax, [nt!KiServiceLinkage] */ && pbCode[13] == 0x8d && pbCode[14] == 0x05 && pbCode[19] == 0x50 /* push rax */ && pbCode[20] == 0xb8 /* mov eax,1fh <- the syscall no. */ /*&& pbCode[21] == 0x1f*/ && pbCode[22] == 0x00 && pbCode[23] == 0x00 && pbCode[24] == 0x00 && pbCode[25] == 0xe9 /* jmp KiServiceInternal */ ) { uint8_t const *pbKiServiceInternal = &pbCode[30] + *(int32_t const *)&pbCode[26]; uint8_t const *pbKiServiceLinkage = &pbCode[19] + *(int32_t const *)&pbCode[15]; if (*pbKiServiceLinkage == 0xc3) { g_pfnKiServiceInternal = (PFNRT)pbKiServiceInternal; g_pfnKiServiceLinkage = (PFNRT)pbKiServiceLinkage; switch (pbCode[21]) { case 0x1e: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x1F; break; case 0x1f: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x20; break; case 0x20: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x21; break; case 0x21: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x22; break; case 0x22: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x23; break; } } } # endif } if (!g_pfnNtQueryVirtualMemory) { LogRel(("vboxdrv: Cannot locate ZwQueryVirtualMemory in ntoskrnl, nor were we able to cook up a replacement.\n")); return STATUS_PROCEDURE_NOT_FOUND; } # ifdef VBOX_STRICT if ( g_uNtVerCombined >= SUP_NT_VER_W70 && ( g_pfnObGetObjectType == NULL || g_pfnZwAlpcCreatePort == NULL) ) { LogRel(("vboxdrv: g_pfnObGetObjectType=%p g_pfnZwAlpcCreatePort=%p.\n", g_pfnObGetObjectType, g_pfnZwAlpcCreatePort)); return STATUS_PROCEDURE_NOT_FOUND; } # endif /* LPC object type. */ g_pAlpcPortObjectType1 = *LpcPortObjectType; /* The spinlock protecting our structures. */ int rc = RTSpinlockCreate(&g_hNtProtectLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtProtectLock"); if (RT_FAILURE(rc)) return VBoxDrvNtErr2NtStatus(rc); g_NtProtectTree = NULL; NTSTATUS rcNt; /* The mutex protecting the error information. */ RTListInit(&g_ErrorInfoHead); rc = RTSemMutexCreate(&g_hErrorInfoLock); if (RT_SUCCESS(rc)) { /* Image stuff + certificates. */ rc = supHardenedWinInitImageVerifier(NULL); if (RT_SUCCESS(rc)) { /* * Intercept process creation and termination. */ if (g_pfnPsSetCreateProcessNotifyRoutineEx) rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, FALSE /*fRemove*/); else rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, FALSE /*fRemove*/); if (NT_SUCCESS(rcNt)) { /* * Intercept process and thread handle creation calls. * The preferred method is only available on Vista SP1+. */ if (g_pfnObRegisterCallbacks && g_pfnObUnRegisterCallbacks) { static OB_OPERATION_REGISTRATION s_aObOperations[] = { { 0, /* PsProcessType - imported, need runtime init, better do it explicitly. */ OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, supdrvNtProtectCallback_ProcessHandlePre, supdrvNtProtectCallback_ProcessHandlePost, }, { 0, /* PsThreadType - imported, need runtime init, better do it explicitly. */ OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, supdrvNtProtectCallback_ThreadHandlePre, supdrvNtProtectCallback_ThreadHandlePost, }, }; s_aObOperations[0].ObjectType = PsProcessType; s_aObOperations[1].ObjectType = PsThreadType; static OB_CALLBACK_REGISTRATION s_ObCallbackReg = { /* .Version = */ OB_FLT_REGISTRATION_VERSION, /* .OperationRegistrationCount = */ RT_ELEMENTS(s_aObOperations), /* .Altitude.Length = */ { 0, /* .Altitude.MaximumLength = */ 0, /* .Altitude.Buffer = */ NULL }, /* .RegistrationContext = */ NULL, /* .OperationRegistration = */ &s_aObOperations[0] }; static WCHAR const *s_apwszAltitudes[] = /** @todo get a valid number */ { L"48596.98940", L"46935.19485", L"49739.39704", L"40334.74976", L"66667.98940", L"69888.19485", L"69889.39704", L"60364.74976", L"85780.98940", L"88978.19485", L"89939.39704", L"80320.74976", L"329879.98940", L"326787.19485", L"328915.39704", L"320314.74976", }; rcNt = STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszAltitudes) && rcNt == STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; i++) { s_ObCallbackReg.Altitude.Buffer = (WCHAR *)s_apwszAltitudes[i]; s_ObCallbackReg.Altitude.Length = (uint16_t)RTUtf16Len(s_apwszAltitudes[i]) * sizeof(WCHAR); s_ObCallbackReg.Altitude.MaximumLength = s_ObCallbackReg.Altitude.Length + sizeof(WCHAR); rcNt = g_pfnObRegisterCallbacks(&s_ObCallbackReg, &g_pvObCallbacksCookie); if (NT_SUCCESS(rcNt)) { /* * Happy ending. */ return STATUS_SUCCESS; } } LogRel(("vboxdrv: ObRegisterCallbacks failed with rcNt=%#x\n", rcNt)); g_pvObCallbacksCookie = NULL; } else { /* * For the time being, we do not implement extra process * protection on pre-Vista-SP1 systems as they are lacking * necessary KPIs. XP is end of life, we do not wish to * spend more time on it, so we don't put up a fuss there. * Vista users without SP1 can install SP1 (or later), darn it, * so refuse to load. */ /** @todo Hack up an XP solution - will require hooking kernel APIs or doing bad * stuff to a couple of object types. */ # ifndef VBOX_WITH_VISTA_NO_SP if (g_uNtVerCombined >= SUP_NT_VER_VISTA) # else if (g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) # endif { DbgPrint("vboxdrv: ObRegisterCallbacks was not found. Please make sure you got the latest updates and service packs installed\n"); rcNt = STATUS_SXS_VERSION_CONFLICT; } else { Log(("vboxdrv: ObRegisterCallbacks was not found; ignored pre-Vista\n")); return rcNt = STATUS_SUCCESS; } g_pvObCallbacksCookie = NULL; } /* * Drop process create/term notifications. */ if (g_pfnPsSetCreateProcessNotifyRoutineEx) g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); else PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); } else LogRel(("vboxdrv: PsSetCreateProcessNotifyRoutine%s failed with rcNt=%#x\n", g_pfnPsSetCreateProcessNotifyRoutineEx ? "Ex" : "", rcNt)); supHardenedWinTermImageVerifier(); } else rcNt = VBoxDrvNtErr2NtStatus(rc); RTSemMutexDestroy(g_hErrorInfoLock); g_hErrorInfoLock = NIL_RTSEMMUTEX; } else rcNt = VBoxDrvNtErr2NtStatus(rc); RTSpinlockDestroy(g_hNtProtectLock); g_NtProtectTree = NIL_RTSPINLOCK; return rcNt; } #endif /* VBOX_WITH_HARDENING */