diff options
Diffstat (limited to 'src/VBox/HostDrivers/Support')
40 files changed, 9731 insertions, 597 deletions
diff --git a/src/VBox/HostDrivers/Support/Makefile.kmk b/src/VBox/HostDrivers/Support/Makefile.kmk index c336e51bfcb..0486cbbce19 100644 --- a/src/VBox/HostDrivers/Support/Makefile.kmk +++ b/src/VBox/HostDrivers/Support/Makefile.kmk @@ -30,7 +30,11 @@ include $(KBUILD_PATH)/subheader.kmk # # Targets # -LIBRARIES += SUPR3 SUPR3Static SUPR3HardenedStatic +LIBRARIES += SUPR3 SUPR3Static +ifdef VBOX_WITH_HARDENING + LIBRARIES += SUPR3HardenedStatic +endif +DLLS.win += VBoxSupLib ifdef VBOX_WITH_32_ON_64_MAIN_API LIBRARIES += SUPR3-x86 endif @@ -69,11 +73,70 @@ if !defined(VBOX_ONLY_DOCS) \ endif endif # !VBOX_ONLY_DOCS && !VBOX_ONLY_EXTPACKS && !VBOX_ONLY_VALIDATIONKIT +# +# Authenticode related trust anchors and certificates -> .cpp +# +VBOX_SUP_WIN_CERTS_FILE = $(SUPR3_0_OUTDIR)/TrustAnchorsAndCerts.cpp +VBOX_SUP_WIN_CERTS := \ + SpcRootMicrosoft0=SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf \ + SpcRootMicrosoft1=SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf \ + SpcRootMicrosoft2=SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf \ + SpcRootMicrosoft3=SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf \ + SpcRootMicrosoft4=SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf \ + SpcRootMicrosoft5=SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf \ + NtRootMicrosoft6=NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf \ + TimeRootMicrosoft0=Timestamp-CopyrightC1997MicrosoftCorp-01.taf \ + TrustedCertVBox0=Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.taf +VBOX_SUP_WIN_CERT_NAMES := $(foreach cert,$(VBOX_SUP_WIN_CERTS),$(firstword $(subst =,$(SPACE) ,$(cert)))) +VBOX_PATH_SUPR3_CERTIFICATES := $(PATH_SUB_CURRENT)/win/Certificates + +# 1=name, 2=filter, 3=buildcert?. +if "$(KBUILD_TARGET)" == "win" && defined(VBOX_WITH_HARDENING) + VBOX_SUP_GEN_CERT_MACRO = 'SUPTAENTRY const g_aSUP$(1)TAs[] =' '{' \ + $(if-expr "$(3)" == "",,' SUPTAENTRY_GEN(g_abSUPBuildCert),') \ + $(foreach certnm,$(filter $(2),$(VBOX_SUP_WIN_CERT_NAMES)), ' SUPTAENTRY_GEN(g_abSUP$(certnm)),') \ + '};' 'unsigned const g_cSUP$(1)TAs = RT_ELEMENTS(g_aSUP$(1)TAs);' '' '' +else + VBOX_SUP_GEN_CERT_MACRO = 'SUPTAENTRY const g_aSUP$(1)TAs[] =' '{' \ + $(foreach certnm,$(filter $(2),$(VBOX_SUP_WIN_CERT_NAMES)), ' SUPTAENTRY_GEN(g_abSUP$(certnm)),') \ + '};' 'unsigned const g_cSUP$(1)TAs = RT_ELEMENTS(g_aSUP$(1)TAs);' '' '' +endif + +$$(VBOX_SUP_WIN_CERTS_FILE): $(MAKEFILE_CURRENT) \ + $(foreach cert,$(VBOX_SUP_WIN_CERTS),$(VBOX_PATH_SUPR3_CERTIFICATES)/$(lastword $(subst =,$(SPACE) ,$(cert)))) \ + $(VBOX_BIN2C) \ + $(if-expr "$(KBUILD_TARGET)" == "win" && defined(VBOX_WITH_HARDENING),$(VBOX_RTSIGNTOOL)) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ $@.cer + $(QUIET)$(APPEND) -n "$@" \ + '' \ + '#include <VBox/sup.h>' \ + '' + $(foreach cert,$(VBOX_SUP_WIN_CERTS), $(NLTAB)$(VBOX_BIN2C) -ascii --append \ + "SUP$(firstword $(subst =,$(SP) ,$(cert)))" \ + "$(VBOX_PATH_SUPR3_CERTIFICATES)/$(lastword $(subst =,$(SP) ,$(cert)))" \ + "$@") +# The build certificate. +if "$(KBUILD_TARGET)" == "win" && defined(VBOX_WITH_HARDENING) + $(VBOX_RTSIGNTOOL) extract-exe-signer-cert --exe $(VBOX_RTSIGNTOOL) --output "$@.cer" --der + $(VBOX_BIN2C) -ascii --append SUPBuildCert "$@.cer" $@ + $(QUIET)$(RM) -f -- $@.cer +endif +# Generate certificate lists. + $(QUIET)$(APPEND) -n "$@" '' \ + $(call VBOX_SUP_GEN_CERT_MACRO,All,%,build) \ + $(call VBOX_SUP_GEN_CERT_MACRO,SpcRoot,SpcRoot%) \ + $(call VBOX_SUP_GEN_CERT_MACRO,NtKernelRoot,NtRoot%) \ + $(call VBOX_SUP_GEN_CERT_MACRO,Timestamp,TimeRoot%) \ + $(call VBOX_SUP_GEN_CERT_MACRO,Trusted,TrustedCert%,build) + +tst: $(VBOX_SUP_WIN_CERTS_FILE) + # # The Ring-3 Support Library (this is linked into the IPRT dll, VBoxRT). # -SUPR3_TEMPLATE = VBOXR3NP +SUPR3_TEMPLATE = VBOXR3NP SUPR3_DEFS = \ IN_SUP_R3 IN_RT_R3 \ $(if $(VBOX_WITH_SUPSVC),VBOX_WITH_SUPSVC) \ @@ -87,32 +150,216 @@ SUPR3_SOURCES = \ SUPLibTracerA.asm \ SUPR3HardenedIPRT.cpp \ SUPR3HardenedVerify.cpp \ - $(KBUILD_TARGET)/SUPLib-$(KBUILD_TARGET).cpp + $(KBUILD_TARGET)/SUPLib-$(KBUILD_TARGET).cpp \ + $(VBOX_SUP_WIN_CERTS_FILE) +ifdef VBOX_WITH_HARDENING + SUPR3_SOURCES.win = \ + win/SUPHardenedVerifyImage-win.cpp +endif SUPR3-x86_TEMPLATE = VBoxR3Dll-x86 SUPR3-x86_EXTENDS = SUPR3 # -# Static version of SUPR3. +# Static version of SUPR3. This is more of a stub than anything else in a +# hardened build, at least on windows. # SUPR3Static_TEMPLATE = VBOXR3STATIC SUPR3Static_EXTENDS = SUPR3 +SUPR3Static_DEFS = $(SUPR3_DEFS) IN_SUP_R3_STATIC +SUPR3Static_SOURCES.win = $(filter-out win/SUPHardenedVerifyImage-win.cpp, $(SUPR3_SOURCES.win)) + # # The static part of the hardened support library (ring-3). # +VBOX_PATH_RUNTIME_SRC ?= $(PATH_ROOT)/src/VBox/Runtime SUPR3HardenedStatic_TEMPLATE = VBOXR3HARDENEDLIB SUPR3HardenedStatic_DEFS = IN_SUP_HARDENED_R3 SUPR3HardenedStatic_DEFS += \ $(if $(VBOX_WITH_SUPSVC),VBOX_WITH_SUPSVC,) \ $(if $(VBOX_WITH_MAIN),VBOX_WITH_MAIN,) \ - $(if $(VBOX_WITH_RAW_MODE),VBOX_WITH_RAW_MODE,) + $(if $(VBOX_WITH_RAW_MODE),VBOX_WITH_RAW_MODE,) \ + $(if $(VBOX_WITHOUT_DEBUGGER_CHECKS),VBOX_WITHOUT_DEBUGGER_CHECKS,) +ifdef VBOX_WITH_VISTA_NO_SP + SUPR3HardenedStatic_DEFS.win += VBOX_WITH_VISTA_NO_SP +endif SUPR3HardenedStatic_INCS = . SUPR3HardenedStatic_SOURCES = \ SUPR3HardenedMain.cpp \ SUPR3HardenedVerify.cpp \ + SUPR3HardenedNoCrt.cpp \ $(KBUILD_TARGET)/SUPLib-$(KBUILD_TARGET).cpp +SUPR3HardenedStatic_SOURCES.win = \ + win/SUPR3HardenedMain-win.cpp \ + win/SUPR3HardenedMainA-win.asm \ + win/SUPHardenedVerifyProcess-win.cpp \ + win/SUPHardenedVerifyImage-win.cpp \ + $(VBOX_SUP_WIN_CERTS_FILE) + +if1of ($(KBUILD_TARGET),win) ## @todo some of this move up. + SUPR3HardenedStatic_SDKS += VBoxOpenSslHardened + SUPR3HardenedStatic_DEFS += IN_RT IN_RT_R3 IN_RT_STATIC IPRT_NO_CRT RT_WITH_NOCRT_ALIASES LOG_DISABLED IPRT_NO_ERROR_DATA + SUPR3HardenedStatic_DEFS.win += LDR_ONLY_PE __STRALIGN_H_ + + SUPR3HardenedStatic_INCS += $(PATH_ROOT)/include/iprt/nocrt $(VBOX_PATH_RUNTIME_SRC)/include + + SUPR3HardenedStatic_SOURCES += \ + $(VBOX_PATH_RUNTIME_SRC)/common/ldr/ldr.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/ldr/ldrEx.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/ldr/ldrPE.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-basics.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-cursor.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-default-allocator.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-dump.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-encode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-bitstring.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-bitstring-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-boolean.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-boolean-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-core-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-dyntype.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-dyntype-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-integer.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-integer-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-null.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-null-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-objid.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-objid-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-octetstring.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-octetstring-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-string.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-string-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-time.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/asn1/asn1-ut-time-decode.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/digest-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/digest-builtin.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkcs7-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkcs7-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkcs7-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkcs7-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkcs7-verify.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkix-signature-builtin.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkix-signature-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkix-signature-rsa.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkix-verify.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/pkix-util.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/rsa-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/rsa-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/rsa-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/rsa-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/spc-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/spc-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/spc-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/spc-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-certpaths.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/x509-verify.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/store.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/store-inmem.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/taf-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/taf-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/taf-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/taf-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/md2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/md5.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha1.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha256.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha512.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/md2str.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/md5str.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha1str.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha256str.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/sha512str.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/err/errinfo.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathChangeToUnixSlashes.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathExt.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrPrintHexBytes.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16PrintHexBytes.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16ICmpAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strstrip.cpp \ + \ + $(VBOX_PATH_RUNTIME_SRC)/common/err/errmsg.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/math/bignum.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg1Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2WeakV.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/memchr.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/memcmp.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/memcpy.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/memmove.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/mempcpy.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/memset.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrCat.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrCmp.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrCopy.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrNCmp.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrNLen.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strchr.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strcmp.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strcpy.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformat.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformatrt.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformattype.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/stringalloc.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strlen.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strncmp.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strncpy.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strprintf.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strtonum.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/utf-16.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/utf-8.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/utf-8-case.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/unidata-upper.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/unidata-lower.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/time/time.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/RTAssertShouldPanic-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/memsafer-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/uuid-generic.cpp \ + + + SUPR3HardenedStatic_SOURCES.win += \ + win/SUPR3HardenedNoCrt-win.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/nt/RTErrConvertFromNtStatus.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/win/RTErrConvertFromWin32.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/win/errmsgwin.cpp + + # Add necessary compiler specific files from libcmt.lib and the lib dir. + ifeq ($(KBUILD_TARGET),win) + SUPR3HardenedStatic_VBOX_LIBC_OBJS = chkstk.obj + ifeq ($(KBUILD_TARGET_ARCH),x86) + SUPR3HardenedStatic_VBOX_LIBC_OBJS += alloca16.obj + endif + SUPR3HardenedStatic_SOURCES.win += $(addprefix $(SUPR3HardenedStatic_0_OUTDIR)/,$(SUPR3HardenedStatic_VBOX_LIBC_OBJS)) + + $(addprefix $$(SUPR3HardenedStatic_0_OUTDIR)/,$(SUPR3HardenedStatic_VBOX_LIBC_OBJS)): \ + $$(PATH_TOOL_$(TEMPLATE_VBOXR3EXE_TOOL.win.$(KBUILD_TARGET_ARCH))_LIB)/libcmt.lib | $$(dir $$@) + $(TOOL_$(TEMPLATE_VBOXR3HARDENEDEXE_TOOL.win.$(KBUILD_TARGET_ARCH))_AR) "$<" \ + /EXTRACT:`$(TOOL_$(TEMPLATE_VBOXR3HARDENEDEXE_TOOL.win.$(KBUILD_TARGET_ARCH))_AR) "$<" /LIST \ + | $(SED_EXT) -n -e '/$(notdir $@)/p'` \ + "/OUT:$@" + endif +endif + + +# +# VBoxSupLib - Windows DLL for catching thread creation and termination. +# +VBoxSupLib_TEMPLATE = $(if "$(KBUILD_TARGET)" == "win",VBOXR3HARDENEDLIB,VBOXR3) +VBoxSupLib_LDFLAGS.win.amd64 = -Entry:DllMainEntrypoint +VBoxSupLib_LDFLAGS.win.x86 = -Entry:DllMainEntrypoint +VBoxSupLib_DEFS = \ + $(if $(VBOX_WITHOUT_DEBUGGER_CHECKS),VBOX_WITHOUT_DEBUGGER_CHECKS,) +VBoxSupLib_SOURCES = \ + $(KBUILD_TARGET)/VBoxSupLib-$(KBUILD_TARGET).cpp +VBoxSupLib_LIBS.win = \ + $(SUPR3HardenedStatic_1_INS_TARGET) + # # VBoxSupSvc - The system wide service/daemon. @@ -262,11 +509,17 @@ if1of ($(KBUILD_TARGET), darwin linux solaris win) VBoxDrv_INST.darwin = $(INST_VBOXDRV)Contents/MacOS/ VBoxDrv_DEBUG_INST.darwin= $(patsubst %/,%,$(INST_VBOXDRV)) VBoxDrv_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK)INCS + ifdef VBOX_WITH_HARDENING + VBoxDrv_SDKS.win += VBoxOpenSslHardened + endif VBoxDrv_DEFS := IN_RT_R0 IN_SUP_R0 SUPDRV_WITH_RELEASE_LOGGER VBOX_SVN_REV=$(VBOX_SVN_REV) ifdef VBOX_WITH_DTRACE_R0DRV VBoxDrv_DEFS += VBOX_WITH_DTRACE VBOX_WITH_DTRACE_R0DRV endif + ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + VBoxDrv_DEFS += VBOX_WITHOUT_DEBUGGER_CHECKS + endif #VBoxDrv_DEFS.debug += DEBUG_DARWIN_GIP VBoxDrv_DEFS.darwin := VBOX_WITH_HOST_VMX VBoxDrv_DEFS.linux := \ @@ -283,6 +536,9 @@ if1of ($(KBUILD_TARGET), darwin linux solaris win) ifdef VBOX_WITHOUT_NATIVE_R0_LOADER VBoxDrv_DEFS.win += VBOX_WITHOUT_NATIVE_R0_LOADER endif + ifdef VBOX_WITH_VISTA_NO_SP + VBoxDrv_DEFS.win += VBOX_WITH_VISTA_NO_SP + endif VBoxDrv_INCS = . $(VBoxDrv_0_OUTDIR) VBoxDrv_INCS.darwin = ./darwin @@ -291,9 +547,9 @@ if1of ($(KBUILD_TARGET), darwin linux solaris win) VBoxDrv_LIBS = $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) VBoxDrv_LIBS.linux.debug = $(VBoxDrv_LIBS) $(VBOX_GCC_LIBGCC) VBoxDrv_LIBS.win = \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ - $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ - $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib #VBoxDrv_LDFLAGS.darwin = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded VBoxDrv_LDFLAGS.solaris += -N misc/ctf @@ -313,6 +569,12 @@ if1of ($(KBUILD_TARGET), darwin linux solaris win) win/SUPDrv-win.cpp \ win/SUPDrvA-win.asm \ win/VBoxDrv.rc + ifdef VBOX_WITH_HARDENING + VBoxDrv_SOURCES.win += \ + win/SUPHardenedVerifyImage-win.cpp \ + win/SUPHardenedVerifyProcess-win.cpp \ + $(VBOX_SUP_WIN_CERTS_FILE) + endif VBoxDrv_SOURCES = \ SUPDrv.d \ SUPDrv.c \ diff --git a/src/VBox/HostDrivers/Support/SUPDrv.c b/src/VBox/HostDrivers/Support/SUPDrv.c index 0be4002ceef..b2f81bab1fe 100644 --- a/src/VBox/HostDrivers/Support/SUPDrv.c +++ b/src/VBox/HostDrivers/Support/SUPDrv.c @@ -463,124 +463,130 @@ int VBOXCALL supdrvInitDevExt(PSUPDRVDEVEXT pDevExt, size_t cbSession) /* * Initialize it. */ - memset(pDevExt, 0, sizeof(*pDevExt)); + memset(pDevExt, 0, sizeof(*pDevExt)); /* Does not wipe OS specific tail section of the structure. */ + pDevExt->Spinlock = NIL_RTSPINLOCK; + pDevExt->hGipSpinlock = NIL_RTSPINLOCK; + pDevExt->hSessionHashTabSpinlock = NIL_RTSPINLOCK; rc = RTSpinlockCreate(&pDevExt->Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "SUPDrvDevExt"); if (RT_SUCCESS(rc)) - { rc = RTSpinlockCreate(&pDevExt->hGipSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "SUPDrvGip"); + if (RT_SUCCESS(rc)) + rc = RTSpinlockCreate(&pDevExt->hSessionHashTabSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "SUPDrvSession"); + + if (RT_SUCCESS(rc)) +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + rc = RTSemMutexCreate(&pDevExt->mtxLdr); +#else + rc = RTSemFastMutexCreate(&pDevExt->mtxLdr); +#endif + if (RT_SUCCESS(rc)) + { + rc = RTSemFastMutexCreate(&pDevExt->mtxComponentFactory); if (RT_SUCCESS(rc)) { #ifdef SUPDRV_USE_MUTEX_FOR_LDR - rc = RTSemMutexCreate(&pDevExt->mtxLdr); + rc = RTSemMutexCreate(&pDevExt->mtxGip); #else - rc = RTSemFastMutexCreate(&pDevExt->mtxLdr); + rc = RTSemFastMutexCreate(&pDevExt->mtxGip); #endif if (RT_SUCCESS(rc)) { - rc = RTSemFastMutexCreate(&pDevExt->mtxComponentFactory); + rc = supdrvGipCreate(pDevExt); if (RT_SUCCESS(rc)) { -#ifdef SUPDRV_USE_MUTEX_FOR_LDR - rc = RTSemMutexCreate(&pDevExt->mtxGip); -#else - rc = RTSemFastMutexCreate(&pDevExt->mtxGip); -#endif + rc = supdrvTracerInit(pDevExt); if (RT_SUCCESS(rc)) { - rc = supdrvGipCreate(pDevExt); - if (RT_SUCCESS(rc)) - { - rc = supdrvTracerInit(pDevExt); - if (RT_SUCCESS(rc)) - { - pDevExt->pLdrInitImage = NULL; - pDevExt->hLdrInitThread = NIL_RTNATIVETHREAD; - pDevExt->u32Cookie = BIRD; /** @todo make this random? */ - pDevExt->cbSession = (uint32_t)cbSession; - - /* - * Fixup the absolute symbols. - * - * Because of the table indexing assumptions we'll have a little #ifdef orgy - * here rather than distributing this to OS specific files. At least for now. - */ + pDevExt->pLdrInitImage = NULL; + pDevExt->hLdrInitThread = NIL_RTNATIVETHREAD; + pDevExt->u32Cookie = BIRD; /** @todo make this random? */ + pDevExt->cbSession = (uint32_t)cbSession; + + /* + * Fixup the absolute symbols. + * + * Because of the table indexing assumptions we'll have a little #ifdef orgy + * here rather than distributing this to OS specific files. At least for now. + */ #ifdef RT_OS_DARWIN # if ARCH_BITS == 32 - if (SUPR0GetPagingMode() >= SUPPAGINGMODE_AMD64) - { - g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ - g_aFunctions[1].pfn = (void *)0x80; /* SUPR0Abs64bitKernelCS - KERNEL64_CS, seg.h */ - g_aFunctions[2].pfn = (void *)0x88; /* SUPR0Abs64bitKernelSS - KERNEL64_SS, seg.h */ - g_aFunctions[3].pfn = (void *)0x88; /* SUPR0Abs64bitKernelDS - KERNEL64_SS, seg.h */ - } - else - g_aFunctions[0].pfn = g_aFunctions[1].pfn = g_aFunctions[2].pfn = g_aFunctions[4].pfn = (void *)0; - g_aFunctions[4].pfn = (void *)0x08; /* SUPR0AbsKernelCS - KERNEL_CS, seg.h */ - g_aFunctions[5].pfn = (void *)0x10; /* SUPR0AbsKernelSS - KERNEL_DS, seg.h */ - g_aFunctions[6].pfn = (void *)0x10; /* SUPR0AbsKernelDS - KERNEL_DS, seg.h */ - g_aFunctions[7].pfn = (void *)0x10; /* SUPR0AbsKernelES - KERNEL_DS, seg.h */ - g_aFunctions[8].pfn = (void *)0x10; /* SUPR0AbsKernelFS - KERNEL_DS, seg.h */ - g_aFunctions[9].pfn = (void *)0x48; /* SUPR0AbsKernelGS - CPU_DATA_GS, seg.h */ + if (SUPR0GetPagingMode() >= SUPPAGINGMODE_AMD64) + { + g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ + g_aFunctions[1].pfn = (void *)0x80; /* SUPR0Abs64bitKernelCS - KERNEL64_CS, seg.h */ + g_aFunctions[2].pfn = (void *)0x88; /* SUPR0Abs64bitKernelSS - KERNEL64_SS, seg.h */ + g_aFunctions[3].pfn = (void *)0x88; /* SUPR0Abs64bitKernelDS - KERNEL64_SS, seg.h */ + } + else + g_aFunctions[0].pfn = g_aFunctions[1].pfn = g_aFunctions[2].pfn = g_aFunctions[4].pfn = (void *)0; + g_aFunctions[4].pfn = (void *)0x08; /* SUPR0AbsKernelCS - KERNEL_CS, seg.h */ + g_aFunctions[5].pfn = (void *)0x10; /* SUPR0AbsKernelSS - KERNEL_DS, seg.h */ + g_aFunctions[6].pfn = (void *)0x10; /* SUPR0AbsKernelDS - KERNEL_DS, seg.h */ + g_aFunctions[7].pfn = (void *)0x10; /* SUPR0AbsKernelES - KERNEL_DS, seg.h */ + g_aFunctions[8].pfn = (void *)0x10; /* SUPR0AbsKernelFS - KERNEL_DS, seg.h */ + g_aFunctions[9].pfn = (void *)0x48; /* SUPR0AbsKernelGS - CPU_DATA_GS, seg.h */ # else /* 64-bit darwin: */ - g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ - g_aFunctions[1].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0Abs64bitKernelCS */ - g_aFunctions[2].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0Abs64bitKernelSS */ - g_aFunctions[3].pfn = (void *)0; /* SUPR0Abs64bitKernelDS */ - g_aFunctions[4].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0AbsKernelCS */ - g_aFunctions[5].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0AbsKernelSS */ - g_aFunctions[6].pfn = (void *)0; /* SUPR0AbsKernelDS */ - g_aFunctions[7].pfn = (void *)0; /* SUPR0AbsKernelES */ - g_aFunctions[8].pfn = (void *)0; /* SUPR0AbsKernelFS */ - g_aFunctions[9].pfn = (void *)0; /* SUPR0AbsKernelGS */ + g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ + g_aFunctions[1].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0Abs64bitKernelCS */ + g_aFunctions[2].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0Abs64bitKernelSS */ + g_aFunctions[3].pfn = (void *)0; /* SUPR0Abs64bitKernelDS */ + g_aFunctions[4].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0AbsKernelCS */ + g_aFunctions[5].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0AbsKernelSS */ + g_aFunctions[6].pfn = (void *)0; /* SUPR0AbsKernelDS */ + g_aFunctions[7].pfn = (void *)0; /* SUPR0AbsKernelES */ + g_aFunctions[8].pfn = (void *)0; /* SUPR0AbsKernelFS */ + g_aFunctions[9].pfn = (void *)0; /* SUPR0AbsKernelGS */ # endif #else /* !RT_OS_DARWIN */ # if ARCH_BITS == 64 - g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ - g_aFunctions[1].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0Abs64bitKernelCS */ - g_aFunctions[2].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0Abs64bitKernelSS */ - g_aFunctions[3].pfn = (void *)(uintptr_t)ASMGetDS(); /* SUPR0Abs64bitKernelDS */ + g_aFunctions[0].pfn = (void *)1; /* SUPR0AbsIs64bit */ + g_aFunctions[1].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0Abs64bitKernelCS */ + g_aFunctions[2].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0Abs64bitKernelSS */ + g_aFunctions[3].pfn = (void *)(uintptr_t)ASMGetDS(); /* SUPR0Abs64bitKernelDS */ # else - g_aFunctions[0].pfn = g_aFunctions[1].pfn = g_aFunctions[2].pfn = g_aFunctions[4].pfn = (void *)0; + g_aFunctions[0].pfn = g_aFunctions[1].pfn = g_aFunctions[2].pfn = g_aFunctions[4].pfn = (void *)0; # endif - g_aFunctions[4].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0AbsKernelCS */ - g_aFunctions[5].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0AbsKernelSS */ - g_aFunctions[6].pfn = (void *)(uintptr_t)ASMGetDS(); /* SUPR0AbsKernelDS */ - g_aFunctions[7].pfn = (void *)(uintptr_t)ASMGetES(); /* SUPR0AbsKernelES */ - g_aFunctions[8].pfn = (void *)(uintptr_t)ASMGetFS(); /* SUPR0AbsKernelFS */ - g_aFunctions[9].pfn = (void *)(uintptr_t)ASMGetGS(); /* SUPR0AbsKernelGS */ + g_aFunctions[4].pfn = (void *)(uintptr_t)ASMGetCS(); /* SUPR0AbsKernelCS */ + g_aFunctions[5].pfn = (void *)(uintptr_t)ASMGetSS(); /* SUPR0AbsKernelSS */ + g_aFunctions[6].pfn = (void *)(uintptr_t)ASMGetDS(); /* SUPR0AbsKernelDS */ + g_aFunctions[7].pfn = (void *)(uintptr_t)ASMGetES(); /* SUPR0AbsKernelES */ + g_aFunctions[8].pfn = (void *)(uintptr_t)ASMGetFS(); /* SUPR0AbsKernelFS */ + g_aFunctions[9].pfn = (void *)(uintptr_t)ASMGetGS(); /* SUPR0AbsKernelGS */ #endif /* !RT_OS_DARWIN */ - return VINF_SUCCESS; - } + return VINF_SUCCESS; + } - supdrvGipDestroy(pDevExt); - } + supdrvGipDestroy(pDevExt); + } #ifdef SUPDRV_USE_MUTEX_FOR_GIP - RTSemMutexDestroy(pDevExt->mtxGip); - pDevExt->mtxGip = NIL_RTSEMMUTEX; + RTSemMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMMUTEX; #else - RTSemFastMutexDestroy(pDevExt->mtxGip); - pDevExt->mtxGip = NIL_RTSEMFASTMUTEX; + RTSemFastMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMFASTMUTEX; #endif - } - RTSemFastMutexDestroy(pDevExt->mtxComponentFactory); - pDevExt->mtxComponentFactory = NIL_RTSEMFASTMUTEX; - } + } + RTSemFastMutexDestroy(pDevExt->mtxComponentFactory); + pDevExt->mtxComponentFactory = NIL_RTSEMFASTMUTEX; + } #ifdef SUPDRV_USE_MUTEX_FOR_LDR - RTSemMutexDestroy(pDevExt->mtxLdr); - pDevExt->mtxLdr = NIL_RTSEMMUTEX; + RTSemMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMMUTEX; #else - RTSemFastMutexDestroy(pDevExt->mtxLdr); - pDevExt->mtxLdr = NIL_RTSEMFASTMUTEX; + RTSemFastMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMFASTMUTEX; #endif - } - RTSpinlockDestroy(pDevExt->hGipSpinlock); - pDevExt->hGipSpinlock = NIL_RTSPINLOCK; - } - RTSpinlockDestroy(pDevExt->Spinlock); - pDevExt->Spinlock = NIL_RTSPINLOCK; } + + RTSpinlockDestroy(pDevExt->Spinlock); + pDevExt->Spinlock = NIL_RTSPINLOCK; + RTSpinlockDestroy(pDevExt->hGipSpinlock); + pDevExt->hGipSpinlock = NIL_RTSPINLOCK; + RTSpinlockDestroy(pDevExt->hSessionHashTabSpinlock); + pDevExt->hSessionHashTabSpinlock = NIL_RTSPINLOCK; + #ifdef SUPDRV_WITH_RELEASE_LOGGER RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); RTLogDestroy(RTLogSetDefaultInstance(NULL)); @@ -621,6 +627,8 @@ void VBOXCALL supdrvDeleteDevExt(PSUPDRVDEVEXT pDevExt) pDevExt->Spinlock = NIL_RTSPINLOCK; RTSemFastMutexDestroy(pDevExt->mtxComponentFactory); pDevExt->mtxComponentFactory = NIL_RTSEMFASTMUTEX; + RTSpinlockDestroy(pDevExt->hSessionHashTabSpinlock); + pDevExt->hSessionHashTabSpinlock = NIL_RTSPINLOCK; /* * Free lists. @@ -699,15 +707,10 @@ int VBOXCALL supdrvCreateSession(PSUPDRVDEVEXT pDevExt, bool fUser, bool fUnrest pSession->pDevExt = pDevExt; pSession->u32Cookie = BIRD_INV; pSession->fUnrestricted = fUnrestricted; + /*pSession->fInHashTable = false; */ pSession->cRefs = 1; - /*pSession->pLdrUsage = NULL; - pSession->pVM = NULL; - pSession->pUsage = NULL; - pSession->pGip = NULL; - pSession->fGipReferenced = false; - pSession->Bundle.cUsed = 0; */ - pSession->Uid = NIL_RTUID; - pSession->Gid = NIL_RTGID; + /*pSession->pCommonNextHash = NULL; + pSession->ppOsSessionPtr = NULL; */ if (fUser) { pSession->Process = RTProcSelf(); @@ -718,6 +721,14 @@ int VBOXCALL supdrvCreateSession(PSUPDRVDEVEXT pDevExt, bool fUser, bool fUnrest pSession->Process = NIL_RTPROCESS; pSession->R0Process = NIL_RTR0PROCESS; } + /*pSession->pLdrUsage = NULL; + pSession->pVM = NULL; + pSession->pUsage = NULL; + pSession->pGip = NULL; + pSession->fGipReferenced = false; + pSession->Bundle.cUsed = 0; */ + pSession->Uid = NIL_RTUID; + pSession->Gid = NIL_RTGID; /*pSession->uTracerData = 0;*/ pSession->hTracerCaller = NIL_RTNATIVETHREAD; RTListInit(&pSession->TpProviders); @@ -745,14 +756,16 @@ int VBOXCALL supdrvCreateSession(PSUPDRVDEVEXT pDevExt, bool fUser, bool fUnrest /** - * Shared code for cleaning up a session (but not quite freeing it). + * Cleans up the session in the context of the process to which it belongs, the + * caller will free the session and the session spinlock. * - * This is primarily intended for MAC OS X where we have to clean up the memory - * stuff before the file handle is closed. + * This should normally occur when the session is closed or as the process + * exits. Careful reference counting in the OS specfic code makes sure that + * there cannot be any races between process/handle cleanup callbacks and + * threads doing I/O control calls. * - * @param pDevExt Device extension. + * @param pDevExt The device extension. * @param pSession Session data. - * This data will be freed by this routine. */ static void supdrvCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) { @@ -760,6 +773,11 @@ static void supdrvCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) PSUPDRVBUNDLE pBundle; LogFlow(("supdrvCleanupSession: pSession=%p\n", pSession)); + Assert(!pSession->fInHashTable); + Assert(!pSession->ppOsSessionPtr); + AssertReleaseMsg(pSession->R0Process == RTR0ProcHandleSelf() || pSession->R0Process == NIL_RTR0PROCESS, + ("R0Process=%p cur=%p; Process=%u curpid=%u\n", RTR0ProcHandleSelf(), RTProcSelf())); + /* * Remove logger instances related to this session. */ @@ -963,13 +981,13 @@ static void supdrvCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) /** - * Shared code for cleaning up a session. + * Common code for freeing a session when the reference count reaches zero. * * @param pDevExt Device extension. * @param pSession Session data. * This data will be freed by this routine. */ -static void supdrvCloseSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +static void supdrvDestroySession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) { VBOXDRV_SESSION_CLOSE(pSession); @@ -977,6 +995,7 @@ static void supdrvCloseSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) * Cleanup the session first. */ supdrvCleanupSession(pDevExt, pSession); + supdrvOSCleanupSession(pDevExt, pSession); /* * Free the rest of the session stuff. @@ -985,7 +1004,261 @@ static void supdrvCloseSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) pSession->Spinlock = NIL_RTSPINLOCK; pSession->pDevExt = NULL; RTMemFree(pSession); - LogFlow(("supdrvCloseSession: returns\n")); + LogFlow(("supdrvDestroySession: returns\n")); +} + + +/** + * Inserts the session into the global hash table. + * + * @retval VINF_SUCCESS on success. + * @retval VERR_WRONG_ORDER if the session was already inserted (asserted). + * @retval VERR_INVALID_PARAMETER if the session handle is invalid or a ring-0 + * session (asserted). + * @retval VERR_DUPLICATE if there is already a session for that pid. + * + * @param pDevExt The device extension. + * @param pSession The session. + * @param ppOsSessionPtr Pointer to the OS session pointer, if any is + * available and used. This will set to point to the + * session while under the protection of the session + * hash table spinlock. It will also be kept in + * PSUPDRVSESSION::ppOsSessionPtr for lookup and + * cleanup use. + * @param pvUser Argument for supdrvOSSessionHashTabInserted. + */ +int VBOXCALL supdrvSessionHashTabInsert(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVSESSION *ppOsSessionPtr, + void *pvUser) +{ + PSUPDRVSESSION pCur; + unsigned iHash; + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process != NIL_RTR0PROCESS, VERR_INVALID_PARAMETER); + + /* + * Calculate the hash table index and acquire the spinlock. + */ + iHash = SUPDRV_SESSION_HASH(pSession->Process); + + RTSpinlockAcquire(pDevExt->hSessionHashTabSpinlock); + + /* + * If there are a collisions, we need to carefully check if we got a + * duplicate. There can only be one open session per process. + */ + pCur = pDevExt->apSessionHashTab[iHash]; + if (pCur) + { + while (pCur && pCur->Process != pSession->Process) + pCur = pCur->pCommonNextHash; + + if (pCur) + { + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + if (pCur == pSession) + { + Assert(pSession->fInHashTable); + AssertFailed(); + return VERR_WRONG_ORDER; + } + Assert(!pSession->fInHashTable); + if (pCur->R0Process == pSession->R0Process) + return VERR_RESOURCE_IN_USE; + return VERR_DUPLICATE; + } + } + Assert(!pSession->fInHashTable); + Assert(!pSession->ppOsSessionPtr); + + /* + * Insert it, doing a callout to the OS specific code in case it has + * anything it wishes to do while we're holding the spinlock. + */ + pSession->pCommonNextHash = pDevExt->apSessionHashTab[iHash]; + pDevExt->apSessionHashTab[iHash] = pSession; + pSession->fInHashTable = true; + ASMAtomicIncS32(&pDevExt->cSessions); + + pSession->ppOsSessionPtr = ppOsSessionPtr; + if (ppOsSessionPtr) + ASMAtomicWritePtr(ppOsSessionPtr, pSession); + + supdrvOSSessionHashTabInserted(pDevExt, pSession, pvUser); + + /* + * Retain a reference for the pointer in the session table. + */ + ASMAtomicIncU32(&pSession->cRefs); + + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + return VINF_SUCCESS; +} + + +/** + * Removes the session from the global hash table. + * + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_FOUND if the session was already removed (asserted). + * @retval VERR_INVALID_PARAMETER if the session handle is invalid or a ring-0 + * session (asserted). + * + * @param pDevExt The device extension. + * @param pSession The session. The caller is expected to have a reference + * to this so it won't croak on us when we release the hash + * table reference. + * @param pvUser OS specific context value for the + * supdrvOSSessionHashTabInserted callback. + */ +int VBOXCALL supdrvSessionHashTabRemove(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + PSUPDRVSESSION pCur; + unsigned iHash; + int32_t cRefs; + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process != NIL_RTR0PROCESS, VERR_INVALID_PARAMETER); + + /* + * Calculate the hash table index and acquire the spinlock. + */ + iHash = SUPDRV_SESSION_HASH(pSession->Process); + + RTSpinlockAcquire(pDevExt->hSessionHashTabSpinlock); + + /* + * Unlink it. + */ + pCur = pDevExt->apSessionHashTab[iHash]; + if (pCur == pSession) + pDevExt->apSessionHashTab[iHash] = pSession->pCommonNextHash; + else + { + PSUPDRVSESSION pPrev = pCur; + while (pCur && pCur != pSession) + { + pPrev = pCur; + pCur = pCur->pCommonNextHash; + } + if (pCur) + pPrev->pCommonNextHash = pCur->pCommonNextHash; + else + { + Assert(!pSession->fInHashTable); + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + return VERR_NOT_FOUND; + } + } + + pSession->pCommonNextHash = NULL; + pSession->fInHashTable = false; + + ASMAtomicDecU32(&pDevExt->cSessions); + + /* + * Clear OS specific session pointer if available and do the OS callback. + */ + if (pSession->ppOsSessionPtr) + { + ASMAtomicCmpXchgPtr(pSession->ppOsSessionPtr, NULL, pSession); + pSession->ppOsSessionPtr = NULL; + } + + supdrvOSSessionHashTabRemoved(pDevExt, pSession, pvUser); + + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + + /* + * Drop the reference the hash table had to the session. This shouldn't + * be the last reference! + */ + cRefs = ASMAtomicDecU32(&pSession->cRefs); + Assert(cRefs > 0 && cRefs < _1M); + if (cRefs == 0) + supdrvDestroySession(pDevExt, pSession); + + return VINF_SUCCESS; +} + + +/** + * Looks up the session for the current process in the global hash table or in + * OS specific pointer. + * + * @returns Pointer to the session with a reference that the caller must + * release. If no valid session was found, NULL is returned. + * + * @param pDevExt The device extension. + * @param Process The process ID. + * @param R0Process The ring-0 process handle. + * @param ppOsSessionPtr The OS session pointer if available. If not NULL, + * this is used instead of the hash table. For + * additional safety it must then be equal to the + * SUPDRVSESSION::ppOsSessionPtr member. + * This can be NULL even if the OS has a session + * pointer. + */ +PSUPDRVSESSION VBOXCALL supdrvSessionHashTabLookup(PSUPDRVDEVEXT pDevExt, RTPROCESS Process, RTR0PROCESS R0Process, + PSUPDRVSESSION *ppOsSessionPtr) +{ + PSUPDRVSESSION pCur; + unsigned iHash; + + /* + * Validate input. + */ + AssertReturn(R0Process != NIL_RTR0PROCESS, NULL); + + /* + * Calculate the hash table index and acquire the spinlock. + */ + iHash = SUPDRV_SESSION_HASH(Process); + + RTSpinlockAcquire(pDevExt->hSessionHashTabSpinlock); + + /* + * If an OS session pointer is provided, always use it. + */ + if (ppOsSessionPtr) + { + pCur = *ppOsSessionPtr; + if ( pCur + && ( pCur->ppOsSessionPtr != ppOsSessionPtr + || pCur->Process != Process + || pCur->R0Process != R0Process) ) + pCur = NULL; + } + else + { + /* + * Otherwise, do the hash table lookup. + */ + pCur = pDevExt->apSessionHashTab[iHash]; + while ( pCur + && ( pCur->Process != Process + || pCur->R0Process != R0Process) ) + pCur = pCur->pCommonNextHash; + } + + /* + * Retain the session. + */ + if (pCur) + { + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + Assert(cRefs > 1 && cRefs < _1M); + } + + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + + return pCur; } @@ -1022,7 +1295,7 @@ uint32_t VBOXCALL supdrvSessionRelease(PSUPDRVSESSION pSession) cRefs = ASMAtomicDecU32(&pSession->cRefs); AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pSession)); if (cRefs == 0) - supdrvCloseSession(pSession->pDevExt, pSession); + supdrvDestroySession(pSession->pDevExt, pSession); return cRefs; } diff --git a/src/VBox/HostDrivers/Support/SUPDrvInternal.h b/src/VBox/HostDrivers/Support/SUPDrvInternal.h index 20cd0512c38..8a22c62a552 100644 --- a/src/VBox/HostDrivers/Support/SUPDrvInternal.h +++ b/src/VBox/HostDrivers/Support/SUPDrvInternal.h @@ -58,7 +58,7 @@ # define _interlockedbittestandset64 _interlockedbittestandset64_StupidDDKVsCompilerCrap # define _interlockedbittestandreset64 _interlockedbittestandreset64_StupidDDKVsCompilerCrap # pragma warning(disable : 4163) -# include <ntddk.h> +# include <iprt/nt/nt.h> # pragma warning(default : 4163) # undef _InterlockedExchange # undef _InterlockedExchangeAdd @@ -69,7 +69,7 @@ # undef _interlockedbittestandset64 # undef _interlockedbittestandreset64 # else -# include <ntddk.h> +# include <iprt/nt/nt.h> # endif # include <memory.h> # define memcmp(a,b,c) mymemcmp(a,b,c) @@ -177,6 +177,13 @@ */ #define OSDBGPRINT(a) SUPR0Printf a +/** Debug printf macro shared with the ring-3 part. */ +#ifdef DEBUG_bird +# define SUP_DPRINTF(a) SUPR0Printf a +#else +# define SUP_DPRINTF(a) do { } while (0) +#endif + /** @name Context values for the per-session handle tables. * The context value is used to distinguish between the different kinds of @@ -434,8 +441,24 @@ typedef struct SUPDRVSESSION uint32_t u32Cookie; /** Set if is an unrestricted session, clear if restricted. */ bool fUnrestricted; - /* Reference counter. */ + + /** Set if we're in the hash table, clear if not. Protected by the hash + * table spinlock. */ + bool fInHashTable; + /** Reference counter. */ uint32_t volatile cRefs; + /** Pointer to the next session with the same hash (common hash table). + * Protected by the hash table spinlock. */ + PSUPDRVSESSION pCommonNextHash; + /** Pointer to the OS specific session pointer, if available and in use. + * This is atomically set and cleared as the session is inserted and removed + * from the hash table (protected by the session hash table spinlock). */ + PSUPDRVSESSION *ppOsSessionPtr; + /** The process (id) of the session. */ + RTPROCESS Process; + /** Which process this session is associated with. + * This is NIL_RTR0PROCESS for kernel sessions and valid for user ones. */ + RTR0PROCESS R0Process; /** The VM associated with the session. */ PVM pVM; @@ -445,7 +468,9 @@ typedef struct SUPDRVSESSION /** Load usage records. (protected by SUPDRVDEVEXT::mtxLdr) */ PSUPDRVLDRUSAGE volatile pLdrUsage; - /** Spinlock protecting the bundles and the GIP members. */ + /** Spinlock protecting the bundles, the GIP members and the + * fProcessCleanupDone flag. It continues to be valid until the last + * reference to the session is released. */ RTSPINLOCK Spinlock; /** The ring-3 mapping of the GIP (readonly). */ RTR0MEMOBJ GipMapObjR3; @@ -460,11 +485,6 @@ typedef struct SUPDRVSESSION RTUID Uid; /** The group id of the session. (Set by the OS part.) */ RTGID Gid; - /** The process (id) of the session. */ - RTPROCESS Process; - /** Which process this session is associated with. - * This is NIL_RTR0PROCESS for kernel sessions and valid for user ones. */ - RTR0PROCESS R0Process; /** Per session tracer specfic data. */ uintptr_t uTracerData; /** The thread currently actively talking to the tracer. (One at the time!) */ @@ -497,6 +517,10 @@ typedef struct SUPDRVSESSION /** Pointer to the next session with the same hash. */ PSUPDRVSESSION pNextHash; # endif +# if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING) + /** Pointer to the process protection structure for this session. */ + struct SUPDRVNTPROTECT *pNtProtect; +# endif #endif /* !SUPDRV_AGNOSTIC */ } SUPDRVSESSION; @@ -612,8 +636,21 @@ typedef struct SUPDRVDEVEXT /** Hash table for user tracer modules (SUPDRVVTGCOPY). */ RTLISTANCHOR aTrackerUmodHash[128]; + /** @name Session Handle Table. + * @{ */ + /** Spinlock protecting apSessionHashTab, cSessions, + * SUPDRVSESSION::ppOsSessionPtr, SUPDRVSESSION::pCommonNextHash, and possibly + * others depending on the OS. */ + RTSPINLOCK hSessionHashTabSpinlock; + /** Session hash table hash table. The size of this table must make sense in + * comparison to GVMM_MAX_HANDLES. */ + PSUPDRVSESSION apSessionHashTab[HC_ARCH_BITS == 64 ? 8191 : 127]; + /** The number of open sessions. */ + int32_t cSessions; + /** @} */ + /* - * Note! The non-agnostic bits must be a the very end of the structure! + * Note! The non-agnostic bits must be at the very end of the structure! */ #ifndef SUPDRV_AGNOSTIC # ifdef RT_OS_WINDOWS @@ -625,12 +662,43 @@ typedef struct SUPDRVDEVEXT #endif } SUPDRVDEVEXT; +/** Calculates the index into g_apSessionHashTab.*/ +#define SUPDRV_SESSION_HASH(a_pid) ( (a_pid) % RT_ELEMENTS(((SUPDRVDEVEXT *)NULL)->apSessionHashTab) ) + RT_C_DECLS_BEGIN /******************************************************************************* * OS Specific Functions * *******************************************************************************/ +/** + * 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); + +/** + * Called to let the OS specfic code perform additional insertion work while + * still under the protection of the hash table spinlock. + * + * @param pDevExt The device globals. + * @param pSession The session that was inserted. + * @param pvUser User context specified to the insert call. + */ +void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser); + +/** + * Called to let the OS specfic code perform additional removal work while still + * under the protection of the hash table spinlock. + * + * @param pDevExt The device globals. + * @param pSession The session that was removed. + * @param pvUser User context specified to the remove call. + */ +void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser); + void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession); bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc); bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt); @@ -762,6 +830,10 @@ int VBOXCALL supdrvIDC(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSIO int VBOXCALL supdrvInitDevExt(PSUPDRVDEVEXT pDevExt, size_t cbSession); void VBOXCALL supdrvDeleteDevExt(PSUPDRVDEVEXT pDevExt); int VBOXCALL supdrvCreateSession(PSUPDRVDEVEXT pDevExt, bool fUser, bool fUnrestricted, PSUPDRVSESSION *ppSession); +int VBOXCALL supdrvSessionHashTabInsert(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVSESSION *ppOsSessionPtr, void *pvUser); +int VBOXCALL supdrvSessionHashTabRemove(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser); +PSUPDRVSESSION VBOXCALL supdrvSessionHashTabLookup(PSUPDRVDEVEXT pDevExt, RTPROCESS Process, RTR0PROCESS R0Process, + PSUPDRVSESSION *ppOsSessionPtr); uint32_t VBOXCALL supdrvSessionRetain(PSUPDRVSESSION pSession); uint32_t VBOXCALL supdrvSessionRelease(PSUPDRVSESSION pSession); diff --git a/src/VBox/HostDrivers/Support/SUPLib.cpp b/src/VBox/HostDrivers/Support/SUPLib.cpp index a9c2b6692a5..2721d072308 100644 --- a/src/VBox/HostDrivers/Support/SUPLib.cpp +++ b/src/VBox/HostDrivers/Support/SUPLib.cpp @@ -1541,7 +1541,7 @@ SUPR3DECL(int) SUPR3HardenedVerifySelf(const char *pszArgv0, bool fInternal, PRT /* * Verify that the image file and parent directories are sane. */ - rc = supR3HardenedVerifyFile(szExecPath, RTHCUINTPTR_MAX, pErrInfo); + rc = supR3HardenedVerifyFile(szExecPath, RTHCUINTPTR_MAX, false /*fMaybe3rdParty*/, pErrInfo); if (RT_FAILURE(rc)) return rc; #endif @@ -1585,7 +1585,7 @@ SUPR3DECL(int) SUPR3HardenedVerifyPlugIn(const char *pszFilename, PRTERRINFO pEr * Only do the actual check in hardened builds. */ #ifdef VBOX_WITH_HARDENING - int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, pErrInfo); + int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, true /*fMaybe3rdParty*/, pErrInfo); if (RT_FAILURE(rc) && !RTErrInfoIsSet(pErrInfo)) LogRel(("supR3HardenedVerifyFile: Verification of \"%s\" failed, rc=%Rrc\n", pszFilename, rc)); return rc; diff --git a/src/VBox/HostDrivers/Support/SUPLibInternal.h b/src/VBox/HostDrivers/Support/SUPLibInternal.h index e1e05512746..bda35003c0b 100644 --- a/src/VBox/HostDrivers/Support/SUPLibInternal.h +++ b/src/VBox/HostDrivers/Support/SUPLibInternal.h @@ -103,7 +103,10 @@ * @{ */ #if defined(IN_SUP_HARDENED_R3) && defined(RT_OS_WINDOWS) +# define SUP_HARDENED_NEED_CRT_FUNCTIONS +DECLHIDDEN(int) suplibHardenedMemComp(void const *pvDst, const void *pvSrc, size_t cbToComp); DECLHIDDEN(void *) suplibHardenedMemCopy(void *pvDst, const void *pvSrc, size_t cbToCopy); +DECLHIDDEN(void *) suplibHardenedMemSet(void *pvDst, int ch, size_t cbToSet); DECLHIDDEN(char *) suplibHardenedStrCopy(char *pszDst, const char *pszSrc); DECLHIDDEN(size_t) suplibHardenedStrLen(const char *psz); DECLHIDDEN(char *) suplibHardenedStrCat(char *pszDst, const char *pszSrc); @@ -111,7 +114,10 @@ DECLHIDDEN(int) suplibHardenedStrCmp(const char *psz1, const char *psz2); DECLHIDDEN(int) suplibHardenedStrNCmp(const char *psz1, const char *psz2, size_t cchMax); DECLHIDDEN(int) suplibHardenedStrICmp(const char *psz1, const char *psz2); #else +# undef SUP_HARDENED_NEED_CRT_FUNCTIONS +# define suplibHardenedMemComp memcmp # define suplibHardenedMemCopy memcpy +# define suplibHardenedMemSet memset # define suplibHardenedStrCopy strcpy # define suplibHardenedStrLen strlen # define suplibHardenedStrCat strcat @@ -119,9 +125,23 @@ DECLHIDDEN(int) suplibHardenedStrICmp(const char *psz1, const char *psz2); # define suplibHardenedStrNCmp strncmp # define suplibHardenedStrICmp stricmp #endif -DECLNORETURN(void) suplibHardenedExit(RTEXITCODE rcExit); +DECLNORETURN(void) suplibHardenedExit(RTEXITCODE rcExit); +DECLHIDDEN(void) suplibHardenedPrintF(const char *pszFormat, ...); +DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va); + /** @} */ +/** Debug output macro. */ +#ifdef DEBUG_bird +# ifdef IN_SUP_HARDENED_R3 +# define SUP_DPRINTF(a) suplibHardenedPrintF a +# else +# define SUP_DPRINTF(a) RTLogPrintf a +# endif +#else +# define SUP_DPRINTF(a) do { } while (0) +#endif + /******************************************************************************* * Structures and Typedefs * @@ -134,6 +154,7 @@ typedef enum SUPINSTFILETYPE kSupIFT_Invalid = 0, kSupIFT_Exe, kSupIFT_Dll, + kSupIFT_Rc, kSupIFT_Sys, kSupIFT_Script, kSupIFT_Data, @@ -275,6 +296,17 @@ typedef DECLCALLBACK(int) FNSUPR3PREINIT(PSUPPREINITDATA pPreInitData, uint32_t /** Pointer to supR3PreInit. */ typedef FNSUPR3PREINIT *PFNSUPR3PREINIT; +/** The current SUPR3HardenedMain state / location. */ +typedef enum SUPR3HARDENEDMAINSTATE +{ + SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED = 0, + SUPR3HARDENEDMAINSTATE_VERIFY_TRUST_READY, + SUPR3HARDENEDMAINSTATE_INIT_RUNTIME, + SUPR3HARDENEDMAINSTATE_GET_TRUSTED_MAIN, + SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN, + SUPR3HARDENEDMAINSTATE_END +} SUPR3HARDENEDMAINSTATE; + /******************************************************************************* * Global Variables * @@ -287,6 +319,7 @@ extern DECLHIDDEN(PSUPGLOBALINFOPAGE) g_pSUPGlobalInfoPageR0; #ifdef ___SUPDrvIOC_h___ extern DECLHIDDEN(PSUPQUERYFUNCS) g_pSupFunctions; #endif +extern DECLHIDDEN(SUPR3HARDENEDMAINSTATE) g_enmSupR3HardenedMainState; /******************************************************************************* @@ -359,14 +392,29 @@ DECLHIDDEN(int) supR3HardenedErrorV(int rc, bool fFatal, const char *pszForma * Display an error which may or may not be fatal. */ DECLHIDDEN(int) supR3HardenedError(int rc, bool fFatal, const char *pszFormat, ...); + DECLHIDDEN(int) supR3HardenedVerifyAll(bool fFatal, bool fLeaveFilesOpen, const char *pszProgName); DECLHIDDEN(int) supR3HardenedVerifyFixedDir(SUPINSTDIR enmDir, bool fFatal); DECLHIDDEN(int) supR3HardenedVerifyFixedFile(const char *pszFilename, bool fFatal); DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo); -DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile, bool fMaybe3rdParty, + PRTERRINFO pErrInfo); DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData); DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData); +#ifdef RT_OS_WINDOWS +DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags); +DECLHIDDEN(void) supR3HardenedWinInitVersion(void); +DECLHIDDEN(void) supR3HardenedWinVerifyProcess(void); +DECLHIDDEN(void) supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(void); +DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int cArgs, char **papszArgs); +DECLHIDDEN(int) supR3HardenedWinReSpawn(void); +DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only); +extern RTUTF16 g_wszSupLibHardenedExePath[1024]; +# ifdef RTPATH_MAX +extern char g_szSupLibHardenedExePath[RTPATH_MAX]; +# endif +#endif SUPR3DECL(int) supR3PageLock(void *pvStart, size_t cPages, PSUPPAGE paPages); SUPR3DECL(int) supR3PageUnlock(void *pvStart); diff --git a/src/VBox/HostDrivers/Support/SUPLibLdr.cpp b/src/VBox/HostDrivers/Support/SUPLibLdr.cpp index 9c9afa685f6..69bfbb0559e 100644 --- a/src/VBox/HostDrivers/Support/SUPLibLdr.cpp +++ b/src/VBox/HostDrivers/Support/SUPLibLdr.cpp @@ -780,7 +780,7 @@ SUPR3DECL(int) SUPR3HardenedLdrLoadPlugIn(const char *pszFilename, PRTLDRMOD phL /* * Verify the image file. */ - int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, pErrInfo); + int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, true /*fMaybe3rdParty*/, pErrInfo); if (RT_FAILURE(rc)) { if (!RTErrInfoIsSet(pErrInfo)) diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp index 151eed4e719..06a499e9229 100644 --- a/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2013 Oracle Corporation + * Copyright (C) 2006-2014 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -120,25 +120,31 @@ typedef FNRTR3INITEX *PFNRTR3INITEX; * Global Variables * *******************************************************************************/ /** The pre-init data we pass on to SUPR3 (residing in VBoxRT). */ -static SUPPREINITDATA g_SupPreInitData; +static SUPPREINITDATA g_SupPreInitData; /** The program executable path. */ -static char g_szSupLibHardenedExePath[RTPATH_MAX]; +#ifndef RT_OS_WINDOWS +static +#endif +char g_szSupLibHardenedExePath[RTPATH_MAX]; /** The program directory path. */ -static char g_szSupLibHardenedDirPath[RTPATH_MAX]; +static char g_szSupLibHardenedDirPath[RTPATH_MAX]; /** The program name. */ -static const char *g_pszSupLibHardenedProgName; +static const char *g_pszSupLibHardenedProgName; #ifdef SUP_HARDENED_SUID /** The real UID at startup. */ -static uid_t g_uid; +static uid_t g_uid; /** The real GID at startup. */ -static gid_t g_gid; +static gid_t g_gid; # ifdef RT_OS_LINUX -static uint32_t g_uCaps; +static uint32_t g_uCaps; # endif #endif +/** The current SUPR3HardenedMain state / location. */ +SUPR3HARDENEDMAINSTATE g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED; + /******************************************************************************* * Internal Functions * @@ -149,129 +155,6 @@ static void supR3HardenedMainDropPrivileges(void); static PFNSUPTRUSTEDERROR supR3HardenedMainGetTrustedError(const char *pszProgName); -#ifdef RT_OS_WINDOWS -/* - * No CRT here, thank you. - */ - -/** memcpy */ -DECLHIDDEN(void *) suplibHardenedMemCopy(void *pvDst, const void *pvSrc, size_t cbToCopy) -{ - size_t *puDst = (size_t *)pvDst; - size_t const *puSrc = (size_t const *)pvSrc; - while (cbToCopy >= sizeof(size_t)) - { - *puDst++ = *puSrc++; - cbToCopy -= sizeof(size_t); - } - - uint8_t *pbDst = (uint8_t *)puDst; - uint8_t const *pbSrc = (uint8_t const *)puSrc; - while (cbToCopy > 0) - { - *pbDst++ = *pbSrc++; - cbToCopy--; - } - - return pvDst; -} - - -/** strcpy */ -DECLHIDDEN(char *) suplibHardenedStrCopy(char *pszDst, const char *pszSrc) -{ - char *pszRet = pszDst; - char ch; - do - { - ch = *pszSrc++; - *pszDst++ = ch; - } while (ch); - return pszRet; -} - - -/** strlen */ -DECLHIDDEN(size_t) suplibHardenedStrLen(const char *psz) -{ - const char *pszStart = psz; - while (*psz) - psz++; - return psz - pszStart; -} - - -/** strcat */ -DECLHIDDEN(char *) suplibHardenedStrCat(char *pszDst, const char *pszSrc) -{ - char *pszRet = pszDst; - while (*pszDst) - pszDst++; - suplibHardenedStrCopy(pszDst, pszSrc); - return pszRet; -} - - -# ifdef RT_OS_WINDOWS -/** stricmp */ -DECLHIDDEN(int) suplibHardenedStrICmp(const char *psz1, const char *psz2) -{ - const char *pszOrg1 = psz1; - const char *pszOrg2 = psz2; - - for (;;) - { - char ch1 = *psz1++; - char ch2 = *psz1++; - if (ch1 != ch2) - { - int rc = CompareStringA(LOCALE_USER_DEFAULT, NORM_IGNORECASE, pszOrg1, -1, pszOrg2, -1); -# ifdef VBOX_STRICT - if (rc == 0) - __debugbreak(); -# endif - return rc - 2; - } - if (ch1 == 0) - return 0; - } -} -# endif - - -/** strcmp */ -DECLHIDDEN(int) suplibHardenedStrCmp(const char *psz1, const char *psz2) -{ - for (;;) - { - char ch1 = *psz1++; - char ch2 = *psz1++; - if (ch1 != ch2) - return ch1 < ch2 ? -1 : 1; - if (ch1 == 0) - return 0; - } -} - - -/** strncmp */ -DECLHIDDEN(int) suplibHardenedStrNCmp(const char *psz1, const char *psz2, size_t cchMax) -{ - while (cchMax-- > 0) - { - char ch1 = *psz1++; - char ch2 = *psz1++; - if (ch1 != ch2) - return ch1 < ch2 ? -1 : 1; - if (ch1 == 0) - break; - } - return 0; -} - -#endif /* RT_OS_WINDOWS */ - - /** * Safely copy one or more strings into the given buffer. * @@ -422,16 +305,13 @@ static void suplibHardenedPrintHexOctal(uint64_t uValue, unsigned uBase, uint32_ do { - unsigned iDigit = uValue & fDigitMask; + *psz-- = pchDigits[uValue & fDigitMask]; uValue >>= cShift; - - *psz-- = uValue % 10; - uValue /= 10; } while (uValue > 0); if ((fFlags & RTSTR_F_SPECIAL) && uBase == 16) { - *psz-- = 'x'; + *psz-- = !(fFlags & RTSTR_F_CAPITAL) ? 'x' : 'X'; *psz-- = '0'; } @@ -441,6 +321,83 @@ static void suplibHardenedPrintHexOctal(uint64_t uValue, unsigned uBase, uint32_ /** + * Writes a wide character string to standard error. + * + * @param pwsz The string. + */ +static void suplibHardenedPrintWideStr(PCRTUTF16 pwsz) +{ + for (;;) + { + RTUTF16 wc = *pwsz++; + if (!wc) + return; + if ( (wc < 0x7f && wc >= 0x20) + || wc == '\n' + || wc == '\r') + suplibHardenedPrintChr((char)wc); + else + { + suplibHardenedPrintStrN(RT_STR_TUPLE("\\x")); + suplibHardenedPrintHexOctal(wc, 16, 0); + } + } +} + +#ifdef IPRT_NO_CRT + +/** Buffer structure used by suplibHardenedOutput. */ +struct SUPLIBHARDENEDOUTPUTBUF +{ + size_t off; + char szBuf[2048]; +}; + +/** Callback for RTStrFormatV, see FNRTSTROUTPUT. */ +static DECLCALLBACK(size_t) suplibHardenedOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + SUPLIBHARDENEDOUTPUTBUF *pBuf = (SUPLIBHARDENEDOUTPUTBUF *)pvArg; + size_t cbTodo = cbChars; + for (;;) + { + size_t cbSpace = sizeof(pBuf->szBuf) - pBuf->off - 1; + + /* Flush the buffer? */ + if ( cbSpace == 0 + || (cbTodo == 0 && pBuf->off)) + { + suplibHardenedPrintStrN(pBuf->szBuf, pBuf->off); +# ifdef RT_OS_WINDOWS + OutputDebugString(pBuf->szBuf); +# endif + pBuf->off = 0; + cbSpace = sizeof(pBuf->szBuf) - 1; + } + + /* Copy the string into the buffer. */ + if (cbTodo == 1) + { + pBuf->szBuf[pBuf->off++] = *pachChars; + break; + } + if (cbSpace >= cbTodo) + { + memcpy(&pBuf->szBuf[pBuf->off], pachChars, cbTodo); + pBuf->off += cbTodo; + break; + } + memcpy(&pBuf->szBuf[pBuf->off], pachChars, cbSpace); + pBuf->off += cbSpace; + cbTodo -= cbSpace; + } + pBuf->szBuf[pBuf->off] = '\0'; + + return cbChars; +} + +#endif /* IPRT_NO_CRT */ + +/** * Simple printf to standard error. * * @param pszFormat The format string. @@ -448,6 +405,17 @@ static void suplibHardenedPrintHexOctal(uint64_t uValue, unsigned uBase, uint32_ */ DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va) { +#ifdef IPRT_NO_CRT + /* + * Use buffered output here to avoid character mixing on the windows + * console and to enable us to use OutputDebugString. + */ + SUPLIBHARDENEDOUTPUTBUF Buf; + Buf.off = 0; + Buf.szBuf[0] = '\0'; + RTStrFormatV(suplibHardenedOutput, &Buf, NULL, NULL, pszFormat, va); + +#else /* !IPRT_NO_CRT */ /* * Format loop. */ @@ -543,13 +511,22 @@ DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va) break; case 's': - { - const char *pszStr = va_arg(va, const char *); - if (!RT_VALID_PTR(pszStr)) - pszStr = "<NULL>"; - suplibHardenedPrintStr(pszStr); + if (chArgSize == 'l') + { + PCRTUTF16 pwszStr = va_arg(va, PCRTUTF16 ); + if (RT_VALID_PTR(pwszStr)) + suplibHardenedPrintWideStr(pwszStr); + else + suplibHardenedPrintStr("<NULL>"); + } + else + { + const char *pszStr = va_arg(va, const char *); + if (!RT_VALID_PTR(pszStr)) + pszStr = "<NULL>"; + suplibHardenedPrintStr(pszStr); + } break; - } case 'd': case 'i': @@ -616,6 +593,20 @@ DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va) break; } + case 'R': + if (pszFormat[0] == 'r' && pszFormat[1] == 'c') + { + int iValue = va_arg(va, int); + if (iValue < 0) + { + suplibHardenedPrintChr('-'); + iValue = -iValue; + } + suplibHardenedPrintDecimal(iValue); + pszFormat += 2; + break; + } + /* fall thru */ /* * Custom format. @@ -635,6 +626,7 @@ DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va) /* Flush the last bits of the string. */ if (pszLast != pszFormat) suplibHardenedPrintStrN(pszLast, pszFormat - pszLast); +#endif /* !IPRT_NO_CRT */ } @@ -653,7 +645,6 @@ DECLHIDDEN(void) suplibHardenedPrintF(const char *pszFormat, ...) } - /** * @copydoc RTPathStripFilename. */ @@ -862,9 +853,12 @@ static void supR3HardenedGetFullExePath(void) suplibHardenedMemCopy(g_szSupLibHardenedExePath, pszImageName, cchImageName + 1); #elif defined(RT_OS_WINDOWS) - HMODULE hExe = GetModuleHandle(NULL); - if (!GetModuleFileName(hExe, &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath))) - supR3HardenedFatal("supR3HardenedExecDir: GetModuleFileName failed, rc=%d\n", GetLastError()); + int cbRet = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, + g_wszSupLibHardenedExePath, -1, + g_szSupLibHardenedExePath, sizeof(g_szSupLibHardenedExePath), + NULL /*pchDefChar*/, NULL /* pfUsedDefChar */); + if (!cbRet) + supR3HardenedFatal("supR3HardenedExecDir: WideCharToMultiByte failed, rc=%d\n", GetLastError()); #else # error needs porting. #endif @@ -955,6 +949,7 @@ DECLHIDDEN(void) supR3HardenedFatalMsgV(const char *pszWhere, SUPINITOP enmWha suplibHardenedPrintStr("Tip! Make sure the kernel module is loaded. It may also help to reinstall VirtualBox.\n"); break; + case kSupInitOp_Misc: case kSupInitOp_IPRT: case kSupInitOp_Integrity: case kSupInitOp_RootCheck: @@ -987,9 +982,17 @@ DECLHIDDEN(void) supR3HardenedFatalMsgV(const char *pszWhere, SUPINITOP enmWha if (pid <= 0) #endif { - PFNSUPTRUSTEDERROR pfnTrustedError = supR3HardenedMainGetTrustedError(g_pszSupLibHardenedProgName); - if (pfnTrustedError) - pfnTrustedError(pszWhere, enmWhat, rc, pszMsgFmt, va); + static volatile bool s_fRecursive = false; /* Loader hooks may cause recursion. */ + if (!s_fRecursive) + { + s_fRecursive = true; + + PFNSUPTRUSTEDERROR pfnTrustedError = supR3HardenedMainGetTrustedError(g_pszSupLibHardenedProgName); + if (pfnTrustedError) + pfnTrustedError(pszWhere, enmWhat, rc, pszMsgFmt, va); + + s_fRecursive = false; + } } /* @@ -1081,6 +1084,14 @@ static void supR3HardenedMainOpenDevice(void) case VERR_NO_MEMORY: supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Kernel memory allocation/mapping failed"); + case VERR_SUPDRV_HARDENING_EVIL_HANDLE: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_SUPDRV_HARDENING_EVIL_HANDLE"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_2: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_2"); default: supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Unknown rc=%d", rc); @@ -1324,11 +1335,10 @@ static void supR3HardenedMainInitRuntime(uint32_t fFlags) * Open it and resolve the symbols. */ #if defined(RT_OS_WINDOWS) - /** @todo consider using LOAD_WITH_ALTERED_SEARCH_PATH here! */ - HMODULE hMod = LoadLibraryEx(szPath, NULL /*hFile*/, 0 /* dwFlags */); + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/); if (!hMod) supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_MODULE_NOT_FOUND, - "LoadLibraryEx(\"%s\",,) failed (rc=%d)", + "LoadLibrary \"%s\" failed (rc=%d)", szPath, GetLastError()); PFNRTR3INITEX pfnRTInitEx = (PFNRTR3INITEX)GetProcAddress(hMod, SUP_HARDENED_SYM("RTR3InitEx")); if (!pfnRTInitEx) @@ -1407,8 +1417,7 @@ static PFNSUPTRUSTEDERROR supR3HardenedMainGetTrustedError(const char *pszProgNa * Open it and resolve the symbol. */ #if defined(RT_OS_WINDOWS) - /** @todo consider using LOAD_WITH_ALTERED_SEARCH_PATH here! */ - HMODULE hMod = LoadLibraryEx(szPath, NULL /*hFile*/, 0 /* dwFlags */); + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/); if (!hMod) return NULL; FARPROC pfn = GetProcAddress(hMod, SUP_HARDENED_SYM("TrustedError")); @@ -1451,10 +1460,9 @@ static PFNSUPTRUSTEDMAIN supR3HardenedMainGetTrustedMain(const char *pszProgName * Open it and resolve the symbol. */ #if defined(RT_OS_WINDOWS) - /** @todo consider using LOAD_WITH_ALTERED_SEARCH_PATH here! */ - HMODULE hMod = LoadLibraryEx(szPath, NULL /*hFile*/, 0 /* dwFlags */); + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/); if (!hMod) - supR3HardenedFatal("supR3HardenedMainGetTrustedMain: LoadLibraryEx(\"%s\",,) failed, rc=%d\n", + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: LoadLibrary \"%s\" failed, rc=%d\n", szPath, GetLastError()); FARPROC pfn = GetProcAddress(hMod, SUP_HARDENED_SYM("TrustedMain")); if (!pfn) @@ -1535,15 +1543,37 @@ DECLHIDDEN(int) SUPR3HardenedMain(const char *pszProgName, uint32_t fFlags, int #endif /* - * Validate the installation. + * Validate the installation. On Windows we leave the files open so they + * cannot be tampered with after they've been verified. We also check + * install loader hooks and check the process integrity. */ +#ifndef RT_OS_WINDOWS supR3HardenedVerifyAll(true /* fFatal */, false /* fLeaveFilesOpen */, pszProgName); +#else + supR3HardenedWinInit(fFlags); + supR3HardenedVerifyAll(true /* fFatal */, true /* fLeaveFilesOpen */, pszProgName); + supR3HardenedWinVerifyProcess(); +#endif + +#ifdef RT_OS_WINDOWS + /* + * On Windows we'll respawn the process with a special vboxdrv arrangement + * in place to monitor access to the process for its inception. + */ + if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + && supR3HardenedWinIsReSpawnNeeded(argc, argv)) + return supR3HardenedWinReSpawn(); +#endif /* * Open the vboxdrv device. */ if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) supR3HardenedMainOpenDevice(); +#ifdef RT_OS_WINDOWS + supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_VERIFY_TRUST_READY; +#endif /* * Open the root service connection. @@ -1567,85 +1597,16 @@ DECLHIDDEN(int) SUPR3HardenedMain(const char *pszProgName, uint32_t fFlags, int * Load the IPRT, hand the SUPLib part the open driver and * call RTR3InitEx. */ + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_INIT_RUNTIME; supR3HardenedMainInitRuntime(fFlags); /* * Load the DLL/SO/DYLIB containing the actual program * and pass control to it. */ + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_GET_TRUSTED_MAIN; PFNSUPTRUSTEDMAIN pfnTrustedMain = supR3HardenedMainGetTrustedMain(pszProgName); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN; return pfnTrustedMain(argc, argv, envp); } - -#ifdef RT_OS_WINDOWS - -extern "C" int main(int argc, char **argv, char **envp); - -/** - * The executable entry point. - */ -extern "C" void __stdcall suplibHardenedWindowsMain(void) -{ - RTEXITCODE rcExit = RTEXITCODE_FAILURE; - - /* - * Convert the arguments to UTF-8. - */ - int cArgs; - PWSTR *papwszArgs = CommandLineToArgvW(GetCommandLineW(), &cArgs); /** @todo fix me! */ - if (papwszArgs) - { - char **papszArgs = (char **)HeapAlloc(GetProcessHeap(), 0 /* dwFlags*/, (cArgs + 1) * sizeof(const char **)); - if (papszArgs) - { - int iArg; - for (iArg = 0; iArg < cArgs; iArg++) - { - int cbNeeded = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, papwszArgs[iArg], -1, NULL /*pszDst*/, 0 /*cbDst*/, - NULL /*pchDefChar*/, NULL /* pfUsedDefChar */); - if (!cbNeeded) - { - suplibHardenedPrintF("CommandLineToArgvW failed on argument %d: %u\n", iArg, GetLastError()); - break; - } - - papszArgs[iArg] = (char *)HeapAlloc(GetProcessHeap(), 0 /*dwFlags*/, cbNeeded); - if (!papszArgs[iArg]) - { - suplibHardenedPrintF("HeapAlloc failed"); - break; - } - - int cbRet = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, papwszArgs[iArg], -1, papszArgs[iArg], cbNeeded, - NULL /*pchDefChar*/, NULL /* pfUsedDefChar */); - if (!cbRet) - { - suplibHardenedPrintF("CommandLineToArgvW failed on argument %d: %u\n", iArg, GetLastError()); - break; - } - } - if (iArg == cArgs) - { - papszArgs[iArg] = NULL; - - /* - * Call the main function. - */ - rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL); - } - } - else - suplibHardenedPrintF("HeapAlloc failed\n"); - } - else - suplibHardenedPrintF("CommandLineToArgvW failed\n"); - - /* - * Exit the process (never return). - */ - for (;;) - ExitProcess(rcExit); -} - -#endif diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp new file mode 100644 index 00000000000..e3ff7c0f605 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp @@ -0,0 +1,205 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library - Hardened main() no-crt routines. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#if RT_OS_WINDOWS +# include <Windows.h> +#endif + +#include <VBox/sup.h> + +#include "SUPLibInternal.h" + + +#ifdef SUP_HARDENED_NEED_CRT_FUNCTIONS + +/** memcmp */ +DECLHIDDEN(int) suplibHardenedMemComp(void const *pvDst, const void *pvSrc, size_t cbToComp) +{ + size_t const *puDst = (size_t const *)pvDst; + size_t const *puSrc = (size_t const *)pvSrc; + while (cbToComp >= sizeof(size_t)) + { + if (*puDst != *puSrc) + break; + puDst++; + puSrc++; + cbToComp -= sizeof(size_t); + } + + uint8_t const *pbDst = (uint8_t const *)puDst; + uint8_t const *pbSrc = (uint8_t const *)puSrc; + while (cbToComp > 0) + { + if (*pbDst != *pbSrc) + { + if (*pbDst < *pbSrc) + return -1; + return 1; + } + + pbDst++; + pbSrc++; + cbToComp--; + } + + return 0; +} + + +/** memcpy */ +DECLHIDDEN(void *) suplibHardenedMemCopy(void *pvDst, const void *pvSrc, size_t cbToCopy) +{ + size_t *puDst = (size_t *)pvDst; + size_t const *puSrc = (size_t const *)pvSrc; + while (cbToCopy >= sizeof(size_t)) + { + *puDst++ = *puSrc++; + cbToCopy -= sizeof(size_t); + } + + uint8_t *pbDst = (uint8_t *)puDst; + uint8_t const *pbSrc = (uint8_t const *)puSrc; + while (cbToCopy > 0) + { + *pbDst++ = *pbSrc++; + cbToCopy--; + } + + return pvDst; +} + + +/** memset */ +DECLHIDDEN(void *) suplibHardenedMemSet(void *pvDst, int ch, size_t cbToSet) +{ + uint8_t *pbDst = (uint8_t *)pvDst; + while (cbToSet > 0) + { + *pbDst++ = (uint8_t)ch; + cbToSet--; + } + + return pvDst; +} + + +/** strcpy */ +DECLHIDDEN(char *) suplibHardenedStrCopy(char *pszDst, const char *pszSrc) +{ + char *pszRet = pszDst; + char ch; + do + { + ch = *pszSrc++; + *pszDst++ = ch; + } while (ch); + return pszRet; +} + + +/** strlen */ +DECLHIDDEN(size_t) suplibHardenedStrLen(const char *psz) +{ + const char *pszStart = psz; + while (*psz) + psz++; + return psz - pszStart; +} + + +/** strcat */ +DECLHIDDEN(char *) suplibHardenedStrCat(char *pszDst, const char *pszSrc) +{ + char *pszRet = pszDst; + while (*pszDst) + pszDst++; + suplibHardenedStrCopy(pszDst, pszSrc); + return pszRet; +} + + +# ifdef RT_OS_WINDOWS +/** stricmp */ +DECLHIDDEN(int) suplibHardenedStrICmp(const char *psz1, const char *psz2) +{ + const char *pszOrg1 = psz1; + const char *pszOrg2 = psz2; + + for (;;) + { + char ch1 = *psz1++; + char ch2 = *psz2++; + if (ch1 != ch2) + { + int rc = CompareStringA(LOCALE_USER_DEFAULT, NORM_IGNORECASE, pszOrg1, -1, pszOrg2, -1); +# ifdef VBOX_STRICT + if (rc == 0) + __debugbreak(); +# endif + return rc - 2; + } + if (ch1 == 0) + return 0; + } +} +# endif + + +/** strcmp */ +DECLHIDDEN(int) suplibHardenedStrCmp(const char *psz1, const char *psz2) +{ + for (;;) + { + char ch1 = *psz1++; + char ch2 = *psz2++; + if (ch1 != ch2) + return ch1 < ch2 ? -1 : 1; + if (ch1 == 0) + return 0; + } +} + + +/** strncmp */ +DECLHIDDEN(int) suplibHardenedStrNCmp(const char *psz1, const char *psz2, size_t cchMax) +{ + while (cchMax-- > 0) + { + char ch1 = *psz1++; + char ch2 = *psz2++; + if (ch1 != ch2) + return ch1 < ch2 ? -1 : 1; + if (ch1 == 0) + break; + } + return 0; +} + +#endif /* SUP_HARDENED_NEED_CRT_FUNCTIONS */ + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp index 74b00c975b8..352c81f4522 100644 --- a/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp @@ -73,6 +73,10 @@ #include <iprt/string.h> #include "SUPLibInternal.h" +#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING) +# define SUPHNTVI_NO_NT_STUFF +# include "win/SUPHardenedVerify-win.h" +#endif /******************************************************************************* @@ -85,6 +89,13 @@ # define dirfd(d) ((d)->d_fd) #endif +/** Compare table file names with externally supplied names. */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) +# define SUP_COMP_FILENAME suplibHardenedStrICmp +#else +# define SUP_COMP_FILENAME suplibHardenedStrCmp +#endif + /******************************************************************************* * Global Variables * @@ -106,9 +117,9 @@ static SUPINSTFILE const g_aSupInstallFiles[] = { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDD2R0.r0" }, #ifdef VBOX_WITH_RAW_MODE - { kSupIFT_Dll, kSupID_AppPrivArch, false, "VMMGC.gc" }, - { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDDGC.gc" }, - { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDD2GC.gc" }, + { kSupIFT_Rc, kSupID_AppPrivArch, false, "VMMGC.gc" }, + { kSupIFT_Rc, kSupID_AppPrivArch, false, "VBoxDDGC.gc" }, + { kSupIFT_Rc, kSupID_AppPrivArch, false, "VBoxDD2GC.gc" }, #endif { kSupIFT_Dll, kSupID_SharedLib, false, "VBoxRT" SUPLIB_DLL_SUFF }, @@ -153,7 +164,7 @@ static SUPINSTFILE const g_aSupInstallFiles[] = #ifdef VBOX_WITH_MAIN { kSupIFT_Exe, kSupID_AppBin, false, "VBoxSVC" SUPLIB_EXE_SUFF }, #ifdef RT_OS_WINDOWS - { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxC" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_SharedLib, false, "VBoxC" SUPLIB_DLL_SUFF }, #else { kSupIFT_Exe, kSupID_AppPrivArch, false, "VBoxXPCOMIPCD" SUPLIB_EXE_SUFF }, { kSupIFT_Dll, kSupID_SharedLib, false, "VBoxXPCOM" SUPLIB_DLL_SUFF }, @@ -346,7 +357,7 @@ DECLHIDDEN(int) supR3HardenedVerifyFixedDir(SUPINSTDIR enmDir, bool fFatal) GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, NULL, - OPEN_ALWAYS, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, NULL); if (hDir != INVALID_HANDLE_VALUE) @@ -465,17 +476,35 @@ static int supR3HardenedVerifyFileInternal(int iFile, bool fFatal, bool fLeaveFi rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal); if (RT_SUCCESS(rc)) { -#if defined(RT_OS_WINDOWS) +#if defined(RT_OS_WINDOWS) /** @todo Need to use WCHAR on windows! */ HANDLE hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_ALWAYS, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { - /** @todo Check the type, and verify the signature (separate function so we can skip it). */ +# if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_R3_STATIC) /* Latter: Not in VBoxCpuReport and friends. */ + + char szErr[1024]; + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, szErr, sizeof(szErr)); + + uint32_t fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT; + if (pFile->enmType == kSupIFT_Rc) + fFlags |= SUPHNTVI_F_RC_IMAGE; + + rc = supHardenedWinVerifyImageByHandleNoName(hFile, fFlags, &ErrInfo); + if (RT_FAILURE(rc)) + { + rc = supR3HardenedError(rc, fFatal, "supR3HardenedVerifyFileInternal: '%s': Image verify error rc=%Rrc: %s\n", + szPath, rc, szErr); + CloseHandle(hFile); + } + else +#endif { /* it's valid. */ if (fLeaveFileOpen) @@ -490,8 +519,7 @@ static int supR3HardenedVerifyFileInternal(int iFile, bool fFatal, bool fLeaveFi int err = GetLastError(); if (!pFile->fOptional || err != ERROR_FILE_NOT_FOUND) rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal, - "supR3HardenedVerifyFileInternal: Failed to open \"%s\": err=%d\n", - szPath, err); + "supR3HardenedVerifyFileInternal: Failed to open '%s': err=%d\n", szPath, err); } #else /* UNIXY */ int fd = open(szPath, O_RDONLY, 0); @@ -580,11 +608,7 @@ static int supR3HardenedVerifySameFile(int iFile, const char *pszFilename, bool int rc = supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal); if (RT_FAILURE(rc)) return rc; -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - if (suplibHardenedStrICmp(szName, pszFilename)) -#else - if (suplibHardenedStrCmp(szName, pszFilename)) -#endif + if (SUP_COMP_FILENAME(szName, pszFilename)) { /* * Normalize the two paths and compare again. @@ -595,14 +619,14 @@ static int supR3HardenedVerifySameFile(int iFile, const char *pszFilename, bool char szName2[RTPATH_MAX]; if ( GetFullPathName(szName, RT_ELEMENTS(szName2), &szName2[0], &pszIgnored) && GetFullPathName(pszFilename, RT_ELEMENTS(szName), &szName[0], &pszIgnored)) - if (!suplibHardenedStrICmp(szName2, szName)) + if (!SUP_COMP_FILENAME(szName2, szName)) rc = VINF_SUCCESS; #else AssertCompile(RTPATH_MAX >= PATH_MAX); char szName2[RTPATH_MAX]; if ( realpath(szName, szName2) != NULL && realpath(pszFilename, szName) != NULL) - if (!suplibHardenedStrCmp(szName2, szName)) + if (!SUP_COMP_FILENAME(szName2, szName)) rc = VINF_SUCCESS; #endif @@ -644,7 +668,7 @@ DECLHIDDEN(int) supR3HardenedVerifyFixedFile(const char *pszFilename, bool fFata */ const char *pszName = supR3HardenedPathFilename(pszFilename); for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++) - if (!suplibHardenedStrCmp(pszName, g_aSupInstallFiles[iFile].pszFile)) + if (!SUP_COMP_FILENAME(pszName, g_aSupInstallFiles[iFile].pszFile)) { int rc = supR3HardenedVerifySameFile(iFile, pszFilename, fFatal); if (RT_SUCCESS(rc)) @@ -903,7 +927,7 @@ static int supR3HardenedVerifyPathSanity(const char *pszPath, PRTERRINFO pErrInf * Check that it's an absolute path and copy the volume/root specifier. */ #if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - if ( RT_C_IS_ALPHA(pszSrc[0]) + if ( !RT_C_IS_ALPHA(pszSrc[0]) || pszSrc[1] != ':' || !RTPATH_IS_SLASH(pszSrc[2])) return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'"); @@ -1441,10 +1465,14 @@ DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, * @param hNativeFile Handle to the file, verify that it's the same * as we ended up with when verifying the path. * RTHCUINTPTR_MAX means NIL here. + * @param fMaybe3rdParty Set if the file is could be a supplied by a + * third party. Different validation rules may + * apply to 3rd party code on some platforms. * @param pErrInfo Where to return extended error information. * Optional. */ -DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile, PRTERRINFO pErrInfo) +DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNativeFile, + bool fMaybe3rdParty, PRTERRINFO pErrInfo) { /* * Validate the input path and parse it. @@ -1476,10 +1504,50 @@ DECLHIDDEN(int) supR3HardenedVerifyFile(const char *pszFilename, RTHCUINTPTR hNa } /* - * Verify the file. + * Verify the file handle against the last component, if specified. */ if (hNativeFile != RTHCUINTPTR_MAX) - return supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo); + { + rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + +#ifdef RT_OS_WINDOWS + /* + * The files shall be signed on windows, verify that. + */ + HANDLE hVerify; + if (hNativeFile == RTHCUINTPTR_MAX) + hVerify = CreateFile(pszFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + else if (!DuplicateHandle(GetCurrentProcess(), (HANDLE)hNativeFile, GetCurrentProcess(), &hVerify, + GENERIC_READ, false /*bInheritHandle*/, 0 /*dwOptions*/)) + hVerify = INVALID_HANDLE_VALUE; + if (hVerify != INVALID_HANDLE_VALUE) + { + uint32_t fFlags = SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING; + if (!fMaybe3rdParty) + fFlags = SUPHNTVI_F_REQUIRE_BUILD_CERT; + const char *pszSuffix = RTPathSuffix(pszFilename); + if ( pszSuffix + && pszSuffix[0] == '.' + && ( RT_C_TO_LOWER(pszSuffix[1]) == 'r' + || RT_C_TO_LOWER(pszSuffix[1]) == 'g') + && RT_C_TO_LOWER(pszSuffix[2]) == 'c' + && pszSuffix[3] == '\0' ) + fFlags |= SUPHNTVI_F_RC_IMAGE; +# ifndef IN_SUP_R3_STATIC /* Not in VBoxCpuReport and friends. */ + rc = supHardenedWinVerifyImageByHandleNoName(hVerify, fFlags, pErrInfo); +# endif + CloseHandle(hVerify); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(GetLastError()), + "Error %u trying to open (or duplicate handle for) '%s'", GetLastError(), pszFilename); + if (RT_FAILURE(rc)) + return rc; +#endif + return VINF_SUCCESS; } diff --git a/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp b/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp index a8f41c830e8..318d8445b6e 100644 --- a/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp +++ b/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp @@ -781,6 +781,25 @@ DECLEXPORT(int) VBOXCALL SUPDrvDarwinIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) } +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + NOREF(pDevExt); + NOREF(pSession); +} + + +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); +} + + /** * Initializes any OS specific object creator fields. */ diff --git a/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c index f356a4998a5..6288a1eaa6c 100644 --- a/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c +++ b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c @@ -495,6 +495,25 @@ int VBOXCALL SUPDrvFreeBSDIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) } +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + NOREF(pDevExt); + NOREF(pSession); +} + + +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); +} + + void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) { NOREF(pObj); diff --git a/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c index 87c361987d2..96b54370330 100644 --- a/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c +++ b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c @@ -808,6 +808,25 @@ int VBOXCALL SUPDrvLinuxIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) EXPORT_SYMBOL(SUPDrvLinuxIDC); +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + NOREF(pDevExt); + NOREF(pSession); +} + + +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); +} + + /** * Initializes any OS specific object creator fields. */ diff --git a/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp index c8fa3d13d3a..7e07ad634d6 100644 --- a/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp +++ b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp @@ -363,6 +363,25 @@ DECLASM(int) VBoxDrvIOCtl(uint16_t sfn, uint8_t iCat, uint8_t iFunction, void *p } +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + NOREF(pDevExt); + NOREF(pSession); +} + + +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); +} + + void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) { NOREF(pObj); diff --git a/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c b/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c index 7b32f225826..bd0a29fd3ee 100644 --- a/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c +++ b/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c @@ -902,6 +902,25 @@ static int VBoxSupDrvErr2SolarisErr(int rc) } +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + NOREF(pDevExt); + NOREF(pSession); +} + + +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); +} + + /** * Initializes any OS specific object creator fields. */ diff --git a/src/VBox/HostDrivers/Support/testcase/Makefile.kmk b/src/VBox/HostDrivers/Support/testcase/Makefile.kmk index 90ab02dffce..6a8f959a2b7 100644 --- a/src/VBox/HostDrivers/Support/testcase/Makefile.kmk +++ b/src/VBox/HostDrivers/Support/testcase/Makefile.kmk @@ -4,7 +4,7 @@ # # -# Copyright (C) 2006-2012 Oracle Corporation +# Copyright (C) 2006-2014 Oracle Corporation # # This file is part of VirtualBox Open Source Edition (OSE), as # available from http://www.virtualbox.org. This file is free software; diff --git a/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp b/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp index 814c4191832..46d6712d769 100644 --- a/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp +++ b/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp @@ -4,7 +4,7 @@ */ /* - * Copyright (C) 2006-2012 Oracle Corporation + * Copyright (C) 2006-2014 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -28,43 +28,7 @@ /******************************************************************************* * Header Files * *******************************************************************************/ -#include <ntstatus.h> -#define WIN32_NO_STATUS -#include <Windows.h> -#include <winternl.h> - -typedef enum -{ - MemoryBasicInformation = 0, - MemoryWorkingSetList, - MemorySectionName, - MemoryBasicVlmInformation -} MEMORY_INFORMATION_CLASS; - -typedef struct -{ - UNICODE_STRING SectionFileName; - WCHAR NameBuffer[ANYSIZE_ARRAY]; -} MEMORY_SECTION_NAME; - -extern "C" -NTSYSAPI NTSTATUS NTAPI NtQueryVirtualMemory(IN HANDLE hProcess, - IN LPCVOID pvWhere, - IN MEMORY_INFORMATION_CLASS MemoryInfo, - OUT PVOID pvBuf, - IN SIZE_T cbBuf, - OUT PSIZE_T pcbReturned OPTIONAL); - -#define ProcessDebugPort ((PROCESSINFOCLASS)7 ) -#define ProcessHandleCount ((PROCESSINFOCLASS)20) -#define ProcessWow64Information ((PROCESSINFOCLASS)26) -#define ProcessImageFileName ((PROCESSINFOCLASS)27) -#define ProcessDebugObjectHandle ((PROCESSINFOCLASS)30) -#define ProcessExecuteFlags ((PROCESSINFOCLASS)34) -#define ProcessImageFileNameWin32 ((PROCESSINFOCLASS)43) -#define ProcessImageFileMapping ((PROCESSINFOCLASS)44) - - +#include <iprt/nt/nt-and-windows.h> #include <iprt/test.h> #include <iprt/string.h> @@ -303,22 +267,21 @@ static void tstQueryInformationProcess(void) RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); if (NT_SUCCESS(rcNt)) RTTestIPrintf(RTTESTLVL_ALWAYS, "BasicInfo:\n" - " UniqueProcessId = %#x (%6d)\n" + " UniqueProcessId = %#x (%6d)\n" + " InheritedFromUniqueProcessId = %#x (%6d)\n" + " ExitStatus = %#x\n" " PebBaseAddress = %p\n" - " Reserved1 = %p ExitStatus?\n" - " Reserved2a = %p AffinityMask?\n" - " Reserved2b = %p (%6d) BasePriority?\n" - " Reserved3 = %p (%6d) InheritedFromUniqueProcessId?\n" + " AffinityMask = %#zx\n" + " BasePriority = %#zx\n" , BasicInfo.UniqueProcessId, BasicInfo.UniqueProcessId, + BasicInfo.InheritedFromUniqueProcessId, BasicInfo.InheritedFromUniqueProcessId, + BasicInfo.ExitStatus, BasicInfo.PebBaseAddress, - BasicInfo.Reserved1, - BasicInfo.Reserved2[0], - BasicInfo.Reserved2[1], BasicInfo.Reserved2[1], - BasicInfo.Reserved3, BasicInfo.Reserved3 + BasicInfo.AffinityMask, + BasicInfo.BasePriority ); - /* Debugger present? */ DWORD_PTR uPtr = ~(DWORD_PTR)0; cbActual = 0; @@ -434,7 +397,7 @@ static void tstQueryInformationProcess(void) } -int main() +int main(int argc, char **argv) { RTEXITCODE rcExit = RTTestInitAndCreate("tstNtQueryStuff", &g_hTest); if (rcExit != RTEXITCODE_SUCCESS) @@ -442,10 +405,25 @@ int main() RTTestBanner(g_hTest); g_hProcess = GetCurrentProcess(); + if (argc >= 2 && argv[1][0] != '-') + { + const char *pszPid = argv[1]; + uint32_t idPid = RTStrToInt32(pszPid); - //tstQueryVirtualMemory(); - tstQueryInformationProcess(); + uint32_t fAccess = PROCESS_QUERY_INFORMATION; + if (argc >= 3) + fAccess = RTStrToInt32(argv[2]); + g_hProcess = OpenProcess(fAccess, FALSE, idPid); + if (g_hProcess == NULL) + { + RTTestIFailed("Error %u opening process %u (%s)\n", GetLastError(), idPid, pszPid); + return RTTestSummaryAndDestroy(g_hTest); + } + } + + tstQueryVirtualMemory(); + tstQueryInformationProcess(); return RTTestSummaryAndDestroy(g_hTest); } diff --git a/src/VBox/HostDrivers/Support/win/Certificates/Makefile.kup b/src/VBox/HostDrivers/Support/win/Certificates/Makefile.kup new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.cer b/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.cer Binary files differnew file mode 100644 index 00000000000..32f92899936 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.cer diff --git a/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf b/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf Binary files differnew file mode 100644 index 00000000000..486b6fd7ae5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf Binary files differnew file mode 100644 index 00000000000..7b46d69b9f5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf Binary files differnew file mode 100644 index 00000000000..e55039abcd5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf Binary files differnew file mode 100644 index 00000000000..46c6250a849 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf Binary files differnew file mode 100644 index 00000000000..3131fc27236 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf Binary files differnew file mode 100644 index 00000000000..945de02c859 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf Binary files differnew file mode 100644 index 00000000000..610b14f1ff1 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf b/src/VBox/HostDrivers/Support/win/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf Binary files differnew file mode 100644 index 00000000000..b9d8f7b8a26 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf diff --git a/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.cer b/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.cer Binary files differnew file mode 100644 index 00000000000..abb6ee6358d --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.cer diff --git a/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.taf b/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.taf Binary files differnew file mode 100644 index 00000000000..1bc36b69a7e --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Certificates/Trusted-OracleCorporationVirtualBox-51ca009816fdbd80f120e015ee75823e.taf diff --git a/src/VBox/HostDrivers/Support/win/NtCreateSection-template-amd64-syscall-type-1.h b/src/VBox/HostDrivers/Support/win/NtCreateSection-template-amd64-syscall-type-1.h new file mode 100644 index 00000000000..2b8c8c89147 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/NtCreateSection-template-amd64-syscall-type-1.h @@ -0,0 +1,78 @@ +SYSCALL(0x30); +SYSCALL(0x31); +SYSCALL(0x32); +SYSCALL(0x33); +SYSCALL(0x34); +SYSCALL(0x35); +SYSCALL(0x36); +SYSCALL(0x37); +SYSCALL(0x38); +SYSCALL(0x39); +SYSCALL(0x3A); +SYSCALL(0x3B); +SYSCALL(0x3C); +SYSCALL(0x3D); +SYSCALL(0x3E); +SYSCALL(0x3F); +SYSCALL(0x40); +SYSCALL(0x41); +SYSCALL(0x42); +SYSCALL(0x43); +SYSCALL(0x44); +SYSCALL(0x45); +SYSCALL(0x46); +SYSCALL(0x47); /* XP64/W2K3-64, Vista, Windows 7 */ +SYSCALL(0x48); /* Windows 8.0 */ +SYSCALL(0x49); /* windows 8.1 */ +SYSCALL(0x4A); +SYSCALL(0x4B); +SYSCALL(0x4C); +SYSCALL(0x4D); +SYSCALL(0x4E); +SYSCALL(0x4F); +SYSCALL(0x51); +SYSCALL(0x52); +SYSCALL(0x53); +SYSCALL(0x54); +SYSCALL(0x55); +SYSCALL(0x56); +SYSCALL(0x57); +SYSCALL(0x59); +SYSCALL(0x5A); +SYSCALL(0x5B); +SYSCALL(0x5C); +SYSCALL(0x5D); +SYSCALL(0x5E); +SYSCALL(0x5F); +SYSCALL(0x60); +SYSCALL(0x61); +SYSCALL(0x62); +SYSCALL(0x63); +SYSCALL(0x64); +SYSCALL(0x65); +SYSCALL(0x66); +SYSCALL(0x67); +SYSCALL(0x68); +SYSCALL(0x69); +SYSCALL(0x6A); +SYSCALL(0x6B); +SYSCALL(0x6C); +SYSCALL(0x6D); +SYSCALL(0x6E); +SYSCALL(0x6F); +SYSCALL(0x70); +SYSCALL(0x71); +SYSCALL(0x72); +SYSCALL(0x73); +SYSCALL(0x74); +SYSCALL(0x75); +SYSCALL(0x76); +SYSCALL(0x77); +SYSCALL(0x78); +SYSCALL(0x79); +SYSCALL(0x7A); +SYSCALL(0x7B); +SYSCALL(0x7C); +SYSCALL(0x7D); +SYSCALL(0x7E); +SYSCALL(0x7F); diff --git a/src/VBox/HostDrivers/Support/win/NtCreateSection-template-x86-syscall-type-1.h b/src/VBox/HostDrivers/Support/win/NtCreateSection-template-x86-syscall-type-1.h new file mode 100644 index 00000000000..f80a4d3fe90 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/NtCreateSection-template-x86-syscall-type-1.h @@ -0,0 +1,286 @@ +SYSCALL(0x28); +SYSCALL(0x29); +SYSCALL(0x2A); +SYSCALL(0x2B); +SYSCALL(0x2C); +SYSCALL(0x2D); +SYSCALL(0x2E); +SYSCALL(0x2F); +SYSCALL(0x30); +SYSCALL(0x31); + +SYSCALL(0x32); /* WinXP */ +SYSCALL(0x33); + +SYSCALL(0x34); /* W2K3 */ +SYSCALL(0x35); +SYSCALL(0x36); +SYSCALL(0x37); +SYSCALL(0x38); +SYSCALL(0x39); +SYSCALL(0x3A); +SYSCALL(0x3B); +SYSCALL(0x3C); +SYSCALL(0x3D); +SYSCALL(0x3E); +SYSCALL(0x3F); +SYSCALL(0x40); +SYSCALL(0x41); +SYSCALL(0x42); +SYSCALL(0x43); +SYSCALL(0x44); +SYSCALL(0x45); +SYSCALL(0x46); +SYSCALL(0x47); +SYSCALL(0x48); +SYSCALL(0x49); +SYSCALL(0x4A); + +SYSCALL(0x4B); /* Vista */ +SYSCALL(0x4C); +SYSCALL(0x4D); +SYSCALL(0x4E); +SYSCALL(0x4F); +SYSCALL(0x50); +SYSCALL(0x51); +SYSCALL(0x52); +SYSCALL(0x53); + +SYSCALL(0x54); /* Windows 7 */ +SYSCALL(0x55); +SYSCALL(0x56); +SYSCALL(0x57); +SYSCALL(0x59); +SYSCALL(0x5A); +SYSCALL(0x5B); +SYSCALL(0x5C); +SYSCALL(0x5D); +SYSCALL(0x5E); +SYSCALL(0x5F); +SYSCALL(0x60); +SYSCALL(0x61); +SYSCALL(0x62); +SYSCALL(0x63); +SYSCALL(0x64); +SYSCALL(0x65); +SYSCALL(0x66); +SYSCALL(0x67); +SYSCALL(0x68); +SYSCALL(0x69); +SYSCALL(0x6A); +SYSCALL(0x6B); +SYSCALL(0x6C); +SYSCALL(0x6D); +SYSCALL(0x6E); +SYSCALL(0x6F); +SYSCALL(0x70); +SYSCALL(0x71); +SYSCALL(0x72); +SYSCALL(0x73); +SYSCALL(0x74); +SYSCALL(0x75); +SYSCALL(0x76); +SYSCALL(0x77); +SYSCALL(0x78); +SYSCALL(0x79); +SYSCALL(0x7A); +SYSCALL(0x7B); +SYSCALL(0x7C); +SYSCALL(0x7D); +SYSCALL(0x7E); +SYSCALL(0x7F); +SYSCALL(0x80); +SYSCALL(0x81); +SYSCALL(0x82); +SYSCALL(0x83); +SYSCALL(0x84); +SYSCALL(0x85); +SYSCALL(0x86); +SYSCALL(0x87); +SYSCALL(0x88); +SYSCALL(0x89); +SYSCALL(0x8A); +SYSCALL(0x8B); +SYSCALL(0x8C); +SYSCALL(0x8D); +SYSCALL(0x8E); +SYSCALL(0x8F); +SYSCALL(0x90); +SYSCALL(0x91); +SYSCALL(0x92); +SYSCALL(0x93); +SYSCALL(0x94); +SYSCALL(0x95); +SYSCALL(0x96); +SYSCALL(0x97); +SYSCALL(0x98); +SYSCALL(0x99); +SYSCALL(0x9A); +SYSCALL(0x9B); +SYSCALL(0x9C); +SYSCALL(0x9D); +SYSCALL(0x9E); +SYSCALL(0x9F); + +SYSCALL(0x100); +SYSCALL(0x101); +SYSCALL(0x102); +SYSCALL(0x103); +SYSCALL(0x104); +SYSCALL(0x105); +SYSCALL(0x106); +SYSCALL(0x107); +SYSCALL(0x108); +SYSCALL(0x109); +SYSCALL(0x10A); +SYSCALL(0x10B); +SYSCALL(0x10C); +SYSCALL(0x10D); +SYSCALL(0x10E); +SYSCALL(0x10F); +SYSCALL(0x110); +SYSCALL(0x111); +SYSCALL(0x112); +SYSCALL(0x113); +SYSCALL(0x114); +SYSCALL(0x115); +SYSCALL(0x116); +SYSCALL(0x117); +SYSCALL(0x118); +SYSCALL(0x119); +SYSCALL(0x11A); +SYSCALL(0x11B); +SYSCALL(0x11C); +SYSCALL(0x11D); +SYSCALL(0x11E); +SYSCALL(0x11F); +SYSCALL(0x120); +SYSCALL(0x121); +SYSCALL(0x122); +SYSCALL(0x123); +SYSCALL(0x124); +SYSCALL(0x125); +SYSCALL(0x126); +SYSCALL(0x127); +SYSCALL(0x128); +SYSCALL(0x129); +SYSCALL(0x12A); +SYSCALL(0x12B); +SYSCALL(0x12C); +SYSCALL(0x12D); +SYSCALL(0x12E); +SYSCALL(0x12F); +SYSCALL(0x130); +SYSCALL(0x131); +SYSCALL(0x132); +SYSCALL(0x133); +SYSCALL(0x134); +SYSCALL(0x135); +SYSCALL(0x136); +SYSCALL(0x137); +SYSCALL(0x138); +SYSCALL(0x139); +SYSCALL(0x13A); +SYSCALL(0x13B); +SYSCALL(0x13C); +SYSCALL(0x13D); +SYSCALL(0x13E); +SYSCALL(0x13F); +SYSCALL(0x140); +SYSCALL(0x141); +SYSCALL(0x142); +SYSCALL(0x143); +SYSCALL(0x144); +SYSCALL(0x145); +SYSCALL(0x146); +SYSCALL(0x147); +SYSCALL(0x148); +SYSCALL(0x149); +SYSCALL(0x14A); +SYSCALL(0x14B); +SYSCALL(0x14C); +SYSCALL(0x14D); +SYSCALL(0x14E); +SYSCALL(0x14F); +SYSCALL(0x150); +SYSCALL(0x151); +SYSCALL(0x152); +SYSCALL(0x153); + +SYSCALL(0x154); /* Windows 8.1 */ +SYSCALL(0x155); +SYSCALL(0x156); +SYSCALL(0x157); +SYSCALL(0x158); +SYSCALL(0x159); +SYSCALL(0x15A); +SYSCALL(0x15B); +SYSCALL(0x15C); +SYSCALL(0x15D); +SYSCALL(0x15E); +SYSCALL(0x15F); +SYSCALL(0x160); +SYSCALL(0x161); +SYSCALL(0x162); +SYSCALL(0x163); +SYSCALL(0x164); +SYSCALL(0x165); +SYSCALL(0x166); +SYSCALL(0x167); +SYSCALL(0x168); +SYSCALL(0x169); +SYSCALL(0x16A); +SYSCALL(0x16B); +SYSCALL(0x16C); +SYSCALL(0x16D); +SYSCALL(0x16E); +SYSCALL(0x16F); +SYSCALL(0x170); +SYSCALL(0x171); +SYSCALL(0x172); +SYSCALL(0x173); +SYSCALL(0x174); +SYSCALL(0x175); +SYSCALL(0x176); +SYSCALL(0x177); +SYSCALL(0x178); +SYSCALL(0x179); +SYSCALL(0x17A); +SYSCALL(0x17B); +SYSCALL(0x17C); +SYSCALL(0x17D); +SYSCALL(0x17E); +SYSCALL(0x17F); +SYSCALL(0x180); +SYSCALL(0x181); +SYSCALL(0x182); +SYSCALL(0x183); +SYSCALL(0x184); +SYSCALL(0x185); +SYSCALL(0x186); +SYSCALL(0x187); +SYSCALL(0x188); +SYSCALL(0x189); +SYSCALL(0x18A); +SYSCALL(0x18B); +SYSCALL(0x18C); +SYSCALL(0x18D); +SYSCALL(0x18E); +SYSCALL(0x18F); +SYSCALL(0x190); +SYSCALL(0x191); +SYSCALL(0x192); +SYSCALL(0x193); +SYSCALL(0x194); +SYSCALL(0x195); +SYSCALL(0x196); +SYSCALL(0x197); +SYSCALL(0x198); +SYSCALL(0x199); +SYSCALL(0x19A); +SYSCALL(0x19B); +SYSCALL(0x19C); +SYSCALL(0x19D); +SYSCALL(0x19E); +SYSCALL(0x19F); + diff --git a/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp index 3c946b751b9..862b0d0c8de 100644 --- a/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp +++ b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp @@ -27,21 +27,30 @@ /******************************************************************************* * Header Files * *******************************************************************************/ +#define IPRT_NT_MAP_TO_ZW #define LOG_GROUP LOG_GROUP_SUP_DRV #include "../SUPDrvInternal.h" #include <excpt.h> #include <ntimage.h> #include <iprt/assert.h> +#include <iprt/avl.h> +#include <iprt/ctype.h> #include <iprt/initterm.h> #include <iprt/mem.h> #include <iprt/process.h> #include <iprt/power.h> +#include <iprt/spinlock.h> #include <iprt/string.h> #include <VBox/log.h> +#include <VBox/err.h> #include <iprt/asm-amd64-x86.h> +#ifdef VBOX_WITH_HARDENING +# include "SUPHardenedVerify-win.h" +#endif + /******************************************************************************* * Defined Constants And Macros * @@ -65,6 +74,22 @@ /** Win Symlink name for user access. */ #define DEVICE_NAME_DOS_USR L"\\DosDevices\\VBoxDrvU" +#ifdef VBOX_WITH_HARDENING +/** Win32 device name for hardened stub access. */ +# define DEVICE_NAME_STUB "\\\\.\\VBoxDrvStub" +/** NT device name for hardened stub access. */ +# define DEVICE_NAME_NT_STUB L"\\Device\\VBoxDrvStub" +///** Win Symlink name for hardened stub access. */ +//# define DEVICE_NAME_DOS_STUB L"\\DosDevices\\VBoxDrvStub" + + +/** 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) 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) +#endif + /** Enables the fast I/O control code path. */ #define VBOXDRV_WITH_FAST_IO @@ -72,15 +97,6 @@ /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ -#if 0 //def RT_ARCH_AMD64 -typedef struct SUPDRVEXECMEM -{ - PMDL pMdl; - void *pvMapping; - void *pvAllocation; -} SUPDRVEXECMEM, *PSUPDRVEXECMEM; -#endif - /** * Device extension used by VBoxDrvU. */ @@ -103,6 +119,122 @@ typedef SUPDRVDEVEXTUSR *PSUPDRVDEVEXTUSR; ? (PSUPDRVDEVEXT)pDevObj->DeviceExtension \ : ((PSUPDRVDEVEXTUSR)pDevObj->DeviceExtension)->pMainDrvExt ) +#ifdef VBOX_WITH_HARDENING + +/** + * Device extension used by VBoxDrvS. + */ +typedef struct SUPDRVDEVEXTSTUB +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTSTUB; +/** Pointer to the VBoxDrvS device extension. */ +typedef SUPDRVDEVEXTSTUB *PSUPDRVDEVEXTSTUB; +/** Value of SUPDRVDEVEXTSTUB::Common.u32Cookie. */ +#define SUPDRVDEVEXTSTUB_COOKIE UINT32_C(0x90abcdef) + + +/** + * 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; + /** 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 CSR + * during process creation. Only applicable to VmProcessUnconfirmed. */ + bool fCsrssFirstProcessDuplicateHandle : 1; + /** 7,: Hack to allow the supid themes service duplicate handle privileges to + * our process. */ + bool fThemesFirstProcessCreateHandle : 1; + + /** The parent PID for VM processes, otherwise NULL. */ + HANDLE hParentPid; + /** 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 + * refernece 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 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); + +#endif /* VBOX_WITH_HARDENINIG */ + /******************************************************************************* * Internal Functions * @@ -122,6 +254,18 @@ static NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj static VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1, PVOID pArgument2); 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); +#endif /******************************************************************************* @@ -174,6 +318,41 @@ static FAST_IO_DISPATCH const g_VBoxDrvFastIoDispatch = }; #endif /* VBOXDRV_WITH_FAST_IO */ +#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 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; + +# 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 +#endif + /** * Takes care of creating the devices and their symbolic links. @@ -191,9 +370,9 @@ static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXT), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjSys); if (NT_SUCCESS(rcNt)) { - UNICODE_STRING DosName; - RtlInitUnicodeString(&DosName, DEVICE_NAME_DOS_SYS); - rcNt = IoCreateSymbolicLink(&DosName, &DevName); + UNICODE_STRING DosNameSys; + RtlInitUnicodeString(&DosNameSys, DEVICE_NAME_DOS_SYS); + rcNt = IoCreateSymbolicLink(&DosNameSys, &DevName); if (NT_SUCCESS(rcNt)) { /* @@ -203,24 +382,49 @@ static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTUSR), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjUsr); if (NT_SUCCESS(rcNt)) { - UNICODE_STRING DosName; - RtlInitUnicodeString(&DosName, DEVICE_NAME_DOS_USR); - rcNt = IoCreateSymbolicLink(&DosName, &DevName); + UNICODE_STRING DosNameUsr; + RtlInitUnicodeString(&DosNameUsr, DEVICE_NAME_DOS_USR); + rcNt = IoCreateSymbolicLink(&DosNameUsr, &DevName); if (NT_SUCCESS(rcNt)) { PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; pDevExtUsr->pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; pDevExtUsr->u32Cookie = SUPDRVDEVEXTUSR_COOKIE; - /* Done. */ - return rcNt; - } +#ifdef VBOX_WITH_HARDENING + /* + * Hardened stub device. + */ + RtlInitUnicodeString(&DevName, DEVICE_NAME_NT_STUB); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTSTUB), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjStub); + if (NT_SUCCESS(rcNt)) + { + //UNICODE_STRING DosNameStub; + //RtlInitUnicodeString(&DosNameStub, DEVICE_NAME_DOS_STUB); + //rcNt = IoCreateSymbolicLink(&DosNameStub, &DevName); + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtStub->Common.u32Cookie = SUPDRVDEVEXTSTUB_COOKIE; +#endif + + /* Done. */ + return rcNt; +#ifdef VBOX_WITH_HARDENING + } - /* Bail out. */ + /* Bail out. */ + IoDeleteDevice(g_pDevObjStub); + g_pDevObjUsr = NULL; + } + IoDeleteSymbolicLink(&DosNameUsr); +#endif + } IoDeleteDevice(g_pDevObjUsr); g_pDevObjUsr = NULL; } - IoDeleteSymbolicLink(&DosName); + IoDeleteSymbolicLink(&DosNameSys); } IoDeleteDevice(g_pDevObjSys); g_pDevObjSys = NULL; @@ -240,9 +444,28 @@ static void vboxdrvNtDestroyDevices(void) RtlInitUnicodeString(&DosName, DEVICE_NAME_DOS_USR); rcNt = IoDeleteSymbolicLink(&DosName); - PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; - pDevExtUsr->pMainDrvExt = NULL; +#ifdef VBOX_WITH_HARDENING + //RtlInitUnicodeString(&DosName, DEVICE_NAME_DOS_STUB); + //rcNt = IoDeleteSymbolicLink(&DosName); +#endif + 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; + } +#endif + +#ifdef VBOX_WITH_HARDENING + IoDeleteDevice(g_pDevObjStub); + g_pDevObjStub = NULL; +#endif IoDeleteDevice(g_pDevObjUsr); g_pDevObjUsr = NULL; IoDeleteDevice(g_pDevObjSys); @@ -272,74 +495,94 @@ ULONG _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) #endif /* - * Create device. - * (That means creating a device object and a symbolic link so the DOS - * subsystems (OS/2, win32, ++) can access the device.) + * Initialize the runtime (IPRT). */ - NTSTATUS rcNt = vboxdrvNtCreateDevices(pDrvObj); - if (NT_SUCCESS(rcNt)) + NTSTATUS rcNt; + int vrc = RTR0Init(0); + if (RT_SUCCESS(vrc)) { - int vrc = RTR0Init(0); - if (RT_SUCCESS(vrc)) - { - Log(("VBoxDrv::DriverEntry\n")); + Log(("VBoxDrv::DriverEntry\n")); +#ifdef VBOX_WITH_HARDENING + /* + * Initialize process protection. + */ + rcNt = supdrvNtProtectInit(); + if (NT_SUCCESS(rcNt)) +#endif + { /* - * Initialize the device extension. + * Create device. + * (That means creating a device object and a symbolic link so the DOS + * subsystems (OS/2, win32, ++) can access the device.) */ - PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; - memset(pDevExt, 0, sizeof(*pDevExt)); - - vrc = supdrvInitDevExt(pDevExt, sizeof(SUPDRVSESSION)); - if (!vrc) + rcNt = vboxdrvNtCreateDevices(pDrvObj); + if (NT_SUCCESS(rcNt)) { /* - * Setup the driver entry points in pDrvObj. + * Initialize the device extension. */ - 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] = VBoxDrvNtNotSupportedStub; - pDrvObj->MajorFunction[IRP_MJ_WRITE] = VBoxDrvNtNotSupportedStub; + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); - /* more? */ + 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] = VBoxDrvNtNotSupportedStub; + 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; + /* Fast I/O to speed up guest execution roundtrips. */ + pDrvObj->FastIoDispatch = (PFAST_IO_DISPATCH)&g_VBoxDrvFastIoDispatch; #endif - /* Register ourselves for power state changes. */ - UNICODE_STRING CallbackName; - OBJECT_ATTRIBUTES Attr; + /* + * Register ourselves for power state changes. We don't + * currently care if this fails. + */ + UNICODE_STRING CallbackName; + RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); - RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); - InitializeObjectAttributes(&Attr, &CallbackName, OBJ_CASE_INSENSITIVE, NULL, NULL); + 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); + rcNt = ExCreateCallback(&pDevExt->pObjPowerCallback, &Attr, TRUE, TRUE); + if (rcNt == STATUS_SUCCESS) + pDevExt->hPowerCallback = ExRegisterCallback(pDevExt->pObjPowerCallback, + VBoxPowerDispatchCallback, + g_pDevObjSys); - Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); - return STATUS_SUCCESS; - } + /* + * Done! Returning success! + */ + Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); + return STATUS_SUCCESS; + } - Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); - rcNt = VBoxDrvNtErr2NtStatus(vrc); + Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); - RTR0Term(); - } - else - { - Log(("RTR0Init failed with vrc=%d!\n", vrc)); - rcNt = VBoxDrvNtErr2NtStatus(vrc); + vboxdrvNtDestroyDevices(); + } +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif } - - vboxdrvNtDestroyDevices(); + RTR0Term(); + } + else + { + Log(("RTR0Init failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); } if (NT_SUCCESS(rcNt)) rcNt = STATUS_INVALID_PARAMETER; @@ -368,6 +611,9 @@ void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) * We ASSUME that it's not possible to unload a driver with open handles. */ supdrvDeleteDevExt(pDevExt); +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif RTR0Term(); vboxdrvNtDestroyDevices(); @@ -376,6 +622,37 @@ void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) /** + * 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. @@ -384,49 +661,180 @@ void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) { Log(("VBoxDrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); - const bool fUnrestricted = pDevObj == g_pDevObjSys; - PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack->FileObject; - PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + 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) - { - pIrp->IoStatus.Status = STATUS_NOT_A_DIRECTORY; - pIrp->IoStatus.Information = 0; - IoCompleteRequest(pIrp, IO_NO_INCREMENT); - return STATUS_NOT_A_DIRECTORY; - } + 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 there be one - * to create a session. + * VBoxDrvNtInternalDeviceControl. The first request will be one to + * create a session. */ NTSTATUS rcNt; if (pIrp->RequestorMode == KernelMode) - rcNt = STATUS_SUCCESS; + { + if (pDevObj == g_pDevObjSys) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + rcNt = STATUS_ACCESS_DENIED; + } else { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) /* - * Call common code for the rest. + * Make sure no debuggers are attached to non-user processes. */ - pFileObj->FsContext = NULL; - PSUPDRVSESSION pSession; - int rc = supdrvCreateSession(pDevExt, true /*fUser*/, fUnrestricted, &pSession); - if (!rc) - pFileObj->FsContext = pSession; - rcNt = pIrp->IoStatus.Status = VBoxDrvNtErr2NtStatus(rc); - } + 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; - pIrp->IoStatus.Information = 0; - IoCompleteRequest(pIrp, IO_NO_INCREMENT); +#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); + } - return rcNt; + 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)) + { + 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_ACCESS_DENIED; + } + supdrvNtProtectRelease(pNtProtect); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process.\n", PsGetProcessId(PsGetCurrentProcess()))); + rc = VERR_ACCESS_DENIED; + } + } + /* + * Call common code to create an unprivileged session. + */ + else + { + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, false /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { + 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)) + { + 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. */ } @@ -441,20 +849,32 @@ 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; - PSUPDRVSESSION pSession = (PSUPDRVSESSION)pFileObj->FsContext; - Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); - if (pSession) +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) { - supdrvSessionRelease(pSession); - pFileObj->FsContext = NULL; + 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 +#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. */ + } } - pIrp->IoStatus.Information = 0; - pIrp->IoStatus.Status = STATUS_SUCCESS; - IoCompleteRequest(pIrp, IO_NO_INCREMENT); - - return STATUS_SUCCESS; + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); } @@ -469,21 +889,232 @@ 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; - PSUPDRVSESSION pSession = (PSUPDRVSESSION)pFileObj->FsContext; - Log(("VBoxDrvNtClose: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); - if (pSession) +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) { - supdrvSessionRelease(pSession); - pFileObj->FsContext = NULL; + 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 +#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. */ + } } - pIrp->IoStatus.Information = 0; - pIrp->IoStatus.Status = STATUS_SUCCESS; - IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + - return STATUS_SUCCESS; +#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 uFunction The function. + * @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) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + + /* + * Check the input a little bit and get a the session references. + */ + 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 == SUP_IOCTL_FAST_DO_RAW_RUN + || uCmd == SUP_IOCTL_FAST_DO_HM_RUN + || uCmd == SUP_IOCTL_FAST_DO_NOP) + { + int rc = supdrvIOCtlFast(uCmd, (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(); + } + } + else + 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(NonPagedPool, cbBuf, 'VBox'); + if (pHdr) + { + __try + { + RtlCopyMemory(pHdr, pvInput, cbInput); + if (cbInput < cbBuf) + RtlZeroMemory((uint8_t *)pHdr + cbInput, cbBuf - cbInput); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + } + else + rcNt = STATUS_NO_MEMORY; + if (NT_SUCCESS(rcNt)) + { + /* + * Now call the common code to do the real work. + */ + rc = supdrvIOCtl(uCmd, pDevExt, pSession, pHdr); + 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)); + } + ExFreePoolWithTag(pHdr, 'VBox'); + } + 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 */ #ifdef VBOXDRV_WITH_FAST_IO @@ -678,26 +1309,41 @@ static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOL */ NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) { + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(pDevObj, pIrp); + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); - PSUPDRVSESSION pSession = (PSUPDRVSESSION)pStack->FileObject->FsContext; + 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. */ - ULONG ulCmd = pStack->Parameters.DeviceIoControl.IoControlCode; - if ( ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN + 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 ulCmd = pStack->Parameters.DeviceIoControl.IoControlCode; + if ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN || ulCmd == SUP_IOCTL_FAST_DO_HM_RUN || ulCmd == SUP_IOCTL_FAST_DO_NOP) - && pSession->fUnrestricted == true) - { - int rc = supdrvIOCtlFast(ulCmd, (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, pDevExt, pSession); + { + int rc = supdrvIOCtlFast(ulCmd, (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, pDevExt, pSession); - /* Complete the I/O request. */ - NTSTATUS rcNt = pIrp->IoStatus.Status = RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; - IoCompleteRequest(pIrp, IO_NO_INCREMENT); - return rcNt; + /* Complete the I/O request. */ + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); + } } return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack); @@ -786,6 +1432,7 @@ static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSes /* complete the request. */ pIrp->IoStatus.Status = rcNt; pIrp->IoStatus.Information = cbOut; + supdrvSessionRelease(pSession); IoCompleteRequest(pIrp, IO_NO_INCREMENT); return rcNt; } @@ -799,6 +1446,8 @@ static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSes */ NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) { + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(pDevObj, pIrp); + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); PFILE_OBJECT pFileObj = pStack ? pStack->FileObject : NULL; @@ -928,6 +1577,36 @@ VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1 /** + * 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; + } +#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); +} + + +/** * Initializes any OS specific object creator fields. */ void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) @@ -1081,10 +1760,10 @@ int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, c case /* 0xC0000263 */ STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: rc = VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND; break; - case 0xC0000428 /* STATUS_INVALID_IMAGE_HASH */ : + case /* 0xC0000428 */ STATUS_INVALID_IMAGE_HASH: rc = VERR_LDR_IMAGE_HASH; break; - case 0xC000010E /* STATUS_IMAGE_ALREADY_LOADED */ : + 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; @@ -1508,6 +2187,12 @@ static NTSTATUS VBoxDrvNtErr2NtStatus(int rc) 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)) + | UINT32_C(0xe9860000) ); /* STATUS_SEVERITY_ERROR + C-bit + facility 0x986 */ + } return STATUS_UNSUCCESSFUL; } @@ -1546,3 +2231,1779 @@ SUPR0DECL(int) SUPR0Printf(const char *pszFormat, ...) return 0; } #endif + + +#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; +} + + +/** + * 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()); + WCHAR wszApiPort[48]; + if (uSessionId == 0) + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + else + { + char szTmp[64]; + ssize_t cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), 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), szTmp); + 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, + *LpcPortObjectType, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if (!NT_SUCCESS(rcNt)) + { + SUPR0Printf("vboxdrv: Error opening '%ls': %#x\n", wszApiPort, rcNt); + return 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. + */ + ULONG cbNeeded = _64K; + uint32_t cbBuf; + uint8_t *pbBuf = NULL; + do + { + cbBuf = RT_ALIGN(cbNeeded + _4K, _64K); + 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) +{ + /* + * Check the process name. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pAnnoyingProcess, "svchost.exe")) + return false; + + /** @todo Come up with more checks. */ + + return true; +} + + +/** @} */ + + +/** @name Process Creation Callbacks. + * @{ */ + +/** + * 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->fCsrssFirstProcessDuplicateHandle = true; + 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) + { + 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; + 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, nothing to do. */ + 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); +} + + +/** + * 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) +{ + /* + * 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); +} + +/** @} */ + + +/** @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); + 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: ctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } + 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. + + 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 + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess())) + { + if ( !pOpInfo->KernelHandle + && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == 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 + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) + ) + { + pNtProtect->fCsrssFirstProcessCreateHandle = false; + if (pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == 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) + && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == 0x1478 /* 6.1.7600.16385 (win7_rtm.090713-1255) */ + && pNtProtect->fThemesFirstProcessCreateHandle + && supdrvNtProtectIsFrigginThemesService(pNtProtect, PsGetCurrentProcess())) + { + pNtProtect->fThemesFirstProcessCreateHandle = true; /* Only once! */ + fAllowedRights |= PROCESS_DUP_HANDLE; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: ctx=%04zx/%p wants %#x to %p/pid=%04zx [%d], allow %#x => %#x; %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()))); + + 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 + { + /* 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->fCsrssFirstProcessDuplicateHandle + && pNtProtect->hParentPid + == PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) + ) + { + pNtProtect->fCsrssFirstProcessDuplicateHandle = false; + if (pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess == s_fCsrssStupidDesires) + { + /* 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. */ + } + } + + 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->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); + 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); + 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: ctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] self\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind)); + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstThreadCreateHandle = false; + } + 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 + && 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) + && 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: ctx=%04zx/%p wants %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()))); + + 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: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] self\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 + { + /* 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() + && 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: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d], allow %#x => %#x; %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, 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); + 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->hCsrssPid = NULL; + pNtProtect->pCsrssProcess = NULL; + + if (fLink) + { + RTSpinlockAcquire(g_hNtProtectLock); + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtProtect->AvlCore); + RTSpinlockRelease(g_hNtProtectLock); + + if (!fSuccess) + { + /* Duplicate entry, fail. */ + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC_DEAD; + RTMemFree(pNtProtect); + return VERR_ACCESS_DENIED; + } + } + + *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); + PSUPDRVNTPROTECT pRemoved = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pNtProtect->AvlCore.Key); + + PSUPDRVNTPROTECT pRemovedChild = NULL; + 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 = ASMAtomicIncU32(&pChild->cRefs); + if (!cChildRefs) + pRemovedChild = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pChild->AvlCore.Key); + else + pChild = NULL; + } + } + else + AssertRelease(pNtProtect->enmProcessKind != kSupDrvNtProtectKind_VmProcessUnconfirmed); + + RTSpinlockRelease(g_hNtProtectLock); + Assert(pRemoved == pNtProtect); + Assert(pRemovedChild == pChild); + + 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; +} + + +/** + * 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. + */ +static int supdrvNtProtectRestrictHandlesToProcessAndThread(PSUPDRVNTPROTECT pNtProtect) +{ + /* + * 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. + */ + uint32_t cbBuf = _256K; + 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); + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + return VERR_NO_MEMORY; + rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + } + if (!NT_SUCCESS(rcNt)) + { + RTMemFree(pbBuf); + return RTErrConvertFromNtStatus(rcNt); + } + } + + /* + * Walk the information and look for handles to the two objects we're protecting. + */ + int rc = VINF_SUCCESS; + + 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; + + SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; + ULONG_PTR i = pInfo->NumberOfHandles; + AssertRelease(RT_OFFSETOF(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 is 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 one open process handle in + Windows 8.1 and later. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) + && cSystemProcessHandles < 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 + continue; + + /* 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\n", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType)); + rc = VERR_SUPDRV_HARDENING_EVIL_HANDLE; + } + } + + 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; + if (pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectRestrictHandlesToProcessAndThread(pNtProtect); + if (RT_SUCCESS(rc)) + { + char szErr[256]; + RT_ZERO(szErr); + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, szErr, sizeof(szErr)); + + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), &ErrInfo); + if (RT_FAILURE(rc)) + RTLogWriteDebugger(szErr, strlen(szErr)); + } + + /* + * Upgrade and return. + */ + 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; + + /* 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; + 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); + 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; + + 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"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"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 + ZwQueryVolumeInformationFile with a different eax value. */ +# 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; + } + + + /* 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; + + /* Image stuff + certificates. */ + NTSTATUS rcNt; + 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[] = + { + { + PsProcessType, + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ProcessHandlePre, + supdrvNtProtectCallback_ProcessHandlePost, + }, + { + PsThreadType, + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ThreadHandlePre, + supdrvNtProtectCallback_ThreadHandlePost, + }, + }; + 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); + + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + return rcNt; +} + +#endif /* VBOX_WITH_HARDENING */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm index 6c5412a5eb9..a6ae8d63482 100644 --- a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm +++ b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm @@ -4,7 +4,7 @@ ; ; -; Copyright (C) 2006-2010 Oracle Corporation +; Copyright (C) 2006-2014 Oracle Corporation ; ; This file is part of VirtualBox Open Source Edition (OSE), as ; available from http://www.virtualbox.org. This file is free software; @@ -44,3 +44,76 @@ BEGINPROC SUPR0Printf ENDPROC SUPR0Printf %endif + +%ifdef RT_ARCH_X86 +; +; Faking up ZwQueryVirtualMemory on XP and W2K3 where it's not exported. +; Using ZwQueryVolumeInformationFile as a helper. +; +extern IMPNAME(ZwQueryVolumeInformationFile@20) + +BEGINPROC supdrvNtQueryVirtualMemory_Xxx + %macro NtQueryVirtualMemorySyscall 1 + GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 + mov eax, %1 + jmp supdrvNtQueryVirtualMemory_Jump + %endm + NtQueryVirtualMemorySyscall 0xAF + NtQueryVirtualMemorySyscall 0xB0 + NtQueryVirtualMemorySyscall 0xB1 + NtQueryVirtualMemorySyscall 0xB2 + NtQueryVirtualMemorySyscall 0xB3 + NtQueryVirtualMemorySyscall 0xB4 + NtQueryVirtualMemorySyscall 0xB5 + NtQueryVirtualMemorySyscall 0xB6 + NtQueryVirtualMemorySyscall 0xB7 + NtQueryVirtualMemorySyscall 0xB8 + NtQueryVirtualMemorySyscall 0xB9 + NtQueryVirtualMemorySyscall 0xBA + NtQueryVirtualMemorySyscall 0xBB + NtQueryVirtualMemorySyscall 0xBC + NtQueryVirtualMemorySyscall 0xBD + NtQueryVirtualMemorySyscall 0xBE + +supdrvNtQueryVirtualMemory_Jump: + mov edx, IMP2(ZwQueryVolumeInformationFile@20) + lea edx, [edx + 5] + jmp edx +ENDPROC supdrvNtQueryVirtualMemory_Xxx + +%endif + +%ifdef RT_ARCH_AMD64 +; +; Faking up ZwQueryVirtualMemory on XP64 and W2K3-64 where it's not exported. +; The C code locates and verifies the essentials in ZwRequestWaitReplyPort. +; +extern NAME(g_pfnKiServiceLinkage) +extern NAME(g_pfnKiServiceInternal) +BEGINPROC supdrvNtQueryVirtualMemory_Xxx + %macro NtQueryVirtualMemorySyscall 1 + GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 + mov eax, %1 + jmp supdrvNtQueryVirtualMemory_Jump + %endm + + NtQueryVirtualMemorySyscall 0x1F + NtQueryVirtualMemorySyscall 0x20 + NtQueryVirtualMemorySyscall 0x21 + NtQueryVirtualMemorySyscall 0x22 + NtQueryVirtualMemorySyscall 0x23 + +supdrvNtQueryVirtualMemory_Jump: + cli + mov r10, rsp ; save call frame pointer. + mov r11, [NAME(g_pfnKiServiceLinkage) wrt rip] + push 0 + push 0 + push r10 ; call frame pointer (incoming rsp). + pushfq + push 10h + push r11 ; r11 = KiServiceLinkage (ret w/ unwind info) + jmp qword [NAME(g_pfnKiServiceInternal) wrt rip] +ENDPROC supdrvNtQueryVirtualMemory_Xxx +%endif + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h b/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h new file mode 100644 index 00000000000..7ab444b49e2 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h @@ -0,0 +1,149 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Verification, Windows. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +#ifndef ___win_SUPHardenedVerify_win_h +#define ___win_SUPHardenedVerify_win_h + +#include <iprt/types.h> +#include <iprt/crypto/x509.h> + +RT_C_DECLS_BEGIN + +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +DECLHIDDEN(int) supHardenedWinInitImageVerifier(PRTERRINFO pErrInfo); +DECLHIDDEN(void) supHardenedWinTermImageVerifier(void); +DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo); + +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, bool *pfCacheable, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByHandleNoName(HANDLE hFile, uint32_t fFlags, PRTERRINFO pErrInfo); +/** @name SUPHNTVI_F_XXX - Flags for supHardenedWinVerifyImageByHandle. + * @{ */ +/** The signing certificate must be the same as the one the VirtualBox build + * was signed with. */ +# define SUPHNTVI_F_REQUIRE_BUILD_CERT RT_BIT(0) +/** Require kernel code signing level. */ +# define SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING RT_BIT(1) +/** Require the image to force the memory mapper to do signature checking. */ +# define SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT RT_BIT(2) +/** Whether to allow image verification by catalog file. */ +# define SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION RT_BIT(3) +/** Raw-mode context image, always 32-bit. */ +# define SUPHNTVI_F_RC_IMAGE RT_BIT(31) +/** @} */ + +/** Which directory under the system root to get. */ +typedef enum SUPHARDNTSYSROOTDIR +{ + kSupHardNtSysRootDir_System32 = 0, + kSupHardNtSysRootDir_WinSxS, +} SUPHARDNTSYSROOTDIR; + +DECLHIDDEN(int) supHardNtGetSystemRootDir(void *pvBuf, uint32_t cbBuf, SUPHARDNTSYSROOTDIR enmDir, PRTERRINFO pErrInfo); + +# ifndef SUPHNTVI_NO_NT_STUFF + +/** Typical system root directory buffer. */ +typedef struct SUPSYSROOTDIRBUF +{ + UNICODE_STRING UniStr; + WCHAR awcBuffer[260]; +} SUPSYSROOTDIRBUF; +extern SUPSYSROOTDIRBUF g_System32NtPath; +extern SUPSYSROOTDIRBUF g_WinSxSNtPath; +extern SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath; +extern uint32_t g_offSupLibHardenedExeNtName; + +/** Pointer to NtQueryVirtualMemory. */ +typedef NTSTATUS (NTAPI *PFNNTQUERYVIRTUALMEMORY)(HANDLE, void const *, MEMORY_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); +extern PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory; + +# endif /* SUPHNTVI_NO_NT_STUFF */ + +/** Creates a combined NT version number for simple comparisons. */ +#define SUP_MAKE_NT_VER_COMBINED(a_uMajor, a_uMinor, a_uBuild, a_uSpMajor, a_uSpMinor) \ + ( ((uint32_t)((a_uMajor) & UINT32_C(0xf)) << 28) \ + | ((uint32_t)((a_uMinor) & UINT32_C(0xf)) << 24) \ + | ((uint32_t)((a_uBuild) & UINT32_C(0xffff)) << 8) \ + | ((uint32_t)((a_uSpMajor) & UINT32_C(0xf)) << 4) \ + | RT_MIN((uint32_t)(a_uSpMinor), UINT32_C(0xf)) ) +/** Simple version of SUP_MAKE_NT_VER_COMBINED. */ +#define SUP_MAKE_NT_VER_SIMPLE(a_uMajor, a_uMinor) SUP_MAKE_NT_VER_COMBINED(a_uMajor, a_uMinor, 0, 0, 0) +extern uint32_t g_uNtVerCombined; + +/** Combined NT version number for XP. */ +#define SUP_NT_VER_XP SUP_MAKE_NT_VER_SIMPLE(5,1) +/** Combined NT version number for Vista. */ +#define SUP_NT_VER_VISTA SUP_MAKE_NT_VER_SIMPLE(6,0) +/** Combined NT version number for Windows 7. */ +#define SUP_NT_VER_W70 SUP_MAKE_NT_VER_SIMPLE(6,1) +/** Combined NT version number for Windows 8.0. */ +#define SUP_NT_VER_W80 SUP_MAKE_NT_VER_SIMPLE(6,2) +/** Combined NT version number for Windows 8.1. */ +#define SUP_NT_VER_W81 SUP_MAKE_NT_VER_SIMPLE(6,3) + +# endif + +# ifndef IN_SUP_HARDENED_R3 +# include <iprt/mem.h> +# include <iprt/string.h> + +# define suplibHardenedAllocZ RTMemAllocZ +# define suplibHardenedReAlloc RTMemRealloc +# define suplibHardenedFree RTMemFree +# define suplibHardenedMemComp memcmp +# define suplibHardenedMemCopy memcpy +# define suplibHardenedMemSet memset +# define suplibHardenedStrCopy strcpy +# define suplibHardenedStrLen strlen +# define suplibHardenedStrCat strcat +# define suplibHardenedStrCmp strcmp +# define suplibHardenedStrNCmp strncmp +# define suplibHardenedStrICmp stricmp +# else /* IN_SUP_HARDENED_R3 */ +# include <iprt/mem.h> +#if 0 +# define memcmp suplibHardenedMemComp +# define memcpy suplibHardenedMemCopy +# define memset suplibHardenedMemSet +# define strcpy suplibHardenedStrCopy +# define strlen suplibHardenedStrLen +# define strcat suplibHardenedStrCat +# define strcmp suplibHardenedStrCmp +# define strncmp suplibHardenedStrNCmp +# define stricmp suplibHardenedStrICmp +#endif +DECLHIDDEN(void *) suplibHardenedAllocZ(size_t cb); +DECLHIDDEN(void *) suplibHardenedReAlloc(void *pvOld, size_t cbNew); +DECLHIDDEN(void) suplibHardenedFree(void *pv); +# endif /* IN_SUP_HARDENED_R3 */ + +#endif /* SUP_CERTIFICATES_ONLY */ + +RT_C_DECLS_END + +#endif + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp new file mode 100644 index 00000000000..a66839c4410 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp @@ -0,0 +1,1873 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Image Verification, Windows. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef IN_RING0 +# define IPRT_NT_MAP_TO_ZW +# include <iprt/nt/nt.h> +# include <ntimage.h> +#else +# include <iprt/nt/nt-and-windows.h> +# include "Wintrust.h" +# include "Softpub.h" +# include "mscat.h" +# ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +# endif +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/crypto/pkcs7.h> +#include <iprt/crypto/store.h> + +#ifdef IN_RING0 +# include "SUPDrvInternal.h" +#else +# include "SUPLibInternal.h" +#endif +#include "win/SUPHardenedVerify-win.h" + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +/** The size of static hash (output) buffers. + * Avoids dynamic allocations and cleanups for of small buffers as well as extra + * calls for getting the appropriate buffer size. The largest digest in regular + * use by current windows version is SHA-512, we double this and hope it's + * enough a good while. */ +#define SUPHARDNTVI_MAX_CAT_HASH_SIZE 128 + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * SUP image verifier loader reader instance. + */ +typedef struct SUPHNTVIRDR +{ + /** The core reader structure. */ + RTLDRREADER Core; + /** The file handle . */ + HANDLE hFile; + /** Current file offset. */ + RTFOFF off; + /** The file size. */ + RTFOFF cbFile; + /** Flags for the verification callback, SUPHNTVI_F_XXX. */ + uint32_t fFlags; + /** The executable timstamp in second since unix epoch. */ + uint64_t uTimestamp; + /** Log name. */ + char szFilename[1]; +} SUPHNTVIRDR; +/** Pointer to an SUP image verifier loader reader instance. */ +typedef SUPHNTVIRDR *PSUPHNTVIRDR; + + +#ifdef IN_RING3 +typedef LONG (WINAPI * PFNWINVERIFYTRUST)(HWND hwnd, GUID const *pgActionID, PVOID pWVTData); +typedef BOOL (WINAPI * PFNCRYPTCATADMINACQUIRECONTEXT)(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, DWORD dwFlags); +typedef BOOL (WINAPI * PFNCRYPTCATADMINACQUIRECONTEXT2)(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, PCWSTR pwszHashAlgorithm, + struct _CERT_STRONG_SIGN_PARA const *pStrongHashPolicy, DWORD dwFlags); +typedef BOOL (WINAPI * PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE)(HANDLE hFile, DWORD *pcbHash, BYTE *pbHash, DWORD dwFlags); +typedef BOOL (WINAPI * PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2)(HCATADMIN hCatAdmin, HANDLE hFile, DWORD *pcbHash, + BYTE *pbHash, DWORD dwFlags); +typedef HCATINFO (WINAPI *PFNCRYPTCATADMINENUMCATALOGFROMHASH)(HCATADMIN hCatAdmin, BYTE *pbHash, DWORD cbHash, + DWORD dwFlags, HCATINFO *phPrevCatInfo); +typedef BOOL (WINAPI * PFNCRYPTCATADMINRELEASECATALOGCONTEXT)(HCATADMIN hCatAdmin, HCATINFO hCatInfo, DWORD dwFlags); +typedef BOOL (WINAPI * PFNCRYPTCATDADMINRELEASECONTEXT)(HCATADMIN hCatAdmin, DWORD dwFlags); +typedef BOOL (WINAPI * PFNCRYPTCATCATALOGINFOFROMCONTEXT)(HCATINFO hCatInfo, CATALOG_INFO *psCatInfo, DWORD dwFlags); + +typedef HCERTSTORE (WINAPI *PFNCERTOPENSTORE)(PCSTR pszStoreProvider, DWORD dwEncodingType, HCRYPTPROV_LEGACY hCryptProv, + DWORD dwFlags, const void *pvParam); +typedef BOOL (WINAPI *PFNCERTCLOSESTORE)(HCERTSTORE hCertStore, DWORD dwFlags); +typedef PCCERT_CONTEXT (WINAPI *PFNCERTENUMCERTIFICATESINSTORE)(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); +#endif + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** The build certificate. */ +static RTCRX509CERTIFICATE g_BuildX509Cert; + +/** Store for root software publisher certificates. */ +static RTCRSTORE g_hSpcRootStore = NIL_RTCRSTORE; +/** Store for root NT kernel certificates. */ +static RTCRSTORE g_hNtKernelRootStore = NIL_RTCRSTORE; + +/** Store containing SPC, NT kernel signing, and timestamp root certificates. */ +static RTCRSTORE g_hSpcAndNtKernelRootStore = NIL_RTCRSTORE; +/** Store for supplemental certificates for use with + * g_hSpcAndNtKernelRootStore. */ +static RTCRSTORE g_hSpcAndNtKernelSuppStore = NIL_RTCRSTORE; + +/** The full \\SystemRoot\\System32 path. */ +SUPSYSROOTDIRBUF g_System32NtPath; +/** The full \\SystemRoot\\WinSxS path. */ +SUPSYSROOTDIRBUF g_WinSxSNtPath; + +/** Set after we've retrived other SPC root certificates from the system. */ +static bool g_fHaveOtherRoots = false; + +#if defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3) +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED and + * SUP_MAKE_NT_VER_SIMPLE. */ +uint32_t g_uNtVerCombined; +#endif + +#ifdef IN_RING3 +/** Pointer to WinVerifyTrust. */ +PFNWINVERIFYTRUST g_pfnWinVerifyTrust; +/** Pointer to CryptCATAdminAcquireContext. */ +PFNCRYPTCATADMINACQUIRECONTEXT g_pfnCryptCATAdminAcquireContext; +/** Pointer to CryptCATAdminAcquireContext2 if available. */ +PFNCRYPTCATADMINACQUIRECONTEXT2 g_pfnCryptCATAdminAcquireContext2; +/** Pointer to CryptCATAdminCalcHashFromFileHandle. */ +PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE g_pfnCryptCATAdminCalcHashFromFileHandle; +/** Pointer to CryptCATAdminCalcHashFromFileHandle2. */ +PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2 g_pfnCryptCATAdminCalcHashFromFileHandle2; +/** Pointer to CryptCATAdminEnumCatalogFromHash. */ +PFNCRYPTCATADMINENUMCATALOGFROMHASH g_pfnCryptCATAdminEnumCatalogFromHash; +/** Pointer to CryptCATAdminReleaseCatalogContext. */ +PFNCRYPTCATADMINRELEASECATALOGCONTEXT g_pfnCryptCATAdminReleaseCatalogContext; +/** Pointer to CryptCATAdminReleaseContext. */ +PFNCRYPTCATDADMINRELEASECONTEXT g_pfnCryptCATAdminReleaseContext; +/** Pointer to CryptCATCatalogInfoFromContext. */ +PFNCRYPTCATCATALOGINFOFROMCONTEXT g_pfnCryptCATCatalogInfoFromContext; +#endif + + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +#ifdef IN_RING3 +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust); +static int supR3HardNtViCallWinVerifyTrustCatFile(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust); +#endif + + + + +/** @copydoc RTLDRREADER::pfnRead */ +static DECLCALLBACK(int) supHardNtViRdrRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + if ((ULONG)cb != cb) + return VERR_OUT_OF_RANGE; + + + /* + * For some reason I'm getting occational read error in an XP VM with + * STATUS_FAILED_DRIVER_ENTRY. Redoing the call again works in the + * debugger, so try do that automatically. + */ + for (uint32_t iTry = 0;; iTry++) + { + LARGE_INTEGER offNt; + offNt.QuadPart = off; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile(pNtViRdr->hFile, + NULL /*hEvent*/, + NULL /*ApcRoutine*/, + NULL /*ApcContext*/, + &Ios, + pvBuf, + (ULONG)cb, + &offNt, + NULL); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + if (Ios.Information == cb) + { + pNtViRdr->off = off + cb; + return VINF_SUCCESS; + } +#ifdef IN_RING3 + supR3HardenedError(VERR_READ_ERROR, false, + "supHardNtViRdrRead: Only got %#zx bytes when requesting %#zx bytes at %#llx in '%s'.\n", + Ios.Information, off, cb, pNtViRdr->szFilename); +#endif + pNtViRdr->off = -1; + return VERR_READ_ERROR; + } + + /* + * Delay a little before we retry? + */ +#ifdef IN_RING3 + if (iTry == 0) + NtYieldExecution(); + else if (iTry >= 1) + { + LARGE_INTEGER Time; + Time.QuadPart = -1000000 / 100; /* 1ms in 100ns units, relative time. */ + NtDelayExecution(TRUE, &Time); + } +#endif + /* + * Before we give up, we'll try split up the request in case the + * kernel is low on memory or similar. For simplicity reasons, we do + * this in a recursion fashion. + */ + if (iTry >= 2) + { + if (cb >= _8K) + { + size_t const cbBlock = RT_ALIGN_Z(cb / 4, 512); + while (cb > 0) + { + size_t cbThisRead = RT_MIN(cb, cbBlock); + int rc = supHardNtViRdrRead(&pNtViRdr->Core, pvBuf, cbThisRead, off); + if (RT_FAILURE(rc)) + return rc; + off += cbThisRead; + cb -= cbThisRead; + pvBuf = (uint8_t *)pvBuf + cbThisRead; + } + return VINF_SUCCESS; + } + +#ifdef IN_RING3 + supR3HardenedError(VERR_READ_ERROR, false, "supHardNtViRdrRead: Error %#x reading %#zx bytes at %#llx in '%s'.\n", + rcNt, off, cb, pNtViRdr->szFilename); +#endif + pNtViRdr->off = -1; + return VERR_READ_ERROR; + } + } +} + + +/** @copydoc RTLDRREADER::pfnTell */ +static DECLCALLBACK(RTFOFF) supHardNtViRdrTell(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + return pNtViRdr->off; +} + + +/** @copydoc RTLDRREADER::pfnSize */ +static DECLCALLBACK(RTFOFF) supHardNtViRdrSize(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + return pNtViRdr->cbFile; +} + + +/** @copydoc RTLDRREADER::pfnLogName */ +static DECLCALLBACK(const char *) supHardNtViRdrLogName(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + return pNtViRdr->szFilename; +} + + +/** @copydoc RTLDRREADER::pfnMap */ +static DECLCALLBACK(int) supHardNtViRdrMap(PRTLDRREADER pReader, const void **ppvBits) +{ + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDRREADER::pfnUnmap */ +static DECLCALLBACK(int) supHardNtViRdrUnmap(PRTLDRREADER pReader, const void *pvBits) +{ + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDRREADER::pfnDestroy */ +static DECLCALLBACK(int) supHardNtViRdrDestroy(PRTLDRREADER pReader) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pReader; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + pNtViRdr->Core.uMagic = ~RTLDRREADER_MAGIC; + pNtViRdr->hFile = NULL; + + RTMemFree(pNtViRdr); + return VINF_SUCCESS; +} + + +/** + * Creates a loader reader instance for the given NT file handle. + * + * @returns iprt status code. + * @param hFile Native NT file handle. + * @param pwszName Optional file name. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param ppNtViRdr Where to store the reader instance on success. + */ +static int supHardNtViRdrCreate(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PSUPHNTVIRDR *ppNtViRdr) +{ + /* + * Try determine the size of the file. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + FILE_STANDARD_INFORMATION StdInfo; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, &StdInfo, sizeof(StdInfo), FileStandardInformation); + if (!NT_SUCCESS(rcNt) || !NT_SUCCESS(Ios.Status)) + return VERR_LDRVI_FILE_LENGTH_ERROR; + + /* + * Calc the file name length and allocate memory for the reader instance. + */ + size_t cchFilename = 0; + if (pwszName) + cchFilename = RTUtf16CalcUtf8Len(pwszName); + + int rc = VERR_NO_MEMORY; + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)RTMemAllocZ(sizeof(*pNtViRdr) + cchFilename); + if (!pNtViRdr) + return VERR_NO_MEMORY; + + /* + * Initialize the structure. + */ + if (cchFilename) + { + char *pszName = &pNtViRdr->szFilename[0]; + rc = RTUtf16ToUtf8Ex(pwszName, RTSTR_MAX, &pszName, cchFilename + 1, NULL); + AssertStmt(RT_SUCCESS(rc), pNtViRdr->szFilename[0] = '\0'); + } + else + pNtViRdr->szFilename[0] = '\0'; + + pNtViRdr->Core.uMagic = RTLDRREADER_MAGIC; + pNtViRdr->Core.pfnRead = supHardNtViRdrRead; + pNtViRdr->Core.pfnTell = supHardNtViRdrTell; + pNtViRdr->Core.pfnSize = supHardNtViRdrSize; + pNtViRdr->Core.pfnLogName = supHardNtViRdrLogName; + pNtViRdr->Core.pfnMap = supHardNtViRdrMap; + pNtViRdr->Core.pfnUnmap = supHardNtViRdrUnmap; + pNtViRdr->Core.pfnDestroy = supHardNtViRdrDestroy; + pNtViRdr->hFile = hFile; + pNtViRdr->off = 0; + pNtViRdr->cbFile = StdInfo.EndOfFile.QuadPart; + pNtViRdr->fFlags = fFlags; + *ppNtViRdr = pNtViRdr; + return VINF_SUCCESS; +} + + +/** + * Simple case insensitive UTF-16 / ASCII path compare. + * + * @returns true if equal, false if not. + * @param pwszLeft The UTF-16 path string. + * @param pszRight The ascii string. + */ +static bool supHardViUtf16PathIsEqual(PCRTUTF16 pwszLeft, const char *pszRight) +{ + for (;;) + { + RTUTF16 wc = *pwszLeft++; + uint8_t b = *pszRight++; + if (b != wc) + { + if (wc >= 0x80) + return false; + wc = RT_C_TO_LOWER(wc); + if (wc != b) + { + b = RT_C_TO_LOWER(b); + if (wc != b) + { + if (wc == '/') + wc = '\\'; + if (b == '/') + b = '\\'; + if (wc != b) + return false; + } + } + } + if (!b) + return true; + } +} + + +/** + * Simple case insensitive UTF-16 / ASCII ends-with path predicate. + * + * @returns true if equal, false if not. + * @param pwsz The UTF-16 path string. + * @param pszSuffix The ascii suffix string. + */ +static bool supHardViUtf16PathEndsWith(PCRTUTF16 pwsz, const char *pszSuffix) +{ + size_t cwc = RTUtf16Len(pwsz); + size_t cchSuffix = strlen(pszSuffix); + if (cwc >= cchSuffix) + return supHardViUtf16PathIsEqual(pwsz + cwc - cchSuffix, pszSuffix); + return false; +} + + +/** + * Simple case insensitive UTF-16 / ASCII starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pwsz The UTF-16 path string. + * @param pszPrefix The ascii prefix string. + */ +static bool supHardViUtf16PathStartsWith(PCRTUTF16 pwszLeft, const char *pszRight) +{ + for (;;) + { + RTUTF16 wc = *pwszLeft++; + uint8_t b = *pszRight++; + if (b != wc) + { + if (!b) + return true; + if (wc >= 0x80 || wc == 0) + return false; + wc = RT_C_TO_LOWER(wc); + if (wc != b) + { + b = RT_C_TO_LOWER(b); + if (wc != b) + { + if (wc == '/') + wc = '\\'; + if (b == '/') + b = '\\'; + if (wc != b) + return false; + } + } + } + } +} + + +/** + * Counts slashes in the given UTF-8 path string. + * + * @returns Number of slashes. + * @param pwsz The UTF-16 path string. + */ +static uint32_t supHardViUtf16PathCountSlashes(PCRTUTF16 pwsz) +{ + uint32_t cSlashes = 0; + RTUTF16 wc; + while ((wc = *pwsz++) != '\0') + if (wc == '/' || wc == '\\') + cSlashes++; + return cSlashes; +} + + +/** + * Checks if the unsigned DLL is fine or not. + * + * @returns VINF_LDRVI_NOT_SIGNED or @a rc. + * @param hLdrMod The loader module handle. + * @param pwszName The NT name of the DLL/EXE. + * @param fFlags Flags. + * @param rc The status code.. + */ +static int supHardNtViCheckIfNotSignedOk(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, uint32_t fFlags, int rc) +{ + if (fFlags & (SUPHNTVI_F_REQUIRE_BUILD_CERT | SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING)) + return rc; + + /* + * Version macros. + */ + uint32_t const uNtVer = g_uNtVerCombined; +#define IS_XP() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(5, 1) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(5, 2) ) +#define IS_W2K3() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(5, 2) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(5, 3) ) +#define IS_VISTA() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 0) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 1) ) +#define IS_W70() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 1) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 2) ) +#define IS_W80() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 2) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 3) ) +#define IS_W81() ( uNtVer >= SUP_MAKE_NT_VER_SIMPLE(6, 3) && uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ) + + /* + * The System32 directory. + * + * System32 is full of unsigned DLLs shipped by microsoft, graphics + * hardware vendors, input device/method vendors and whatnot else that + * actually needs to be loaded into a process for it to work correctly. + * We have to ASSUME that anything our process attempts to load from + * System32 is trustworthy and that the Windows system with the help of + * anti-virus software make sure there is nothing evil lurking in System32 + * or being loaded from it. + * + * A small measure of protection is to list DLLs we know should be signed + * and decline loading unsigned versions of them, assuming they have been + * replaced by an adversary with evil intentions. + */ + PCRTUTF16 pwsz; + uint32_t cwcName = (uint32_t)RTUtf16Len(pwszName); + uint32_t cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); + if ( cwcName > cwcOther + && RTPATH_IS_SLASH(pwszName[cwcOther]) + && memcmp(pwszName, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0) + { + pwsz = pwszName + cwcOther + 1; + + /* Core DLLs. */ + if (supHardViUtf16PathIsEqual(pwsz, "ntdll.dll")) + return uNtVer < SUP_NT_VER_VISTA ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "kernel32.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "kernelbase.dll")) + return IS_W80() || IS_W70() ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "apisetschema.dll")) + return IS_W70() ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "apphelp.dll")) + return uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ? VINF_LDRVI_NOT_SIGNED : rc; + +#ifndef IN_RING0 + /* The ATI drivers load system drivers into the process, allow this, + but reject anything else from a subdirectory. */ + uint32_t cSlashes = supHardViUtf16PathCountSlashes(pwsz); + if (cSlashes > 0) + { + if ( cSlashes == 1 + && supHardViUtf16PathStartsWith(pwsz, "drivers\\ati") + && ( supHardViUtf16PathEndsWith(pwsz, ".sys") + || supHardViUtf16PathEndsWith(pwsz, ".dll") ) ) + return VINF_LDRVI_NOT_SIGNED; + return rc; + } + + /* Check that this DLL isn't supposed to be signed on this windows + version. If it should, it's likely to be a fake. */ + /** @todo list of signed dlls for various windows versions. */ + + /** @todo check file permissions? TrustedInstaller is supposed to be involved + * with all of them. */ + return VINF_LDRVI_NOT_SIGNED; +#else + return rc; +#endif + } + +#ifndef IN_RING0 + /* + * The WinSxS white list. + * + * Just like with System32 there are potentially a number of DLLs that + * could be required from WinSxS. However, so far only comctl32.dll + * variations have been required. So, we limit ourselves to explicit + * whitelisting of unsigned families of DLLs. + */ + cwcOther = g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR); + if ( cwcName > cwcOther + && RTPATH_IS_SLASH(pwszName[cwcOther]) + && memcmp(pwszName, g_WinSxSNtPath.UniStr.Buffer, g_WinSxSNtPath.UniStr.Length) == 0) + { + pwsz = pwszName + cwcOther + 1; + cwcName -= cwcOther + 1; + + /* The WinSxS layout means everything worth loading is exactly one level down. */ + uint32_t cSlashes = supHardViUtf16PathCountSlashes(pwsz); + if (cSlashes != 1) + return rc; + +# if 0 /* See below */ + /* The common controls mess. */ +# ifdef RT_ARCH_AMD64 + if (supHardViUtf16PathStartsWith(pwsz, "amd64_microsoft.windows.common-controls_")) +# elif defined(RT_ARCH_X86) + if (supHardViUtf16PathStartsWith(pwsz, "x86_microsoft.windows.common-controls_")) +# else +# error "Unsupported architecture" +# endif + { + if (supHardViUtf16PathEndsWith(pwsz, "\\comctl32.dll")) + return VINF_LDRVI_NOT_SIGNED; + } +# endif + + /* Allow anything slightly microsoftish from WinSxS. W2K3 wanted winhttp.dll early on... */ +# ifdef RT_ARCH_AMD64 + if (supHardViUtf16PathStartsWith(pwsz, "amd64_microsoft.")) +# elif defined(RT_ARCH_X86) + if (supHardViUtf16PathStartsWith(pwsz, "x86_microsoft.")) +# else +# error "Unsupported architecture" +# endif + { + return VINF_LDRVI_NOT_SIGNED; + } + + return rc; + } +#endif + + return rc; +} + + +/** + * @callback_method_impl{RTCRPKCS7VERIFYCERTCALLBACK, + * Standard code signing. Use this for Microsoft SPC.} + */ +static DECLCALLBACK(int) supHardNtViCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, + void *pvUser, PRTERRINFO pErrInfo) +{ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pvUser; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + /* + * If there is no certificate path build & validator associated with this + * callback, it must be because of the build certificate. We trust the + * build certificate without any second thoughts. + */ + if (hCertPaths == NIL_RTCRX509CERTPATHS) + { + if (RTCrX509Certificate_Compare(pCert, &g_BuildX509Cert) == 0) /* healthy paranoia */ + return VINF_SUCCESS; + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_BUILD_CERT_IPE, "Not valid kernel code signature."); + } + + /* + * Standard code signing capabilites required. + */ + int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, NULL, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * If kernel signing, a valid certificate path must be anchored by the + * microsoft kernel signing root certificate. + */ + if (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING) + { + uint32_t cPaths = RTCrX509CertPathsGetPathCount(hCertPaths); + uint32_t cFound = 0; + uint32_t cValid = 0; + for (uint32_t iPath = 0; iPath < cPaths; iPath++) + { + bool fTrusted; + PCRTCRX509NAME pSubject; + PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo; + int rcVerify; + rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, &pPublicKeyInfo, + NULL, NULL /*pCertCtx*/, &rcVerify); + AssertRCBreak(rc); + + if (RT_SUCCESS(rcVerify)) + { + Assert(fTrusted); + cValid++; + + /* + * Search the kernel signing root store for a matching anchor. + */ + RTCRSTORECERTSEARCH Search; + rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(g_hNtKernelRootStore, pSubject, &Search); + AssertRCBreak(rc); + + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(g_hNtKernelRootStore, &Search)) != NULL) + { + if (RTCrX509SubjectPublicKeyInfo_Compare(&pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo, + pPublicKeyInfo) == 0) + cFound++; + RTCrCertCtxRelease(pCertCtx); + } + + int rc2 = RTCrStoreCertSearchDestroy(g_hNtKernelRootStore, &Search); AssertRC(rc2); + } + } + if (RT_SUCCESS(rc) && cFound == 0) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE, "Not valid kernel code signature."); + if (RT_SUCCESS(rc) && cValid != 2 && g_fHaveOtherRoots) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNEXPECTED_VALID_PATH_COUNT, + "Expected exactly %u valid paths, not %u.", 2, cValid); + } + } + + /* + * More requirements? NT5 build lab? + */ + + return rc; +} + + +static DECLCALLBACK(int) supHardNtViCallback(RTLDRMOD hLdrMod, RTLDRSIGNATURETYPE enmSignature, + void const *pvSignature, size_t cbSignature, + PRTERRINFO pErrInfo, void *pvUser) +{ + /* + * Check out the input. + */ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pvUser; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + + AssertReturn(cbSignature == sizeof(RTCRPKCS7CONTENTINFO), VERR_INTERNAL_ERROR_5); + PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pvSignature; + AssertReturn(RTCrPkcs7ContentInfo_IsSignedData(pContentInfo), VERR_INTERNAL_ERROR_5); + AssertReturn(pContentInfo->u.pSignedData->SignerInfos.cItems == 1, VERR_INTERNAL_ERROR_5); + PCRTCRPKCS7SIGNERINFO pSignerInfo = &pContentInfo->u.pSignedData->SignerInfos.paItems[0]; + + /* + * If special certificate requirements, check them out before validating + * the signature. + */ + if (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) + { + if (!RTCrX509Certificate_MatchIssuerAndSerialNumber(&g_BuildX509Cert, + &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber)) + return RTErrInfoSet(pErrInfo, VERR_SUP_VP_NOT_SIGNED_WITH_BUILD_CERT, "Not signed with the build certificate."); + } + + /* + * Verify the signature. + */ + RTTIMESPEC ValidationTime; + RTTimeSpecSetSeconds(&ValidationTime, pNtViRdr->uTimestamp); + + return RTCrPkcs7VerifySignedData(pContentInfo, 0, g_hSpcAndNtKernelSuppStore, g_hSpcAndNtKernelRootStore, &ValidationTime, + supHardNtViCertVerifyCallback, pNtViRdr, pErrInfo); +} + + +/** + * Checks if it's safe to call WinVerifyTrust or whether we might end up in an + * infinite recursion. + * + * @returns true if ok, false if not. + * @param hFile The file name. + * @param pwszName The executable name. + */ +static bool supR3HardNtViCanCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName) +{ + /* + * Recursion preventions hacks: + * - Don't try call WinVerifyTrust on Wintrust.dll when called from the + * create section hook. CRYPT32.DLL tries to load WinTrust.DLL in some cases. + */ + size_t cwcName = RTUtf16Len(pwszName); + if ( hFile != NULL + && cwcName > g_System32NtPath.UniStr.Length / sizeof(WCHAR) + && !memcmp(pwszName, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) + && supHardViUtf16PathIsEqual(&pwszName[g_System32NtPath.UniStr.Length / sizeof(WCHAR)], "\\wintrust.dll")) + return false; + + return true; +} + + +/** + * Verifies the given executable image. + * + * @returns IPRT status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for dealing + * with unsigned system dlls as well as for error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pfCacheable Where to return whether the result can be cached. A + * valid value is always returned. Optional. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, + bool *pfCacheable, PRTERRINFO pErrInfo) +{ + /* Clear the cacheable indicator as it needs to be valid in all return paths. */ + if (pfCacheable) + *pfCacheable = false; + + /* + * Create a reader instance. + */ + PSUPHNTVIRDR pNtViRdr; + int rc = supHardNtViRdrCreate(hFile, pwszName, fFlags, &pNtViRdr); + if (RT_SUCCESS(rc)) + { + /* + * Open the image. + */ + RTLDRMOD hLdrMod; + rc = RTLdrOpenWithReader(&pNtViRdr->Core, RTLDR_O_FOR_VALIDATION, + fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST, + &hLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Verify it. + * + * The PKCS #7 SignedData signature is checked in the callback. Any + * signing certificate restrictions are also enforced there. + * + * For the time being, we use the executable timestamp as the + * certificate validation date. We must query that first to avoid + * potential issues re-entering the loader code from the callback. + */ + rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &pNtViRdr->uTimestamp, sizeof(pNtViRdr->uTimestamp)); + if (RT_SUCCESS(rc)) + { + rc = RTLdrVerifySignature(hLdrMod, supHardNtViCallback, pNtViRdr, pErrInfo); + + /* + * Microsoft doesn't sign a whole bunch of DLLs, so we have to + * ASSUME that a bunch of system DLLs are fine. + */ + if (rc == VERR_LDRVI_NOT_SIGNED) + rc = supHardNtViCheckIfNotSignedOk(hLdrMod, pwszName, fFlags, rc); + if (RT_FAILURE(rc)) + RTErrInfoAddF(pErrInfo, rc, ": %ls", pwszName); + + /* + * Check for the signature checking enforcement, if requested to do so. + */ + if (RT_SUCCESS(rc) && (fFlags & SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT)) + { + bool fEnforced = false; + int rc2 = RTLdrQueryProp(hLdrMod, RTLDRPROP_SIGNATURE_CHECKS_ENFORCED, &fEnforced, sizeof(fEnforced)); + if (RT_FAILURE(rc2)) + rc = RTErrInfoSetF(pErrInfo, rc2, "Querying RTLDRPROP_SIGNATURE_CHECKS_ENFORCED failed on %ls: %Rrc.", + pwszName, rc2); + else if (!fEnforced) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SIGNATURE_CHECKS_NOT_ENFORCED, + "The image '%ls' was not linked with /IntegrityCheck.", pwszName); + } + } + else + RTErrInfoSetF(pErrInfo, rc, "RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on %ls: %Rrc", pwszName, rc); + + int rc2 = RTLdrClose(hLdrMod); AssertRC(rc2); + +#ifdef IN_RING3 + /* + * Call the windows verify trust API if we've resolved it. + */ + if ( g_pfnWinVerifyTrust + && supR3HardNtViCanCallWinVerifyTrust(hFile, pwszName)) + { + if (pfCacheable) + *pfCacheable = g_pfnWinVerifyTrust != NULL; + if (rc != VERR_LDRVI_NOT_SIGNED) + { + if (rc == VINF_LDRVI_NOT_SIGNED) + { + if (fFlags & SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION) + { + int rc2 = supR3HardNtViCallWinVerifyTrustCatFile(hFile, pwszName, fFlags, pErrInfo, + g_pfnWinVerifyTrust); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile -> %d (org %d)\n", rc2, rc)); + rc = rc2; + } + else + { + AssertFailed(); + rc = VERR_LDRVI_NOT_SIGNED; + } + } + else if (RT_SUCCESS(rc)) + rc = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust); + else + { + int rc2 = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust); + AssertMsg(RT_FAILURE_NP(rc2), + ("rc=%Rrc, rc2=%Rrc %s", rc, rc2, pErrInfo ? pErrInfo->pszMsg : "<no-err-info>")); + } + } + } +#else + if (pfCacheable) + *pfCacheable = true; +#endif /* IN_RING3 */ + } + else + supHardNtViRdrDestroy(&pNtViRdr->Core); + } + SUP_DPRINTF(("supHardenedWinVerifyImageByHandle: -> %d (%ls)\n", rc, pwszName)); + return rc; +} + + +#ifdef IN_RING3 +/** + * supHardenedWinVerifyImageByHandle version without the name. + * + * The name is derived from the handle. + * + * @returns IPRT status code. + * @param hFile File handle to the executable file. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByHandleNoName(HANDLE hFile, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* + * Determine the NT name and call the verification function. + */ + union + { + UNICODE_STRING UniStr; + uint8_t abBuffer[(MAX_PATH + 8 + 1) * 2]; + } uBuf; + + ULONG cbIgn; + NTSTATUS rcNt = NtQueryObject(hFile, + ObjectNameInformation, + &uBuf, + sizeof(uBuf) - sizeof(WCHAR), + &cbIgn); + if (NT_SUCCESS(rcNt)) + uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0'; + else + uBuf.UniStr.Buffer = (WCHAR *)L"TODO3"; + + return supHardenedWinVerifyImageByHandle(hFile, uBuf.UniStr.Buffer, fFlags, NULL /*pfCacheable*/, pErrInfo); +} +#endif /* IN_RING3 */ + + +/** + * Retrieves the full official path to the system root or one of it's sub + * directories. + * + * This code is also used by the support driver. + * + * @returns VBox status code. + * @param pvBuf The output buffer. This will contain a + * UNICODE_STRING followed (at the kernel's + * discretion) the string buffer. + * @param cbBuf The size of the buffer @a pvBuf points to. + * @param enmDir Which directory under the system root we're + * interested in. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardNtGetSystemRootDir(void *pvBuf, uint32_t cbBuf, SUPHARDNTSYSROOTDIR enmDir, PRTERRINFO pErrInfo) +{ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING NtName; + switch (enmDir) + { + case kSupHardNtSysRootDir_System32: + { + static const WCHAR s_wszNameSystem32[] = L"\\SystemRoot\\System32\\"; + NtName.Buffer = (PWSTR)s_wszNameSystem32; + NtName.Length = sizeof(s_wszNameSystem32) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszNameSystem32); + break; + } + case kSupHardNtSysRootDir_WinSxS: + { + static const WCHAR s_wszNameWinSxS[] = L"\\SystemRoot\\WinSxS\\"; + NtName.Buffer = (PWSTR)s_wszNameWinSxS; + NtName.Length = sizeof(s_wszNameWinSxS) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszNameWinSxS); + break; + } + default: + AssertFailed(); + return VERR_INVALID_PARAMETER; + } + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + ULONG cbIgn; + rcNt = NtQueryObject(hFile, + ObjectNameInformation, + pvBuf, + cbBuf - sizeof(WCHAR), + &cbIgn); + NtClose(hFile); + if (NT_SUCCESS(rcNt)) + { + PUNICODE_STRING pUniStr = (PUNICODE_STRING)pvBuf; + if (pUniStr->Length > 0) + { + /* Make sure it's terminated so it can safely be printed.*/ + pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0'; + return VINF_SUCCESS; + } + + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, + "NtQueryObject returned an empty path for '%ls'", NtName.Buffer); + } + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, "NtQueryObject failed on '%ls' dir: %#x", NtName.Buffer, rcNt); + } + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_SYSTEM32_PATH, "Failure to open '%ls': %#x", NtName.Buffer, rcNt); +} + + +/** + * Initialize one certificate entry. + * + * @returns VBox status code. + * @param pCert The X.509 certificate representation to init. + * @param pabCert The raw DER encoded certificate. + * @param cbCert The size of the raw certificate. + * @param pErrInfo Where to return extended error info. Optional. + * @param pszErrorTag Error tag. + */ +static int supHardNtViCertInit(PRTCRX509CERTIFICATE pCert, unsigned char const *pabCert, unsigned cbCert, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(cbCert > 16 && cbCert < _128K, + RTErrInfoSetF(pErrInfo, VERR_INTERNAL_ERROR_3, "%s: cbCert=%#x out of range", pszErrorTag, cbCert)); + AssertReturn(!RTCrX509Certificate_IsPresent(pCert), + RTErrInfoSetF(pErrInfo, VERR_WRONG_ORDER, "%s: Certificate already decoded?", pszErrorTag)); + + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pabCert, cbCert, pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, NULL); + int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, pCert, pszErrorTag); + if (RT_SUCCESS(rc)) + rc = RTCrX509Certificate_CheckSanity(pCert, 0, pErrInfo, pszErrorTag); + return rc; +} + + +static int supHardNtViCertStoreAddArray(RTCRSTORE hStore, PCSUPTAENTRY paCerts, unsigned cCerts, PRTERRINFO pErrInfo) +{ + for (uint32_t i = 0; i < cCerts; i++) + { + int rc = RTCrStoreCertAddEncoded(hStore, RTCRCERTCTX_F_ENC_TAF_DER, paCerts[i].pch, paCerts[i].cb, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Initialize a certificate table. + * + * @param phStore Where to return the store pointer. + * @param paCerts1 Pointer to the first certificate table. + * @param cCerts1 Entries in the first certificate table. + * @param paCerts2 Pointer to the second certificate table. + * @param cCerts2 Entries in the second certificate table. + * @param paCerts3 Pointer to the third certificate table. + * @param cCerts3 Entries in the third certificate table. + * @param pErrInfo Where to return extended error info. Optional. + * @param pszErrorTag Error tag. + */ +static int supHardNtViCertStoreInit(PRTCRSTORE phStore, + PCSUPTAENTRY paCerts1, unsigned cCerts1, + PCSUPTAENTRY paCerts2, unsigned cCerts2, + PCSUPTAENTRY paCerts3, unsigned cCerts3, + PRTERRINFO pErrInfo, const char *pszErrorTag) +{ + AssertReturn(*phStore == NIL_RTCRSTORE, VERR_WRONG_ORDER); + + int rc = RTCrStoreCreateInMem(phStore, cCerts1 + cCerts2); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "RTCrStoreCreateMemoryStore failed: %Rrc", rc); + + rc = supHardNtViCertStoreAddArray(*phStore, paCerts1, cCerts1, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreAddArray(*phStore, paCerts2, cCerts2, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreAddArray(*phStore, paCerts3, cCerts3, pErrInfo); + return rc; +} + + +/** + * This initializes the certificates globals so we don't have to reparse them + * every time we need to verify an image. + * + * @returns IPRT status code. + * @param pErrInfo Where to return extended error info. Optional. + */ +DECLHIDDEN(int) supHardenedWinInitImageVerifier(PRTERRINFO pErrInfo) +{ + AssertReturn(!RTCrX509Certificate_IsPresent(&g_BuildX509Cert), VERR_WRONG_ORDER); + + /* + * Get the system root paths. + */ + int rc = supHardNtGetSystemRootDir(&g_System32NtPath, sizeof(g_System32NtPath), kSupHardNtSysRootDir_System32, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtGetSystemRootDir(&g_WinSxSNtPath, sizeof(g_WinSxSNtPath), kSupHardNtSysRootDir_WinSxS, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Initialize it, leaving the cleanup to the termination call. + */ + rc = supHardNtViCertInit(&g_BuildX509Cert, g_abSUPBuildCert, g_cbSUPBuildCert, pErrInfo, "BuildCertificate"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcRootStore, g_aSUPSpcRootTAs, g_cSUPSpcRootTAs, + NULL, 0, NULL, 0, pErrInfo, "SpcRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hNtKernelRootStore, g_aSUPNtKernelRootTAs, g_cSUPNtKernelRootTAs, + NULL, 0, NULL, 0, pErrInfo, "NtKernelRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcAndNtKernelRootStore, + g_aSUPSpcRootTAs, g_cSUPSpcRootTAs, + g_aSUPNtKernelRootTAs, g_cSUPNtKernelRootTAs, + g_aSUPTimestampTAs, g_cSUPTimestampTAs, + pErrInfo, "SpcAndNtKernelRoot"); + if (RT_SUCCESS(rc)) + rc = supHardNtViCertStoreInit(&g_hSpcAndNtKernelSuppStore, + NULL, 0, NULL, 0, NULL, 0, + pErrInfo, "SpcAndNtKernelSupplemental"); + + /* If the build certificate is a test singing certificate, it must be a + trusted root or we'll fail to validate anything. */ + if ( RT_SUCCESS(rc) + && RTCrX509Name_Compare(&g_BuildX509Cert.TbsCertificate.Subject, &g_BuildX509Cert.TbsCertificate.Issuer) == 0) + rc = RTCrStoreCertAddEncoded(g_hSpcAndNtKernelRootStore, RTCRCERTCTX_F_ENC_X509_DER, + g_abSUPBuildCert, g_cbSUPBuildCert, pErrInfo); + + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + supHardenedWinTermImageVerifier(); + } + return rc; +} + + +/** + * Releases resources allocated by supHardenedWinInitImageVerifier. + */ +DECLHIDDEN(void) supHardenedWinTermImageVerifier(void) +{ + if (RTCrX509Certificate_IsPresent(&g_BuildX509Cert)) + RTAsn1VtDelete(&g_BuildX509Cert.SeqCore.Asn1Core); + + RTCrStoreRelease(g_hSpcAndNtKernelSuppStore); + g_hSpcAndNtKernelSuppStore = NIL_RTCRSTORE; + RTCrStoreRelease(g_hSpcAndNtKernelRootStore); + g_hSpcAndNtKernelRootStore = NIL_RTCRSTORE; + + RTCrStoreRelease(g_hNtKernelRootStore); + g_hNtKernelRootStore = NIL_RTCRSTORE; + RTCrStoreRelease(g_hSpcRootStore); + g_hSpcRootStore = NIL_RTCRSTORE; +} + +#ifdef IN_RING3 + +/** + * This is a hardcoded list of certificates we thing we might need. + * + * @returns true if wanted, false if not. + * @param pCert The certificate. + */ +static bool supR3HardenedWinIsDesiredRootCA(PCRTCRX509CERTIFICATE pCert) +{ + /* + * Check that it's a plausible root certificate. + */ + if (!RTCrX509Certificate_IsSelfSigned(pCert)) + return false; + if (RTAsn1Integer_UnsignedCompareWithU32(&pCert->TbsCertificate.T0.Version, 3) > 0) + { + if ( !(pCert->TbsCertificate.T3.fExtKeyUsage & RTCRX509CERT_KEY_USAGE_F_KEY_CERT_SIGN) + && (pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE) ) + return false; + if ( pCert->TbsCertificate.T3.pBasicConstraints + && !pCert->TbsCertificate.T3.pBasicConstraints->CA.fValue) + return false; + } + if (pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.cBits < 256) /* mostly for u64KeyId reading. */ + return false; + + /* + * Array of names and key clues of the certificates we want. + */ + static struct + { + uint64_t u64KeyId; + const char *pszName; + } const s_aWanted[] = + { + /* SPC */ + { UINT64_C(0xffffffffffffffff), "C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority" }, + { UINT64_C(0xffffffffffffffff), "L=Internet, O=VeriSign, Inc., OU=VeriSign Commercial Software Publishers CA" }, + { UINT64_C(0x491857ead79dde00), "C=US, O=The Go Daddy Group, Inc., OU=Go Daddy Class 2 Certification Authority" }, + + /* TS */ + { UINT64_C(0xffffffffffffffff), "O=Microsoft Trust Network, OU=Microsoft Corporation, OU=Microsoft Time Stamping Service Root, OU=Copyright (c) 1997 Microsoft Corp." }, + { UINT64_C(0xffffffffffffffff), "O=VeriSign Trust Network, OU=VeriSign, Inc., OU=VeriSign Time Stamping Service Root, OU=NO LIABILITY ACCEPTED, (c)97 VeriSign, Inc." }, + { UINT64_C(0xffffffffffffffff), "C=ZA, ST=Western Cape, L=Durbanville, O=Thawte, OU=Thawte Certification, CN=Thawte Timestamping CA" }, + + /* Additional Windows 8.1 list: */ + { UINT64_C(0x5ad46780fa5df300), "DC=com, DC=microsoft, CN=Microsoft Root Certificate Authority" }, + { UINT64_C(0x3be670c1bd02a900), "OU=Copyright (c) 1997 Microsoft Corp., OU=Microsoft Corporation, CN=Microsoft Root Authority" }, + { UINT64_C(0x4d3835aa4180b200), "C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2011" }, + { UINT64_C(0x646e3fe3ba08df00), "C=US, O=MSFT, CN=Microsoft Authenticode(tm) Root Authority" }, + { UINT64_C(0xece4e4289e08b900), "C=US, ST=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Root Certificate Authority 2010" }, + { UINT64_C(0x59faf1086271bf00), "C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2" }, + { UINT64_C(0x3d98ab22bb04a300), "C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root" }, + { UINT64_C(0x91e3728b8b40d000), "C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO Certification Authority" }, + { UINT64_C(0x61a3a33f81aace00), "C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Object" }, + { UINT64_C(0x9e5bc2d78b6a3636), "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA, Email=premium-server@thawte.com" }, + { UINT64_C(0xf4fd306318ccda00), "C=US, O=GeoTrust Inc., CN=GeoTrust Global CA" }, + { UINT64_C(0xa0ee62086758b15d), "C=US, O=Equifax, OU=Equifax Secure Certificate Authority" }, + { UINT64_C(0x8ff6fc03c1edbd00), "C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., CN=Starfield Root Certificate Authority - G2" }, + { UINT64_C(0xa3ce8d99e60eda00), "C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA" }, + { UINT64_C(0xa671e9fec832b700), "C=US, O=Starfield Technologies, Inc., OU=Starfield Class 2 Certification Authority" }, + { UINT64_C(0xa8de7211e13be200), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA" }, + { UINT64_C(0x0ff3891b54348328), "C=US, O=Entrust.net, OU=www.entrust.net/CPS incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.netSecure Server Certification Authority" }, + { UINT64_C(0x7ae89c50f0b6a00f), "C=US, O=GTE Corporation, OU=GTE CyberTrust Solutions, Inc., CN=GTE CyberTrust Global Root" }, + { UINT64_C(0xd45980fbf0a0ac00), "C=US, O=thawte, Inc., OU=Certification Services Division, OU=(c) 2006 thawte, Inc. - For authorized use only, CN=thawte Primary Root CA" }, + { UINT64_C(0x9e5bc2d78b6a3636), "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Premium Server CA, Email=premium-server@thawte.com" }, + { UINT64_C(0x7c4fd32ec1b1ce00), "C=PL, O=Unizeto Sp. z o.o., CN=Certum CA" }, + { UINT64_C(0xd4fbe673e5ccc600), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA" }, + { UINT64_C(0x16e64d2a56ccf200), "C=US, ST=Arizona, L=Scottsdale, O=Starfield Technologies, Inc., OU=http://certificates.starfieldtech.com/repository/, CN=Starfield Services Root Certificate Authority" }, + { UINT64_C(0x6e2ba21058eedf00), "C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN - DATACorp SGC" }, + { UINT64_C(0xb28612a94b4dad00), "O=Entrust.net, OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.), OU=(c) 1999 Entrust.net Limited, CN=Entrust.netCertification Authority (2048)" }, + { UINT64_C(0x357a29080824af00), "C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class3 Public Primary Certification Authority - G5" }, + { UINT64_C(0x466cbc09db88c100), "C=IL, O=StartCom Ltd., OU=Secure Digital Certificate Signing, CN=StartCom Certification Authority" }, + { UINT64_C(0x9259c8abe5ca713a), "L=ValiCert Validation Network, O=ValiCert, Inc., OU=ValiCert Class 2 Policy Validation Authority, CN=http://www.valicert.com/, Email=info@valicert.com" }, + { UINT64_C(0x1f78fc529cbacb00), "C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 1999 VeriSign, Inc. - For authorized use only, CN=VeriSign Class3 Public Primary Certification Authority - G3" }, + { UINT64_C(0x8043e4ce150ead00), "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Assured ID Root CA" }, + { UINT64_C(0x00f2e6331af7b700), "C=SE, O=AddTrust AB, OU=AddTrust External TTP Network, CN=AddTrust External CA Root" }, + }; + + + uint64_t const u64KeyId = pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.uBits.pu64[1]; + uint32_t i = RT_ELEMENTS(s_aWanted); + while (i-- > 0) + if ( s_aWanted[i].u64KeyId == u64KeyId + || s_aWanted[i].u64KeyId == UINT64_MAX) + if (RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, s_aWanted[i].pszName)) + return true; + +#ifdef DEBUG_bird + char szTmp[512]; + szTmp[sizeof(szTmp) - 1] = '\0'; + RTCrX509Name_FormatAsString(&pCert->TbsCertificate.Issuer, szTmp, sizeof(szTmp) - 1, NULL); + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: %#llx %s\n", u64KeyId, szTmp)); +#endif + return false; +} + +/** + * Called by supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation to + * import selected root CAs from the system certificate store. + * + * These certificates permits us to correctly validate third party DLLs. + * + * @param fLoadLibraryFlags The LoadLibraryExW flags that the caller + * found to work. Avoids us having to retry on + * ERROR_INVALID_PARAMETER. + */ +static void supR3HardenedWinRetrieveTrustedRootCAs(DWORD fLoadLibraryFlags) +{ + uint32_t cAdded = 0; + + /* + * Load crypt32.dll and resolve the APIs we need. + */ + HMODULE hCrypt32 = LoadLibraryExW(L"\\\\.\\GLOBALROOT\\SystemRoot\\System32\\crypt32.dll", NULL, fLoadLibraryFlags); + if (!hCrypt32) + supR3HardenedFatal("Error loading 'crypt32.dll': %u", GetLastError()); + +#define RESOLVE_CRYPT32_API(a_Name, a_pfnType) \ + a_pfnType pfn##a_Name = (a_pfnType)GetProcAddress(hCrypt32, #a_Name); \ + if (pfn##a_Name == NULL) supR3HardenedFatal("Error locating '" #a_Name "' in 'crypt32.dll': %u", GetLastError()) + RESOLVE_CRYPT32_API(CertOpenStore, PFNCERTOPENSTORE); + RESOLVE_CRYPT32_API(CertCloseStore, PFNCERTCLOSESTORE); + RESOLVE_CRYPT32_API(CertEnumCertificatesInStore, PFNCERTENUMCERTIFICATESINSTORE); +#undef RESOLVE_CRYPT32_API + + /* + * Open the root store and look for the certificates we wish to use. + */ + DWORD fOpenStore = CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG; + HCERTSTORE hStore = pfnCertOpenStore(CERT_STORE_PROV_SYSTEM_W, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL /* hCryptProv = default */, CERT_SYSTEM_STORE_LOCAL_MACHINE | fOpenStore, L"Root"); + if (!hStore) + hStore = pfnCertOpenStore(CERT_STORE_PROV_SYSTEM_W, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL /* hCryptProv = default */, CERT_SYSTEM_STORE_CURRENT_USER | fOpenStore, L"Root"); + if (hStore) + { + PCCERT_CONTEXT pCurCtx = NULL; + while ((pCurCtx = pfnCertEnumCertificatesInStore(hStore, pCurCtx)) != NULL) + { + if (pCurCtx->dwCertEncodingType & X509_ASN_ENCODING) + { + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, NULL /*pErrInfo*/, + &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx"); + RTCRX509CERTIFICATE MyCert; + int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &MyCert, "Cert"); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (supR3HardenedWinIsDesiredRootCA(&MyCert)) + { + rc = RTCrStoreCertAddEncoded(g_hSpcRootStore, RTCRCERTCTX_F_ENC_X509_DER, + pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, NULL /*pErrInfo*/); + AssertRC(rc); + + rc = RTCrStoreCertAddEncoded(g_hSpcAndNtKernelRootStore, RTCRCERTCTX_F_ENC_X509_DER, + pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, NULL /*pErrInfo*/); + AssertRC(rc); + cAdded++; + } + + RTCrX509Certificate_Delete(&MyCert); + } + } + } + pfnCertCloseStore(hStore, CERT_CLOSE_STORE_CHECK_FLAG); + g_fHaveOtherRoots = true; + } + SUP_DPRINTF(("supR3HardenedWinRetrieveTrustedRootCAs: cAdded=%u\n", cAdded)); +} + + +/** + * Resolves the WinVerifyTrust API after the process has been verified and + * installs a thread creation hook. + * + * The WinVerifyTrust API is used in addition our own Authenticode verification + * code. If the image has the IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY flag + * set, it will be checked again by the kernel. All our image has this flag set + * and we require all VBox extensions to have it set as well. In effect, the + * authenticode signature will be checked two or three times. + */ +DECLHIDDEN(void) supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(void) +{ +# ifdef IN_SUP_HARDENED_R3 + /* + * Load our the support library DLL that does the thread hooking as the + * security API may trigger the creation of COM worker threads (or + * whatever they are). + * + * The thread creation hook makes the threads very slippery to debuggers by + * irreversably disabling most (if not all) debug events for them. + */ + char szPath[RTPATH_MAX]; + supR3HardenedPathSharedLibs(szPath, sizeof(szPath) - sizeof("/VBoxSupLib.DLL")); + suplibHardenedStrCat(szPath, "/VBoxSupLib.DLL"); + HMODULE hSupLibMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, true /*fSystem32Only*/); + if (hSupLibMod == NULL) + supR3HardenedFatal("Error loading '%s': %u", szPath, GetLastError()); +# endif + + /* + * Resolve it. + */ + DWORD fFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + fFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; + HMODULE hWintrust = LoadLibraryExW(L"\\\\.\\GLOBALROOT\\SystemRoot\\System32\\Wintrust.dll", NULL, fFlags); + if ( hWintrust == NULL + && fFlags + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && GetLastError() == ERROR_INVALID_PARAMETER) + { + fFlags = 0; + hWintrust = LoadLibraryExW(L"\\\\.\\GLOBALROOT\\SystemRoot\\System32\\Wintrust.dll", NULL, fFlags); + } + if (hWintrust == NULL) + supR3HardenedFatal("Error loading 'Wintrust.dll': %u", GetLastError()); + +#define RESOLVE_CRYPT_API(a_Name, a_pfnType, a_uMinWinVer) \ + do { \ + g_pfn##a_Name = (a_pfnType)GetProcAddress(hWintrust, #a_Name); \ + if (g_pfn##a_Name == NULL && (a_uMinWinVer) < g_uNtVerCombined) \ + supR3HardenedFatal("Error locating '" #a_Name "' in 'Wintrust.dll': %u", GetLastError()); \ + } while (0) + + PFNWINVERIFYTRUST pfnWinVerifyTrust = (PFNWINVERIFYTRUST)GetProcAddress(hWintrust, "WinVerifyTrust"); + if (!pfnWinVerifyTrust) + supR3HardenedFatal("Error locating 'WinVerifyTrust' in 'Wintrust.dll': %u", GetLastError()); + + RESOLVE_CRYPT_API(CryptCATAdminAcquireContext, PFNCRYPTCATADMINACQUIRECONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATAdminCalcHashFromFileHandle, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE, 0); + RESOLVE_CRYPT_API(CryptCATAdminEnumCatalogFromHash, PFNCRYPTCATADMINENUMCATALOGFROMHASH, 0); + RESOLVE_CRYPT_API(CryptCATAdminReleaseCatalogContext, PFNCRYPTCATADMINRELEASECATALOGCONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATAdminReleaseContext, PFNCRYPTCATDADMINRELEASECONTEXT, 0); + RESOLVE_CRYPT_API(CryptCATCatalogInfoFromContext, PFNCRYPTCATCATALOGINFOFROMCONTEXT, 0); + + RESOLVE_CRYPT_API(CryptCATAdminAcquireContext2, PFNCRYPTCATADMINACQUIRECONTEXT2, SUP_NT_VER_W80); + RESOLVE_CRYPT_API(CryptCATAdminCalcHashFromFileHandle2, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2, SUP_NT_VER_W80); + + /* + * Call it on ourselves and ntdll to make sure it loads all the providers + * now, we would otherwise geting into recursive trouble in the + * NtCreateSection hook. + */ +# ifdef IN_SUP_HARDENED_R3 + RTERRINFOSTATIC ErrInfoStatic; + RTErrInfoInitStatic(&ErrInfoStatic); + int rc = supR3HardNtViCallWinVerifyTrust(NULL, g_SupLibHardenedExeNtPath.UniStr.Buffer, 0, + &ErrInfoStatic.Core, pfnWinVerifyTrust); + if (RT_FAILURE(rc)) + supR3HardenedFatal("WinVerifyTrust failed on stub executable: %s", ErrInfoStatic.szMsg); +# endif + + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* ntdll isn't signed on XP, assuming this is the case on W2K3 for now. */ + supR3HardNtViCallWinVerifyTrust(NULL, L"\\SystemRoot\\System32\\ntdll.dll", 0, NULL, pfnWinVerifyTrust); + supR3HardNtViCallWinVerifyTrustCatFile(NULL, L"\\SystemRoot\\System32\\ntdll.dll", 0, NULL, pfnWinVerifyTrust); + + g_pfnWinVerifyTrust = pfnWinVerifyTrust; + + /* + * Now, get trusted root CAs so we can verify a broader scope of signatures. + */ + supR3HardenedWinRetrieveTrustedRootCAs(fFlags); +} + + +static int supR3HardNtViNtToWinPath(PCRTUTF16 pwszNtName, PCRTUTF16 *ppwszWinPath, + PRTUTF16 pwszWinPathBuf, size_t cwcWinPathBuf) +{ + static const RTUTF16 s_wszPrefix[] = L"\\\\.\\GLOBALROOT"; + + if (*pwszNtName != '\\' && *pwszNtName != '/') + return VERR_PATH_DOES_NOT_START_WITH_ROOT; + + size_t cwcNtName = RTUtf16Len(pwszNtName); + if (RT_ELEMENTS(s_wszPrefix) + cwcNtName > cwcWinPathBuf) + return VERR_FILENAME_TOO_LONG; + + memcpy(pwszWinPathBuf, s_wszPrefix, sizeof(s_wszPrefix)); + memcpy(&pwszWinPathBuf[sizeof(s_wszPrefix) / sizeof(RTUTF16) - 1], pwszNtName, (cwcNtName + 1) * sizeof(RTUTF16)); + *ppwszWinPath = pwszWinPathBuf; + return VINF_SUCCESS; +} + + +/** + * Calls WinVerifyTrust to verify an PE image. + * + * @returns VBox status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + * @param pfnWinVerifyTrust Pointer to the API. + */ +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust) +{ + /* + * Convert the name into a Windows name. + */ + RTUTF16 wszWinPathBuf[MAX_PATH]; + PCRTUTF16 pwszWinPath; + int rc = supR3HardNtViNtToWinPath(pwszName, &pwszWinPath, wszWinPathBuf, RT_ELEMENTS(wszWinPathBuf)); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "Bad path passed to supR3HardNtViCallWinVerifyTrust: rc=%Rrc '%ls'", rc, pwszName); + + /* + * Construct input parameters and call the API. + */ + WINTRUST_FILE_INFO FileInfo; + RT_ZERO(FileInfo); + FileInfo.cbStruct = sizeof(FileInfo); + FileInfo.pcwszFilePath = pwszWinPath; + FileInfo.hFile = hFile; + + GUID PolicyActionGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + WINTRUST_DATA TrustData; + RT_ZERO(TrustData); + TrustData.cbStruct = sizeof(TrustData); + TrustData.fdwRevocationChecks = WTD_REVOKE_NONE; /* Keep simple for now. */ + TrustData.dwStateAction = WTD_STATEACTION_VERIFY; + TrustData.dwUIChoice = WTD_UI_NONE; + TrustData.dwProvFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + TrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + else + TrustData.dwProvFlags = WTD_REVOCATION_CHECK_NONE; + TrustData.dwUnionChoice = WTD_CHOICE_FILE; + TrustData.pFile = &FileInfo; + + HRESULT hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &PolicyActionGuid, &TrustData); + if (hrc == S_OK) + rc = VINF_SUCCESS; + else + { + /* + * Failed. Format a nice error message. + */ +# ifdef DEBUG_bird + __debugbreak(); +# endif + const char *pszErrConst = NULL; + switch (hrc) + { + case TRUST_E_SYSTEM_ERROR: pszErrConst = "TRUST_E_SYSTEM_ERROR"; break; + case TRUST_E_NO_SIGNER_CERT: pszErrConst = "TRUST_E_NO_SIGNER_CERT"; break; + case TRUST_E_COUNTER_SIGNER: pszErrConst = "TRUST_E_COUNTER_SIGNER"; break; + case TRUST_E_CERT_SIGNATURE: pszErrConst = "TRUST_E_CERT_SIGNATURE"; break; + case TRUST_E_TIME_STAMP: pszErrConst = "TRUST_E_TIME_STAMP"; break; + case TRUST_E_BAD_DIGEST: pszErrConst = "TRUST_E_BAD_DIGEST"; break; + case TRUST_E_BASIC_CONSTRAINTS: pszErrConst = "TRUST_E_BASIC_CONSTRAINTS"; break; + case TRUST_E_FINANCIAL_CRITERIA: pszErrConst = "TRUST_E_FINANCIAL_CRITERIA"; break; + case TRUST_E_PROVIDER_UNKNOWN: pszErrConst = "TRUST_E_PROVIDER_UNKNOWN"; break; + case TRUST_E_ACTION_UNKNOWN: pszErrConst = "TRUST_E_ACTION_UNKNOWN"; break; + case TRUST_E_SUBJECT_FORM_UNKNOWN: pszErrConst = "TRUST_E_SUBJECT_FORM_UNKNOWN"; break; + case TRUST_E_SUBJECT_NOT_TRUSTED: pszErrConst = "TRUST_E_SUBJECT_NOT_TRUSTED"; break; + case TRUST_E_NOSIGNATURE: pszErrConst = "TRUST_E_NOSIGNATURE"; break; + case TRUST_E_FAIL: pszErrConst = "TRUST_E_FAIL"; break; + case TRUST_E_EXPLICIT_DISTRUST: pszErrConst = "TRUST_E_EXPLICIT_DISTRUST"; break; + } + if (pszErrConst) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_UNSUPPORTED_ARCH, + "WinVerifyTrust failed with hrc=%s on '%ls'", pszErrConst, pwszName); + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_UNSUPPORTED_ARCH, + "WinVerifyTrust failed with hrc=%Rhrc on '%ls'", hrc, pwszName); + } + + /* clean up state data. */ + TrustData.dwStateAction = WTD_STATEACTION_CLOSE; + FileInfo.hFile = NULL; + hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &PolicyActionGuid, &TrustData); + + return rc; +} + + +/** + * Calls WinVerifyTrust to verify an PE image via catalog files. + * + * @returns VBox status code. + * @param hFile File handle to the executable file. + * @param pwszName Full NT path to the DLL in question, used for + * dealing with unsigned system dlls as well as for + * error/logging. + * @param fFlags Flags, SUPHNTVI_F_XXX. + * @param pErrInfo Pointer to error info structure. Optional. + * @param pfnWinVerifyTrust Pointer to the API. + */ +static int supR3HardNtViCallWinVerifyTrustCatFile(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust) +{ + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: hFile=%p pwszName=%ls\n", hFile, pwszName)); + + /* + * Convert the name into a Windows name. + */ + RTUTF16 wszWinPathBuf[MAX_PATH]; + PCRTUTF16 pwszWinPath; + int rc = supR3HardNtViNtToWinPath(pwszName, &pwszWinPath, wszWinPathBuf, RT_ELEMENTS(wszWinPathBuf)); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "Bad path passed to supR3HardNtViCallWinVerifyTrustCatFile: rc=%Rrc '%ls'", rc, pwszName); + + /* + * Open the file if we didn't get a handle. + */ + HANDLE hFileClose = NULL; + if (hFile == RTNT_INVALID_HANDLE_VALUE || hFile == NULL) + { + hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)pwszName; + NtName.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR)); + NtName.MaximumLength = NtName.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), + "NtCreateFile returned %#x opening '%ls'.", rcNt, pwszName); + hFileClose = hFile; + } + + /* + * On Windows 8.0 and later there are more than one digest choice. + */ + rc = VERR_LDRVI_NOT_SIGNED; + static struct + { + /** The digest algorithm name. */ + const WCHAR *pszAlgorithm; + /** Cached catalog admin handle. */ + HCATADMIN volatile hCachedCatAdmin; + } s_aHashes[] = + { + { NULL, NULL }, + { L"SHA256", NULL }, + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_aHashes); i++) + { + /* + * Another loop for dealing with different trust provider policies + * required for successfully validating different catalog signatures. + */ + bool fTryNextPolicy; + uint32_t iPolicy = 0; + static const GUID s_aPolicies[] = + { + DRIVER_ACTION_VERIFY, /* Works with microsoft bits. Most frequently used, thus first. */ + WINTRUST_ACTION_GENERIC_VERIFY_V2, /* Works with ATI and other SPC kernel-code signed stuff. */ + }; + do + { + /* + * Create a context. + */ + fTryNextPolicy = false; + BOOL fRc; + HCATADMIN hCatAdmin = ASMAtomicXchgPtr(&s_aHashes[i].hCachedCatAdmin, NULL); + if (hCatAdmin) + fRc = TRUE; + else if (g_pfnCryptCATAdminAcquireContext2) + fRc = g_pfnCryptCATAdminAcquireContext2(&hCatAdmin, &s_aPolicies[iPolicy], s_aHashes[i].pszAlgorithm, + NULL /*pStrongHashPolicy*/, 0 /*dwFlags*/); + else + fRc = g_pfnCryptCATAdminAcquireContext(&hCatAdmin, &s_aPolicies[iPolicy], 0 /*dwFlags*/); + if (fRc) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: hCatAdmin=%p\n", hCatAdmin)); + + /* + * Hash the file. + */ + BYTE abHash[SUPHARDNTVI_MAX_CAT_HASH_SIZE]; + DWORD cbHash = sizeof(abHash); + if (g_pfnCryptCATAdminCalcHashFromFileHandle2) + fRc = g_pfnCryptCATAdminCalcHashFromFileHandle2(hCatAdmin, hFile, &cbHash, abHash, 0 /*dwFlags*/); + else + fRc = g_pfnCryptCATAdminCalcHashFromFileHandle(hFile, &cbHash, abHash, 0 /*dwFlags*/); + if (fRc) + { + /* Produce a string version of it that we can pass to WinVerifyTrust. */ + RTUTF16 wszDigest[SUPHARDNTVI_MAX_CAT_HASH_SIZE * 2 + 1]; + int rc2 = RTUtf16PrintHexBytes(wszDigest, RT_ELEMENTS(wszDigest), abHash, cbHash, RTSTRPRINTHEXBYTES_F_UPPER); + if (RT_SUCCESS(rc2)) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: cbHash=%u wszDigest=%ls\n", cbHash, wszDigest)); + + /* + * Enumerate catalog information that matches the hash. + */ + uint32_t iCat = 0; + HCATINFO hCatInfoPrev = NULL; + do + { + /* Get the next match. */ + HCATINFO hCatInfo = g_pfnCryptCATAdminEnumCatalogFromHash(hCatAdmin, abHash, cbHash, 0, &hCatInfoPrev); + if (!hCatInfo) + { + if (iCat == 0) + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATAdminEnumCatalogFromHash failed %u\n", GetLastError())); + break; + } + Assert(hCatInfoPrev == NULL); + hCatInfoPrev = hCatInfo; + + /* + * Call WinVerifyTrust. + */ + CATALOG_INFO CatInfo; + CatInfo.cbStruct = sizeof(CatInfo); + CatInfo.wszCatalogFile[0] = '\0'; + if (g_pfnCryptCATCatalogInfoFromContext(hCatInfo, &CatInfo, 0 /*dwFlags*/)) + { + WINTRUST_CATALOG_INFO WtCatInfo; + RT_ZERO(WtCatInfo); + WtCatInfo.cbStruct = sizeof(WtCatInfo); + WtCatInfo.dwCatalogVersion = 0; + WtCatInfo.pcwszCatalogFilePath = CatInfo.wszCatalogFile; + WtCatInfo.pcwszMemberTag = wszDigest; + WtCatInfo.pcwszMemberFilePath = pwszWinPath; + WtCatInfo.pbCalculatedFileHash = abHash; + WtCatInfo.cbCalculatedFileHash = cbHash; + WtCatInfo.pcCatalogContext = NULL; + + WINTRUST_DATA TrustData; + RT_ZERO(TrustData); + TrustData.cbStruct = sizeof(TrustData); + TrustData.fdwRevocationChecks = WTD_REVOKE_NONE; /* Keep simple for now. */ + TrustData.dwStateAction = WTD_STATEACTION_VERIFY; + TrustData.dwUIChoice = WTD_UI_NONE; + TrustData.dwProvFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + TrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + else + TrustData.dwProvFlags = WTD_REVOCATION_CHECK_NONE; + TrustData.dwUnionChoice = WTD_CHOICE_CATALOG; + TrustData.pCatalog = &WtCatInfo; + + HRESULT hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &s_aPolicies[iPolicy], &TrustData); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: WinVerifyTrust => %#x; cat=%ls\n", hrc, CatInfo.wszCatalogFile)); + + if (SUCCEEDED(hrc)) + rc = VINF_SUCCESS; + else if (hrc == TRUST_E_NOSIGNATURE) + { /* ignore because it's useless. */ } + else if (hrc == ERROR_INVALID_PARAMETER) + { /* This is returned if the given file isn't found in the catalog, it seems. */ } + else + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_WINTRUST_CAT_FAILURE, + "WinVerifyTrust failed with hrc=%#x on '%ls' and .cat-file='%ls'.", + hrc, pwszWinPath, CatInfo.wszCatalogFile); + fTryNextPolicy = (hrc == CERT_E_UNTRUSTEDROOT); + } + + /* clean up state data. */ + TrustData.dwStateAction = WTD_STATEACTION_CLOSE; + hrc = pfnWinVerifyTrust(NULL /*hwnd*/, &s_aPolicies[iPolicy], &TrustData); + Assert(SUCCEEDED(hrc)); + } + else + { + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(GetLastError()), + "CryptCATCatalogInfoFromContext failed: %d [file=%s]", + GetLastError(), pwszName); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATCatalogInfoFromContext failed\n")); + } + iCat++; + } while (rc == VERR_LDRVI_NOT_SIGNED && iCat < 128); + + if (hCatInfoPrev != NULL) + if (!g_pfnCryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfoPrev, 0 /*dwFlags*/)) + AssertFailed(); + } + else + rc = RTErrInfoSetF(pErrInfo, rc2, "RTUtf16PrintHexBytes failed: %Rrc", rc); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(GetLastError()), + "CryptCATAdminCalcHashFromFileHandle[2] failed: %d [file=%s]", GetLastError(), pwszName); + + if (!ASMAtomicCmpXchgPtr(&s_aHashes[i].hCachedCatAdmin, hCatAdmin, NULL)) + if (!g_pfnCryptCATAdminReleaseContext(hCatAdmin, 0 /*dwFlags*/)) + AssertFailed(); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(GetLastError()), + "CryptCATAdminAcquireContext[2] failed: %d [file=%s]", GetLastError(), pwszName); + iPolicy++; + } while ( fTryNextPolicy + && iPolicy < RT_ELEMENTS(s_aPolicies)); + + /* + * Only repeat if we've got g_pfnCryptCATAdminAcquireContext2 and can specify the hash algorithm. + */ + if (!g_pfnCryptCATAdminAcquireContext2) + break; + if (rc != VERR_LDRVI_NOT_SIGNED) + break; + } + + if (hFileClose != NULL) + NtClose(hFileClose); + + return rc; +} + + +/** + * Initializes g_uNtVerCombined and g_NtVerInfo. + * Called from suplibHardenedWindowsMain and suplibOsInit. + */ +DECLHIDDEN(void) supR3HardenedWinInitVersion(void) +{ + /* + * Get the windows version. Use RtlGetVersion as GetVersionExW and + * GetVersion might not be telling the whole truth (8.0 on 8.1 depending on + * the application manifest). + */ + OSVERSIONINFOEXW NtVerInfo; + + suplibHardenedMemSet(&NtVerInfo, 0, sizeof(NtVerInfo)); + NtVerInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + if (!NT_SUCCESS(RtlGetVersion((PRTL_OSVERSIONINFOW)&NtVerInfo))) + { + suplibHardenedMemSet(&NtVerInfo, 0, sizeof(NtVerInfo)); + NtVerInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW); + if (!NT_SUCCESS(RtlGetVersion((PRTL_OSVERSIONINFOW)&NtVerInfo))) + { + NtVerInfo.dwOSVersionInfoSize = sizeof(NtVerInfo); + if (!GetVersionExW((OSVERSIONINFOW *)&NtVerInfo)) + { + suplibHardenedMemSet(&NtVerInfo, 0, sizeof(NtVerInfo)); + DWORD dwVer = GetVersion(); + NtVerInfo.dwMajorVersion = RT_BYTE1(dwVer); + NtVerInfo.dwMinorVersion = RT_BYTE2(dwVer); + NtVerInfo.dwBuildNumber = RT_BIT_32(31) & dwVer ? 0 : RT_HI_U16(dwVer); + } + } + } + g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(NtVerInfo.dwMajorVersion, NtVerInfo.dwMinorVersion, NtVerInfo.dwBuildNumber, + NtVerInfo.wServicePackMajor, NtVerInfo.wServicePackMinor); +} + +#endif /* IN_RING3 */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp new file mode 100644 index 00000000000..8477352daf2 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp @@ -0,0 +1,1196 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Process Verification, Windows. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#ifdef IN_RING0 +# define IPRT_NT_MAP_TO_ZW +# include <iprt/nt/nt.h> +# include <ntimage.h> +#else +# include <iprt/nt/nt-and-windows.h> +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/param.h> + +#ifdef IN_RING0 +# include "SUPDrvInternal.h" +#else +# include "SUPLibInternal.h" +#endif +#include "win/SUPHardenedVerify-win.h" + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * Virtual address space region. + */ +typedef struct SUPHNTVPREGION +{ + /** The RVA of the region. */ + uint32_t uRva; + /** The size of the region. */ + uint32_t cb; + /** The protection of the region. */ + uint32_t fProt; +} SUPHNTVPREGION; +/** Pointer to a virtual address space region. */ +typedef SUPHNTVPREGION *PSUPHNTVPREGION; + +/** + * Virtual address space image information. + */ +typedef struct SUPHNTVPIMAGE +{ + /** The base address of the image. */ + uintptr_t uImageBase; + /** The size of the image mapping. */ + uintptr_t cbImage; + + /** The name from the allowed lists. */ + const char *pszName; + /** Name structure for NtQueryVirtualMemory/MemorySectionName. */ + struct + { + /** The full unicode name. */ + UNICODE_STRING UniStr; + /** Buffer space. */ + WCHAR awcBuffer[260]; + } Name; + + /** The number of mapping regions. */ + uint32_t cRegions; + /** Mapping regions. */ + SUPHNTVPREGION aRegions[16]; + + /** The image characteristics from the FileHeader. */ + uint16_t fImageCharecteristics; + /** The DLL characteristics from the OptionalHeader. */ + uint16_t fDllCharecteristics; + + /** Set if this is the DLL. */ + bool fDll; + /** Set if the image is NTDLL an the verficiation code needs to watch out for + * the NtCreateSection patch. */ + bool fNtCreateSectionPatch; + /** Whether the API set schema hack needs to be applied when verifying memory + * content. The hack means that we only check if the 1st section is mapped. */ + bool fApiSetSchemaOnlySection1; +} SUPHNTVPIMAGE; +/** Pointer to image info from the virtual address space scan. */ +typedef SUPHNTVPIMAGE *PSUPHNTVPIMAGE; + +/** + * Virtual address space scanning state. + */ +typedef struct SUPHNTVPSTATE +{ + /** Number of images in aImages. */ + uint32_t cImages; + /** Images found in the process. + * The array is large enough to hold the executable, all allowed DLLs, and one + * more so we can get the image name of the first unwanted DLL. */ + SUPHNTVPIMAGE aImages[1+5+1]; + /** Memory compare scratch buffer.*/ + uint8_t abMemory[_4K]; + /** File compare scratch buffer.*/ + uint8_t abFile[_4K]; + /** Section headers for use when comparing file and loaded image. */ + IMAGE_SECTION_HEADER aSecHdrs[16]; + +} SUPHNTVPSTATE; +/** Pointer to stat information of a virtual address space scan. */ +typedef SUPHNTVPSTATE *PSUPHNTVPSTATE; + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** + * System DLLs allowed to be loaded into the process. + * @remarks supHardNtVpCheckDlls assumes these are lower case. + */ +static const char *g_apszSupNtVpAllowedDlls[] = +{ + "ntdll.dll", + "kernel32.dll", + "kernelbase.dll", + "apphelp.dll", + "apisetschema.dll" +}; + +/** + * VBox executables allowed to start VMs. + * @remarks Remember to keep in sync with SUPR3HardenedVerify.cpp. + */ +static const char *g_apszSupNtVpAllowedVmExes[] = +{ + "VBoxHeadless.exe", + "VirtualBox.exe", + "VBoxSDL.exe", + "VBoxNetDHCP.exe", + "VBoxNetNAT.exe", +}; + +/** Pointer to NtQueryVirtualMemory. Initialized by SUPDrv-win.cpp in + * ring-0, in ring-3 it's just a slightly confusing define. */ +#ifdef IN_RING0 +PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory = NULL; +#else +# define g_pfnNtQueryVirtualMemory NtQueryVirtualMemory +#endif + + +/** + * Fills in error information. + * + * @returns @a rc. + * @param pErrInfo Pointer to the extened error info structure. + * Can be NULL. + * @param pszErr Where to return error details. + * @param cbErr Size of the buffer @a pszErr points to. + * @param rc The status to return. + * @param pszMsg The format string for the message. + * @param ... The arguments for the format string. + */ +static int supHardNtVpSetInfo(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...) +{ + va_list va; +#ifdef IN_RING3 + va_start(va, pszMsg); + supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va); + va_end(va); +#endif + + va_start(va, pszMsg); + RTErrInfoSetV(pErrInfo, rc, pszMsg, va); + va_end(va); + + return rc; +} + + +static NTSTATUS supHardNtVpReadFile(HANDLE hFile, uint64_t off, void *pvBuf, size_t cbRead) +{ + if ((ULONG)cbRead != cbRead) + return STATUS_INTEGER_OVERFLOW; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtReadFile(hFile, + NULL /*hEvent*/, + NULL /*ApcRoutine*/, + NULL /*ApcContext*/, + &Ios, + pvBuf, + (ULONG)cbRead, + (PLARGE_INTEGER)&off, + NULL); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + return rcNt; +} + + +static NTSTATUS supHardNtVpReadMem(HANDLE hProcess, uintptr_t uPtr, void *pvBuf, size_t cbRead) +{ +#ifdef IN_RING0 + /* ASSUMES hProcess is the current process. */ + /** @todo use MmCopyVirtualMemory where available! */ + int rc = RTR0MemUserCopyFrom(pvBuf, uPtr, cbRead); + if (RT_SUCCESS(rc)) + return STATUS_SUCCESS; + return STATUS_ACCESS_DENIED; +#else + SIZE_T cbIgn; + NTSTATUS rcNt = NtReadVirtualMemory(hProcess, (PVOID)uPtr, pvBuf, cbRead, &cbIgn); + if (NT_SUCCESS(rcNt) && cbIgn != cbRead) + rcNt = STATUS_IO_DEVICE_ERROR; + return rcNt; +#endif +} + + +static int supHardNtVpFileMemCompare(const void *pvFile, const void *pvMemory, size_t cbToCompare, + PSUPHNTVPIMAGE pImage, uint32_t uRva, PRTERRINFO pErrInfo) +{ + if (suplibHardenedMemComp(pvFile, pvMemory, cbToCompare) == 0) + return VINF_SUCCESS; + + /* Find the exact location. */ + const uint8_t *pbFile = (const uint8_t *)pvFile; + const uint8_t *pbMemory = (const uint8_t *)pvMemory; + while (cbToCompare > 0 && *pbFile == *pbMemory) + { + cbToCompare--; + pbFile++; + pbMemory++; + uRva++; + } + + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH, + "%s: memory compare at %#x failed: %#x != %#x\n", pImage->pszName, uRva, *pbFile, *pbMemory); +} + + +static int supHardNtVpCheckSectionProtection(PSUPHNTVPIMAGE pImage, uint32_t uRva, uint32_t cb, uint32_t fProt, + PRTERRINFO pErrInfo) +{ + uint32_t const cbOrg = cb; + if (!cb) + return VINF_SUCCESS; + + for (uint32_t i = 0; i < pImage->cRegions; i++) + { + uint32_t offRegion = uRva - pImage->aRegions[i].uRva; + if (offRegion < pImage->aRegions[i].cb) + { + uint32_t cbLeft = pImage->aRegions[i].cb - offRegion; + if ( pImage->aRegions[i].fProt != fProt + && ( fProt != PAGE_READWRITE + || pImage->aRegions[i].fProt != PAGE_WRITECOPY)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_SECTION_PROTECTION_MISMATCH, + "%s: RVA range %#x-%#x protection is %#x, expected %#x. (cb=%#x)", + pImage->pszName, uRva, uRva + cbLeft - 1, pImage->aRegions[i].fProt, fProt, cb); + if (cbLeft >= cb) + return VINF_SUCCESS; + cb -= cbLeft; + uRva += cbLeft; + +#if 0 /* This shouldn't ever be necessary. */ + if ( i + 1 < pImage->cRegions + && uRva < pImage->aRegions[i + 1].uRva) + { + cbLeft = pImage->aRegions[i + 1].uRva - uRva; + if (cbLeft >= cb) + return VINF_SUCCESS; + cb -= cbLeft; + uRva += cbLeft; + } +#endif + } + } + + return supHardNtVpSetInfo(pErrInfo, cbOrg == cb ? VERR_SUP_VP_SECTION_NOT_MAPPED : VERR_SUP_VP_SECTION_NOT_FULLY_MAPPED, + "%s: RVA range %#x-%#x is not mapped?", pImage->pszName, uRva, uRva + cb - 1); +} + + +/** + * Compares process memory with the disk content. + * + * @returns VBox status code. + * @param pThis The process scanning state structure (for the + * two scratch buffers). + * @param pImage The image data collected during the address + * space scan. + * @param hProcess Handle to the process. + * @param hFile Handle to the image file. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpVerifyImageCompareMemory(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, HANDLE hProcess, HANDLE hFile, + PRTERRINFO pErrInfo) +{ + /* + * Read and find the file headers. + */ + NTSTATUS rcNt = supHardNtVpReadFile(hFile, 0, pThis->abFile, sizeof(pThis->abFile)); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_IMAGE_HDR_READ_ERROR, + "%s: Error reading image header: %#x", pImage->pszName, rcNt); + + uint32_t offNtHdrs = 0; + PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)&pThis->abFile[0]; + if (pDosHdr->e_magic == IMAGE_DOS_SIGNATURE) + { + offNtHdrs = pDosHdr->e_lfanew; + if (offNtHdrs > 512 || offNtHdrs < sizeof(*pDosHdr)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_MZ_OFFSET, + "%s: Unexpected e_lfanew value: %#x", pImage->pszName, offNtHdrs); + } + PIMAGE_NT_HEADERS pNtHdrs = (PIMAGE_NT_HEADERS)&pThis->abFile[offNtHdrs]; + if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_IMAGE_SIGNATURE, + "%s: No PE signature at %#x: %#x", pImage->pszName, offNtHdrs, pNtHdrs->Signature); + + /* + * Do basic header validation. + */ +#ifdef RT_ARCH_AMD64 + if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64) +#else + if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) +#endif + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_UNEXPECTED_IMAGE_MACHINE, + "%s: Unexpected machine: %#x", pImage->pszName, pNtHdrs->FileHeader.Machine); + + if (pNtHdrs->FileHeader.SizeOfOptionalHeader != sizeof(pNtHdrs->OptionalHeader)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected optional header size: %#x", + pImage->pszName, pNtHdrs->FileHeader.SizeOfOptionalHeader); + + if (pNtHdrs->OptionalHeader.Magic != RT_CONCAT3(IMAGE_NT_OPTIONAL_HDR,ARCH_BITS,_MAGIC)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected optional header magic: %#x", pImage->pszName, pNtHdrs->OptionalHeader.Magic); + if (pNtHdrs->OptionalHeader.NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected data dirs: %#x", pImage->pszName, pNtHdrs->OptionalHeader.NumberOfRvaAndSizes); + + /* + * Before we start comparing things, store what we need to know from the headers. + */ + uint32_t const cSections = pNtHdrs->FileHeader.NumberOfSections; + if (cSections > RT_ELEMENTS(pThis->aSecHdrs)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_MANY_SECTIONS, + "%s: Too many section headers: %#x", pImage->pszName, cSections); + suplibHardenedMemCopy(pThis->aSecHdrs, pNtHdrs + 1, cSections * sizeof(IMAGE_SECTION_HEADER)); + + uintptr_t const uImageBase = pNtHdrs->OptionalHeader.ImageBase; + if (uImageBase & PAGE_OFFSET_MASK) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_IMAGE_BASE, + "%s: Invalid image base: %p", pImage->pszName, uImageBase); + + uint32_t const cbImage = pNtHdrs->OptionalHeader.SizeOfImage; + if (RT_ALIGN_32(pImage->cbImage, PAGE_SIZE) != RT_ALIGN_32(cbImage, PAGE_SIZE) && !pImage->fApiSetSchemaOnlySection1) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_IMAGE_SIZE, + "%s: SizeOfImage (%#x) isn't close enough to the mapping size (%#x)", + pImage->pszName, cbImage, pImage->cbImage); + + uint32_t const cbSectAlign = pNtHdrs->OptionalHeader.SectionAlignment; + if ( !RT_IS_POWER_OF_TWO(cbSectAlign) + || cbSectAlign < PAGE_SIZE + || cbSectAlign > (pImage->fApiSetSchemaOnlySection1 ? _64K : (uint32_t)PAGE_SIZE) ) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SECTION_ALIGNMENT_VALUE, + "%s: Unexpected SectionAlignment value: %#x", pImage->pszName, cbSectAlign); + + uint32_t const cbFileAlign = pNtHdrs->OptionalHeader.FileAlignment; + if (!RT_IS_POWER_OF_TWO(cbFileAlign) || cbFileAlign < 512 || cbFileAlign > PAGE_SIZE || cbFileAlign > cbSectAlign) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_FILE_ALIGNMENT_VALUE, + "%s: Unexpected FileAlignment value: %#x (cbSectAlign=%#x)", + pImage->pszName, cbFileAlign, cbSectAlign); + + uint32_t const cbHeaders = pNtHdrs->OptionalHeader.SizeOfHeaders; + uint32_t const cbMinHdrs = offNtHdrs + sizeof(*pNtHdrs) + sizeof(IMAGE_SECTION_HEADER) * cSections; + if (cbHeaders < cbMinHdrs) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SIZE_OF_HEADERS, + "%s: Headers are too small: %#x < %#x (cSections=%#x)", + pImage->pszName, cbHeaders, cbMinHdrs, cSections); + uint32_t const cbHdrsFile = RT_ALIGN_32(cbHeaders, cbFileAlign); + if (cbHdrsFile > sizeof(pThis->abFile)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SIZE_OF_HEADERS, + "%s: Headers are larger than expected: %#x/%#x (expected max %zx)", + pImage->pszName, cbHeaders, cbHdrsFile, sizeof(pThis->abFile)); + + /* + * Compare the file header with the loaded bits. The loader will fiddle + * with image base, changing it to the actual load address. + */ + int rc; + if (!pImage->fApiSetSchemaOnlySection1) + { + rcNt = supHardNtVpReadMem(hProcess, pImage->uImageBase, pThis->abMemory, cbHdrsFile); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_MEMORY_READ_ERROR, + "%s: Error reading image header from memory: %#x", pImage->pszName, rcNt); + if (uImageBase != pImage->uImageBase) + pNtHdrs->OptionalHeader.ImageBase = pImage->uImageBase; + + rc = supHardNtVpFileMemCompare(pThis->abFile, pThis->abMemory, cbHeaders, pImage, 0 /*uRva*/, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + rc = supHardNtVpCheckSectionProtection(pImage, 0 /*uRva*/, cbHdrsFile, PAGE_READONLY, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Save some header fields we might be using later on. + */ + pImage->fImageCharecteristics = pNtHdrs->FileHeader.Characteristics; + pImage->fDllCharecteristics = pNtHdrs->OptionalHeader.DllCharacteristics; + + /* + * Validate sections and check them against the mapping regions. + */ + uint32_t uRva = cbHdrsFile; + for (uint32_t i = 0; i < cSections; i++) + { + /* Validate the section. */ + uint32_t uSectRva = pThis->aSecHdrs[i].VirtualAddress; + if (uSectRva < uRva || uSectRva > cbImage || RT_ALIGN_32(uSectRva, cbSectAlign) != uSectRva) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SECTION_RVA, + "%s: Section %u: Invalid virtual address: %#x (uRva=%#x, cbImage=%#x, cbSectAlign=%#x)", + pImage->pszName, i, uSectRva, uRva, cbImage, cbSectAlign); + uint32_t cbMap = pThis->aSecHdrs[i].Misc.VirtualSize; + if (cbMap > cbImage || uRva + cbMap > cbImage) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SECTION_VIRTUAL_SIZE, + "%s: Section %u: Invalid virtual size: %#x (uSectRva=%#x, uRva=%#x, cbImage=%#x)", + pImage->pszName, i, cbMap, uSectRva, uRva, cbImage); + uint32_t cbFile = pThis->aSecHdrs[i].SizeOfRawData; + if (cbFile != RT_ALIGN_32(cbFile, cbFileAlign) || cbFile > RT_ALIGN_32(cbMap, cbSectAlign)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_BAD_SECTION_FILE_SIZE, + "%s: Section %u: Invalid file size: %#x (cbMap=%#x, uSectRva=%#x)", + pImage->pszName, i, cbFile, cbMap, uSectRva); + + /* Validate the protection. */ + if (!pImage->fApiSetSchemaOnlySection1 || i == 0) + { + if (pImage->fApiSetSchemaOnlySection1) + { + pImage->uImageBase -= uSectRva; + pImage->cbImage += uSectRva; + pImage->aRegions[i].uRva = uSectRva; + } + + uint32_t fProt; + switch (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) + { + case IMAGE_SCN_MEM_READ: + fProt = PAGE_READONLY; + break; + case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: + fProt = PAGE_READWRITE; + if (!suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll */ + fProt = PAGE_READONLY; + break; + case IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE: + fProt = PAGE_EXECUTE_READ; + break; + case IMAGE_SCN_MEM_EXECUTE: + fProt = PAGE_EXECUTE; + break; + default: + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_UNEXPECTED_SECTION_FLAGS, + "%s: Section %u: Unexpected characteristics: %#x (uSectRva=%#x, cbMap=%#x)", + pImage->pszName, i, pThis->aSecHdrs[i].Characteristics, uSectRva, cbMap); + + } + rc = supHardNtVpCheckSectionProtection(pImage, uSectRva, RT_ALIGN_32(cbMap, PAGE_SIZE), fProt, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + + /* Advance the RVA. */ + uRva = uSectRva + RT_ALIGN_32(cbMap, cbSectAlign); + } + + /* + * Check the mapping regions with the image to make sure someone didn't + * fill executable code into some gap in the image. + */ + /** @todo not vital. */ + + + /* + * Compare executable code. If we're not loaded at the link address, we + * need to load base relocations and apply them while making the compare. + * A special case + */ + /** @todo not vital. */ + + + return VINF_SUCCESS; +} + + +/** + * Verifies the signature of the given image on disk, then checks if the memory + * mapping matches what we verified. + * + * @returns VBox status code. + * @param pThis The process scanning state structure (for the + * two scratch buffers). + * @param pImage The image data collected during the address + * space scan. + * @param hProcess Handle to the process. + * @param hFile Handle to the image file. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpVerifyImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, HANDLE hProcess, PRTERRINFO pErrInfo) +{ + /* + * Open the image. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &pImage->Name.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_IMAGE_FILE_OPEN_ERROR, + "Error opening image for scanning: %#x (name %ls)", rcNt, pImage->Name.UniStr.Buffer); + + /* + * Validate the signature, then make an attempt at comparing memory and + * disk content. + */ + int rc = supHardenedWinVerifyImageByHandle(hFile, pImage->Name.UniStr.Buffer, + pImage->fDll ? 0 : SUPHNTVI_F_REQUIRE_BUILD_CERT, + NULL /*pfCacheable*/, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpVerifyImageCompareMemory(pThis, pImage, hProcess, hFile, pErrInfo); + + /* + * Clean up and return. + */ + rcNt = NtClose(hFile); + if (!NT_SUCCESS(rcNt) && RT_SUCCESS(rc)) + rc = supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_IMAGE_FILE_CLOSE_ERROR, + "Error closing image after scanning: %#x (name %ls)", rcNt, pImage->Name.UniStr.Buffer); + return rc; +} + + +/** + * Verifies that there is only one thread in the process. + * + * @returns VBox status code. + * @param hProcess The process. + * @param hThread The thread. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo) +{ + /* + * Use the ThreadAmILastThread request to check that there is only one + * thread in the process. + */ + ULONG cbIgn = 0; + ULONG fAmI = 0; + NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_THREAD_ERROR, + "NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt); + if (!fAmI) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_THREAD_NOT_ALONE, + "More than one thread in process"); + + /** @todo Would be nice to verify the relation ship between hProcess and hThread + * as well... */ + return VINF_SUCCESS; +} + + +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS +/** + * Verifies that there isn't a debugger attached to the process. + * + * @returns VBox status code. + * @param hProcess The process. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo) +{ + /* + * Use the ProcessDebugPort request to check there is no debugger + * currently attached to the process. + */ + ULONG cbIgn = 0; + uintptr_t uPtr = ~(uintptr_t)0; + NTSTATUS rcNt = NtQueryInformationProcess(hProcess, + ProcessDebugPort, + &uPtr, sizeof(uPtr), &cbIgn); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_DBG_PORT_ERROR, + "NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt); + if (uPtr != 0) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_DEBUGGED, + "Debugger attached (%#zx)", uPtr); + return VINF_SUCCESS; +} +#endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** + * Allocates and initalizes a process stat structure for process virtual memory + * scanning. + * + * @returns Pointer to the state structure on success, NULL on failure. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static PSUPHNTVPSTATE supHardNtVpCreateState(PRTERRINFO pErrInfo) +{ + /* + * Allocate the memory. + */ + PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)suplibHardenedAllocZ(sizeof(*pThis)); + if (pThis) + return pThis; + supHardNtVpSetInfo(pErrInfo, VERR_NO_MEMORY, "Failed to allocate %zu bytes for state structures.", sizeof(*pThis)); + return NULL; +} + + +/** + * Matches two UNICODE_STRING structures in a case sensitive fashion. + * + * @returns true if equal, false if not. + * @param pUniStr1 The first unicode string. + * @param pUniStr2 The first unicode string. + */ +static bool supHardNtVpAreUniStringsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2) +{ + if (pUniStr1->Length != pUniStr2->Length) + return false; + return suplibHardenedMemComp(pUniStr1->Buffer, pUniStr2->Buffer, pUniStr1->Length) == 0; +} + + +/** + * Performs a case insensitive comparison of an ASCII and an UTF-16 file name. + * + * @returns true / false + * @param pszName1 The ASCII name. + * @param pwszName2 The UTF-16 name. + */ +static bool supHardNtVpAreNamesEqual(const char *pszName1, PCRTUTF16 pwszName2) +{ + for (;;) + { + char ch1 = *pszName1++; + RTUTF16 wc2 = *pwszName2++; + if (ch1 != wc2) + { + ch1 = RT_C_TO_LOWER(ch1); + wc2 = wc2 < 0x80 ? RT_C_TO_LOWER(wc2) : wc2; + if (ch1 != wc2) + return false; + } + if (!ch1) + return true; + } +} + + +/** + * Records an additional memory region for an image. + * + * @returns VBox status code. + * @param pImage The new image structure. Only the unicode name + * buffer is valid. + * @param pMemInfo The memory information for the image. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpNewImage(PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo, PRTERRINFO pErrInfo) +{ + /* + * Extract the final component. + */ + unsigned cwcDirName = pImage->Name.UniStr.Length / sizeof(WCHAR); + PCRTUTF16 pwszFilename = &pImage->Name.UniStr.Buffer[cwcDirName]; + while ( cwcDirName > 0 + && pwszFilename[-1] != '\\' + && pwszFilename[-1] != '/' + && pwszFilename[-1] != ':') + { + pwszFilename--; + cwcDirName--; + } + if (!*pwszFilename) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NO_IMAGE_MAPPING_NAME, + "Empty filename (len=%u) for image at %p.", pImage->Name.UniStr.Length, pMemInfo->BaseAddress); + + /* + * Drop trailing slashes from the directory name. + */ + while ( cwcDirName > 0 + && ( pImage->Name.UniStr.Buffer[cwcDirName - 1] == '\\' + || pImage->Name.UniStr.Buffer[cwcDirName - 1] == '/')) + cwcDirName--; + + /* + * Match it against known DLLs. + */ + pImage->pszName = NULL; + for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls); i++) + if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedDlls[i], pwszFilename)) + { + pImage->pszName = g_apszSupNtVpAllowedDlls[i]; + pImage->fDll = true; + + /* The directory name must match the one we've got for System32. */ + if ( cwcDirName * sizeof(WCHAR) != g_System32NtPath.UniStr.Length + || suplibHardenedMemComp(pImage->Name.UniStr.Buffer, + g_System32NtPath.UniStr.Buffer, + cwcDirName * sizeof(WCHAR))) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NON_SYSTEM32_DLL, + "Expected %ls to be loaded from %ls.", + pImage->Name.UniStr.Buffer, g_System32NtPath.UniStr.Buffer); + + break; + } + if (!pImage->pszName) + { + /* + * Not a known DLL, executable? + */ + for (uint32_t i = 0; i < RT_ELEMENTS(g_apszSupNtVpAllowedVmExes); i++) + if (supHardNtVpAreNamesEqual(g_apszSupNtVpAllowedVmExes[i], pwszFilename)) + { + pImage->pszName = g_apszSupNtVpAllowedVmExes[i]; + pImage->fDll = false; + break; + } + } + if (!pImage->pszName) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE, + "Unknown image file %ls at %p.", pImage->Name.UniStr.Buffer, pMemInfo->BaseAddress); + + /* + * Since it's a new image, we expect to be at the start of the mapping now. + */ + if (pMemInfo->AllocationBase != pMemInfo->BaseAddress) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_IMAGE_MAPPING_BASE_ERROR, + "Invalid AllocationBase/BaseAddress for %s: %p vs %p.", + pImage->pszName, pMemInfo->AllocationBase, pMemInfo->BaseAddress); + + /* + * Check for size/rva overflow. + */ + if (pMemInfo->RegionSize >= _2G) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_LARGE_REGION, + "Region 0 of image %s is too large: %p.", pImage->pszName, pMemInfo->RegionSize); + + /* + * Fill in details from the memory info. + */ + pImage->uImageBase = (uintptr_t)pMemInfo->AllocationBase; + pImage->cbImage = pMemInfo->RegionSize; + pImage->cRegions = 1; + pImage->aRegions[0].uRva = 0; + pImage->aRegions[0].cb = (uint32_t)pMemInfo->RegionSize; + pImage->aRegions[0].fProt = pMemInfo->Protect; + + return VINF_SUCCESS; +} + + +/** + * Records an additional memory region for an image. + * + * @returns VBox status code. + * @param pImage The image. + * @param pMemInfo The memory information for the region. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpAddRegion(PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo, PRTERRINFO pErrInfo) +{ + /* + * Make sure the base address matches. + */ + if (pImage->uImageBase != (uintptr_t)pMemInfo->AllocationBase) + return supHardNtVpSetInfo(pErrInfo, VERR_SUPLIB_NT_PROCESS_UNTRUSTED_3, + "Base address mismatch for %s: have %p, found %p for region %p LB %#zx.", + pImage->pszName, pImage->uImageBase, pMemInfo->AllocationBase, + pMemInfo->BaseAddress, pMemInfo->RegionSize); + + /* + * Check for size and rva overflows. + */ + uintptr_t uRva = (uintptr_t)pMemInfo->BaseAddress - pImage->uImageBase; + if (pMemInfo->RegionSize >= _2G) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_LARGE_REGION, + "Region %u of image %s is too large: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva); + if (uRva >= _2G) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_HIGH_REGION_RVA, + "Region %u of image %s is too high: %p/%p.", pImage->pszName, pMemInfo->RegionSize, uRva); + + + /* + * Record the region. + */ + uint32_t iRegion = pImage->cRegions; + if (iRegion + 1 >= RT_ELEMENTS(pImage->aRegions)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_MANY_IMAGE_REGIONS, + "Too many regions for %s.", pImage->pszName); + pImage->aRegions[iRegion].uRva = (uint32_t)uRva; + pImage->aRegions[iRegion].cb = (uint32_t)pMemInfo->RegionSize; + pImage->aRegions[iRegion].fProt = pMemInfo->Protect; + pImage->cbImage = pImage->aRegions[iRegion].uRva + pImage->aRegions[iRegion].cb; + pImage->cRegions++; + + return VINF_SUCCESS; +} + + +/** + * Scans the virtual memory of the process. + * + * This collects the locations of DLLs and the EXE, and verifies that executable + * memory is only associated with these. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. Details + * about images are added to this. + * @param hProcess The process to verify. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpScanVirtualMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess, PRTERRINFO pErrInfo) +{ + uint32_t cXpExceptions = 0; + uintptr_t cbAdvance = 0; + uintptr_t uPtrWhere = 0; + for (uint32_t i = 0; i < 1024; i++) + { + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 }; + NTSTATUS rcNt = g_pfnNtQueryVirtualMemory(hProcess, + (void const *)uPtrWhere, + MemoryBasicInformation, + &MemInfo, + sizeof(MemInfo), + &cbActual); + if (!NT_SUCCESS(rcNt)) + { + if (rcNt == STATUS_INVALID_PARAMETER) + return VINF_SUCCESS; + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_ERROR, + "NtQueryVirtualMemory failed for %p: %#x", uPtrWhere, rcNt); + } + + /* + * Record images. + */ + if ( MemInfo.Type == SEC_IMAGE + || MemInfo.Type == SEC_PROTECTED_IMAGE + || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE)) + { + uint32_t iImg = pThis->cImages; + rcNt = g_pfnNtQueryVirtualMemory(hProcess, + (void const *)uPtrWhere, + MemorySectionName, + &pThis->aImages[iImg].Name, + sizeof(pThis->aImages[iImg].Name) - sizeof(WCHAR), + &cbActual); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_VIRTUAL_MEMORY_NM_ERROR, + "NtQueryVirtualMemory/MemorySectionName failed for %p: %#x", uPtrWhere, rcNt); + pThis->aImages[iImg].Name.UniStr.Buffer[pThis->aImages[iImg].Name.UniStr.Length / sizeof(WCHAR)] = '\0'; + + /* New or existing image? */ + bool fNew = true; + uint32_t iSearch = iImg; + while (iSearch-- > 0) + if (supHardNtVpAreUniStringsEqual(&pThis->aImages[iSearch].Name.UniStr, &pThis->aImages[iImg].Name.UniStr)) + { + int rc = supHardNtVpAddRegion(&pThis->aImages[iSearch], &MemInfo, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + fNew = false; + break; + } + else if (pThis->aImages[iSearch].uImageBase == (uintptr_t)MemInfo.AllocationBase) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_MAPPING_NAME_CHANGED, + "Unexpected base address match"); + + if (fNew) + { + int rc = supHardNtVpNewImage(&pThis->aImages[iImg], &MemInfo, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + pThis->cImages++; + if (pThis->cImages >= RT_ELEMENTS(pThis->aImages)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_MANY_DLLS_LOADED, + "Internal error: aImages is full.\n"); + } + } + /* + * XP, W2K3: Ignore the CSRSS read-only region as best we can. + */ + else if ( (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) + == PAGE_EXECUTE_READ + && cXpExceptions == 0 + && (uintptr_t)MemInfo.BaseAddress >= UINT32_C(0x78000000) + /* && MemInfo.BaseAddress == pPeb->ReadOnlySharedMemoryBase */ + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 0) ) + cXpExceptions++; + /* + * Executable memory? + */ + else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_FOUND_EXEC_MEMORY, + "Found executable memory at %p (%p LB %#zx): type=%#x prot=%#x state=%#x aprot=%#x abase=%p", + uPtrWhere, + MemInfo.BaseAddress, + MemInfo.RegionSize, + MemInfo.Type, + MemInfo.Protect, + MemInfo.State, + MemInfo.AllocationBase, + MemInfo.AllocationProtect); + + /* + * Advance. + */ + cbAdvance = MemInfo.RegionSize; + if (uPtrWhere + cbAdvance <= uPtrWhere) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_EMPTY_REGION_TOO_LARGE, + "Empty region at %p.", uPtrWhere); + uPtrWhere += MemInfo.RegionSize; + } + + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_TOO_MANY_MEMORY_REGIONS, + "Too many virtual memory regions.\n"); +} + + +/** + * Check the integrity of the executable of the process. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. Details + * about images are added to this. + * @param hProcess The process to verify. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpCheckExe(PSUPHNTVPSTATE pThis, HANDLE hProcess, PRTERRINFO pErrInfo) +{ + /* + * Make sure there is exactly one executable image. + */ + unsigned cExecs = 0; + unsigned iExe = ~0U; + unsigned i = pThis->cImages; + while (i-- > 0) + { + if (!pThis->aImages[i].fDll) + { + cExecs++; + iExe = i; + } + } + if (cExecs == 0) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NO_FOUND_NO_EXE_MAPPING, + "No executable mapping found in the virtual address space."); + if (cExecs != 1) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_FOUND_MORE_THAN_ONE_EXE_MAPPING, + "Found more than one executable mapping in the virtual address space."); + PSUPHNTVPIMAGE pImage = &pThis->aImages[iExe]; + + /* + * Check that it matches the executable image of the process. + */ + int rc; + ULONG cbUniStr = sizeof(UNICODE_STRING) + RTPATH_MAX * sizeof(RTUTF16); + PUNICODE_STRING pUniStr = (PUNICODE_STRING)suplibHardenedAllocZ(cbUniStr); + if (!pUniStr) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NO_MEMORY, + "Error allocating %zu bytes for process name.", cbUniStr); + ULONG cbIgn = 0; + NTSTATUS rcNt = NtQueryInformationProcess(hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn); + if (NT_SUCCESS(rcNt)) + { + if (supHardNtVpAreUniStringsEqual(pUniStr, &pImage->Name.UniStr)) + rc = VINF_SUCCESS; + else + { + pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0'; + rc = supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_EXE_VS_PROC_NAME_MISMATCH, + "Process image name does not match the exectuable we found: %ls vs %ls.", + pUniStr->Buffer, pImage->Name.UniStr.Buffer); + } + } + else + rc = supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_NM_ERROR, + "NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt); + suplibHardenedFree(pUniStr); + if (RT_FAILURE(rc)) + return rc; + + /* + * Validate the signing of the executable image. + * This will load the fDllCharecteristics and fImageCharecteristics members we use below. + */ + rc = supHardNtVpVerifyImage(pThis, pImage, hProcess, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check linking requirements. + */ + SECTION_IMAGE_INFORMATION ImageInfo; + rcNt = NtQueryInformationProcess(hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_IMG_INFO_ERROR, + "NtQueryInformationProcess/ProcessImageInformation failed: %#x", rcNt); + if ( !(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_EXE_MISSING_FORCE_INTEGRITY, + "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY to be set.", + ImageInfo.DllCharacteristics); + if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_EXE_MISSING_DYNAMIC_BASE, + "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE to be set.", + ImageInfo.DllCharacteristics); + if (!(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_EXE_MISSING_NX_COMPAT, + "EXE DllCharacteristics=%#x, expected IMAGE_DLLCHARACTERISTICS_NX_COMPAT to be set.", + ImageInfo.DllCharacteristics); + + if (pImage->fDllCharecteristics != ImageInfo.DllCharacteristics) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH, + "EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.", + ImageInfo.DllCharacteristics, pImage->fDllCharecteristics); + + if (pImage->fImageCharecteristics != ImageInfo.ImageCharacteristics) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH, + "EXE Info.ImageCharacteristics=%#x fImageCharecteristics=%#x.", + ImageInfo.ImageCharacteristics, pImage->fImageCharecteristics); + + return VINF_SUCCESS; +} + + +/** + * Check the integrity of the DLLs found in the process. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. Details + * about images are added to this. + * @param hProcess The process to verify. + * @param pErrInfo Pointer to error info structure. Optional. + */ +static int supHardNtVpCheckDlls(PSUPHNTVPSTATE pThis, HANDLE hProcess, PRTERRINFO pErrInfo) +{ + /* + * Check for duplicate entries. + */ + uint32_t i = pThis->cImages; + while (i-- > 1) + { + const char *pszName = pThis->aImages[i].pszName; + uint32_t j = i; + while (j-- > 0) + if (pThis->aImages[j].pszName == pszName) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_DUPLICATE_DLL_MAPPING, + "Duplicate image entries for %s: %ls and %ls", + pszName, pThis->aImages[i].Name.UniStr.Buffer, pThis->aImages[j].Name.UniStr.Buffer); + } + + /* + * Check that both ntdll and kernel32 are present. + * ASSUMES the entries in g_apszSupNtVpAllowedDlls are all lower case. + */ + uint32_t iNtDll = UINT32_MAX; + uint32_t iKernel32 = UINT32_MAX; + uint32_t iApiSetSchema = UINT32_MAX; + i = pThis->cImages; + while (i-- > 0) + if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "ntdll.dll") == 0) + iNtDll = i; + else if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "kernel32.dll") == 0) + iKernel32 = i; + else if (suplibHardenedStrCmp(pThis->aImages[i].pszName, "apisetschema.dll") == 0) + iApiSetSchema = i; + if (iNtDll == UINT32_MAX) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NO_NTDLL_MAPPING, + "The process has no NTDLL.DLL."); + if (iKernel32 == UINT32_MAX) + return supHardNtVpSetInfo(pErrInfo, VERR_SUP_VP_NO_KERNEL32_MAPPING, + "The process has no KERNEL32.DLL."); + + + /* + * Verify that the DLLs are correctly signed (by MS). + */ + i = pThis->cImages; + while (i-- > 0) + { + pThis->aImages[i].fNtCreateSectionPatch = i == iNtDll; + pThis->aImages[i].fApiSetSchemaOnlySection1 = i == iApiSetSchema && pThis->aImages[i].cRegions == 1; + + int rc = supHardNtVpVerifyImage(pThis, &pThis->aImages[i], hProcess, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * Verifies the given process. + * + * The following requirements are checked: + * - The process only has one thread, the calling thread. + * - The process has no debugger attached. + * - The executable image of the process is verified to be signed with + * certificate known to this code at build time. + * - The executable image is one of a predefined set. + * - The process has only a very limited set of system DLLs loaded. + * - The system DLLs signatures check out fine. + * - The only executable memory in the process belongs to the system DLLs and + * the executable image. + * + * @returns VBox status code. + * @param hProcess The process to verify. + * @param hThread A thread in the process (the caller). + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo) +{ + int rc = supHardNtVpThread(hProcess, hThread, pErrInfo); +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + if (RT_SUCCESS(rc)) + rc = supHardNtVpDebugger(hProcess, pErrInfo); +#endif + if (RT_SUCCESS(rc)) + { + PSUPHNTVPSTATE pThis = supHardNtVpCreateState(pErrInfo); + if (pThis) + { + rc = supHardNtVpScanVirtualMemory(pThis, hProcess, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpCheckExe(pThis, hProcess, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpCheckDlls(pThis, hProcess, pErrInfo); + + suplibHardenedFree(pThis); + } + else + rc = VERR_SUP_VP_NO_MEMORY_STATE; + } + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp index 336724548c9..3881efa76aa 100644 --- a/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp +++ b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp @@ -30,6 +30,7 @@ #define LOG_GROUP LOG_GROUP_SUP #ifdef IN_SUP_HARDENED_R3 # undef DEBUG /* Warning: disables RT_STRICT */ +# undef LOG_DISABLED # define LOG_DISABLED /** @todo RTLOGREL_DISABLED */ # include <iprt/log.h> @@ -38,11 +39,7 @@ #endif #define USE_NT_DEVICE_IO_CONTROL_FILE -#ifdef USE_NT_DEVICE_IO_CONTROL_FILE -# include <iprt/nt/nt-and-windows.h> -#else -# include <Windows.h> -#endif +#include <iprt/nt/nt-and-windows.h> #include <VBox/sup.h> #include <VBox/types.h> @@ -54,6 +51,9 @@ #include <iprt/string.h> #include "../SUPLibInternal.h" #include "../SUPDrvIOC.h" +#ifdef VBOX_WITH_HARDENING +# include "win/SUPHardenedVerify-win.h" +#endif /******************************************************************************* @@ -74,11 +74,13 @@ /******************************************************************************* * Internal Functions * *******************************************************************************/ +#ifndef IN_SUP_HARDENED_R3 static int suplibOsCreateService(void); //unused: static int suplibOsUpdateService(void); static int suplibOsDeleteService(void); static int suplibOsStartService(void); static int suplibOsStopService(void); +#endif #ifdef USE_NT_DEVICE_IO_CONTROL_FILE static int suplibConvertNtStatus(NTSTATUS rcNt); #else @@ -86,64 +88,104 @@ static int suplibConvertWin32Err(int); #endif - - int suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, bool fUnrestricted) { /* - * Nothing to do if pre-inited. + * Almost nothing to do if pre-inited. */ if (fPreInited) + { +#if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_HARDENED_R3) +# ifdef IN_SUP_R3_STATIC + return VERR_NOT_SUPPORTED; +# else + supR3HardenedWinInitVersion(); + return supHardenedWinInitImageVerifier(NULL); +# endif +#else return VINF_SUCCESS; +#endif + } /* * Try open the device. */ - HANDLE hDevice = CreateFile(fUnrestricted ? DEVICE_NAME_SYS : DEVICE_NAME_USR, - GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - if (hDevice == INVALID_HANDLE_VALUE) - { #ifndef IN_SUP_HARDENED_R3 - /* - * Try start the service and retry opening it. - */ - suplibOsStartService(); - - hDevice = CreateFile(fUnrestricted ? DEVICE_NAME_SYS : DEVICE_NAME_USR, - GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, - NULL); - if (hDevice == INVALID_HANDLE_VALUE) -#endif /* !IN_SUP_HARDENED_R3 */ + uint32_t cTry = 0; +#endif + HANDLE hDevice; + for (;;) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + static const WCHAR s_wszName[] = L"\\Device\\VBoxDrvU"; + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)s_wszName; + NtName.Length = sizeof(s_wszName) - sizeof(WCHAR) * (fUnrestricted ? 2 : 1); + NtName.MaximumLength = NtName.Length; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + hDevice = RTNT_INVALID_HANDLE_VALUE; + + NTSTATUS rcNt = NtCreateFile(&hDevice, + GENERIC_READ | GENERIC_WRITE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (!NT_SUCCESS(rcNt)) { - int rc = GetLastError(); - switch (rc) +#ifndef IN_SUP_HARDENED_R3 + /* + * Failed to open, try starting the service and reopen the device + * exactly once. + */ + if (cTry == 0 && !NT_SUCCESS(rcNt)) + { + cTry++; + suplibOsStartService(); + continue; + } +#endif + switch (rcNt) { /** @todo someone must test what is actually returned. */ - case ERROR_DEV_NOT_EXIST: - case ERROR_DEVICE_NOT_CONNECTED: - case ERROR_BAD_DEVICE: - case ERROR_DEVICE_REMOVED: - case ERROR_DEVICE_NOT_AVAILABLE: + case STATUS_DEVICE_DOES_NOT_EXIST: + case STATUS_DEVICE_NOT_CONNECTED: + //case ERROR_BAD_DEVICE: + case STATUS_DEVICE_REMOVED: + //case ERROR_DEVICE_NOT_AVAILABLE: return VERR_VM_DRIVER_LOAD_ERROR; - case ERROR_PATH_NOT_FOUND: - case ERROR_FILE_NOT_FOUND: + case STATUS_OBJECT_PATH_NOT_FOUND: + case STATUS_NO_SUCH_DEVICE: + case STATUS_NO_SUCH_FILE: + case STATUS_OBJECT_NAME_NOT_FOUND: return VERR_VM_DRIVER_NOT_INSTALLED; - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: + case STATUS_ACCESS_DENIED: + case STATUS_SHARING_VIOLATION: return VERR_VM_DRIVER_NOT_ACCESSIBLE; + case STATUS_UNSUCCESSFUL: + return VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0; + case STATUS_TRUST_FAILURE: + return VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1; + case STATUS_TOO_LATE: + return VERR_SUPDRV_HARDENING_EVIL_HANDLE; default: + + return rcNt; return VERR_VM_DRIVER_OPEN_ERROR; } - - return -1 /** @todo define proper error codes for suplibOsInit failure. */; } + break; } /* @@ -154,7 +196,6 @@ int suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, bool fUnrestricted) return VINF_SUCCESS; } - #ifndef IN_SUP_HARDENED_R3 int suplibOsInstall(void) @@ -439,7 +480,9 @@ static int suplibOsStartService(void) */ fRc = StartService(hService, 0, NULL); DWORD LastError = GetLastError(); NOREF(LastError); +#ifndef DEBUG_bird AssertMsg(fRc, ("StartService failed with LastError=%Rwa\n", LastError)); +#endif } /* @@ -651,6 +694,10 @@ static int suplibConvertNtStatus(NTSTATUS rcNt) case STATUS_REVISION_MISMATCH: return VERR_VERSION_MISMATCH; } + /* See VBoxDrvNtErr2NtStatus. */ + if (((uint32_t)rcNt & 0xffff0000) == UINT32_C(0xe9860000)) /** @todo defines for these? */ + return (int)((uint32_t)rcNt | UINT32_C(0xffff0000)); + /* Fall back on IPRT for the rest. */ return RTErrConvertFromNtStatus(rcNt); } diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp new file mode 100644 index 00000000000..2700b778c14 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp @@ -0,0 +1,1650 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <iprt/nt/nt-and-windows.h> +#include <AccCtrl.h> +#include <AclApi.h> +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif +#ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x200 +# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +/** The first argument of a respawed stub argument. + * This just needs to be unique enough to avoid most confusion with real + * executable names, there are other checks in place to make sure we've respanwed. */ +#define SUPR3_RESPAWN_ARG0 "81954AF5-4D2F-31EB-A142-B7AF187A1C41-suplib-2ndchild" + +/** Unconditional assertion. */ +#define SUPR3HARDENED_ASSERT(a_Expr) \ + do { \ + if (!(a_Expr)) \ + supR3HardenedFatal("%s: %s", __FUNCTION__, #a_Expr); \ + } while (0) + +/** Unconditional assertion of NT_SUCCESS. */ +#define SUPR3HARDENED_ASSERT_NT_SUCCESS(a_Expr) \ + do { \ + NTSTATUS rcNtAssert = (a_Expr); \ + if (!NT_SUCCESS(rcNtAssert)) \ + supR3HardenedFatal("%s: %s -> %#x", __FUNCTION__, #a_Expr, rcNtAssert); \ + } while (0) + +/** Unconditional assertion of a WIN32 API returning non-FALSE. */ +#define SUPR3HARDENED_ASSERT_WIN32_SUCCESS(a_Expr) \ + do { \ + BOOL fRcAssert = (a_Expr); \ + if (fRcAssert == FALSE) \ + supR3HardenedFatal("%s: %s -> %#x", __FUNCTION__, #a_Expr, GetLastError()); \ + } while (0) + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * Security descriptor cleanup structure. + */ +typedef struct MYSECURITYCLEANUP +{ + union + { + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; + } Everyone, Owner, User, Login; + union + { + ACL AclHdr; + uint8_t abPadding[1024]; + } Acl; + PSECURITY_DESCRIPTOR pSecDesc; +} MYSECURITYCLEANUP; +/** Pointer to security cleanup structure. */ +typedef MYSECURITYCLEANUP *PMYSECURITYCLEANUP; + + +/** + * Image verifier cache entry. + */ +typedef struct VERIFIERCACHEENTRY +{ + /** Pointer to the next entry with the same hash value. */ + struct VERIFIERCACHEENTRY * volatile pNext; + /** The file handle. */ + HANDLE hFile; + /** If fIndexNumber is set, this is an file system internal file identifier. */ + LARGE_INTEGER IndexNumber; + /** The path hash value. */ + uint32_t uHash; + /** The verification result. */ + int rc; + /** Whether IndexNumber is valid */ + bool fIndexNumberValid; + /** cwcPath * sizeof(RTUTF16). */ + uint16_t cbPath; + /** The full path of this entry (variable size). */ + RTUTF16 wszPath[1]; +} VERIFIERCACHEENTRY; +/** Pointer to an image verifier path entry. */ +typedef VERIFIERCACHEENTRY *PVERIFIERCACHEENTRY; + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +/** @name Global variables initialized by suplibHardenedWindowsMain. + * @{ */ +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ +uint32_t g_uNtVerCombined = 0; +/** Count calls to the special main function for linking santity checks. */ +static uint32_t volatile g_cSuplibHardenedWindowsMainCalls; +/** The UTF-16 windows path to the executable. */ +RTUTF16 g_wszSupLibHardenedExePath[1024]; +/** The NT path of the executable. */ +SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath; +/** The offset into g_SupLibHardenedExeNtPath of the executable name (WCHAR, + * not byte). This also gives the length of the exectuable directory path, + * including a trailing slash. */ +uint32_t g_offSupLibHardenedExeNtName; +/** @} */ + +/** @name Hook related variables. + * @{ */ +/** The jump back address of the patched NtCreateSection. */ +extern "C" PFNRT g_pfnNtCreateSectionJmpBack = NULL; +/** Pointer to the bit of assembly code that will perform the original + * NtCreateSection operation. */ +static NTSTATUS (NTAPI * g_pfnNtCreateSectionReal)(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, + PLARGE_INTEGER, ULONG, ULONG, HANDLE); +/** The hash table of verifier cache . */ +static VERIFIERCACHEENTRY * volatile g_apVerifierCache[128]; +/** @ */ + +/** Static error info structure used during init. */ +static RTERRINFOSTATIC g_ErrInfoStatic; + + +/******************************************************************************* +* Internal Functions * +*******************************************************************************/ +#ifdef RT_ARCH_AMD64 +# define SYSCALL(a_Num) DECLASM(void) RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num)(void) +# include "NtCreateSection-template-amd64-syscall-type-1.h" +# undef SYSCALL +#endif +#ifdef RT_ARCH_X86 +# define SYSCALL(a_Num) DECLASM(void) RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num)(void) +# include "NtCreateSection-template-x86-syscall-type-1.h" +# undef SYSCALL +#endif + + + +/** + * Simple wide char search routine. + * + * @returns Pointer to the first location of @a wcNeedle in @a pwszHaystack. + * NULL if not found. + * @param pwszHaystack Pointer to the string that should be searched. + * @param wcNeedle The character to search for. + */ +static PRTUTF16 suplibHardenedWStrChr(PCRTUTF16 pwszHaystack, RTUTF16 wcNeedle) +{ + for (;;) + { + RTUTF16 wcCur = *pwszHaystack; + if (wcCur == wcNeedle) + return (PRTUTF16)pwszHaystack; + if (wcCur == '\0') + return NULL; + pwszHaystack++; + } +} + + +/** + * Simple wide char string length routine. + * + * @returns The number of characters in the given string. (Excludes the + * terminator.) + * @param pwsz The string. + */ +static size_t suplibHardenedWStrLen(PCRTUTF16 pwsz) +{ + PCRTUTF16 pwszCur = pwsz; + while (*pwszCur != '\0') + pwszCur++; + return pwszCur - pwsz; +} + + +/** + * Allocate zero filled memory on the heap. + * + * @returns Pointer to the memory. Will never return NULL, triggers a fatal + * error instead. + * @param cb The number of bytes to allocate. + */ +DECLHIDDEN(void *) suplibHardenedAllocZ(size_t cb) +{ + void *pv = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb); + if (!pv) + supR3HardenedFatal("HeapAlloc failed to allocate %zu bytes.\n", cb); + return pv; +} + + +/** + * Reallocates memory on the heap. + * + * @returns Pointer to the resized memory block. Will never return NULL, + * triggers a fatal error instead. + * @param pvOld The old memory block. + * @param cbNew The new block size. + */ +DECLHIDDEN(void *) suplibHardenedReAlloc(void *pvOld, size_t cbNew) +{ + if (!pvOld) + return suplibHardenedAllocZ(cbNew); + void *pv = HeapReAlloc(GetProcessHeap(), 0 /*dwFlags*/, pvOld, cbNew); + if (!pv) + supR3HardenedFatal("HeapReAlloc failed to allocate %zu bytes.\n", cbNew); + return pv; +} + + +/** + * Frees memory allocated by suplibHardenedAlloc, suplibHardenedAllocZ or + * suplibHardenedReAlloc. + * + * @param pv Pointer to the memeory to be freed. + */ +DECLHIDDEN(void) suplibHardenedFree(void *pv) +{ + if (pv) + HeapFree(GetProcessHeap(), 0 /* dwFlags*/, pv); +} + + +/** + * Wrapper around LoadLibraryEx that deals with the UTF-8 to UTF-16 conversion + * and supplies the right flags. + * + * @returns Module handle on success, NULL on failure. + * @param pszName The full path to the DLL. + * @param fSystem32Only Whether to only look for imports in the system32 + * directory. If set to false, the application + * directory is also searched. + */ +DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only) +{ + WCHAR wszPath[RTPATH_MAX]; + PRTUTF16 pwszPath = wszPath; + int rc = RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszPath, RT_ELEMENTS(wszPath), NULL); + if (RT_SUCCESS(rc)) + { + while (*pwszPath) + { + if (*pwszPath == '/') + *pwszPath = '\\'; + pwszPath++; + } + + DWORD fFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + { + fFlags |= LOAD_LIBRARY_SEARCH_SYSTEM32; + if (!fSystem32Only) + fFlags |= LOAD_LIBRARY_SEARCH_APPLICATION_DIR; + } + + void *pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, fFlags); + + /* Vista, W7, W2K8R might not work without KB2533623, so retry with no flags. */ + if ( !pvRet + && fFlags + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && GetLastError() == ERROR_INVALID_PARAMETER) + pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0); + + return pvRet; + } + supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc); + return NULL; +} + + +/** + * Gets the internal index number of the file. + * + * @returns True if we got an index number, false if not. + * @param hFile The file in question. + * @param pIndexNumber where to return the index number. + */ +static bool supR3HardenedWinVerifyCacheGetIndexNumber(HANDLE hFile, PLARGE_INTEGER pIndexNumber) +{ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pIndexNumber, sizeof(*pIndexNumber), FileInternalInformation); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; +#ifdef DEBUG_bird + if (!NT_SUCCESS(rcNt)) + __debugbreak(); +#endif + return NT_SUCCESS(rcNt) && pIndexNumber->QuadPart != 0; +} + + +/** + * Calculates the hash value for the given UTF-16 string. + * + * @returns Hash value. + * @param pUniStr String to hash. + */ +static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr) +{ + uint32_t uHash = 0; + unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR); + PRTUTF16 pwc = pUniStr->Buffer; + + while (cwcLeft-- > 0) + { + RTUTF16 wc = *pwc++; + uHash = wc + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * Inserts the given verifier result into the cache. + * + * @param pUniStr The full path of the image. + * @param hFile The file handle - must either be entered into + * the cache or closed. + * @param rc The verifier result. + * @param fCacheable Whether this is a cacheable result. Passed in + * here instead of being handled by the caller to + * save code duplication. + */ +static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc, bool fCacheable) +{ + /* + * Don't cache anything until we've got the WinVerifyTrust API up and running. + */ + if ( g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_VERIFY_TRUST_READY + && fCacheable) + { + /* + * Allocate and initalize a new entry. + */ + PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(VERIFIERCACHEENTRY) + pUniStr->Length); + if (pEntry) + { + pEntry->pNext = NULL; + pEntry->hFile = hFile; + pEntry->rc = rc; + pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr); + pEntry->cbPath = pUniStr->Length; + memcpy(pEntry->wszPath, pUniStr->Buffer, pUniStr->Length); + pEntry->wszPath[pUniStr->Length / sizeof(WCHAR)] = '\0'; + pEntry->fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &pEntry->IndexNumber); + + /* + * Try insert it, careful with concurrent code as well as potential duplicates. + */ + uint32_t iHashTab = pEntry->uHash % RT_ELEMENTS(g_apVerifierCache); + VERIFIERCACHEENTRY * volatile *ppEntry = &g_apVerifierCache[iHashTab]; + for (;;) + { + if (ASMAtomicCmpXchgPtr(ppEntry, pEntry, NULL)) + return; + PVERIFIERCACHEENTRY pOther = *ppEntry; + if (!pOther) + continue; + if ( pOther->uHash == pEntry->uHash + && pOther->cbPath == pEntry->cbPath + && memcmp(pOther->wszPath, pEntry->wszPath, pEntry->cbPath) == 0) + break; + ppEntry = &pOther->pNext; + } + + /* Duplicate entry. */ +#ifdef DEBUG_bird + __debugbreak(); +#endif + HeapFree(GetProcessHeap(), 0 /* dwFlags*/, pEntry); + } + } + NtClose(hFile); +} + + +/** + * Looks up an entry in the verifier hash table. + * + * @return Pointer to the entry on if found, NULL if not. + * @param pUniStr The full path of the image. + * @param hFile The file handle. + */ +static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookup(PCUNICODE_STRING pUniStr, HANDLE hFile) +{ + PRTUTF16 const pwszPath = pUniStr->Buffer; + uint16_t const cbPath = pUniStr->Length; + uint32_t uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr); + uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache); + PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab]; + while (pCur) + { + if ( pCur->uHash == uHash + && pCur->cbPath == cbPath + && memcmp(pCur->wszPath, pwszPath, cbPath) == 0) + { + + if (!pCur->fIndexNumberValid) + return pCur; + LARGE_INTEGER IndexNumber; + bool fIndexNumberValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &IndexNumber); + if ( fIndexNumberValid + && IndexNumber.QuadPart == pCur->IndexNumber.QuadPart) + return pCur; +#ifdef DEBUG_bird + __debugbreak(); +#endif + } + pCur = pCur->pNext; + } + return NULL; +} + + +/** + * Hook that monitors NtCreateSection calls. + * + * @returns NT status code. + * @param phSection Where to return the section handle. + * @param fAccess The desired access. + * @param pObjAttribs The object attributes (optional). + * @param pcbSection The section size (optional). + * @param fProtect The max section protection. + * @param fAttribs The section attributes. + * @param hFile The file to create a section from (optional). + */ +static NTSTATUS NTAPI +supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs, + PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile) +{ + if ( hFile != NULL + && hFile != INVALID_HANDLE_VALUE) + { + bool const fImage = RT_BOOL(fAttribs & (SEC_IMAGE | SEC_PROTECTED_IMAGE)); + bool const fExecMap = RT_BOOL(fAccess & SECTION_MAP_EXECUTE); + bool const fExecProt = RT_BOOL(fProtect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_WRITECOPY + | PAGE_EXECUTE_READWRITE)); + if (fImage || fExecMap || fExecProt) + { + /* + * Query the name of the file, making sure to zero terminator the + * string. (2nd half of buffer is used for error info, see below.) + */ + union + { + UNICODE_STRING UniStr; + uint8_t abBuffer[sizeof(UNICODE_STRING) + 2048 * sizeof(WCHAR)]; + } uBuf; + RT_ZERO(uBuf); + ULONG cbNameBuf; + NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR) - 128, &cbNameBuf); + if (!NT_SUCCESS(rcNt)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: NtQueryObject -> %#x (fImage=%d fExecMap=%d fExecProt=%d)\n", + fImage, fExecMap, fExecProt); + return rcNt; + } + + /* + * Check the cache. + */ + PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile); + if (pCacheHit) + { + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: cache hit (%Rrc) on %ls\n", pCacheHit->rc, pCacheHit->wszPath)); + if (RT_SUCCESS(pCacheHit->rc)) + return g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile); + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: cached rc=%Rrc fImage=%d fExecMap=%d fExecProt=%d %ls\n", + pCacheHit->rc, fImage, fExecMap, fExecProt, uBuf.UniStr.Buffer); + return STATUS_TRUST_FAILURE; + } + + /* + * On XP the loader might hand us handles with just FILE_EXECUTE and + * SYNCRHONIZE, the means reading will fail later on. So, we might + * have to reopen the file here in order to validate it - annoying. + */ + HANDLE hMyFile = NULL; + rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(), + &hMyFile, + FILE_READ_DATA | SYNCHRONIZE, + 0 /* Handle attributes*/, 0 /* Options */); + if (!NT_SUCCESS(rcNt)) + { + if (rcNt == STATUS_ACCESS_DENIED) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &uBuf.UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hMyFile, + FILE_READ_DATA | SYNCHRONIZE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (!NT_SUCCESS(rcNt)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n", + rcNt, hFile, uBuf.UniStr.Buffer); + return rcNt; + } + } + else + { +#ifdef DEBUG + + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_NtCreateSection: NtDuplicateObject(,%#x,) failed: %#x\n", hFile, rcNt); +#endif + hMyFile = hFile; + } + } + + /* + * Special Kludge for Windows XP and W2K3 and their stupid attempts + * at mapping a hidden XML file called c:\Windows\WindowsShell.Manifest + * with executable access. The image bit isn't set, fortunately. + */ + if ( !fImage + && uBuf.UniStr.Length > g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR) + && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) == 0) + { + PRTUTF16 pwszName = &uBuf.UniStr.Buffer[(g_System32NtPath.UniStr.Length - sizeof(L"System32") + sizeof(WCHAR)) / sizeof(WCHAR)]; + if (RTUtf16ICmpAscii(pwszName, "WindowsShell.Manifest") == 0) + { + /* + * Drop all executable access to the mapping and let it continue. + */ + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: Applying the drop-exec-kludge for '%ls'\n", uBuf.UniStr.Buffer)); + if (fAccess & SECTION_MAP_EXECUTE) + fAccess = (fAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ; + if (fProtect & PAGE_EXECUTE) + fProtect = (fProtect & ~PAGE_EXECUTE) | PAGE_READONLY; + fProtect = (fProtect & ~UINT32_C(0xf0)) | ((fProtect & UINT32_C(0xe0)) >> 4); + return g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile); + } + } + + /* + * Check the path. We don't allow DLLs to be loaded from just anywhere: + * 1. System32 - normal code or cat signing. + * 2. WinSxS - normal code or cat signing. + * 3. VirtualBox - kernel code signing and integrity checks. + */ + bool fSystem32 = false; + Assert(g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] == '\\'); + uint32_t fFlags = 0; + if ( uBuf.UniStr.Length > g_System32NtPath.UniStr.Length + && memcmp(uBuf.UniStr.Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0 + && uBuf.UniStr.Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\') + { + fSystem32 = true; + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION; + } + else if ( uBuf.UniStr.Length > g_WinSxSNtPath.UniStr.Length + && memcmp(uBuf.UniStr.Buffer, g_WinSxSNtPath.UniStr.Buffer, g_WinSxSNtPath.UniStr.Length) == 0 + && uBuf.UniStr.Buffer[g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR)] == '\\') + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION; + else if ( uBuf.UniStr.Length > g_offSupLibHardenedExeNtName + && memcmp(uBuf.UniStr.Buffer, g_SupLibHardenedExeNtPath.UniStr.Buffer, + g_offSupLibHardenedExeNtName * sizeof(WCHAR)) == 0) + fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT; + else + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: Not a trusted location: '%ls' (fImage=%d fExecMap=%d fExecProt=%d)\n", + uBuf.UniStr.Buffer, fImage, fExecMap, fExecProt); + if (hMyFile != hFile) + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + + /* + * Do the verification. For better error message we borrow what's + * left of the path buffer for an RTERRINFO buffer. + */ + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf); + + bool fCacheable = true; + int rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, &fCacheable, &ErrInfo); + if (RT_FAILURE(rc)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: rc=%Rrc fImage=%d fExecMap=%d fExecProt=%d %ls: %s\n", + rc, fImage, fExecMap, fExecProt, uBuf.UniStr.Buffer, ErrInfo.pszMsg); + if (hMyFile != hFile) + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + if (hMyFile != hFile) + supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fCacheable); + } + } + + /* + * Call checked out OK, call the original. + */ + return g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile); +} + + +#ifdef RT_ARCH_AMD64 +/** + * Tries to allocate memory between @a uStart and @a uEnd. + * + * @returns Pointer to the memory on success. NULL on failure. + * @param uStart The start address. + * @param uEnd The end address. This is lower than @a uStart + * if @a iDirection is negative, and higher if + * positive. + * @param iDirection The search direction. + * @param cbAlloc The number of bytes to allocate. + */ +static void *supR3HardenedWinAllocHookMemory(uintptr_t uStart, uintptr_t uEnd, intptr_t iDirection, size_t cbAlloc) +{ + size_t const cbAllocGranularity = _64K; + size_t const uAllocGranularityMask = ~(cbAllocGranularity - 1); + HANDLE const hProc = GetCurrentProcess(); + + /* + * Make uEnd the last valid return address. + */ + if (iDirection > 0) + { + SUPR3HARDENED_ASSERT(uEnd > cbAlloc); + uEnd -= cbAlloc; + uEnd &= uAllocGranularityMask; + } + else + uEnd = RT_ALIGN_Z(uEnd, cbAllocGranularity); + + /* + * Search for free memory. + */ + uintptr_t uCur = uStart & uAllocGranularityMask; + for (;;) + { + /* + * Examine the memory at this address, if it's free, try make the allocation here. + */ + SIZE_T cbIgn; + MEMORY_BASIC_INFORMATION MemInfo; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryVirtualMemory(hProc, + (void *)uCur, + MemoryBasicInformation, + &MemInfo, + sizeof(MemInfo), + &cbIgn)); + if ( MemInfo.State == MEM_FREE + && MemInfo.RegionSize >= cbAlloc) + { + for (;;) + { + SUPR3HARDENED_ASSERT((uintptr_t)MemInfo.BaseAddress <= uCur); + void *pvMem = VirtualAllocEx(hProc, (void *)uCur, cbAlloc, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (pvMem) + { + if ( iDirection > 0 + ? (uintptr_t)pvMem >= uStart + && (uintptr_t)pvMem <= uEnd + : (uintptr_t)pvMem >= uEnd + && (uintptr_t)pvMem <= uStart) + return pvMem; + VirtualFreeEx(hProc, pvMem, cbAlloc, MEM_RELEASE); + } + + /* Advance within the free area and try again? */ + uintptr_t uNext = iDirection > 0 ? uCur + cbAllocGranularity : uCur - cbAllocGranularity; + uNext &= uAllocGranularityMask; + if ( iDirection > 0 + ? uNext <= uCur + || uNext > uEnd + || uNext - (uintptr_t)MemInfo.BaseAddress > MemInfo.RegionSize + || MemInfo.RegionSize - (uNext - (uintptr_t)MemInfo.BaseAddress) < cbAlloc + : uNext >= uCur + || uNext < uEnd + || uNext < (uintptr_t)MemInfo.BaseAddress) + break; + uCur = uNext; + } + } + + /* + * Advance to the next memory region. + */ + if (iDirection > 0) + { + uCur = (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize; + uCur = RT_ALIGN_Z(uCur, cbAllocGranularity); + if (uCur >= uEnd) + break; + } + else + { + uCur = (uintptr_t)(MemInfo.AllocationBase ? MemInfo.AllocationBase : MemInfo.BaseAddress); + if (uCur > uEnd) + uCur -= cbAlloc; + uCur &= uAllocGranularityMask; + if (uCur < uEnd) + break; + } + } + return NULL; +} +#endif + + +/** + * Install hooks for intercepting calls dealing with mapping shared libraries + * into the process. + * + * This allows us to prevent undesirable shared libraries from being loaded. + * + * @remarks We assume we're alone in this process, so no seralizing trickery is + * necessary when installing the patch. + */ +DECLHIDDEN(void) supR3HardenedWinInstallHooks(void) +{ +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Install a anti debugging hack before we continue. This prevents most + * notifications from ending up in the debugger. + */ + NTSTATUS rcNt = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY, + "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt); +#endif + + /* + * Locate the routine. + */ + HMODULE hmodNtDll = GetModuleHandleW(L"NTDLL"); + SUPR3HARDENED_ASSERT(hmodNtDll != NULL); + + FARPROC pfnNtCreateSection = GetProcAddress(hmodNtDll, "NtCreateSection"); + SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL); + SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection); + + uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection; + +#ifdef RT_ARCH_AMD64 + /* + * For 64-bit hosts we need some memory within a +/-2GB range of the + * actual function to be able to patch it. + */ + size_t cbMem = _4K; + void *pvMem = supR3HardenedWinAllocHookMemory((uintptr_t)pfnNtCreateSection, + (uintptr_t)pfnNtCreateSection - _2G + PAGE_SIZE, + -1, cbMem); + if (!pvMem) + { + pvMem = supR3HardenedWinAllocHookMemory((uintptr_t)pfnNtCreateSection, + (uintptr_t)pfnNtCreateSection + _2G - PAGE_SIZE, + 1, cbMem); + if (!pvMem) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY, + "Failed to allocate memory within the +/-2GB range from NTDLL.\n"); + } + uintptr_t *puJmpTab = (uintptr_t *)pvMem; + + /* + * Patch 64-bit hosts. + */ + PFNRT pfnCallReal = NULL; + uint8_t offJmpBack = UINT8_MAX; + + /* Pattern #1: XP64/W2K3-64 thru Windows 8.1 + 0:000> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 00000000`779f1750 4c8bd1 mov r10,rcx + 00000000`779f1753 b847000000 mov eax,47h + 00000000`779f1758 0f05 syscall + 00000000`779f175a c3 ret + 00000000`779f175b 0f1f440000 nop dword ptr [rax+rax] + The variant is the value loaded into eax: W2K3=??, Vista=47h?, W7=47h, W80=48h, W81=49h */ + if ( pbNtCreateSection[ 0] == 0x4c /* mov r10, rcx */ + && pbNtCreateSection[ 1] == 0x8b + && pbNtCreateSection[ 2] == 0xd1 + && pbNtCreateSection[ 3] == 0xb8 /* mov eax, 000000xxh */ + && pbNtCreateSection[ 5] == 0x00 + && pbNtCreateSection[ 6] == 0x00 + && pbNtCreateSection[ 7] == 0x00 + && pbNtCreateSection[ 8] == 0x0f /* syscall */ + && pbNtCreateSection[ 9] == 0x05 + && pbNtCreateSection[10] == 0xc3 /* ret */ + ) + { + offJmpBack = 8; /* the 3rd instruction (syscall). */ + switch (pbNtCreateSection[4]) + { +# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break; +# include "NtCreateSection-template-amd64-syscall-type-1.h" +# undef SYSCALL + } + } + + if (pfnCallReal) + { + g_pfnNtCreateSectionJmpBack = (PFNRT)(uintptr_t)(pbNtCreateSection + offJmpBack); + *(PFNRT *)&g_pfnNtCreateSectionReal = pfnCallReal; + *puJmpTab = (uintptr_t)supR3HardenedMonitor_NtCreateSection; + + DWORD dwOldProt; + SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(GetCurrentProcess(), pbNtCreateSection, 16, + PAGE_EXECUTE_READWRITE, &dwOldProt)); + pbNtCreateSection[0] = 0xff; + pbNtCreateSection[1] = 0x25; + *(uint32_t *)&pbNtCreateSection[2] = (uint32_t)((uintptr_t)puJmpTab - (uintptr_t)&pbNtCreateSection[2+4]); + + SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(GetCurrentProcess(), pbNtCreateSection, 16, + PAGE_EXECUTE_READ, &dwOldProt)); + return; + } + +#else + /* + * Patch 32-bit hosts. + */ + PFNRT pfnCallReal = NULL; + uint8_t offJmpBack = UINT8_MAX; + + /* Pattern #1: XP thru Windows 7 + kd> u ntdll!NtCreateSection + ntdll!NtCreateSection: + 7c90d160 b832000000 mov eax,32h + 7c90d165 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) + 7c90d16a ff12 call dword ptr [edx] + 7c90d16c c21c00 ret 1Ch + 7c90d16f 90 nop + The variable bit is the value loaded into eax: XP=32h, W2K3=34h, Vista=4bh, W7=54h + + Pattern #2: Windows 8.1 + 0:000:x86> u ntdll_6a0f0000!NtCreateSection + ntdll_6a0f0000!NtCreateSection: + 6a15eabc b854010000 mov eax,154h + 6a15eac1 e803000000 call ntdll_6a0f0000!NtCreateSection+0xd (6a15eac9) + 6a15eac6 c21c00 ret 1Ch + 6a15eac9 8bd4 mov edx,esp + 6a15eacb 0f34 sysenter + 6a15eacd c3 ret + The variable bit is the value loaded into eax: W81=154h + Note! One nice thing here is that we can share code pattern #1. */ + + if ( pbNtCreateSection[ 0] == 0xb8 /* mov eax, 000000xxh*/ + && pbNtCreateSection[ 2] <= 0x02 + && pbNtCreateSection[ 3] == 0x00 + && pbNtCreateSection[ 4] == 0x00 + ( ( pbNtCreateSection[ 5] == 0xba /* mov edx, offset SharedUserData!SystemCallStub */ + && pbNtCreateSection[ 6] == 0x00 + && pbNtCreateSection[ 7] == 0x03 + && pbNtCreateSection[ 8] == 0xfe + && pbNtCreateSection[ 9] == 0x7f + && pbNtCreateSection[10] == 0xff /* call [edx] */ + && pbNtCreateSection[11] == 0x12 + && pbNtCreateSection[12] == 0xc2 /* ret 1ch */ + && pbNtCreateSection[13] == 0x1c + && pbNtCreateSection[14] == 0x00) + + || ( pbNtCreateSection[ 5] == 0xe8 /* call [$+3] */ + && RT_ABS(*(int32_t *)&pbNtCreateSection[6]) < 0x10 + && pbNtCreateSection[10] == 0xc2 /* ret 1ch */ + && pbNtCreateSection[11] == 0x1c + && pbNtCreateSection[12] == 0x00 ) + ) + ) + { + offJmpBack = 5; /* the 2nd instruction. */ + switch (*(uint32_t const *)&pbNtCreateSection[1]) + { +# define SYSCALL(a_Num) case a_Num: pfnCallReal = RT_CONCAT(supR3HardenedJmpBack_NtCreateSection_,a_Num); break; +# include "NtCreateSection-template-x86-syscall-type-1.h" +# undef SYSCALL + } + } + + if (pfnCallReal) + { + g_pfnNtCreateSectionJmpBack = (PFNRT)(uintptr_t)(pbNtCreateSection + offJmpBack); + *(PFNRT *)&g_pfnNtCreateSectionReal = pfnCallReal; + + DWORD dwOldProt; + SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(GetCurrentProcess(), pbNtCreateSection, 16, + PAGE_EXECUTE_READWRITE, &dwOldProt)); + pbNtCreateSection[0] = 0xe9; + *(uint32_t *)&pbNtCreateSection[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection + - (uintptr_t)&pbNtCreateSection[1+4]; + + SUPR3HARDENED_ASSERT_WIN32_SUCCESS(VirtualProtectEx(GetCurrentProcess(), pbNtCreateSection, 16, + PAGE_EXECUTE_READ, &dwOldProt)); + return; + } +#endif + + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY, + "Failed to install NtCreateSection monitor: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n " +#ifdef RT_ARCH_X86 + "(It is also possible you are running 32-bit VirtualBox under 64-bit windows.)\n" +#endif + , + pbNtCreateSection[0], pbNtCreateSection[1], pbNtCreateSection[2], pbNtCreateSection[3], + pbNtCreateSection[4], pbNtCreateSection[5], pbNtCreateSection[6], pbNtCreateSection[7], + pbNtCreateSection[8], pbNtCreateSection[9], pbNtCreateSection[10], pbNtCreateSection[11], + pbNtCreateSection[12], pbNtCreateSection[13], pbNtCreateSection[14], pbNtCreateSection[15]); +} + + +/** + * Verifies the process integrity. + */ +DECLHIDDEN(void) supR3HardenedWinVerifyProcess(void) +{ + RTErrInfoInitStatic(&g_ErrInfoStatic); + int rc = supHardenedWinVerifyProcess(GetCurrentProcess(), GetCurrentThread(), &g_ErrInfoStatic.Core); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinVerifyProcess", kSupInitOp_Integrity, rc, + "Failed to verify process integrity: %s", g_ErrInfoStatic.szMsg); +} + + +/** + * Gets the SID of the user associated with the process. + * + * @returns @c true if we've got a login SID, @c false if not. + * @param pSidUser Where to return the user SID. + * @param cbSidUser The size of the user SID buffer. + * @param pSidLogin Where to return the login SID. + * @param cbSidLogin The size of the login SID buffer. + */ +static bool supR3HardenedGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin) +{ + HANDLE hToken; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)); + union + { + TOKEN_USER UserInfo; + TOKEN_GROUPS Groups; + uint8_t abPadding[4096]; + } uBuf; + ULONG cbRet = 0; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtQueryInformationToken(hToken, TokenUser, &uBuf, sizeof(uBuf), &cbRet)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidUser, pSidUser, uBuf.UserInfo.User.Sid)); + + bool fLoginSid = false; + NTSTATUS rcNt = NtQueryInformationToken(hToken, TokenLogonSid, &uBuf, sizeof(uBuf), &cbRet); + if (NT_SUCCESS(rcNt)) + { + for (DWORD i = 0; i < uBuf.Groups.GroupCount; i++) + if ((uBuf.Groups.Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) + { + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCopySid(cbSidLogin, pSidLogin, uBuf.Groups.Groups[i].Sid)); + fLoginSid = true; + break; + } + } + + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtClose(hToken)); + + return fLoginSid; +} + + +/** + * Build security attributes for the process or the primary thread (@a fProcess) + * + * Process DACLs can be bypassed using the SeDebugPrivilege (generally available + * to admins, i.e. normal windows users), or by taking ownership and/or + * modifying the DACL. However, it restricts + * + * @param pSecAttrs Where to return the security attributes. + * @param pCleanup Cleanup record. + * @param fProcess Set if it's for the process, clear if it's for + * the primary thread. + */ +static void supR3HardenedInitSecAttrs(PSECURITY_ATTRIBUTES pSecAttrs, PMYSECURITYCLEANUP pCleanup, bool fProcess) +{ + /* + * Safe return values. + */ + suplibHardenedMemSet(pCleanup, 0, sizeof(*pCleanup)); + + pSecAttrs->nLength = sizeof(*pSecAttrs); + pSecAttrs->bInheritHandle = FALSE; + pSecAttrs->lpSecurityDescriptor = NULL; + +/** @todo This isn't at all complete, just sketches... */ + + /* + * Create an ACL detailing the access of the above groups. + */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateAcl(&pCleanup->Acl.AclHdr, sizeof(pCleanup->Acl), ACL_REVISION)); + + ULONG fDeny = DELETE | WRITE_DAC | WRITE_OWNER | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL; + ULONG fAllow = SYNCHRONIZE | READ_CONTROL; + ULONG fAllowLogin = SYNCHRONIZE | READ_CONTROL; + if (fProcess) + { + fDeny |= PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID | PROCESS_VM_OPERATION | PROCESS_VM_WRITE + | PROCESS_CREATE_PROCESS | PROCESS_DUP_HANDLE | PROCESS_SET_QUOTA + | PROCESS_SET_INFORMATION | PROCESS_SUSPEND_RESUME; + fAllow |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; + fAllowLogin |= PROCESS_TERMINATE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + { + fAllow |= PROCESS_QUERY_LIMITED_INFORMATION; + fAllowLogin |= PROCESS_QUERY_LIMITED_INFORMATION; + } + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3)) /* Introduced in Windows 8.1. */ + fAllow |= PROCESS_SET_LIMITED_INFORMATION; + } + else + { + fDeny |= THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT | THREAD_SET_INFORMATION | THREAD_SET_THREAD_TOKEN + | THREAD_IMPERSONATE | THREAD_DIRECT_IMPERSONATION; + fAllow |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION; + fAllowLogin |= THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + { + fAllow |= THREAD_QUERY_LIMITED_INFORMATION | THREAD_SET_LIMITED_INFORMATION; + fAllowLogin |= THREAD_QUERY_LIMITED_INFORMATION; + } + + } + fDeny |= ~fAllow & (SPECIFIC_RIGHTS_ALL | STANDARD_RIGHTS_ALL); + + /* Deny everyone access to bad bits. */ +#if 1 + SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Everyone.Sid, &SIDAuthWorld, 1)); + *RtlSubAuthoritySid(&pCleanup->Everyone.Sid, 0) = SECURITY_WORLD_RID; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->Everyone.Sid)); +#endif + +#if 0 + /* Grant some access to the owner - doesn't work. */ + SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlInitializeSid(&pCleanup->Owner.Sid, &SIDAuthCreator, 1)); + *RtlSubAuthoritySid(&pCleanup->Owner.Sid, 0) = SECURITY_CREATOR_OWNER_RID; + + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->Owner.Sid)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllow, &pCleanup->Owner.Sid)); +#endif + +#if 1 + bool fHasLoginSid = supR3HardenedGetUserAndLogSids(&pCleanup->User.Sid, sizeof(pCleanup->User), + &pCleanup->Login.Sid, sizeof(pCleanup->Login)); + +# if 1 + /* Grant minimal access to the user. */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessDeniedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fDeny, &pCleanup->User.Sid)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllow, &pCleanup->User.Sid)); +# endif + +# if 1 + /* Grant very limited access to the login sid. */ + if (fHasLoginSid) + { + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlAddAccessAllowedAce(&pCleanup->Acl.AclHdr, ACL_REVISION, + fAllowLogin, &pCleanup->Login.Sid)); + } +# endif + +#endif + + /* + * Create a security descriptor with the above ACL. + */ + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)suplibHardenedAllocZ(SECURITY_DESCRIPTOR_MIN_LENGTH); + pCleanup->pSecDesc = pSecDesc; + + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)); + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, &pCleanup->Acl.AclHdr, + FALSE /*fDaclDefaulted*/)); + pSecAttrs->lpSecurityDescriptor = pSecDesc; +} + + +/** + * Predicate function which tests whether @a ch is a argument separator + * character. + * + * @returns True/false. + * @param ch The character to examine. + */ +DECLINLINE(bool) suplibCommandLineIsArgSeparator(int ch) +{ + return ch == ' ' + || ch == '\t' + || ch == '\n' + || ch == '\r'; +} + + +/** + * Construct the new command line. Since argc/argv are both derived from + * GetCommandLineW (see suplibHardenedWindowsMain), we skip the argument + * by argument UTF-8 -> UTF-16 conversion and quoting by going to the + * original source. + * + * The executable name, though, is replaced in case it's not a fullly + * qualified path. + * + * The re-spawn indicator is added immediately after the executable name + * so that we don't get tripped up missing close quote chars in the last + * argument. + * + * @returns Pointer to a command line string (heap). + */ +static PRTUTF16 supR3HardenedWinConstructCmdLine(void) +{ + /* + * Get the command line and skip the executable name. + */ + PCRTUTF16 pwszArgs = GetCommandLineW(); + + /* Skip leading space (shouldn't be any, but whatever). */ + while (suplibCommandLineIsArgSeparator(*pwszArgs)) + pwszArgs++; + SUPR3HARDENED_ASSERT(*pwszArgs != '\0'); + + /* Walk to the end of it. */ + int fQuoted = false; + do + { + if (*pwszArgs == '"') + { + fQuoted = !fQuoted; + pwszArgs++; + } + else if (*pwszArgs != '\\' || (pwszArgs[1] != '\\' && pwszArgs[1] != '"')) + pwszArgs++; + else + { + unsigned cSlashes = 0; + do + cSlashes++; + while (*++pwszArgs == '\\'); + if (*pwszArgs == '"' && (cSlashes & 1)) + pwszArgs++; /* odd number of slashes == escaped quote */ + } + } while (*pwszArgs && (fQuoted || !suplibCommandLineIsArgSeparator(*pwszArgs))); + + /* Skip trailing spaces. */ + while (suplibCommandLineIsArgSeparator(*pwszArgs)) + pwszArgs++; + + /* + * Allocate a new buffer. + */ + size_t cwcArgs = suplibHardenedWStrLen(pwszArgs); + size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_ARG0) - 1) / sizeof(SUPR3_RESPAWN_ARG0[0]) /* Respawn exe name. */ + + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */ + PRTUTF16 pwszCmdLine = (PRTUTF16)HeapAlloc(GetProcessHeap(), 0 /* dwFlags*/, (cwcCmdLine + 1) * sizeof(RTUTF16)); + SUPR3HARDENED_ASSERT(pwszCmdLine != NULL); + + /* + * Construct the new command line. + */ + PRTUTF16 pwszDst = pwszCmdLine; + for (const char *pszSrc = SUPR3_RESPAWN_ARG0; *pszSrc; pszSrc++) + *pwszDst++ = *pszSrc; + + if (cwcArgs) + { + *pwszDst++ = ' '; + suplibHardenedMemCopy(pwszDst, pwszArgs, cwcArgs * sizeof(RTUTF16)); + pwszDst += cwcArgs; + } + + *pwszDst = '\0'; + SUPR3HARDENED_ASSERT(pwszDst - pwszCmdLine == cwcCmdLine); + + return pwszCmdLine; +} + + +/** + * Does the actually respawning. + * + * @returns Exit code (if we get that far). + */ +static int supR3HardenedWinDoReSpawn(void) +{ + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + + /* + * Configure the startup info and creation flags. + */ + DWORD dwCreationFlags = 0; + + STARTUPINFOEXW SiEx; + suplibHardenedMemSet(&SiEx, 0, sizeof(SiEx)); + if (1) + SiEx.StartupInfo.cb = sizeof(SiEx.StartupInfo); + else + { + SiEx.StartupInfo.cb = sizeof(SiEx); + dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; + /** @todo experiment with protected process stuff later on. */ + } + + SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + SiEx.StartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + SiEx.StartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + SiEx.StartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + /* + * Set up security descriptors. + */ + SECURITY_ATTRIBUTES ProcessSecAttrs; + MYSECURITYCLEANUP ProcessSecAttrsCleanup; + supR3HardenedInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/); + + SECURITY_ATTRIBUTES ThreadSecAttrs; + MYSECURITYCLEANUP ThreadSecAttrsCleanup; + supR3HardenedInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/); + + /* + * Construct the command line and launch the process. + */ + PRTUTF16 pwszCmdLine = supR3HardenedWinConstructCmdLine(); + + PROCESS_INFORMATION ProcessInfo; + if (!CreateProcessW(g_wszSupLibHardenedExePath, + pwszCmdLine, + &ProcessSecAttrs, + &ThreadSecAttrs, + TRUE /*fInheritHandles*/, + dwCreationFlags, + NULL /*pwszzEnvironment*/, + NULL /*pwszCurDir*/, + &SiEx.StartupInfo, + &ProcessInfo)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "Error relaunching VirtualBox VM process: %u\n" + "Command line: '%ls'", + GetLastError(), pwszCmdLine); + + /* + * Close the unrestricted access handles. Since we need to wait on the + * child process, we'll reopen the process with limited access before doing + * away with the process handle returned by CreateProcess. + */ + SUPR3HARDENED_ASSERT(CloseHandle(ProcessInfo.hThread)); + ProcessInfo.hThread = NULL; + + HANDLE hProcWait; + DWORD dwRights = SYNCHRONIZE; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + dwRights |= PROCESS_QUERY_LIMITED_INFORMATION; + else + dwRights |= PROCESS_QUERY_INFORMATION; + if (!DuplicateHandle(GetCurrentProcess(), + ProcessInfo.hProcess, + GetCurrentProcess(), + &hProcWait, + SYNCHRONIZE, + FALSE /*fInheritHandle*/, + 0)) + { + /* This is unacceptable, kill the process. */ + DWORD dwErr = GetLastError(); + TerminateProcess(ProcessInfo.hProcess, RTEXITCODE_FAILURE); + supR3HardenedError(dwErr, false /*fFatal*/, "DuplicateHandle failed on child process handle: %u\n", dwErr); + + DWORD dwExit; + BOOL fExitOk = GetExitCodeProcess(ProcessInfo.hProcess, &dwExit) + && dwExit != STILL_ACTIVE; + if (!fExitOk) + { + DWORD dwStartTick = GetTickCount(); + DWORD dwWait; + do + { + TerminateProcess(ProcessInfo.hProcess, RTEXITCODE_FAILURE); + dwWait = WaitForSingleObject(ProcessInfo.hProcess, 1000); + fExitOk = GetExitCodeProcess(ProcessInfo.hProcess, &dwExit) + && dwExit != STILL_ACTIVE; + } while ( !fExitOk + && (dwWait == WAIT_TIMEOUT || dwWait == WAIT_IO_COMPLETION) + && GetTickCount() - dwStartTick < 60 * 1000); + if (fExitOk) + supR3HardenedError(dwErr, false /*fFatal*/, + "DuplicateHandle failed and we failed to kill child: dwErr=%u dwWait=%u err=%u hProcess=%p\n", + dwErr, dwWait, GetLastError(), ProcessInfo.hProcess); + } + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "DuplicateHandle failed on child process handle: %u\n", dwErr); + } + + SUPR3HARDENED_ASSERT(CloseHandle(ProcessInfo.hProcess)); + ProcessInfo.hProcess = NULL; + + /* + * Wait for the process to terminate and proxy the termination code. + */ + for (;;) + { + SetLastError(NO_ERROR); + DWORD dwWait = WaitForSingleObject(hProcWait, INFINITE); + if ( dwWait == WAIT_OBJECT_0 + || dwWait == WAIT_ABANDONED_0) + break; + if ( dwWait != WAIT_TIMEOUT + && dwWait != WAIT_IO_COMPLETION) + supR3HardenedFatal("WaitForSingleObject returned %#x (last error %#x)\n", dwWait, GetLastError()); + } + + DWORD dwExit; + if ( !GetExitCodeProcess(hProcWait, &dwExit) + || dwExit == STILL_ACTIVE) + dwExit = RTEXITCODE_FAILURE; + + CloseHandle(hProcWait); + suplibHardenedExit((RTEXITCODE)dwExit); +} + + +/** + * Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true. + * + * @returns Program exit code. + */ +DECLHIDDEN(int) supR3HardenedWinReSpawn(void) +{ + /* + * Open the stub device. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + static const WCHAR s_wszName[] = L"\\Device\\VBoxDrvStub"; + UNICODE_STRING NtName; + NtName.Buffer = (PWSTR)s_wszName; + NtName.Length = sizeof(s_wszName) - sizeof(WCHAR); + NtName.MaximumLength = sizeof(s_wszName); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ | GENERIC_WRITE, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (!NT_SUCCESS(rcNt)) + { + int rc; + if ((rcNt & UINT32_C(0xffff0000)) == 0xe9860000) /* See VBoxDrvNtErr2NtStatus. */ /** @todo #defines for VBoxDrvNtErr2NtStatus mangling */ + rc = (int)(rcNt | UINT32_C(0xffff0000)); + else + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x\n", s_wszName, rcNt); + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %Rrc (rcNt=%#x)\n", s_wszName, rc, rcNt); + } + + /* + * Respawn the process with kernel protection for the new process. + */ + return supR3HardenedWinDoReSpawn(); +} + + +/** + * Checks if re-spawning is required, replacing the respawn argument if not. + * + * @returns true if required, false if not. In the latter case, the first + * argument in the vector is replaced. + * @param cArgs The number of arguments. + * @param papszArgs Pointer to the argument vector. + */ +DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int cArgs, char **papszArgs) +{ + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + + if (cArgs < 1) + return true; + if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_ARG0)) + return true; + + /* Replace the argument. */ + papszArgs[0] = g_szSupLibHardenedExePath; + return false; +} + + +/** + * Initializes the windows verficiation bits. + * @param fFlags The main flags. + */ +DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags) +{ + RTErrInfoInitStatic(&g_ErrInfoStatic); + int rc = supHardenedWinInitImageVerifier(&g_ErrInfoStatic.Core); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rc, + "supHardenedWinInitImageVerifier failed: %s", g_ErrInfoStatic.szMsg); + supR3HardenedWinInstallHooks(); + +#ifndef VBOX_WITH_VISTA_NO_SP + /* + * Complain about Vista w/o service pack if we're launching a VM. + */ + if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + && g_uNtVerCombined >= SUP_NT_VER_VISTA + && g_uNtVerCombined < SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_NOT_SUPPORTED, + "Window Vista without any service pack installed is not supported. Please install the latest service pack."); +#endif +} + + +/** + * Converts the Windows command line string (UTF-16) to an array of UTF-8 + * arguments suitable for passing to main(). + * + * @returns Pointer to the argument array. + * @param pwszCmdLine The UTF-16 windows command line to parse. + * @param pcArgs Where to return the number of arguments. + */ +static char **suplibCommandLineToArgvWStub(PCRTUTF16 pwszCmdLine, int *pcArgs) +{ + /* + * Convert the command line string to UTF-8. + */ + int cbNeeded = WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, pwszCmdLine, -1, NULL /*pszDst*/, 0 /*cbDst*/, + NULL /*pchDefChar*/, NULL /* pfUsedDefChar */); + SUPR3HARDENED_ASSERT(cbNeeded > 0); + int cbAllocated = cbNeeded + 16; + char *pszCmdLine = (char *)suplibHardenedAllocZ(cbAllocated); + + SUPR3HARDENED_ASSERT(WideCharToMultiByte(CP_UTF8, 0 /*dwFlags*/, pwszCmdLine, -1, + pszCmdLine, cbAllocated - 1, + NULL /*pchDefChar*/, NULL /* pfUsedDefChar */) == cbNeeded); + + /* + * Parse the command line, carving argument strings out of it. + */ + int cArgs = 0; + int cArgsAllocated = 4; + char **papszArgs = (char **)suplibHardenedAllocZ(sizeof(char *) * cArgsAllocated); + char *pszSrc = pszCmdLine; + for (;;) + { + /* skip leading blanks. */ + char ch = *pszSrc; + while (suplibCommandLineIsArgSeparator(ch)) + ch = *++pszSrc; + if (!ch) + break; + + /* Add argument to the vector. */ + if (cArgs + 2 >= cArgsAllocated) + { + cArgsAllocated *= 2; + papszArgs = (char **)suplibHardenedReAlloc(papszArgs, sizeof(char *) * cArgsAllocated); + } + papszArgs[cArgs++] = pszSrc; + papszArgs[cArgs] = NULL; + + /* Unquote and unescape the string. */ + char *pszDst = pszSrc++; + bool fQuoted = false; + do + { + if (ch == '"') + fQuoted = !fQuoted; + else if (ch != '\\' || (*pszSrc != '\\' && *pszSrc != '"')) + *pszDst++ = ch; + else + { + unsigned cSlashes = 0; + while ((ch = *pszSrc++) == '\\') + cSlashes++; + if (ch == '"') + { + while (cSlashes >= 2) + { + cSlashes -= 2; + *pszDst++ = '\\'; + } + if (cSlashes) + *pszDst++ = '"'; + else + fQuoted = !fQuoted; + } + else + { + pszSrc--; + while (cSlashes-- > 0) + *pszDst++ = '\\'; + } + } + + ch = *pszSrc++; + } while (ch != '\0' && (fQuoted || !suplibCommandLineIsArgSeparator(ch))); + + /* Terminate the argument. */ + *pszDst = '\0'; + if (!ch) + break; + } + + *pcArgs = cArgs; + return papszArgs; +} + + +extern "C" int main(int argc, char **argv, char **envp); + +/** + * The executable entry point. + * + * This is normally taken care of by the C runtime library, but we don't want to + * get involved with anything as complicated like the CRT in this setup. So, we + * it everything ourselves, including parameter parsing. + */ +extern "C" void __stdcall suplibHardenedWindowsMain(void) +{ + RTEXITCODE rcExit = RTEXITCODE_FAILURE; + + g_cSuplibHardenedWindowsMainCalls++; + + /* + * Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in + * SUPHardenedVerfiyImage-win.cpp.) + */ + supR3HardenedWinInitVersion(); + + /* + * Get the executable name. + */ + DWORD cwcExecName = GetModuleFileNameW(GetModuleHandle(NULL), g_wszSupLibHardenedExePath, + RT_ELEMENTS(g_wszSupLibHardenedExePath)); + if (cwcExecName >= RT_ELEMENTS(g_wszSupLibHardenedExePath)) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, VERR_BUFFER_OVERFLOW, + "The executable path is too long."); + + /* The NT version. */ + HANDLE hFile = CreateFileW(g_wszSupLibHardenedExePath, GENERIC_READ, FILE_SHARE_READ, NULL /*pSecurityAttributes*/, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/); + if (hFile == NULL || hFile == INVALID_HANDLE_VALUE) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromWin32(GetLastError()), + "Error opening the executable: %u (%ls).", GetLastError()); + RT_ZERO(g_SupLibHardenedExeNtPath); + ULONG cbIgn; + NTSTATUS rcNt = NtQueryObject(hFile, ObjectNameInformation, &g_SupLibHardenedExeNtPath, + sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbIgn); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("suplibHardenedWindowsMain", kSupInitOp_Integrity, RTErrConvertFromNtStatus(rcNt), + "NtQueryObject -> %#x (on %ls)\n", rcNt, g_wszSupLibHardenedExePath); + CloseHandle(hFile); + + /* The NT executable name offset / dir path length. */ + g_offSupLibHardenedExeNtName = g_SupLibHardenedExeNtPath.UniStr.Length / sizeof(WCHAR); + while ( g_offSupLibHardenedExeNtName > 1 + && g_SupLibHardenedExeNtPath.UniStr.Buffer[g_offSupLibHardenedExeNtName - 1] != '\\' ) + g_offSupLibHardenedExeNtName--; + + /* + * Convert the arguments to UTF-8 and call the C/C++ main function. + */ + int cArgs; + char **papszArgs = suplibCommandLineToArgvWStub(GetCommandLineW(), &cArgs); + + rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL); + + /* + * Exit the process (never return). + */ + suplibHardenedExit(rcExit); +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm new file mode 100644 index 00000000000..282ce6a7b2a --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm @@ -0,0 +1,76 @@ +; $Id$ +;; @file +; VirtualBox Support Library - Hardened main(), Windows assembly bits. +; + +; +; Copyright (C) 2012-2014 Oracle Corporation +; +; This file is part of VirtualBox Open Source Edition (OSE), as +; available from http://www.virtualbox.org. This file is free software; +; you can redistribute it and/or modify it under the terms of the GNU +; General Public License (GPL) as published by the Free Software +; Foundation, in version 2 as it comes in the "COPYING" file of the +; VirtualBox OSE distribution. VirtualBox OSE is distributed in the +; hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL) only, as it comes in the "COPYING.CDDL" file of the +; VirtualBox OSE 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. +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + + +; External data. +extern NAME(g_pfnNtCreateSectionJmpBack) + + +BEGINCODE + +; +; 64-bit +; +%ifdef RT_ARCH_AMD64 + %macro supR3HardenedJmpBack_NtCreateSection_Xxx 1 + BEGINPROC supR3HardenedJmpBack_NtCreateSection_ %+ %1 + ; The code we replaced. + mov r10, rcx + mov eax, %1 + + ; Jump back to the original code. + jmp [NAME(g_pfnNtCreateSectionJmpBack) wrt RIP] + ENDPROC supR3HardenedJmpBack_NtCreateSection_ %+ %1 + %endm + %define SYSCALL(a_Num) supR3HardenedJmpBack_NtCreateSection_Xxx a_Num + %include "NtCreateSection-template-amd64-syscall-type-1.h" + +%endif + + +; +; 32-bit. +; +%ifdef RT_ARCH_X86 + %macro supR3HardenedJmpBack_NtCreateSection_Xxx 1 + BEGINPROC supR3HardenedJmpBack_NtCreateSection_ %+ %1 + ; The code we replaced. + mov eax, %1 + + ; Jump back to the original code. + jmp [NAME(g_pfnNtCreateSectionJmpBack)] + ENDPROC supR3HardenedJmpBack_NtCreateSection_ %+ %1 + %endm + %define SYSCALL(a_Num) supR3HardenedJmpBack_NtCreateSection_Xxx a_Num + %include "NtCreateSection-template-x86-syscall-type-1.h" + +%endif + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp new file mode 100644 index 00000000000..ea644ff2df9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp @@ -0,0 +1,191 @@ +/* $Id$ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <iprt/nt/nt-and-windows.h> +#include <AccCtrl.h> +#include <AclApi.h> +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/mem.h> + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" + + +/* + * assert.cpp + */ + +RTDATADECL(char) g_szRTAssertMsg1[1024]; +RTDATADECL(char) g_szRTAssertMsg2[4096]; +RTDATADECL(const char * volatile) g_pszRTAssertExpr; +RTDATADECL(const char * volatile) g_pszRTAssertFile; +RTDATADECL(uint32_t volatile) g_u32RTAssertLine; +RTDATADECL(const char * volatile) g_pszRTAssertFunction; + +RTDECL(bool) RTAssertMayPanic(void) +{ + return true; +} + + +RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + /* + * Fill in the globals. + */ + g_pszRTAssertExpr = pszExpr; + g_pszRTAssertFile = pszFile; + g_pszRTAssertFunction = pszFunction; + g_u32RTAssertLine = uLine; + RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +} + + +RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) +{ + RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, va); + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN) + supR3HardenedFatalMsg(g_pszRTAssertExpr, kSupInitOp_Misc, VERR_INTERNAL_ERROR, + "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); + else + supR3HardenedError(VERR_INTERNAL_ERROR, false/*fFatal*/, "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); +} + + +/* + * Memory allocator. + */ + +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW +{ + return RTMemAllocTag(cb, pszTag); +} + + +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW +{ + return RTMemAllocZTag(cb, pszTag); +} + + +RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW +{ + RTMemFree(pv); +} + + +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW +{ + return suplibHardenedAllocZ(cb); +} + + +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW +{ + return suplibHardenedAllocZ(cb); +} + + +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocTag(cbAligned, pszTag); +} + + +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocZTag(cbAligned, pszTag); +} + + +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW +{ + return suplibHardenedReAlloc(pvOld, cbNew); +} + + +RTDECL(void) RTMemFree(void *pv) RT_NO_THROW +{ + suplibHardenedFree(pv); +} + + +/* + * Simplified version of RTMemWipeThoroughly that avoids dragging in the + * random number code. + */ + +RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW +{ + size_t cPasses = RT_MIN(cMinPasses, 6); + static const uint32_t s_aPatterns[] = { 0x00, 0xaa, 0x55, 0xff, 0xf0, 0x0f, 0xcc, 0x3c, 0xc3 }; + uint32_t iPattern = 0; + do + { + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + } while (cPasses-- > 0); + + memset(pv, 0xff, cb); + ASMMemoryFence(); +} + diff --git a/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp b/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp new file mode 100644 index 00000000000..ed307a35949 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp @@ -0,0 +1,92 @@ +/* $Id$ */ +/** @file + * IPRT - VBoxSupLib.dll, Windows. + */ + +/* + * Copyright (C) 2006-2012 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE 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. + */ + + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/path.h> + + +/** + * The Dll main entry point. + */ +extern "C" +BOOL __stdcall DllMainEntrypoint(HANDLE hModule, DWORD dwReason, PVOID pvReserved) +{ + switch (dwReason) + { + /* + * Make sure the DLL isn't ever unloaded. + */ + case DLL_PROCESS_ATTACH: + { + WCHAR wszName[RTPATH_MAX]; + SetLastError(NO_ERROR); + if ( GetModuleFileNameW((HMODULE)hModule, wszName, RT_ELEMENTS(wszName)) > 0 + && GetLastError() == NO_ERROR) + { + int cExtraLoads = 2; + while (cExtraLoads-- > 0) + LoadLibraryW(wszName); + } + break; + } + + case DLL_THREAD_ATTACH: + { +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Anti debugging hack that prevents most debug notifications from + * ending up in the debugger. + */ + NTSTATUS rcNt = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + { + __debugbreak(); + return FALSE; + } +#endif + break; + } + + case DLL_THREAD_DETACH: + /* Nothing to do. */ + break; + + case DLL_PROCESS_DETACH: + /* Nothing to do. */ + break; + + default: + /* ignore */ + break; + } + return TRUE; +} + |