diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/HostDrivers/Support | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/HostDrivers/Support')
141 files changed, 74193 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf Binary files differnew file mode 100644 index 00000000..82a8d623 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf Binary files differnew file mode 100644 index 00000000..71aaf14d --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/Makefile.kup b/src/VBox/HostDrivers/Support/Certificates/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf Binary files differnew file mode 100644 index 00000000..d8754c9a --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf Binary files differnew file mode 100644 index 00000000..c1d18a26 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf Binary files differnew file mode 100644 index 00000000..486b6fd7 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf Binary files differnew file mode 100644 index 00000000..7b46d69b --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf Binary files differnew file mode 100644 index 00000000..f2fa96a3 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf Binary files differnew file mode 100644 index 00000000..e55039ab --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf Binary files differnew file mode 100644 index 00000000..46c6250a --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf Binary files differnew file mode 100644 index 00000000..3131fc27 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf Binary files differnew file mode 100644 index 00000000..945de02c --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf Binary files differnew file mode 100644 index 00000000..610b14f1 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf Binary files differnew file mode 100644 index 00000000..0c1a1af7 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf Binary files differnew file mode 100644 index 00000000..b9d8f7b8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt Binary files differnew file mode 100644 index 00000000..bb6c0bcf --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf Binary files differnew file mode 100644 index 00000000..f7d4e6ba --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt Binary files differnew file mode 100644 index 00000000..57d117d1 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf Binary files differnew file mode 100644 index 00000000..a3216fb8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf diff --git a/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer Binary files differnew file mode 100644 index 00000000..e47fb25a --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer diff --git a/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf Binary files differnew file mode 100644 index 00000000..a592810b --- /dev/null +++ b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf diff --git a/src/VBox/HostDrivers/Support/Makefile.kmk b/src/VBox/HostDrivers/Support/Makefile.kmk new file mode 100644 index 00000000..6543b624 --- /dev/null +++ b/src/VBox/HostDrivers/Support/Makefile.kmk @@ -0,0 +1,938 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the support library and the drivers/modules/kexts it uses. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# Globals. +# +VBOX_PATH_SUP_SRC := $(PATH_SUB_CURRENT) +VBOX_PATH_SUPR3_CERTIFICATES := $(PATH_SUB_CURRENT)/Certificates +VBOX_PATH_RUNTIME_SRC ?= $(PATH_ROOT)/src/VBox/Runtime + + +# +# Targets +# +LIBRARIES += SUPR3 SUPR3Static +if defined(VBOX_WITH_HARDENING) \ + && !defined(VBOX_ONLY_VALIDATIONKIT) + LIBRARIES += SUPR3HardenedStatic +endif +ifndef VBOX_ONLY_BUILD + DLLS.win += VBoxSupLib +endif +ifdef VBOX_WITH_32_ON_64_MAIN_API + LIBRARIES += SUPR3-x86 +endif +if !defined(VBOX_ONLY_DOCS) + if1of ($(VBOX_LDR_FMT), pe lx) + LIBRARIES += SUPR0 + endif +endif +if !defined(VBOX_ONLY_BUILD) && defined(VBOX_WITH_RAW_MODE) + LIBRARIES += SUPRC +endif +if !defined(VBOX_ONLY_DOCS) \ + && !defined(VBOX_ONLY_EXTPACKS) \ + && !defined(VBOX_ONLY_VALIDATIONKIT) \ + && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" + ifdef VBOX_WITH_SUPSVC + PROGRAMS += VBoxSupSvc + endif + ifdef VBOX_WITH_VBOXDRV + LIBRARIES += SUPR0IdcClient + SYSMODS.os2 += VBoxDrv + endif + INSTALLS.linux += vboxdrv-src + INSTALLS.freebsd += vboxdrv-src + + # + # Include sub-makefile(s). + # + include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk + + # + # Populate FILES_VBOXDRV_NOBIN and FILES_VBOXDRV_BIN + # + ifeq ($(KBUILD_TARGET),linux) + include $(PATH_SUB_CURRENT)/linux/files_vboxdrv + endif + ifeq ($(KBUILD_TARGET),freebsd) + include $(PATH_SUB_CURRENT)/freebsd/files_vboxdrv + 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 \ + SpcRootMicrosoft6=SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf \ + SpcRootMicrosoft7=SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf \ + NtRootMicrosoft8=NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf \ + TimeRootMicrosoft0=Timestamp-CopyrightC1997MicrosoftCorp-01.taf \ + TimeRootOracle0=Timestamp-VBoxLegacyWinCA.taf \ + TimeRootOracle1=Timestamp-VBoxLegacyWinSha1CA.taf \ + TrustedCertVBox0=Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf \ + AppleRoot0=AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf \ + AppleRoot1=AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf +# Hack Alert! Because the DigiCert cross signing certificate we use expired 2020-04-15, we add it as a trusted NT kernel +# signing root instead, so we can bypass the expiry check in IPRT. NtRootMicrosoft8 is the one found in the +# 6.1.20 extpack, NtRootMicrosoft9 is the one bird uses at home. +VBOX_SUP_WIN_CERTS += NtRootDigiCert8=NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf +VBOX_SUP_WIN_CERTS += NtRootDigiCert9=NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf + +VBOX_SUP_WIN_CERT_NAMES := $(foreach cert,$(VBOX_SUP_WIN_CERTS),$(firstword $(subst =,$(SPACE) ,$(cert)))) + +# 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 + +if ("$(KBUILD_TARGET)" == "win" && defined(VBOX_SIGNING_MODE)) \ + || ("$(KBUILD_TARGET)" == "darwin" && defined(VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION) && defined(VBOX_SIGNING_MODE)) + BLDPROGS += bldSUPSignedDummy + bldSUPSignedDummy_TEMPLATE = VBoxBldProg + bldSUPSignedDummy_SOURCES = bldSUPSignedDummy.cpp + bldSUPSignedDummy_ORDERDEPS.win = $(VBOX_SIGN_IMAGE_ORDERDEPS) + bldSUPSignedDummy_POST_CMDS.win = $(call VBOX_SIGN_IMAGE_FN,$(out),,2) + bldSUPSignedDummy_POST_CMDS.darwin = $(call VBOX_SIGN_MACHO_FN,$(out),org.virtualbox.org.bldtool.$(target)) +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 defined(bldSUPSignedDummy_SOURCES),$(VBOX_RTSIGNTOOL) $$(bldSUPSignedDummy_1_TARGET),) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ $@.cer + $(QUIET)$(APPEND) -n "$@" \ + '' \ + '#include <VBox/sup.h>' \ + '' + $(foreach cert,$(VBOX_SUP_WIN_CERTS), $(NLTAB)$(VBOX_BIN2C) -ascii --append --static --no-size \ + "SUP$(firstword $(subst =,$(SP) ,$(cert)))" \ + "$(VBOX_PATH_SUPR3_CERTIFICATES)/$(lastword $(subst =,$(SP) ,$(cert)))" \ + "$@") +# The build certificate. +ifdef bldSUPSignedDummy_SOURCES + $(VBOX_RTSIGNTOOL) extract-exe-signer-cert --exe "$(bldSUPSignedDummy_1_TARGET)" --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,AppleRoot,AppleRoot%) \ + $(call VBOX_SUP_GEN_CERT_MACRO,Trusted,TrustedCert%,build) + +OTHER_CLEAN += $(VBOX_SUP_WIN_CERTS_FILE) + +tst: $(VBOX_SUP_WIN_CERTS_FILE) + + +# +# The Ring-3 Support Library (this is linked into the IPRT dll, VBoxRT). +# +SUPR3_TEMPLATE = VBoxR3Dll +SUPR3_DEFS = \ + IN_SUP_R3 IN_RT_R3 \ + $(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_DRIVERLESS_NEM_FALLBACK),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) \ + VBOX_PERMIT_MORE \ + VBOX_PERMIT_EVEN_MORE +SUPR3_INCS := $(PATH_SUB_CURRENT) +SUPR3_SOURCES = \ + SUPLib.cpp \ + SUPLibLdr.cpp \ + SUPLibSem.cpp \ + SUPLibAll.cpp \ + SUPR3HardenedIPRT.cpp \ + SUPR3HardenedVerify.cpp \ + $(KBUILD_TARGET)/SUPLib-$(KBUILD_TARGET).cpp \ + $(VBOX_SUP_WIN_CERTS_FILE) +SUPR3_SOURCES.amd64 = \ + SUPLibTracerA.asm +SUPR3_SOURCES.x86 = \ + SUPLibTracerA.asm +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. 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). +# +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_DRIVERLESS_NEM_FALLBACK),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) \ + $(if $(VBOX_WITHOUT_DEBUGGER_CHECKS),VBOX_WITHOUT_DEBUGGER_CHECKS,) \ + $(if $(VBOX_PERMIT_VISUAL_STUDIO_PROFILING),VBOX_PERMIT_VISUAL_STUDIO_PROFILING,) \ + VBOX_PERMIT_MORE \ + VBOX_PERMIT_EVEN_MORE +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/SUPR3HardenedMainImports-win.cpp \ + win/SUPHardenedVerifyProcess-win.cpp \ + win/SUPHardenedVerifyImage-win.cpp \ + $(VBOX_SUP_WIN_CERTS_FILE) +SUPR3HardenedStatic_SOURCES.x86 += \ + $(VBOX_PATH_RUNTIME_SRC)/common/asm/ASMMemFirstMismatchingU8.asm +SUPR3HardenedStatic_SOURCES.amd64 += \ + $(VBOX_PATH_RUNTIME_SRC)/common/asm/ASMMemFirstMismatchingU8.asm + + +if "$(KBUILD_TARGET)" == "win" && defined(VBOX_WITH_HARDENING) && !defined(VBOX_ONLY_VALIDATIONKIT) ## @todo some of this move up. + SUPR3HardenedStatic_DEFS += \ + IN_RT_R3 \ + IN_RT_STATIC \ + IN_DIS \ + IN_DIS_STATIC \ + DIS_CORE_ONLY \ + IPRT_NO_CRT \ + RT_WITH_NOCRT_ALIASES \ + LOG_DISABLED \ + IPRT_NO_ERROR_DATA \ + IPRT_WITHOUT_DIGEST_MD4 + 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/alloc/heapsimple.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-safer-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/key.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/crypto/tsp-asn1-decoder.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/tsp-core.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/tsp-init.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/crypto/tsp-sanity.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-md2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-md5.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-sha1.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-sha256.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-sha512.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/checksum/alt-sha3.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/sha384str.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/RTUtf16PrintHexBytes.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16ICmpAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16NICmpAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16CatAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16CopyAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16End.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/math/bignum-amd64-x86.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg1Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2WeakV.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/zero.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathAbsEx.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathFilename.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathParse.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/path/RTPathParsedReassemble.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/strversion.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrPrintHexBytes.cpp \ + $(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/RTStrEnd.asm \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrICmpAscii.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrNCmp.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTStrNLen.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16Copy.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/RTUtf16NLenEx.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/RTStrFormat.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformatrt.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformattype.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strformatnum.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/strprintf-ellipsis.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strprintf2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/string/strprintf2-ellipsis.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/RTPathGetCurrentDrive-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/RTPathGetCurrentOnDrive-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/rtStrFormatKernelAddress-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/memsafer-generic.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/uuid-generic.cpp \ + \ + ../../Disassembler/DisasmCore.cpp \ + ../../Disassembler/DisasmTables.cpp \ + ../../Disassembler/DisasmTablesX64.cpp \ + ../../Disassembler/DisasmMisc.cpp + + SUPR3HardenedStatic_SOURCES.amd64 += \ + $(VBOX_PATH_RUNTIME_SRC)/common/math/RTUInt128MulByU64.asm \ + $(VBOX_PATH_RUNTIME_SRC)/win/amd64/ASMGetCS.asm \ + $(VBOX_PATH_RUNTIME_SRC)/win/amd64/ASMGetSS.asm + + SUPR3HardenedStatic_SOURCES.win += \ + win/SUPR3HardenedNoCrt-win.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/nt/RTErrConvertFromNtStatus.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/nt/RTNtPathFindPossible8dot3Name.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/nt/RTNtPathExpand8dot3Path.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/nt/RTNtPathExpand8dot3PathA.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/r3/nt/pathint-nt.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/win/RTErrConvertFromWin32.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/win/errmsgwin.cpp + + # Add necessary compiler specific files from the compiler lib dir. + ifeq ($(KBUILD_TARGET),win) + include $(KBUILD_PATH)/tools/$(VBOX_VCC_TOOL).kmk + SUPR3HardenedStatic_SOURCES.win += \ + $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/chkstk.obj + + # And a few extracted from the static libc to support -guard and cookes. + # In 14.2 these files does not import anything from the win32 API and are + # mostly tiny bits of code. Needs not initialization that I can spot. + ifneq ($(VBOX_VCC_LD_GUARD_CF),) + SUPR3HardenedStatic_SOURCES.win += \ + $(SUPR3HardenedStatic_0_OUTDIR)/loadcfg.obj + # These are for the /guard option. + SUPR3HardenedStatic_SOURCES.win += \ + $(SUPR3HardenedStatic_0_OUTDIR)/gs_cookie.obj \ + $(SUPR3HardenedStatic_0_OUTDIR)/guard_support.obj + SUPR3HardenedStatic_SOURCES.win.amd64 += \ + $(SUPR3HardenedStatic_0_OUTDIR)/guard_dispatch.obj \ + $(SUPR3HardenedStatic_0_OUTDIR)/guard_xfg_dispatch.obj + # These next ones are for supporting the /GS option. We skip gs_report.obj as it + # import lots from kernel32 and we're better of reporting the problem ourselves. + SUPR3HardenedStatic_SOURCES.win.amd64 += \ + $(SUPR3HardenedStatic_0_OUTDIR)/amdsecgs.obj \ + $(SUPR3HardenedStatic_0_OUTDIR)/gshandler.obj + SUPR3HardenedStatic_SOURCES.win.x86 += \ + $(SUPR3HardenedStatic_0_OUTDIR)/secchk.obj \ + $(SUPR3HardenedStatic_0_OUTDIR)/alloca16.obj + + $$(SUPR3HardenedStatic_0_OUTDIR)/loadcfg.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/gs_cookie.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/guard_support.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/guard_dispatch.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/guard_xfg_dispatch.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/amdsecgs.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/gs_report.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/gshandler.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/secchk.obj \ + $$(SUPR3HardenedStatic_0_OUTDIR)/alloca16.obj: \ + $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/libcmt.lib | $$(dir $$@) + set -x; $(TOOL_$(VBOX_VCC_TOOL)_AR) "/EXTRACT:$$($(TOOL_$(VBOX_VCC_TOOL)_AR) /LIST "$<" | $(SED_EXT) -e '/$(notdir $@)/!d' )" "/OUT:$@" "$<" + endif + endif +endif + +# macOS specifics. +ifeq ($(KBUILD_TARGET),darwin) + SUPR3HardenedStatic_DEFS += \ + LOG_DISABLED + + SUPR3HardenedStatic_INCS += $(VBOX_PATH_RUNTIME_SRC)/include + + SUPR3HardenedStatic_SOURCES += \ + darwin/SUPR3HardenedMain-darwin.cpp \ + \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg1Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2WeakV.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/RTAssertShouldPanic-generic.cpp +endif + +# Things specific to the rest of the posix crowd. +if1of ($(KBUILD_TARGET), linux solaris) + SUPR3HardenedStatic_DEFS += \ + IN_DIS \ + IN_DIS_STATIC \ + DIS_CORE_ONLY \ + LOG_DISABLED + SUPR3HardenedStatic_DEFS.linux += \ + SUP_HARDENED_WITH_DLMOPEN + SUPR3HardenedStatic_DEFS.solaris += \ + SUP_HARDENED_WITH_DLMOPEN + SUPR3HardenedStatic_DEFS.asan += SUP_HARDENED_WITHOUT_DLOPEN_PATCHING + + SUPR3HardenedStatic_INCS += $(VBOX_PATH_RUNTIME_SRC)/include + + SUPR3HardenedStatic_SOURCES += \ + posix/SUPR3HardenedMain-posix.cpp \ + posix/SUPR3HardenedMainA-posix.asm \ + \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg1Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2Weak.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/common/misc/RTAssertMsg2WeakV.cpp \ + $(VBOX_PATH_RUNTIME_SRC)/generic/RTAssertShouldPanic-generic.cpp \ + \ + ../../Disassembler/DisasmCore.cpp \ + ../../Disassembler/DisasmTables.cpp \ + ../../Disassembler/DisasmTablesX64.cpp \ + ../../Disassembler/DisasmMisc.cpp +endif + +SUPR3HardenedMain.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + + +# +# VBoxSupLib - Windows DLL for catching thread creation and termination. +# +VBoxSupLib_TEMPLATE = VBoxR3StaticNoCrt +VBoxSupLib_SDKS.win = VBoxNtDll +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_SOURCES.win.amd64 = \ + $(VBOX_PATH_RUNTIME_SRC)/common/compiler/vcc/stack-probe-vcc.asm +VBoxSupLib_SOURCES.win.x86 = \ + $(VBOX_PATH_RUNTIME_SRC)/common/compiler/vcc/stack-probe-vcc.asm +VBoxSupLib_SOURCES.win = \ + win/VBoxSupLib.rc +ifndef VBOX_WITH_NOCRT_STATIC + ifdef VBOX_WITH_HARDENING # for /guard:cf stuff + VBoxSupLib_LIBS.win.x86 = \ + $(PATH_TOOL_$(TEMPLATE_VBoxR3StaticNoCrt_TOOL.win.x86)_LIB)/libcmt.lib + VBoxSupLib_LIBS.win.amd64 = \ + $(PATH_TOOL_$(TEMPLATE_VBoxR3StaticNoCrt_TOOL.win.amd64)_LIB)/libcmt.lib + endif +endif +VBoxSupLib_VBOX_IMPORT_CHECKER.win.x86 = xp +VBoxSupLib_VBOX_IMPORT_CHECKER.win.amd64 = xp64 + + +# +# VBoxSupSvc - The system wide service/daemon. +# +VBoxSupSvc_TEMPLATE = VBoxR3Exe +VBoxSupSvc_SOURCES = \ + SUPSvc.cpp \ + SUPSvcGlobal.cpp \ + $(KBUILD_TARGET)/SUPSvc-$(KBUILD_TARGET).cpp +if1of ($(KBUILD_TARGET), win) + VBoxSupSvc_SOURCES += \ + SUPSvcGrant.cpp +endif +ifn1of ($(KBUILD_TARGET), win) + VBoxSupSvc_SOURCES += \ + SUPSvcMain-posix.cpp +endif +VBoxSupSvc_LIBS = \ + $(LIB_RUNTIME) + + +# +# SUPR0 - The Ring-0 Import library. +# +SUPR0_TEMPLATE = VBoxR0 +if1of ($(VBOX_LDR_FMT), pe lx) + SUPR0_SOURCES = $(SUPR0_0_OUTDIR)/SUPR0.def + SUPR0_CLEAN = $(SUPR0_0_OUTDIR)/SUPR0.def + $$(SUPR0_0_OUTDIR)/SUPR0.def: \ + $(PATH_SUB_CURRENT)/SUPDrv.cpp \ + $(PATH_SUB_CURRENT)/SUPR0-def-$(VBOX_LDR_FMT).sed \ + | $$(dir $$@) + $(SED) \ + -f $(dir $<)/SUPR0-def-$(VBOX_LDR_FMT).sed \ + --output $@ \ + $< + # Experiment: Let's see how blunt the ones messing our NULL_THUNK_DATA entries on W10 are. + ifeq ($(KBUILD_TARGET),win) + ifdef KLIBTWEAKER_EXT + SUPR0_POST_CMDS = $(KLIBTWEAKER_EXT) --clear-timestamps --fill-null_thunk_data $(out) + endif + endif +endif + + +# +# SUPRC - The raw-mode context library. +# +SUPRC_TEMPLATE := VBoxRc +SUPRC_DEFS := IN_SUP_RC IN_RT_RC IN_VMM_RC +SUPRC_SOURCES := SUPLibAll.cpp + + +# +# SUPR0IdcClient - The Ring-0 IDC client driver library. +# +SUPR0IdcClient_TEMPLATE = VBoxR0DrvLib +SUPR0IdcClient_DEFS = IN_RT_R0 IN_SUP_R0 IN_SUP_STATIC +SUPR0IdcClient_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK_INCS) +SUPR0IdcClient_SOURCES.$(KBUILD_TARGET) = \ + $(KBUILD_TARGET)/SUPR0IdcClient-$(KBUILD_TARGET).c +SUPR0IdcClient_SOURCES = \ + SUPR0IdcClient.c \ + SUPR0IdcClientComponent.c \ + SUPR0IdcClientStubs.c + + + +if !defined(VBOX_ONLY_DOCS) \ + && !defined(VBOX_ONLY_EXTPACKS) \ + && !defined(VBOX_ONLY_VALIDATIONKIT) + + ifeq ($(KBUILD_TARGET),os2) + + # + # VBoxDrv.sys - The OS/2 driver. + # + VBoxDrv_TEMPLATE = VBoxR0Drv + VBoxDrv_DEFS = IN_RT_R0 IN_SUP_R0 + VBoxDrv_INCS := $(PATH_SUB_CURRENT) + #VBoxDrv_LDFLAGS = -s -t -v + VBoxDrv_SOURCES = \ + os2/SUPDrvA-os2.asm \ + os2/SUPDrv-os2.def + VBoxDrv_LIBS = \ + $(VBoxDrvLib_1_TARGET) \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(VBOX_GCC_LIBGCC) \ + end + + # temp hack to ensure that SUPDrvA-os2.asm is first in the link. + LIBRARIES += VBoxDrvLib + VBoxDrvLib_TEMPLATE = VBoxR0Drv + VBoxDrvLib_INSTTYPE = none + VBoxDrvLib_DEFS = IN_RT_R0 IN_SUP_R0 + VBoxDrvLib_INCS := \ + . \ + $(PATH_ROOT)/src/VBox/Runtime/include + VBoxDrvLib_SOURCES = \ + os2/SUPDrv-os2.cpp \ + SUPDrv.cpp \ + SUPDrvGip.cpp \ + SUPDrvSem.cpp \ + SUPLibAll.cpp + + endif # os2 + + + # + # New VBoxDrv target. TODO: Convert all the above to use this! + # + if1of ($(KBUILD_TARGET), darwin freebsd solaris win) + ifdef VBOX_WITH_VBOXDRV + SYSMODS += VBoxDrv + endif + VBoxDrv_TEMPLATE = VBoxR0Drv + VBoxDrv_NAME.freebsd = vboxdrv + VBoxDrv_NAME.solaris = vboxdrv + VBoxDrv_NAME.win = VBoxSup + ifdef VBOX_SIGNING_MODE + VBoxDrv_INSTTYPE.win = none + VBoxDrv_DEBUG_INSTTYPE.win = both + endif + VBoxDrv_INST.darwin = $(INST_VBOXDRV)Contents/MacOS/ + VBoxDrv_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXDRV)) + VBoxDrv_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK_INCS) + + 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 + ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + VBoxDrv_DEFS += VBOX_PERMIT_VISUAL_STUDIO_PROFILING + endif + VBoxDrv_DEFS += VBOX_PERMIT_MORE VBOX_PERMIT_EVEN_MORE + #VBoxDrv_DEFS.debug += DEBUG_DARWIN_GIP + VBoxDrv_DEFS.darwin := VBOX_WITH_HOST_VMX + ifdef VBOX_WITH_RAW_MODE + VBoxDrv_DEFS.darwin += VBOX_WITH_RAW_MODE + endif + if defined(VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION) && defined(VBOX_SIGNING_MODE) + VBoxDrv_DEFS.darwin += VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + ifeq ($(VBOX_SIGNING_MODE),test) + VBoxDrv_DEFS.darwin += VBOX_WITH_DARWIN_R0_TEST_SIGN + endif + endif + ifdef VBOX_WITH_NETFLT + VBoxDrv_DEFS.solaris += VBOX_WITH_NETFLT + endif + ifdef VBOX_WITH_NATIVE_SOLARIS_LOADING + VBoxDrv_DEFS.solaris += VBOX_WITH_NATIVE_SOLARIS_LOADING + endif + 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 + ifdef VBOX_WITH_HARDENING + VBoxDrv_ASDEFS += VBOX_WITH_HARDENING + endif + ifdef VBOX_WITH_RAM_IN_KERNEL + VBoxDrv_DEFS += VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV IPRT_WITHOUT_EFLAGS_AC_PRESERVING + else if ($(VBOX_VERSION_BUILD) % 2) == 1 + VBoxDrv_DEFS += VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV IPRT_WITH_EFLAGS_AC_PRESERVING + endif + + VBoxDrv_INCS = . $(VBoxDrv_0_OUTDIR) + VBoxDrv_INCS.darwin = ./darwin + + VBoxDrv_LIBS = $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxDrv_LIBS.win = \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib + ifn1of ($(VBOX_WINDDK), WINDDK80 WINDDK71) + VBoxDrv_LIBS.win.x86 = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/BufferOverflowK.lib + endif + + #VBoxDrv_LDFLAGS.darwin = -Wl,-sectcreate,__TEXT,__info_plist,$(VBoxDrv.kext_0_OUTDIR)/Info.plist + #VBoxDrv_LDFLAGS.darwin = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded + VBoxDrv_LDFLAGS.solaris += -N misc/ctf + ifdef VBOX_WITH_NATIVE_DTRACE + VBoxDrv_LDFLAGS.solaris += -N drv/dtrace + endif + VBoxDrv_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxDrv_LDFLAGS.win.amd64 = -Entry:DriverEntry + + VBoxDrv_SOURCES.darwin = \ + darwin/SUPDrv-darwin.cpp + ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + VBoxDrv_SOURCES.darwin += \ + $(VBOX_SUP_WIN_CERTS_FILE) + endif + VBoxDrv_SOURCES.solaris = \ + solaris/SUPDrv-solaris.c + VBoxDrv_SOURCES.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.cpp \ + SUPDrvGip.cpp \ + SUPDrvSem.cpp \ + SUPDrvTracer.cpp \ + SUPLibAll.cpp + ifdef VBOX_WITH_NATIVE_DTRACE + VBoxDrv_SOURCES += \ + SUPDrv-dtrace.cpp + SUPDrv-dtrace.cpp_DEFS.darwin += VBOX_PATH_MACOSX_DTRACE_H=\"$(VBOX_PATH_MACOSX_SDK)/usr/include/sys/dtrace.h\" + endif + ifn1of ($(KBUILD_TARGET), linux freebsd) + VBoxDrv_SOURCES += \ + SUPDrvTracerA.asm + endif + + linux/SUPDrv-linux.c_DEPS = $(VBOX_SVN_REV_HEADER) + + endif + + + + if defined(VBOX_WITH_VBOXDRV) && "$(KBUILD_TARGET)" == "darwin" + # Files necessary to make a darwin kernel extension bundle. + INSTALLS.darwin += VBoxDrv.kext + VBoxDrv.kext_INST = $(INST_VBOXDRV)Contents/ + VBoxDrv.kext_SOURCES = $(VBoxDrv.kext_0_OUTDIR)/Contents/Info.plist + VBoxDrv.kext_CLEAN = $(VBoxDrv.kext_0_OUTDIR)/Contents/Info.plist + VBoxDrv.kext_BLDDIRS = $(VBoxDrv.kext_0_OUTDIR)/Contents/ + + $$(VBoxDrv.kext_0_OUTDIR)/Contents/Info.plist: \ + $(PATH_SUB_CURRENT)/darwin/Info.plist \ + $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxDrv,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's+@VBOX_VERSION_STRING@+$(if !defined(VBOX_MAVERICS_CODE_SIGNING_HACK),$(VBOX_VERSION_STRING),4.2.51)+g' \ + -e 's+@VBOX_VERSION_MAJOR@+$(if !defined(VBOX_MAVERICS_CODE_SIGNING_HACK),$(VBOX_VERSION_MAJOR),4)+g' \ + -e 's+@VBOX_VERSION_MINOR@+$(if !defined(VBOX_MAVERICS_CODE_SIGNING_HACK),$(VBOX_VERSION_MINOR),2)+g' \ + -e 's+@VBOX_VERSION_BUILD@+$(if !defined(VBOX_MAVERICS_CODE_SIGNING_HACK),$(VBOX_VERSION_BUILD),51)+g' \ + -e 's+@VBOX_VENDOR@+$(VBOX_VENDOR)+g' \ + -e 's+@VBOX_PRODUCT@+$(VBOX_PRODUCT)+g' \ + -e 's+@VBOX_C_YEAR@+$(VBOX_C_YEAR)+g' \ + --output $@ \ + $< + + $(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxDrv) + endif + + + if1of ($(KBUILD_TARGET), darwin solaris) + ifdef VBOX_WITH_VBOXDRV + # Common manual loader script. + INSTALLS += SUPDrvScripts + SUPDrvScripts_INST = $(INST_DIST) + SUPDrvScripts_EXEC_SOURCES = \ + $(KBUILD_TARGET)/load.sh + endif + endif + + + if1of ($(KBUILD_TARGET), linux freebsd) + if1of ($(KBUILD_TARGET_ARCH), $(VBOX_SUPPORTED_HOST_ARCHS)) + # + # Targets for installing the linux sources. + # + vboxdrv-src_INST = bin/src/vboxdrv/ + vboxdrv-src_SOURCES = \ + $(subst $(DQUOTE),,$(FILES_VBOXDRV_NOBIN)) \ + $(vboxdrv-src_0_OUTDIR)/Makefile + vboxdrv-src_EXEC_SOURCES = \ + $(subst $(DQUOTE),,$(FILES_VBOXDRV_BIN)) + vboxdrv-src_CLEAN = \ + $(vboxdrv-src_0_OUTDIR)/Makefile \ + $(PATH_TARGET)/vboxdrv-src-1.dep + + # Scripts needed for building the kernel modules + includedep $(PATH_TARGET)/vboxdrv-src-1.dep + $$(vboxdrv-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/$(KBUILD_TARGET)/Makefile \ + $$(if $$(eq $$(Support/$(KBUILD_TARGET)/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + $$(if $$(eq $$(Support/$(KBUILD_TARGET)/Makefile_VBOX_RAM_IN_KERNEL),$$(VBOX_WITH_RAM_IN_KERNEL)),,FORCE) \ + | $$(dir $$@) + $(call MSG_TOOL,Creating,,$@) + $(QUIET)$(SED) -e "" \ + $(if-expr !defined(VBOX_WITH_HARDENING),-e "s;VBOX_WITH_HARDENING;;g",) \ + --output $@ $< + %$(QUIET2)$(APPEND) -t -n '$(PATH_TARGET)/vboxdrv-src-1.dep' \ + 'Support/$(KBUILD_TARGET)/Makefile_VBOX_HARDENED=$(VBOX_WITH_HARDENING)' \ + 'Support/$(KBUILD_TARGET)/Makefile_VBOX_RAM_IN_KERNEL=$(VBOX_WITH_RAM_IN_KERNEL)' + + # + # Build test for the linux host kernel modules. + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,vboxdrv-src,,save_symvers) + + ifdef VBOX_WITH_KMOD_WRAPPED_R0_MODS + # + # Common wrapper module files. + # + INSTALLS.linux += vboxwrappermod-common-src + vboxwrappermod-common-src_INST = bin/src/common/ + vboxwrappermod-common-src_SOURCES = \ + linux/SUPWrapperMod-linux.c=>SUPWrapperMod-linux.c \ + linux/Makefile-wrapper.gmk=>Makefile-wrapper.gmk \ + $(PATH_ROOT)/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \ + $(PATH_ROOT)/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk + endif + + endif # supported host arch + endif # linux freebsd + + + ifeq ($(KBUILD_TARGET),win) + INSTALLS.win += VBoxSup-inf + VBoxSup-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxSup-inf_SOURCES = \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.inf + VBoxSup-inf_CLEAN = $(VBoxSup-inf_SOURCES) + VBoxSup-inf_BLDDIRS = $(PATH_TARGET)/VBoxSupCat.dir + + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.inf: $(PATH_SUB_CURRENT)/win/VBoxSup.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxSup-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxSup-inf_SOURCES += \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.sys \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.cat \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.cat=>VBoxSup-PreW10.cat + + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.sys: $$(VBoxDrv_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 -- "$<" "$(@D)" + + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.cat: \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.inf \ + $(PATH_TARGET)/VBoxSupCat.dir/VBoxSup.sys + $(call MSG_TOOL,Inf2Cat,VBoxSup-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + endif # signing + endif # win + + # + # Linux only. + # + PROGRAMS.linux += LnxPerfHack + LnxPerfHack_TEMPLATE = VBoxR3Tool + LnxPerfHack_SOURCES = linux/LnxPerfHack.cpp + + + +endif # !VBOX_ONLY_DOCS && !VBOX_ONLY_EXTPACKS && !VBOX_ONLY_VALIDATIONKIT +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp b/src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp new file mode 100644 index 00000000..e33f244a --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp @@ -0,0 +1,1201 @@ +/* $Id: SUPDrv-dtrace.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - DTrace Provider. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "SUPDrvInternal.h" + +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/VBoxTpG.h> + +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/mem.h> +#include <iprt/errno.h> +#ifdef RT_OS_DARWIN +# include <iprt/dbg.h> +#endif + +#ifdef RT_OS_DARWIN +# include VBOX_PATH_MACOSX_DTRACE_H +#elif defined(RT_OS_LINUX) +/* Avoid type and define conflicts. */ +# undef UINT8_MAX +# undef UINT16_MAX +# undef UINT32_MAX +# undef UINT64_MAX +# undef INT64_MAX +# undef INT64_MIN +# define intptr_t dtrace_intptr_t + +# if 0 +/* DTrace experiments with the Unbreakable Enterprise Kernel (UEK2) + (Oracle Linux). + 1. The dtrace.h here is from the dtrace module source, not + /usr/include/sys/dtrace.h nor /usr/include/dtrace.h. + 2. To generate the missing entries for the dtrace module in Module.symvers + of UEK: + nm /lib/modules/....../kernel/drivers/dtrace/dtrace.ko \ + | grep _crc_ \ + | sed -e 's/^......../0x/' -e 's/ A __crc_/\t/' \ + -e 's/$/\tdrivers\/dtrace\/dtrace\tEXPORT_SYMBOL/' \ + >> Module.symvers + Update: Althernative workaround (active), resolve symbols dynamically. + 3. No tracepoints in vboxdrv, vboxnet* or vboxpci yet. This requires yasm + and VBoxTpG and build time. */ +# include "dtrace.h" +# else +/* DTrace experiments with the Unbreakable Enterprise Kernel (UEKR3) + (Oracle Linux). + 1. To generate the missing entries for the dtrace module in Module.symvers + of UEK: + nm /lib/modules/....../kernel/drivers/dtrace/dtrace.ko \ + | grep _crc_ \ + | sed -e 's/^......../0x/' -e 's/ A __crc_/\t/' \ + -e 's/$/\tdrivers\/dtrace\/dtrace\tEXPORT_SYMBOL/' \ + >> Module.symvers + Update: Althernative workaround (active), resolve symbols dynamically. + 2. No tracepoints in vboxdrv, vboxnet* or vboxpci yet. This requires yasm + and VBoxTpG and build time. */ +# include <dtrace/provider.h> +# include <dtrace/enabling.h> /* Missing from provider.h. */ +# include <dtrace/arg.h> /* Missing from provider.h. */ +# endif +# include <linux/module.h> +/** Status code fixer (UEK uses linux convension unlike the others). */ +# define FIX_UEK_RC(a_rc) (-(a_rc)) +#else +# include <sys/dtrace.h> +# undef u +#endif + + +/** + * The UEK DTrace port is trying to be smart and seems to have turned all + * errno return codes negative. While this conforms to the linux kernel way of + * doing things, it breaks with the way the interfaces work on Solaris and + * Mac OS X. + */ +#ifndef FIX_UEK_RC +# define FIX_UEK_RC(a_rc) (a_rc) +#endif + + +/** @name Macros for preserving EFLAGS.AC (despair / paranoid) + * @remarks We have to restore it unconditionally on darwin. + * @{ */ +#if defined(RT_OS_DARWIN) \ + || ( defined(RT_OS_LINUX) \ + && (defined(CONFIG_X86_SMAP) || defined(RT_STRICT) || defined(IPRT_WITH_EFLAGS_AC_PRESERVING) ) ) +# include <iprt/asm-amd64-x86.h> +# include <iprt/x86.h> +# define SUPDRV_SAVE_EFL_AC() RTCCUINTREG const fSavedEfl = ASMGetFlags(); +# define SUPDRV_RESTORE_EFL_AC() ASMSetFlags(fSavedEfl) +# define SUPDRV_RESTORE_EFL_ONLY_AC() ASMChangeFlags(~X86_EFL_AC, fSavedEfl & X86_EFL_AC) +#else +# define SUPDRV_SAVE_EFL_AC() do { } while (0) +# define SUPDRV_RESTORE_EFL_AC() do { } while (0) +# define SUPDRV_RESTORE_EFL_ONLY_AC() do { } while (0) +#endif +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/* Seems there is some return code difference here. Keep the return code and + case it to whatever the host desires. */ +#ifdef RT_OS_DARWIN +# if MAC_OS_X_VERSION_MIN_REQUIRED < 1070 +typedef void FNPOPS_ENABLE(void *, dtrace_id_t, void *); +# else +typedef int FNPOPS_ENABLE(void *, dtrace_id_t, void *); +# endif +#else +# if !defined(RT_OS_SOLARIS) || defined(DOF_SEC_ISLOADABLE) /* DOF_SEC_ISLOADABLE was added in the next commit after dtps_enable change signature, but it's the simplest check we can do. */ +typedef int FNPOPS_ENABLE(void *, dtrace_id_t, void *); +# else +typedef void FNPOPS_ENABLE(void *, dtrace_id_t, void *); +# endif +#endif + +/** Caller indicator. */ +typedef enum VBOXDTCALLER +{ + kVBoxDtCaller_Invalid = 0, + kVBoxDtCaller_Generic, + kVBoxDtCaller_ProbeFireUser, + kVBoxDtCaller_ProbeFireKernel +} VBOXDTCALLER; + + +/** + * Stack data planted before calling dtrace_probe so that we can easily find the + * stack argument later. + */ +typedef struct VBDTSTACKDATA +{ + /** Eyecatcher no. 1 (SUPDRVDT_STACK_DATA_MAGIC2). */ + uint32_t u32Magic1; + /** Eyecatcher no. 2 (SUPDRVDT_STACK_DATA_MAGIC2). */ + uint32_t u32Magic2; + /** The format of the caller specific data. */ + VBOXDTCALLER enmCaller; + /** Caller specific data. */ + union + { + /** kVBoxDtCaller_ProbeFireKernel. */ + struct + { + /** Pointer to the stack arguments of a probe function call. */ + uintptr_t *pauStackArgs; + } ProbeFireKernel; + /** kVBoxDtCaller_ProbeFireUser. */ + struct + { + /** The user context. */ + PCSUPDRVTRACERUSRCTX pCtx; + /** The argument displacement caused by 64-bit arguments passed directly to + * dtrace_probe. */ + int offArg; + } ProbeFireUser; + } u; + /** Pointer to this structure. + * This is the final bit of integrity checking. */ + struct VBDTSTACKDATA *pSelf; +} VBDTSTACKDATA; +/** Pointer to the on-stack thread specific data. */ +typedef VBDTSTACKDATA *PVBDTSTACKDATA; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The first magic value. */ +#define SUPDRVDT_STACK_DATA_MAGIC1 RT_MAKE_U32_FROM_U8('S', 'U', 'P', 'D') +/** The second magic value. */ +#define SUPDRVDT_STACK_DATA_MAGIC2 RT_MAKE_U32_FROM_U8('D', 'T', 'r', 'c') + +/** The alignment of the stack data. + * The data doesn't require more than sizeof(uintptr_t) alignment, but the + * greater alignment the quicker lookup. */ +#define SUPDRVDT_STACK_DATA_ALIGN 32 + +/** Plants the stack data. */ +#define VBDT_SETUP_STACK_DATA(a_enmCaller) \ + uint8_t abBlob[sizeof(VBDTSTACKDATA) + SUPDRVDT_STACK_DATA_ALIGN - 1]; \ + PVBDTSTACKDATA pStackData = (PVBDTSTACKDATA)( (uintptr_t)&abBlob[SUPDRVDT_STACK_DATA_ALIGN - 1] \ + & ~(uintptr_t)(SUPDRVDT_STACK_DATA_ALIGN - 1)); \ + pStackData->u32Magic1 = SUPDRVDT_STACK_DATA_MAGIC1; \ + pStackData->u32Magic2 = SUPDRVDT_STACK_DATA_MAGIC2; \ + pStackData->enmCaller = a_enmCaller; \ + pStackData->pSelf = pStackData + +/** Passifies the stack data and frees up resource held within it. */ +#define VBDT_CLEAR_STACK_DATA() \ + do \ + { \ + pStackData->u32Magic1 = 0; \ + pStackData->u32Magic2 = 0; \ + pStackData->pSelf = NULL; \ + } while (0) + +/** Simple SUPR0Printf-style logging. */ +#if 0 /*def DEBUG_bird*/ +# define LOG_DTRACE(a) SUPR0Printf a +#else +# define LOG_DTRACE(a) do { } while (0) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/** @name DTrace kernel interface used on Darwin and Linux. + * @{ */ +static DECLCALLBACKPTR_EX(void, RT_NOTHING, g_pfnDTraceProbeFire,(dtrace_id_t, uint64_t, uint64_t, uint64_t, uint64_t, + uint64_t)); +static DECLCALLBACKPTR_EX(dtrace_id_t, RT_NOTHING, g_pfnDTraceProbeCreate,(dtrace_provider_id_t, const char *, const char *, + const char *, int, void *)); +static DECLCALLBACKPTR_EX(dtrace_id_t, RT_NOTHING, g_pfnDTraceProbeLookup,(dtrace_provider_id_t, const char *, const char *, + const char *)); +static DECLCALLBACKPTR_EX(int, RT_NOTHING, g_pfnDTraceProviderRegister,(const char *, const dtrace_pattr_t *, uint32_t, + /*cred_t*/ void *, const dtrace_pops_t *, + void *, dtrace_provider_id_t *)); +static DECLCALLBACKPTR_EX(void, RT_NOTHING, g_pfnDTraceProviderInvalidate,(dtrace_provider_id_t)); +static DECLCALLBACKPTR_EX(int, RT_NOTHING, g_pfnDTraceProviderUnregister,(dtrace_provider_id_t)); + +#define dtrace_probe g_pfnDTraceProbeFire +#define dtrace_probe_create g_pfnDTraceProbeCreate +#define dtrace_probe_lookup g_pfnDTraceProbeLookup +#define dtrace_register g_pfnDTraceProviderRegister +#define dtrace_invalidate g_pfnDTraceProviderInvalidate +#define dtrace_unregister g_pfnDTraceProviderUnregister + +/** For dynamical resolving and releasing. */ +static const struct +{ + const char *pszName; + uintptr_t *ppfn; /**< @note Clang 11 nothrow weirdness forced this from PFNRT * to uintptr_t *. */ +} g_aDTraceFunctions[] = +{ + { "dtrace_probe", (uintptr_t *)&dtrace_probe }, + { "dtrace_probe_create", (uintptr_t *)&dtrace_probe_create }, + { "dtrace_probe_lookup", (uintptr_t *)&dtrace_probe_lookup }, + { "dtrace_register", (uintptr_t *)&dtrace_register }, + { "dtrace_invalidate", (uintptr_t *)&dtrace_invalidate }, + { "dtrace_unregister", (uintptr_t *)&dtrace_unregister }, +}; + +/** @} */ +#endif + + +/** + * Gets the stack data. + * + * @returns Pointer to the stack data. Never NULL. + */ +static PVBDTSTACKDATA vboxDtGetStackData(void) +{ + /* + * Locate the caller of probe_dtrace. + */ + int volatile iDummy = 1; /* use this to get the stack address. */ + PVBDTSTACKDATA pData = (PVBDTSTACKDATA)( ((uintptr_t)&iDummy + SUPDRVDT_STACK_DATA_ALIGN - 1) + & ~(uintptr_t)(SUPDRVDT_STACK_DATA_ALIGN - 1)); + for (;;) + { + if ( pData->u32Magic1 == SUPDRVDT_STACK_DATA_MAGIC1 + && pData->u32Magic2 == SUPDRVDT_STACK_DATA_MAGIC2 + && pData->pSelf == pData) + return pData; + pData = (PVBDTSTACKDATA)((uintptr_t)pData + SUPDRVDT_STACK_DATA_ALIGN); + } +} + + +/* + * + * Helpers for handling VTG structures. + * Helpers for handling VTG structures. + * Helpers for handling VTG structures. + * + */ + + + +/** + * Converts an attribute from VTG description speak to DTrace. + * + * @param pDtAttr The DTrace attribute (dst). + * @param pVtgAttr The VTG attribute descriptor (src). + */ +static void vboxDtVtgConvAttr(dtrace_attribute_t *pDtAttr, PCVTGDESCATTR pVtgAttr) +{ + pDtAttr->dtat_name = pVtgAttr->u8Code - 1; + pDtAttr->dtat_data = pVtgAttr->u8Data - 1; + pDtAttr->dtat_class = pVtgAttr->u8DataDep - 1; +} + +/** + * Gets a string from the string table. + * + * @returns Pointer to the string. + * @param pVtgHdr The VTG object header. + * @param offStrTab The string table offset. + */ +static const char *vboxDtVtgGetString(PVTGOBJHDR pVtgHdr, uint32_t offStrTab) +{ + Assert(offStrTab < pVtgHdr->cbStrTab); + return (const char *)pVtgHdr + pVtgHdr->offStrTab + offStrTab; +} + + + +/* + * + * DTrace Provider Interface. + * DTrace Provider Interface. + * DTrace Provider Interface. + * + */ + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_provide} + */ +static void vboxDtPOps_Provide(void *pvProv, const dtrace_probedesc_t *pDtProbeDesc) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + AssertPtrReturnVoid(pProv); + LOG_DTRACE(("%s: %p / %p pDtProbeDesc=%p\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, pDtProbeDesc)); + + if (pDtProbeDesc) + return; /* We don't generate probes, so never mind these requests. */ + + if (pProv->TracerData.DTrace.fZombie) + return; + + dtrace_provider_id_t const idProvider = pProv->TracerData.DTrace.idProvider; + AssertPtrReturnVoid(idProvider); + + AssertPtrReturnVoid(pProv->pHdr); + AssertReturnVoid(pProv->pHdr->offProbeLocs != 0); + uint32_t const cProbeLocs = pProv->pHdr->cbProbeLocs / sizeof(VTGPROBELOC); + + /* Need a buffer for extracting the function names and mangling them in + case of collision. */ + size_t const cbFnNmBuf = _4K + _1K; + char *pszFnNmBuf = (char *)RTMemAlloc(cbFnNmBuf); + if (!pszFnNmBuf) + return; + + /* + * Itereate the probe location list and register all probes related to + * this provider. + */ + uint16_t const idxProv = (uint16_t)((PVTGDESCPROVIDER)((uintptr_t)pProv->pHdr + pProv->pHdr->offProviders) - pProv->pDesc); + uint32_t idxProbeLoc; + for (idxProbeLoc = 0; idxProbeLoc < cProbeLocs; idxProbeLoc++) + { + /* Skip probe location belonging to other providers or once that + we've already reported. */ + PCVTGPROBELOC pProbeLocRO = &pProv->paProbeLocsRO[idxProbeLoc]; + PVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + if (pProbeDesc->idxProvider != idxProv) + continue; + + uint32_t *pidProbe; + if (!pProv->fUmod) + pidProbe = (uint32_t *)&pProbeLocRO->idProbe; + else + pidProbe = &pProv->paR0ProbeLocs[idxProbeLoc].idProbe; + if (*pidProbe != 0) + continue; + + /* The function name may need to be stripped since we're using C++ + compilers for most of the code. ASSUMES nobody are brave/stupid + enough to use function pointer returns without typedef'ing + properly them (e.g. signal). */ + const char *pszPrbName = vboxDtVtgGetString(pProv->pHdr, pProbeDesc->offName); + const char *pszFunc = pProbeLocRO->pszFunction; + const char *psz = strchr(pProbeLocRO->pszFunction, '('); + size_t cch; + if (psz) + { + /* skip blanks preceeding the parameter parenthesis. */ + while ( (uintptr_t)psz > (uintptr_t)pProbeLocRO->pszFunction + && RT_C_IS_BLANK(psz[-1])) + psz--; + + /* Find the start of the function name. */ + pszFunc = psz - 1; + while ((uintptr_t)pszFunc > (uintptr_t)pProbeLocRO->pszFunction) + { + char ch = pszFunc[-1]; + if (!RT_C_IS_ALNUM(ch) && ch != '_' && ch != ':') + break; + pszFunc--; + } + cch = psz - pszFunc; + } + else + cch = strlen(pszFunc); + RTStrCopyEx(pszFnNmBuf, cbFnNmBuf, pszFunc, cch); + + /* Look up the probe, if we have one in the same function, mangle + the function name a little to avoid having to deal with having + multiple location entries with the same probe ID. (lazy bird) */ + Assert(!*pidProbe); + if (dtrace_probe_lookup(idProvider, pProv->pszModName, pszFnNmBuf, pszPrbName) != DTRACE_IDNONE) + { + RTStrPrintf(pszFnNmBuf+cch, cbFnNmBuf - cch, "-%u", pProbeLocRO->uLine); + if (dtrace_probe_lookup(idProvider, pProv->pszModName, pszFnNmBuf, pszPrbName) != DTRACE_IDNONE) + { + unsigned iOrd = 2; + while (iOrd < 128) + { + RTStrPrintf(pszFnNmBuf+cch, cbFnNmBuf - cch, "-%u-%u", pProbeLocRO->uLine, iOrd); + if (dtrace_probe_lookup(idProvider, pProv->pszModName, pszFnNmBuf, pszPrbName) == DTRACE_IDNONE) + break; + iOrd++; + } + if (iOrd >= 128) + { + LogRel(("VBoxDrv: More than 128 duplicate probe location instances %s at line %u in function %s [%s], probe %s\n", + pProbeLocRO->uLine, pProbeLocRO->pszFunction, pszFnNmBuf, pszPrbName)); + continue; + } + } + } + + /* Create the probe. */ + AssertCompile(sizeof(*pidProbe) == sizeof(dtrace_id_t)); + *pidProbe = dtrace_probe_create(idProvider, pProv->pszModName, pszFnNmBuf, pszPrbName, + 1 /*aframes*/, (void *)(uintptr_t)idxProbeLoc); + pProv->TracerData.DTrace.cProvidedProbes++; + } + + RTMemFree(pszFnNmBuf); + LOG_DTRACE(("%s: returns\n", __FUNCTION__)); +} + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_enable} + */ +static int vboxDtPOps_Enable(void *pvProv, dtrace_id_t idProbe, void *pvProbe) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + RT_NOREF(idProbe); + LOG_DTRACE(("%s: %p / %p - %#x / %p\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, idProbe, pvProbe)); + AssertPtrReturn(pProv->TracerData.DTrace.idProvider, EINVAL); + + if (!pProv->TracerData.DTrace.fZombie) + { + uint32_t idxProbeLoc = (uint32_t)(uintptr_t)pvProbe; + PVTGPROBELOC32 pProbeLocEn = (PVTGPROBELOC32)( (uintptr_t)pProv->pvProbeLocsEn + idxProbeLoc * pProv->cbProbeLocsEn); + PCVTGPROBELOC pProbeLocRO = (PVTGPROBELOC)&pProv->paProbeLocsRO[idxProbeLoc]; + PCVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + uint32_t const idxProbe = pProbeDesc->idxEnabled; + + if (!pProv->fUmod) + { + if (!pProbeLocEn->fEnabled) + { + pProbeLocEn->fEnabled = 1; + ASMAtomicIncU32(&pProv->pacProbeEnabled[idxProbe]); + ASMAtomicIncU32(&pProv->pDesc->cProbesEnabled); + ASMAtomicIncU32(&pProv->pDesc->uSettingsSerialNo); + } + } + else + { + /* Update kernel mode structure */ + if (!pProv->paR0ProbeLocs[idxProbeLoc].fEnabled) + { + pProv->paR0ProbeLocs[idxProbeLoc].fEnabled = 1; + ASMAtomicIncU32(&pProv->paR0Probes[idxProbe].cEnabled); + ASMAtomicIncU32(&pProv->pDesc->cProbesEnabled); + ASMAtomicIncU32(&pProv->pDesc->uSettingsSerialNo); + } + + /* Update user mode structure. */ + pProbeLocEn->fEnabled = 1; + pProv->pacProbeEnabled[idxProbe] = pProv->paR0Probes[idxProbe].cEnabled; + } + } + + return 0; +} + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_disable} + */ +static void vboxDtPOps_Disable(void *pvProv, dtrace_id_t idProbe, void *pvProbe) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + AssertPtrReturnVoid(pProv); + RT_NOREF(idProbe); + LOG_DTRACE(("%s: %p / %p - %#x / %p\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, idProbe, pvProbe)); + AssertPtrReturnVoid(pProv->TracerData.DTrace.idProvider); + + if (!pProv->TracerData.DTrace.fZombie) + { + uint32_t idxProbeLoc = (uint32_t)(uintptr_t)pvProbe; + PVTGPROBELOC32 pProbeLocEn = (PVTGPROBELOC32)( (uintptr_t)pProv->pvProbeLocsEn + idxProbeLoc * pProv->cbProbeLocsEn); + PCVTGPROBELOC pProbeLocRO = (PVTGPROBELOC)&pProv->paProbeLocsRO[idxProbeLoc]; + PCVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + uint32_t const idxProbe = pProbeDesc->idxEnabled; + + if (!pProv->fUmod) + { + if (pProbeLocEn->fEnabled) + { + pProbeLocEn->fEnabled = 0; + ASMAtomicDecU32(&pProv->pacProbeEnabled[idxProbe]); + ASMAtomicIncU32(&pProv->pDesc->cProbesEnabled); + ASMAtomicIncU32(&pProv->pDesc->uSettingsSerialNo); + } + } + else + { + /* Update kernel mode structure */ + if (pProv->paR0ProbeLocs[idxProbeLoc].fEnabled) + { + pProv->paR0ProbeLocs[idxProbeLoc].fEnabled = 0; + ASMAtomicDecU32(&pProv->paR0Probes[idxProbe].cEnabled); + ASMAtomicDecU32(&pProv->pDesc->cProbesEnabled); + ASMAtomicIncU32(&pProv->pDesc->uSettingsSerialNo); + } + + /* Update user mode structure. */ + pProbeLocEn->fEnabled = 0; + pProv->pacProbeEnabled[idxProbe] = pProv->paR0Probes[idxProbe].cEnabled; + } + } +} + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_getargdesc} + */ +static void vboxDtPOps_GetArgDesc(void *pvProv, dtrace_id_t idProbe, void *pvProbe, + dtrace_argdesc_t *pArgDesc) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + unsigned uArg = pArgDesc->dtargd_ndx; + RT_NOREF(idProbe); + + pArgDesc->dtargd_ndx = DTRACE_ARGNONE; + AssertPtrReturnVoid(pProv); + LOG_DTRACE(("%s: %p / %p - %#x / %p uArg=%d\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, idProbe, pvProbe, uArg)); + AssertPtrReturnVoid(pProv->TracerData.DTrace.idProvider); + + if (!pProv->TracerData.DTrace.fZombie) + { + uint32_t idxProbeLoc = (uint32_t)(uintptr_t)pvProbe; + PCVTGPROBELOC pProbeLocRO = (PVTGPROBELOC)&pProv->paProbeLocsRO[idxProbeLoc]; + PCVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + PCVTGDESCARGLIST pArgList = (PCVTGDESCARGLIST)( (uintptr_t)pProv->pHdr + + pProv->pHdr->offArgLists + + pProbeDesc->offArgList); + AssertReturnVoid(pProbeDesc->offArgList < pProv->pHdr->cbArgLists); + + if (uArg < pArgList->cArgs) + { + const char *pszType = vboxDtVtgGetString(pProv->pHdr, pArgList->aArgs[uArg].offType); + size_t cchType = strlen(pszType); + if (cchType < sizeof(pArgDesc->dtargd_native)) + { + memcpy(pArgDesc->dtargd_native, pszType, cchType + 1); + /** @todo mapping? */ + pArgDesc->dtargd_ndx = uArg; + LOG_DTRACE(("%s: returns dtargd_native = %s\n", __FUNCTION__, pArgDesc->dtargd_native)); + return; + } + } + } +} + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_getargval} + * + * + * We just cook our own stuff here, using a stack marker for finding the + * required information. That's more reliable than subjecting oneself to the + * solaris bugs and 32-bit apple peculiarities. + * + * + * @remarks Solaris Bug + * + * dtrace_getarg on AMD64 has a different opinion about how to use the cFrames + * argument than dtrace_caller() and/or dtrace_getpcstack(), at least when the + * probe is fired by dtrace_probe() the way we do. + * + * Setting aframes to 1 when calling dtrace_probe_create gives me the right + * arguments, but the wrong 'caller'. Since I cannot do anything about + * 'caller', the only solution is this hack. + * + * Not sure why the Solaris guys hasn't seen this issue before, but maybe there + * isn't anyone using the default argument getter path for ring-0 dtrace_probe() + * calls, SDT surely isn't. + * + * @todo File a solaris bug on dtrace_probe() + dtrace_getarg(). + * + * + * @remarks 32-bit XNU (Apple) + * + * The dtrace_probe arguments are 64-bit unsigned integers instead of uintptr_t, + * so we need to make an extra call. + * + */ +static uint64_t vboxDtPOps_GetArgVal(void *pvProv, dtrace_id_t idProbe, void *pvProbe, + int iArg, int cFrames) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + AssertPtrReturn(pProv, UINT64_MAX); + RT_NOREF(idProbe, cFrames); + LOG_DTRACE(("%s: %p / %p - %#x / %p iArg=%d cFrames=%u\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, idProbe, pvProbe, iArg, cFrames)); + AssertReturn(iArg >= 5, UINT64_MAX); + if (pProv->TracerData.DTrace.fZombie) + return UINT64_MAX; + + uint32_t idxProbeLoc = (uint32_t)(uintptr_t)pvProbe; + PCVTGPROBELOC pProbeLocRO = (PVTGPROBELOC)&pProv->paProbeLocsRO[idxProbeLoc]; + PCVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + PCVTGDESCARGLIST pArgList = (PCVTGDESCARGLIST)( (uintptr_t)pProv->pHdr + + pProv->pHdr->offArgLists + + pProbeDesc->offArgList); + AssertReturn(pProbeDesc->offArgList < pProv->pHdr->cbArgLists, UINT64_MAX); + + PVBDTSTACKDATA pData = vboxDtGetStackData(); + + /* + * Get the stack data. This is a wee bit complicated on 32-bit systems + * since we want to support 64-bit integer arguments. + */ + uint64_t u64Ret; + if (iArg >= 20) + u64Ret = UINT64_MAX; + else if (pData->enmCaller == kVBoxDtCaller_ProbeFireKernel) + { +#if ARCH_BITS == 64 + u64Ret = pData->u.ProbeFireKernel.pauStackArgs[iArg - 5]; +#else + if ( !pArgList->fHaveLargeArgs + || iArg >= pArgList->cArgs) + u64Ret = pData->u.ProbeFireKernel.pauStackArgs[iArg - 5]; + else + { + /* Similar to what we did for mac in when calling dtrace_probe(). */ + uint32_t offArg = 0; + for (int i = 5; i < iArg; i++) + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iArg].fType)) + offArg++; + u64Ret = pData->u.ProbeFireKernel.pauStackArgs[iArg - 5 + offArg]; + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iArg].fType)) + u64Ret |= (uint64_t)pData->u.ProbeFireKernel.pauStackArgs[iArg - 5 + offArg + 1] << 32; + } +#endif + } + else if (pData->enmCaller == kVBoxDtCaller_ProbeFireUser) + { + int offArg = pData->u.ProbeFireUser.offArg; + PCSUPDRVTRACERUSRCTX pCtx = pData->u.ProbeFireUser.pCtx; + AssertPtrReturn(pCtx, UINT64_MAX); + + if (pCtx->cBits == 32) + { + if ( !pArgList->fHaveLargeArgs + || iArg >= pArgList->cArgs) + { + if (iArg + offArg < (int)RT_ELEMENTS(pCtx->u.X86.aArgs)) + u64Ret = pCtx->u.X86.aArgs[iArg + offArg]; + else + u64Ret = UINT64_MAX; + } + else + { + int i; + for (i = 5; i < iArg; i++) + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iArg].fType)) + offArg++; + if (offArg + iArg < (int)RT_ELEMENTS(pCtx->u.X86.aArgs)) + { + u64Ret = pCtx->u.X86.aArgs[iArg + offArg]; + if ( VTG_TYPE_IS_LARGE(pArgList->aArgs[iArg].fType) + && offArg + iArg + 1 < (int)RT_ELEMENTS(pCtx->u.X86.aArgs)) + u64Ret |= (uint64_t)pCtx->u.X86.aArgs[iArg + offArg + 1] << 32; + } + else + u64Ret = UINT64_MAX; + } + } + else + { + if (iArg + offArg < (int)RT_ELEMENTS(pCtx->u.Amd64.aArgs)) + u64Ret = pCtx->u.Amd64.aArgs[iArg + offArg]; + else + u64Ret = UINT64_MAX; + } + } + else + AssertFailedReturn(UINT64_MAX); + + LOG_DTRACE(("%s: returns %#llx\n", __FUNCTION__, u64Ret)); + return u64Ret; +} + + +/** + * @callback_method_impl{dtrace_pops_t,dtps_destroy} + */ +static void vboxDtPOps_Destroy(void *pvProv, dtrace_id_t idProbe, void *pvProbe) +{ + PSUPDRVVDTPROVIDERCORE pProv = (PSUPDRVVDTPROVIDERCORE)pvProv; + AssertPtrReturnVoid(pProv); + LOG_DTRACE(("%s: %p / %p - %#x / %p\n", __FUNCTION__, pProv, pProv->TracerData.DTrace.idProvider, idProbe, pvProbe)); + AssertReturnVoid(pProv->TracerData.DTrace.cProvidedProbes > 0); + AssertPtrReturnVoid(pProv->TracerData.DTrace.idProvider); + + if (!pProv->TracerData.DTrace.fZombie) + { + uint32_t idxProbeLoc = (uint32_t)(uintptr_t)pvProbe; + PCVTGPROBELOC pProbeLocRO = (PVTGPROBELOC)&pProv->paProbeLocsRO[idxProbeLoc]; + uint32_t *pidProbe; + if (!pProv->fUmod) + { + pidProbe = (uint32_t *)&pProbeLocRO->idProbe; + Assert(!pProbeLocRO->fEnabled); + Assert(*pidProbe == idProbe); + } + else + { + pidProbe = &pProv->paR0ProbeLocs[idxProbeLoc].idProbe; + Assert(!pProv->paR0ProbeLocs[idxProbeLoc].fEnabled); + Assert(*pidProbe == idProbe); NOREF(idProbe); + } + *pidProbe = 0; + } + pProv->TracerData.DTrace.cProvidedProbes--; +} + + + +/** + * DTrace provider method table. + */ +static const dtrace_pops_t g_vboxDtVtgProvOps = +{ + /* .dtps_provide = */ vboxDtPOps_Provide, + /* .dtps_provide_module = */ NULL, + /* .dtps_enable = */ (FNPOPS_ENABLE *)vboxDtPOps_Enable, + /* .dtps_disable = */ vboxDtPOps_Disable, + /* .dtps_suspend = */ NULL, + /* .dtps_resume = */ NULL, + /* .dtps_getargdesc = */ vboxDtPOps_GetArgDesc, + /* .dtps_getargval = */ vboxDtPOps_GetArgVal, + /* .dtps_usermode = */ NULL, + /* .dtps_destroy = */ vboxDtPOps_Destroy +}; + + + + +/* + * + * Support Driver Tracer Interface. + * Support Driver Tracer Interface. + * Support Driver Tracer Interface. + * + */ + + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnProbeFireKernel} + */ +static DECLCALLBACK(void) vboxDtTOps_ProbeFireKernel(struct VTGPROBELOC *pVtgProbeLoc, uintptr_t uArg0, uintptr_t uArg1, uintptr_t uArg2, + uintptr_t uArg3, uintptr_t uArg4) +{ + AssertPtrReturnVoid(pVtgProbeLoc); + LOG_DTRACE(("%s: %p / %p\n", __FUNCTION__, pVtgProbeLoc, pVtgProbeLoc->idProbe)); + AssertPtrReturnVoid(pVtgProbeLoc->pProbe); + AssertPtrReturnVoid(pVtgProbeLoc->pszFunction); + + SUPDRV_SAVE_EFL_AC(); + VBDT_SETUP_STACK_DATA(kVBoxDtCaller_ProbeFireKernel); + + pStackData->u.ProbeFireKernel.pauStackArgs = &uArg4 + 1; + +#if defined(RT_OS_DARWIN) && ARCH_BITS == 32 + /* + * Convert arguments from uintptr_t to uint64_t. + */ + PVTGDESCPROBE pProbe = (PVTGDESCPROBE)((PVTGPROBELOC)pVtgProbeLoc)->pProbe; + AssertPtrReturnVoid(pProbe); + PVTGOBJHDR pVtgHdr = (PVTGOBJHDR)((uintptr_t)pProbe + pProbe->offObjHdr); + AssertPtrReturnVoid(pVtgHdr); + PVTGDESCARGLIST pArgList = (PVTGDESCARGLIST)((uintptr_t)pVtgHdr + pVtgHdr->offArgLists + pProbe->offArgList); + AssertPtrReturnVoid(pArgList); + if (!pArgList->fHaveLargeArgs) + dtrace_probe(pVtgProbeLoc->idProbe, uArg0, uArg1, uArg2, uArg3, uArg4); + else + { + uintptr_t *auSrcArgs = &uArg0; + uint32_t iSrcArg = 0; + uint32_t iDstArg = 0; + uint64_t au64DstArgs[5]; + + while ( iDstArg < RT_ELEMENTS(au64DstArgs) + && iSrcArg < pArgList->cArgs) + { + au64DstArgs[iDstArg] = auSrcArgs[iSrcArg]; + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iDstArg].fType)) + au64DstArgs[iDstArg] |= (uint64_t)auSrcArgs[++iSrcArg] << 32; + iSrcArg++; + iDstArg++; + } + while (iDstArg < RT_ELEMENTS(au64DstArgs)) + au64DstArgs[iDstArg++] = auSrcArgs[iSrcArg++]; + + pStackData->u.ProbeFireKernel.pauStackArgs = &auSrcArgs[iSrcArg]; + dtrace_probe(pVtgProbeLoc->idProbe, au64DstArgs[0], au64DstArgs[1], au64DstArgs[2], au64DstArgs[3], au64DstArgs[4]); + } +#else + dtrace_probe(pVtgProbeLoc->idProbe, uArg0, uArg1, uArg2, uArg3, uArg4); +#endif + + VBDT_CLEAR_STACK_DATA(); + SUPDRV_RESTORE_EFL_AC(); + LOG_DTRACE(("%s: returns\n", __FUNCTION__)); +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnProbeFireUser} + */ +static DECLCALLBACK(void) vboxDtTOps_ProbeFireUser(PCSUPDRVTRACERREG pThis, PSUPDRVSESSION pSession, PCSUPDRVTRACERUSRCTX pCtx, + PCVTGOBJHDR pVtgHdr, PCVTGPROBELOC pProbeLocRO) +{ + RT_NOREF(pThis, pSession); + LOG_DTRACE(("%s: %p / %p\n", __FUNCTION__, pCtx, pCtx->idProbe)); + AssertPtrReturnVoid(pProbeLocRO); + AssertPtrReturnVoid(pVtgHdr); + + SUPDRV_SAVE_EFL_AC(); + VBDT_SETUP_STACK_DATA(kVBoxDtCaller_ProbeFireUser); + + if (pCtx->cBits == 32) + { + pStackData->u.ProbeFireUser.pCtx = pCtx; + pStackData->u.ProbeFireUser.offArg = 0; + +#if ARCH_BITS == 64 || defined(RT_OS_DARWIN) + /* + * Combine two 32-bit arguments into one 64-bit argument where needed. + */ + PVTGDESCPROBE pProbeDesc = pProbeLocRO->pProbe; + AssertPtrReturnVoid(pProbeDesc); + PVTGDESCARGLIST pArgList = (PVTGDESCARGLIST)((uintptr_t)pVtgHdr + pVtgHdr->offArgLists + pProbeDesc->offArgList); + AssertPtrReturnVoid(pArgList); + + if (!pArgList->fHaveLargeArgs) + dtrace_probe(pCtx->idProbe, + pCtx->u.X86.aArgs[0], + pCtx->u.X86.aArgs[1], + pCtx->u.X86.aArgs[2], + pCtx->u.X86.aArgs[3], + pCtx->u.X86.aArgs[4]); + else + { + uint32_t const *auSrcArgs = &pCtx->u.X86.aArgs[0]; + uint32_t iSrcArg = 0; + uint32_t iDstArg = 0; + uint64_t au64DstArgs[5]; + + while ( iDstArg < RT_ELEMENTS(au64DstArgs) + && iSrcArg < pArgList->cArgs) + { + au64DstArgs[iDstArg] = auSrcArgs[iSrcArg]; + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iDstArg].fType)) + au64DstArgs[iDstArg] |= (uint64_t)auSrcArgs[++iSrcArg] << 32; + iSrcArg++; + iDstArg++; + } + while (iDstArg < RT_ELEMENTS(au64DstArgs)) + au64DstArgs[iDstArg++] = auSrcArgs[iSrcArg++]; + + pStackData->u.ProbeFireUser.offArg = iSrcArg - RT_ELEMENTS(au64DstArgs); + dtrace_probe(pCtx->idProbe, au64DstArgs[0], au64DstArgs[1], au64DstArgs[2], au64DstArgs[3], au64DstArgs[4]); + } +#else + dtrace_probe(pCtx->idProbe, + pCtx->u.X86.aArgs[0], + pCtx->u.X86.aArgs[1], + pCtx->u.X86.aArgs[2], + pCtx->u.X86.aArgs[3], + pCtx->u.X86.aArgs[4]); +#endif + } + else if (pCtx->cBits == 64) + { + pStackData->u.ProbeFireUser.pCtx = pCtx; + pStackData->u.ProbeFireUser.offArg = 0; + dtrace_probe(pCtx->idProbe, + pCtx->u.Amd64.aArgs[0], + pCtx->u.Amd64.aArgs[1], + pCtx->u.Amd64.aArgs[2], + pCtx->u.Amd64.aArgs[3], + pCtx->u.Amd64.aArgs[4]); + } + else + AssertFailed(); + + VBDT_CLEAR_STACK_DATA(); + SUPDRV_RESTORE_EFL_AC(); + LOG_DTRACE(("%s: returns\n", __FUNCTION__)); +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnTracerOpen} + */ +static DECLCALLBACK(int) vboxDtTOps_TracerOpen(PCSUPDRVTRACERREG pThis, PSUPDRVSESSION pSession, uint32_t uCookie, + uintptr_t uArg, uintptr_t *puSessionData) +{ + NOREF(pThis); NOREF(pSession); NOREF(uCookie); NOREF(uArg); + *puSessionData = 0; + return VERR_NOT_SUPPORTED; +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnTracerClose} + */ +static DECLCALLBACK(int) vboxDtTOps_TracerIoCtl(PCSUPDRVTRACERREG pThis, PSUPDRVSESSION pSession, uintptr_t uSessionData, + uintptr_t uCmd, uintptr_t uArg, int32_t *piRetVal) +{ + NOREF(pThis); NOREF(pSession); NOREF(uSessionData); + NOREF(uCmd); NOREF(uArg); NOREF(piRetVal); + return VERR_NOT_SUPPORTED; +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnTracerClose} + */ +static DECLCALLBACK(void) vboxDtTOps_TracerClose(PCSUPDRVTRACERREG pThis, PSUPDRVSESSION pSession, uintptr_t uSessionData) +{ + NOREF(pThis); NOREF(pSession); NOREF(uSessionData); + return; +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnProviderRegister} + */ +static DECLCALLBACK(int) vboxDtTOps_ProviderRegister(PCSUPDRVTRACERREG pThis, PSUPDRVVDTPROVIDERCORE pCore) +{ + RT_NOREF(pThis); + LOG_DTRACE(("%s: %p %s/%s\n", __FUNCTION__, pThis, pCore->pszModName, pCore->pszName)); + AssertReturn(pCore->TracerData.DTrace.idProvider == 0, VERR_INTERNAL_ERROR_3); + + PVTGDESCPROVIDER pDesc = pCore->pDesc; + dtrace_pattr_t DtAttrs; + vboxDtVtgConvAttr(&DtAttrs.dtpa_provider, &pDesc->AttrSelf); + vboxDtVtgConvAttr(&DtAttrs.dtpa_mod, &pDesc->AttrModules); + vboxDtVtgConvAttr(&DtAttrs.dtpa_func, &pDesc->AttrFunctions); + vboxDtVtgConvAttr(&DtAttrs.dtpa_name, &pDesc->AttrNames); + vboxDtVtgConvAttr(&DtAttrs.dtpa_args, &pDesc->AttrArguments); + + /* Note! DTrace may call us back before dtrace_register returns, so we + have to point it to pCore->TracerData.DTrace.idProvider. */ + AssertCompile(sizeof(dtrace_provider_id_t) == sizeof(pCore->TracerData.DTrace.idProvider)); + SUPDRV_SAVE_EFL_AC(); + int rc = dtrace_register(pCore->pszName, + &DtAttrs, + DTRACE_PRIV_KERNEL, + NULL /* cred */, + &g_vboxDtVtgProvOps, + pCore, + &pCore->TracerData.DTrace.idProvider); + SUPDRV_RESTORE_EFL_AC(); + if (!rc) + { + LOG_DTRACE(("%s: idProvider=%p\n", __FUNCTION__, pCore->TracerData.DTrace.idProvider)); + AssertPtr(pCore->TracerData.DTrace.idProvider); + rc = VINF_SUCCESS; + } + else + { + pCore->TracerData.DTrace.idProvider = 0; + rc = RTErrConvertFromErrno(FIX_UEK_RC(rc)); + } + + LOG_DTRACE(("%s: returns %Rrc\n", __FUNCTION__, rc)); + return rc; +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnProviderDeregister} + */ +static DECLCALLBACK(int) vboxDtTOps_ProviderDeregister(PCSUPDRVTRACERREG pThis, PSUPDRVVDTPROVIDERCORE pCore) +{ + uintptr_t idProvider = pCore->TracerData.DTrace.idProvider; + RT_NOREF(pThis); + LOG_DTRACE(("%s: %p / %p\n", __FUNCTION__, pThis, idProvider)); + AssertPtrReturn(idProvider, VERR_INTERNAL_ERROR_3); + + SUPDRV_SAVE_EFL_AC(); + dtrace_invalidate(idProvider); + int rc = dtrace_unregister(idProvider); + SUPDRV_RESTORE_EFL_AC(); + if (!rc) + { + pCore->TracerData.DTrace.idProvider = 0; + rc = VINF_SUCCESS; + } + else + { + AssertMsg(FIX_UEK_RC(rc) == EBUSY, ("%d\n", rc)); + pCore->TracerData.DTrace.fZombie = true; + rc = VERR_TRY_AGAIN; + } + + LOG_DTRACE(("%s: returns %Rrc\n", __FUNCTION__, rc)); + return rc; +} + + +/** + * interface_method_impl{SUPDRVTRACERREG,pfnProviderDeregisterZombie} + */ +static DECLCALLBACK(int) vboxDtTOps_ProviderDeregisterZombie(PCSUPDRVTRACERREG pThis, PSUPDRVVDTPROVIDERCORE pCore) +{ + uintptr_t idProvider = pCore->TracerData.DTrace.idProvider; + RT_NOREF(pThis); + LOG_DTRACE(("%s: %p / %p\n", __FUNCTION__, pThis, idProvider)); + AssertPtrReturn(idProvider, VERR_INTERNAL_ERROR_3); + Assert(pCore->TracerData.DTrace.fZombie); + + SUPDRV_SAVE_EFL_AC(); + int rc = dtrace_unregister(idProvider); + SUPDRV_RESTORE_EFL_AC(); + if (!rc) + { + pCore->TracerData.DTrace.idProvider = 0; + rc = VINF_SUCCESS; + } + else + { + AssertMsg(FIX_UEK_RC(rc) == EBUSY, ("%d\n", rc)); + rc = VERR_TRY_AGAIN; + } + + LOG_DTRACE(("%s: returns %Rrc\n", __FUNCTION__, rc)); + return rc; +} + + + +/** + * The tracer registration record of the VBox DTrace implementation + */ +static SUPDRVTRACERREG g_VBoxDTraceReg = +{ + SUPDRVTRACERREG_MAGIC, + SUPDRVTRACERREG_VERSION, + vboxDtTOps_ProbeFireKernel, + vboxDtTOps_ProbeFireUser, + vboxDtTOps_TracerOpen, + vboxDtTOps_TracerIoCtl, + vboxDtTOps_TracerClose, + vboxDtTOps_ProviderRegister, + vboxDtTOps_ProviderDeregister, + vboxDtTOps_ProviderDeregisterZombie, + SUPDRVTRACERREG_MAGIC +}; + + + +/** + * Module initialization code. + */ +const SUPDRVTRACERREG * VBOXCALL supdrvDTraceInit(void) +{ +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) + /* + * Resolve the kernel symbols we need. + */ +# ifndef RT_OS_LINUX + RTDBGKRNLINFO hKrnlInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlInfo, 0); + if (RT_FAILURE(rc)) + { + SUPR0Printf("supdrvDTraceInit: RTR0DbgKrnlInfoOpen failed with rc=%d.\n", rc); + return NULL; + } +# endif + + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aDTraceFunctions); i++) + { +# ifndef RT_OS_LINUX + rc = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, g_aDTraceFunctions[i].pszName, + (void **)g_aDTraceFunctions[i].ppfn); + if (RT_FAILURE(rc)) + { + SUPR0Printf("supdrvDTraceInit: Failed to resolved '%s' (rc=%Rrc, i=%u).\n", g_aDTraceFunctions[i].pszName, rc, i); + break; + } +# else /* RT_OS_LINUX */ + uintptr_t ulAddr = (uintptr_t)__symbol_get(g_aDTraceFunctions[i].pszName); + if (!ulAddr) + { + SUPR0Printf("supdrvDTraceInit: Failed to resolved '%s' (i=%u).\n", g_aDTraceFunctions[i].pszName, i); + while (i-- > 0) + { + __symbol_put(g_aDTraceFunctions[i].pszName); + *g_aDTraceFunctions[i].ppfn = NULL; + } + return NULL; + } + *g_aDTraceFunctions[i].ppfn = (PFNRT)ulAddr; +# endif /* RT_OS_LINUX */ + } + +# ifndef RT_OS_LINUX + RTR0DbgKrnlInfoRelease(hKrnlInfo); + if (RT_FAILURE(rc)) + return NULL; +# endif +#endif + + return &g_VBoxDTraceReg; +} + +/** + * Module teardown code. + */ +void VBOXCALL supdrvDTraceFini(void) +{ +#ifdef RT_OS_LINUX + /* Release the references. */ + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aDTraceFunctions); i++) + if (*g_aDTraceFunctions[i].ppfn) + { + __symbol_put(g_aDTraceFunctions[i].pszName); + *g_aDTraceFunctions[i].ppfn = NULL; + } +#endif +} + +#ifndef VBOX_WITH_NATIVE_DTRACE +# error "VBOX_WITH_NATIVE_DTRACE is not defined as it should" +#endif + diff --git a/src/VBox/HostDrivers/Support/SUPDrv.cpp b/src/VBox/HostDrivers/Support/SUPDrv.cpp new file mode 100644 index 00000000..59573a31 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrv.cpp @@ -0,0 +1,7204 @@ +/* $Id: SUPDrv.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Common code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#define SUPDRV_AGNOSTIC +#include "SUPDrvInternal.h" +#ifndef PAGE_SHIFT +# include <iprt/param.h> +#endif +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/asm-math.h> +#include <iprt/cpuset.h> +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) +# include <iprt/dbg.h> +#endif +#include <iprt/handletable.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/thread.h> +#include <iprt/uuid.h> +#include <iprt/net.h> +#include <iprt/crc.h> +#include <iprt/string.h> +#include <iprt/timer.h> +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include <iprt/rand.h> +# include <iprt/path.h> +#endif +#include <iprt/uint128.h> +#include <iprt/x86.h> + +#include <VBox/param.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/hm_vmx.h> + +#if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN) +# include "dtrace/SUPDrv.h" +#else +# define VBOXDRV_SESSION_CREATE(pvSession, fUser) do { } while (0) +# define VBOXDRV_SESSION_CLOSE(pvSession) do { } while (0) +# define VBOXDRV_IOCTL_ENTRY(pvSession, uIOCtl, pvReqHdr) do { } while (0) +# define VBOXDRV_IOCTL_RETURN(pvSession, uIOCtl, pvReqHdr, rcRet, rcReq) do { } while (0) +#endif + +#ifdef __cplusplus +# if __cplusplus >= 201100 || RT_MSC_PREREQ(RT_MSC_VER_VS2019) +# define SUPDRV_CAN_COUNT_FUNCTION_ARGS +# ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4577) +# include <type_traits> +# pragma warning(pop) + +# elif defined(RT_OS_DARWIN) +# define _LIBCPP_CSTDDEF +# include <__nullptr> +# include <type_traits> + +# else +# include <type_traits> +# endif +# endif +#endif + + +/* + * Logging assignments: + * Log - useful stuff, like failures. + * LogFlow - program flow, except the really noisy bits. + * Log2 - Cleanup. + * Log3 - Loader flow noise. + * Log4 - Call VMMR0 flow noise. + * Log5 - Native yet-to-be-defined noise. + * Log6 - Native ioctl flow noise. + * + * Logging requires KBUILD_TYPE=debug and possibly changes to the logger + * instantiation in log-vbox.c(pp). + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def VBOX_SVN_REV + * The makefile should define this if it can. */ +#ifndef VBOX_SVN_REV +# define VBOX_SVN_REV 0 +#endif + +/** @ SUPDRV_CHECK_SMAP_SETUP + * SMAP check setup. */ +/** @def SUPDRV_CHECK_SMAP_CHECK + * Checks that the AC flag is set if SMAP is enabled. If AC is not set, it + * will be logged and @a a_BadExpr is executed. */ +#if (defined(RT_OS_DARWIN) || defined(RT_OS_LINUX)) && !defined(VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV) +# define SUPDRV_CHECK_SMAP_SETUP() uint32_t const fKernelFeatures = SUPR0GetKernelFeatures() +# define SUPDRV_CHECK_SMAP_CHECK(a_pDevExt, a_BadExpr) \ + do { \ + if (fKernelFeatures & SUPKERNELFEATURES_SMAP) \ + { \ + RTCCUINTREG fEfl = ASMGetFlags(); \ + if (RT_LIKELY(fEfl & X86_EFL_AC)) \ + { /* likely */ } \ + else \ + { \ + supdrvBadContext(a_pDevExt, "SUPDrv.cpp", __LINE__, "EFLAGS.AC is 0!"); \ + a_BadExpr; \ + } \ + } \ + } while (0) +#else +# define SUPDRV_CHECK_SMAP_SETUP() uint32_t const fKernelFeatures = 0 +# define SUPDRV_CHECK_SMAP_CHECK(a_pDevExt, a_BadExpr) NOREF(fKernelFeatures) +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(int) supdrvSessionObjHandleRetain(RTHANDLETABLE hHandleTable, void *pvObj, void *pvCtx, void *pvUser); +static DECLCALLBACK(void) supdrvSessionObjHandleDelete(RTHANDLETABLE hHandleTable, uint32_t h, void *pvObj, void *pvCtx, void *pvUser); +static int supdrvMemAdd(PSUPDRVMEMREF pMem, PSUPDRVSESSION pSession); +static int supdrvMemRelease(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr, SUPDRVMEMREFTYPE eType); +static int supdrvIOCtl_LdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDROPEN pReq); +static int supdrvIOCtl_LdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRLOAD pReq); +static int supdrvIOCtl_LdrFree(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRFREE pReq); +static int supdrvIOCtl_LdrLockDown(PSUPDRVDEVEXT pDevExt); +static int supdrvIOCtl_LdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRGETSYMBOL pReq); +static int supdrvIDC_LdrGetSymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVIDCREQGETSYM pReq); +static int supdrvLdrAddUsage(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVLDRIMAGE pImage, bool fRing3Usage); +DECLINLINE(void) supdrvLdrSubtractUsage(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, uint32_t cReference); +static void supdrvLdrFree(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); +DECLINLINE(int) supdrvLdrLock(PSUPDRVDEVEXT pDevExt); +DECLINLINE(int) supdrvLdrUnlock(PSUPDRVDEVEXT pDevExt); +static int supdrvIOCtl_CallServiceModule(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPCALLSERVICE pReq); +static int supdrvIOCtl_LoggerSettings(PSUPLOGGERSETTINGS pReq); +static int supdrvIOCtl_MsrProber(PSUPDRVDEVEXT pDevExt, PSUPMSRPROBER pReq); +static int supdrvIOCtl_ResumeSuspendedKbds(void); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @def SUPEXP_CHECK_ARGS + * This is for checking the argument count of the function in the entry, + * just to make sure we don't accidentally export something the wrapper + * can't deal with. + * + * Using some C++11 magic to do the counting. + * + * The error is reported by overflowing the SUPFUNC::cArgs field, so the + * warnings can probably be a little mysterious. + * + * @note Doesn't work for CLANG 11. Works for Visual C++, unless there + * are function pointers in the argument list. + */ +#if defined(SUPDRV_CAN_COUNT_FUNCTION_ARGS) && RT_CLANG_PREREQ(99, 0) +template <typename RetType, typename ... Types> +constexpr std::integral_constant<unsigned, sizeof ...(Types)> +CountFunctionArguments(RetType(RTCALL *)(Types ...)) +{ + return std::integral_constant<unsigned, sizeof ...(Types)>{}; +} +# define SUPEXP_CHECK_ARGS(a_cArgs, a_Name) \ + ((a_cArgs) >= decltype(CountFunctionArguments(a_Name))::value ? (uint8_t)(a_cArgs) : 1023) + +#else +# define SUPEXP_CHECK_ARGS(a_cArgs, a_Name) a_cArgs +#endif + +/** @name Function table entry macros. + * @note The SUPEXP_STK_BACKF macro is because VC++ has trouble with functions + * with function pointer arguments (probably noexcept related). + * @{ */ +#define SUPEXP_CUSTOM(a_cArgs, a_Name, a_Value) { #a_Name, a_cArgs, (void *)(uintptr_t)(a_Value) } +#define SUPEXP_STK_OKAY(a_cArgs, a_Name) { #a_Name, SUPEXP_CHECK_ARGS(a_cArgs, a_Name), (void *)(uintptr_t)a_Name } +#if 0 +# define SUPEXP_STK_BACK(a_cArgs, a_Name) { "StkBack_" #a_Name, SUPEXP_CHECK_ARGS(a_cArgs, a_Name), (void *)(uintptr_t)a_Name } +# define SUPEXP_STK_BACKF(a_cArgs, a_Name) { "StkBack_" #a_Name, SUPEXP_CHECK_ARGS(a_cArgs, a_Name), (void *)(uintptr_t)a_Name } +#else +# define SUPEXP_STK_BACK(a_cArgs, a_Name) { #a_Name, SUPEXP_CHECK_ARGS(a_cArgs, a_Name), (void *)(uintptr_t)a_Name } +# ifdef _MSC_VER +# define SUPEXP_STK_BACKF(a_cArgs, a_Name) { #a_Name, a_cArgs, (void *)(uintptr_t)a_Name } +# else +# define SUPEXP_STK_BACKF(a_cArgs, a_Name) { #a_Name, SUPEXP_CHECK_ARGS(a_cArgs, a_Name), (void *)(uintptr_t)a_Name } +# endif +#endif +/** @} */ + +/** + * Array of the R0 SUP API. + * + * While making changes to these exports, make sure to update the IOC + * minor version (SUPDRV_IOC_VERSION). + * + * @remarks This array is processed by SUPR0-def-pe.sed and SUPR0-def-lx.sed to + * produce definition files from which import libraries are generated. + * Take care when commenting things and especially with \#ifdef'ing. + */ +static SUPFUNC g_aFunctions[] = +{ +/* SED: START */ + /* name function */ + /* Entries with absolute addresses determined at runtime, fixup + code makes ugly ASSUMPTIONS about the order here: */ + SUPEXP_CUSTOM( 0, SUPR0AbsIs64bit, 0), + SUPEXP_CUSTOM( 0, SUPR0Abs64bitKernelCS, 0), + SUPEXP_CUSTOM( 0, SUPR0Abs64bitKernelSS, 0), + SUPEXP_CUSTOM( 0, SUPR0Abs64bitKernelDS, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelCS, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelSS, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelDS, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelES, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelFS, 0), + SUPEXP_CUSTOM( 0, SUPR0AbsKernelGS, 0), + /* Normal function & data pointers: */ + SUPEXP_CUSTOM( 0, g_pSUPGlobalInfoPage, &g_pSUPGlobalInfoPage), /* SED: DATA */ + SUPEXP_STK_OKAY( 0, SUPGetGIP), + SUPEXP_STK_BACK( 1, SUPReadTscWithDelta), + SUPEXP_STK_BACK( 1, SUPGetTscDeltaSlow), + SUPEXP_STK_BACK( 1, SUPGetCpuHzFromGipForAsyncMode), + SUPEXP_STK_OKAY( 3, SUPIsTscFreqCompatible), + SUPEXP_STK_OKAY( 3, SUPIsTscFreqCompatibleEx), + SUPEXP_STK_BACK( 4, SUPR0BadContext), + SUPEXP_STK_BACK( 2, SUPR0ComponentDeregisterFactory), + SUPEXP_STK_BACK( 4, SUPR0ComponentQueryFactory), + SUPEXP_STK_BACK( 2, SUPR0ComponentRegisterFactory), + SUPEXP_STK_BACK( 5, SUPR0ContAlloc), + SUPEXP_STK_BACK( 2, SUPR0ContFree), + SUPEXP_STK_BACK( 2, SUPR0ChangeCR4), + SUPEXP_STK_BACK( 1, SUPR0EnableVTx), + SUPEXP_STK_OKAY( 1, SUPR0FpuBegin), + SUPEXP_STK_OKAY( 1, SUPR0FpuEnd), + SUPEXP_STK_BACK( 0, SUPR0SuspendVTxOnCpu), + SUPEXP_STK_BACK( 1, SUPR0ResumeVTxOnCpu), + SUPEXP_STK_OKAY( 1, SUPR0GetCurrentGdtRw), + SUPEXP_STK_OKAY( 0, SUPR0GetKernelFeatures), + SUPEXP_STK_BACK( 3, SUPR0GetHwvirtMsrs), + SUPEXP_STK_BACK( 0, SUPR0GetPagingMode), + SUPEXP_STK_BACK( 1, SUPR0GetSvmUsability), + SUPEXP_STK_BACK( 1, SUPR0GetVTSupport), + SUPEXP_STK_BACK( 1, SUPR0GetVmxUsability), + SUPEXP_STK_BACK( 2, SUPR0LdrIsLockOwnerByMod), + SUPEXP_STK_BACK( 1, SUPR0LdrLock), + SUPEXP_STK_BACK( 1, SUPR0LdrUnlock), + SUPEXP_STK_BACK( 3, SUPR0LdrModByName), + SUPEXP_STK_BACK( 2, SUPR0LdrModRelease), + SUPEXP_STK_BACK( 2, SUPR0LdrModRetain), + SUPEXP_STK_BACK( 4, SUPR0LockMem), + SUPEXP_STK_BACK( 5, SUPR0LowAlloc), + SUPEXP_STK_BACK( 2, SUPR0LowFree), + SUPEXP_STK_BACK( 4, SUPR0MemAlloc), + SUPEXP_STK_BACK( 2, SUPR0MemFree), + SUPEXP_STK_BACK( 3, SUPR0MemGetPhys), + SUPEXP_STK_BACK( 2, SUPR0ObjAddRef), + SUPEXP_STK_BACK( 3, SUPR0ObjAddRefEx), + SUPEXP_STK_BACKF( 5, SUPR0ObjRegister), + SUPEXP_STK_BACK( 2, SUPR0ObjRelease), + SUPEXP_STK_BACK( 3, SUPR0ObjVerifyAccess), + SUPEXP_STK_BACK( 6, SUPR0PageAllocEx), + SUPEXP_STK_BACK( 2, SUPR0PageFree), + SUPEXP_STK_BACK( 6, SUPR0PageMapKernel), + SUPEXP_STK_BACK( 6, SUPR0PageProtect), +#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) + SUPEXP_STK_OKAY( 2, SUPR0HCPhysToVirt), /* only-linux, only-solaris, only-freebsd */ +#endif + SUPEXP_STK_BACK( 2, SUPR0PrintfV), + SUPEXP_STK_BACK( 1, SUPR0GetSessionGVM), + SUPEXP_STK_BACK( 1, SUPR0GetSessionVM), + SUPEXP_STK_BACK( 3, SUPR0SetSessionVM), + SUPEXP_STK_BACK( 1, SUPR0GetSessionUid), + SUPEXP_STK_BACK( 6, SUPR0TscDeltaMeasureBySetIndex), + SUPEXP_STK_BACK( 1, SUPR0TracerDeregisterDrv), + SUPEXP_STK_BACK( 2, SUPR0TracerDeregisterImpl), + SUPEXP_STK_BACK( 6, SUPR0TracerFireProbe), + SUPEXP_STK_BACK( 3, SUPR0TracerRegisterDrv), + SUPEXP_STK_BACK( 4, SUPR0TracerRegisterImpl), + SUPEXP_STK_BACK( 2, SUPR0TracerRegisterModule), + SUPEXP_STK_BACK( 2, SUPR0TracerUmodProbeFire), + SUPEXP_STK_BACK( 2, SUPR0UnlockMem), +#ifdef RT_OS_WINDOWS + SUPEXP_STK_BACK( 4, SUPR0IoCtlSetupForHandle), /* only-windows */ + SUPEXP_STK_BACK( 9, SUPR0IoCtlPerform), /* only-windows */ + SUPEXP_STK_BACK( 1, SUPR0IoCtlCleanup), /* only-windows */ +#endif + SUPEXP_STK_BACK( 2, SUPSemEventClose), + SUPEXP_STK_BACK( 2, SUPSemEventCreate), + SUPEXP_STK_BACK( 1, SUPSemEventGetResolution), + SUPEXP_STK_BACK( 2, SUPSemEventMultiClose), + SUPEXP_STK_BACK( 2, SUPSemEventMultiCreate), + SUPEXP_STK_BACK( 1, SUPSemEventMultiGetResolution), + SUPEXP_STK_BACK( 2, SUPSemEventMultiReset), + SUPEXP_STK_BACK( 2, SUPSemEventMultiSignal), + SUPEXP_STK_BACK( 3, SUPSemEventMultiWait), + SUPEXP_STK_BACK( 3, SUPSemEventMultiWaitNoResume), + SUPEXP_STK_BACK( 3, SUPSemEventMultiWaitNsAbsIntr), + SUPEXP_STK_BACK( 3, SUPSemEventMultiWaitNsRelIntr), + SUPEXP_STK_BACK( 2, SUPSemEventSignal), + SUPEXP_STK_BACK( 3, SUPSemEventWait), + SUPEXP_STK_BACK( 3, SUPSemEventWaitNoResume), + SUPEXP_STK_BACK( 3, SUPSemEventWaitNsAbsIntr), + SUPEXP_STK_BACK( 3, SUPSemEventWaitNsRelIntr), + + SUPEXP_STK_BACK( 0, RTAssertAreQuiet), + SUPEXP_STK_BACK( 0, RTAssertMayPanic), + SUPEXP_STK_BACK( 4, RTAssertMsg1), + SUPEXP_STK_BACK( 2, RTAssertMsg2AddV), + SUPEXP_STK_BACK( 2, RTAssertMsg2V), + SUPEXP_STK_BACK( 1, RTAssertSetMayPanic), + SUPEXP_STK_BACK( 1, RTAssertSetQuiet), + SUPEXP_STK_OKAY( 2, RTCrc32), + SUPEXP_STK_OKAY( 1, RTCrc32Finish), + SUPEXP_STK_OKAY( 3, RTCrc32Process), + SUPEXP_STK_OKAY( 0, RTCrc32Start), + SUPEXP_STK_OKAY( 1, RTErrConvertFromErrno), + SUPEXP_STK_OKAY( 1, RTErrConvertToErrno), + SUPEXP_STK_BACK( 4, RTHandleTableAllocWithCtx), + SUPEXP_STK_BACK( 1, RTHandleTableCreate), + SUPEXP_STK_BACKF( 6, RTHandleTableCreateEx), + SUPEXP_STK_BACKF( 3, RTHandleTableDestroy), + SUPEXP_STK_BACK( 3, RTHandleTableFreeWithCtx), + SUPEXP_STK_BACK( 3, RTHandleTableLookupWithCtx), + SUPEXP_STK_BACK( 4, RTLogBulkNestedWrite), + SUPEXP_STK_BACK( 5, RTLogBulkUpdate), + SUPEXP_STK_BACK( 2, RTLogCheckGroupFlags), + SUPEXP_STK_BACKF( 17, RTLogCreateExV), + SUPEXP_STK_BACK( 1, RTLogDestroy), + SUPEXP_STK_BACK( 0, RTLogDefaultInstance), + SUPEXP_STK_BACK( 1, RTLogDefaultInstanceEx), + SUPEXP_STK_BACK( 1, SUPR0DefaultLogInstanceEx), + SUPEXP_STK_BACK( 0, RTLogGetDefaultInstance), + SUPEXP_STK_BACK( 1, RTLogGetDefaultInstanceEx), + SUPEXP_STK_BACK( 1, SUPR0GetDefaultLogInstanceEx), + SUPEXP_STK_BACK( 5, RTLogLoggerExV), + SUPEXP_STK_BACK( 2, RTLogPrintfV), + SUPEXP_STK_BACK( 0, RTLogRelGetDefaultInstance), + SUPEXP_STK_BACK( 1, RTLogRelGetDefaultInstanceEx), + SUPEXP_STK_BACK( 1, SUPR0GetDefaultLogRelInstanceEx), + SUPEXP_STK_BACK( 2, RTLogSetDefaultInstanceThread), + SUPEXP_STK_BACKF( 2, RTLogSetFlushCallback), + SUPEXP_STK_BACK( 2, RTLogSetR0ProgramStart), + SUPEXP_STK_BACK( 3, RTLogSetR0ThreadNameV), + SUPEXP_STK_BACK( 5, RTMemAllocExTag), + SUPEXP_STK_BACK( 2, RTMemAllocTag), + SUPEXP_STK_BACK( 2, RTMemAllocVarTag), + SUPEXP_STK_BACK( 2, RTMemAllocZTag), + SUPEXP_STK_BACK( 2, RTMemAllocZVarTag), + SUPEXP_STK_BACK( 4, RTMemDupExTag), + SUPEXP_STK_BACK( 3, RTMemDupTag), + SUPEXP_STK_BACK( 1, RTMemFree), + SUPEXP_STK_BACK( 2, RTMemFreeEx), + SUPEXP_STK_BACK( 3, RTMemReallocTag), + SUPEXP_STK_BACK( 0, RTMpCpuId), + SUPEXP_STK_BACK( 1, RTMpCpuIdFromSetIndex), + SUPEXP_STK_BACK( 1, RTMpCpuIdToSetIndex), + SUPEXP_STK_BACK( 0, RTMpCurSetIndex), + SUPEXP_STK_BACK( 1, RTMpCurSetIndexAndId), + SUPEXP_STK_BACK( 0, RTMpGetArraySize), + SUPEXP_STK_BACK( 0, RTMpGetCount), + SUPEXP_STK_BACK( 0, RTMpGetMaxCpuId), + SUPEXP_STK_BACK( 0, RTMpGetOnlineCount), + SUPEXP_STK_BACK( 1, RTMpGetOnlineSet), + SUPEXP_STK_BACK( 1, RTMpGetSet), + SUPEXP_STK_BACK( 1, RTMpIsCpuOnline), + SUPEXP_STK_BACK( 1, RTMpIsCpuPossible), + SUPEXP_STK_BACK( 0, RTMpIsCpuWorkPending), + SUPEXP_STK_BACKF( 2, RTMpNotificationDeregister), + SUPEXP_STK_BACKF( 2, RTMpNotificationRegister), + SUPEXP_STK_BACKF( 3, RTMpOnAll), + SUPEXP_STK_BACKF( 3, RTMpOnOthers), + SUPEXP_STK_BACKF( 4, RTMpOnSpecific), + SUPEXP_STK_BACK( 1, RTMpPokeCpu), + SUPEXP_STK_OKAY( 4, RTNetIPv4AddDataChecksum), + SUPEXP_STK_OKAY( 2, RTNetIPv4AddTCPChecksum), + SUPEXP_STK_OKAY( 2, RTNetIPv4AddUDPChecksum), + SUPEXP_STK_OKAY( 1, RTNetIPv4FinalizeChecksum), + SUPEXP_STK_OKAY( 1, RTNetIPv4HdrChecksum), + SUPEXP_STK_OKAY( 4, RTNetIPv4IsDHCPValid), + SUPEXP_STK_OKAY( 4, RTNetIPv4IsHdrValid), + SUPEXP_STK_OKAY( 4, RTNetIPv4IsTCPSizeValid), + SUPEXP_STK_OKAY( 6, RTNetIPv4IsTCPValid), + SUPEXP_STK_OKAY( 3, RTNetIPv4IsUDPSizeValid), + SUPEXP_STK_OKAY( 5, RTNetIPv4IsUDPValid), + SUPEXP_STK_OKAY( 1, RTNetIPv4PseudoChecksum), + SUPEXP_STK_OKAY( 4, RTNetIPv4PseudoChecksumBits), + SUPEXP_STK_OKAY( 3, RTNetIPv4TCPChecksum), + SUPEXP_STK_OKAY( 3, RTNetIPv4UDPChecksum), + SUPEXP_STK_OKAY( 1, RTNetIPv6PseudoChecksum), + SUPEXP_STK_OKAY( 4, RTNetIPv6PseudoChecksumBits), + SUPEXP_STK_OKAY( 3, RTNetIPv6PseudoChecksumEx), + SUPEXP_STK_OKAY( 4, RTNetTCPChecksum), + SUPEXP_STK_OKAY( 2, RTNetUDPChecksum), + SUPEXP_STK_BACKF( 2, RTPowerNotificationDeregister), + SUPEXP_STK_BACKF( 2, RTPowerNotificationRegister), + SUPEXP_STK_BACK( 0, RTProcSelf), + SUPEXP_STK_BACK( 0, RTR0AssertPanicSystem), +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) + SUPEXP_STK_BACK( 2, RTR0DbgKrnlInfoOpen), /* only-darwin, only-solaris, only-windows */ + SUPEXP_STK_BACK( 5, RTR0DbgKrnlInfoQueryMember), /* only-darwin, only-solaris, only-windows */ +# if defined(RT_OS_SOLARIS) + SUPEXP_STK_BACK( 4, RTR0DbgKrnlInfoQuerySize), /* only-solaris */ +# endif + SUPEXP_STK_BACK( 4, RTR0DbgKrnlInfoQuerySymbol), /* only-darwin, only-solaris, only-windows */ + SUPEXP_STK_BACK( 1, RTR0DbgKrnlInfoRelease), /* only-darwin, only-solaris, only-windows */ + SUPEXP_STK_BACK( 1, RTR0DbgKrnlInfoRetain), /* only-darwin, only-solaris, only-windows */ +#endif + SUPEXP_STK_BACK( 0, RTR0MemAreKrnlAndUsrDifferent), + SUPEXP_STK_BACK( 1, RTR0MemKernelIsValidAddr), + SUPEXP_STK_BACK( 3, RTR0MemKernelCopyFrom), + SUPEXP_STK_BACK( 3, RTR0MemKernelCopyTo), + SUPEXP_STK_OKAY( 1, RTR0MemObjAddress), + SUPEXP_STK_OKAY( 1, RTR0MemObjAddressR3), + SUPEXP_STK_BACK( 4, RTR0MemObjAllocContTag), + SUPEXP_STK_BACK( 5, RTR0MemObjAllocLargeTag), + SUPEXP_STK_BACK( 4, RTR0MemObjAllocLowTag), + SUPEXP_STK_BACK( 4, RTR0MemObjAllocPageTag), + SUPEXP_STK_BACK( 5, RTR0MemObjAllocPhysExTag), + SUPEXP_STK_BACK( 4, RTR0MemObjAllocPhysNCTag), + SUPEXP_STK_BACK( 4, RTR0MemObjAllocPhysTag), + SUPEXP_STK_BACK( 5, RTR0MemObjEnterPhysTag), + SUPEXP_STK_BACK( 2, RTR0MemObjFree), + SUPEXP_STK_BACK( 2, RTR0MemObjGetPagePhysAddr), + SUPEXP_STK_OKAY( 1, RTR0MemObjIsMapping), + SUPEXP_STK_BACK( 6, RTR0MemObjLockUserTag), + SUPEXP_STK_BACK( 5, RTR0MemObjLockKernelTag), + SUPEXP_STK_BACK( 8, RTR0MemObjMapKernelExTag), + SUPEXP_STK_BACK( 6, RTR0MemObjMapKernelTag), + SUPEXP_STK_BACK( 9, RTR0MemObjMapUserExTag), + SUPEXP_STK_BACK( 7, RTR0MemObjMapUserTag), + SUPEXP_STK_BACK( 4, RTR0MemObjProtect), + SUPEXP_STK_OKAY( 1, RTR0MemObjSize), + SUPEXP_STK_OKAY( 1, RTR0MemObjWasZeroInitialized), + SUPEXP_STK_BACK( 3, RTR0MemUserCopyFrom), + SUPEXP_STK_BACK( 3, RTR0MemUserCopyTo), + SUPEXP_STK_BACK( 1, RTR0MemUserIsValidAddr), + SUPEXP_STK_BACK( 0, RTR0ProcHandleSelf), + SUPEXP_STK_BACK( 1, RTSemEventCreate), + SUPEXP_STK_BACK( 1, RTSemEventDestroy), + SUPEXP_STK_BACK( 0, RTSemEventGetResolution), + SUPEXP_STK_BACK( 0, RTSemEventIsSignalSafe), + SUPEXP_STK_BACK( 1, RTSemEventMultiCreate), + SUPEXP_STK_BACK( 1, RTSemEventMultiDestroy), + SUPEXP_STK_BACK( 0, RTSemEventMultiGetResolution), + SUPEXP_STK_BACK( 0, RTSemEventMultiIsSignalSafe), + SUPEXP_STK_BACK( 1, RTSemEventMultiReset), + SUPEXP_STK_BACK( 1, RTSemEventMultiSignal), + SUPEXP_STK_BACK( 2, RTSemEventMultiWait), + SUPEXP_STK_BACK( 3, RTSemEventMultiWaitEx), + SUPEXP_STK_BACK( 7, RTSemEventMultiWaitExDebug), + SUPEXP_STK_BACK( 2, RTSemEventMultiWaitNoResume), + SUPEXP_STK_BACK( 1, RTSemEventSignal), + SUPEXP_STK_BACK( 2, RTSemEventWait), + SUPEXP_STK_BACK( 3, RTSemEventWaitEx), + SUPEXP_STK_BACK( 7, RTSemEventWaitExDebug), + SUPEXP_STK_BACK( 2, RTSemEventWaitNoResume), + SUPEXP_STK_BACK( 1, RTSemFastMutexCreate), + SUPEXP_STK_BACK( 1, RTSemFastMutexDestroy), + SUPEXP_STK_BACK( 1, RTSemFastMutexRelease), + SUPEXP_STK_BACK( 1, RTSemFastMutexRequest), + SUPEXP_STK_BACK( 1, RTSemMutexCreate), + SUPEXP_STK_BACK( 1, RTSemMutexDestroy), + SUPEXP_STK_BACK( 1, RTSemMutexRelease), + SUPEXP_STK_BACK( 2, RTSemMutexRequest), + SUPEXP_STK_BACK( 6, RTSemMutexRequestDebug), + SUPEXP_STK_BACK( 2, RTSemMutexRequestNoResume), + SUPEXP_STK_BACK( 6, RTSemMutexRequestNoResumeDebug), + SUPEXP_STK_BACK( 1, RTSpinlockAcquire), + SUPEXP_STK_BACK( 3, RTSpinlockCreate), + SUPEXP_STK_BACK( 1, RTSpinlockDestroy), + SUPEXP_STK_BACK( 1, RTSpinlockRelease), + SUPEXP_STK_OKAY( 3, RTStrCopy), + SUPEXP_STK_BACK( 2, RTStrDupTag), + SUPEXP_STK_BACK( 6, RTStrFormatNumber), + SUPEXP_STK_BACK( 1, RTStrFormatTypeDeregister), + SUPEXP_STK_BACKF( 3, RTStrFormatTypeRegister), + SUPEXP_STK_BACKF( 2, RTStrFormatTypeSetUser), + SUPEXP_STK_BACKF( 6, RTStrFormatV), + SUPEXP_STK_BACK( 1, RTStrFree), + SUPEXP_STK_OKAY( 3, RTStrNCmp), + SUPEXP_STK_BACKF( 6, RTStrPrintfExV), + SUPEXP_STK_BACK( 4, RTStrPrintfV), + SUPEXP_STK_BACKF( 6, RTStrPrintf2ExV), + SUPEXP_STK_BACK( 4, RTStrPrintf2V), + SUPEXP_STK_BACKF( 7, RTThreadCreate), + SUPEXP_STK_BACK( 1, RTThreadCtxHookIsEnabled), + SUPEXP_STK_BACKF( 4, RTThreadCtxHookCreate), + SUPEXP_STK_BACK( 1, RTThreadCtxHookDestroy), + SUPEXP_STK_BACK( 1, RTThreadCtxHookDisable), + SUPEXP_STK_BACK( 1, RTThreadCtxHookEnable), + SUPEXP_STK_BACK( 1, RTThreadGetName), + SUPEXP_STK_BACK( 1, RTThreadGetNative), + SUPEXP_STK_BACK( 1, RTThreadGetType), + SUPEXP_STK_BACK( 1, RTThreadIsInInterrupt), + SUPEXP_STK_BACK( 0, RTThreadNativeSelf), + SUPEXP_STK_BACK( 1, RTThreadPreemptDisable), + SUPEXP_STK_BACK( 1, RTThreadPreemptIsEnabled), + SUPEXP_STK_BACK( 1, RTThreadPreemptIsPending), + SUPEXP_STK_BACK( 0, RTThreadPreemptIsPendingTrusty), + SUPEXP_STK_BACK( 0, RTThreadPreemptIsPossible), + SUPEXP_STK_BACK( 1, RTThreadPreemptRestore), + SUPEXP_STK_BACK( 1, RTThreadQueryTerminationStatus), + SUPEXP_STK_BACK( 0, RTThreadSelf), + SUPEXP_STK_BACK( 0, RTThreadSelfName), + SUPEXP_STK_BACK( 1, RTThreadSleep), + SUPEXP_STK_BACK( 1, RTThreadUserReset), + SUPEXP_STK_BACK( 1, RTThreadUserSignal), + SUPEXP_STK_BACK( 2, RTThreadUserWait), + SUPEXP_STK_BACK( 2, RTThreadUserWaitNoResume), + SUPEXP_STK_BACK( 3, RTThreadWait), + SUPEXP_STK_BACK( 3, RTThreadWaitNoResume), + SUPEXP_STK_BACK( 0, RTThreadYield), + SUPEXP_STK_BACK( 1, RTTimeNow), + SUPEXP_STK_BACK( 0, RTTimerCanDoHighResolution), + SUPEXP_STK_BACK( 2, RTTimerChangeInterval), + SUPEXP_STK_BACKF( 4, RTTimerCreate), + SUPEXP_STK_BACKF( 5, RTTimerCreateEx), + SUPEXP_STK_BACK( 1, RTTimerDestroy), + SUPEXP_STK_BACK( 0, RTTimerGetSystemGranularity), + SUPEXP_STK_BACK( 1, RTTimerReleaseSystemGranularity), + SUPEXP_STK_BACK( 2, RTTimerRequestSystemGranularity), + SUPEXP_STK_BACK( 2, RTTimerStart), + SUPEXP_STK_BACK( 1, RTTimerStop), + SUPEXP_STK_BACK( 0, RTTimeSystemMilliTS), + SUPEXP_STK_BACK( 0, RTTimeSystemNanoTS), + SUPEXP_STK_OKAY( 2, RTUuidCompare), + SUPEXP_STK_OKAY( 2, RTUuidCompareStr), + SUPEXP_STK_OKAY( 2, RTUuidFromStr), +/* SED: END */ +}; + +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +/** + * Drag in the rest of IRPT since we share it with the + * rest of the kernel modules on darwin. + */ +struct CLANG11WERIDNESS { PFNRT pfn; } g_apfnVBoxDrvIPRTDeps[] = +{ + /* VBoxNetAdp */ + { (PFNRT)RTRandBytes }, + /* VBoxUSB */ + { (PFNRT)RTPathStripFilename }, +#if !defined(RT_OS_FREEBSD) + { (PFNRT)RTHandleTableAlloc }, + { (PFNRT)RTStrPurgeEncoding }, +#endif + { NULL } +}; +#endif /* RT_OS_DARWIN || RT_OS_SOLARIS || RT_OS_FREEBSD */ + + + +/** + * Initializes the device extentsion structure. + * + * @returns IPRT status code. + * @param pDevExt The device extension to initialize. + * @param cbSession The size of the session structure. The size of + * SUPDRVSESSION may be smaller when SUPDRV_AGNOSTIC is + * defined because we're skipping the OS specific members + * then. + */ +int VBOXCALL supdrvInitDevExt(PSUPDRVDEVEXT pDevExt, size_t cbSession) +{ + int rc; + +#ifdef SUPDRV_WITH_RELEASE_LOGGER + /* + * Create the release log. + */ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + PRTLOGGER pRelLogger; + rc = RTLogCreate(&pRelLogger, 0 /* fFlags */, "all", + "VBOX_RELEASE_LOG", RT_ELEMENTS(s_apszGroups), s_apszGroups, RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, NULL); + if (RT_SUCCESS(rc)) + RTLogRelSetDefaultInstance(pRelLogger); + /** @todo Add native hook for getting logger config parameters and setting + * them. On linux we should use the module parameter stuff... */ +#endif + +#if (defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)) && !defined(VBOX_WITH_OLD_CPU_SUPPORT) + /* + * Require SSE2 to be present. + */ + if (!(ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_SSE2)) + { + SUPR0Printf("vboxdrv: Requires SSE2 (cpuid(0).EDX=%#x)\n", ASMCpuId_EDX(1)); + return VERR_UNSUPPORTED_CPU; + } +#endif + + /* + * Initialize it. + */ + 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; +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + pDevExt->mtxLdr = NIL_RTSEMMUTEX; +#else + pDevExt->mtxLdr = NIL_RTSEMFASTMUTEX; +#endif +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + pDevExt->mtxGip = NIL_RTSEMMUTEX; + pDevExt->mtxTscDelta = NIL_RTSEMMUTEX; +#else + pDevExt->mtxGip = NIL_RTSEMFASTMUTEX; + pDevExt->mtxTscDelta = NIL_RTSEMFASTMUTEX; +#endif + + 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)) +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + rc = RTSemMutexCreate(&pDevExt->mtxTscDelta); +#else + rc = RTSemFastMutexCreate(&pDevExt->mtxTscDelta); +#endif + if (RT_SUCCESS(rc)) + { + rc = RTSemFastMutexCreate(&pDevExt->mtxComponentFactory); + if (RT_SUCCESS(rc)) + { +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + rc = RTSemMutexCreate(&pDevExt->mtxGip); +#else + rc = RTSemFastMutexCreate(&pDevExt->mtxGip); +#endif + 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->hLdrTermThread = 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[3].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 */ + +# 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 */ +# else + g_aFunctions[0].pfn = g_aFunctions[1].pfn = g_aFunctions[2].pfn = g_aFunctions[3].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 */ +#endif /* !RT_OS_DARWIN */ + return VINF_SUCCESS; + } + + supdrvGipDestroy(pDevExt); + } + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMMUTEX; +#else + RTSemFastMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMFASTMUTEX; +#endif + } + RTSemFastMutexDestroy(pDevExt->mtxComponentFactory); + pDevExt->mtxComponentFactory = NIL_RTSEMFASTMUTEX; + } + } + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexDestroy(pDevExt->mtxTscDelta); + pDevExt->mtxTscDelta = NIL_RTSEMMUTEX; +#else + RTSemFastMutexDestroy(pDevExt->mtxTscDelta); + pDevExt->mtxTscDelta = NIL_RTSEMFASTMUTEX; +#endif +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + RTSemMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMMUTEX; +#else + RTSemFastMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMFASTMUTEX; +#endif + 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)); +#endif + + return rc; +} + + +/** + * Delete the device extension (e.g. cleanup members). + * + * @param pDevExt The device extension to delete. + */ +void VBOXCALL supdrvDeleteDevExt(PSUPDRVDEVEXT pDevExt) +{ + PSUPDRVOBJ pObj; + PSUPDRVUSAGE pUsage; + + /* + * Kill mutexes and spinlocks. + */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMMUTEX; + RTSemMutexDestroy(pDevExt->mtxTscDelta); + pDevExt->mtxTscDelta = NIL_RTSEMMUTEX; +#else + RTSemFastMutexDestroy(pDevExt->mtxGip); + pDevExt->mtxGip = NIL_RTSEMFASTMUTEX; + RTSemFastMutexDestroy(pDevExt->mtxTscDelta); + pDevExt->mtxTscDelta = NIL_RTSEMFASTMUTEX; +#endif +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + RTSemMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMMUTEX; +#else + RTSemFastMutexDestroy(pDevExt->mtxLdr); + pDevExt->mtxLdr = NIL_RTSEMFASTMUTEX; +#endif + RTSpinlockDestroy(pDevExt->Spinlock); + pDevExt->Spinlock = NIL_RTSPINLOCK; + RTSemFastMutexDestroy(pDevExt->mtxComponentFactory); + pDevExt->mtxComponentFactory = NIL_RTSEMFASTMUTEX; + RTSpinlockDestroy(pDevExt->hSessionHashTabSpinlock); + pDevExt->hSessionHashTabSpinlock = NIL_RTSPINLOCK; + + /* + * Free lists. + */ + /* objects. */ + pObj = pDevExt->pObjs; + Assert(!pObj); /* (can trigger on forced unloads) */ + pDevExt->pObjs = NULL; + while (pObj) + { + void *pvFree = pObj; + pObj = pObj->pNext; + RTMemFree(pvFree); + } + + /* usage records. */ + pUsage = pDevExt->pUsageFree; + pDevExt->pUsageFree = NULL; + while (pUsage) + { + void *pvFree = pUsage; + pUsage = pUsage->pNext; + RTMemFree(pvFree); + } + + /* kill the GIP. */ + supdrvGipDestroy(pDevExt); + RTSpinlockDestroy(pDevExt->hGipSpinlock); + pDevExt->hGipSpinlock = NIL_RTSPINLOCK; + + supdrvTracerTerm(pDevExt); + +#ifdef SUPDRV_WITH_RELEASE_LOGGER + /* destroy the loggers. */ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); +#endif +} + + +/** + * Create session. + * + * @returns IPRT status code. + * @param pDevExt Device extension. + * @param fUser Flag indicating whether this is a user or kernel + * session. + * @param fUnrestricted Unrestricted access (system) or restricted access + * (user)? + * @param ppSession Where to store the pointer to the session data. + */ +int VBOXCALL supdrvCreateSession(PSUPDRVDEVEXT pDevExt, bool fUser, bool fUnrestricted, PSUPDRVSESSION *ppSession) +{ + int rc; + PSUPDRVSESSION pSession; + + if (!SUP_IS_DEVEXT_VALID(pDevExt)) + return VERR_INVALID_PARAMETER; + + /* + * Allocate memory for the session data. + */ + pSession = *ppSession = (PSUPDRVSESSION)RTMemAllocZ(pDevExt->cbSession); + if (pSession) + { + /* Initialize session data. */ + rc = RTSpinlockCreate(&pSession->Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "SUPDrvSession"); + if (!rc) + { + rc = RTHandleTableCreateEx(&pSession->hHandleTable, + RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE | RTHANDLETABLE_FLAGS_CONTEXT, + 1 /*uBase*/, 32768 /*cMax*/, supdrvSessionObjHandleRetain, pSession); + if (RT_SUCCESS(rc)) + { + Assert(pSession->Spinlock != NIL_RTSPINLOCK); + pSession->pDevExt = pDevExt; + pSession->u32Cookie = BIRD_INV; + pSession->fUnrestricted = fUnrestricted; + /*pSession->fInHashTable = false; */ + pSession->cRefs = 1; + /*pSession->pCommonNextHash = NULL; + pSession->ppOsSessionPtr = NULL; */ + if (fUser) + { + pSession->Process = RTProcSelf(); + pSession->R0Process = RTR0ProcHandleSelf(); + } + else + { + 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); + /*pSession->cTpProviders = 0;*/ + /*pSession->cTpProbesFiring = 0;*/ + RTListInit(&pSession->TpUmods); + /*RT_ZERO(pSession->apTpLookupTable);*/ + + VBOXDRV_SESSION_CREATE(pSession, fUser); + LogFlow(("Created session %p initial cookie=%#x\n", pSession, pSession->u32Cookie)); + return VINF_SUCCESS; + } + + RTSpinlockDestroy(pSession->Spinlock); + } + RTMemFree(pSession); + *ppSession = NULL; + Log(("Failed to create spinlock, rc=%d!\n", rc)); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + + +/** + * 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 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 The device extension. + * @param pSession Session data. + */ +static void supdrvCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + int rc; + PSUPDRVBUNDLE pBundle; + LogFlow(("supdrvCleanupSession: pSession=%p\n", pSession)); + + Assert(!pSession->fInHashTable); + Assert(!pSession->ppOsSessionPtr); + AssertLogRelMsg(pSession->R0Process == RTR0ProcHandleSelf() || pSession->R0Process == NIL_RTR0PROCESS, + ("R0Process=%p cur=%p; curpid=%u\n", + pSession->R0Process, RTR0ProcHandleSelf(), RTProcSelf())); + + /* + * Remove logger instances related to this session. + */ + RTLogSetDefaultInstanceThread(NULL, (uintptr_t)pSession); + + /* + * Destroy the handle table. + */ + rc = RTHandleTableDestroy(pSession->hHandleTable, supdrvSessionObjHandleDelete, pSession); + AssertRC(rc); + pSession->hHandleTable = NIL_RTHANDLETABLE; + + /* + * Release object references made in this session. + * In theory there should be noone racing us in this session. + */ + Log2(("release objects - start\n")); + if (pSession->pUsage) + { + PSUPDRVUSAGE pUsage; + RTSpinlockAcquire(pDevExt->Spinlock); + + while ((pUsage = pSession->pUsage) != NULL) + { + PSUPDRVOBJ pObj = pUsage->pObj; + pSession->pUsage = pUsage->pNext; + + AssertMsg(pUsage->cUsage >= 1 && pObj->cUsage >= pUsage->cUsage, ("glob %d; sess %d\n", pObj->cUsage, pUsage->cUsage)); + if (pUsage->cUsage < pObj->cUsage) + { + pObj->cUsage -= pUsage->cUsage; + RTSpinlockRelease(pDevExt->Spinlock); + } + else + { + /* Destroy the object and free the record. */ + if (pDevExt->pObjs == pObj) + pDevExt->pObjs = pObj->pNext; + else + { + PSUPDRVOBJ pObjPrev; + for (pObjPrev = pDevExt->pObjs; pObjPrev; pObjPrev = pObjPrev->pNext) + if (pObjPrev->pNext == pObj) + { + pObjPrev->pNext = pObj->pNext; + break; + } + Assert(pObjPrev); + } + RTSpinlockRelease(pDevExt->Spinlock); + + Log(("supdrvCleanupSession: destroying %p/%d (%p/%p) cpid=%RTproc pid=%RTproc dtor=%p\n", + pObj, pObj->enmType, pObj->pvUser1, pObj->pvUser2, pObj->CreatorProcess, RTProcSelf(), pObj->pfnDestructor)); + if (pObj->pfnDestructor) + pObj->pfnDestructor(pObj, pObj->pvUser1, pObj->pvUser2); + RTMemFree(pObj); + } + + /* free it and continue. */ + RTMemFree(pUsage); + + RTSpinlockAcquire(pDevExt->Spinlock); + } + + RTSpinlockRelease(pDevExt->Spinlock); + AssertMsg(!pSession->pUsage, ("Some buster reregistered an object during desturction!\n")); + } + Log2(("release objects - done\n")); + + /* + * Make sure the associated VM pointers are NULL. + */ + if (pSession->pSessionGVM || pSession->pSessionVM || pSession->pFastIoCtrlVM) + { + SUPR0Printf("supdrvCleanupSession: VM not disassociated! pSessionGVM=%p pSessionVM=%p pFastIoCtrlVM=%p\n", + pSession->pSessionGVM, pSession->pSessionVM, pSession->pFastIoCtrlVM); + pSession->pSessionGVM = NULL; + pSession->pSessionVM = NULL; + pSession->pFastIoCtrlVM = NULL; + } + + /* + * Do tracer cleanups related to this session. + */ + Log2(("release tracer stuff - start\n")); + supdrvTracerCleanupSession(pDevExt, pSession); + Log2(("release tracer stuff - end\n")); + + /* + * Release memory allocated in the session. + * + * We do not serialize this as we assume that the application will + * not allocated memory while closing the file handle object. + */ + Log2(("freeing memory:\n")); + pBundle = &pSession->Bundle; + while (pBundle) + { + PSUPDRVBUNDLE pToFree; + unsigned i; + + /* + * Check and unlock all entries in the bundle. + */ + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if (pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ) + { + Log2(("eType=%d pvR0=%p pvR3=%p cb=%ld\n", pBundle->aMem[i].eType, RTR0MemObjAddress(pBundle->aMem[i].MemObj), + (void *)RTR0MemObjAddressR3(pBundle->aMem[i].MapObjR3), (long)RTR0MemObjSize(pBundle->aMem[i].MemObj))); + if (pBundle->aMem[i].MapObjR3 != NIL_RTR0MEMOBJ) + { + rc = RTR0MemObjFree(pBundle->aMem[i].MapObjR3, false); + AssertRC(rc); /** @todo figure out how to handle this. */ + pBundle->aMem[i].MapObjR3 = NIL_RTR0MEMOBJ; + } + rc = RTR0MemObjFree(pBundle->aMem[i].MemObj, true /* fFreeMappings */); + AssertRC(rc); /** @todo figure out how to handle this. */ + pBundle->aMem[i].MemObj = NIL_RTR0MEMOBJ; + pBundle->aMem[i].eType = MEMREF_TYPE_UNUSED; + } + } + + /* + * Advance and free previous bundle. + */ + pToFree = pBundle; + pBundle = pBundle->pNext; + + pToFree->pNext = NULL; + pToFree->cUsed = 0; + if (pToFree != &pSession->Bundle) + RTMemFree(pToFree); + } + Log2(("freeing memory - done\n")); + + /* + * Deregister component factories. + */ + RTSemFastMutexRequest(pDevExt->mtxComponentFactory); + Log2(("deregistering component factories:\n")); + if (pDevExt->pComponentFactoryHead) + { + PSUPDRVFACTORYREG pPrev = NULL; + PSUPDRVFACTORYREG pCur = pDevExt->pComponentFactoryHead; + while (pCur) + { + if (pCur->pSession == pSession) + { + /* unlink it */ + PSUPDRVFACTORYREG pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + pDevExt->pComponentFactoryHead = pNext; + + /* free it */ + pCur->pNext = NULL; + pCur->pSession = NULL; + pCur->pFactory = NULL; + RTMemFree(pCur); + + /* next */ + pCur = pNext; + } + else + { + /* next */ + pPrev = pCur; + pCur = pCur->pNext; + } + } + } + RTSemFastMutexRelease(pDevExt->mtxComponentFactory); + Log2(("deregistering component factories - done\n")); + + /* + * Loaded images needs to be dereferenced and possibly freed up. + */ + supdrvLdrLock(pDevExt); + Log2(("freeing images:\n")); + if (pSession->pLdrUsage) + { + PSUPDRVLDRUSAGE pUsage = pSession->pLdrUsage; + pSession->pLdrUsage = NULL; + while (pUsage) + { + void *pvFree = pUsage; + PSUPDRVLDRIMAGE pImage = pUsage->pImage; + uint32_t cUsage = pUsage->cRing0Usage + pUsage->cRing3Usage; + if (pImage->cImgUsage > cUsage) + supdrvLdrSubtractUsage(pDevExt, pImage, cUsage); + else + supdrvLdrFree(pDevExt, pImage); + pUsage->pImage = NULL; + pUsage = pUsage->pNext; + RTMemFree(pvFree); + } + } + supdrvLdrUnlock(pDevExt); + Log2(("freeing images - done\n")); + + /* + * Unmap the GIP. + */ + Log2(("umapping GIP:\n")); + if (pSession->GipMapObjR3 != NIL_RTR0MEMOBJ) + { + SUPR0GipUnmap(pSession); + pSession->fGipReferenced = 0; + } + Log2(("umapping GIP - done\n")); +} + + +/** + * 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 supdrvDestroySession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + VBOXDRV_SESSION_CLOSE(pSession); + + /* + * Cleanup the session first. + */ + supdrvCleanupSession(pDevExt, pSession); + supdrvOSCleanupSession(pDevExt, pSession); + + /* + * Free the rest of the session stuff. + */ + RTSpinlockDestroy(pSession->Spinlock); + pSession->Spinlock = NIL_RTSPINLOCK; + pSession->pDevExt = NULL; + RTMemFree(pSession); + 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; + + ASMAtomicDecS32(&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); + NOREF(cRefs); + Assert(cRefs > 1 && cRefs < _1M); + } + + RTSpinlockRelease(pDevExt->hSessionHashTabSpinlock); + + return pCur; +} + + +/** + * Retain a session to make sure it doesn't go away while it is in use. + * + * @returns New reference count on success, UINT32_MAX on failure. + * @param pSession Session data. + */ +uint32_t VBOXCALL supdrvSessionRetain(PSUPDRVSESSION pSession) +{ + uint32_t cRefs; + AssertPtrReturn(pSession, UINT32_MAX); + AssertReturn(SUP_IS_SESSION_VALID(pSession), UINT32_MAX); + + cRefs = ASMAtomicIncU32(&pSession->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pSession)); + return cRefs; +} + + +/** + * Releases a given session. + * + * @returns New reference count on success (0 if closed), UINT32_MAX on failure. + * @param pSession Session data. + */ +uint32_t VBOXCALL supdrvSessionRelease(PSUPDRVSESSION pSession) +{ + uint32_t cRefs; + AssertPtrReturn(pSession, UINT32_MAX); + AssertReturn(SUP_IS_SESSION_VALID(pSession), UINT32_MAX); + + cRefs = ASMAtomicDecU32(&pSession->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pSession)); + if (cRefs == 0) + supdrvDestroySession(pSession->pDevExt, pSession); + return cRefs; +} + + +/** + * RTHandleTableDestroy callback used by supdrvCleanupSession. + * + * @returns IPRT status code, see SUPR0ObjAddRef. + * @param hHandleTable The handle table handle. Ignored. + * @param pvObj The object pointer. + * @param pvCtx Context, the handle type. Ignored. + * @param pvUser Session pointer. + */ +static DECLCALLBACK(int) supdrvSessionObjHandleRetain(RTHANDLETABLE hHandleTable, void *pvObj, void *pvCtx, void *pvUser) +{ + NOREF(pvCtx); + NOREF(hHandleTable); + return SUPR0ObjAddRefEx(pvObj, (PSUPDRVSESSION)pvUser, true /*fNoBlocking*/); +} + + +/** + * RTHandleTableDestroy callback used by supdrvCleanupSession. + * + * @param hHandleTable The handle table handle. Ignored. + * @param h The handle value. Ignored. + * @param pvObj The object pointer. + * @param pvCtx Context, the handle type. Ignored. + * @param pvUser Session pointer. + */ +static DECLCALLBACK(void) supdrvSessionObjHandleDelete(RTHANDLETABLE hHandleTable, uint32_t h, void *pvObj, void *pvCtx, void *pvUser) +{ + NOREF(pvCtx); + NOREF(h); + NOREF(hHandleTable); + SUPR0ObjRelease(pvObj, (PSUPDRVSESSION)pvUser); +} + + +/** + * Fast path I/O Control worker. + * + * @returns VBox status code that should be passed down to ring-3 unchanged. + * @param uOperation SUP_VMMR0_DO_XXX (not the I/O control number!). + * @param idCpu VMCPU id. + * @param pDevExt Device extention. + * @param pSession Session data. + */ +int VBOXCALL supdrvIOCtlFast(uintptr_t uOperation, VMCPUID idCpu, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + /* + * Validate input and check that the VM has a session. + */ + if (RT_LIKELY(RT_VALID_PTR(pSession))) + { + PVM pVM = pSession->pSessionVM; + PGVM pGVM = pSession->pSessionGVM; + if (RT_LIKELY( pGVM != NULL + && pVM != NULL + && pVM == pSession->pFastIoCtrlVM)) + { + if (RT_LIKELY(pDevExt->pfnVMMR0EntryFast)) + { + /* + * Make the call. + */ + pDevExt->pfnVMMR0EntryFast(pGVM, pVM, idCpu, uOperation); + return VINF_SUCCESS; + } + + SUPR0Printf("supdrvIOCtlFast: pfnVMMR0EntryFast is NULL\n"); + } + else + SUPR0Printf("supdrvIOCtlFast: Misconfig session: pGVM=%p pVM=%p pFastIoCtrlVM=%p\n", + pGVM, pVM, pSession->pFastIoCtrlVM); + } + else + SUPR0Printf("supdrvIOCtlFast: Bad session pointer %p\n", pSession); + return VERR_INTERNAL_ERROR; +} + + +/** + * Helper for supdrvIOCtl used to validate module names passed to SUP_IOCTL_LDR_OPEN. + * + * Check if pszStr contains any character of pszChars. We would use strpbrk + * here if this function would be contained in the RedHat kABI white list, see + * http://www.kerneldrivers.org/RHEL5. + * + * @returns true if fine, false if not. + * @param pszName The module name to check. + */ +static bool supdrvIsLdrModuleNameValid(const char *pszName) +{ + int chCur; + while ((chCur = *pszName++) != '\0') + { + static const char s_szInvalidChars[] = ";:()[]{}/\\|&*%#@!~`\"'"; + unsigned offInv = RT_ELEMENTS(s_szInvalidChars); + while (offInv-- > 0) + if (s_szInvalidChars[offInv] == chCur) + return false; + } + return true; +} + + + +/** + * I/O Control inner worker (tracing reasons). + * + * @returns IPRT status code. + * @retval VERR_INVALID_PARAMETER if the request is invalid. + * + * @param uIOCtl Function number. + * @param pDevExt Device extention. + * @param pSession Session data. + * @param pReqHdr The request header. + */ +static int supdrvIOCtlInnerUnrestricted(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPREQHDR pReqHdr) +{ + /* + * Validation macros + */ +#define REQ_CHECK_SIZES_EX(Name, cbInExpect, cbOutExpect) \ + do { \ + if (RT_UNLIKELY(pReqHdr->cbIn != (cbInExpect) || pReqHdr->cbOut != (cbOutExpect))) \ + { \ + OSDBGPRINT(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld. cbOut=%ld expected %ld.\n", \ + (long)pReqHdr->cbIn, (long)(cbInExpect), (long)pReqHdr->cbOut, (long)(cbOutExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_SIZES(Name) REQ_CHECK_SIZES_EX(Name, Name ## _SIZE_IN, Name ## _SIZE_OUT) + +#define REQ_CHECK_SIZE_IN(Name, cbInExpect) \ + do { \ + if (RT_UNLIKELY(pReqHdr->cbIn != (cbInExpect))) \ + { \ + OSDBGPRINT(( #Name ": Invalid input/output sizes. cbIn=%ld expected %ld.\n", \ + (long)pReqHdr->cbIn, (long)(cbInExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_SIZE_OUT(Name, cbOutExpect) \ + do { \ + if (RT_UNLIKELY(pReqHdr->cbOut != (cbOutExpect))) \ + { \ + OSDBGPRINT(( #Name ": Invalid input/output sizes. cbOut=%ld expected %ld.\n", \ + (long)pReqHdr->cbOut, (long)(cbOutExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_EXPR(Name, expr) \ + do { \ + if (RT_UNLIKELY(!(expr))) \ + { \ + OSDBGPRINT(( #Name ": %s\n", #expr)); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + +#define REQ_CHECK_EXPR_FMT(expr, fmt) \ + do { \ + if (RT_UNLIKELY(!(expr))) \ + { \ + OSDBGPRINT( fmt ); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + + /* + * The switch. + */ + switch (SUP_CTL_CODE_NO_SIZE(uIOCtl)) + { + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_COOKIE): + { + PSUPCOOKIE pReq = (PSUPCOOKIE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_COOKIE); + if (strncmp(pReq->u.In.szMagic, SUPCOOKIE_MAGIC, sizeof(pReq->u.In.szMagic))) + { + OSDBGPRINT(("SUP_IOCTL_COOKIE: invalid magic %.16s\n", pReq->u.In.szMagic)); + pReq->Hdr.rc = VERR_INVALID_MAGIC; + return 0; + } + +#if 0 + /* + * Call out to the OS specific code and let it do permission checks on the + * client process. + */ + if (!supdrvOSValidateClientProcess(pDevExt, pSession)) + { + pReq->u.Out.u32Cookie = 0xffffffff; + pReq->u.Out.u32SessionCookie = 0xffffffff; + pReq->u.Out.u32SessionVersion = 0xffffffff; + pReq->u.Out.u32DriverVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.pSession = NULL; + pReq->u.Out.cFunctions = 0; + pReq->Hdr.rc = VERR_PERMISSION_DENIED; + return 0; + } +#endif + + /* + * Match the version. + * The current logic is very simple, match the major interface version. + */ + if ( pReq->u.In.u32MinVersion > SUPDRV_IOC_VERSION + || (pReq->u.In.u32MinVersion & 0xffff0000) != (SUPDRV_IOC_VERSION & 0xffff0000)) + { + OSDBGPRINT(("SUP_IOCTL_COOKIE: Version mismatch. Requested: %#x Min: %#x Current: %#x\n", + pReq->u.In.u32ReqVersion, pReq->u.In.u32MinVersion, SUPDRV_IOC_VERSION)); + pReq->u.Out.u32Cookie = 0xffffffff; + pReq->u.Out.u32SessionCookie = 0xffffffff; + pReq->u.Out.u32SessionVersion = 0xffffffff; + pReq->u.Out.u32DriverVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.pSession = NULL; + pReq->u.Out.cFunctions = 0; + pReq->Hdr.rc = VERR_VERSION_MISMATCH; + return 0; + } + + /* + * Fill in return data and be gone. + * N.B. The first one to change SUPDRV_IOC_VERSION shall makes sure that + * u32SessionVersion <= u32ReqVersion! + */ + /** @todo Somehow validate the client and negotiate a secure cookie... */ + pReq->u.Out.u32Cookie = pDevExt->u32Cookie; + pReq->u.Out.u32SessionCookie = pSession->u32Cookie; + pReq->u.Out.u32SessionVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.u32DriverVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.pSession = pSession; + pReq->u.Out.cFunctions = sizeof(g_aFunctions) / sizeof(g_aFunctions[0]); + pReq->Hdr.rc = VINF_SUCCESS; + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_QUERY_FUNCS(0)): + { + /* validate */ + PSUPQUERYFUNCS pReq = (PSUPQUERYFUNCS)pReqHdr; + REQ_CHECK_SIZES_EX(SUP_IOCTL_QUERY_FUNCS, SUP_IOCTL_QUERY_FUNCS_SIZE_IN, SUP_IOCTL_QUERY_FUNCS_SIZE_OUT(RT_ELEMENTS(g_aFunctions))); + + /* execute */ + pReq->u.Out.cFunctions = RT_ELEMENTS(g_aFunctions); + RT_BCOPY_UNFORTIFIED(&pReq->u.Out.aFunctions[0], g_aFunctions, sizeof(g_aFunctions)); + pReq->Hdr.rc = VINF_SUCCESS; + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_LOCK): + { + /* validate */ + PSUPPAGELOCK pReq = (PSUPPAGELOCK)pReqHdr; + REQ_CHECK_SIZE_IN(SUP_IOCTL_PAGE_LOCK, SUP_IOCTL_PAGE_LOCK_SIZE_IN); + REQ_CHECK_SIZE_OUT(SUP_IOCTL_PAGE_LOCK, SUP_IOCTL_PAGE_LOCK_SIZE_OUT(pReq->u.In.cPages)); + REQ_CHECK_EXPR(SUP_IOCTL_PAGE_LOCK, pReq->u.In.cPages > 0); + REQ_CHECK_EXPR(SUP_IOCTL_PAGE_LOCK, pReq->u.In.pvR3 >= PAGE_SIZE); + + /* execute */ + pReq->Hdr.rc = SUPR0LockMem(pSession, pReq->u.In.pvR3, pReq->u.In.cPages, &pReq->u.Out.aPages[0]); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_UNLOCK): + { + /* validate */ + PSUPPAGEUNLOCK pReq = (PSUPPAGEUNLOCK)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_PAGE_UNLOCK); + + /* execute */ + pReq->Hdr.rc = SUPR0UnlockMem(pSession, pReq->u.In.pvR3); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_CONT_ALLOC): + { + /* validate */ + PSUPCONTALLOC pReq = (PSUPCONTALLOC)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_CONT_ALLOC); + + /* execute */ + pReq->Hdr.rc = SUPR0ContAlloc(pSession, pReq->u.In.cPages, &pReq->u.Out.pvR0, &pReq->u.Out.pvR3, &pReq->u.Out.HCPhys); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_CONT_FREE): + { + /* validate */ + PSUPCONTFREE pReq = (PSUPCONTFREE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_CONT_FREE); + + /* execute */ + pReq->Hdr.rc = SUPR0ContFree(pSession, (RTHCUINTPTR)pReq->u.In.pvR3); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LDR_OPEN): + { + /* validate */ + PSUPLDROPEN pReq = (PSUPLDROPEN)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_LDR_OPEN); + if ( pReq->u.In.cbImageWithEverything != 0 + || pReq->u.In.cbImageBits != 0) + { + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageWithEverything > 0); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageWithEverything < 16*_1M); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageBits > 0); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.cbImageBits < pReq->u.In.cbImageWithEverything); + } + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, pReq->u.In.szName[0]); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, RTStrEnd(pReq->u.In.szName, sizeof(pReq->u.In.szName))); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, supdrvIsLdrModuleNameValid(pReq->u.In.szName)); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_OPEN, RTStrEnd(pReq->u.In.szFilename, sizeof(pReq->u.In.szFilename))); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_LdrOpen(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LDR_LOAD): + { + /* validate */ + PSUPLDRLOAD pReq = (PSUPLDRLOAD)pReqHdr; + REQ_CHECK_EXPR(Name, pReq->Hdr.cbIn >= SUP_IOCTL_LDR_LOAD_SIZE_IN(32)); + REQ_CHECK_SIZES_EX(SUP_IOCTL_LDR_LOAD, SUP_IOCTL_LDR_LOAD_SIZE_IN(pReq->u.In.cbImageWithEverything), SUP_IOCTL_LDR_LOAD_SIZE_OUT); + REQ_CHECK_EXPR_FMT( !pReq->u.In.cSymbols + || ( pReq->u.In.cSymbols <= 16384 + && pReq->u.In.offSymbols >= pReq->u.In.cbImageBits + && pReq->u.In.offSymbols < pReq->u.In.cbImageWithEverything + && pReq->u.In.offSymbols + pReq->u.In.cSymbols * sizeof(SUPLDRSYM) <= pReq->u.In.cbImageWithEverything), + ("SUP_IOCTL_LDR_LOAD: offSymbols=%#lx cSymbols=%#lx cbImageWithEverything=%#lx\n", (long)pReq->u.In.offSymbols, + (long)pReq->u.In.cSymbols, (long)pReq->u.In.cbImageWithEverything)); + REQ_CHECK_EXPR_FMT( !pReq->u.In.cbStrTab + || ( pReq->u.In.offStrTab < pReq->u.In.cbImageWithEverything + && pReq->u.In.offStrTab >= pReq->u.In.cbImageBits + && pReq->u.In.offStrTab + pReq->u.In.cbStrTab <= pReq->u.In.cbImageWithEverything + && pReq->u.In.cbStrTab <= pReq->u.In.cbImageWithEverything), + ("SUP_IOCTL_LDR_LOAD: offStrTab=%#lx cbStrTab=%#lx cbImageWithEverything=%#lx\n", (long)pReq->u.In.offStrTab, + (long)pReq->u.In.cbStrTab, (long)pReq->u.In.cbImageWithEverything)); + REQ_CHECK_EXPR_FMT( pReq->u.In.cSegments >= 1 + && pReq->u.In.cSegments <= 128 + && pReq->u.In.cSegments <= (pReq->u.In.cbImageBits + PAGE_SIZE - 1) / PAGE_SIZE + && pReq->u.In.offSegments >= pReq->u.In.cbImageBits + && pReq->u.In.offSegments < pReq->u.In.cbImageWithEverything + && pReq->u.In.offSegments + pReq->u.In.cSegments * sizeof(SUPLDRSEG) <= pReq->u.In.cbImageWithEverything, + ("SUP_IOCTL_LDR_LOAD: offSegments=%#lx cSegments=%#lx cbImageWithEverything=%#lx\n", (long)pReq->u.In.offSegments, + (long)pReq->u.In.cSegments, (long)pReq->u.In.cbImageWithEverything)); + + if (pReq->u.In.cSymbols) + { + uint32_t i; + PSUPLDRSYM paSyms = (PSUPLDRSYM)&pReq->u.In.abImage[pReq->u.In.offSymbols]; + for (i = 0; i < pReq->u.In.cSymbols; i++) + { + REQ_CHECK_EXPR_FMT(paSyms[i].offSymbol < pReq->u.In.cbImageWithEverything, + ("SUP_IOCTL_LDR_LOAD: sym #%ld: symb off %#lx (max=%#lx)\n", (long)i, (long)paSyms[i].offSymbol, (long)pReq->u.In.cbImageWithEverything)); + REQ_CHECK_EXPR_FMT(paSyms[i].offName < pReq->u.In.cbStrTab, + ("SUP_IOCTL_LDR_LOAD: sym #%ld: name off %#lx (max=%#lx)\n", (long)i, (long)paSyms[i].offName, (long)pReq->u.In.cbImageWithEverything)); + REQ_CHECK_EXPR_FMT(RTStrEnd((char const *)&pReq->u.In.abImage[pReq->u.In.offStrTab + paSyms[i].offName], + pReq->u.In.cbStrTab - paSyms[i].offName), + ("SUP_IOCTL_LDR_LOAD: sym #%ld: unterminated name! (%#lx / %#lx)\n", (long)i, (long)paSyms[i].offName, (long)pReq->u.In.cbImageWithEverything)); + } + } + { + uint32_t i; + uint32_t offPrevEnd = 0; + PSUPLDRSEG paSegs = (PSUPLDRSEG)&pReq->u.In.abImage[pReq->u.In.offSegments]; + for (i = 0; i < pReq->u.In.cSegments; i++) + { + REQ_CHECK_EXPR_FMT(paSegs[i].off < pReq->u.In.cbImageBits && !(paSegs[i].off & PAGE_OFFSET_MASK), + ("SUP_IOCTL_LDR_LOAD: seg #%ld: off %#lx (max=%#lx)\n", (long)i, (long)paSegs[i].off, (long)pReq->u.In.cbImageBits)); + REQ_CHECK_EXPR_FMT(paSegs[i].cb <= pReq->u.In.cbImageBits, + ("SUP_IOCTL_LDR_LOAD: seg #%ld: cb %#lx (max=%#lx)\n", (long)i, (long)paSegs[i].cb, (long)pReq->u.In.cbImageBits)); + REQ_CHECK_EXPR_FMT(paSegs[i].off + paSegs[i].cb <= pReq->u.In.cbImageBits, + ("SUP_IOCTL_LDR_LOAD: seg #%ld: off %#lx + cb %#lx = %#lx (max=%#lx)\n", (long)i, (long)paSegs[i].off, (long)paSegs[i].cb, (long)(paSegs[i].off + paSegs[i].cb), (long)pReq->u.In.cbImageBits)); + REQ_CHECK_EXPR_FMT(paSegs[i].fProt != 0, + ("SUP_IOCTL_LDR_LOAD: seg #%ld: off %#lx + cb %#lx\n", (long)i, (long)paSegs[i].off, (long)paSegs[i].cb)); + REQ_CHECK_EXPR_FMT(paSegs[i].fUnused == 0, ("SUP_IOCTL_LDR_LOAD: seg #%ld: fUnused=1\n", (long)i)); + REQ_CHECK_EXPR_FMT(offPrevEnd == paSegs[i].off, + ("SUP_IOCTL_LDR_LOAD: seg #%ld: off %#lx offPrevEnd %#lx\n", (long)i, (long)paSegs[i].off, (long)offPrevEnd)); + offPrevEnd = paSegs[i].off + paSegs[i].cb; + } + REQ_CHECK_EXPR_FMT(offPrevEnd == pReq->u.In.cbImageBits, + ("SUP_IOCTL_LDR_LOAD: offPrevEnd %#lx cbImageBits %#lx\n", (long)i, (long)offPrevEnd, (long)pReq->u.In.cbImageBits)); + } + REQ_CHECK_EXPR_FMT(!(pReq->u.In.fFlags & ~SUPLDRLOAD_F_VALID_MASK), + ("SUP_IOCTL_LDR_LOAD: fFlags=%#x\n", (unsigned)pReq->u.In.fFlags)); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_LdrLoad(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LDR_FREE): + { + /* validate */ + PSUPLDRFREE pReq = (PSUPLDRFREE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_LDR_FREE); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_LdrFree(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LDR_LOCK_DOWN): + { + /* validate */ + REQ_CHECK_SIZES(SUP_IOCTL_LDR_LOCK_DOWN); + + /* execute */ + pReqHdr->rc = supdrvIOCtl_LdrLockDown(pDevExt); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LDR_GET_SYMBOL): + { + /* validate */ + PSUPLDRGETSYMBOL pReq = (PSUPLDRGETSYMBOL)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_LDR_GET_SYMBOL); + REQ_CHECK_EXPR(SUP_IOCTL_LDR_GET_SYMBOL, RTStrEnd(pReq->u.In.szSymbol, sizeof(pReq->u.In.szSymbol))); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_LdrQuerySymbol(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_CALL_VMMR0_NO_SIZE()): + { + /* validate */ + PSUPCALLVMMR0 pReq = (PSUPCALLVMMR0)pReqHdr; + Log4(("SUP_IOCTL_CALL_VMMR0: op=%u in=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->u.In.uOperation, pReq->Hdr.cbIn, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + + if (pReq->Hdr.cbIn == SUP_IOCTL_CALL_VMMR0_SIZE(0)) + { + REQ_CHECK_SIZES_EX(SUP_IOCTL_CALL_VMMR0, SUP_IOCTL_CALL_VMMR0_SIZE_IN(0), SUP_IOCTL_CALL_VMMR0_SIZE_OUT(0)); + + /* execute */ + if (RT_LIKELY(pDevExt->pfnVMMR0EntryEx)) + { + if (pReq->u.In.pVMR0 == NULL) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(NULL, NULL, pReq->u.In.idCpu, + pReq->u.In.uOperation, NULL, pReq->u.In.u64Arg, pSession); + else if (pReq->u.In.pVMR0 == pSession->pSessionVM) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(pSession->pSessionGVM, pSession->pSessionVM, pReq->u.In.idCpu, + pReq->u.In.uOperation, NULL, pReq->u.In.u64Arg, pSession); + else + pReq->Hdr.rc = VERR_INVALID_VM_HANDLE; + } + else + pReq->Hdr.rc = VERR_WRONG_ORDER; + } + else + { + PSUPVMMR0REQHDR pVMMReq = (PSUPVMMR0REQHDR)&pReq->abReqPkt[0]; + REQ_CHECK_EXPR_FMT(pReq->Hdr.cbIn >= SUP_IOCTL_CALL_VMMR0_SIZE(sizeof(SUPVMMR0REQHDR)), + ("SUP_IOCTL_CALL_VMMR0: cbIn=%#x < %#lx\n", pReq->Hdr.cbIn, SUP_IOCTL_CALL_VMMR0_SIZE(sizeof(SUPVMMR0REQHDR)))); + REQ_CHECK_EXPR(SUP_IOCTL_CALL_VMMR0, pVMMReq->u32Magic == SUPVMMR0REQHDR_MAGIC); + REQ_CHECK_SIZES_EX(SUP_IOCTL_CALL_VMMR0, SUP_IOCTL_CALL_VMMR0_SIZE_IN(pVMMReq->cbReq), SUP_IOCTL_CALL_VMMR0_SIZE_OUT(pVMMReq->cbReq)); + + /* execute */ + if (RT_LIKELY(pDevExt->pfnVMMR0EntryEx)) + { + if (pReq->u.In.pVMR0 == NULL) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(NULL, NULL, pReq->u.In.idCpu, + pReq->u.In.uOperation, pVMMReq, pReq->u.In.u64Arg, pSession); + else if (pReq->u.In.pVMR0 == pSession->pSessionVM) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(pSession->pSessionGVM, pSession->pSessionVM, pReq->u.In.idCpu, + pReq->u.In.uOperation, pVMMReq, pReq->u.In.u64Arg, pSession); + else + pReq->Hdr.rc = VERR_INVALID_VM_HANDLE; + } + else + pReq->Hdr.rc = VERR_WRONG_ORDER; + } + + if ( RT_FAILURE(pReq->Hdr.rc) + && pReq->Hdr.rc != VERR_INTERRUPTED + && pReq->Hdr.rc != VERR_TIMEOUT) + Log(("SUP_IOCTL_CALL_VMMR0: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->Hdr.rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + else + Log4(("SUP_IOCTL_CALL_VMMR0: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->Hdr.rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_CALL_VMMR0_BIG): + { + /* validate */ + PSUPCALLVMMR0 pReq = (PSUPCALLVMMR0)pReqHdr; + PSUPVMMR0REQHDR pVMMReq; + Log4(("SUP_IOCTL_CALL_VMMR0_BIG: op=%u in=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->u.In.uOperation, pReq->Hdr.cbIn, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + + pVMMReq = (PSUPVMMR0REQHDR)&pReq->abReqPkt[0]; + REQ_CHECK_EXPR_FMT(pReq->Hdr.cbIn >= SUP_IOCTL_CALL_VMMR0_BIG_SIZE(sizeof(SUPVMMR0REQHDR)), + ("SUP_IOCTL_CALL_VMMR0_BIG: cbIn=%#x < %#lx\n", pReq->Hdr.cbIn, SUP_IOCTL_CALL_VMMR0_BIG_SIZE(sizeof(SUPVMMR0REQHDR)))); + REQ_CHECK_EXPR(SUP_IOCTL_CALL_VMMR0_BIG, pVMMReq->u32Magic == SUPVMMR0REQHDR_MAGIC); + REQ_CHECK_SIZES_EX(SUP_IOCTL_CALL_VMMR0_BIG, SUP_IOCTL_CALL_VMMR0_BIG_SIZE_IN(pVMMReq->cbReq), SUP_IOCTL_CALL_VMMR0_BIG_SIZE_OUT(pVMMReq->cbReq)); + + /* execute */ + if (RT_LIKELY(pDevExt->pfnVMMR0EntryEx)) + { + if (pReq->u.In.pVMR0 == NULL) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(NULL, NULL, pReq->u.In.idCpu, pReq->u.In.uOperation, pVMMReq, pReq->u.In.u64Arg, pSession); + else if (pReq->u.In.pVMR0 == pSession->pSessionVM) + pReq->Hdr.rc = pDevExt->pfnVMMR0EntryEx(pSession->pSessionGVM, pSession->pSessionVM, pReq->u.In.idCpu, + pReq->u.In.uOperation, pVMMReq, pReq->u.In.u64Arg, pSession); + else + pReq->Hdr.rc = VERR_INVALID_VM_HANDLE; + } + else + pReq->Hdr.rc = VERR_WRONG_ORDER; + + if ( RT_FAILURE(pReq->Hdr.rc) + && pReq->Hdr.rc != VERR_INTERRUPTED + && pReq->Hdr.rc != VERR_TIMEOUT) + Log(("SUP_IOCTL_CALL_VMMR0_BIG: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->Hdr.rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + else + Log4(("SUP_IOCTL_CALL_VMMR0_BIG: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->Hdr.rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_GET_PAGING_MODE): + { + /* validate */ + PSUPGETPAGINGMODE pReq = (PSUPGETPAGINGMODE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_GET_PAGING_MODE); + + /* execute */ + pReq->Hdr.rc = VINF_SUCCESS; + pReq->u.Out.enmMode = SUPR0GetPagingMode(); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LOW_ALLOC): + { + /* validate */ + PSUPLOWALLOC pReq = (PSUPLOWALLOC)pReqHdr; + REQ_CHECK_EXPR(SUP_IOCTL_LOW_ALLOC, pReq->Hdr.cbIn <= SUP_IOCTL_LOW_ALLOC_SIZE_IN); + REQ_CHECK_SIZES_EX(SUP_IOCTL_LOW_ALLOC, SUP_IOCTL_LOW_ALLOC_SIZE_IN, SUP_IOCTL_LOW_ALLOC_SIZE_OUT(pReq->u.In.cPages)); + + /* execute */ + pReq->Hdr.rc = SUPR0LowAlloc(pSession, pReq->u.In.cPages, &pReq->u.Out.pvR0, &pReq->u.Out.pvR3, &pReq->u.Out.aPages[0]); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LOW_FREE): + { + /* validate */ + PSUPLOWFREE pReq = (PSUPLOWFREE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_LOW_FREE); + + /* execute */ + pReq->Hdr.rc = SUPR0LowFree(pSession, (RTHCUINTPTR)pReq->u.In.pvR3); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_GIP_MAP): + { + /* validate */ + PSUPGIPMAP pReq = (PSUPGIPMAP)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_GIP_MAP); + + /* execute */ + pReq->Hdr.rc = SUPR0GipMap(pSession, &pReq->u.Out.pGipR3, &pReq->u.Out.HCPhysGip); + if (RT_SUCCESS(pReq->Hdr.rc)) + pReq->u.Out.pGipR0 = pDevExt->pGip; + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_GIP_UNMAP): + { + /* validate */ + PSUPGIPUNMAP pReq = (PSUPGIPUNMAP)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_GIP_UNMAP); + + /* execute */ + pReq->Hdr.rc = SUPR0GipUnmap(pSession); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_SET_VM_FOR_FAST): + { + /* validate */ + PSUPSETVMFORFAST pReq = (PSUPSETVMFORFAST)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_SET_VM_FOR_FAST); + REQ_CHECK_EXPR_FMT( !pReq->u.In.pVMR0 + || ( RT_VALID_PTR(pReq->u.In.pVMR0) + && !((uintptr_t)pReq->u.In.pVMR0 & (PAGE_SIZE - 1))), + ("SUP_IOCTL_SET_VM_FOR_FAST: pVMR0=%p!\n", pReq->u.In.pVMR0)); + + /* execute */ + RTSpinlockAcquire(pDevExt->Spinlock); + if (pSession->pSessionVM == pReq->u.In.pVMR0) + { + if (pSession->pFastIoCtrlVM == NULL) + { + pSession->pFastIoCtrlVM = pSession->pSessionVM; + RTSpinlockRelease(pDevExt->Spinlock); + pReq->Hdr.rc = VINF_SUCCESS; + } + else + { + RTSpinlockRelease(pDevExt->Spinlock); + OSDBGPRINT(("SUP_IOCTL_SET_VM_FOR_FAST: pSession->pFastIoCtrlVM=%p! (pVMR0=%p)\n", + pSession->pFastIoCtrlVM, pReq->u.In.pVMR0)); + pReq->Hdr.rc = VERR_ALREADY_EXISTS; + } + } + else + { + RTSpinlockRelease(pDevExt->Spinlock); + OSDBGPRINT(("SUP_IOCTL_SET_VM_FOR_FAST: pSession->pSessionVM=%p vs pVMR0=%p)\n", + pSession->pSessionVM, pReq->u.In.pVMR0)); + pReq->Hdr.rc = pSession->pSessionVM ? VERR_ACCESS_DENIED : VERR_WRONG_ORDER; + } + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_ALLOC_EX): + { + /* validate */ + PSUPPAGEALLOCEX pReq = (PSUPPAGEALLOCEX)pReqHdr; + REQ_CHECK_EXPR(SUP_IOCTL_PAGE_ALLOC_EX, pReq->Hdr.cbIn <= SUP_IOCTL_PAGE_ALLOC_EX_SIZE_IN); + REQ_CHECK_SIZES_EX(SUP_IOCTL_PAGE_ALLOC_EX, SUP_IOCTL_PAGE_ALLOC_EX_SIZE_IN, SUP_IOCTL_PAGE_ALLOC_EX_SIZE_OUT(pReq->u.In.cPages)); + REQ_CHECK_EXPR_FMT(pReq->u.In.fKernelMapping || pReq->u.In.fUserMapping, + ("SUP_IOCTL_PAGE_ALLOC_EX: No mapping requested!\n")); + REQ_CHECK_EXPR_FMT(pReq->u.In.fUserMapping, + ("SUP_IOCTL_PAGE_ALLOC_EX: Must have user mapping!\n")); + REQ_CHECK_EXPR_FMT(!pReq->u.In.fReserved0 && !pReq->u.In.fReserved1, + ("SUP_IOCTL_PAGE_ALLOC_EX: fReserved0=%d fReserved1=%d\n", pReq->u.In.fReserved0, pReq->u.In.fReserved1)); + + /* execute */ + pReq->Hdr.rc = SUPR0PageAllocEx(pSession, pReq->u.In.cPages, 0 /* fFlags */, + pReq->u.In.fUserMapping ? &pReq->u.Out.pvR3 : NULL, + pReq->u.In.fKernelMapping ? &pReq->u.Out.pvR0 : NULL, + &pReq->u.Out.aPages[0]); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_MAP_KERNEL): + { + /* validate */ + PSUPPAGEMAPKERNEL pReq = (PSUPPAGEMAPKERNEL)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_PAGE_MAP_KERNEL); + REQ_CHECK_EXPR_FMT(!pReq->u.In.fFlags, ("SUP_IOCTL_PAGE_MAP_KERNEL: fFlags=%#x! MBZ\n", pReq->u.In.fFlags)); + REQ_CHECK_EXPR_FMT(!(pReq->u.In.offSub & PAGE_OFFSET_MASK), ("SUP_IOCTL_PAGE_MAP_KERNEL: offSub=%#x\n", pReq->u.In.offSub)); + REQ_CHECK_EXPR_FMT(pReq->u.In.cbSub && !(pReq->u.In.cbSub & PAGE_OFFSET_MASK), + ("SUP_IOCTL_PAGE_MAP_KERNEL: cbSub=%#x\n", pReq->u.In.cbSub)); + + /* execute */ + pReq->Hdr.rc = SUPR0PageMapKernel(pSession, pReq->u.In.pvR3, pReq->u.In.offSub, pReq->u.In.cbSub, + pReq->u.In.fFlags, &pReq->u.Out.pvR0); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_PROTECT): + { + /* validate */ + PSUPPAGEPROTECT pReq = (PSUPPAGEPROTECT)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_PAGE_PROTECT); + REQ_CHECK_EXPR_FMT(!(pReq->u.In.fProt & ~(RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC | RTMEM_PROT_NONE)), + ("SUP_IOCTL_PAGE_PROTECT: fProt=%#x!\n", pReq->u.In.fProt)); + REQ_CHECK_EXPR_FMT(!(pReq->u.In.offSub & PAGE_OFFSET_MASK), ("SUP_IOCTL_PAGE_PROTECT: offSub=%#x\n", pReq->u.In.offSub)); + REQ_CHECK_EXPR_FMT(pReq->u.In.cbSub && !(pReq->u.In.cbSub & PAGE_OFFSET_MASK), + ("SUP_IOCTL_PAGE_PROTECT: cbSub=%#x\n", pReq->u.In.cbSub)); + + /* execute */ + pReq->Hdr.rc = SUPR0PageProtect(pSession, pReq->u.In.pvR3, pReq->u.In.pvR0, pReq->u.In.offSub, pReq->u.In.cbSub, pReq->u.In.fProt); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_PAGE_FREE): + { + /* validate */ + PSUPPAGEFREE pReq = (PSUPPAGEFREE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_PAGE_FREE); + + /* execute */ + pReq->Hdr.rc = SUPR0PageFree(pSession, pReq->u.In.pvR3); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_CALL_SERVICE_NO_SIZE()): + { + /* validate */ + PSUPCALLSERVICE pReq = (PSUPCALLSERVICE)pReqHdr; + Log4(("SUP_IOCTL_CALL_SERVICE: op=%u in=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + pReq->u.In.uOperation, pReq->Hdr.cbIn, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + + if (pReq->Hdr.cbIn == SUP_IOCTL_CALL_SERVICE_SIZE(0)) + REQ_CHECK_SIZES_EX(SUP_IOCTL_CALL_SERVICE, SUP_IOCTL_CALL_SERVICE_SIZE_IN(0), SUP_IOCTL_CALL_SERVICE_SIZE_OUT(0)); + else + { + PSUPR0SERVICEREQHDR pSrvReq = (PSUPR0SERVICEREQHDR)&pReq->abReqPkt[0]; + REQ_CHECK_EXPR_FMT(pReq->Hdr.cbIn >= SUP_IOCTL_CALL_SERVICE_SIZE(sizeof(SUPR0SERVICEREQHDR)), + ("SUP_IOCTL_CALL_SERVICE: cbIn=%#x < %#lx\n", pReq->Hdr.cbIn, SUP_IOCTL_CALL_SERVICE_SIZE(sizeof(SUPR0SERVICEREQHDR)))); + REQ_CHECK_EXPR(SUP_IOCTL_CALL_SERVICE, pSrvReq->u32Magic == SUPR0SERVICEREQHDR_MAGIC); + REQ_CHECK_SIZES_EX(SUP_IOCTL_CALL_SERVICE, SUP_IOCTL_CALL_SERVICE_SIZE_IN(pSrvReq->cbReq), SUP_IOCTL_CALL_SERVICE_SIZE_OUT(pSrvReq->cbReq)); + } + REQ_CHECK_EXPR(SUP_IOCTL_CALL_SERVICE, RTStrEnd(pReq->u.In.szName, sizeof(pReq->u.In.szName))); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_CallServiceModule(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_LOGGER_SETTINGS_NO_SIZE()): + { + /* validate */ + PSUPLOGGERSETTINGS pReq = (PSUPLOGGERSETTINGS)pReqHdr; + size_t cbStrTab; + REQ_CHECK_SIZE_OUT(SUP_IOCTL_LOGGER_SETTINGS, SUP_IOCTL_LOGGER_SETTINGS_SIZE_OUT); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->Hdr.cbIn >= SUP_IOCTL_LOGGER_SETTINGS_SIZE_IN(1)); + cbStrTab = pReq->Hdr.cbIn - SUP_IOCTL_LOGGER_SETTINGS_SIZE_IN(0); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->u.In.offGroups < cbStrTab); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->u.In.offFlags < cbStrTab); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->u.In.offDestination < cbStrTab); + REQ_CHECK_EXPR_FMT(pReq->u.In.szStrings[cbStrTab - 1] == '\0', + ("SUP_IOCTL_LOGGER_SETTINGS: cbIn=%#x cbStrTab=%#zx LastChar=%d\n", + pReq->Hdr.cbIn, cbStrTab, pReq->u.In.szStrings[cbStrTab - 1])); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->u.In.fWhich <= SUPLOGGERSETTINGS_WHICH_RELEASE); + REQ_CHECK_EXPR(SUP_IOCTL_LOGGER_SETTINGS, pReq->u.In.fWhat <= SUPLOGGERSETTINGS_WHAT_DESTROY); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_LoggerSettings(pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_SEM_OP2): + { + /* validate */ + PSUPSEMOP2 pReq = (PSUPSEMOP2)pReqHdr; + REQ_CHECK_SIZES_EX(SUP_IOCTL_SEM_OP2, SUP_IOCTL_SEM_OP2_SIZE_IN, SUP_IOCTL_SEM_OP2_SIZE_OUT); + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP2, pReq->u.In.uReserved == 0); + + /* execute */ + switch (pReq->u.In.uType) + { + case SUP_SEM_TYPE_EVENT: + { + SUPSEMEVENT hEvent = (SUPSEMEVENT)(uintptr_t)pReq->u.In.hSem; + switch (pReq->u.In.uOp) + { + case SUPSEMOP2_WAIT_MS_REL: + pReq->Hdr.rc = SUPSemEventWaitNoResume(pSession, hEvent, pReq->u.In.uArg.cRelMsTimeout); + break; + case SUPSEMOP2_WAIT_NS_ABS: + pReq->Hdr.rc = SUPSemEventWaitNsAbsIntr(pSession, hEvent, pReq->u.In.uArg.uAbsNsTimeout); + break; + case SUPSEMOP2_WAIT_NS_REL: + pReq->Hdr.rc = SUPSemEventWaitNsRelIntr(pSession, hEvent, pReq->u.In.uArg.cRelNsTimeout); + break; + case SUPSEMOP2_SIGNAL: + pReq->Hdr.rc = SUPSemEventSignal(pSession, hEvent); + break; + case SUPSEMOP2_CLOSE: + pReq->Hdr.rc = SUPSemEventClose(pSession, hEvent); + break; + case SUPSEMOP2_RESET: + default: + pReq->Hdr.rc = VERR_INVALID_FUNCTION; + break; + } + break; + } + + case SUP_SEM_TYPE_EVENT_MULTI: + { + SUPSEMEVENTMULTI hEventMulti = (SUPSEMEVENTMULTI)(uintptr_t)pReq->u.In.hSem; + switch (pReq->u.In.uOp) + { + case SUPSEMOP2_WAIT_MS_REL: + pReq->Hdr.rc = SUPSemEventMultiWaitNoResume(pSession, hEventMulti, pReq->u.In.uArg.cRelMsTimeout); + break; + case SUPSEMOP2_WAIT_NS_ABS: + pReq->Hdr.rc = SUPSemEventMultiWaitNsAbsIntr(pSession, hEventMulti, pReq->u.In.uArg.uAbsNsTimeout); + break; + case SUPSEMOP2_WAIT_NS_REL: + pReq->Hdr.rc = SUPSemEventMultiWaitNsRelIntr(pSession, hEventMulti, pReq->u.In.uArg.cRelNsTimeout); + break; + case SUPSEMOP2_SIGNAL: + pReq->Hdr.rc = SUPSemEventMultiSignal(pSession, hEventMulti); + break; + case SUPSEMOP2_CLOSE: + pReq->Hdr.rc = SUPSemEventMultiClose(pSession, hEventMulti); + break; + case SUPSEMOP2_RESET: + pReq->Hdr.rc = SUPSemEventMultiReset(pSession, hEventMulti); + break; + default: + pReq->Hdr.rc = VERR_INVALID_FUNCTION; + break; + } + break; + } + + default: + pReq->Hdr.rc = VERR_INVALID_PARAMETER; + break; + } + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_SEM_OP3): + { + /* validate */ + PSUPSEMOP3 pReq = (PSUPSEMOP3)pReqHdr; + REQ_CHECK_SIZES_EX(SUP_IOCTL_SEM_OP3, SUP_IOCTL_SEM_OP3_SIZE_IN, SUP_IOCTL_SEM_OP3_SIZE_OUT); + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP3, pReq->u.In.u32Reserved == 0 && pReq->u.In.u64Reserved == 0); + + /* execute */ + switch (pReq->u.In.uType) + { + case SUP_SEM_TYPE_EVENT: + { + SUPSEMEVENT hEvent = (SUPSEMEVENT)(uintptr_t)pReq->u.In.hSem; + switch (pReq->u.In.uOp) + { + case SUPSEMOP3_CREATE: + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP3, hEvent == NIL_SUPSEMEVENT); + pReq->Hdr.rc = SUPSemEventCreate(pSession, &hEvent); + pReq->u.Out.hSem = (uint32_t)(uintptr_t)hEvent; + break; + case SUPSEMOP3_GET_RESOLUTION: + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP3, hEvent == NIL_SUPSEMEVENT); + pReq->Hdr.rc = VINF_SUCCESS; + pReq->Hdr.cbOut = sizeof(*pReq); + pReq->u.Out.cNsResolution = SUPSemEventGetResolution(pSession); + break; + default: + pReq->Hdr.rc = VERR_INVALID_FUNCTION; + break; + } + break; + } + + case SUP_SEM_TYPE_EVENT_MULTI: + { + SUPSEMEVENTMULTI hEventMulti = (SUPSEMEVENTMULTI)(uintptr_t)pReq->u.In.hSem; + switch (pReq->u.In.uOp) + { + case SUPSEMOP3_CREATE: + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP3, hEventMulti == NIL_SUPSEMEVENTMULTI); + pReq->Hdr.rc = SUPSemEventMultiCreate(pSession, &hEventMulti); + pReq->u.Out.hSem = (uint32_t)(uintptr_t)hEventMulti; + break; + case SUPSEMOP3_GET_RESOLUTION: + REQ_CHECK_EXPR(SUP_IOCTL_SEM_OP3, hEventMulti == NIL_SUPSEMEVENTMULTI); + pReq->Hdr.rc = VINF_SUCCESS; + pReq->u.Out.cNsResolution = SUPSemEventMultiGetResolution(pSession); + break; + default: + pReq->Hdr.rc = VERR_INVALID_FUNCTION; + break; + } + break; + } + + default: + pReq->Hdr.rc = VERR_INVALID_PARAMETER; + break; + } + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_VT_CAPS): + { + /* validate */ + PSUPVTCAPS pReq = (PSUPVTCAPS)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_VT_CAPS); + + /* execute */ + pReq->Hdr.rc = SUPR0QueryVTCaps(pSession, &pReq->u.Out.fCaps); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_OPEN): + { + /* validate */ + PSUPTRACEROPEN pReq = (PSUPTRACEROPEN)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_OPEN); + + /* execute */ + pReq->Hdr.rc = supdrvIOCtl_TracerOpen(pDevExt, pSession, pReq->u.In.uCookie, pReq->u.In.uArg); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_CLOSE): + { + /* validate */ + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_CLOSE); + + /* execute */ + pReqHdr->rc = supdrvIOCtl_TracerClose(pDevExt, pSession); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_IOCTL): + { + /* validate */ + PSUPTRACERIOCTL pReq = (PSUPTRACERIOCTL)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_IOCTL); + + /* execute */ + pReqHdr->rc = supdrvIOCtl_TracerIOCtl(pDevExt, pSession, pReq->u.In.uCmd, pReq->u.In.uArg, &pReq->u.Out.iRetVal); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_UMOD_REG): + { + /* validate */ + PSUPTRACERUMODREG pReq = (PSUPTRACERUMODREG)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_UMOD_REG); + if (!RTStrEnd(pReq->u.In.szName, sizeof(pReq->u.In.szName))) + return VERR_INVALID_PARAMETER; + + /* execute */ + pReqHdr->rc = supdrvIOCtl_TracerUmodRegister(pDevExt, pSession, + pReq->u.In.R3PtrVtgHdr, pReq->u.In.uVtgHdrAddr, + pReq->u.In.R3PtrStrTab, pReq->u.In.cbStrTab, + pReq->u.In.szName, pReq->u.In.fFlags); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_UMOD_DEREG): + { + /* validate */ + PSUPTRACERUMODDEREG pReq = (PSUPTRACERUMODDEREG)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_UMOD_DEREG); + + /* execute */ + pReqHdr->rc = supdrvIOCtl_TracerUmodDeregister(pDevExt, pSession, pReq->u.In.pVtgHdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TRACER_UMOD_FIRE_PROBE): + { + /* validate */ + PSUPTRACERUMODFIREPROBE pReq = (PSUPTRACERUMODFIREPROBE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TRACER_UMOD_FIRE_PROBE); + + supdrvIOCtl_TracerUmodProbeFire(pDevExt, pSession, &pReq->u.In); + pReqHdr->rc = VINF_SUCCESS; + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_MSR_PROBER): + { + /* validate */ + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_MSR_PROBER); + REQ_CHECK_EXPR(SUP_IOCTL_MSR_PROBER, + pReq->u.In.enmOp > SUPMSRPROBEROP_INVALID && pReq->u.In.enmOp < SUPMSRPROBEROP_END); + + pReqHdr->rc = supdrvIOCtl_MsrProber(pDevExt, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_RESUME_SUSPENDED_KBDS): + { + /* validate */ + REQ_CHECK_SIZES(SUP_IOCTL_RESUME_SUSPENDED_KBDS); + + pReqHdr->rc = supdrvIOCtl_ResumeSuspendedKbds(); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TSC_DELTA_MEASURE): + { + /* validate */ + PSUPTSCDELTAMEASURE pReq = (PSUPTSCDELTAMEASURE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TSC_DELTA_MEASURE); + + pReqHdr->rc = supdrvIOCtl_TscDeltaMeasure(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_TSC_READ): + { + /* validate */ + PSUPTSCREAD pReq = (PSUPTSCREAD)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_TSC_READ); + + pReqHdr->rc = supdrvIOCtl_TscRead(pDevExt, pSession, pReq); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_GIP_SET_FLAGS): + { + /* validate */ + PSUPGIPSETFLAGS pReq = (PSUPGIPSETFLAGS)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_GIP_SET_FLAGS); + + pReqHdr->rc = supdrvIOCtl_GipSetFlags(pDevExt, pSession, pReq->u.In.fOrMask, pReq->u.In.fAndMask); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_UCODE_REV): + { + /* validate */ + PSUPUCODEREV pReq = (PSUPUCODEREV)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_UCODE_REV); + + /* execute */ + pReq->Hdr.rc = SUPR0QueryUcodeRev(pSession, &pReq->u.Out.MicrocodeRev); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_GET_HWVIRT_MSRS): + { + /* validate */ + PSUPGETHWVIRTMSRS pReq = (PSUPGETHWVIRTMSRS)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_GET_HWVIRT_MSRS); + REQ_CHECK_EXPR_FMT(!pReq->u.In.fReserved0 && !pReq->u.In.fReserved1 && !pReq->u.In.fReserved2, + ("SUP_IOCTL_GET_HWVIRT_MSRS: fReserved0=%d fReserved1=%d fReserved2=%d\n", pReq->u.In.fReserved0, + pReq->u.In.fReserved1, pReq->u.In.fReserved2)); + + /* execute */ + pReq->Hdr.rc = SUPR0GetHwvirtMsrs(&pReq->u.Out.HwvirtMsrs, 0 /* fCaps */, pReq->u.In.fForce); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + default: + Log(("Unknown IOCTL %#lx\n", (long)uIOCtl)); + break; + } + return VERR_GENERAL_FAILURE; +} + + +/** + * I/O Control inner worker for the restricted operations. + * + * @returns IPRT status code. + * @retval VERR_INVALID_PARAMETER if the request is invalid. + * + * @param uIOCtl Function number. + * @param pDevExt Device extention. + * @param pSession Session data. + * @param pReqHdr The request header. + */ +static int supdrvIOCtlInnerRestricted(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPREQHDR pReqHdr) +{ + /* + * The switch. + */ + switch (SUP_CTL_CODE_NO_SIZE(uIOCtl)) + { + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_COOKIE): + { + PSUPCOOKIE pReq = (PSUPCOOKIE)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_COOKIE); + if (strncmp(pReq->u.In.szMagic, SUPCOOKIE_MAGIC, sizeof(pReq->u.In.szMagic))) + { + OSDBGPRINT(("SUP_IOCTL_COOKIE: invalid magic %.16s\n", pReq->u.In.szMagic)); + pReq->Hdr.rc = VERR_INVALID_MAGIC; + return 0; + } + + /* + * Match the version. + * The current logic is very simple, match the major interface version. + */ + if ( pReq->u.In.u32MinVersion > SUPDRV_IOC_VERSION + || (pReq->u.In.u32MinVersion & 0xffff0000) != (SUPDRV_IOC_VERSION & 0xffff0000)) + { + OSDBGPRINT(("SUP_IOCTL_COOKIE: Version mismatch. Requested: %#x Min: %#x Current: %#x\n", + pReq->u.In.u32ReqVersion, pReq->u.In.u32MinVersion, SUPDRV_IOC_VERSION)); + pReq->u.Out.u32Cookie = 0xffffffff; + pReq->u.Out.u32SessionCookie = 0xffffffff; + pReq->u.Out.u32SessionVersion = 0xffffffff; + pReq->u.Out.u32DriverVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.pSession = NULL; + pReq->u.Out.cFunctions = 0; + pReq->Hdr.rc = VERR_VERSION_MISMATCH; + return 0; + } + + /* + * Fill in return data and be gone. + * N.B. The first one to change SUPDRV_IOC_VERSION shall makes sure that + * u32SessionVersion <= u32ReqVersion! + */ + /** @todo Somehow validate the client and negotiate a secure cookie... */ + pReq->u.Out.u32Cookie = pDevExt->u32Cookie; + pReq->u.Out.u32SessionCookie = pSession->u32Cookie; + pReq->u.Out.u32SessionVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.u32DriverVersion = SUPDRV_IOC_VERSION; + pReq->u.Out.pSession = NULL; + pReq->u.Out.cFunctions = 0; + pReq->Hdr.rc = VINF_SUCCESS; + return 0; + } + + case SUP_CTL_CODE_NO_SIZE(SUP_IOCTL_VT_CAPS): + { + /* validate */ + PSUPVTCAPS pReq = (PSUPVTCAPS)pReqHdr; + REQ_CHECK_SIZES(SUP_IOCTL_VT_CAPS); + + /* execute */ + pReq->Hdr.rc = SUPR0QueryVTCaps(pSession, &pReq->u.Out.fCaps); + if (RT_FAILURE(pReq->Hdr.rc)) + pReq->Hdr.cbOut = sizeof(pReq->Hdr); + return 0; + } + + default: + Log(("Unknown IOCTL %#lx\n", (long)uIOCtl)); + break; + } + return VERR_GENERAL_FAILURE; +} + + +/** + * I/O Control worker. + * + * @returns IPRT status code. + * @retval VERR_INVALID_PARAMETER if the request is invalid. + * + * @param uIOCtl Function number. + * @param pDevExt Device extention. + * @param pSession Session data. + * @param pReqHdr The request header. + * @param cbReq The size of the request buffer. + */ +int VBOXCALL supdrvIOCtl(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPREQHDR pReqHdr, size_t cbReq) +{ + int rc; + VBOXDRV_IOCTL_ENTRY(pSession, uIOCtl, pReqHdr); + + /* + * Validate the request. + */ + if (RT_UNLIKELY(cbReq < sizeof(*pReqHdr))) + { + OSDBGPRINT(("vboxdrv: Bad ioctl request size; cbReq=%#lx\n", (long)cbReq)); + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, VERR_INVALID_PARAMETER, VINF_SUCCESS); + return VERR_INVALID_PARAMETER; + } + if (RT_UNLIKELY( (pReqHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC + || pReqHdr->cbIn < sizeof(*pReqHdr) + || pReqHdr->cbIn > cbReq + || pReqHdr->cbOut < sizeof(*pReqHdr) + || pReqHdr->cbOut > cbReq)) + { + OSDBGPRINT(("vboxdrv: Bad ioctl request header; cbIn=%#lx cbOut=%#lx fFlags=%#lx\n", + (long)pReqHdr->cbIn, (long)pReqHdr->cbOut, (long)pReqHdr->fFlags)); + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, VERR_INVALID_PARAMETER, VINF_SUCCESS); + return VERR_INVALID_PARAMETER; + } + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) + { + OSDBGPRINT(("vboxdrv: Invalid pSession value %p (ioctl=%p)\n", pSession, (void *)uIOCtl)); + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, VERR_INVALID_PARAMETER, VINF_SUCCESS); + return VERR_INVALID_PARAMETER; + } + if (RT_UNLIKELY(uIOCtl == SUP_IOCTL_COOKIE)) + { + if (pReqHdr->u32Cookie != SUPCOOKIE_INITIAL_COOKIE) + { + OSDBGPRINT(("SUP_IOCTL_COOKIE: bad cookie %#lx\n", (long)pReqHdr->u32Cookie)); + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, VERR_INVALID_PARAMETER, VINF_SUCCESS); + return VERR_INVALID_PARAMETER; + } + } + else if (RT_UNLIKELY( pReqHdr->u32Cookie != pDevExt->u32Cookie + || pReqHdr->u32SessionCookie != pSession->u32Cookie)) + { + OSDBGPRINT(("vboxdrv: bad cookie %#lx / %#lx.\n", (long)pReqHdr->u32Cookie, (long)pReqHdr->u32SessionCookie)); + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, VERR_INVALID_PARAMETER, VINF_SUCCESS); + return VERR_INVALID_PARAMETER; + } + + /* + * Hand it to an inner function to avoid lots of unnecessary return tracepoints. + */ + if (pSession->fUnrestricted) + rc = supdrvIOCtlInnerUnrestricted(uIOCtl, pDevExt, pSession, pReqHdr); + else + rc = supdrvIOCtlInnerRestricted(uIOCtl, pDevExt, pSession, pReqHdr); + + VBOXDRV_IOCTL_RETURN(pSession, uIOCtl, pReqHdr, pReqHdr->rc, rc); + return rc; +} + + +/** + * Inter-Driver Communication (IDC) worker. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the request is invalid. + * @retval VERR_NOT_SUPPORTED if the request isn't supported. + * + * @param uReq The request (function) code. + * @param pDevExt Device extention. + * @param pSession Session data. + * @param pReqHdr The request header. + */ +int VBOXCALL supdrvIDC(uintptr_t uReq, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVIDCREQHDR pReqHdr) +{ + /* + * The OS specific code has already validated the pSession + * pointer, and the request size being greater or equal to + * size of the header. + * + * So, just check that pSession is a kernel context session. + */ + if (RT_UNLIKELY( pSession + && pSession->R0Process != NIL_RTR0PROCESS)) + return VERR_INVALID_PARAMETER; + +/* + * Validation macro. + */ +#define REQ_CHECK_IDC_SIZE(Name, cbExpect) \ + do { \ + if (RT_UNLIKELY(pReqHdr->cb != (cbExpect))) \ + { \ + OSDBGPRINT(( #Name ": Invalid input/output sizes. cb=%ld expected %ld.\n", \ + (long)pReqHdr->cb, (long)(cbExpect))); \ + return pReqHdr->rc = VERR_INVALID_PARAMETER; \ + } \ + } while (0) + + switch (uReq) + { + case SUPDRV_IDC_REQ_CONNECT: + { + PSUPDRVIDCREQCONNECT pReq = (PSUPDRVIDCREQCONNECT)pReqHdr; + REQ_CHECK_IDC_SIZE(SUPDRV_IDC_REQ_CONNECT, sizeof(*pReq)); + + /* + * Validate the cookie and other input. + */ + if (pReq->Hdr.pSession != NULL) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: Hdr.pSession=%p expected NULL!\n", pReq->Hdr.pSession)); + return pReqHdr->rc = VERR_INVALID_PARAMETER; + } + if (pReq->u.In.u32MagicCookie != SUPDRVIDCREQ_CONNECT_MAGIC_COOKIE) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: u32MagicCookie=%#x expected %#x!\n", + (unsigned)pReq->u.In.u32MagicCookie, (unsigned)SUPDRVIDCREQ_CONNECT_MAGIC_COOKIE)); + return pReqHdr->rc = VERR_INVALID_PARAMETER; + } + if ( pReq->u.In.uMinVersion > pReq->u.In.uReqVersion + || (pReq->u.In.uMinVersion & UINT32_C(0xffff0000)) != (pReq->u.In.uReqVersion & UINT32_C(0xffff0000))) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: uMinVersion=%#x uMaxVersion=%#x doesn't match!\n", + pReq->u.In.uMinVersion, pReq->u.In.uReqVersion)); + return pReqHdr->rc = VERR_INVALID_PARAMETER; + } + if (pSession != NULL) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: pSession=%p expected NULL!\n", pSession)); + return pReqHdr->rc = VERR_INVALID_PARAMETER; + } + + /* + * Match the version. + * The current logic is very simple, match the major interface version. + */ + if ( pReq->u.In.uMinVersion > SUPDRV_IDC_VERSION + || (pReq->u.In.uMinVersion & 0xffff0000) != (SUPDRV_IDC_VERSION & 0xffff0000)) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: Version mismatch. Requested: %#x Min: %#x Current: %#x\n", + pReq->u.In.uReqVersion, pReq->u.In.uMinVersion, (unsigned)SUPDRV_IDC_VERSION)); + pReq->u.Out.pSession = NULL; + pReq->u.Out.uSessionVersion = 0xffffffff; + pReq->u.Out.uDriverVersion = SUPDRV_IDC_VERSION; + pReq->u.Out.uDriverRevision = VBOX_SVN_REV; + pReq->Hdr.rc = VERR_VERSION_MISMATCH; + return VINF_SUCCESS; + } + + pReq->u.Out.pSession = NULL; + pReq->u.Out.uSessionVersion = SUPDRV_IDC_VERSION; + pReq->u.Out.uDriverVersion = SUPDRV_IDC_VERSION; + pReq->u.Out.uDriverRevision = VBOX_SVN_REV; + + pReq->Hdr.rc = supdrvCreateSession(pDevExt, false /* fUser */, true /*fUnrestricted*/, &pSession); + if (RT_FAILURE(pReq->Hdr.rc)) + { + OSDBGPRINT(("SUPDRV_IDC_REQ_CONNECT: failed to create session, rc=%d\n", pReq->Hdr.rc)); + return VINF_SUCCESS; + } + + pReq->u.Out.pSession = pSession; + pReq->Hdr.pSession = pSession; + + return VINF_SUCCESS; + } + + case SUPDRV_IDC_REQ_DISCONNECT: + { + REQ_CHECK_IDC_SIZE(SUPDRV_IDC_REQ_DISCONNECT, sizeof(*pReqHdr)); + + supdrvSessionRelease(pSession); + return pReqHdr->rc = VINF_SUCCESS; + } + + case SUPDRV_IDC_REQ_GET_SYMBOL: + { + PSUPDRVIDCREQGETSYM pReq = (PSUPDRVIDCREQGETSYM)pReqHdr; + REQ_CHECK_IDC_SIZE(SUPDRV_IDC_REQ_GET_SYMBOL, sizeof(*pReq)); + + pReq->Hdr.rc = supdrvIDC_LdrGetSymbol(pDevExt, pSession, pReq); + return VINF_SUCCESS; + } + + case SUPDRV_IDC_REQ_COMPONENT_REGISTER_FACTORY: + { + PSUPDRVIDCREQCOMPREGFACTORY pReq = (PSUPDRVIDCREQCOMPREGFACTORY)pReqHdr; + REQ_CHECK_IDC_SIZE(SUPDRV_IDC_REQ_COMPONENT_REGISTER_FACTORY, sizeof(*pReq)); + + pReq->Hdr.rc = SUPR0ComponentRegisterFactory(pSession, pReq->u.In.pFactory); + return VINF_SUCCESS; + } + + case SUPDRV_IDC_REQ_COMPONENT_DEREGISTER_FACTORY: + { + PSUPDRVIDCREQCOMPDEREGFACTORY pReq = (PSUPDRVIDCREQCOMPDEREGFACTORY)pReqHdr; + REQ_CHECK_IDC_SIZE(SUPDRV_IDC_REQ_COMPONENT_DEREGISTER_FACTORY, sizeof(*pReq)); + + pReq->Hdr.rc = SUPR0ComponentDeregisterFactory(pSession, pReq->u.In.pFactory); + return VINF_SUCCESS; + } + + default: + Log(("Unknown IDC %#lx\n", (long)uReq)); + break; + } + +#undef REQ_CHECK_IDC_SIZE + return VERR_NOT_SUPPORTED; +} + + +/** + * Register a object for reference counting. + * The object is registered with one reference in the specified session. + * + * @returns Unique identifier on success (pointer). + * All future reference must use this identifier. + * @returns NULL on failure. + * @param pSession The caller's session. + * @param enmType The object type. + * @param pfnDestructor The destructore function which will be called when the reference count reaches 0. + * @param pvUser1 The first user argument. + * @param pvUser2 The second user argument. + */ +SUPR0DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType, PFNSUPDRVDESTRUCTOR pfnDestructor, void *pvUser1, void *pvUser2) +{ + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + PSUPDRVOBJ pObj; + PSUPDRVUSAGE pUsage; + + /* + * Validate the input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), NULL); + AssertReturn(enmType > SUPDRVOBJTYPE_INVALID && enmType < SUPDRVOBJTYPE_END, NULL); + AssertPtrReturn(pfnDestructor, NULL); + + /* + * Allocate and initialize the object. + */ + pObj = (PSUPDRVOBJ)RTMemAlloc(sizeof(*pObj)); + if (!pObj) + return NULL; + pObj->u32Magic = SUPDRVOBJ_MAGIC; + pObj->enmType = enmType; + pObj->pNext = NULL; + pObj->cUsage = 1; + pObj->pfnDestructor = pfnDestructor; + pObj->pvUser1 = pvUser1; + pObj->pvUser2 = pvUser2; + pObj->CreatorUid = pSession->Uid; + pObj->CreatorGid = pSession->Gid; + pObj->CreatorProcess= pSession->Process; + supdrvOSObjInitCreator(pObj, pSession); + + /* + * Allocate the usage record. + * (We keep freed usage records around to simplify SUPR0ObjAddRefEx().) + */ + RTSpinlockAcquire(pDevExt->Spinlock); + + pUsage = pDevExt->pUsageFree; + if (pUsage) + pDevExt->pUsageFree = pUsage->pNext; + else + { + RTSpinlockRelease(pDevExt->Spinlock); + pUsage = (PSUPDRVUSAGE)RTMemAlloc(sizeof(*pUsage)); + if (!pUsage) + { + RTMemFree(pObj); + return NULL; + } + RTSpinlockAcquire(pDevExt->Spinlock); + } + + /* + * Insert the object and create the session usage record. + */ + /* The object. */ + pObj->pNext = pDevExt->pObjs; + pDevExt->pObjs = pObj; + + /* The session record. */ + pUsage->cUsage = 1; + pUsage->pObj = pObj; + pUsage->pNext = pSession->pUsage; + /* Log2(("SUPR0ObjRegister: pUsage=%p:{.pObj=%p, .pNext=%p}\n", pUsage, pUsage->pObj, pUsage->pNext)); */ + pSession->pUsage = pUsage; + + RTSpinlockRelease(pDevExt->Spinlock); + + Log(("SUPR0ObjRegister: returns %p (pvUser1=%p, pvUser=%p)\n", pObj, pvUser1, pvUser2)); + return pObj; +} +SUPR0_EXPORT_SYMBOL(SUPR0ObjRegister); + + +/** + * Increment the reference counter for the object associating the reference + * with the specified session. + * + * @returns IPRT status code. + * @param pvObj The identifier returned by SUPR0ObjRegister(). + * @param pSession The session which is referencing the object. + * + * @remarks The caller should not own any spinlocks and must carefully protect + * itself against potential race with the destructor so freed memory + * isn't accessed here. + */ +SUPR0DECL(int) SUPR0ObjAddRef(void *pvObj, PSUPDRVSESSION pSession) +{ + return SUPR0ObjAddRefEx(pvObj, pSession, false /* fNoBlocking */); +} +SUPR0_EXPORT_SYMBOL(SUPR0ObjAddRef); + + +/** + * Increment the reference counter for the object associating the reference + * with the specified session. + * + * @returns IPRT status code. + * @retval VERR_TRY_AGAIN if fNoBlocking was set and a new usage record + * couldn't be allocated. (If you see this you're not doing the right + * thing and it won't ever work reliably.) + * + * @param pvObj The identifier returned by SUPR0ObjRegister(). + * @param pSession The session which is referencing the object. + * @param fNoBlocking Set if it's not OK to block. Never try to make the + * first reference to an object in a session with this + * argument set. + * + * @remarks The caller should not own any spinlocks and must carefully protect + * itself against potential race with the destructor so freed memory + * isn't accessed here. + */ +SUPR0DECL(int) SUPR0ObjAddRefEx(void *pvObj, PSUPDRVSESSION pSession, bool fNoBlocking) +{ + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + PSUPDRVOBJ pObj = (PSUPDRVOBJ)pvObj; + int rc = VINF_SUCCESS; + PSUPDRVUSAGE pUsagePre; + PSUPDRVUSAGE pUsage; + + /* + * Validate the input. + * Be ready for the destruction race (someone might be stuck in the + * destructor waiting a lock we own). + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(pObj, VERR_INVALID_POINTER); + AssertMsgReturn(pObj->u32Magic == SUPDRVOBJ_MAGIC || pObj->u32Magic == SUPDRVOBJ_MAGIC_DEAD, + ("Invalid pvObj=%p magic=%#x (expected %#x or %#x)\n", pvObj, pObj->u32Magic, SUPDRVOBJ_MAGIC, SUPDRVOBJ_MAGIC_DEAD), + VERR_INVALID_PARAMETER); + + RTSpinlockAcquire(pDevExt->Spinlock); + + if (RT_UNLIKELY(pObj->u32Magic != SUPDRVOBJ_MAGIC)) + { + RTSpinlockRelease(pDevExt->Spinlock); + + AssertMsgFailed(("pvObj=%p magic=%#x\n", pvObj, pObj->u32Magic)); + return VERR_WRONG_ORDER; + } + + /* + * Preallocate the usage record if we can. + */ + pUsagePre = pDevExt->pUsageFree; + if (pUsagePre) + pDevExt->pUsageFree = pUsagePre->pNext; + else if (!fNoBlocking) + { + RTSpinlockRelease(pDevExt->Spinlock); + pUsagePre = (PSUPDRVUSAGE)RTMemAlloc(sizeof(*pUsagePre)); + if (!pUsagePre) + return VERR_NO_MEMORY; + + RTSpinlockAcquire(pDevExt->Spinlock); + if (RT_UNLIKELY(pObj->u32Magic != SUPDRVOBJ_MAGIC)) + { + RTSpinlockRelease(pDevExt->Spinlock); + + AssertMsgFailed(("pvObj=%p magic=%#x\n", pvObj, pObj->u32Magic)); + return VERR_WRONG_ORDER; + } + } + + /* + * Reference the object. + */ + pObj->cUsage++; + + /* + * Look for the session record. + */ + for (pUsage = pSession->pUsage; pUsage; pUsage = pUsage->pNext) + { + /*Log(("SUPR0AddRef: pUsage=%p:{.pObj=%p, .pNext=%p}\n", pUsage, pUsage->pObj, pUsage->pNext));*/ + if (pUsage->pObj == pObj) + break; + } + if (pUsage) + pUsage->cUsage++; + else if (pUsagePre) + { + /* create a new session record. */ + pUsagePre->cUsage = 1; + pUsagePre->pObj = pObj; + pUsagePre->pNext = pSession->pUsage; + pSession->pUsage = pUsagePre; + /*Log(("SUPR0AddRef: pUsagePre=%p:{.pObj=%p, .pNext=%p}\n", pUsagePre, pUsagePre->pObj, pUsagePre->pNext));*/ + + pUsagePre = NULL; + } + else + { + pObj->cUsage--; + rc = VERR_TRY_AGAIN; + } + + /* + * Put any unused usage record into the free list.. + */ + if (pUsagePre) + { + pUsagePre->pNext = pDevExt->pUsageFree; + pDevExt->pUsageFree = pUsagePre; + } + + RTSpinlockRelease(pDevExt->Spinlock); + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ObjAddRefEx); + + +/** + * Decrement / destroy a reference counter record for an object. + * + * The object is uniquely identified by pfnDestructor+pvUser1+pvUser2. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if not destroyed. + * @retval VINF_OBJECT_DESTROYED if it's destroyed by this release call. + * @retval VERR_INVALID_PARAMETER if the object isn't valid. Will assert in + * string builds. + * + * @param pvObj The identifier returned by SUPR0ObjRegister(). + * @param pSession The session which is referencing the object. + */ +SUPR0DECL(int) SUPR0ObjRelease(void *pvObj, PSUPDRVSESSION pSession) +{ + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + PSUPDRVOBJ pObj = (PSUPDRVOBJ)pvObj; + int rc = VERR_INVALID_PARAMETER; + PSUPDRVUSAGE pUsage; + PSUPDRVUSAGE pUsagePrev; + + /* + * Validate the input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertMsgReturn(RT_VALID_PTR(pObj) && pObj->u32Magic == SUPDRVOBJ_MAGIC, + ("Invalid pvObj=%p magic=%#x (expected %#x)\n", pvObj, pObj ? pObj->u32Magic : 0, SUPDRVOBJ_MAGIC), + VERR_INVALID_PARAMETER); + + /* + * Acquire the spinlock and look for the usage record. + */ + RTSpinlockAcquire(pDevExt->Spinlock); + + for (pUsagePrev = NULL, pUsage = pSession->pUsage; + pUsage; + pUsagePrev = pUsage, pUsage = pUsage->pNext) + { + /*Log2(("SUPR0ObjRelease: pUsage=%p:{.pObj=%p, .pNext=%p}\n", pUsage, pUsage->pObj, pUsage->pNext));*/ + if (pUsage->pObj == pObj) + { + rc = VINF_SUCCESS; + AssertMsg(pUsage->cUsage >= 1 && pObj->cUsage >= pUsage->cUsage, ("glob %d; sess %d\n", pObj->cUsage, pUsage->cUsage)); + if (pUsage->cUsage > 1) + { + pObj->cUsage--; + pUsage->cUsage--; + } + else + { + /* + * Free the session record. + */ + if (pUsagePrev) + pUsagePrev->pNext = pUsage->pNext; + else + pSession->pUsage = pUsage->pNext; + pUsage->pNext = pDevExt->pUsageFree; + pDevExt->pUsageFree = pUsage; + + /* What about the object? */ + if (pObj->cUsage > 1) + pObj->cUsage--; + else + { + /* + * Object is to be destroyed, unlink it. + */ + pObj->u32Magic = SUPDRVOBJ_MAGIC_DEAD; + rc = VINF_OBJECT_DESTROYED; + if (pDevExt->pObjs == pObj) + pDevExt->pObjs = pObj->pNext; + else + { + PSUPDRVOBJ pObjPrev; + for (pObjPrev = pDevExt->pObjs; pObjPrev; pObjPrev = pObjPrev->pNext) + if (pObjPrev->pNext == pObj) + { + pObjPrev->pNext = pObj->pNext; + break; + } + Assert(pObjPrev); + } + } + } + break; + } + } + + RTSpinlockRelease(pDevExt->Spinlock); + + /* + * Call the destructor and free the object if required. + */ + if (rc == VINF_OBJECT_DESTROYED) + { + Log(("SUPR0ObjRelease: destroying %p/%d (%p/%p) cpid=%RTproc pid=%RTproc dtor=%p\n", + pObj, pObj->enmType, pObj->pvUser1, pObj->pvUser2, pObj->CreatorProcess, RTProcSelf(), pObj->pfnDestructor)); + if (pObj->pfnDestructor) + pObj->pfnDestructor(pObj, pObj->pvUser1, pObj->pvUser2); + RTMemFree(pObj); + } + + AssertMsg(pUsage, ("pvObj=%p\n", pvObj)); + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ObjRelease); + + +/** + * Verifies that the current process can access the specified object. + * + * @returns The following IPRT status code: + * @retval VINF_SUCCESS if access was granted. + * @retval VERR_PERMISSION_DENIED if denied access. + * @retval VERR_INVALID_PARAMETER if invalid parameter. + * + * @param pvObj The identifier returned by SUPR0ObjRegister(). + * @param pSession The session which wishes to access the object. + * @param pszObjName Object string name. This is optional and depends on the object type. + * + * @remark The caller is responsible for making sure the object isn't removed while + * we're inside this function. If uncertain about this, just call AddRef before calling us. + */ +SUPR0DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, const char *pszObjName) +{ + PSUPDRVOBJ pObj = (PSUPDRVOBJ)pvObj; + int rc; + + /* + * Validate the input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertMsgReturn(RT_VALID_PTR(pObj) && pObj->u32Magic == SUPDRVOBJ_MAGIC, + ("Invalid pvObj=%p magic=%#x (exepcted %#x)\n", pvObj, pObj ? pObj->u32Magic : 0, SUPDRVOBJ_MAGIC), + VERR_INVALID_PARAMETER); + + /* + * Check access. (returns true if a decision has been made.) + */ + rc = VERR_INTERNAL_ERROR; + if (supdrvOSObjCanAccess(pObj, pSession, pszObjName, &rc)) + return rc; + + /* + * Default policy is to allow the user to access his own + * stuff but nothing else. + */ + if (pObj->CreatorUid == pSession->Uid) + return VINF_SUCCESS; + return VERR_PERMISSION_DENIED; +} +SUPR0_EXPORT_SYMBOL(SUPR0ObjVerifyAccess); + + +/** + * API for the VMMR0 module to get the SUPDRVSESSION::pSessionVM member. + * + * @returns The associated VM pointer. + * @param pSession The session of the current thread. + */ +SUPR0DECL(PVM) SUPR0GetSessionVM(PSUPDRVSESSION pSession) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), NULL); + return pSession->pSessionVM; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetSessionVM); + + +/** + * API for the VMMR0 module to get the SUPDRVSESSION::pSessionGVM member. + * + * @returns The associated GVM pointer. + * @param pSession The session of the current thread. + */ +SUPR0DECL(PGVM) SUPR0GetSessionGVM(PSUPDRVSESSION pSession) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), NULL); + return pSession->pSessionGVM; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetSessionGVM); + + +/** + * API for the VMMR0 module to work the SUPDRVSESSION::pSessionVM member. + * + * This will fail if there is already a VM associated with the session and pVM + * isn't NULL. + * + * @retval VINF_SUCCESS + * @retval VERR_ALREADY_EXISTS if there already is a VM associated with the + * session. + * @retval VERR_INVALID_PARAMETER if only one of the parameters are NULL or if + * the session is invalid. + * + * @param pSession The session of the current thread. + * @param pGVM The GVM to associate with the session. Pass NULL to + * dissassociate. + * @param pVM The VM to associate with the session. Pass NULL to + * dissassociate. + */ +SUPR0DECL(int) SUPR0SetSessionVM(PSUPDRVSESSION pSession, PGVM pGVM, PVM pVM) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn((pGVM != NULL) == (pVM != NULL), VERR_INVALID_PARAMETER); + + RTSpinlockAcquire(pSession->pDevExt->Spinlock); + if (pGVM) + { + if (!pSession->pSessionGVM) + { + pSession->pSessionGVM = pGVM; + pSession->pSessionVM = pVM; + pSession->pFastIoCtrlVM = NULL; + } + else + { + RTSpinlockRelease(pSession->pDevExt->Spinlock); + SUPR0Printf("SUPR0SetSessionVM: Unable to associated GVM/VM %p/%p with session %p as it has %p/%p already!\n", + pGVM, pVM, pSession, pSession->pSessionGVM, pSession->pSessionVM); + return VERR_ALREADY_EXISTS; + } + } + else + { + pSession->pSessionGVM = NULL; + pSession->pSessionVM = NULL; + pSession->pFastIoCtrlVM = NULL; + } + RTSpinlockRelease(pSession->pDevExt->Spinlock); + return VINF_SUCCESS; +} +SUPR0_EXPORT_SYMBOL(SUPR0SetSessionVM); + + +/** + * For getting SUPDRVSESSION::Uid. + * + * @returns The session UID. NIL_RTUID if invalid pointer or not successfully + * set by the host code. + * @param pSession The session of the current thread. + */ +SUPR0DECL(RTUID) SUPR0GetSessionUid(PSUPDRVSESSION pSession) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), NIL_RTUID); + return pSession->Uid; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetSessionUid); + + +/** @copydoc RTLogDefaultInstanceEx + * @remarks To allow overriding RTLogDefaultInstanceEx locally. */ +SUPR0DECL(struct RTLOGGER *) SUPR0DefaultLogInstanceEx(uint32_t fFlagsAndGroup) +{ + return RTLogDefaultInstanceEx(fFlagsAndGroup); +} +SUPR0_EXPORT_SYMBOL(SUPR0DefaultLogInstanceEx); + + +/** @copydoc RTLogGetDefaultInstanceEx + * @remarks To allow overriding RTLogGetDefaultInstanceEx locally. */ +SUPR0DECL(struct RTLOGGER *) SUPR0GetDefaultLogInstanceEx(uint32_t fFlagsAndGroup) +{ + return RTLogGetDefaultInstanceEx(fFlagsAndGroup); +} +SUPR0_EXPORT_SYMBOL(SUPR0GetDefaultLogInstanceEx); + + +/** @copydoc RTLogRelGetDefaultInstanceEx + * @remarks To allow overriding RTLogRelGetDefaultInstanceEx locally. */ +SUPR0DECL(struct RTLOGGER *) SUPR0GetDefaultLogRelInstanceEx(uint32_t fFlagsAndGroup) +{ + return RTLogRelGetDefaultInstanceEx(fFlagsAndGroup); +} +SUPR0_EXPORT_SYMBOL(SUPR0GetDefaultLogRelInstanceEx); + + +/** + * Lock pages. + * + * @returns IPRT status code. + * @param pSession Session to which the locked memory should be associated. + * @param pvR3 Start of the memory range to lock. + * This must be page aligned. + * @param cPages Number of pages to lock. + * @param paPages Where to put the physical addresses of locked memory. + */ +SUPR0DECL(int) SUPR0LockMem(PSUPDRVSESSION pSession, RTR3PTR pvR3, uint32_t cPages, PRTHCPHYS paPages) +{ + int rc; + SUPDRVMEMREF Mem = { NIL_RTR0MEMOBJ, NIL_RTR0MEMOBJ, MEMREF_TYPE_UNUSED }; + const size_t cb = (size_t)cPages << PAGE_SHIFT; + LogFlow(("SUPR0LockMem: pSession=%p pvR3=%p cPages=%d paPages=%p\n", pSession, (void *)pvR3, cPages, paPages)); + + /* + * Verify input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(paPages, VERR_INVALID_PARAMETER); + if ( RT_ALIGN_R3PT(pvR3, PAGE_SIZE, RTR3PTR) != pvR3 + || !pvR3) + { + Log(("pvR3 (%p) must be page aligned and not NULL!\n", (void *)pvR3)); + return VERR_INVALID_PARAMETER; + } + + /* + * Let IPRT do the job. + */ + Mem.eType = MEMREF_TYPE_LOCKED; + rc = RTR0MemObjLockUser(&Mem.MemObj, pvR3, cb, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + uint32_t iPage = cPages; + AssertMsg(RTR0MemObjAddressR3(Mem.MemObj) == pvR3, ("%p == %p\n", RTR0MemObjAddressR3(Mem.MemObj), pvR3)); + AssertMsg(RTR0MemObjSize(Mem.MemObj) == cb, ("%x == %x\n", RTR0MemObjSize(Mem.MemObj), cb)); + + while (iPage-- > 0) + { + paPages[iPage] = RTR0MemObjGetPagePhysAddr(Mem.MemObj, iPage); + if (RT_UNLIKELY(paPages[iPage] == NIL_RTCCPHYS)) + { + AssertMsgFailed(("iPage=%d\n", iPage)); + rc = VERR_INTERNAL_ERROR; + break; + } + } + if (RT_SUCCESS(rc)) + rc = supdrvMemAdd(&Mem, pSession); + if (RT_FAILURE(rc)) + { + int rc2 = RTR0MemObjFree(Mem.MemObj, false); + AssertRC(rc2); + } + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0LockMem); + + +/** + * Unlocks the memory pointed to by pv. + * + * @returns IPRT status code. + * @param pSession Session to which the memory was locked. + * @param pvR3 Memory to unlock. + */ +SUPR0DECL(int) SUPR0UnlockMem(PSUPDRVSESSION pSession, RTR3PTR pvR3) +{ + LogFlow(("SUPR0UnlockMem: pSession=%p pvR3=%p\n", pSession, (void *)pvR3)); + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvMemRelease(pSession, (RTHCUINTPTR)pvR3, MEMREF_TYPE_LOCKED); +} +SUPR0_EXPORT_SYMBOL(SUPR0UnlockMem); + + +/** + * Allocates a chunk of page aligned memory with contiguous and fixed physical + * backing. + * + * @returns IPRT status code. + * @param pSession Session data. + * @param cPages Number of pages to allocate. + * @param ppvR0 Where to put the address of Ring-0 mapping the allocated memory. + * @param ppvR3 Where to put the address of Ring-3 mapping the allocated memory. + * @param pHCPhys Where to put the physical address of allocated memory. + */ +SUPR0DECL(int) SUPR0ContAlloc(PSUPDRVSESSION pSession, uint32_t cPages, PRTR0PTR ppvR0, PRTR3PTR ppvR3, PRTHCPHYS pHCPhys) +{ + int rc; + SUPDRVMEMREF Mem = { NIL_RTR0MEMOBJ, NIL_RTR0MEMOBJ, MEMREF_TYPE_UNUSED }; + LogFlow(("SUPR0ContAlloc: pSession=%p cPages=%d ppvR0=%p ppvR3=%p pHCPhys=%p\n", pSession, cPages, ppvR0, ppvR3, pHCPhys)); + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (!ppvR3 || !ppvR0 || !pHCPhys) + { + Log(("Null pointer. All of these should be set: pSession=%p ppvR0=%p ppvR3=%p pHCPhys=%p\n", + pSession, ppvR0, ppvR3, pHCPhys)); + return VERR_INVALID_PARAMETER; + + } + if (cPages < 1 || cPages >= 256) + { + Log(("Illegal request cPages=%d, must be greater than 0 and smaller than 256.\n", cPages)); + return VERR_PAGE_COUNT_OUT_OF_RANGE; + } + + /* + * Let IPRT do the job. + */ + rc = RTR0MemObjAllocCont(&Mem.MemObj, cPages << PAGE_SHIFT, true /* executable R0 mapping */); + if (RT_SUCCESS(rc)) + { + int rc2; + rc = RTR0MemObjMapUser(&Mem.MapObjR3, Mem.MemObj, (RTR3PTR)-1, 0, + RTMEM_PROT_EXEC | RTMEM_PROT_WRITE | RTMEM_PROT_READ, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + Mem.eType = MEMREF_TYPE_CONT; + rc = supdrvMemAdd(&Mem, pSession); + if (!rc) + { + *ppvR0 = RTR0MemObjAddress(Mem.MemObj); + *ppvR3 = RTR0MemObjAddressR3(Mem.MapObjR3); + *pHCPhys = RTR0MemObjGetPagePhysAddr(Mem.MemObj, 0); + return 0; + } + + rc2 = RTR0MemObjFree(Mem.MapObjR3, false); + AssertRC(rc2); + } + rc2 = RTR0MemObjFree(Mem.MemObj, false); + AssertRC(rc2); + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ContAlloc); + + +/** + * Frees memory allocated using SUPR0ContAlloc(). + * + * @returns IPRT status code. + * @param pSession The session to which the memory was allocated. + * @param uPtr Pointer to the memory (ring-3 or ring-0). + */ +SUPR0DECL(int) SUPR0ContFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) +{ + LogFlow(("SUPR0ContFree: pSession=%p uPtr=%p\n", pSession, (void *)uPtr)); + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvMemRelease(pSession, uPtr, MEMREF_TYPE_CONT); +} +SUPR0_EXPORT_SYMBOL(SUPR0ContFree); + + +/** + * Allocates a chunk of page aligned memory with fixed physical backing below 4GB. + * + * The memory isn't zeroed. + * + * @returns IPRT status code. + * @param pSession Session data. + * @param cPages Number of pages to allocate. + * @param ppvR0 Where to put the address of Ring-0 mapping of the allocated memory. + * @param ppvR3 Where to put the address of Ring-3 mapping of the allocated memory. + * @param paPages Where to put the physical addresses of allocated memory. + */ +SUPR0DECL(int) SUPR0LowAlloc(PSUPDRVSESSION pSession, uint32_t cPages, PRTR0PTR ppvR0, PRTR3PTR ppvR3, PRTHCPHYS paPages) +{ + unsigned iPage; + int rc; + SUPDRVMEMREF Mem = { NIL_RTR0MEMOBJ, NIL_RTR0MEMOBJ, MEMREF_TYPE_UNUSED }; + LogFlow(("SUPR0LowAlloc: pSession=%p cPages=%d ppvR3=%p ppvR0=%p paPages=%p\n", pSession, cPages, ppvR3, ppvR0, paPages)); + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (!ppvR3 || !ppvR0 || !paPages) + { + Log(("Null pointer. All of these should be set: pSession=%p ppvR3=%p ppvR0=%p paPages=%p\n", + pSession, ppvR3, ppvR0, paPages)); + return VERR_INVALID_PARAMETER; + + } + if (cPages < 1 || cPages >= 256) + { + Log(("Illegal request cPages=%d, must be greater than 0 and smaller than 256.\n", cPages)); + return VERR_PAGE_COUNT_OUT_OF_RANGE; + } + + /* + * Let IPRT do the work. + */ + rc = RTR0MemObjAllocLow(&Mem.MemObj, cPages << PAGE_SHIFT, true /* executable ring-0 mapping */); + if (RT_SUCCESS(rc)) + { + int rc2; + rc = RTR0MemObjMapUser(&Mem.MapObjR3, Mem.MemObj, (RTR3PTR)-1, 0, + RTMEM_PROT_EXEC | RTMEM_PROT_WRITE | RTMEM_PROT_READ, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + Mem.eType = MEMREF_TYPE_LOW; + rc = supdrvMemAdd(&Mem, pSession); + if (!rc) + { + for (iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage] = RTR0MemObjGetPagePhysAddr(Mem.MemObj, iPage); + AssertMsg(!(paPages[iPage] & (PAGE_SIZE - 1)), ("iPage=%d Phys=%RHp\n", paPages[iPage])); + } + *ppvR0 = RTR0MemObjAddress(Mem.MemObj); + *ppvR3 = RTR0MemObjAddressR3(Mem.MapObjR3); + return 0; + } + + rc2 = RTR0MemObjFree(Mem.MapObjR3, false); + AssertRC(rc2); + } + + rc2 = RTR0MemObjFree(Mem.MemObj, false); + AssertRC(rc2); + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0LowAlloc); + + +/** + * Frees memory allocated using SUPR0LowAlloc(). + * + * @returns IPRT status code. + * @param pSession The session to which the memory was allocated. + * @param uPtr Pointer to the memory (ring-3 or ring-0). + */ +SUPR0DECL(int) SUPR0LowFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) +{ + LogFlow(("SUPR0LowFree: pSession=%p uPtr=%p\n", pSession, (void *)uPtr)); + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvMemRelease(pSession, uPtr, MEMREF_TYPE_LOW); +} +SUPR0_EXPORT_SYMBOL(SUPR0LowFree); + + + +/** + * Allocates a chunk of memory with both R0 and R3 mappings. + * The memory is fixed and it's possible to query the physical addresses using SUPR0MemGetPhys(). + * + * @returns IPRT status code. + * @param pSession The session to associated the allocation with. + * @param cb Number of bytes to allocate. + * @param ppvR0 Where to store the address of the Ring-0 mapping. + * @param ppvR3 Where to store the address of the Ring-3 mapping. + */ +SUPR0DECL(int) SUPR0MemAlloc(PSUPDRVSESSION pSession, uint32_t cb, PRTR0PTR ppvR0, PRTR3PTR ppvR3) +{ + int rc; + SUPDRVMEMREF Mem = { NIL_RTR0MEMOBJ, NIL_RTR0MEMOBJ, MEMREF_TYPE_UNUSED }; + LogFlow(("SUPR0MemAlloc: pSession=%p cb=%d ppvR0=%p ppvR3=%p\n", pSession, cb, ppvR0, ppvR3)); + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(ppvR0, VERR_INVALID_POINTER); + AssertPtrReturn(ppvR3, VERR_INVALID_POINTER); + if (cb < 1 || cb >= _4M) + { + Log(("Illegal request cb=%u; must be greater than 0 and smaller than 4MB.\n", cb)); + return VERR_INVALID_PARAMETER; + } + + /* + * Let IPRT do the work. + */ + rc = RTR0MemObjAllocPage(&Mem.MemObj, cb, true /* executable ring-0 mapping */); + if (RT_SUCCESS(rc)) + { + int rc2; + rc = RTR0MemObjMapUser(&Mem.MapObjR3, Mem.MemObj, (RTR3PTR)-1, 0, + RTMEM_PROT_EXEC | RTMEM_PROT_WRITE | RTMEM_PROT_READ, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + Mem.eType = MEMREF_TYPE_MEM; + rc = supdrvMemAdd(&Mem, pSession); + if (!rc) + { + *ppvR0 = RTR0MemObjAddress(Mem.MemObj); + *ppvR3 = RTR0MemObjAddressR3(Mem.MapObjR3); + return VINF_SUCCESS; + } + + rc2 = RTR0MemObjFree(Mem.MapObjR3, false); + AssertRC(rc2); + } + + rc2 = RTR0MemObjFree(Mem.MemObj, false); + AssertRC(rc2); + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0MemAlloc); + + +/** + * Get the physical addresses of memory allocated using SUPR0MemAlloc(). + * + * @returns IPRT status code. + * @param pSession The session to which the memory was allocated. + * @param uPtr The Ring-0 or Ring-3 address returned by SUPR0MemAlloc(). + * @param paPages Where to store the physical addresses. + */ +SUPR0DECL(int) SUPR0MemGetPhys(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr, PSUPPAGE paPages) /** @todo switch this bugger to RTHCPHYS */ +{ + PSUPDRVBUNDLE pBundle; + LogFlow(("SUPR0MemGetPhys: pSession=%p uPtr=%p paPages=%p\n", pSession, (void *)uPtr, paPages)); + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(paPages, VERR_INVALID_POINTER); + AssertReturn(uPtr, VERR_INVALID_PARAMETER); + + /* + * Search for the address. + */ + RTSpinlockAcquire(pSession->Spinlock); + for (pBundle = &pSession->Bundle; pBundle; pBundle = pBundle->pNext) + { + if (pBundle->cUsed > 0) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if ( pBundle->aMem[i].eType == MEMREF_TYPE_MEM + && pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ + && ( (RTHCUINTPTR)RTR0MemObjAddress(pBundle->aMem[i].MemObj) == uPtr + || ( pBundle->aMem[i].MapObjR3 != NIL_RTR0MEMOBJ + && RTR0MemObjAddressR3(pBundle->aMem[i].MapObjR3) == uPtr) + ) + ) + { + const size_t cPages = RTR0MemObjSize(pBundle->aMem[i].MemObj) >> PAGE_SHIFT; + size_t iPage; + for (iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage].Phys = RTR0MemObjGetPagePhysAddr(pBundle->aMem[i].MemObj, iPage); + paPages[iPage].uReserved = 0; + } + RTSpinlockRelease(pSession->Spinlock); + return VINF_SUCCESS; + } + } + } + } + RTSpinlockRelease(pSession->Spinlock); + Log(("Failed to find %p!!!\n", (void *)uPtr)); + return VERR_INVALID_PARAMETER; +} +SUPR0_EXPORT_SYMBOL(SUPR0MemGetPhys); + + +/** + * Free memory allocated by SUPR0MemAlloc(). + * + * @returns IPRT status code. + * @param pSession The session owning the allocation. + * @param uPtr The Ring-0 or Ring-3 address returned by SUPR0MemAlloc(). + */ +SUPR0DECL(int) SUPR0MemFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) +{ + LogFlow(("SUPR0MemFree: pSession=%p uPtr=%p\n", pSession, (void *)uPtr)); + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvMemRelease(pSession, uPtr, MEMREF_TYPE_MEM); +} +SUPR0_EXPORT_SYMBOL(SUPR0MemFree); + + +/** + * Allocates a chunk of memory with a kernel or/and a user mode mapping. + * + * The memory is fixed and it's possible to query the physical addresses using + * SUPR0MemGetPhys(). + * + * @returns IPRT status code. + * @param pSession The session to associated the allocation with. + * @param cPages The number of pages to allocate. + * @param fFlags Flags, reserved for the future. Must be zero. + * @param ppvR3 Where to store the address of the Ring-3 mapping. + * NULL if no ring-3 mapping. + * @param ppvR0 Where to store the address of the Ring-0 mapping. + * NULL if no ring-0 mapping. + * @param paPages Where to store the addresses of the pages. Optional. + */ +SUPR0DECL(int) SUPR0PageAllocEx(PSUPDRVSESSION pSession, uint32_t cPages, uint32_t fFlags, PRTR3PTR ppvR3, PRTR0PTR ppvR0, PRTHCPHYS paPages) +{ + int rc; + SUPDRVMEMREF Mem = { NIL_RTR0MEMOBJ, NIL_RTR0MEMOBJ, MEMREF_TYPE_UNUSED }; + LogFlow(("SUPR0PageAlloc: pSession=%p cb=%d ppvR3=%p\n", pSession, cPages, ppvR3)); + + /* + * Validate input. The allowed allocation size must be at least equal to the maximum guest VRAM size. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrNullReturn(ppvR3, VERR_INVALID_POINTER); + AssertPtrNullReturn(ppvR0, VERR_INVALID_POINTER); + AssertReturn(ppvR3 || ppvR0, VERR_INVALID_PARAMETER); + AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + if (cPages < 1 || cPages > VBOX_MAX_ALLOC_PAGE_COUNT) + { + Log(("SUPR0PageAlloc: Illegal request cb=%u; must be greater than 0 and smaller than %uMB (VBOX_MAX_ALLOC_PAGE_COUNT pages).\n", cPages, VBOX_MAX_ALLOC_PAGE_COUNT * (_1M / _4K))); + return VERR_PAGE_COUNT_OUT_OF_RANGE; + } + + /* + * Let IPRT do the work. + */ + if (ppvR0) + rc = RTR0MemObjAllocPage(&Mem.MemObj, (size_t)cPages * PAGE_SIZE, false /*fExecutable*/); + else + rc = RTR0MemObjAllocPhysNC(&Mem.MemObj, (size_t)cPages * PAGE_SIZE, NIL_RTHCPHYS); + if (RT_SUCCESS(rc)) + { + int rc2; + if (ppvR3) + { + /* Make sure memory mapped into ring-3 is zero initialized if we can: */ + if ( ppvR0 + && !RTR0MemObjWasZeroInitialized(Mem.MemObj)) + { + void *pv = RTR0MemObjAddress(Mem.MemObj); + Assert(pv || !ppvR0); + if (pv) + RT_BZERO(pv, (size_t)cPages * PAGE_SIZE); + } + + rc = RTR0MemObjMapUser(&Mem.MapObjR3, Mem.MemObj, (RTR3PTR)-1, 0, RTMEM_PROT_WRITE | RTMEM_PROT_READ, NIL_RTR0PROCESS); + } + else + Mem.MapObjR3 = NIL_RTR0MEMOBJ; + if (RT_SUCCESS(rc)) + { + Mem.eType = MEMREF_TYPE_PAGE; + rc = supdrvMemAdd(&Mem, pSession); + if (!rc) + { + if (ppvR3) + *ppvR3 = RTR0MemObjAddressR3(Mem.MapObjR3); + if (ppvR0) + *ppvR0 = RTR0MemObjAddress(Mem.MemObj); + if (paPages) + { + uint32_t iPage = cPages; + while (iPage-- > 0) + { + paPages[iPage] = RTR0MemObjGetPagePhysAddr(Mem.MapObjR3, iPage); + Assert(paPages[iPage] != NIL_RTHCPHYS); + } + } + return VINF_SUCCESS; + } + + rc2 = RTR0MemObjFree(Mem.MapObjR3, false); + AssertRC(rc2); + } + + rc2 = RTR0MemObjFree(Mem.MemObj, false); + AssertRC(rc2); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0PageAllocEx); + + +/** + * Maps a chunk of memory previously allocated by SUPR0PageAllocEx into kernel + * space. + * + * @returns IPRT status code. + * @param pSession The session to associated the allocation with. + * @param pvR3 The ring-3 address returned by SUPR0PageAllocEx. + * @param offSub Where to start mapping. Must be page aligned. + * @param cbSub How much to map. Must be page aligned. + * @param fFlags Flags, MBZ. + * @param ppvR0 Where to return the address of the ring-0 mapping on + * success. + */ +SUPR0DECL(int) SUPR0PageMapKernel(PSUPDRVSESSION pSession, RTR3PTR pvR3, uint32_t offSub, uint32_t cbSub, + uint32_t fFlags, PRTR0PTR ppvR0) +{ + int rc; + PSUPDRVBUNDLE pBundle; + RTR0MEMOBJ hMemObj = NIL_RTR0MEMOBJ; + LogFlow(("SUPR0PageMapKernel: pSession=%p pvR3=%p offSub=%#x cbSub=%#x\n", pSession, pvR3, offSub, cbSub)); + + /* + * Validate input. The allowed allocation size must be at least equal to the maximum guest VRAM size. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrNullReturn(ppvR0, VERR_INVALID_POINTER); + AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + AssertReturn(!(offSub & PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER); + AssertReturn(!(cbSub & PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER); + AssertReturn(cbSub, VERR_INVALID_PARAMETER); + + /* + * Find the memory object. + */ + RTSpinlockAcquire(pSession->Spinlock); + for (pBundle = &pSession->Bundle; pBundle; pBundle = pBundle->pNext) + { + if (pBundle->cUsed > 0) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if ( ( pBundle->aMem[i].eType == MEMREF_TYPE_PAGE + && pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ + && pBundle->aMem[i].MapObjR3 != NIL_RTR0MEMOBJ + && RTR0MemObjAddressR3(pBundle->aMem[i].MapObjR3) == pvR3) + || ( pBundle->aMem[i].eType == MEMREF_TYPE_LOCKED + && pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ + && pBundle->aMem[i].MapObjR3 == NIL_RTR0MEMOBJ + && RTR0MemObjAddressR3(pBundle->aMem[i].MemObj) == pvR3)) + { + hMemObj = pBundle->aMem[i].MemObj; + break; + } + } + } + } + RTSpinlockRelease(pSession->Spinlock); + + rc = VERR_INVALID_PARAMETER; + if (hMemObj != NIL_RTR0MEMOBJ) + { + /* + * Do some further input validations before calling IPRT. + * (Cleanup is done indirectly by telling RTR0MemObjFree to include mappings.) + */ + size_t cbMemObj = RTR0MemObjSize(hMemObj); + if ( offSub < cbMemObj + && cbSub <= cbMemObj + && offSub + cbSub <= cbMemObj) + { + RTR0MEMOBJ hMapObj; + rc = RTR0MemObjMapKernelEx(&hMapObj, hMemObj, (void *)-1, 0, + RTMEM_PROT_READ | RTMEM_PROT_WRITE, offSub, cbSub); + if (RT_SUCCESS(rc)) + *ppvR0 = RTR0MemObjAddress(hMapObj); + } + else + SUPR0Printf("SUPR0PageMapKernel: cbMemObj=%#x offSub=%#x cbSub=%#x\n", cbMemObj, offSub, cbSub); + + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0PageMapKernel); + + +/** + * Changes the page level protection of one or more pages previously allocated + * by SUPR0PageAllocEx. + * + * @returns IPRT status code. + * @param pSession The session to associated the allocation with. + * @param pvR3 The ring-3 address returned by SUPR0PageAllocEx. + * NIL_RTR3PTR if the ring-3 mapping should be unaffected. + * @param pvR0 The ring-0 address returned by SUPR0PageAllocEx. + * NIL_RTR0PTR if the ring-0 mapping should be unaffected. + * @param offSub Where to start changing. Must be page aligned. + * @param cbSub How much to change. Must be page aligned. + * @param fProt The new page level protection, see RTMEM_PROT_*. + */ +SUPR0DECL(int) SUPR0PageProtect(PSUPDRVSESSION pSession, RTR3PTR pvR3, RTR0PTR pvR0, uint32_t offSub, uint32_t cbSub, uint32_t fProt) +{ + int rc; + PSUPDRVBUNDLE pBundle; + RTR0MEMOBJ hMemObjR0 = NIL_RTR0MEMOBJ; + RTR0MEMOBJ hMemObjR3 = NIL_RTR0MEMOBJ; + LogFlow(("SUPR0PageProtect: pSession=%p pvR3=%p pvR0=%p offSub=%#x cbSub=%#x fProt-%#x\n", pSession, pvR3, pvR0, offSub, cbSub, fProt)); + + /* + * Validate input. The allowed allocation size must be at least equal to the maximum guest VRAM size. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(!(fProt & ~(RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC | RTMEM_PROT_NONE)), VERR_INVALID_PARAMETER); + AssertReturn(!(offSub & PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER); + AssertReturn(!(cbSub & PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER); + AssertReturn(cbSub, VERR_INVALID_PARAMETER); + + /* + * Find the memory object. + */ + RTSpinlockAcquire(pSession->Spinlock); + for (pBundle = &pSession->Bundle; pBundle; pBundle = pBundle->pNext) + { + if (pBundle->cUsed > 0) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if ( pBundle->aMem[i].eType == MEMREF_TYPE_PAGE + && pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ + && ( pBundle->aMem[i].MapObjR3 != NIL_RTR0MEMOBJ + || pvR3 == NIL_RTR3PTR) + && ( pvR0 == NIL_RTR0PTR + || RTR0MemObjAddress(pBundle->aMem[i].MemObj) == pvR0) + && ( pvR3 == NIL_RTR3PTR + || RTR0MemObjAddressR3(pBundle->aMem[i].MapObjR3) == pvR3)) + { + if (pvR0 != NIL_RTR0PTR) + hMemObjR0 = pBundle->aMem[i].MemObj; + if (pvR3 != NIL_RTR3PTR) + hMemObjR3 = pBundle->aMem[i].MapObjR3; + break; + } + } + } + } + RTSpinlockRelease(pSession->Spinlock); + + rc = VERR_INVALID_PARAMETER; + if ( hMemObjR0 != NIL_RTR0MEMOBJ + || hMemObjR3 != NIL_RTR0MEMOBJ) + { + /* + * Do some further input validations before calling IPRT. + */ + size_t cbMemObj = hMemObjR0 != NIL_RTR0PTR ? RTR0MemObjSize(hMemObjR0) : RTR0MemObjSize(hMemObjR3); + if ( offSub < cbMemObj + && cbSub <= cbMemObj + && offSub + cbSub <= cbMemObj) + { + rc = VINF_SUCCESS; + if (hMemObjR3 != NIL_RTR0PTR) + rc = RTR0MemObjProtect(hMemObjR3, offSub, cbSub, fProt); + if (hMemObjR0 != NIL_RTR0PTR && RT_SUCCESS(rc)) + rc = RTR0MemObjProtect(hMemObjR0, offSub, cbSub, fProt); + } + else + SUPR0Printf("SUPR0PageMapKernel: cbMemObj=%#x offSub=%#x cbSub=%#x\n", cbMemObj, offSub, cbSub); + + } + return rc; + +} +SUPR0_EXPORT_SYMBOL(SUPR0PageProtect); + + +/** + * Free memory allocated by SUPR0PageAlloc() and SUPR0PageAllocEx(). + * + * @returns IPRT status code. + * @param pSession The session owning the allocation. + * @param pvR3 The Ring-3 address returned by SUPR0PageAlloc() or + * SUPR0PageAllocEx(). + */ +SUPR0DECL(int) SUPR0PageFree(PSUPDRVSESSION pSession, RTR3PTR pvR3) +{ + LogFlow(("SUPR0PageFree: pSession=%p pvR3=%p\n", pSession, (void *)pvR3)); + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvMemRelease(pSession, (RTHCUINTPTR)pvR3, MEMREF_TYPE_PAGE); +} +SUPR0_EXPORT_SYMBOL(SUPR0PageFree); + + +/** + * Reports a bad context, currenctly that means EFLAGS.AC is 0 instead of 1. + * + * @param pDevExt The device extension. + * @param pszFile The source file where the caller detected the bad + * context. + * @param uLine The line number in @a pszFile. + * @param pszExtra Optional additional message to give further hints. + */ +void VBOXCALL supdrvBadContext(PSUPDRVDEVEXT pDevExt, const char *pszFile, uint32_t uLine, const char *pszExtra) +{ + uint32_t cCalls; + + /* + * Shorten the filename before displaying the message. + */ + for (;;) + { + const char *pszTmp = strchr(pszFile, '/'); + if (!pszTmp) + pszTmp = strchr(pszFile, '\\'); + if (!pszTmp) + break; + pszFile = pszTmp + 1; + } + if (RT_VALID_PTR(pszExtra) && *pszExtra) + SUPR0Printf("vboxdrv: Bad CPU context error at line %u in %s: %s\n", uLine, pszFile, pszExtra); + else + SUPR0Printf("vboxdrv: Bad CPU context error at line %u in %s!\n", uLine, pszFile); + + /* + * Record the incident so that we stand a chance of blocking I/O controls + * before panicing the system. + */ + cCalls = ASMAtomicIncU32(&pDevExt->cBadContextCalls); + if (cCalls > UINT32_MAX - _1K) + ASMAtomicWriteU32(&pDevExt->cBadContextCalls, UINT32_MAX - _1K); +} + + +/** + * Reports a bad context, currenctly that means EFLAGS.AC is 0 instead of 1. + * + * @param pSession The session of the caller. + * @param pszFile The source file where the caller detected the bad + * context. + * @param uLine The line number in @a pszFile. + * @param pszExtra Optional additional message to give further hints. + */ +SUPR0DECL(void) SUPR0BadContext(PSUPDRVSESSION pSession, const char *pszFile, uint32_t uLine, const char *pszExtra) +{ + PSUPDRVDEVEXT pDevExt; + + AssertReturnVoid(SUP_IS_SESSION_VALID(pSession)); + pDevExt = pSession->pDevExt; + + supdrvBadContext(pDevExt, pszFile, uLine, pszExtra); +} +SUPR0_EXPORT_SYMBOL(SUPR0BadContext); + + +/** + * Gets the paging mode of the current CPU. + * + * @returns Paging mode, SUPPAGEINGMODE_INVALID on error. + */ +SUPR0DECL(SUPPAGINGMODE) SUPR0GetPagingMode(void) +{ + SUPPAGINGMODE enmMode; + + RTR0UINTREG cr0 = ASMGetCR0(); + if ((cr0 & (X86_CR0_PG | X86_CR0_PE)) != (X86_CR0_PG | X86_CR0_PE)) + enmMode = SUPPAGINGMODE_INVALID; + else + { + RTR0UINTREG cr4 = ASMGetCR4(); + uint32_t fNXEPlusLMA = 0; + if (cr4 & X86_CR4_PAE) + { + uint32_t fExtFeatures = ASMCpuId_EDX(0x80000001); + if (fExtFeatures & (X86_CPUID_EXT_FEATURE_EDX_NX | X86_CPUID_EXT_FEATURE_EDX_LONG_MODE)) + { + uint64_t efer = ASMRdMsr(MSR_K6_EFER); + if ((fExtFeatures & X86_CPUID_EXT_FEATURE_EDX_NX) && (efer & MSR_K6_EFER_NXE)) + fNXEPlusLMA |= RT_BIT(0); + if ((fExtFeatures & X86_CPUID_EXT_FEATURE_EDX_LONG_MODE) && (efer & MSR_K6_EFER_LMA)) + fNXEPlusLMA |= RT_BIT(1); + } + } + + switch ((cr4 & (X86_CR4_PAE | X86_CR4_PGE)) | fNXEPlusLMA) + { + case 0: + enmMode = SUPPAGINGMODE_32_BIT; + break; + + case X86_CR4_PGE: + enmMode = SUPPAGINGMODE_32_BIT_GLOBAL; + break; + + case X86_CR4_PAE: + enmMode = SUPPAGINGMODE_PAE; + break; + + case X86_CR4_PAE | RT_BIT(0): + enmMode = SUPPAGINGMODE_PAE_NX; + break; + + case X86_CR4_PAE | X86_CR4_PGE: + enmMode = SUPPAGINGMODE_PAE_GLOBAL; + break; + + case X86_CR4_PAE | X86_CR4_PGE | RT_BIT(0): + enmMode = SUPPAGINGMODE_PAE_GLOBAL; + break; + + case RT_BIT(1) | X86_CR4_PAE: + enmMode = SUPPAGINGMODE_AMD64; + break; + + case RT_BIT(1) | X86_CR4_PAE | RT_BIT(0): + enmMode = SUPPAGINGMODE_AMD64_NX; + break; + + case RT_BIT(1) | X86_CR4_PAE | X86_CR4_PGE: + enmMode = SUPPAGINGMODE_AMD64_GLOBAL; + break; + + case RT_BIT(1) | X86_CR4_PAE | X86_CR4_PGE | RT_BIT(0): + enmMode = SUPPAGINGMODE_AMD64_GLOBAL_NX; + break; + + default: + AssertMsgFailed(("Cannot happen! cr4=%#x fNXEPlusLMA=%d\n", cr4, fNXEPlusLMA)); + enmMode = SUPPAGINGMODE_INVALID; + break; + } + } + return enmMode; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetPagingMode); + + +/** + * Change CR4 and take care of the kernel CR4 shadow if applicable. + * + * CR4 shadow handling is required for Linux >= 4.0. Calling this function + * instead of ASMSetCR4() is only necessary for semi-permanent CR4 changes + * for code with interrupts enabled. + * + * @returns the old CR4 value. + * + * @param fOrMask bits to be set in CR4. + * @param fAndMask bits to be cleard in CR4. + * + * @remarks Must be called with preemption/interrupts disabled. + */ +SUPR0DECL(RTCCUINTREG) SUPR0ChangeCR4(RTCCUINTREG fOrMask, RTCCUINTREG fAndMask) +{ +#ifdef RT_OS_LINUX + return supdrvOSChangeCR4(fOrMask, fAndMask); +#else + RTCCUINTREG uOld = ASMGetCR4(); + RTCCUINTREG uNew = (uOld & fAndMask) | fOrMask; + if (uNew != uOld) + ASMSetCR4(uNew); + return uOld; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0ChangeCR4); + + +/** + * Enables or disabled hardware virtualization extensions using native OS APIs. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_SUPPORTED if not supported by the native OS. + * + * @param fEnable Whether to enable or disable. + */ +SUPR0DECL(int) SUPR0EnableVTx(bool fEnable) +{ +#ifdef RT_OS_DARWIN + return supdrvOSEnableVTx(fEnable); +#else + RT_NOREF1(fEnable); + return VERR_NOT_SUPPORTED; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0EnableVTx); + + +/** + * Suspends hardware virtualization extensions using the native OS API. + * + * This is called prior to entering raw-mode context. + * + * @returns @c true if suspended, @c false if not. + */ +SUPR0DECL(bool) SUPR0SuspendVTxOnCpu(void) +{ +#ifdef RT_OS_DARWIN + return supdrvOSSuspendVTxOnCpu(); +#else + return false; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0SuspendVTxOnCpu); + + +/** + * Resumes hardware virtualization extensions using the native OS API. + * + * This is called after to entering raw-mode context. + * + * @param fSuspended The return value of SUPR0SuspendVTxOnCpu. + */ +SUPR0DECL(void) SUPR0ResumeVTxOnCpu(bool fSuspended) +{ +#ifdef RT_OS_DARWIN + supdrvOSResumeVTxOnCpu(fSuspended); +#else + RT_NOREF1(fSuspended); + Assert(!fSuspended); +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0ResumeVTxOnCpu); + + +SUPR0DECL(int) SUPR0GetCurrentGdtRw(RTHCUINTPTR *pGdtRw) +{ +#ifdef RT_OS_LINUX + return supdrvOSGetCurrentGdtRw(pGdtRw); +#else + NOREF(pGdtRw); + return VERR_NOT_IMPLEMENTED; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0GetCurrentGdtRw); + + +/** + * Gets AMD-V and VT-x support for the calling CPU. + * + * @returns VBox status code. + * @param pfCaps Where to store whether VT-x (SUPVTCAPS_VT_X) or AMD-V + * (SUPVTCAPS_AMD_V) is supported. + */ +SUPR0DECL(int) SUPR0GetVTSupport(uint32_t *pfCaps) +{ + Assert(pfCaps); + *pfCaps = 0; + + /* Check if the CPU even supports CPUID (extremely ancient CPUs). */ + if (ASMHasCpuId()) + { + /* Check the range of standard CPUID leafs. */ + uint32_t uMaxLeaf, uVendorEbx, uVendorEcx, uVendorEdx; + ASMCpuId(0, &uMaxLeaf, &uVendorEbx, &uVendorEcx, &uVendorEdx); + if (RTX86IsValidStdRange(uMaxLeaf)) + { + /* Query the standard CPUID leaf. */ + uint32_t fFeatEcx, fFeatEdx, uDummy; + ASMCpuId(1, &uDummy, &uDummy, &fFeatEcx, &fFeatEdx); + + /* Check if the vendor is Intel (or compatible). */ + if ( RTX86IsIntelCpu(uVendorEbx, uVendorEcx, uVendorEdx) + || RTX86IsViaCentaurCpu(uVendorEbx, uVendorEcx, uVendorEdx) + || RTX86IsShanghaiCpu(uVendorEbx, uVendorEcx, uVendorEdx)) + { + /* Check VT-x support. In addition, VirtualBox requires MSR and FXSAVE/FXRSTOR to function. */ + if ( (fFeatEcx & X86_CPUID_FEATURE_ECX_VMX) + && (fFeatEdx & X86_CPUID_FEATURE_EDX_MSR) + && (fFeatEdx & X86_CPUID_FEATURE_EDX_FXSR)) + { + *pfCaps = SUPVTCAPS_VT_X; + return VINF_SUCCESS; + } + return VERR_VMX_NO_VMX; + } + + /* Check if the vendor is AMD (or compatible). */ + if ( RTX86IsAmdCpu(uVendorEbx, uVendorEcx, uVendorEdx) + || RTX86IsHygonCpu(uVendorEbx, uVendorEcx, uVendorEdx)) + { + uint32_t fExtFeatEcx, uExtMaxId; + ASMCpuId(0x80000000, &uExtMaxId, &uDummy, &uDummy, &uDummy); + ASMCpuId(0x80000001, &uDummy, &uDummy, &fExtFeatEcx, &uDummy); + + /* Check AMD-V support. In addition, VirtualBox requires MSR and FXSAVE/FXRSTOR to function. */ + if ( RTX86IsValidExtRange(uExtMaxId) + && uExtMaxId >= 0x8000000a + && (fExtFeatEcx & X86_CPUID_AMD_FEATURE_ECX_SVM) + && (fFeatEdx & X86_CPUID_FEATURE_EDX_MSR) + && (fFeatEdx & X86_CPUID_FEATURE_EDX_FXSR)) + { + *pfCaps = SUPVTCAPS_AMD_V; + return VINF_SUCCESS; + } + return VERR_SVM_NO_SVM; + } + } + } + return VERR_UNSUPPORTED_CPU; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetVTSupport); + + +/** + * Checks if Intel VT-x feature is usable on this CPU. + * + * @returns VBox status code. + * @param pfIsSmxModeAmbiguous Where to return whether the SMX mode causes + * ambiguity that makes us unsure whether we + * really can use VT-x or not. + * + * @remarks Must be called with preemption disabled. + * The caller is also expected to check that the CPU is an Intel (or + * VIA/Shanghai) CPU -and- that it supports VT-x. Otherwise, this + * function might throw a \#GP fault as it tries to read/write MSRs + * that may not be present! + */ +SUPR0DECL(int) SUPR0GetVmxUsability(bool *pfIsSmxModeAmbiguous) +{ + uint64_t fFeatMsr; + bool fMaybeSmxMode; + bool fMsrLocked; + bool fSmxVmxAllowed; + bool fVmxAllowed; + bool fIsSmxModeAmbiguous; + int rc; + + Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + + fFeatMsr = ASMRdMsr(MSR_IA32_FEATURE_CONTROL); + fMaybeSmxMode = RT_BOOL(ASMGetCR4() & X86_CR4_SMXE); + fMsrLocked = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_LOCK); + fSmxVmxAllowed = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_SMX_VMXON); + fVmxAllowed = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_VMXON); + fIsSmxModeAmbiguous = false; + rc = VERR_INTERNAL_ERROR_5; + + /* Check if the LOCK bit is set but excludes the required VMXON bit. */ + if (fMsrLocked) + { + if (fVmxAllowed && fSmxVmxAllowed) + rc = VINF_SUCCESS; + else if (!fVmxAllowed && !fSmxVmxAllowed) + rc = VERR_VMX_MSR_ALL_VMX_DISABLED; + else if (!fMaybeSmxMode) + { + if (fVmxAllowed) + rc = VINF_SUCCESS; + else + rc = VERR_VMX_MSR_VMX_DISABLED; + } + else + { + /* + * CR4.SMXE is set but this doesn't mean the CPU is necessarily in SMX mode. We shall assume + * that it is -not- and that it is a stupid BIOS/OS setting CR4.SMXE for no good reason. + * See @bugref{6873}. + */ + Assert(fMaybeSmxMode == true); + fIsSmxModeAmbiguous = true; + rc = VINF_SUCCESS; + } + } + else + { + /* + * MSR is not yet locked; we can change it ourselves here. Once the lock bit is set, + * this MSR can no longer be modified. + * + * Set both the VMX and SMX_VMX bits (if supported) as we can't determine SMX mode + * accurately. See @bugref{6873}. + * + * We need to check for SMX hardware support here, before writing the MSR as + * otherwise we will #GP fault on CPUs that do not support it. Callers do not check + * for it. + */ + uint32_t fFeaturesECX, uDummy; +#ifdef VBOX_STRICT + /* Callers should have verified these at some point. */ + uint32_t uMaxId, uVendorEBX, uVendorECX, uVendorEDX; + ASMCpuId(0, &uMaxId, &uVendorEBX, &uVendorECX, &uVendorEDX); + Assert(RTX86IsValidStdRange(uMaxId)); + Assert( RTX86IsIntelCpu( uVendorEBX, uVendorECX, uVendorEDX) + || RTX86IsViaCentaurCpu(uVendorEBX, uVendorECX, uVendorEDX) + || RTX86IsShanghaiCpu( uVendorEBX, uVendorECX, uVendorEDX)); +#endif + ASMCpuId(1, &uDummy, &uDummy, &fFeaturesECX, &uDummy); + bool fSmxVmxHwSupport = false; + if ( (fFeaturesECX & X86_CPUID_FEATURE_ECX_VMX) + && (fFeaturesECX & X86_CPUID_FEATURE_ECX_SMX)) + fSmxVmxHwSupport = true; + + fFeatMsr |= MSR_IA32_FEATURE_CONTROL_LOCK + | MSR_IA32_FEATURE_CONTROL_VMXON; + if (fSmxVmxHwSupport) + fFeatMsr |= MSR_IA32_FEATURE_CONTROL_SMX_VMXON; + + /* + * Commit. + */ + ASMWrMsr(MSR_IA32_FEATURE_CONTROL, fFeatMsr); + + /* + * Verify. + */ + fFeatMsr = ASMRdMsr(MSR_IA32_FEATURE_CONTROL); + fMsrLocked = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_LOCK); + if (fMsrLocked) + { + fSmxVmxAllowed = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_SMX_VMXON); + fVmxAllowed = RT_BOOL(fFeatMsr & MSR_IA32_FEATURE_CONTROL_VMXON); + if ( fVmxAllowed + && ( !fSmxVmxHwSupport + || fSmxVmxAllowed)) + rc = VINF_SUCCESS; + else + rc = !fSmxVmxHwSupport ? VERR_VMX_MSR_VMX_ENABLE_FAILED : VERR_VMX_MSR_SMX_VMX_ENABLE_FAILED; + } + else + rc = VERR_VMX_MSR_LOCKING_FAILED; + } + + if (pfIsSmxModeAmbiguous) + *pfIsSmxModeAmbiguous = fIsSmxModeAmbiguous; + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetVmxUsability); + + +/** + * Checks if AMD-V SVM feature is usable on this CPU. + * + * @returns VBox status code. + * @param fInitSvm If usable, try to initialize SVM on this CPU. + * + * @remarks Must be called with preemption disabled. + */ +SUPR0DECL(int) SUPR0GetSvmUsability(bool fInitSvm) +{ + int rc; + uint64_t fVmCr; + uint64_t fEfer; + + Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + fVmCr = ASMRdMsr(MSR_K8_VM_CR); + if (!(fVmCr & MSR_K8_VM_CR_SVM_DISABLE)) + { + rc = VINF_SUCCESS; + if (fInitSvm) + { + /* Turn on SVM in the EFER MSR. */ + fEfer = ASMRdMsr(MSR_K6_EFER); + if (fEfer & MSR_K6_EFER_SVME) + rc = VERR_SVM_IN_USE; + else + { + ASMWrMsr(MSR_K6_EFER, fEfer | MSR_K6_EFER_SVME); + + /* Paranoia. */ + fEfer = ASMRdMsr(MSR_K6_EFER); + if (fEfer & MSR_K6_EFER_SVME) + { + /* Restore previous value. */ + ASMWrMsr(MSR_K6_EFER, fEfer & ~MSR_K6_EFER_SVME); + } + else + rc = VERR_SVM_ILLEGAL_EFER_MSR; + } + } + } + else + rc = VERR_SVM_DISABLED; + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetSvmUsability); + + +/** + * Queries the AMD-V and VT-x capabilities of the calling CPU. + * + * @returns VBox status code. + * @retval VERR_VMX_NO_VMX + * @retval VERR_VMX_MSR_ALL_VMX_DISABLED + * @retval VERR_VMX_MSR_VMX_DISABLED + * @retval VERR_VMX_MSR_LOCKING_FAILED + * @retval VERR_VMX_MSR_VMX_ENABLE_FAILED + * @retval VERR_VMX_MSR_SMX_VMX_ENABLE_FAILED + * @retval VERR_SVM_NO_SVM + * @retval VERR_SVM_DISABLED + * @retval VERR_UNSUPPORTED_CPU if not identifiable as an AMD, Intel or VIA + * (centaur)/Shanghai CPU. + * + * @param pfCaps Where to store the capabilities. + */ +int VBOXCALL supdrvQueryVTCapsInternal(uint32_t *pfCaps) +{ + int rc = VERR_UNSUPPORTED_CPU; + bool fIsSmxModeAmbiguous = false; + RTTHREADPREEMPTSTATE PreemptState = RTTHREADPREEMPTSTATE_INITIALIZER; + + /* + * Input validation. + */ + AssertPtrReturn(pfCaps, VERR_INVALID_POINTER); + *pfCaps = 0; + + /* We may modify MSRs and re-read them, disable preemption so we make sure we don't migrate CPUs. */ + RTThreadPreemptDisable(&PreemptState); + + /* Check if VT-x/AMD-V is supported. */ + rc = SUPR0GetVTSupport(pfCaps); + if (RT_SUCCESS(rc)) + { + /* Check if VT-x is supported. */ + if (*pfCaps & SUPVTCAPS_VT_X) + { + /* Check if VT-x is usable. */ + rc = SUPR0GetVmxUsability(&fIsSmxModeAmbiguous); + if (RT_SUCCESS(rc)) + { + /* Query some basic VT-x capabilities (mainly required by our GUI). */ + VMXCTLSMSR vtCaps; + vtCaps.u = ASMRdMsr(MSR_IA32_VMX_PROCBASED_CTLS); + if (vtCaps.n.allowed1 & VMX_PROC_CTLS_USE_SECONDARY_CTLS) + { + vtCaps.u = ASMRdMsr(MSR_IA32_VMX_PROCBASED_CTLS2); + if (vtCaps.n.allowed1 & VMX_PROC_CTLS2_EPT) + *pfCaps |= SUPVTCAPS_NESTED_PAGING; + if (vtCaps.n.allowed1 & VMX_PROC_CTLS2_UNRESTRICTED_GUEST) + *pfCaps |= SUPVTCAPS_VTX_UNRESTRICTED_GUEST; + if (vtCaps.n.allowed1 & VMX_PROC_CTLS2_VMCS_SHADOWING) + *pfCaps |= SUPVTCAPS_VTX_VMCS_SHADOWING; + } + } + } + /* Check if AMD-V is supported. */ + else if (*pfCaps & SUPVTCAPS_AMD_V) + { + /* Check is SVM is usable. */ + rc = SUPR0GetSvmUsability(false /* fInitSvm */); + if (RT_SUCCESS(rc)) + { + /* Query some basic AMD-V capabilities (mainly required by our GUI). */ + uint32_t uDummy, fSvmFeatures; + ASMCpuId(0x8000000a, &uDummy, &uDummy, &uDummy, &fSvmFeatures); + if (fSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_NESTED_PAGING) + *pfCaps |= SUPVTCAPS_NESTED_PAGING; + if (fSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_VIRT_VMSAVE_VMLOAD) + *pfCaps |= SUPVTCAPS_AMDV_VIRT_VMSAVE_VMLOAD; + } + } + } + + /* Restore preemption. */ + RTThreadPreemptRestore(&PreemptState); + + /* After restoring preemption, if we may be in SMX mode, print a warning as it's difficult to debug such problems. */ + if (fIsSmxModeAmbiguous) + SUPR0Printf(("WARNING! CR4 hints SMX mode but your CPU is too secretive. Proceeding anyway... We wish you good luck!\n")); + + return rc; +} + + +/** + * Queries the AMD-V and VT-x capabilities of the calling CPU. + * + * @returns VBox status code. + * @retval VERR_VMX_NO_VMX + * @retval VERR_VMX_MSR_ALL_VMX_DISABLED + * @retval VERR_VMX_MSR_VMX_DISABLED + * @retval VERR_VMX_MSR_LOCKING_FAILED + * @retval VERR_VMX_MSR_VMX_ENABLE_FAILED + * @retval VERR_VMX_MSR_SMX_VMX_ENABLE_FAILED + * @retval VERR_SVM_NO_SVM + * @retval VERR_SVM_DISABLED + * @retval VERR_UNSUPPORTED_CPU if not identifiable as an AMD, Intel or VIA + * (centaur)/Shanghai CPU. + * + * @param pSession The session handle. + * @param pfCaps Where to store the capabilities. + */ +SUPR0DECL(int) SUPR0QueryVTCaps(PSUPDRVSESSION pSession, uint32_t *pfCaps) +{ + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(pfCaps, VERR_INVALID_POINTER); + + /* + * Call common worker. + */ + return supdrvQueryVTCapsInternal(pfCaps); +} +SUPR0_EXPORT_SYMBOL(SUPR0QueryVTCaps); + + +/** + * Queries the CPU microcode revision. + * + * @returns VBox status code. + * @retval VERR_UNSUPPORTED_CPU if not identifiable as a processor with + * readable microcode rev. + * + * @param puRevision Where to store the microcode revision. + */ +static int VBOXCALL supdrvQueryUcodeRev(uint32_t *puRevision) +{ + int rc = VERR_UNSUPPORTED_CPU; + RTTHREADPREEMPTSTATE PreemptState = RTTHREADPREEMPTSTATE_INITIALIZER; + + /* + * Input validation. + */ + AssertPtrReturn(puRevision, VERR_INVALID_POINTER); + + *puRevision = 0; + + /* Disable preemption so we make sure we don't migrate CPUs, just in case. */ + /* NB: We assume that there aren't mismatched microcode revs in the system. */ + RTThreadPreemptDisable(&PreemptState); + + if (ASMHasCpuId()) + { + uint32_t uDummy, uTFMSEAX; + uint32_t uMaxId, uVendorEBX, uVendorECX, uVendorEDX; + + ASMCpuId(0, &uMaxId, &uVendorEBX, &uVendorECX, &uVendorEDX); + ASMCpuId(1, &uTFMSEAX, &uDummy, &uDummy, &uDummy); + + if (RTX86IsValidStdRange(uMaxId)) + { + uint64_t uRevMsr; + if (RTX86IsIntelCpu(uVendorEBX, uVendorECX, uVendorEDX)) + { + /* Architectural MSR available on Pentium Pro and later. */ + if (RTX86GetCpuFamily(uTFMSEAX) >= 6) + { + /* Revision is in the high dword. */ + uRevMsr = ASMRdMsr(MSR_IA32_BIOS_SIGN_ID); + *puRevision = RT_HIDWORD(uRevMsr); + rc = VINF_SUCCESS; + } + } + else if ( RTX86IsAmdCpu(uVendorEBX, uVendorECX, uVendorEDX) + || RTX86IsHygonCpu(uVendorEBX, uVendorECX, uVendorEDX)) + { + /* Not well documented, but at least all AMD64 CPUs support this. */ + if (RTX86GetCpuFamily(uTFMSEAX) >= 15) + { + /* Revision is in the low dword. */ + uRevMsr = ASMRdMsr(MSR_IA32_BIOS_SIGN_ID); /* Same MSR as Intel. */ + *puRevision = RT_LODWORD(uRevMsr); + rc = VINF_SUCCESS; + } + } + } + } + + RTThreadPreemptRestore(&PreemptState); + + return rc; +} + + +/** + * Queries the CPU microcode revision. + * + * @returns VBox status code. + * @retval VERR_UNSUPPORTED_CPU if not identifiable as a processor with + * readable microcode rev. + * + * @param pSession The session handle. + * @param puRevision Where to store the microcode revision. + */ +SUPR0DECL(int) SUPR0QueryUcodeRev(PSUPDRVSESSION pSession, uint32_t *puRevision) +{ + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(puRevision, VERR_INVALID_POINTER); + + /* + * Call common worker. + */ + return supdrvQueryUcodeRev(puRevision); +} +SUPR0_EXPORT_SYMBOL(SUPR0QueryUcodeRev); + + +/** + * Gets hardware-virtualization MSRs of the calling CPU. + * + * @returns VBox status code. + * @param pMsrs Where to store the hardware-virtualization MSRs. + * @param fCaps Hardware virtualization capabilities (SUPVTCAPS_XXX). Pass 0 + * to explicitly check for the presence of VT-x/AMD-V before + * querying MSRs. + * @param fForce Force querying of MSRs from the hardware. + */ +SUPR0DECL(int) SUPR0GetHwvirtMsrs(PSUPHWVIRTMSRS pMsrs, uint32_t fCaps, bool fForce) +{ + NOREF(fForce); + + int rc; + RTTHREADPREEMPTSTATE PreemptState = RTTHREADPREEMPTSTATE_INITIALIZER; + + /* + * Input validation. + */ + AssertPtrReturn(pMsrs, VERR_INVALID_POINTER); + + /* + * Disable preemption so we make sure we don't migrate CPUs and because + * we access global data. + */ + RTThreadPreemptDisable(&PreemptState); + + /* + * Query the MSRs from the hardware. + */ + SUPHWVIRTMSRS Msrs; + RT_ZERO(Msrs); + + /* If the caller claims VT-x/AMD-V is supported, don't need to recheck it. */ + if (!(fCaps & (SUPVTCAPS_VT_X | SUPVTCAPS_AMD_V))) + rc = SUPR0GetVTSupport(&fCaps); + else + rc = VINF_SUCCESS; + if (RT_SUCCESS(rc)) + { + if (fCaps & SUPVTCAPS_VT_X) + { + Msrs.u.vmx.u64FeatCtrl = ASMRdMsr(MSR_IA32_FEATURE_CONTROL); + Msrs.u.vmx.u64Basic = ASMRdMsr(MSR_IA32_VMX_BASIC); + Msrs.u.vmx.PinCtls.u = ASMRdMsr(MSR_IA32_VMX_PINBASED_CTLS); + Msrs.u.vmx.ProcCtls.u = ASMRdMsr(MSR_IA32_VMX_PROCBASED_CTLS); + Msrs.u.vmx.ExitCtls.u = ASMRdMsr(MSR_IA32_VMX_EXIT_CTLS); + Msrs.u.vmx.EntryCtls.u = ASMRdMsr(MSR_IA32_VMX_ENTRY_CTLS); + Msrs.u.vmx.u64Misc = ASMRdMsr(MSR_IA32_VMX_MISC); + Msrs.u.vmx.u64Cr0Fixed0 = ASMRdMsr(MSR_IA32_VMX_CR0_FIXED0); + Msrs.u.vmx.u64Cr0Fixed1 = ASMRdMsr(MSR_IA32_VMX_CR0_FIXED1); + Msrs.u.vmx.u64Cr4Fixed0 = ASMRdMsr(MSR_IA32_VMX_CR4_FIXED0); + Msrs.u.vmx.u64Cr4Fixed1 = ASMRdMsr(MSR_IA32_VMX_CR4_FIXED1); + Msrs.u.vmx.u64VmcsEnum = ASMRdMsr(MSR_IA32_VMX_VMCS_ENUM); + + if (RT_BF_GET(Msrs.u.vmx.u64Basic, VMX_BF_BASIC_TRUE_CTLS)) + { + Msrs.u.vmx.TruePinCtls.u = ASMRdMsr(MSR_IA32_VMX_TRUE_PINBASED_CTLS); + Msrs.u.vmx.TrueProcCtls.u = ASMRdMsr(MSR_IA32_VMX_TRUE_PROCBASED_CTLS); + Msrs.u.vmx.TrueEntryCtls.u = ASMRdMsr(MSR_IA32_VMX_TRUE_ENTRY_CTLS); + Msrs.u.vmx.TrueExitCtls.u = ASMRdMsr(MSR_IA32_VMX_TRUE_EXIT_CTLS); + } + + if (Msrs.u.vmx.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_SECONDARY_CTLS) + { + Msrs.u.vmx.ProcCtls2.u = ASMRdMsr(MSR_IA32_VMX_PROCBASED_CTLS2); + + if (Msrs.u.vmx.ProcCtls2.n.allowed1 & (VMX_PROC_CTLS2_EPT | VMX_PROC_CTLS2_VPID)) + Msrs.u.vmx.u64EptVpidCaps = ASMRdMsr(MSR_IA32_VMX_EPT_VPID_CAP); + + if (Msrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VMFUNC) + Msrs.u.vmx.u64VmFunc = ASMRdMsr(MSR_IA32_VMX_VMFUNC); + } + + if (Msrs.u.vmx.ProcCtls.n.allowed1 & VMX_PROC_CTLS_USE_TERTIARY_CTLS) + Msrs.u.vmx.u64ProcCtls3 = ASMRdMsr(MSR_IA32_VMX_PROCBASED_CTLS3); + + if (Msrs.u.vmx.ExitCtls.n.allowed1 & VMX_EXIT_CTLS_USE_SECONDARY_CTLS) + Msrs.u.vmx.u64ExitCtls2 = ASMRdMsr(MSR_IA32_VMX_EXIT_CTLS2); + } + else if (fCaps & SUPVTCAPS_AMD_V) + { + Msrs.u.svm.u64MsrHwcr = ASMRdMsr(MSR_K8_HWCR); + Msrs.u.svm.u64MsrSmmAddr = ASMRdMsr(MSR_K7_SMM_ADDR); + Msrs.u.svm.u64MsrSmmMask = ASMRdMsr(MSR_K7_SMM_MASK); + } + else + { + RTThreadPreemptRestore(&PreemptState); + AssertMsgFailedReturn(("SUPR0GetVTSupport returns success but neither VT-x nor AMD-V reported!\n"), + VERR_INTERNAL_ERROR_2); + } + + /* + * Copy the MSRs out. + */ + memcpy(pMsrs, &Msrs, sizeof(*pMsrs)); + } + + RTThreadPreemptRestore(&PreemptState); + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetHwvirtMsrs); + + +/** + * Register a component factory with the support driver. + * + * This is currently restricted to kernel sessions only. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_NO_MEMORY if we're out of memory. + * @retval VERR_ALREADY_EXISTS if the factory has already been registered. + * @retval VERR_ACCESS_DENIED if it isn't a kernel session. + * @retval VERR_INVALID_PARAMETER on invalid parameter. + * @retval VERR_INVALID_POINTER on invalid pointer parameter. + * + * @param pSession The SUPDRV session (must be a ring-0 session). + * @param pFactory Pointer to the component factory registration structure. + * + * @remarks This interface is also available via SUPR0IdcComponentRegisterFactory. + */ +SUPR0DECL(int) SUPR0ComponentRegisterFactory(PSUPDRVSESSION pSession, PCSUPDRVFACTORY pFactory) +{ + PSUPDRVFACTORYREG pNewReg; + const char *psz; + int rc; + + /* + * Validate parameters. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process == NIL_RTR0PROCESS, VERR_ACCESS_DENIED); + AssertPtrReturn(pFactory, VERR_INVALID_POINTER); + AssertPtrReturn(pFactory->pfnQueryFactoryInterface, VERR_INVALID_POINTER); + psz = RTStrEnd(pFactory->szName, sizeof(pFactory->szName)); + AssertReturn(psz, VERR_INVALID_PARAMETER); + + /* + * Allocate and initialize a new registration structure. + */ + pNewReg = (PSUPDRVFACTORYREG)RTMemAlloc(sizeof(SUPDRVFACTORYREG)); + if (pNewReg) + { + pNewReg->pNext = NULL; + pNewReg->pFactory = pFactory; + pNewReg->pSession = pSession; + pNewReg->cchName = psz - &pFactory->szName[0]; + + /* + * Add it to the tail of the list after checking for prior registration. + */ + rc = RTSemFastMutexRequest(pSession->pDevExt->mtxComponentFactory); + if (RT_SUCCESS(rc)) + { + PSUPDRVFACTORYREG pPrev = NULL; + PSUPDRVFACTORYREG pCur = pSession->pDevExt->pComponentFactoryHead; + while (pCur && pCur->pFactory != pFactory) + { + pPrev = pCur; + pCur = pCur->pNext; + } + if (!pCur) + { + if (pPrev) + pPrev->pNext = pNewReg; + else + pSession->pDevExt->pComponentFactoryHead = pNewReg; + rc = VINF_SUCCESS; + } + else + rc = VERR_ALREADY_EXISTS; + + RTSemFastMutexRelease(pSession->pDevExt->mtxComponentFactory); + } + + if (RT_FAILURE(rc)) + RTMemFree(pNewReg); + } + else + rc = VERR_NO_MEMORY; + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ComponentRegisterFactory); + + +/** + * Deregister a component factory. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_NOT_FOUND if the factory wasn't registered. + * @retval VERR_ACCESS_DENIED if it isn't a kernel session. + * @retval VERR_INVALID_PARAMETER on invalid parameter. + * @retval VERR_INVALID_POINTER on invalid pointer parameter. + * + * @param pSession The SUPDRV session (must be a ring-0 session). + * @param pFactory Pointer to the component factory registration structure + * previously passed SUPR0ComponentRegisterFactory(). + * + * @remarks This interface is also available via SUPR0IdcComponentDeregisterFactory. + */ +SUPR0DECL(int) SUPR0ComponentDeregisterFactory(PSUPDRVSESSION pSession, PCSUPDRVFACTORY pFactory) +{ + int rc; + + /* + * Validate parameters. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process == NIL_RTR0PROCESS, VERR_ACCESS_DENIED); + AssertPtrReturn(pFactory, VERR_INVALID_POINTER); + + /* + * Take the lock and look for the registration record. + */ + rc = RTSemFastMutexRequest(pSession->pDevExt->mtxComponentFactory); + if (RT_SUCCESS(rc)) + { + PSUPDRVFACTORYREG pPrev = NULL; + PSUPDRVFACTORYREG pCur = pSession->pDevExt->pComponentFactoryHead; + while (pCur && pCur->pFactory != pFactory) + { + pPrev = pCur; + pCur = pCur->pNext; + } + if (pCur) + { + if (!pPrev) + pSession->pDevExt->pComponentFactoryHead = pCur->pNext; + else + pPrev->pNext = pCur->pNext; + + pCur->pNext = NULL; + pCur->pFactory = NULL; + pCur->pSession = NULL; + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + + RTSemFastMutexRelease(pSession->pDevExt->mtxComponentFactory); + + RTMemFree(pCur); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ComponentDeregisterFactory); + + +/** + * Queries a component factory. + * + * @returns VBox status code. + * @retval VERR_INVALID_PARAMETER on invalid parameter. + * @retval VERR_INVALID_POINTER on invalid pointer parameter. + * @retval VERR_SUPDRV_COMPONENT_NOT_FOUND if the component factory wasn't found. + * @retval VERR_SUPDRV_INTERFACE_NOT_SUPPORTED if the interface wasn't supported. + * + * @param pSession The SUPDRV session. + * @param pszName The name of the component factory. + * @param pszInterfaceUuid The UUID of the factory interface (stringified). + * @param ppvFactoryIf Where to store the factory interface. + */ +SUPR0DECL(int) SUPR0ComponentQueryFactory(PSUPDRVSESSION pSession, const char *pszName, const char *pszInterfaceUuid, void **ppvFactoryIf) +{ + const char *pszEnd; + size_t cchName; + int rc; + + /* + * Validate parameters. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + pszEnd = RTStrEnd(pszName, RT_SIZEOFMEMB(SUPDRVFACTORY, szName)); + AssertReturn(pszEnd, VERR_INVALID_PARAMETER); + cchName = pszEnd - pszName; + + AssertPtrReturn(pszInterfaceUuid, VERR_INVALID_POINTER); + pszEnd = RTStrEnd(pszInterfaceUuid, RTUUID_STR_LENGTH); + AssertReturn(pszEnd, VERR_INVALID_PARAMETER); + + AssertPtrReturn(ppvFactoryIf, VERR_INVALID_POINTER); + *ppvFactoryIf = NULL; + + /* + * Take the lock and try all factories by this name. + */ + rc = RTSemFastMutexRequest(pSession->pDevExt->mtxComponentFactory); + if (RT_SUCCESS(rc)) + { + PSUPDRVFACTORYREG pCur = pSession->pDevExt->pComponentFactoryHead; + rc = VERR_SUPDRV_COMPONENT_NOT_FOUND; + while (pCur) + { + if ( pCur->cchName == cchName + && !memcmp(pCur->pFactory->szName, pszName, cchName)) + { + void *pvFactory = pCur->pFactory->pfnQueryFactoryInterface(pCur->pFactory, pSession, pszInterfaceUuid); + if (pvFactory) + { + *ppvFactoryIf = pvFactory; + rc = VINF_SUCCESS; + break; + } + rc = VERR_SUPDRV_INTERFACE_NOT_SUPPORTED; + } + + /* next */ + pCur = pCur->pNext; + } + + RTSemFastMutexRelease(pSession->pDevExt->mtxComponentFactory); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0ComponentQueryFactory); + + +/** + * Adds a memory object to the session. + * + * @returns IPRT status code. + * @param pMem Memory tracking structure containing the + * information to track. + * @param pSession The session. + */ +static int supdrvMemAdd(PSUPDRVMEMREF pMem, PSUPDRVSESSION pSession) +{ + PSUPDRVBUNDLE pBundle; + + /* + * Find free entry and record the allocation. + */ + RTSpinlockAcquire(pSession->Spinlock); + for (pBundle = &pSession->Bundle; pBundle; pBundle = pBundle->pNext) + { + if (pBundle->cUsed < RT_ELEMENTS(pBundle->aMem)) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if (pBundle->aMem[i].MemObj == NIL_RTR0MEMOBJ) + { + pBundle->cUsed++; + pBundle->aMem[i] = *pMem; + RTSpinlockRelease(pSession->Spinlock); + return VINF_SUCCESS; + } + } + AssertFailed(); /* !!this can't be happening!!! */ + } + } + RTSpinlockRelease(pSession->Spinlock); + + /* + * Need to allocate a new bundle. + * Insert into the last entry in the bundle. + */ + pBundle = (PSUPDRVBUNDLE)RTMemAllocZ(sizeof(*pBundle)); + if (!pBundle) + return VERR_NO_MEMORY; + + /* take last entry. */ + pBundle->cUsed++; + pBundle->aMem[RT_ELEMENTS(pBundle->aMem) - 1] = *pMem; + + /* insert into list. */ + RTSpinlockAcquire(pSession->Spinlock); + pBundle->pNext = pSession->Bundle.pNext; + pSession->Bundle.pNext = pBundle; + RTSpinlockRelease(pSession->Spinlock); + + return VINF_SUCCESS; +} + + +/** + * Releases a memory object referenced by pointer and type. + * + * @returns IPRT status code. + * @param pSession Session data. + * @param uPtr Pointer to memory. This is matched against both the R0 and R3 addresses. + * @param eType Memory type. + */ +static int supdrvMemRelease(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr, SUPDRVMEMREFTYPE eType) +{ + PSUPDRVBUNDLE pBundle; + + /* + * Validate input. + */ + if (!uPtr) + { + Log(("Illegal address %p\n", (void *)uPtr)); + return VERR_INVALID_PARAMETER; + } + + /* + * Search for the address. + */ + RTSpinlockAcquire(pSession->Spinlock); + for (pBundle = &pSession->Bundle; pBundle; pBundle = pBundle->pNext) + { + if (pBundle->cUsed > 0) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pBundle->aMem); i++) + { + if ( pBundle->aMem[i].eType == eType + && pBundle->aMem[i].MemObj != NIL_RTR0MEMOBJ + && ( (RTHCUINTPTR)RTR0MemObjAddress(pBundle->aMem[i].MemObj) == uPtr + || ( pBundle->aMem[i].MapObjR3 != NIL_RTR0MEMOBJ + && RTR0MemObjAddressR3(pBundle->aMem[i].MapObjR3) == uPtr)) + ) + { + /* Make a copy of it and release it outside the spinlock. */ + SUPDRVMEMREF Mem = pBundle->aMem[i]; + pBundle->aMem[i].eType = MEMREF_TYPE_UNUSED; + pBundle->aMem[i].MemObj = NIL_RTR0MEMOBJ; + pBundle->aMem[i].MapObjR3 = NIL_RTR0MEMOBJ; + RTSpinlockRelease(pSession->Spinlock); + + if (Mem.MapObjR3 != NIL_RTR0MEMOBJ) + { + int rc = RTR0MemObjFree(Mem.MapObjR3, false); + AssertRC(rc); /** @todo figure out how to handle this. */ + } + if (Mem.MemObj != NIL_RTR0MEMOBJ) + { + int rc = RTR0MemObjFree(Mem.MemObj, true /* fFreeMappings */); + AssertRC(rc); /** @todo figure out how to handle this. */ + } + return VINF_SUCCESS; + } + } + } + } + RTSpinlockRelease(pSession->Spinlock); + Log(("Failed to find %p!!! (eType=%d)\n", (void *)uPtr, eType)); + return VERR_INVALID_PARAMETER; +} + + +/** + * Opens an image. If it's the first time it's opened the call must upload + * the bits using the supdrvIOCtl_LdrLoad() / SUPDRV_IOCTL_LDR_LOAD function. + * + * This is the 1st step of the loading. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + * @param pSession Session data. + * @param pReq The open request. + */ +static int supdrvIOCtl_LdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDROPEN pReq) +{ + int rc; + PSUPDRVLDRIMAGE pImage; + void *pv; + size_t cchName = strlen(pReq->u.In.szName); /* (caller checked < 32). */ + SUPDRV_CHECK_SMAP_SETUP(); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + LogFlow(("supdrvIOCtl_LdrOpen: szName=%s cbImageWithEverything=%d\n", pReq->u.In.szName, pReq->u.In.cbImageWithEverything)); + + /* + * Check if we got an instance of the image already. + */ + supdrvLdrLock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + for (pImage = pDevExt->pLdrImages; pImage; pImage = pImage->pNext) + { + if ( pImage->szName[cchName] == '\0' + && !memcmp(pImage->szName, pReq->u.In.szName, cchName)) + { + /** @todo Add an _1M (or something) per session reference. */ + if (RT_LIKELY(pImage->cImgUsage < UINT32_MAX / 2U)) + { + /** @todo check cbImageBits and cbImageWithEverything here, if they differs + * that indicates that the images are different. */ + pReq->u.Out.pvImageBase = pImage->pvImage; + pReq->u.Out.fNeedsLoading = pImage->uState == SUP_IOCTL_LDR_OPEN; + pReq->u.Out.fNativeLoader = pImage->fNative; + supdrvLdrAddUsage(pDevExt, pSession, pImage, true /*fRing3Usage*/); + supdrvLdrUnlock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + return VINF_SUCCESS; + } + supdrvLdrUnlock(pDevExt); + Log(("supdrvIOCtl_LdrOpen: Too many existing references to '%s'!\n", pReq->u.In.szName)); + return VERR_TOO_MANY_REFERENCES; + } + } + /* (not found - add it!) */ + + /* If the loader interface is locked down, make userland fail early */ + if (pDevExt->fLdrLockedDown) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvIOCtl_LdrOpen: Not adding '%s' to image list, loader interface is locked down!\n", pReq->u.In.szName)); + return VERR_PERMISSION_DENIED; + } + + /* Stop if caller doesn't wish to prepare loading things. */ + if (!pReq->u.In.cbImageBits) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvIOCtl_LdrOpen: Returning VERR_MODULE_NOT_FOUND for '%s'!\n", pReq->u.In.szName)); + return VERR_MODULE_NOT_FOUND; + } + + /* + * Allocate memory. + */ + Assert(cchName < sizeof(pImage->szName)); + pv = RTMemAllocZ(sizeof(SUPDRVLDRIMAGE)); + if (!pv) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvIOCtl_LdrOpen: RTMemAllocZ() failed\n")); + return VERR_NO_MEMORY; + } + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + /* + * Setup and link in the LDR stuff. + */ + pImage = (PSUPDRVLDRIMAGE)pv; + pImage->pvImage = NULL; + pImage->hMemObjImage = NIL_RTR0MEMOBJ; + pImage->cbImageWithEverything = pReq->u.In.cbImageWithEverything; + pImage->cbImageBits = pReq->u.In.cbImageBits; + pImage->cSymbols = 0; + pImage->paSymbols = NULL; + pImage->pachStrTab = NULL; + pImage->cbStrTab = 0; + pImage->cSegments = 0; + pImage->paSegments = NULL; + pImage->pfnModuleInit = NULL; + pImage->pfnModuleTerm = NULL; + pImage->pfnServiceReqHandler = NULL; + pImage->uState = SUP_IOCTL_LDR_OPEN; + pImage->cImgUsage = 0; /* Increased by supdrvLdrAddUsage later */ + pImage->pDevExt = pDevExt; + pImage->pImageImport = NULL; + pImage->uMagic = SUPDRVLDRIMAGE_MAGIC; + pImage->pWrappedModInfo = NULL; + memcpy(pImage->szName, pReq->u.In.szName, cchName + 1); + + /* + * Try load it using the native loader, if that isn't supported, fall back + * on the older method. + */ + pImage->fNative = true; + rc = supdrvOSLdrOpen(pDevExt, pImage, pReq->u.In.szFilename); + if (rc == VERR_NOT_SUPPORTED) + { + rc = RTR0MemObjAllocPage(&pImage->hMemObjImage, pImage->cbImageBits, true /*fExecutable*/); + if (RT_SUCCESS(rc)) + { + pImage->pvImage = RTR0MemObjAddress(pImage->hMemObjImage); + pImage->fNative = false; + } + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + } + if (RT_SUCCESS(rc)) + rc = supdrvLdrAddUsage(pDevExt, pSession, pImage, true /*fRing3Usage*/); + if (RT_FAILURE(rc)) + { + supdrvLdrUnlock(pDevExt); + pImage->uMagic = SUPDRVLDRIMAGE_MAGIC_DEAD; + RTMemFree(pImage); + Log(("supdrvIOCtl_LdrOpen(%s): failed - %Rrc\n", pReq->u.In.szName, rc)); + return rc; + } + Assert(RT_VALID_PTR(pImage->pvImage) || RT_FAILURE(rc)); + + /* + * Link it. + */ + pImage->pNext = pDevExt->pLdrImages; + pDevExt->pLdrImages = pImage; + + pReq->u.Out.pvImageBase = pImage->pvImage; + pReq->u.Out.fNeedsLoading = true; + pReq->u.Out.fNativeLoader = pImage->fNative; + supdrvOSLdrNotifyOpened(pDevExt, pImage, pReq->u.In.szFilename); + + supdrvLdrUnlock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + return VINF_SUCCESS; +} + + +/** + * Formats a load error message. + * + * @returns @a rc + * @param rc Return code. + * @param pReq The request. + * @param pszFormat The error message format string. + * @param ... Argument to the format string. + */ +int VBOXCALL supdrvLdrLoadError(int rc, PSUPLDRLOAD pReq, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + pReq->u.Out.uErrorMagic = SUPLDRLOAD_ERROR_MAGIC; + RTStrPrintfV(pReq->u.Out.szError, sizeof(pReq->u.Out.szError), pszFormat, va); + va_end(va); + Log(("SUP_IOCTL_LDR_LOAD: %s [rc=%Rrc]\n", pReq->u.Out.szError, rc)); + return rc; +} + + +/** + * Worker that validates a pointer to an image entrypoint. + * + * Calls supdrvLdrLoadError on error. + * + * @returns IPRT status code. + * @param pDevExt The device globals. + * @param pImage The loader image. + * @param pv The pointer into the image. + * @param fMayBeNull Whether it may be NULL. + * @param pszSymbol The entrypoint name or log name. If the symbol is + * capitalized it signifies a specific symbol, otherwise it + * for logging. + * @param pbImageBits The image bits prepared by ring-3. + * @param pReq The request for passing to supdrvLdrLoadError. + * + * @note Will leave the loader lock on failure! + */ +static int supdrvLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, bool fMayBeNull, + const uint8_t *pbImageBits, const char *pszSymbol, PSUPLDRLOAD pReq) +{ + if (!fMayBeNull || pv) + { + uint32_t iSeg; + + /* Must be within the image bits: */ + uintptr_t const uRva = (uintptr_t)pv - (uintptr_t)pImage->pvImage; + if (uRva >= pImage->cbImageBits) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, + "Invalid entry point address %p given for %s: RVA %#zx, image size %#zx", + pv, pszSymbol, uRva, pImage->cbImageBits); + } + + /* Must be in an executable segment: */ + for (iSeg = 0; iSeg < pImage->cSegments; iSeg++) + if (uRva - pImage->paSegments[iSeg].off < (uintptr_t)pImage->paSegments[iSeg].cb) + { + if (pImage->paSegments[iSeg].fProt & SUPLDR_PROT_EXEC) + break; + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, + "Bad entry point %p given for %s: not executable (seg #%u: %#RX32 LB %#RX32 prot %#x)", + pv, pszSymbol, iSeg, pImage->paSegments[iSeg].off, pImage->paSegments[iSeg].cb, + pImage->paSegments[iSeg].fProt); + } + if (iSeg >= pImage->cSegments) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, + "Bad entry point %p given for %s: no matching segment found (RVA %#zx)!", + pv, pszSymbol, uRva); + } + + if (pImage->fNative) + { + /** @todo pass pReq along to the native code. */ + int rc = supdrvOSLdrValidatePointer(pDevExt, pImage, pv, pbImageBits, pszSymbol); + if (RT_FAILURE(rc)) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, + "Bad entry point address %p for %s: rc=%Rrc\n", pv, pszSymbol, rc); + } + } + } + return VINF_SUCCESS; +} + + +/** + * Loads the image bits. + * + * This is the 2nd step of the loading. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + * @param pSession Session data. + * @param pReq The request. + */ +static int supdrvIOCtl_LdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRLOAD pReq) +{ + PSUPDRVLDRUSAGE pUsage; + PSUPDRVLDRIMAGE pImage; + PSUPDRVLDRIMAGE pImageImport; + int rc; + SUPDRV_CHECK_SMAP_SETUP(); + LogFlow(("supdrvIOCtl_LdrLoad: pvImageBase=%p cbImageWithEverything=%d\n", pReq->u.In.pvImageBase, pReq->u.In.cbImageWithEverything)); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + /* + * Find the ldr image. + */ + supdrvLdrLock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + pUsage = pSession->pLdrUsage; + while (pUsage && pUsage->pImage->pvImage != pReq->u.In.pvImageBase) + pUsage = pUsage->pNext; + if (!pUsage) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_HANDLE, pReq, "Image not found"); + } + pImage = pUsage->pImage; + + /* + * Validate input. + */ + if ( pImage->cbImageWithEverything != pReq->u.In.cbImageWithEverything + || pImage->cbImageBits != pReq->u.In.cbImageBits) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_HANDLE, pReq, "Image size mismatch found: %u(prep) != %u(load) or %u != %u", + pImage->cbImageWithEverything, pReq->u.In.cbImageWithEverything, pImage->cbImageBits, pReq->u.In.cbImageBits); + } + + if (pImage->uState != SUP_IOCTL_LDR_OPEN) + { + unsigned uState = pImage->uState; + supdrvLdrUnlock(pDevExt); + if (uState != SUP_IOCTL_LDR_LOAD) + AssertMsgFailed(("SUP_IOCTL_LDR_LOAD: invalid image state %d (%#x)!\n", uState, uState)); + pReq->u.Out.uErrorMagic = 0; + return VERR_ALREADY_LOADED; + } + + /* If the loader interface is locked down, don't load new images */ + if (pDevExt->fLdrLockedDown) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_PERMISSION_DENIED, pReq, "Loader is locked down"); + } + + /* + * If the new image is a dependant of VMMR0.r0, resolve it via the + * caller's usage list and make sure it's in ready state. + */ + pImageImport = NULL; + if (pReq->u.In.fFlags & SUPLDRLOAD_F_DEP_VMMR0) + { + PSUPDRVLDRUSAGE pUsageDependency = pSession->pLdrUsage; + while (pUsageDependency && pUsageDependency->pImage->pvImage != pDevExt->pvVMMR0) + pUsageDependency = pUsageDependency->pNext; + if (!pUsageDependency || !pDevExt->pvVMMR0) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_MODULE_NOT_FOUND, pReq, "VMMR0.r0 not loaded by session"); + } + pImageImport = pUsageDependency->pImage; + if (pImageImport->uState != SUP_IOCTL_LDR_LOAD) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_MODULE_NOT_FOUND, pReq, "VMMR0.r0 is not ready (state %#x)", pImageImport->uState); + } + } + + /* + * Copy the segments before we start using supdrvLdrValidatePointer for entrypoint validation. + */ + pImage->cSegments = pReq->u.In.cSegments; + { + size_t cbSegments = pImage->cSegments * sizeof(SUPLDRSEG); + pImage->paSegments = (PSUPLDRSEG)RTMemDup(&pReq->u.In.abImage[pReq->u.In.offSegments], cbSegments); + if (pImage->paSegments) /* Align the last segment size to avoid upsetting RTR0MemObjProtect. */ /** @todo relax RTR0MemObjProtect */ + pImage->paSegments[pImage->cSegments - 1].cb = RT_ALIGN_32(pImage->paSegments[pImage->cSegments - 1].cb, PAGE_SIZE); + else + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_NO_MEMORY, pReq, "Out of memory for segment table: %#x", cbSegments); + } + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + } + + /* + * Validate entrypoints. + */ + switch (pReq->u.In.eEPType) + { + case SUPLDRLOADEP_NOTHING: + break; + + case SUPLDRLOADEP_VMMR0: + rc = supdrvLdrValidatePointer(pDevExt, pImage, pReq->u.In.EP.VMMR0.pvVMMR0EntryFast, false, pReq->u.In.abImage, "VMMR0EntryFast", pReq); + if (RT_FAILURE(rc)) + return rc; + rc = supdrvLdrValidatePointer(pDevExt, pImage, pReq->u.In.EP.VMMR0.pvVMMR0EntryEx, false, pReq->u.In.abImage, "VMMR0EntryEx", pReq); + if (RT_FAILURE(rc)) + return rc; + + /* Fail here if there is already a VMMR0 module. */ + if (pDevExt->pvVMMR0 != NULL) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, "There is already a VMMR0 module loaded (%p)", pDevExt->pvVMMR0); + } + break; + + case SUPLDRLOADEP_SERVICE: + rc = supdrvLdrValidatePointer(pDevExt, pImage, pReq->u.In.EP.Service.pfnServiceReq, false, pReq->u.In.abImage, "pfnServiceReq", pReq); + if (RT_FAILURE(rc)) + return rc; + if ( pReq->u.In.EP.Service.apvReserved[0] != NIL_RTR0PTR + || pReq->u.In.EP.Service.apvReserved[1] != NIL_RTR0PTR + || pReq->u.In.EP.Service.apvReserved[2] != NIL_RTR0PTR) + { + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, "apvReserved={%p,%p,%p} MBZ!", + pReq->u.In.EP.Service.apvReserved[0], pReq->u.In.EP.Service.apvReserved[1], + pReq->u.In.EP.Service.apvReserved[2]); + } + break; + + default: + supdrvLdrUnlock(pDevExt); + return supdrvLdrLoadError(VERR_INVALID_PARAMETER, pReq, "Invalid eEPType=%d", pReq->u.In.eEPType); + } + + rc = supdrvLdrValidatePointer(pDevExt, pImage, pReq->u.In.pfnModuleInit, true, pReq->u.In.abImage, "ModuleInit", pReq); + if (RT_FAILURE(rc)) + return rc; + rc = supdrvLdrValidatePointer(pDevExt, pImage, pReq->u.In.pfnModuleTerm, true, pReq->u.In.abImage, "ModuleTerm", pReq); + if (RT_FAILURE(rc)) + return rc; + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + /* + * Allocate and copy the tables if non-native. + * (No need to do try/except as this is a buffered request.) + */ + if (!pImage->fNative) + { + pImage->cbStrTab = pReq->u.In.cbStrTab; + if (pImage->cbStrTab) + { + pImage->pachStrTab = (char *)RTMemDup(&pReq->u.In.abImage[pReq->u.In.offStrTab], pImage->cbStrTab); + if (!pImage->pachStrTab) + rc = supdrvLdrLoadError(VERR_NO_MEMORY, pReq, "Out of memory for string table: %#x", pImage->cbStrTab); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + } + + pImage->cSymbols = pReq->u.In.cSymbols; + if (RT_SUCCESS(rc) && pImage->cSymbols) + { + size_t cbSymbols = pImage->cSymbols * sizeof(SUPLDRSYM); + pImage->paSymbols = (PSUPLDRSYM)RTMemDup(&pReq->u.In.abImage[pReq->u.In.offSymbols], cbSymbols); + if (!pImage->paSymbols) + rc = supdrvLdrLoadError(VERR_NO_MEMORY, pReq, "Out of memory for symbol table: %#x", cbSymbols); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + } + } + + /* + * Copy the bits and apply permissions / complete native loading. + */ + if (RT_SUCCESS(rc)) + { + pImage->uState = SUP_IOCTL_LDR_LOAD; + pImage->pfnModuleInit = (PFNR0MODULEINIT)(uintptr_t)pReq->u.In.pfnModuleInit; + pImage->pfnModuleTerm = (PFNR0MODULETERM)(uintptr_t)pReq->u.In.pfnModuleTerm; + + if (pImage->fNative) + rc = supdrvOSLdrLoad(pDevExt, pImage, pReq->u.In.abImage, pReq); + else + { + uint32_t i; + memcpy(pImage->pvImage, &pReq->u.In.abImage[0], pImage->cbImageBits); + + for (i = 0; i < pImage->cSegments; i++) + { + rc = RTR0MemObjProtect(pImage->hMemObjImage, pImage->paSegments[i].off, pImage->paSegments[i].cb, + pImage->paSegments[i].fProt); + if (RT_SUCCESS(rc)) + continue; + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + else + rc = supdrvLdrLoadError(rc, pReq, "RTR0MemObjProtect failed on seg#%u %#RX32 LB %#RX32 fProt=%#x", + i, pImage->paSegments[i].off, pImage->paSegments[i].cb, pImage->paSegments[i].fProt); + break; + } + Log(("vboxdrv: Loaded '%s' at %p\n", pImage->szName, pImage->pvImage)); + } + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + } + + /* + * On success call the module initialization. + */ + LogFlow(("supdrvIOCtl_LdrLoad: pfnModuleInit=%p\n", pImage->pfnModuleInit)); + if (RT_SUCCESS(rc) && pImage->pfnModuleInit) + { + Log(("supdrvIOCtl_LdrLoad: calling pfnModuleInit=%p\n", pImage->pfnModuleInit)); + pDevExt->pLdrInitImage = pImage; + pDevExt->hLdrInitThread = RTThreadNativeSelf(); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + rc = pImage->pfnModuleInit(pImage); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + pDevExt->pLdrInitImage = NULL; + pDevExt->hLdrInitThread = NIL_RTNATIVETHREAD; + if (RT_FAILURE(rc)) + supdrvLdrLoadError(rc, pReq, "ModuleInit failed: %Rrc", rc); + } + if (RT_SUCCESS(rc)) + { + /* + * Publish any standard entry points. + */ + switch (pReq->u.In.eEPType) + { + case SUPLDRLOADEP_VMMR0: + Assert(!pDevExt->pvVMMR0); + Assert(!pDevExt->pfnVMMR0EntryFast); + Assert(!pDevExt->pfnVMMR0EntryEx); + ASMAtomicWritePtrVoid(&pDevExt->pvVMMR0, pImage->pvImage); + ASMAtomicWritePtrVoid((void * volatile *)(uintptr_t)&pDevExt->pfnVMMR0EntryFast, + (void *)(uintptr_t) pReq->u.In.EP.VMMR0.pvVMMR0EntryFast); + ASMAtomicWritePtrVoid((void * volatile *)(uintptr_t)&pDevExt->pfnVMMR0EntryEx, + (void *)(uintptr_t) pReq->u.In.EP.VMMR0.pvVMMR0EntryEx); + break; + case SUPLDRLOADEP_SERVICE: + pImage->pfnServiceReqHandler = (PFNSUPR0SERVICEREQHANDLER)(uintptr_t)pReq->u.In.EP.Service.pfnServiceReq; + break; + default: + break; + } + + /* + * Increase the usage counter of any imported image. + */ + if (pImageImport) + { + pImageImport->cImgUsage++; + if (pImageImport->cImgUsage == 2 && pImageImport->pWrappedModInfo) + supdrvOSLdrRetainWrapperModule(pDevExt, pImageImport); + pImage->pImageImport = pImageImport; + } + + /* + * Done! + */ + SUPR0Printf("vboxdrv: %RKv %s\n", pImage->pvImage, pImage->szName); + pReq->u.Out.uErrorMagic = 0; + pReq->u.Out.szError[0] = '\0'; + } + else + { + /* Inform the tracing component in case ModuleInit registered TPs. */ + supdrvTracerModuleUnloading(pDevExt, pImage); + + pImage->uState = SUP_IOCTL_LDR_OPEN; + pImage->pfnModuleInit = NULL; + pImage->pfnModuleTerm = NULL; + pImage->pfnServiceReqHandler= NULL; + pImage->cbStrTab = 0; + RTMemFree(pImage->pachStrTab); + pImage->pachStrTab = NULL; + RTMemFree(pImage->paSymbols); + pImage->paSymbols = NULL; + pImage->cSymbols = 0; + } + + supdrvLdrUnlock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + return rc; +} + + +/** + * Registers a .r0 module wrapped in a native one and manually loaded. + * + * @returns VINF_SUCCESS or error code (no info statuses). + * @param pDevExt Device globals. + * @param pWrappedModInfo The wrapped module info. + * @param pvNative OS specific information. + * @param phMod Where to store the module handle. + */ +int VBOXCALL supdrvLdrRegisterWrappedModule(PSUPDRVDEVEXT pDevExt, PCSUPLDRWRAPPEDMODULE pWrappedModInfo, + void *pvNative, void **phMod) +{ + size_t cchName; + PSUPDRVLDRIMAGE pImage; + PCSUPLDRWRAPMODSYMBOL paSymbols; + uint16_t idx; + const char *pszPrevSymbol; + int rc; + SUPDRV_CHECK_SMAP_SETUP(); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + /* + * Validate input. + */ + AssertPtrReturn(phMod, VERR_INVALID_POINTER); + *phMod = NULL; + AssertPtrReturn(pDevExt, VERR_INTERNAL_ERROR_2); + + AssertPtrReturn(pWrappedModInfo, VERR_INVALID_POINTER); + AssertMsgReturn(pWrappedModInfo->uMagic == SUPLDRWRAPPEDMODULE_MAGIC, + ("uMagic=%#x, expected %#x\n", pWrappedModInfo->uMagic, SUPLDRWRAPPEDMODULE_MAGIC), + VERR_INVALID_MAGIC); + AssertMsgReturn(pWrappedModInfo->uVersion == SUPLDRWRAPPEDMODULE_VERSION, + ("Unsupported uVersion=%#x, current version %#x\n", pWrappedModInfo->uVersion, SUPLDRWRAPPEDMODULE_VERSION), + VERR_VERSION_MISMATCH); + AssertMsgReturn(pWrappedModInfo->uEndMagic == SUPLDRWRAPPEDMODULE_MAGIC, + ("uEndMagic=%#x, expected %#x\n", pWrappedModInfo->uEndMagic, SUPLDRWRAPPEDMODULE_MAGIC), + VERR_INVALID_MAGIC); + AssertMsgReturn(pWrappedModInfo->fFlags <= SUPLDRWRAPPEDMODULE_F_VMMR0, ("Unknown flags in: %#x\n", pWrappedModInfo->fFlags), + VERR_INVALID_FLAGS); + + /* szName: */ + AssertReturn(RTStrEnd(pWrappedModInfo->szName, sizeof(pWrappedModInfo->szName)) != NULL, VERR_INVALID_NAME); + AssertReturn(supdrvIsLdrModuleNameValid(pWrappedModInfo->szName), VERR_INVALID_NAME); + AssertCompile(sizeof(pImage->szName) == sizeof(pWrappedModInfo->szName)); + cchName = strlen(pWrappedModInfo->szName); + + /* Image range: */ + AssertPtrReturn(pWrappedModInfo->pvImageStart, VERR_INVALID_POINTER); + AssertPtrReturn(pWrappedModInfo->pvImageEnd, VERR_INVALID_POINTER); + AssertReturn((uintptr_t)pWrappedModInfo->pvImageEnd > (uintptr_t)pWrappedModInfo->pvImageStart, VERR_INVALID_PARAMETER); + + /* Symbol table: */ + AssertMsgReturn(pWrappedModInfo->cSymbols <= _8K, ("Too many symbols: %u, max 8192\n", pWrappedModInfo->cSymbols), + VERR_TOO_MANY_SYMLINKS); + pszPrevSymbol = "\x7f"; + paSymbols = pWrappedModInfo->paSymbols; + idx = pWrappedModInfo->cSymbols; + while (idx-- > 0) + { + const char *pszSymbol = paSymbols[idx].pszSymbol; + AssertMsgReturn(RT_VALID_PTR(pszSymbol) && RT_VALID_PTR(paSymbols[idx].pfnValue), + ("paSymbols[%u]: %p/%p\n", idx, pszSymbol, paSymbols[idx].pfnValue), + VERR_INVALID_POINTER); + AssertReturn(*pszSymbol != '\0', VERR_EMPTY_STRING); + AssertMsgReturn(strcmp(pszSymbol, pszPrevSymbol) < 0, + ("symbol table out of order at index %u: '%s' vs '%s'\n", idx, pszSymbol, pszPrevSymbol), + VERR_WRONG_ORDER); + pszPrevSymbol = pszSymbol; + } + + /* Standard entry points: */ + AssertPtrNullReturn(pWrappedModInfo->pfnModuleInit, VERR_INVALID_POINTER); + AssertPtrNullReturn(pWrappedModInfo->pfnModuleTerm, VERR_INVALID_POINTER); + AssertReturn((uintptr_t)pWrappedModInfo->pfnModuleInit != (uintptr_t)pWrappedModInfo->pfnModuleTerm || pWrappedModInfo->pfnModuleInit == NULL, + VERR_INVALID_PARAMETER); + if (pWrappedModInfo->fFlags & SUPLDRWRAPPEDMODULE_F_VMMR0) + { + AssertReturn(pWrappedModInfo->pfnServiceReqHandler == NULL, VERR_INVALID_PARAMETER); + AssertPtrReturn(pWrappedModInfo->pfnVMMR0EntryFast, VERR_INVALID_POINTER); + AssertPtrReturn(pWrappedModInfo->pfnVMMR0EntryEx, VERR_INVALID_POINTER); + AssertReturn(pWrappedModInfo->pfnVMMR0EntryFast != pWrappedModInfo->pfnVMMR0EntryEx, VERR_INVALID_PARAMETER); + } + else + { + AssertPtrNullReturn(pWrappedModInfo->pfnServiceReqHandler, VERR_INVALID_POINTER); + AssertReturn(pWrappedModInfo->pfnVMMR0EntryFast == NULL, VERR_INVALID_PARAMETER); + AssertReturn(pWrappedModInfo->pfnVMMR0EntryEx == NULL, VERR_INVALID_PARAMETER); + } + + /* + * Check if we got an instance of the image already. + */ + supdrvLdrLock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + for (pImage = pDevExt->pLdrImages; pImage; pImage = pImage->pNext) + { + if ( pImage->szName[cchName] == '\0' + && !memcmp(pImage->szName, pWrappedModInfo->szName, cchName)) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvLdrRegisterWrappedModule: '%s' already loaded!\n", pWrappedModInfo->szName)); + return VERR_ALREADY_LOADED; + } + } + /* (not found - add it!) */ + + /* If the loader interface is locked down, make userland fail early */ + if (pDevExt->fLdrLockedDown) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvLdrRegisterWrappedModule: Not adding '%s' to image list, loader interface is locked down!\n", pWrappedModInfo->szName)); + return VERR_PERMISSION_DENIED; + } + + /* Only one VMMR0: */ + if ( pDevExt->pvVMMR0 != NULL + && (pWrappedModInfo->fFlags & SUPLDRWRAPPEDMODULE_F_VMMR0)) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvLdrRegisterWrappedModule: Rejecting '%s' as we already got a VMMR0 module!\n", pWrappedModInfo->szName)); + return VERR_ALREADY_EXISTS; + } + + /* + * Allocate memory. + */ + Assert(cchName < sizeof(pImage->szName)); + pImage = (PSUPDRVLDRIMAGE)RTMemAllocZ(sizeof(SUPDRVLDRIMAGE)); + if (!pImage) + { + supdrvLdrUnlock(pDevExt); + Log(("supdrvLdrRegisterWrappedModule: RTMemAllocZ() failed\n")); + return VERR_NO_MEMORY; + } + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + + /* + * Setup and link in the LDR stuff. + */ + pImage->pvImage = (void *)pWrappedModInfo->pvImageStart; + pImage->hMemObjImage = NIL_RTR0MEMOBJ; + pImage->cbImageWithEverything + = pImage->cbImageBits = (uintptr_t)pWrappedModInfo->pvImageEnd - (uintptr_t)pWrappedModInfo->pvImageStart; + pImage->cSymbols = 0; + pImage->paSymbols = NULL; + pImage->pachStrTab = NULL; + pImage->cbStrTab = 0; + pImage->cSegments = 0; + pImage->paSegments = NULL; + pImage->pfnModuleInit = pWrappedModInfo->pfnModuleInit; + pImage->pfnModuleTerm = pWrappedModInfo->pfnModuleTerm; + pImage->pfnServiceReqHandler = NULL; /* Only setting this after module init */ + pImage->uState = SUP_IOCTL_LDR_LOAD; + pImage->cImgUsage = 1; /* Held by the wrapper module till unload. */ + pImage->pDevExt = pDevExt; + pImage->pImageImport = NULL; + pImage->uMagic = SUPDRVLDRIMAGE_MAGIC; + pImage->pWrappedModInfo = pWrappedModInfo; + pImage->pvWrappedNative = pvNative; + pImage->fNative = true; + memcpy(pImage->szName, pWrappedModInfo->szName, cchName + 1); + + /* + * Link it. + */ + pImage->pNext = pDevExt->pLdrImages; + pDevExt->pLdrImages = pImage; + + /* + * Call module init function if found. + */ + rc = VINF_SUCCESS; + if (pImage->pfnModuleInit) + { + Log(("supdrvIOCtl_LdrLoad: calling pfnModuleInit=%p\n", pImage->pfnModuleInit)); + pDevExt->pLdrInitImage = pImage; + pDevExt->hLdrInitThread = RTThreadNativeSelf(); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + rc = pImage->pfnModuleInit(pImage); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + pDevExt->pLdrInitImage = NULL; + pDevExt->hLdrInitThread = NIL_RTNATIVETHREAD; + } + if (RT_SUCCESS(rc)) + { + /* + * Update entry points. + */ + if (pWrappedModInfo->fFlags & SUPLDRWRAPPEDMODULE_F_VMMR0) + { + Assert(!pDevExt->pvVMMR0); + Assert(!pDevExt->pfnVMMR0EntryFast); + Assert(!pDevExt->pfnVMMR0EntryEx); + ASMAtomicWritePtrVoid(&pDevExt->pvVMMR0, pImage->pvImage); + ASMAtomicWritePtrVoid((void * volatile *)(uintptr_t)&pDevExt->pfnVMMR0EntryFast, + (void *)(uintptr_t) pWrappedModInfo->pfnVMMR0EntryFast); + ASMAtomicWritePtrVoid((void * volatile *)(uintptr_t)&pDevExt->pfnVMMR0EntryEx, + (void *)(uintptr_t) pWrappedModInfo->pfnVMMR0EntryEx); + } + else + pImage->pfnServiceReqHandler = pWrappedModInfo->pfnServiceReqHandler; +#ifdef IN_RING3 +# error "WTF?" +#endif + *phMod = pImage; + } + else + { + /* + * Module init failed - bail, no module term callout. + */ + SUPR0Printf("ModuleInit failed for '%s': %Rrc\n", pImage->szName, rc); + + pImage->pfnModuleTerm = NULL; + pImage->uState = SUP_IOCTL_LDR_OPEN; + supdrvLdrFree(pDevExt, pImage); + } + + supdrvLdrUnlock(pDevExt); + SUPDRV_CHECK_SMAP_CHECK(pDevExt, RT_NOTHING); + return VINF_SUCCESS; +} + + +/** + * Decrements SUPDRVLDRIMAGE::cImgUsage when two or greater. + * + * @param pDevExt Device globals. + * @param pImage The image. + * @param cReference Number of references being removed. + */ +DECLINLINE(void) supdrvLdrSubtractUsage(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, uint32_t cReference) +{ + Assert(cReference > 0); + Assert(pImage->cImgUsage > cReference); + pImage->cImgUsage -= cReference; + if (pImage->cImgUsage == 1 && pImage->pWrappedModInfo) + supdrvOSLdrReleaseWrapperModule(pDevExt, pImage); +} + + +/** + * Frees a previously loaded (prep'ed) image. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + * @param pSession Session data. + * @param pReq The request. + */ +static int supdrvIOCtl_LdrFree(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRFREE pReq) +{ + int rc; + PSUPDRVLDRUSAGE pUsagePrev; + PSUPDRVLDRUSAGE pUsage; + PSUPDRVLDRIMAGE pImage; + LogFlow(("supdrvIOCtl_LdrFree: pvImageBase=%p\n", pReq->u.In.pvImageBase)); + + /* + * Find the ldr image. + */ + supdrvLdrLock(pDevExt); + pUsagePrev = NULL; + pUsage = pSession->pLdrUsage; + while (pUsage && pUsage->pImage->pvImage != pReq->u.In.pvImageBase) + { + pUsagePrev = pUsage; + pUsage = pUsage->pNext; + } + if (!pUsage) + { + supdrvLdrUnlock(pDevExt); + Log(("SUP_IOCTL_LDR_FREE: couldn't find image!\n")); + return VERR_INVALID_HANDLE; + } + if (pUsage->cRing3Usage == 0) + { + supdrvLdrUnlock(pDevExt); + Log(("SUP_IOCTL_LDR_FREE: No ring-3 reference to the image!\n")); + return VERR_CALLER_NO_REFERENCE; + } + + /* + * Check if we can remove anything. + */ + rc = VINF_SUCCESS; + pImage = pUsage->pImage; + Log(("SUP_IOCTL_LDR_FREE: pImage=%p %s cImgUsage=%d r3=%d r0=%u\n", + pImage, pImage->szName, pImage->cImgUsage, pUsage->cRing3Usage, pUsage->cRing0Usage)); + if (pImage->cImgUsage <= 1 || pUsage->cRing3Usage + pUsage->cRing0Usage <= 1) + { + /* + * Check if there are any objects with destructors in the image, if + * so leave it for the session cleanup routine so we get a chance to + * clean things up in the right order and not leave them all dangling. + */ + RTSpinlockAcquire(pDevExt->Spinlock); + if (pImage->cImgUsage <= 1) + { + PSUPDRVOBJ pObj; + for (pObj = pDevExt->pObjs; pObj; pObj = pObj->pNext) + if (RT_UNLIKELY((uintptr_t)pObj->pfnDestructor - (uintptr_t)pImage->pvImage < pImage->cbImageBits)) + { + rc = VERR_DANGLING_OBJECTS; + break; + } + } + else + { + PSUPDRVUSAGE pGenUsage; + for (pGenUsage = pSession->pUsage; pGenUsage; pGenUsage = pGenUsage->pNext) + if (RT_UNLIKELY((uintptr_t)pGenUsage->pObj->pfnDestructor - (uintptr_t)pImage->pvImage < pImage->cbImageBits)) + { + rc = VERR_DANGLING_OBJECTS; + break; + } + } + RTSpinlockRelease(pDevExt->Spinlock); + if (rc == VINF_SUCCESS) + { + /* unlink it */ + if (pUsagePrev) + pUsagePrev->pNext = pUsage->pNext; + else + pSession->pLdrUsage = pUsage->pNext; + + /* free it */ + pUsage->pImage = NULL; + pUsage->pNext = NULL; + RTMemFree(pUsage); + + /* + * Dereference the image. + */ + if (pImage->cImgUsage <= 1) + supdrvLdrFree(pDevExt, pImage); + else + supdrvLdrSubtractUsage(pDevExt, pImage, 1); + } + else + Log(("supdrvIOCtl_LdrFree: Dangling objects in %p/%s!\n", pImage->pvImage, pImage->szName)); + } + else + { + /* + * Dereference both image and usage. + */ + pUsage->cRing3Usage--; + supdrvLdrSubtractUsage(pDevExt, pImage, 1); + } + + supdrvLdrUnlock(pDevExt); + return rc; +} + + +/** + * Deregisters a wrapped .r0 module. + * + * @param pDevExt Device globals. + * @param pWrappedModInfo The wrapped module info. + * @param phMod Where to store the module is stored (NIL'ed on + * success). + */ +int VBOXCALL supdrvLdrDeregisterWrappedModule(PSUPDRVDEVEXT pDevExt, PCSUPLDRWRAPPEDMODULE pWrappedModInfo, void **phMod) +{ + PSUPDRVLDRIMAGE pImage; + uint32_t cSleeps; + + /* + * Validate input. + */ + AssertPtrReturn(pWrappedModInfo, VERR_INVALID_POINTER); + AssertMsgReturn(pWrappedModInfo->uMagic == SUPLDRWRAPPEDMODULE_MAGIC, + ("uMagic=%#x, expected %#x\n", pWrappedModInfo->uMagic, SUPLDRWRAPPEDMODULE_MAGIC), + VERR_INVALID_MAGIC); + AssertMsgReturn(pWrappedModInfo->uEndMagic == SUPLDRWRAPPEDMODULE_MAGIC, + ("uEndMagic=%#x, expected %#x\n", pWrappedModInfo->uEndMagic, SUPLDRWRAPPEDMODULE_MAGIC), + VERR_INVALID_MAGIC); + + AssertPtrReturn(phMod, VERR_INVALID_POINTER); + pImage = *(PSUPDRVLDRIMAGE *)phMod; + if (!pImage) + return VINF_SUCCESS; + AssertPtrReturn(pImage, VERR_INVALID_POINTER); + AssertMsgReturn(pImage->uMagic == SUPDRVLDRIMAGE_MAGIC, ("pImage=%p uMagic=%#x\n", pImage, pImage->uMagic), + VERR_INVALID_MAGIC); + AssertMsgReturn(pImage->pvImage == pWrappedModInfo->pvImageStart, + ("pWrappedModInfo(%p)->pvImageStart=%p vs. pImage(=%p)->pvImage=%p\n", + pWrappedModInfo, pWrappedModInfo->pvImageStart, pImage, pImage->pvImage), + VERR_MISMATCH); + + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + + /* + * Try free it, but first we have to wait for its usage count to reach 1 (our). + */ + supdrvLdrLock(pDevExt); + for (cSleeps = 0; ; cSleeps++) + { + PSUPDRVLDRIMAGE pCur; + + /* Check that the image is in the list. */ + for (pCur = pDevExt->pLdrImages; pCur; pCur = pCur->pNext) + if (pCur == pImage) + break; + AssertBreak(pCur == pImage); + + /* Anyone still using it? */ + if (pImage->cImgUsage <= 1) + break; + + /* Someone is using it, wait and check again. */ + if (!(cSleeps % 60)) + SUPR0Printf("supdrvLdrUnregisterWrappedModule: Still %u users of wrapped image '%s' ...\n", + pImage->cImgUsage, pImage->szName); + supdrvLdrUnlock(pDevExt); + RTThreadSleep(1000); + supdrvLdrLock(pDevExt); + } + + /* We're the last 'user', free it. */ + supdrvLdrFree(pDevExt, pImage); + + supdrvLdrUnlock(pDevExt); + + *phMod = NULL; + return VINF_SUCCESS; +} + + +/** + * Lock down the image loader interface. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + */ +static int supdrvIOCtl_LdrLockDown(PSUPDRVDEVEXT pDevExt) +{ + LogFlow(("supdrvIOCtl_LdrLockDown:\n")); + + supdrvLdrLock(pDevExt); + if (!pDevExt->fLdrLockedDown) + { + pDevExt->fLdrLockedDown = true; + Log(("supdrvIOCtl_LdrLockDown: Image loader interface locked down\n")); + } + supdrvLdrUnlock(pDevExt); + + return VINF_SUCCESS; +} + + +/** + * Worker for getting the address of a symbol in an image. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + * @param pImage The image to search. + * @param pszSymbol The symbol name. + * @param cchSymbol The length of the symbol name. + * @param ppvValue Where to return the symbol + * @note Caller owns the loader lock. + */ +static int supdrvLdrQuerySymbolWorker(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvValue) +{ + int rc = VERR_SYMBOL_NOT_FOUND; + if (pImage->fNative && !pImage->pWrappedModInfo) + rc = supdrvOSLdrQuerySymbol(pDevExt, pImage, pszSymbol, cchSymbol, ppvValue); + else if (pImage->fNative && pImage->pWrappedModInfo) + { + PCSUPLDRWRAPMODSYMBOL paSymbols = pImage->pWrappedModInfo->paSymbols; + uint32_t iEnd = pImage->pWrappedModInfo->cSymbols; + uint32_t iStart = 0; + while (iStart < iEnd) + { + uint32_t const i = iStart + (iEnd - iStart) / 2; + int const iDiff = strcmp(paSymbols[i].pszSymbol, pszSymbol); + if (iDiff < 0) + iStart = i + 1; + else if (iDiff > 0) + iEnd = i; + else + { + *ppvValue = (void *)(uintptr_t)paSymbols[i].pfnValue; + rc = VINF_SUCCESS; + break; + } + } +#ifdef VBOX_STRICT + if (rc != VINF_SUCCESS) + for (iStart = 0, iEnd = pImage->pWrappedModInfo->cSymbols; iStart < iEnd; iStart++) + Assert(strcmp(paSymbols[iStart].pszSymbol, pszSymbol)); +#endif + } + else + { + const char *pchStrings = pImage->pachStrTab; + PSUPLDRSYM paSyms = pImage->paSymbols; + uint32_t i; + Assert(!pImage->pWrappedModInfo); + for (i = 0; i < pImage->cSymbols; i++) + { + if ( paSyms[i].offName + cchSymbol + 1 <= pImage->cbStrTab + && !memcmp(pchStrings + paSyms[i].offName, pszSymbol, cchSymbol + 1)) + { + /* + * Note! The int32_t is for native loading on solaris where the data + * and text segments are in very different places. + */ + *ppvValue = (uint8_t *)pImage->pvImage + (int32_t)paSyms[i].offSymbol; + rc = VINF_SUCCESS; + break; + } + } + } + return rc; +} + + +/** + * Queries the address of a symbol in an open image. + * + * @returns IPRT status code. + * @param pDevExt Device globals. + * @param pSession Session data. + * @param pReq The request buffer. + */ +static int supdrvIOCtl_LdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPLDRGETSYMBOL pReq) +{ + PSUPDRVLDRIMAGE pImage; + PSUPDRVLDRUSAGE pUsage; + const size_t cchSymbol = strlen(pReq->u.In.szSymbol); + void *pvSymbol = NULL; + int rc; + Log3(("supdrvIOCtl_LdrQuerySymbol: pvImageBase=%p szSymbol=\"%s\"\n", pReq->u.In.pvImageBase, pReq->u.In.szSymbol)); + + /* + * Find the ldr image. + */ + supdrvLdrLock(pDevExt); + + pUsage = pSession->pLdrUsage; + while (pUsage && pUsage->pImage->pvImage != pReq->u.In.pvImageBase) + pUsage = pUsage->pNext; + if (pUsage) + { + pImage = pUsage->pImage; + if (pImage->uState == SUP_IOCTL_LDR_LOAD) + { + /* + * Search the image exports / symbol strings. + */ + rc = supdrvLdrQuerySymbolWorker(pDevExt, pImage, pReq->u.In.szSymbol, cchSymbol, &pvSymbol); + } + else + { + Log(("SUP_IOCTL_LDR_GET_SYMBOL: invalid image state %d (%#x)!\n", pImage->uState, pImage->uState)); + rc = VERR_WRONG_ORDER; + } + } + else + { + Log(("SUP_IOCTL_LDR_GET_SYMBOL: couldn't find image!\n")); + rc = VERR_INVALID_HANDLE; + } + + supdrvLdrUnlock(pDevExt); + + pReq->u.Out.pvSymbol = pvSymbol; + return rc; +} + + +/** + * Gets the address of a symbol in an open image or the support driver. + * + * @returns VBox status code. + * @param pDevExt Device globals. + * @param pSession Session data. + * @param pReq The request buffer. + */ +static int supdrvIDC_LdrGetSymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVIDCREQGETSYM pReq) +{ + const char *pszSymbol = pReq->u.In.pszSymbol; + const char *pszModule = pReq->u.In.pszModule; + size_t cchSymbol; + char const *pszEnd; + uint32_t i; + int rc; + + /* + * Input validation. + */ + AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER); + pszEnd = RTStrEnd(pszSymbol, 512); + AssertReturn(pszEnd, VERR_INVALID_PARAMETER); + cchSymbol = pszEnd - pszSymbol; + + if (pszModule) + { + AssertPtrReturn(pszModule, VERR_INVALID_POINTER); + pszEnd = RTStrEnd(pszModule, 64); + AssertReturn(pszEnd, VERR_INVALID_PARAMETER); + } + Log3(("supdrvIDC_LdrGetSymbol: pszModule=%p:{%s} pszSymbol=%p:{%s}\n", pszModule, pszModule, pszSymbol, pszSymbol)); + + if ( !pszModule + || !strcmp(pszModule, "SupDrv")) + { + /* + * Search the support driver export table. + */ + rc = VERR_SYMBOL_NOT_FOUND; + for (i = 0; i < RT_ELEMENTS(g_aFunctions); i++) + if (!strcmp(g_aFunctions[i].szName, pszSymbol)) + { + pReq->u.Out.pfnSymbol = (PFNRT)(uintptr_t)g_aFunctions[i].pfn; + rc = VINF_SUCCESS; + break; + } + } + else + { + /* + * Find the loader image. + */ + PSUPDRVLDRIMAGE pImage; + + supdrvLdrLock(pDevExt); + + for (pImage = pDevExt->pLdrImages; pImage; pImage = pImage->pNext) + if (!strcmp(pImage->szName, pszModule)) + break; + if (pImage && pImage->uState == SUP_IOCTL_LDR_LOAD) + { + /* + * Search the image exports / symbol strings. Do usage counting on the session. + */ + rc = supdrvLdrQuerySymbolWorker(pDevExt, pImage, pszSymbol, cchSymbol, (void **)&pReq->u.Out.pfnSymbol); + if (RT_SUCCESS(rc)) + rc = supdrvLdrAddUsage(pDevExt, pSession, pImage, true /*fRing3Usage*/); + } + else + rc = pImage ? VERR_WRONG_ORDER : VERR_MODULE_NOT_FOUND; + + supdrvLdrUnlock(pDevExt); + } + return rc; +} + + +/** + * Looks up a symbol in g_aFunctions + * + * @returns VINF_SUCCESS on success, VERR_SYMBOL_NOT_FOUND on failure. + * @param pszSymbol The symbol to look up. + * @param puValue Where to return the value. + */ +int VBOXCALL supdrvLdrGetExportedSymbol(const char *pszSymbol, uintptr_t *puValue) +{ + uint32_t i; + for (i = 0; i < RT_ELEMENTS(g_aFunctions); i++) + if (!strcmp(g_aFunctions[i].szName, pszSymbol)) + { + *puValue = (uintptr_t)g_aFunctions[i].pfn; + return VINF_SUCCESS; + } + + if (!strcmp(pszSymbol, "g_SUPGlobalInfoPage")) + { + *puValue = (uintptr_t)g_pSUPGlobalInfoPage; + return VINF_SUCCESS; + } + + return VERR_SYMBOL_NOT_FOUND; +} + + +/** + * Adds a usage reference in the specified session of an image. + * + * Called while owning the loader semaphore. + * + * @returns VINF_SUCCESS on success and VERR_NO_MEMORY on failure. + * @param pDevExt Pointer to device extension. + * @param pSession Session in question. + * @param pImage Image which the session is using. + * @param fRing3Usage Set if it's ring-3 usage, clear if ring-0. + */ +static int supdrvLdrAddUsage(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVLDRIMAGE pImage, bool fRing3Usage) +{ + PSUPDRVLDRUSAGE pUsage; + LogFlow(("supdrvLdrAddUsage: pImage=%p %d\n", pImage, fRing3Usage)); + + /* + * Referenced it already? + */ + pUsage = pSession->pLdrUsage; + while (pUsage) + { + if (pUsage->pImage == pImage) + { + if (fRing3Usage) + pUsage->cRing3Usage++; + else + pUsage->cRing0Usage++; + Assert(pImage->cImgUsage > 1 || !pImage->pWrappedModInfo); + pImage->cImgUsage++; + return VINF_SUCCESS; + } + pUsage = pUsage->pNext; + } + + /* + * Allocate new usage record. + */ + pUsage = (PSUPDRVLDRUSAGE)RTMemAlloc(sizeof(*pUsage)); + AssertReturn(pUsage, VERR_NO_MEMORY); + pUsage->cRing3Usage = fRing3Usage ? 1 : 0; + pUsage->cRing0Usage = fRing3Usage ? 0 : 1; + pUsage->pImage = pImage; + pUsage->pNext = pSession->pLdrUsage; + pSession->pLdrUsage = pUsage; + + /* + * Wrapped modules needs to retain a native module reference. + */ + pImage->cImgUsage++; + if (pImage->cImgUsage == 2 && pImage->pWrappedModInfo) + supdrvOSLdrRetainWrapperModule(pDevExt, pImage); + + return VINF_SUCCESS; +} + + +/** + * Frees a load image. + * + * @param pDevExt Pointer to device extension. + * @param pImage Pointer to the image we're gonna free. + * This image must exit! + * @remark The caller MUST own SUPDRVDEVEXT::mtxLdr! + */ +static void supdrvLdrFree(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + unsigned cLoops; + for (cLoops = 0; ; cLoops++) + { + PSUPDRVLDRIMAGE pImagePrev; + PSUPDRVLDRIMAGE pImageImport; + LogFlow(("supdrvLdrFree: pImage=%p %s [loop %u]\n", pImage, pImage->szName, cLoops)); + AssertBreak(cLoops < 2); + + /* + * Warn if we're releasing images while the image loader interface is + * locked down -- we won't be able to reload them! + */ + if (pDevExt->fLdrLockedDown) + Log(("supdrvLdrFree: Warning: unloading '%s' image, while loader interface is locked down!\n", pImage->szName)); + + /* find it - arg. should've used doubly linked list. */ + Assert(pDevExt->pLdrImages); + pImagePrev = NULL; + if (pDevExt->pLdrImages != pImage) + { + pImagePrev = pDevExt->pLdrImages; + while (pImagePrev->pNext != pImage) + pImagePrev = pImagePrev->pNext; + Assert(pImagePrev->pNext == pImage); + } + + /* unlink */ + if (pImagePrev) + pImagePrev->pNext = pImage->pNext; + else + pDevExt->pLdrImages = pImage->pNext; + + /* check if this is VMMR0.r0 unset its entry point pointers. */ + if (pDevExt->pvVMMR0 == pImage->pvImage) + { + pDevExt->pvVMMR0 = NULL; + pDevExt->pfnVMMR0EntryFast = NULL; + pDevExt->pfnVMMR0EntryEx = NULL; + } + + /* check for objects with destructors in this image. (Shouldn't happen.) */ + if (pDevExt->pObjs) + { + unsigned cObjs = 0; + PSUPDRVOBJ pObj; + RTSpinlockAcquire(pDevExt->Spinlock); + for (pObj = pDevExt->pObjs; pObj; pObj = pObj->pNext) + if (RT_UNLIKELY((uintptr_t)pObj->pfnDestructor - (uintptr_t)pImage->pvImage < pImage->cbImageBits)) + { + pObj->pfnDestructor = NULL; + cObjs++; + } + RTSpinlockRelease(pDevExt->Spinlock); + if (cObjs) + OSDBGPRINT(("supdrvLdrFree: Image '%s' has %d dangling objects!\n", pImage->szName, cObjs)); + } + + /* call termination function if fully loaded. */ + if ( pImage->pfnModuleTerm + && pImage->uState == SUP_IOCTL_LDR_LOAD) + { + LogFlow(("supdrvIOCtl_LdrLoad: calling pfnModuleTerm=%p\n", pImage->pfnModuleTerm)); + pDevExt->hLdrTermThread = RTThreadNativeSelf(); + pImage->pfnModuleTerm(pImage); + pDevExt->hLdrTermThread = NIL_RTNATIVETHREAD; + } + + /* Inform the tracing component. */ + supdrvTracerModuleUnloading(pDevExt, pImage); + + /* Do native unload if appropriate, then inform the native code about the + unloading (mainly for non-native loading case). */ + if (pImage->fNative) + supdrvOSLdrUnload(pDevExt, pImage); + supdrvOSLdrNotifyUnloaded(pDevExt, pImage); + + /* free the image */ + pImage->uMagic = SUPDRVLDRIMAGE_MAGIC_DEAD; + pImage->cImgUsage = 0; + pImage->pDevExt = NULL; + pImage->pNext = NULL; + pImage->uState = SUP_IOCTL_LDR_FREE; + RTR0MemObjFree(pImage->hMemObjImage, true /*fMappings*/); + pImage->hMemObjImage = NIL_RTR0MEMOBJ; + pImage->pvImage = NULL; + RTMemFree(pImage->pachStrTab); + pImage->pachStrTab = NULL; + RTMemFree(pImage->paSymbols); + pImage->paSymbols = NULL; + RTMemFree(pImage->paSegments); + pImage->paSegments = NULL; + + pImageImport = pImage->pImageImport; + pImage->pImageImport = NULL; + + RTMemFree(pImage); + + /* + * Deal with any import image. + */ + if (!pImageImport) + break; + if (pImageImport->cImgUsage > 1) + { + supdrvLdrSubtractUsage(pDevExt, pImageImport, 1); + break; + } + pImage = pImageImport; + } +} + + +/** + * Acquires the loader lock. + * + * @returns IPRT status code. + * @param pDevExt The device extension. + * @note Not recursive on all platforms yet. + */ +DECLINLINE(int) supdrvLdrLock(PSUPDRVDEVEXT pDevExt) +{ +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + int rc = RTSemMutexRequest(pDevExt->mtxLdr, RT_INDEFINITE_WAIT); +#else + int rc = RTSemFastMutexRequest(pDevExt->mtxLdr); +#endif + AssertRC(rc); + return rc; +} + + +/** + * Releases the loader lock. + * + * @returns IPRT status code. + * @param pDevExt The device extension. + */ +DECLINLINE(int) supdrvLdrUnlock(PSUPDRVDEVEXT pDevExt) +{ +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + return RTSemMutexRelease(pDevExt->mtxLdr); +#else + return RTSemFastMutexRelease(pDevExt->mtxLdr); +#endif +} + + +/** + * Acquires the global loader lock. + * + * This can be useful when accessing structures being modified by the ModuleInit + * and ModuleTerm. Use SUPR0LdrUnlock() to unlock. + * + * @returns VBox status code. + * @param pSession The session doing the locking. + * + * @note Cannot be used during ModuleInit or ModuleTerm callbacks. + */ +SUPR0DECL(int) SUPR0LdrLock(PSUPDRVSESSION pSession) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvLdrLock(pSession->pDevExt); +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrLock); + + +/** + * Releases the global loader lock. + * + * Must correspond to a SUPR0LdrLock call! + * + * @returns VBox status code. + * @param pSession The session doing the locking. + * + * @note Cannot be used during ModuleInit or ModuleTerm callbacks. + */ +SUPR0DECL(int) SUPR0LdrUnlock(PSUPDRVSESSION pSession) +{ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + return supdrvLdrUnlock(pSession->pDevExt); +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrUnlock); + + +/** + * For checking lock ownership in Assert() statements during ModuleInit and + * ModuleTerm. + * + * @returns Whether we own the loader lock or not. + * @param hMod The module in question. + * @param fWantToHear For hosts where it is difficult to know who owns the + * lock, this will be returned instead. + */ +SUPR0DECL(bool) SUPR0LdrIsLockOwnerByMod(void *hMod, bool fWantToHear) +{ + PSUPDRVDEVEXT pDevExt; + RTNATIVETHREAD hOwner; + + PSUPDRVLDRIMAGE pImage = (PSUPDRVLDRIMAGE)hMod; + AssertPtrReturn(pImage, fWantToHear); + AssertReturn(pImage->uMagic == SUPDRVLDRIMAGE_MAGIC, fWantToHear); + + pDevExt = pImage->pDevExt; + AssertPtrReturn(pDevExt, fWantToHear); + + /* + * Expecting this to be called at init/term time only, so this will be sufficient. + */ + hOwner = pDevExt->hLdrInitThread; + if (hOwner == NIL_RTNATIVETHREAD) + hOwner = pDevExt->hLdrTermThread; + if (hOwner != NIL_RTNATIVETHREAD) + return hOwner == RTThreadNativeSelf(); + + /* + * Neither of the two semaphore variants currently offers very good + * introspection, so we wing it for now. This API is VBOX_STRICT only. + */ +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + return RTSemMutexIsOwned(pDevExt->mtxLdr) && fWantToHear; +#else + return fWantToHear; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrIsLockOwnerByMod); + + +/** + * Locates and retains the given module for ring-0 usage. + * + * @returns VBox status code. + * @param pSession The session to associate the module reference with. + * @param pszName The module name (no path). + * @param phMod Where to return the module handle. The module is + * referenced and a call to SUPR0LdrModRelease() is + * necessary when done with it. + */ +SUPR0DECL(int) SUPR0LdrModByName(PSUPDRVSESSION pSession, const char *pszName, void **phMod) +{ + int rc; + size_t cchName; + PSUPDRVDEVEXT pDevExt; + + /* + * Validate input. + */ + AssertPtrReturn(phMod, VERR_INVALID_POINTER); + *phMod = NULL; + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + cchName = strlen(pszName); + AssertReturn(cchName > 0, VERR_EMPTY_STRING); + AssertReturn(cchName < RT_SIZEOFMEMB(SUPDRVLDRIMAGE, szName), VERR_MODULE_NOT_FOUND); + + /* + * Do the lookup. + */ + pDevExt = pSession->pDevExt; + rc = supdrvLdrLock(pDevExt); + if (RT_SUCCESS(rc)) + { + PSUPDRVLDRIMAGE pImage; + for (pImage = pDevExt->pLdrImages; pImage; pImage = pImage->pNext) + { + if ( pImage->szName[cchName] == '\0' + && !memcmp(pImage->szName, pszName, cchName)) + { + /* + * Check the state and make sure we don't overflow the reference counter before return it. + */ + uint32_t uState = pImage->uState; + if (uState == SUP_IOCTL_LDR_LOAD) + { + if (RT_LIKELY(pImage->cImgUsage < UINT32_MAX / 2U)) + { + supdrvLdrAddUsage(pDevExt, pSession, pImage, false /*fRing3Usage*/); + *phMod = pImage; + supdrvLdrUnlock(pDevExt); + return VINF_SUCCESS; + } + supdrvLdrUnlock(pDevExt); + Log(("SUPR0LdrModByName: Too many existing references to '%s'!\n", pszName)); + return VERR_TOO_MANY_REFERENCES; + } + supdrvLdrUnlock(pDevExt); + Log(("SUPR0LdrModByName: Module '%s' is not in the loaded state (%d)!\n", pszName, uState)); + return VERR_INVALID_STATE; + } + } + supdrvLdrUnlock(pDevExt); + Log(("SUPR0LdrModByName: Module '%s' not found!\n", pszName)); + rc = VERR_MODULE_NOT_FOUND; + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrModByName); + + +/** + * Retains a ring-0 module reference. + * + * Release reference when done by calling SUPR0LdrModRelease(). + * + * @returns VBox status code. + * @param pSession The session to reference the module in. A usage + * record is added if needed. + * @param hMod The handle to the module to retain. + */ +SUPR0DECL(int) SUPR0LdrModRetain(PSUPDRVSESSION pSession, void *hMod) +{ + PSUPDRVDEVEXT pDevExt; + PSUPDRVLDRIMAGE pImage; + int rc; + + /* Validate input a little. */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(hMod, VERR_INVALID_HANDLE); + pImage = (PSUPDRVLDRIMAGE)hMod; + AssertReturn(pImage->uMagic == SUPDRVLDRIMAGE_MAGIC, VERR_INVALID_HANDLE); + + /* Reference the module: */ + pDevExt = pSession->pDevExt; + rc = supdrvLdrLock(pDevExt); + if (RT_SUCCESS(rc)) + { + if (pImage->uMagic == SUPDRVLDRIMAGE_MAGIC) + { + if (RT_LIKELY(pImage->cImgUsage < UINT32_MAX / 2U)) + rc = supdrvLdrAddUsage(pDevExt, pSession, pImage, false /*fRing3Usage*/); + else + AssertFailedStmt(rc = VERR_TOO_MANY_REFERENCES); + } + else + AssertFailedStmt(rc = VERR_INVALID_HANDLE); + supdrvLdrUnlock(pDevExt); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrModRetain); + + +/** + * Releases a ring-0 module reference retained by SUPR0LdrModByName() or + * SUPR0LdrModRetain(). + * + * @returns VBox status code. + * @param pSession The session that the module was retained in. + * @param hMod The module handle. NULL is silently ignored. + */ +SUPR0DECL(int) SUPR0LdrModRelease(PSUPDRVSESSION pSession, void *hMod) +{ + PSUPDRVDEVEXT pDevExt; + PSUPDRVLDRIMAGE pImage; + int rc; + + /* + * Validate input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (!hMod) + return VINF_SUCCESS; + AssertPtrReturn(hMod, VERR_INVALID_HANDLE); + pImage = (PSUPDRVLDRIMAGE)hMod; + AssertReturn(pImage->uMagic == SUPDRVLDRIMAGE_MAGIC, VERR_INVALID_HANDLE); + + /* + * Take the loader lock and revalidate the module: + */ + pDevExt = pSession->pDevExt; + rc = supdrvLdrLock(pDevExt); + if (RT_SUCCESS(rc)) + { + if (pImage->uMagic == SUPDRVLDRIMAGE_MAGIC) + { + /* + * Find the usage record for the module: + */ + PSUPDRVLDRUSAGE pPrevUsage = NULL; + PSUPDRVLDRUSAGE pUsage; + + rc = VERR_MODULE_NOT_FOUND; + for (pUsage = pSession->pLdrUsage; pUsage; pUsage = pUsage->pNext) + { + if (pUsage->pImage == pImage) + { + /* + * Drop a ring-0 reference: + */ + Assert(pImage->cImgUsage >= pUsage->cRing0Usage + pUsage->cRing3Usage); + if (pUsage->cRing0Usage > 0) + { + if (pImage->cImgUsage > 1) + { + pUsage->cRing0Usage -= 1; + supdrvLdrSubtractUsage(pDevExt, pImage, 1); + rc = VINF_SUCCESS; + } + else + { + Assert(!pImage->pWrappedModInfo /* (The wrapper kmod has the last reference.) */); + supdrvLdrFree(pDevExt, pImage); + + if (pPrevUsage) + pPrevUsage->pNext = pUsage->pNext; + else + pSession->pLdrUsage = pUsage->pNext; + pUsage->pNext = NULL; + pUsage->pImage = NULL; + pUsage->cRing0Usage = 0; + pUsage->cRing3Usage = 0; + RTMemFree(pUsage); + + rc = VINF_OBJECT_DESTROYED; + } + } + else + AssertFailedStmt(rc = VERR_CALLER_NO_REFERENCE); + break; + } + pPrevUsage = pUsage; + } + } + else + AssertFailedStmt(rc = VERR_INVALID_HANDLE); + supdrvLdrUnlock(pDevExt); + } + return rc; + +} +SUPR0_EXPORT_SYMBOL(SUPR0LdrModRelease); + + +/** + * Implements the service call request. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pSession The calling session. + * @param pReq The request packet, valid. + */ +static int supdrvIOCtl_CallServiceModule(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPCALLSERVICE pReq) +{ +#if !defined(RT_OS_WINDOWS) || defined(RT_ARCH_AMD64) || defined(DEBUG) + int rc; + + /* + * Find the module first in the module referenced by the calling session. + */ + rc = supdrvLdrLock(pDevExt); + if (RT_SUCCESS(rc)) + { + PFNSUPR0SERVICEREQHANDLER pfnServiceReqHandler = NULL; + PSUPDRVLDRUSAGE pUsage; + + for (pUsage = pSession->pLdrUsage; pUsage; pUsage = pUsage->pNext) + if ( pUsage->pImage->pfnServiceReqHandler + && !strcmp(pUsage->pImage->szName, pReq->u.In.szName)) + { + pfnServiceReqHandler = pUsage->pImage->pfnServiceReqHandler; + break; + } + supdrvLdrUnlock(pDevExt); + + if (pfnServiceReqHandler) + { + /* + * Call it. + */ + if (pReq->Hdr.cbIn == SUP_IOCTL_CALL_SERVICE_SIZE(0)) + rc = pfnServiceReqHandler(pSession, pReq->u.In.uOperation, pReq->u.In.u64Arg, NULL); + else + rc = pfnServiceReqHandler(pSession, pReq->u.In.uOperation, pReq->u.In.u64Arg, (PSUPR0SERVICEREQHDR)&pReq->abReqPkt[0]); + } + else + rc = VERR_SUPDRV_SERVICE_NOT_FOUND; + } + + /* log it */ + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED + && rc != VERR_TIMEOUT) + Log(("SUP_IOCTL_CALL_SERVICE: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + else + Log4(("SUP_IOCTL_CALL_SERVICE: rc=%Rrc op=%u out=%u arg=%RX64 p/t=%RTproc/%RTthrd\n", + rc, pReq->u.In.uOperation, pReq->Hdr.cbOut, pReq->u.In.u64Arg, RTProcSelf(), RTThreadNativeSelf())); + return rc; +#else /* RT_OS_WINDOWS && !RT_ARCH_AMD64 && !DEBUG */ + RT_NOREF3(pDevExt, pSession, pReq); + return VERR_NOT_IMPLEMENTED; +#endif /* RT_OS_WINDOWS && !RT_ARCH_AMD64 && !DEBUG */ +} + + +/** + * Implements the logger settings request. + * + * @returns VBox status code. + * @param pReq The request. + */ +static int supdrvIOCtl_LoggerSettings(PSUPLOGGERSETTINGS pReq) +{ + const char *pszGroup = &pReq->u.In.szStrings[pReq->u.In.offGroups]; + const char *pszFlags = &pReq->u.In.szStrings[pReq->u.In.offFlags]; + const char *pszDest = &pReq->u.In.szStrings[pReq->u.In.offDestination]; + PRTLOGGER pLogger = NULL; + int rc; + + /* + * Some further validation. + */ + switch (pReq->u.In.fWhat) + { + case SUPLOGGERSETTINGS_WHAT_SETTINGS: + case SUPLOGGERSETTINGS_WHAT_CREATE: + break; + + case SUPLOGGERSETTINGS_WHAT_DESTROY: + if (*pszGroup || *pszFlags || *pszDest) + return VERR_INVALID_PARAMETER; + if (pReq->u.In.fWhich == SUPLOGGERSETTINGS_WHICH_RELEASE) + return VERR_ACCESS_DENIED; + break; + + default: + return VERR_INTERNAL_ERROR; + } + + /* + * Get the logger. + */ + switch (pReq->u.In.fWhich) + { + case SUPLOGGERSETTINGS_WHICH_DEBUG: + pLogger = RTLogGetDefaultInstance(); + break; + + case SUPLOGGERSETTINGS_WHICH_RELEASE: + pLogger = RTLogRelGetDefaultInstance(); + break; + + default: + return VERR_INTERNAL_ERROR; + } + + /* + * Do the job. + */ + switch (pReq->u.In.fWhat) + { + case SUPLOGGERSETTINGS_WHAT_SETTINGS: + if (pLogger) + { + rc = RTLogFlags(pLogger, pszFlags); + if (RT_SUCCESS(rc)) + rc = RTLogGroupSettings(pLogger, pszGroup); + NOREF(pszDest); + } + else + rc = VERR_NOT_FOUND; + break; + + case SUPLOGGERSETTINGS_WHAT_CREATE: + { + if (pLogger) + rc = VERR_ALREADY_EXISTS; + else + { + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + + rc = RTLogCreate(&pLogger, + 0 /* fFlags */, + pszGroup, + pReq->u.In.fWhich == SUPLOGGERSETTINGS_WHICH_DEBUG + ? "VBOX_LOG" + : "VBOX_RELEASE_LOG", + RT_ELEMENTS(s_apszGroups), + s_apszGroups, + RTLOGDEST_STDOUT | RTLOGDEST_DEBUGGER, + NULL); + if (RT_SUCCESS(rc)) + { + rc = RTLogFlags(pLogger, pszFlags); + NOREF(pszDest); + if (RT_SUCCESS(rc)) + { + switch (pReq->u.In.fWhich) + { + case SUPLOGGERSETTINGS_WHICH_DEBUG: + pLogger = RTLogSetDefaultInstance(pLogger); + break; + case SUPLOGGERSETTINGS_WHICH_RELEASE: + pLogger = RTLogRelSetDefaultInstance(pLogger); + break; + } + } + RTLogDestroy(pLogger); + } + } + break; + } + + case SUPLOGGERSETTINGS_WHAT_DESTROY: + switch (pReq->u.In.fWhich) + { + case SUPLOGGERSETTINGS_WHICH_DEBUG: + pLogger = RTLogSetDefaultInstance(NULL); + break; + case SUPLOGGERSETTINGS_WHICH_RELEASE: + pLogger = RTLogRelSetDefaultInstance(NULL); + break; + } + rc = RTLogDestroy(pLogger); + break; + + default: + { + rc = VERR_INTERNAL_ERROR; + break; + } + } + + return rc; +} + + +/** + * Implements the MSR prober operations. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pReq The request. + */ +static int supdrvIOCtl_MsrProber(PSUPDRVDEVEXT pDevExt, PSUPMSRPROBER pReq) +{ +#ifdef SUPDRV_WITH_MSR_PROBER + RTCPUID const idCpu = pReq->u.In.idCpu == UINT32_MAX ? NIL_RTCPUID : pReq->u.In.idCpu; + int rc; + + switch (pReq->u.In.enmOp) + { + case SUPMSRPROBEROP_READ: + { + uint64_t uValue; + rc = supdrvOSMsrProberRead(pReq->u.In.uMsr, idCpu, &uValue); + if (RT_SUCCESS(rc)) + { + pReq->u.Out.uResults.Read.uValue = uValue; + pReq->u.Out.uResults.Read.fGp = false; + } + else if (rc == VERR_ACCESS_DENIED) + { + pReq->u.Out.uResults.Read.uValue = 0; + pReq->u.Out.uResults.Read.fGp = true; + rc = VINF_SUCCESS; + } + break; + } + + case SUPMSRPROBEROP_WRITE: + rc = supdrvOSMsrProberWrite(pReq->u.In.uMsr, idCpu, pReq->u.In.uArgs.Write.uToWrite); + if (RT_SUCCESS(rc)) + pReq->u.Out.uResults.Write.fGp = false; + else if (rc == VERR_ACCESS_DENIED) + { + pReq->u.Out.uResults.Write.fGp = true; + rc = VINF_SUCCESS; + } + break; + + case SUPMSRPROBEROP_MODIFY: + case SUPMSRPROBEROP_MODIFY_FASTER: + rc = supdrvOSMsrProberModify(idCpu, pReq); + break; + + default: + return VERR_INVALID_FUNCTION; + } + RT_NOREF1(pDevExt); + return rc; +#else + RT_NOREF2(pDevExt, pReq); + return VERR_NOT_IMPLEMENTED; +#endif +} + + +/** + * Resume built-in keyboard on MacBook Air and Pro hosts. + * If there is no built-in keyboard device, return success anyway. + * + * @returns 0 on Mac OS X platform, VERR_NOT_IMPLEMENTED on the other ones. + */ +static int supdrvIOCtl_ResumeSuspendedKbds(void) +{ +#if defined(RT_OS_DARWIN) + return supdrvDarwinResumeSuspendedKbds(); +#else + return VERR_NOT_IMPLEMENTED; +#endif +} + diff --git a/src/VBox/HostDrivers/Support/SUPDrv.d b/src/VBox/HostDrivers/Support/SUPDrv.d new file mode 100644 index 00000000..e2249a96 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrv.d @@ -0,0 +1,51 @@ +/* $Id: SUPDrv.d $ */ +/** @file + * SUPDrv - Static dtrace probes. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +provider vboxdrv +{ + probe session__create(struct SUPDRVSESSION *pSession, int fUser); + probe session__close(struct SUPDRVSESSION *pSession); + probe ioctl__entry(struct SUPDRVSESSION *pSession, uintptr_t uIOCtl, void *pvReqHdr); + probe ioctl__return(struct SUPDRVSESSION *pSession, uintptr_t uIOCtl, void *pvReqHdr, int rc, int rcReq); +}; + +#pragma D attributes Evolving/Evolving/Common provider vboxdrv provider +#pragma D attributes Private/Private/Unknown provider vboxdrv module +#pragma D attributes Private/Private/Unknown provider vboxdrv function +#pragma D attributes Evolving/Evolving/Common provider vboxdrv name +#pragma D attributes Evolving/Evolving/Common provider vboxdrv args + diff --git a/src/VBox/HostDrivers/Support/SUPDrvGip.cpp b/src/VBox/HostDrivers/Support/SUPDrvGip.cpp new file mode 100644 index 00000000..c203dd46 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvGip.cpp @@ -0,0 +1,5120 @@ +/* $Id: SUPDrvGip.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Common code for GIP. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#define SUPDRV_AGNOSTIC +#include "SUPDrvInternal.h" +#ifndef PAGE_SHIFT +# include <iprt/param.h> +#endif +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/asm-math.h> +#include <iprt/cpuset.h> +#include <iprt/handletable.h> +#include <iprt/mem.h> +#include <iprt/mp.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/thread.h> +#include <iprt/uuid.h> +#include <iprt/net.h> +#include <iprt/crc.h> +#include <iprt/string.h> +#include <iprt/timer.h> +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include <iprt/rand.h> +# include <iprt/path.h> +#endif +#include <iprt/uint128.h> +#include <iprt/x86.h> + +#include <VBox/param.h> +#include <VBox/log.h> +#include <VBox/err.h> + +#if defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN) +# include "dtrace/SUPDrv.h" +#else +/* ... */ +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The frequency by which we recalculate the u32UpdateHz and + * u32UpdateIntervalNS GIP members. The value must be a power of 2. + * + * Warning: Bumping this too high might overflow u32UpdateIntervalNS. + */ +#define GIP_UPDATEHZ_RECALC_FREQ 0x800 + +/** A reserved TSC value used for synchronization as well as measurement of + * TSC deltas. */ +#define GIP_TSC_DELTA_RSVD UINT64_MAX +/** The number of TSC delta measurement loops in total (includes primer and + * read-time loops). */ +#define GIP_TSC_DELTA_LOOPS 96 +/** The number of cache primer loops. */ +#define GIP_TSC_DELTA_PRIMER_LOOPS 4 +/** The number of loops until we keep computing the minumum read time. */ +#define GIP_TSC_DELTA_READ_TIME_LOOPS 24 + +/** The TSC frequency refinement period in seconds. + * The timer fires after 200ms, then every second, this value just says when + * to stop it after that. */ +#define GIP_TSC_REFINE_PERIOD_IN_SECS 12 +/** The TSC-delta threshold for the SUPGIPUSETSCDELTA_PRACTICALLY_ZERO rating */ +#define GIP_TSC_DELTA_THRESHOLD_PRACTICALLY_ZERO 32 +/** The TSC-delta threshold for the SUPGIPUSETSCDELTA_ROUGHLY_ZERO rating */ +#define GIP_TSC_DELTA_THRESHOLD_ROUGHLY_ZERO 448 +/** The TSC delta value for the initial GIP master - 0 in regular builds. + * To test the delta code this can be set to a non-zero value. */ +#if 0 +# define GIP_TSC_DELTA_INITIAL_MASTER_VALUE INT64_C(170139095182512) /* 0x00009abd9854acb0 */ +#else +# define GIP_TSC_DELTA_INITIAL_MASTER_VALUE INT64_C(0) +#endif + +AssertCompile(GIP_TSC_DELTA_PRIMER_LOOPS < GIP_TSC_DELTA_READ_TIME_LOOPS); +AssertCompile(GIP_TSC_DELTA_PRIMER_LOOPS + GIP_TSC_DELTA_READ_TIME_LOOPS < GIP_TSC_DELTA_LOOPS); + +/** @def VBOX_SVN_REV + * The makefile should define this if it can. */ +#ifndef VBOX_SVN_REV +# define VBOX_SVN_REV 0 +#endif + +#if 0 /* Don't start the GIP timers. Useful when debugging the IPRT timer code. */ +# define DO_NOT_START_GIP +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static DECLCALLBACK(void) supdrvGipSyncAndInvariantTimer(PRTTIMER pTimer, void *pvUser, uint64_t iTick); +static DECLCALLBACK(void) supdrvGipAsyncTimer(PRTTIMER pTimer, void *pvUser, uint64_t iTick); +static int supdrvGipSetFlags(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t fOrMask, uint32_t fAndMask); +static void supdrvGipInitCpu(PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pCpu, uint64_t u64NanoTS, uint64_t uCpuHz); +static void supdrvTscResetSamples(PSUPDRVDEVEXT pDevExt, bool fClearDeltas); +#ifdef SUPDRV_USE_TSC_DELTA_THREAD +static int supdrvTscDeltaThreadInit(PSUPDRVDEVEXT pDevExt); +static void supdrvTscDeltaTerm(PSUPDRVDEVEXT pDevExt); +static void supdrvTscDeltaThreadStartMeasurement(PSUPDRVDEVEXT pDevExt, bool fForceAll); +#else +static int supdrvTscMeasureInitialDeltas(PSUPDRVDEVEXT pDevExt); +static int supdrvTscMeasureDeltaOne(PSUPDRVDEVEXT pDevExt, uint32_t idxWorker); +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +DECLEXPORT(PSUPGLOBALINFOPAGE) g_pSUPGlobalInfoPage = NULL; +SUPR0_EXPORT_SYMBOL(g_pSUPGlobalInfoPage); + + + +/* + * + * Misc Common GIP Code + * Misc Common GIP Code + * Misc Common GIP Code + * + * + */ + + +/** + * Finds the GIP CPU index corresponding to @a idCpu. + * + * @returns GIP CPU array index, UINT32_MAX if not found. + * @param pGip The GIP. + * @param idCpu The CPU ID. + */ +static uint32_t supdrvGipFindCpuIndexForCpuId(PSUPGLOBALINFOPAGE pGip, RTCPUID idCpu) +{ + uint32_t i; + for (i = 0; i < pGip->cCpus; i++) + if (pGip->aCPUs[i].idCpu == idCpu) + return i; + return UINT32_MAX; +} + + +/** + * Gets the APIC ID using the best available method. + * + * @returns APIC ID. + * @param pGip The GIP, for SUPGIPGETCPU_XXX. + */ +DECLINLINE(uint32_t) supdrvGipGetApicId(PSUPGLOBALINFOPAGE pGip) +{ + if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B) + return ASMGetApicIdExt0B(); + if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E) + return ASMGetApicIdExt8000001E(); + return ASMGetApicId(); +} + + +/** + * Gets the APIC ID using the best available method, slow version. + */ +static uint32_t supdrvGipGetApicIdSlow(void) +{ + uint32_t const idApic = ASMGetApicId(); + + /* The Intel CPU topology leaf: */ + uint32_t uOther = ASMCpuId_EAX(0); + if (uOther >= UINT32_C(0xb) && RTX86IsValidStdRange(uOther)) + { + uint32_t uEax = 0; + uint32_t uEbx = 0; + uint32_t uEcx = 0; + uint32_t uEdx = 0; +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) + ASMCpuId_Idx_ECX(0xb, 0, &uEax, &uEbx, &uEcx, &uEdx); +#else + ASMCpuIdExSlow(0xb, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); +#endif + if ((uEcx >> 8) != 0) /* level type != invalid */ + { + if ((uEdx & 0xff) == idApic) + return uEdx; + AssertMsgFailed(("ASMGetApicIdExt0B=>%#x idApic=%#x\n", uEdx, idApic)); + } + } + + /* The AMD leaf: */ + uOther = ASMCpuId_EAX(UINT32_C(0x80000000)); + if (uOther >= UINT32_C(0x8000001e) && RTX86IsValidExtRange(uOther)) + { + uOther = ASMGetApicIdExt8000001E(); + if ((uOther & 0xff) == idApic) + return uOther; + AssertMsgFailed(("ASMGetApicIdExt8000001E=>%#x idApic=%#x\n", uOther, idApic)); + } + return idApic; +} + + +/* + * + * GIP Mapping and Unmapping Related Code. + * GIP Mapping and Unmapping Related Code. + * GIP Mapping and Unmapping Related Code. + * + * + */ + + +/** + * (Re-)initializes the per-cpu structure prior to starting or resuming the GIP + * updating. + * + * @param pGipCpu The per CPU structure for this CPU. + * @param u64NanoTS The current time. + */ +static void supdrvGipReInitCpu(PSUPGIPCPU pGipCpu, uint64_t u64NanoTS) +{ + /* + * Here we don't really care about applying the TSC delta. The re-initialization of this + * value is not relevant especially while (re)starting the GIP as the first few ones will + * be ignored anyway, see supdrvGipDoUpdateCpu(). + */ + pGipCpu->u64TSC = ASMReadTSC() - pGipCpu->u32UpdateIntervalTSC; + pGipCpu->u64NanoTS = u64NanoTS; +} + + +/** + * Set the current TSC and NanoTS value for the CPU. + * + * @param idCpu The CPU ID. Unused - we have to use the APIC ID. + * @param pvUser1 Pointer to the ring-0 GIP mapping. + * @param pvUser2 Pointer to the variable holding the current time. + */ +static DECLCALLBACK(void) supdrvGipReInitCpuCallback(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPGLOBALINFOPAGE pGip = (PSUPGLOBALINFOPAGE)pvUser1; + uint32_t const idApic = supdrvGipGetApicId(pGip); + if (idApic < RT_ELEMENTS(pGip->aiCpuFromApicId)) + { + unsigned const iCpu = pGip->aiCpuFromApicId[idApic]; + + if (RT_LIKELY(iCpu < pGip->cCpus && pGip->aCPUs[iCpu].idCpu == idCpu)) + supdrvGipReInitCpu(&pGip->aCPUs[iCpu], *(uint64_t *)pvUser2); + else + LogRelMax(64, ("supdrvGipReInitCpuCallback: iCpu=%#x out of bounds (%#zx, idApic=%#x)\n", + iCpu, RT_ELEMENTS(pGip->aiCpuFromApicId), idApic)); + } + else + LogRelMax(64, ("supdrvGipReInitCpuCallback: idApic=%#x out of bounds (%#zx)\n", + idApic, RT_ELEMENTS(pGip->aiCpuFromApicId))); + + NOREF(pvUser2); +} + + +/** + * State structure for supdrvGipDetectGetGipCpuCallback. + */ +typedef struct SUPDRVGIPDETECTGETCPU +{ + /** Bitmap of APIC IDs that has been seen (initialized to zero). + * Used to detect duplicate APIC IDs (paranoia). */ + uint8_t volatile bmApicId[4096 / 8]; + /** Mask of supported GIP CPU getter methods (SUPGIPGETCPU_XXX) (all bits set + * initially). The callback clears the methods not detected. */ + uint32_t volatile fSupported; + /** The first callback detecting any kind of range issues (initialized to + * NIL_RTCPUID). */ + RTCPUID volatile idCpuProblem; +} SUPDRVGIPDETECTGETCPU; +/** Pointer to state structure for supdrvGipDetectGetGipCpuCallback. */ +typedef SUPDRVGIPDETECTGETCPU *PSUPDRVGIPDETECTGETCPU; + + +/** + * Checks for alternative ways of getting the CPU ID. + * + * This also checks the APIC ID, CPU ID and CPU set index values against the + * GIP tables. + * + * @param idCpu The CPU ID. Unused - we have to use the APIC ID. + * @param pvUser1 Pointer to the state structure. + * @param pvUser2 Pointer to the GIP. + */ +static DECLCALLBACK(void) supdrvGipDetectGetGipCpuCallback(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPDRVGIPDETECTGETCPU pState = (PSUPDRVGIPDETECTGETCPU)pvUser1; + PSUPGLOBALINFOPAGE pGip = (PSUPGLOBALINFOPAGE)pvUser2; + uint32_t fSupported = 0; + uint32_t idApic; + uint32_t uEax, uEbx, uEcx, uEdx; + int iCpuSet; + NOREF(pGip); + + AssertMsg(idCpu == RTMpCpuId(), ("idCpu=%#x RTMpCpuId()=%#x\n", idCpu, RTMpCpuId())); /* paranoia^3 */ + + /* + * Check that the CPU ID and CPU set index are interchangable. + */ + iCpuSet = RTMpCpuIdToSetIndex(idCpu); + if ((RTCPUID)iCpuSet == idCpu) + { + AssertCompile(RT_IS_POWER_OF_TWO(RTCPUSET_MAX_CPUS)); + if ( iCpuSet >= 0 + && iCpuSet < RTCPUSET_MAX_CPUS + && RT_IS_POWER_OF_TWO(RTCPUSET_MAX_CPUS)) + { + PSUPGIPCPU pGipCpu = SUPGetGipCpuBySetIndex(pGip, iCpuSet); + + /* + * Check whether the IDTR.LIMIT contains a CPU number. + */ +#ifdef RT_ARCH_X86 + uint16_t const cbIdt = sizeof(X86DESC64SYSTEM) * 256; +#else + uint16_t const cbIdt = sizeof(X86DESCGATE) * 256; +#endif + RTIDTR Idtr; + ASMGetIDTR(&Idtr); + if (Idtr.cbIdt >= cbIdt) + { + uint32_t uTmp = Idtr.cbIdt - cbIdt; + uTmp &= RTCPUSET_MAX_CPUS - 1; + if (uTmp == idCpu) + { + RTIDTR Idtr2; + ASMGetIDTR(&Idtr2); + if (Idtr2.cbIdt == Idtr.cbIdt) + fSupported |= SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS; + } + } + + /* + * Check whether RDTSCP is an option. + */ + if (ASMHasCpuId()) + { + if ( RTX86IsValidExtRange(ASMCpuId_EAX(UINT32_C(0x80000000))) + && (ASMCpuId_EDX(UINT32_C(0x80000001)) & X86_CPUID_EXT_FEATURE_EDX_RDTSCP) ) + { + uint32_t uAux; + ASMReadTscWithAux(&uAux); + if ((uAux & (RTCPUSET_MAX_CPUS - 1)) == idCpu) + { + ASMNopPause(); + ASMReadTscWithAux(&uAux); + if ((uAux & (RTCPUSET_MAX_CPUS - 1)) == idCpu) + fSupported |= SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS; + } + + if (pGipCpu) + { + uint32_t const uGroupedAux = (uint8_t)pGipCpu->iCpuGroupMember | ((uint32_t)pGipCpu->iCpuGroup << 8); + if ( (uAux & UINT16_MAX) == uGroupedAux + && pGipCpu->iCpuGroupMember <= UINT8_MAX) + { + ASMNopPause(); + ASMReadTscWithAux(&uAux); + if ((uAux & UINT16_MAX) == uGroupedAux) + fSupported |= SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL; + } + } + } + } + } + } + + /* + * Check for extended APIC ID methods. + */ + idApic = UINT32_MAX; + uEax = ASMCpuId_EAX(0); + if (uEax >= UINT32_C(0xb) && RTX86IsValidStdRange(uEax)) + { +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) + ASMCpuId_Idx_ECX(0xb, 0, &uEax, &uEbx, &uEcx, &uEdx); +#else + ASMCpuIdExSlow(0xb, 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); +#endif + if ((uEcx >> 8) != 0) /* level type != invalid */ + { + if (RT_LIKELY( uEdx < RT_ELEMENTS(pGip->aiCpuFromApicId) + && !ASMBitTest(pState->bmApicId, uEdx))) + { + if (uEdx == ASMGetApicIdExt0B()) + { + idApic = uEdx; + fSupported |= SUPGIPGETCPU_APIC_ID_EXT_0B; + } + else + AssertMsgFailed(("%#x vs %#x\n", uEdx, ASMGetApicIdExt0B())); + } + } + } + + uEax = ASMCpuId_EAX(UINT32_C(0x80000000)); + if (uEax >= UINT32_C(0x8000001e) && RTX86IsValidExtRange(uEax)) + { +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) + ASMCpuId_Idx_ECX(UINT32_C(0x8000001e), 0, &uEax, &uEbx, &uEcx, &uEdx); +#else + ASMCpuIdExSlow(UINT32_C(0x8000001e), 0, 0, 0, &uEax, &uEbx, &uEcx, &uEdx); +#endif + if (uEax || uEbx || uEcx || uEdx) + { + if (RT_LIKELY( uEax < RT_ELEMENTS(pGip->aiCpuFromApicId) + && ( idApic == UINT32_MAX + || idApic == uEax) + && !ASMBitTest(pState->bmApicId, uEax))) + { + if (uEax == ASMGetApicIdExt8000001E()) + { + idApic = uEax; + fSupported |= SUPGIPGETCPU_APIC_ID_EXT_8000001E; + } + else + AssertMsgFailed(("%#x vs %#x\n", uEax, ASMGetApicIdExt8000001E())); + } + } + } + + /* + * Check that the APIC ID is unique. + */ + uEax = ASMGetApicId(); + if (RT_LIKELY( uEax < RT_ELEMENTS(pGip->aiCpuFromApicId) + && ( idApic == UINT32_MAX + || idApic == uEax) + && !ASMAtomicBitTestAndSet(pState->bmApicId, uEax))) + { + idApic = uEax; + fSupported |= SUPGIPGETCPU_APIC_ID; + } + else if ( idApic == UINT32_MAX + || idApic >= RT_ELEMENTS(pGip->aiCpuFromApicId) /* parnaoia */ + || ASMAtomicBitTestAndSet(pState->bmApicId, idApic)) + { + AssertCompile(sizeof(pState->bmApicId) * 8 == RT_ELEMENTS(pGip->aiCpuFromApicId)); + ASMAtomicCmpXchgU32(&pState->idCpuProblem, idCpu, NIL_RTCPUID); + LogRel(("supdrvGipDetectGetGipCpuCallback: idCpu=%#x iCpuSet=%d idApic=%#x/%#x - duplicate APIC ID.\n", + idCpu, iCpuSet, uEax, idApic)); + } + + /* + * Check that the iCpuSet is within the expected range. + */ + if (RT_UNLIKELY( iCpuSet < 0 + || (unsigned)iCpuSet >= RTCPUSET_MAX_CPUS + || (unsigned)iCpuSet >= RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + { + ASMAtomicCmpXchgU32(&pState->idCpuProblem, idCpu, NIL_RTCPUID); + LogRel(("supdrvGipDetectGetGipCpuCallback: idCpu=%#x iCpuSet=%d idApic=%#x - CPU set index is out of range.\n", + idCpu, iCpuSet, idApic)); + } + else + { + RTCPUID idCpu2 = RTMpCpuIdFromSetIndex(iCpuSet); + if (RT_UNLIKELY(idCpu2 != idCpu)) + { + ASMAtomicCmpXchgU32(&pState->idCpuProblem, idCpu, NIL_RTCPUID); + LogRel(("supdrvGipDetectGetGipCpuCallback: idCpu=%#x iCpuSet=%d idApic=%#x - CPU id/index roundtrip problem: %#x\n", + idCpu, iCpuSet, idApic, idCpu2)); + } + } + + /* + * Update the supported feature mask before we return. + */ + ASMAtomicAndU32(&pState->fSupported, fSupported); + + NOREF(pvUser2); +} + + +/** + * Increase the timer freqency on hosts where this is possible (NT). + * + * The idea is that more interrupts is better for us... Also, it's better than + * we increase the timer frequence, because we might end up getting inaccurate + * callbacks if someone else does it. + * + * @param pDevExt Sets u32SystemTimerGranularityGrant if increased. + */ +static void supdrvGipRequestHigherTimerFrequencyFromSystem(PSUPDRVDEVEXT pDevExt) +{ + if (pDevExt->u32SystemTimerGranularityGrant == 0) + { + uint32_t u32SystemResolution; + if ( RT_SUCCESS_NP(RTTimerRequestSystemGranularity( 976563 /* 1024 HZ */, &u32SystemResolution)) + || RT_SUCCESS_NP(RTTimerRequestSystemGranularity( 1000000 /* 1000 HZ */, &u32SystemResolution)) + || RT_SUCCESS_NP(RTTimerRequestSystemGranularity( 1953125 /* 512 HZ */, &u32SystemResolution)) + || RT_SUCCESS_NP(RTTimerRequestSystemGranularity( 2000000 /* 500 HZ */, &u32SystemResolution)) + ) + { +#if 0 /* def VBOX_STRICT - this is somehow triggers bogus assertions on windows 10 */ + uint32_t u32After = RTTimerGetSystemGranularity(); + AssertMsg(u32After <= u32SystemResolution, ("u32After=%u u32SystemResolution=%u\n", u32After, u32SystemResolution)); +#endif + pDevExt->u32SystemTimerGranularityGrant = u32SystemResolution; + } + } +} + + +/** + * Undoes supdrvGipRequestHigherTimerFrequencyFromSystem. + * + * @param pDevExt Clears u32SystemTimerGranularityGrant. + */ +static void supdrvGipReleaseHigherTimerFrequencyFromSystem(PSUPDRVDEVEXT pDevExt) +{ + if (pDevExt->u32SystemTimerGranularityGrant) + { + int rc2 = RTTimerReleaseSystemGranularity(pDevExt->u32SystemTimerGranularityGrant); + AssertRC(rc2); + pDevExt->u32SystemTimerGranularityGrant = 0; + } +} + + +/** + * Maps the GIP into userspace and/or get the physical address of the GIP. + * + * @returns IPRT status code. + * @param pSession Session to which the GIP mapping should belong. + * @param ppGipR3 Where to store the address of the ring-3 mapping. (optional) + * @param pHCPhysGip Where to store the physical address. (optional) + * + * @remark There is no reference counting on the mapping, so one call to this function + * count globally as one reference. One call to SUPR0GipUnmap() is will unmap GIP + * and remove the session as a GIP user. + */ +SUPR0DECL(int) SUPR0GipMap(PSUPDRVSESSION pSession, PRTR3PTR ppGipR3, PRTHCPHYS pHCPhysGip) +{ + int rc; + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + RTR3PTR pGipR3 = NIL_RTR3PTR; + RTHCPHYS HCPhys = NIL_RTHCPHYS; + LogFlow(("SUPR0GipMap: pSession=%p ppGipR3=%p pHCPhysGip=%p\n", pSession, ppGipR3, pHCPhysGip)); + + /* + * Validate + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrNullReturn(ppGipR3, VERR_INVALID_POINTER); + AssertPtrNullReturn(pHCPhysGip, VERR_INVALID_POINTER); + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRequest(pDevExt->mtxGip, RT_INDEFINITE_WAIT); +#else + RTSemFastMutexRequest(pDevExt->mtxGip); +#endif + if (pDevExt->pGip) + { + /* + * Map it? + */ + rc = VINF_SUCCESS; + if (ppGipR3) + { + if (pSession->GipMapObjR3 == NIL_RTR0MEMOBJ) + rc = RTR0MemObjMapUser(&pSession->GipMapObjR3, pDevExt->GipMemObj, (RTR3PTR)-1, 0, + RTMEM_PROT_READ, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + pGipR3 = RTR0MemObjAddressR3(pSession->GipMapObjR3); + } + + /* + * Get physical address. + */ + if (pHCPhysGip && RT_SUCCESS(rc)) + HCPhys = pDevExt->HCPhysGip; + + /* + * Reference globally. + */ + if (!pSession->fGipReferenced && RT_SUCCESS(rc)) + { + pSession->fGipReferenced = 1; + pDevExt->cGipUsers++; + if (pDevExt->cGipUsers == 1) + { + PSUPGLOBALINFOPAGE pGipR0 = pDevExt->pGip; + uint64_t u64NanoTS; + + /* + * GIP starts/resumes updating again. On windows we bump the + * host timer frequency to make sure we don't get stuck in guest + * mode and to get better timer (and possibly clock) accuracy. + */ + LogFlow(("SUPR0GipMap: Resumes GIP updating\n")); + + supdrvGipRequestHigherTimerFrequencyFromSystem(pDevExt); + + /* + * document me + */ + if (pGipR0->aCPUs[0].u32TransactionId != 2 /* not the first time */) + { + unsigned i; + for (i = 0; i < pGipR0->cCpus; i++) + ASMAtomicUoWriteU32(&pGipR0->aCPUs[i].u32TransactionId, + (pGipR0->aCPUs[i].u32TransactionId + GIP_UPDATEHZ_RECALC_FREQ * 2) + & ~(GIP_UPDATEHZ_RECALC_FREQ * 2 - 1)); + ASMAtomicWriteU64(&pGipR0->u64NanoTSLastUpdateHz, 0); + } + + /* + * document me + */ + u64NanoTS = RTTimeSystemNanoTS() - pGipR0->u32UpdateIntervalNS; + if ( pGipR0->u32Mode == SUPGIPMODE_INVARIANT_TSC + || pGipR0->u32Mode == SUPGIPMODE_SYNC_TSC + || RTMpGetOnlineCount() == 1) + supdrvGipReInitCpu(&pGipR0->aCPUs[0], u64NanoTS); + else + RTMpOnAll(supdrvGipReInitCpuCallback, pGipR0, &u64NanoTS); + + /* + * Detect alternative ways to figure the CPU ID in ring-3 and + * raw-mode context. Check the sanity of the APIC IDs, CPU IDs, + * and CPU set indexes while we're at it. + */ + if (RT_SUCCESS(rc)) + { + PSUPDRVGIPDETECTGETCPU pDetectState = (PSUPDRVGIPDETECTGETCPU)RTMemTmpAllocZ(sizeof(*pDetectState)); + if (pDetectState) + { + pDetectState->fSupported = UINT32_MAX; + pDetectState->idCpuProblem = NIL_RTCPUID; + rc = RTMpOnAll(supdrvGipDetectGetGipCpuCallback, pDetectState, pGipR0); + if (pDetectState->idCpuProblem == NIL_RTCPUID) + { + if ( pDetectState->fSupported != UINT32_MAX + && pDetectState->fSupported != 0) + { + if (pGipR0->fGetGipCpu != pDetectState->fSupported) + { + pGipR0->fGetGipCpu = pDetectState->fSupported; + LogRel(("SUPR0GipMap: fGetGipCpu=%#x\n", pDetectState->fSupported)); + } + } + else + { + LogRel(("SUPR0GipMap: No supported ways of getting the APIC ID or CPU number in ring-3! (%#x)\n", + pDetectState->fSupported)); + rc = VERR_UNSUPPORTED_CPU; + } + } + else + { + LogRel(("SUPR0GipMap: APIC ID, CPU ID or CPU set index problem detected on CPU #%u (%#x)!\n", + pDetectState->idCpuProblem, pDetectState->idCpuProblem)); + rc = VERR_INVALID_CPU_ID; + } + RTMemTmpFree(pDetectState); + } + else + rc = VERR_NO_TMP_MEMORY; + } + + /* + * Start the GIP timer if all is well.. + */ + if (RT_SUCCESS(rc)) + { +#ifndef DO_NOT_START_GIP + rc = RTTimerStart(pDevExt->pGipTimer, 0 /* fire ASAP */); AssertRC(rc); +#endif + rc = VINF_SUCCESS; + } + + /* + * Bail out on error. + */ + if (RT_FAILURE(rc)) + { + LogRel(("SUPR0GipMap: failed rc=%Rrc\n", rc)); + pDevExt->cGipUsers = 0; + pSession->fGipReferenced = 0; + if (pSession->GipMapObjR3 != NIL_RTR0MEMOBJ) + { + int rc2 = RTR0MemObjFree(pSession->GipMapObjR3, false); AssertRC(rc2); + if (RT_SUCCESS(rc2)) + pSession->GipMapObjR3 = NIL_RTR0MEMOBJ; + } + HCPhys = NIL_RTHCPHYS; + pGipR3 = NIL_RTR3PTR; + } + } + } + } + else + { + rc = VERR_GENERAL_FAILURE; + Log(("SUPR0GipMap: GIP is not available!\n")); + } +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRelease(pDevExt->mtxGip); +#else + RTSemFastMutexRelease(pDevExt->mtxGip); +#endif + + /* + * Write returns. + */ + if (pHCPhysGip) + *pHCPhysGip = HCPhys; + if (ppGipR3) + *ppGipR3 = pGipR3; + +#ifdef DEBUG_DARWIN_GIP + OSDBGPRINT(("SUPR0GipMap: returns %d *pHCPhysGip=%lx pGipR3=%p\n", rc, (unsigned long)HCPhys, (void *)pGipR3)); +#else + LogFlow(( "SUPR0GipMap: returns %d *pHCPhysGip=%lx pGipR3=%p\n", rc, (unsigned long)HCPhys, (void *)pGipR3)); +#endif + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0GipMap); + + +/** + * Unmaps any user mapping of the GIP and terminates all GIP access + * from this session. + * + * @returns IPRT status code. + * @param pSession Session to which the GIP mapping should belong. + */ +SUPR0DECL(int) SUPR0GipUnmap(PSUPDRVSESSION pSession) +{ + int rc = VINF_SUCCESS; + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; +#ifdef DEBUG_DARWIN_GIP + OSDBGPRINT(("SUPR0GipUnmap: pSession=%p pGip=%p GipMapObjR3=%p\n", + pSession, + pSession->GipMapObjR3 != NIL_RTR0MEMOBJ ? RTR0MemObjAddress(pSession->GipMapObjR3) : NULL, + pSession->GipMapObjR3)); +#else + LogFlow(("SUPR0GipUnmap: pSession=%p\n", pSession)); +#endif + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRequest(pDevExt->mtxGip, RT_INDEFINITE_WAIT); +#else + RTSemFastMutexRequest(pDevExt->mtxGip); +#endif + + /* + * GIP test-mode session? + */ + if ( pSession->fGipTestMode + && pDevExt->pGip) + { + supdrvGipSetFlags(pDevExt, pSession, 0, ~SUPGIP_FLAGS_TESTING_ENABLE); + Assert(!pSession->fGipTestMode); + } + + /* + * Unmap anything? + */ + if (pSession->GipMapObjR3 != NIL_RTR0MEMOBJ) + { + rc = RTR0MemObjFree(pSession->GipMapObjR3, false); + AssertRC(rc); + if (RT_SUCCESS(rc)) + pSession->GipMapObjR3 = NIL_RTR0MEMOBJ; + } + + /* + * Dereference global GIP. + */ + if (pSession->fGipReferenced && !rc) + { + pSession->fGipReferenced = 0; + if ( pDevExt->cGipUsers > 0 + && !--pDevExt->cGipUsers) + { + LogFlow(("SUPR0GipUnmap: Suspends GIP updating\n")); +#ifndef DO_NOT_START_GIP + rc = RTTimerStop(pDevExt->pGipTimer); AssertRC(rc); rc = VINF_SUCCESS; +#endif + supdrvGipReleaseHigherTimerFrequencyFromSystem(pDevExt); + } + } + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRelease(pDevExt->mtxGip); +#else + RTSemFastMutexRelease(pDevExt->mtxGip); +#endif + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0GipUnmap); + + +/** + * Gets the GIP pointer. + * + * @returns Pointer to the GIP or NULL. + */ +SUPDECL(PSUPGLOBALINFOPAGE) SUPGetGIP(void) +{ + return g_pSUPGlobalInfoPage; +} + + + + + +/* + * + * + * GIP Initialization, Termination and CPU Offline / Online Related Code. + * GIP Initialization, Termination and CPU Offline / Online Related Code. + * GIP Initialization, Termination and CPU Offline / Online Related Code. + * + * + */ + +/** + * Used by supdrvGipInitRefineInvariantTscFreqTimer and supdrvGipInitMeasureTscFreq + * to update the TSC frequency related GIP variables. + * + * @param pGip The GIP. + * @param nsElapsed The number of nanoseconds elapsed. + * @param cElapsedTscTicks The corresponding number of TSC ticks. + * @param iTick The tick number for debugging. + */ +static void supdrvGipInitSetCpuFreq(PSUPGLOBALINFOPAGE pGip, uint64_t nsElapsed, uint64_t cElapsedTscTicks, uint32_t iTick) +{ + /* + * Calculate the frequency. + */ + uint64_t uCpuHz; + if ( cElapsedTscTicks < UINT64_MAX / RT_NS_1SEC + && nsElapsed < UINT32_MAX) + uCpuHz = ASMMultU64ByU32DivByU32(cElapsedTscTicks, RT_NS_1SEC, (uint32_t)nsElapsed); + else + { + RTUINT128U CpuHz, Tmp, Divisor; + CpuHz.s.Lo = CpuHz.s.Hi = 0; + RTUInt128MulU64ByU64(&Tmp, cElapsedTscTicks, RT_NS_1SEC_64); + RTUInt128Div(&CpuHz, &Tmp, RTUInt128AssignU64(&Divisor, nsElapsed)); + uCpuHz = CpuHz.s.Lo; + } + + /* + * Update the GIP. + */ + ASMAtomicWriteU64(&pGip->u64CpuHz, uCpuHz); + if (pGip->u32Mode != SUPGIPMODE_ASYNC_TSC) + { + ASMAtomicWriteU64(&pGip->aCPUs[0].u64CpuHz, uCpuHz); + + /* For inspecting the frequency calcs using tstGIP-2, debugger or similar. */ + if (iTick + 1 < pGip->cCpus) + ASMAtomicWriteU64(&pGip->aCPUs[iTick + 1].u64CpuHz, uCpuHz); + } +} + + +/** + * Timer callback function for TSC frequency refinement in invariant GIP mode. + * + * This is started during driver init and fires once + * GIP_TSC_REFINE_PERIOD_IN_SECS seconds later. + * + * @param pTimer The timer. + * @param pvUser Opaque pointer to the device instance data. + * @param iTick The timer tick. + */ +static DECLCALLBACK(void) supdrvGipInitRefineInvariantTscFreqTimer(PRTTIMER pTimer, void *pvUser, uint64_t iTick) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + RTCPUID idCpu; + uint64_t cNsElapsed; + uint64_t cTscTicksElapsed; + uint64_t nsNow; + uint64_t uTsc; + RTCCUINTREG fEFlags; + + /* Paranoia. */ + AssertReturnVoid(pGip); + AssertReturnVoid(pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC); + + /* + * If we got a power event, stop the refinement process. + */ + if (pDevExt->fInvTscRefinePowerEvent) + { + int rc = RTTimerStop(pTimer); AssertRC(rc); + return; + } + + /* + * Read the TSC and time, noting which CPU we are on. + * + * Don't bother spinning until RTTimeSystemNanoTS changes, since on + * systems where it matters we're in a context where we cannot waste that + * much time (DPC watchdog, called from clock interrupt). + */ + fEFlags = ASMIntDisableFlags(); + uTsc = ASMReadTSC(); + nsNow = RTTimeSystemNanoTS(); + idCpu = RTMpCpuId(); + ASMSetFlags(fEFlags); + + cNsElapsed = nsNow - pDevExt->nsStartInvarTscRefine; + cTscTicksElapsed = uTsc - pDevExt->uTscStartInvarTscRefine; + + /* + * If the above measurement was taken on a different CPU than the one we + * started the process on, cTscTicksElapsed will need to be adjusted with + * the TSC deltas of both the CPUs. + * + * We ASSUME that the delta calculation process takes less time than the + * TSC frequency refinement timer. If it doesn't, we'll complain and + * drop the frequency refinement. + * + * Note! We cannot entirely trust enmUseTscDelta here because it's + * downgraded after each delta calculation. + */ + if ( idCpu != pDevExt->idCpuInvarTscRefine + && pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { + uint32_t iStartCpuSet = RTMpCpuIdToSetIndex(pDevExt->idCpuInvarTscRefine); + uint32_t iStopCpuSet = RTMpCpuIdToSetIndex(idCpu); + uint16_t iStartGipCpu = iStartCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + ? pGip->aiCpuFromCpuSetIdx[iStartCpuSet] : UINT16_MAX; + uint16_t iStopGipCpu = iStopCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + ? pGip->aiCpuFromCpuSetIdx[iStopCpuSet] : UINT16_MAX; + int64_t iStartTscDelta = iStartGipCpu < pGip->cCpus ? pGip->aCPUs[iStartGipCpu].i64TSCDelta : INT64_MAX; + int64_t iStopTscDelta = iStopGipCpu < pGip->cCpus ? pGip->aCPUs[iStopGipCpu].i64TSCDelta : INT64_MAX; + if (RT_LIKELY(iStartTscDelta != INT64_MAX && iStopTscDelta != INT64_MAX)) + { + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_PRACTICALLY_ZERO) + { + /* cTscTicksElapsed = (uTsc - iStopTscDelta) - (pDevExt->uTscStartInvarTscRefine - iStartTscDelta); */ + cTscTicksElapsed += iStartTscDelta - iStopTscDelta; + } + } + /* + * Allow 5 times the refinement period to elapse before we give up on the TSC delta + * calculations. + */ + else if (cNsElapsed > GIP_TSC_REFINE_PERIOD_IN_SECS * 5 * RT_NS_1SEC_64) + { + SUPR0Printf("vboxdrv: Failed to refine invariant TSC frequency because deltas are unavailable after %u (%u) seconds\n", + (uint32_t)(cNsElapsed / RT_NS_1SEC), GIP_TSC_REFINE_PERIOD_IN_SECS); + SUPR0Printf("vboxdrv: start: %u, %u, %#llx stop: %u, %u, %#llx\n", + iStartCpuSet, iStartGipCpu, iStartTscDelta, iStopCpuSet, iStopGipCpu, iStopTscDelta); + int rc = RTTimerStop(pTimer); AssertRC(rc); + return; + } + } + + /* + * Calculate and update the CPU frequency variables in GIP. + * + * If there is a GIP user already and we've already refined the frequency + * a couple of times, don't update it as we want a stable frequency value + * for all VMs. + */ + if ( pDevExt->cGipUsers == 0 + || cNsElapsed < RT_NS_1SEC * 2) + { + supdrvGipInitSetCpuFreq(pGip, cNsElapsed, cTscTicksElapsed, (uint32_t)iTick); + + /* + * Stop the timer once we've reached the defined refinement period. + */ + if (cNsElapsed > GIP_TSC_REFINE_PERIOD_IN_SECS * RT_NS_1SEC_64) + { + int rc = RTTimerStop(pTimer); + AssertRC(rc); + } + } + else + { + int rc = RTTimerStop(pTimer); + AssertRC(rc); + } +} + + +/** + * @callback_method_impl{FNRTPOWERNOTIFICATION} + */ +static DECLCALLBACK(void) supdrvGipPowerNotificationCallback(RTPOWEREVENT enmEvent, void *pvUser) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + + /* + * If the TSC frequency refinement timer is running, we need to cancel it so it + * doesn't screw up the frequency after a long suspend. + * + * Recalculate all TSC-deltas on host resume as it may have changed, seen + * on Windows 7 running on the Dell Optiplex Intel Core i5-3570. + */ + if (enmEvent == RTPOWEREVENT_RESUME) + { + ASMAtomicWriteBool(&pDevExt->fInvTscRefinePowerEvent, true); + if ( RT_LIKELY(pGip) + && pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED + && !supdrvOSAreCpusOfflinedOnSuspend()) + { +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + supdrvTscDeltaThreadStartMeasurement(pDevExt, true /* fForceAll */); +#else + RTCpuSetCopy(&pDevExt->TscDeltaCpuSet, &pGip->OnlineCpuSet); + supdrvTscMeasureInitialDeltas(pDevExt); +#endif + } + } + else if (enmEvent == RTPOWEREVENT_SUSPEND) + ASMAtomicWriteBool(&pDevExt->fInvTscRefinePowerEvent, true); +} + + +/** + * Start the TSC-frequency refinment timer for the invariant TSC GIP mode. + * + * We cannot use this in the synchronous and asynchronous tsc GIP modes because + * the CPU may change the TSC frequence between now and when the timer fires + * (supdrvInitAsyncRefineTscTimer). + * + * @param pDevExt Pointer to the device instance data. + */ +static void supdrvGipInitStartTimerForRefiningInvariantTscFreq(PSUPDRVDEVEXT pDevExt) +{ + uint64_t u64NanoTS; + RTCCUINTREG fEFlags; + int rc; + + /* + * Register a power management callback. + */ + pDevExt->fInvTscRefinePowerEvent = false; + rc = RTPowerNotificationRegister(supdrvGipPowerNotificationCallback, pDevExt); + AssertRC(rc); /* ignore */ + + /* + * Record the TSC and NanoTS as the starting anchor point for refinement + * of the TSC. We try get as close to a clock tick as possible on systems + * which does not provide high resolution time. + */ + u64NanoTS = RTTimeSystemNanoTS(); + while (RTTimeSystemNanoTS() == u64NanoTS) + ASMNopPause(); + + fEFlags = ASMIntDisableFlags(); + pDevExt->uTscStartInvarTscRefine = ASMReadTSC(); + pDevExt->nsStartInvarTscRefine = RTTimeSystemNanoTS(); + pDevExt->idCpuInvarTscRefine = RTMpCpuId(); + ASMSetFlags(fEFlags); + + /* + * Create a timer that runs on the same CPU so we won't have a depencency + * on the TSC-delta and can run in parallel to it. On systems that does not + * implement CPU specific timers we'll apply deltas in the timer callback, + * just like we do for CPUs going offline. + * + * The longer the refinement interval the better the accuracy, at least in + * theory. If it's too long though, ring-3 may already be starting its + * first VMs before we're done. On most systems we will be loading the + * support driver during boot and VMs won't be started for a while yet, + * it is really only a problem during development (especially with + * on-demand driver starting on windows). + * + * To avoid wasting time doing a long supdrvGipInitMeasureTscFreq() call + * to calculate the frequency during driver loading, the timer is set + * to fire after 200 ms the first time. It will then reschedule itself + * to fire every second until GIP_TSC_REFINE_PERIOD_IN_SECS has been + * reached or it notices that there is a user land client with GIP + * mapped (we want a stable frequency for all VMs). + */ + rc = RTTimerCreateEx(&pDevExt->pInvarTscRefineTimer, RT_NS_1SEC, + RTTIMER_FLAGS_CPU(RTMpCpuIdToSetIndex(pDevExt->idCpuInvarTscRefine)), + supdrvGipInitRefineInvariantTscFreqTimer, pDevExt); + if (RT_SUCCESS(rc)) + { + rc = RTTimerStart(pDevExt->pInvarTscRefineTimer, 2*RT_NS_100MS); + if (RT_SUCCESS(rc)) + return; + RTTimerDestroy(pDevExt->pInvarTscRefineTimer); + } + + if (rc == VERR_CPU_OFFLINE || rc == VERR_NOT_SUPPORTED) + { + rc = RTTimerCreateEx(&pDevExt->pInvarTscRefineTimer, RT_NS_1SEC, RTTIMER_FLAGS_CPU_ANY, + supdrvGipInitRefineInvariantTscFreqTimer, pDevExt); + if (RT_SUCCESS(rc)) + { + rc = RTTimerStart(pDevExt->pInvarTscRefineTimer, 2*RT_NS_100MS); + if (RT_SUCCESS(rc)) + return; + RTTimerDestroy(pDevExt->pInvarTscRefineTimer); + } + } + + pDevExt->pInvarTscRefineTimer = NULL; + OSDBGPRINT(("vboxdrv: Failed to create or start TSC frequency refinement timer: rc=%Rrc\n", rc)); +} + + +/** + * @callback_method_impl{PFNRTMPWORKER, + * RTMpOnSpecific callback for reading TSC and time on the CPU we started + * the measurements on.} + */ +static DECLCALLBACK(void) supdrvGipInitReadTscAndNanoTsOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + RTCCUINTREG fEFlags = ASMIntDisableFlags(); + uint64_t *puTscStop = (uint64_t *)pvUser1; + uint64_t *pnsStop = (uint64_t *)pvUser2; + RT_NOREF1(idCpu); + + *puTscStop = ASMReadTSC(); + *pnsStop = RTTimeSystemNanoTS(); + + ASMSetFlags(fEFlags); +} + + +/** + * Measures the TSC frequency of the system. + * + * The TSC frequency can vary on systems which are not reported as invariant. + * On such systems the object of this function is to find out what the nominal, + * maximum TSC frequency under 'normal' CPU operation. + * + * @returns VBox status code. + * @param pGip Pointer to the GIP. + * @param fRough Set if we're doing the rough calculation that the + * TSC measuring code needs, where accuracy isn't all + * that important (too high is better than too low). + * When clear we try for best accuracy that we can + * achieve in reasonably short time. + */ +static int supdrvGipInitMeasureTscFreq(PSUPGLOBALINFOPAGE pGip, bool fRough) +{ + uint32_t nsTimerIncr = RTTimerGetSystemGranularity(); + int cTriesLeft = fRough ? 4 : 2; + while (cTriesLeft-- > 0) + { + RTCCUINTREG fEFlags; + uint64_t nsStart; + uint64_t nsStop; + uint64_t uTscStart; + uint64_t uTscStop; + RTCPUID idCpuStart; + RTCPUID idCpuStop; + + /* + * Synchronize with the host OS clock tick on systems without high + * resolution time API (older Windows version for example). + */ + nsStart = RTTimeSystemNanoTS(); + while (RTTimeSystemNanoTS() == nsStart) + ASMNopPause(); + + /* + * Read the TSC and current time, noting which CPU we're on. + */ + fEFlags = ASMIntDisableFlags(); + uTscStart = ASMReadTSC(); + nsStart = RTTimeSystemNanoTS(); + idCpuStart = RTMpCpuId(); + ASMSetFlags(fEFlags); + + /* + * Delay for a while. + */ + if (pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC) + { + /* + * Sleep-wait since the TSC frequency is constant, it eases host load. + * Shorter interval produces more variance in the frequency (esp. Windows). + */ + uint64_t msElapsed = 0; + uint64_t msDelay = ( ((fRough ? 16 : 200) * RT_NS_1MS + nsTimerIncr - 1) / nsTimerIncr * nsTimerIncr - RT_NS_100US ) + / RT_NS_1MS; + do + { + RTThreadSleep((RTMSINTERVAL)(msDelay - msElapsed)); + nsStop = RTTimeSystemNanoTS(); + msElapsed = (nsStop - nsStart) / RT_NS_1MS; + } while (msElapsed < msDelay); + + while (RTTimeSystemNanoTS() == nsStop) + ASMNopPause(); + } + else + { + /* + * Busy-wait keeping the frequency up. + */ + do + { + ASMNopPause(); + nsStop = RTTimeSystemNanoTS(); + } while (nsStop - nsStart < RT_NS_100MS); + } + + /* + * Read the TSC and time again. + */ + fEFlags = ASMIntDisableFlags(); + uTscStop = ASMReadTSC(); + nsStop = RTTimeSystemNanoTS(); + idCpuStop = RTMpCpuId(); + ASMSetFlags(fEFlags); + + /* + * If the CPU changes, things get a bit complicated and what we + * can get away with depends on the GIP mode / TSC reliability. + */ + if (idCpuStop != idCpuStart) + { + bool fDoXCall = false; + + /* + * Synchronous TSC mode: we're probably fine as it's unlikely + * that we were rescheduled because of TSC throttling or power + * management reasons, so just go ahead. + */ + if (pGip->u32Mode == SUPGIPMODE_SYNC_TSC) + { + /* Probably ok, maybe we should retry once?. */ + Assert(pGip->enmUseTscDelta == SUPGIPUSETSCDELTA_NOT_APPLICABLE); + } + /* + * If we're just doing the rough measurement, do the cross call and + * get on with things (we don't have deltas!). + */ + else if (fRough) + fDoXCall = true; + /* + * Invariant TSC mode: It doesn't matter if we have delta available + * for both CPUs. That is not something we can assume at this point. + * + * Note! We cannot necessarily trust enmUseTscDelta here because it's + * downgraded after each delta calculation and the delta + * calculations may not be complete yet. + */ + else if (pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC) + { +/** @todo This section of code is never reached atm, consider dropping it later on... */ + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { + uint32_t iStartCpuSet = RTMpCpuIdToSetIndex(idCpuStart); + uint32_t iStopCpuSet = RTMpCpuIdToSetIndex(idCpuStop); + uint16_t iStartGipCpu = iStartCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + ? pGip->aiCpuFromCpuSetIdx[iStartCpuSet] : UINT16_MAX; + uint16_t iStopGipCpu = iStopCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + ? pGip->aiCpuFromCpuSetIdx[iStopCpuSet] : UINT16_MAX; + int64_t iStartTscDelta = iStartGipCpu < pGip->cCpus ? pGip->aCPUs[iStartGipCpu].i64TSCDelta : INT64_MAX; + int64_t iStopTscDelta = iStopGipCpu < pGip->cCpus ? pGip->aCPUs[iStopGipCpu].i64TSCDelta : INT64_MAX; + if (RT_LIKELY(iStartTscDelta != INT64_MAX && iStopTscDelta != INT64_MAX)) + { + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_PRACTICALLY_ZERO) + { + uTscStart -= iStartTscDelta; + uTscStop -= iStopTscDelta; + } + } + /* + * Invalid CPU indexes are not caused by online/offline races, so + * we have to trigger driver load failure if that happens as GIP + * and IPRT assumptions are busted on this system. + */ + else if (iStopGipCpu >= pGip->cCpus || iStartGipCpu >= pGip->cCpus) + { + SUPR0Printf("vboxdrv: Unexpected CPU index in supdrvGipInitMeasureTscFreq.\n"); + SUPR0Printf("vboxdrv: start: %u, %u, %#llx stop: %u, %u, %#llx\n", + iStartCpuSet, iStartGipCpu, iStartTscDelta, iStopCpuSet, iStopGipCpu, iStopTscDelta); + return VERR_INVALID_CPU_INDEX; + } + /* + * No valid deltas. We retry, if we're on our last retry + * we do the cross call instead just to get a result. The + * frequency will be refined in a few seconds anyway. + */ + else if (cTriesLeft > 0) + continue; + else + fDoXCall = true; + } + } + /* + * Asynchronous TSC mode: This is bad, as the reason we usually + * use this mode is to deal with variable TSC frequencies and + * deltas. So, we need to get the TSC from the same CPU as + * started it, we also need to keep that CPU busy. So, retry + * and fall back to the cross call on the last attempt. + */ + else + { + Assert(pGip->u32Mode == SUPGIPMODE_ASYNC_TSC); + if (cTriesLeft > 0) + continue; + fDoXCall = true; + } + + if (fDoXCall) + { + /* + * Try read the TSC and timestamp on the start CPU. + */ + int rc = RTMpOnSpecific(idCpuStart, supdrvGipInitReadTscAndNanoTsOnCpu, &uTscStop, &nsStop); + if (RT_FAILURE(rc) && (!fRough || cTriesLeft > 0)) + continue; + } + } + + /* + * Calculate the TSC frequency and update it (shared with the refinement timer). + */ + supdrvGipInitSetCpuFreq(pGip, nsStop - nsStart, uTscStop - uTscStart, 0); + return VINF_SUCCESS; + } + + Assert(!fRough); + return VERR_SUPDRV_TSC_FREQ_MEASUREMENT_FAILED; +} + + +/** + * Finds our (@a idCpu) entry, or allocates a new one if not found. + * + * @returns Index of the CPU in the cache set. + * @param pGip The GIP. + * @param idCpu The CPU ID. + */ +static uint32_t supdrvGipFindOrAllocCpuIndexForCpuId(PSUPGLOBALINFOPAGE pGip, RTCPUID idCpu) +{ + uint32_t i, cTries; + + /* + * ASSUMES that CPU IDs are constant. + */ + for (i = 0; i < pGip->cCpus; i++) + if (pGip->aCPUs[i].idCpu == idCpu) + return i; + + cTries = 0; + do + { + for (i = 0; i < pGip->cCpus; i++) + { + bool fRc; + ASMAtomicCmpXchgSize(&pGip->aCPUs[i].idCpu, idCpu, NIL_RTCPUID, fRc); + if (fRc) + return i; + } + } while (cTries++ < 32); + AssertReleaseFailed(); + return i - 1; +} + + +/** + * The calling CPU should be accounted as online, update GIP accordingly. + * + * This is used by supdrvGipCreate() as well as supdrvGipMpEvent(). + * + * @param pDevExt The device extension. + * @param idCpu The CPU ID. + */ +static void supdrvGipMpEventOnlineOrInitOnCpu(PSUPDRVDEVEXT pDevExt, RTCPUID idCpu) +{ + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + int iCpuSet = 0; + uint32_t idApic; + uint32_t i = 0; + uint64_t u64NanoTS = 0; + + AssertPtrReturnVoid(pGip); + Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + AssertRelease(idCpu == RTMpCpuId()); + Assert(pGip->cPossibleCpus == RTMpGetCount()); + + /* + * Do this behind a spinlock with interrupts disabled as this can fire + * on all CPUs simultaneously, see @bugref{6110}. + */ + RTSpinlockAcquire(pDevExt->hGipSpinlock); + + /* + * Update the globals. + */ + ASMAtomicWriteU16(&pGip->cPresentCpus, RTMpGetPresentCount()); + ASMAtomicWriteU16(&pGip->cOnlineCpus, RTMpGetOnlineCount()); + iCpuSet = RTMpCpuIdToSetIndex(idCpu); + if (iCpuSet >= 0) + { + Assert(RTCpuSetIsMemberByIndex(&pGip->PossibleCpuSet, iCpuSet)); + RTCpuSetAddByIndex(&pGip->OnlineCpuSet, iCpuSet); + RTCpuSetAddByIndex(&pGip->PresentCpuSet, iCpuSet); + } + + /* + * Update the entry. + */ + u64NanoTS = RTTimeSystemNanoTS() - pGip->u32UpdateIntervalNS; + i = supdrvGipFindOrAllocCpuIndexForCpuId(pGip, idCpu); + + supdrvGipInitCpu(pGip, &pGip->aCPUs[i], u64NanoTS, pGip->u64CpuHz); + + idApic = supdrvGipGetApicIdSlow(); + ASMAtomicWriteU16(&pGip->aCPUs[i].idApic, idApic); + ASMAtomicWriteS16(&pGip->aCPUs[i].iCpuSet, (int16_t)iCpuSet); + ASMAtomicWriteSize(&pGip->aCPUs[i].idCpu, idCpu); + + pGip->aCPUs[i].iCpuGroup = 0; + pGip->aCPUs[i].iCpuGroupMember = iCpuSet; +#ifdef RT_OS_WINDOWS + supdrvOSGipInitGroupBitsForCpu(pDevExt, pGip, &pGip->aCPUs[i]); +#endif + + /* + * Update the APIC ID and CPU set index mappings. + */ + if (idApic < RT_ELEMENTS(pGip->aiCpuFromApicId)) + ASMAtomicWriteU16(&pGip->aiCpuFromApicId[idApic], i); + else + LogRelMax(64, ("supdrvGipMpEventOnlineOrInitOnCpu: idApic=%#x is out of bounds (%#zx, i=%u, iCpuSet=%d)\n", + idApic, RT_ELEMENTS(pGip->aiCpuFromApicId), i, iCpuSet)); + if ((unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)) + ASMAtomicWriteU16(&pGip->aiCpuFromCpuSetIdx[iCpuSet], i); + else + LogRelMax(64, ("supdrvGipMpEventOnlineOrInitOnCpu: iCpuSet=%d is out of bounds (%#zx, i=%u, idApic=%d)\n", + iCpuSet, RT_ELEMENTS(pGip->aiCpuFromApicId), i, idApic)); + + /* Add this CPU to this set of CPUs we need to calculate the TSC-delta for. */ + RTCpuSetAddByIndex(&pDevExt->TscDeltaCpuSet, RTMpCpuIdToSetIndex(idCpu)); + + /* Update the Mp online/offline counter. */ + ASMAtomicIncU32(&pDevExt->cMpOnOffEvents); + + /* Commit it. */ + ASMAtomicWriteSize(&pGip->aCPUs[i].enmState, SUPGIPCPUSTATE_ONLINE); + + RTSpinlockRelease(pDevExt->hGipSpinlock); +} + + +/** + * RTMpOnSpecific callback wrapper for supdrvGipMpEventOnlineOrInitOnCpu(). + * + * @param idCpu The CPU ID we are running on. + * @param pvUser1 Opaque pointer to the device instance data. + * @param pvUser2 Not used. + */ +static DECLCALLBACK(void) supdrvGipMpEventOnlineCallback(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser1; + NOREF(pvUser2); + supdrvGipMpEventOnlineOrInitOnCpu(pDevExt, idCpu); +} + + +/** + * The CPU should be accounted as offline, update the GIP accordingly. + * + * This is used by supdrvGipMpEvent. + * + * @param pDevExt The device extension. + * @param idCpu The CPU ID. + */ +static void supdrvGipMpEventOffline(PSUPDRVDEVEXT pDevExt, RTCPUID idCpu) +{ + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + int iCpuSet; + unsigned i; + + AssertPtrReturnVoid(pGip); + RTSpinlockAcquire(pDevExt->hGipSpinlock); + + iCpuSet = RTMpCpuIdToSetIndex(idCpu); + AssertReturnVoid(iCpuSet >= 0); + + i = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + AssertReturnVoid(i < pGip->cCpus); + AssertReturnVoid(pGip->aCPUs[i].idCpu == idCpu); + + Assert(RTCpuSetIsMemberByIndex(&pGip->PossibleCpuSet, iCpuSet)); + RTCpuSetDelByIndex(&pGip->OnlineCpuSet, iCpuSet); + + /* Update the Mp online/offline counter. */ + ASMAtomicIncU32(&pDevExt->cMpOnOffEvents); + + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { + /* Reset the TSC delta, we will recalculate it lazily. */ + ASMAtomicWriteS64(&pGip->aCPUs[i].i64TSCDelta, INT64_MAX); + /* Remove this CPU from the set of CPUs that we have obtained the TSC deltas. */ + RTCpuSetDelByIndex(&pDevExt->TscDeltaObtainedCpuSet, iCpuSet); + } + + /* Commit it. */ + ASMAtomicWriteSize(&pGip->aCPUs[i].enmState, SUPGIPCPUSTATE_OFFLINE); + + RTSpinlockRelease(pDevExt->hGipSpinlock); +} + + +/** + * Multiprocessor event notification callback. + * + * This is used to make sure that the GIP master gets passed on to + * another CPU. It also updates the associated CPU data. + * + * @param enmEvent The event. + * @param idCpu The cpu it applies to. + * @param pvUser Pointer to the device extension. + */ +static DECLCALLBACK(void) supdrvGipMpEvent(RTMPEVENT enmEvent, RTCPUID idCpu, void *pvUser) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + + if (pGip) + { + RTTHREADPREEMPTSTATE PreemptState = RTTHREADPREEMPTSTATE_INITIALIZER; + switch (enmEvent) + { + case RTMPEVENT_ONLINE: + { + RTThreadPreemptDisable(&PreemptState); + if (idCpu == RTMpCpuId()) + { + supdrvGipMpEventOnlineOrInitOnCpu(pDevExt, idCpu); + RTThreadPreemptRestore(&PreemptState); + } + else + { + RTThreadPreemptRestore(&PreemptState); + RTMpOnSpecific(idCpu, supdrvGipMpEventOnlineCallback, pDevExt, NULL /* pvUser2 */); + } + + /* + * Recompute TSC-delta for the newly online'd CPU. + */ + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + supdrvTscDeltaThreadStartMeasurement(pDevExt, false /* fForceAll */); +#else + uint32_t iCpu = supdrvGipFindOrAllocCpuIndexForCpuId(pGip, idCpu); + supdrvTscMeasureDeltaOne(pDevExt, iCpu); +#endif + } + break; + } + + case RTMPEVENT_OFFLINE: + supdrvGipMpEventOffline(pDevExt, idCpu); + break; + } + } + + /* + * Make sure there is a master GIP. + */ + if (enmEvent == RTMPEVENT_OFFLINE) + { + RTCPUID idGipMaster = ASMAtomicReadU32(&pDevExt->idGipMaster); + if (idGipMaster == idCpu) + { + /* + * The GIP master is going offline, find a new one. + */ + bool fIgnored; + unsigned i; + RTCPUID idNewGipMaster = NIL_RTCPUID; + RTCPUSET OnlineCpus; + RTMpGetOnlineSet(&OnlineCpus); + + for (i = 0; i < RTCPUSET_MAX_CPUS; i++) + if (RTCpuSetIsMemberByIndex(&OnlineCpus, i)) + { + RTCPUID idCurCpu = RTMpCpuIdFromSetIndex(i); + if (idCurCpu != idGipMaster) + { + idNewGipMaster = idCurCpu; + break; + } + } + + Log(("supdrvGipMpEvent: Gip master %#lx -> %#lx\n", (long)idGipMaster, (long)idNewGipMaster)); + ASMAtomicCmpXchgSize(&pDevExt->idGipMaster, idNewGipMaster, idGipMaster, fIgnored); + NOREF(fIgnored); + } + } +} + + +/** + * On CPU initialization callback for RTMpOnAll. + * + * @param idCpu The CPU ID. + * @param pvUser1 The device extension. + * @param pvUser2 The GIP. + */ +static DECLCALLBACK(void) supdrvGipInitOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* This is good enough, even though it will update some of the globals a + bit to much. */ + supdrvGipMpEventOnlineOrInitOnCpu((PSUPDRVDEVEXT)pvUser1, idCpu); + NOREF(pvUser2); +} + + +/** + * Callback used by supdrvDetermineAsyncTSC to read the TSC on a CPU. + * + * @param idCpu Ignored. + * @param pvUser1 Where to put the TSC. + * @param pvUser2 Ignored. + */ +static DECLCALLBACK(void) supdrvGipInitDetermineAsyncTscWorker(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + Assert(RTMpCpuIdToSetIndex(idCpu) == (intptr_t)pvUser2); + ASMAtomicWriteU64((uint64_t volatile *)pvUser1, ASMReadTSC()); + RT_NOREF2(idCpu, pvUser2); +} + + +/** + * Determine if Async GIP mode is required because of TSC drift. + * + * When using the default/normal timer code it is essential that the time stamp counter + * (TSC) runs never backwards, that is, a read operation to the counter should return + * a bigger value than any previous read operation. This is guaranteed by the latest + * AMD CPUs and by newer Intel CPUs which never enter the C2 state (P4). In any other + * case we have to choose the asynchronous timer mode. + * + * @param poffMin Pointer to the determined difference between different + * cores (optional, can be NULL). + * @return false if the time stamp counters appear to be synchronized, true otherwise. + */ +static bool supdrvGipInitDetermineAsyncTsc(uint64_t *poffMin) +{ + /* + * Just iterate all the cpus 8 times and make sure that the TSC is + * ever increasing. We don't bother taking TSC rollover into account. + */ + int iEndCpu = RTMpGetArraySize(); + int iCpu; + int cLoops = 8; + bool fAsync = false; + int rc = VINF_SUCCESS; + uint64_t offMax = 0; + uint64_t offMin = ~(uint64_t)0; + uint64_t PrevTsc = ASMReadTSC(); + + while (cLoops-- > 0) + { + for (iCpu = 0; iCpu < iEndCpu; iCpu++) + { + uint64_t CurTsc; + rc = RTMpOnSpecific(RTMpCpuIdFromSetIndex(iCpu), supdrvGipInitDetermineAsyncTscWorker, + &CurTsc, (void *)(uintptr_t)iCpu); + if (RT_SUCCESS(rc)) + { + if (CurTsc <= PrevTsc) + { + fAsync = true; + offMin = offMax = PrevTsc - CurTsc; + Log(("supdrvGipInitDetermineAsyncTsc: iCpu=%d cLoops=%d CurTsc=%llx PrevTsc=%llx\n", + iCpu, cLoops, CurTsc, PrevTsc)); + break; + } + + /* Gather statistics (except the first time). */ + if (iCpu != 0 || cLoops != 7) + { + uint64_t off = CurTsc - PrevTsc; + if (off < offMin) + offMin = off; + if (off > offMax) + offMax = off; + Log2(("%d/%d: off=%llx\n", cLoops, iCpu, off)); + } + + /* Next */ + PrevTsc = CurTsc; + } + else if (rc == VERR_NOT_SUPPORTED) + break; + else + AssertMsg(rc == VERR_CPU_NOT_FOUND || rc == VERR_CPU_OFFLINE, ("%d\n", rc)); + } + + /* broke out of the loop. */ + if (iCpu < iEndCpu) + break; + } + + if (poffMin) + *poffMin = offMin; /* Almost RTMpOnSpecific profiling. */ + Log(("supdrvGipInitDetermineAsyncTsc: returns %d; iEndCpu=%d rc=%d offMin=%llx offMax=%llx\n", + fAsync, iEndCpu, rc, offMin, offMax)); +#if !defined(RT_OS_SOLARIS) && !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) + OSDBGPRINT(("vboxdrv: fAsync=%d offMin=%#lx offMax=%#lx\n", fAsync, (long)offMin, (long)offMax)); +#endif + return fAsync; +} + + +/** + * supdrvGipInit() worker that determines the GIP TSC mode. + * + * @returns The most suitable TSC mode. + * @param pDevExt Pointer to the device instance data. + */ +static SUPGIPMODE supdrvGipInitDetermineTscMode(PSUPDRVDEVEXT pDevExt) +{ + uint64_t u64DiffCoresIgnored; + uint32_t uEAX, uEBX, uECX, uEDX; + + /* + * Establish whether the CPU advertises TSC as invariant, we need that in + * a couple of places below. + */ + bool fInvariantTsc = false; + if (ASMHasCpuId()) + { + uEAX = ASMCpuId_EAX(0x80000000); + if (RTX86IsValidExtRange(uEAX) && uEAX >= 0x80000007) + { + uEDX = ASMCpuId_EDX(0x80000007); + if (uEDX & X86_CPUID_AMD_ADVPOWER_EDX_TSCINVAR) + fInvariantTsc = true; + } + } + + /* + * On single CPU systems, we don't need to consider ASYNC mode. + */ + if (RTMpGetCount() <= 1) + return fInvariantTsc ? SUPGIPMODE_INVARIANT_TSC : SUPGIPMODE_SYNC_TSC; + + /* + * Allow the user and/or OS specific bits to force async mode. + */ + if (supdrvOSGetForcedAsyncTscMode(pDevExt)) + return SUPGIPMODE_ASYNC_TSC; + + /* + * Use invariant mode if the CPU says TSC is invariant. + */ + if (fInvariantTsc) + return SUPGIPMODE_INVARIANT_TSC; + + /* + * TSC is not invariant and we're on SMP, this presents two problems: + * + * (1) There might be a skew between the CPU, so that cpu0 + * returns a TSC that is slightly different from cpu1. + * This screw may be due to (2), bad TSC initialization + * or slightly different TSC rates. + * + * (2) Power management (and other things) may cause the TSC + * to run at a non-constant speed, and cause the speed + * to be different on the cpus. This will result in (1). + * + * If any of the above is detected, we will have to use ASYNC mode. + */ + /* (1). Try check for current differences between the cpus. */ + if (supdrvGipInitDetermineAsyncTsc(&u64DiffCoresIgnored)) + return SUPGIPMODE_ASYNC_TSC; + + /* (2) If it's an AMD CPU with power management, we won't trust its TSC. */ + ASMCpuId(0, &uEAX, &uEBX, &uECX, &uEDX); + if ( RTX86IsValidStdRange(uEAX) + && (RTX86IsAmdCpu(uEBX, uECX, uEDX) || RTX86IsHygonCpu(uEBX, uECX, uEDX)) ) + { + /* Check for APM support. */ + uEAX = ASMCpuId_EAX(0x80000000); + if (RTX86IsValidExtRange(uEAX) && uEAX >= 0x80000007) + { + uEDX = ASMCpuId_EDX(0x80000007); + if (uEDX & 0x3e) /* STC|TM|THERMTRIP|VID|FID. Ignore TS. */ + return SUPGIPMODE_ASYNC_TSC; + } + } + + return SUPGIPMODE_SYNC_TSC; +} + + +/** + * Initializes per-CPU GIP information. + * + * @param pGip Pointer to the GIP. + * @param pCpu Pointer to which GIP CPU to initialize. + * @param u64NanoTS The current nanosecond timestamp. + * @param uCpuHz The CPU frequency to set, 0 if the caller doesn't know. + */ +static void supdrvGipInitCpu(PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pCpu, uint64_t u64NanoTS, uint64_t uCpuHz) +{ + pCpu->u32TransactionId = 2; + pCpu->u64NanoTS = u64NanoTS; + pCpu->u64TSC = ASMReadTSC(); + pCpu->u64TSCSample = GIP_TSC_DELTA_RSVD; + pCpu->i64TSCDelta = pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED ? INT64_MAX : 0; + + ASMAtomicWriteSize(&pCpu->enmState, SUPGIPCPUSTATE_INVALID); + ASMAtomicWriteU32(&pCpu->idCpu, NIL_RTCPUID); + ASMAtomicWriteS16(&pCpu->iCpuSet, -1); + ASMAtomicWriteU16(&pCpu->iCpuGroup, 0); + ASMAtomicWriteU16(&pCpu->iCpuGroupMember, UINT16_MAX); + ASMAtomicWriteU16(&pCpu->idApic, UINT16_MAX); + ASMAtomicWriteU32(&pCpu->iReservedForNumaNode, 0); + + /* + * The first time we're called, we don't have a CPU frequency handy, + * so pretend it's a 4 GHz CPU. On CPUs that are online, we'll get + * called again and at that point we have a more plausible CPU frequency + * value handy. The frequency history will also be adjusted again on + * the 2nd timer callout (maybe we can skip that now?). + */ + if (!uCpuHz) + { + pCpu->u64CpuHz = _4G - 1; + pCpu->u32UpdateIntervalTSC = (uint32_t)((_4G - 1) / pGip->u32UpdateHz); + } + else + { + pCpu->u64CpuHz = uCpuHz; + pCpu->u32UpdateIntervalTSC = (uint32_t)(uCpuHz / pGip->u32UpdateHz); + } + pCpu->au32TSCHistory[0] + = pCpu->au32TSCHistory[1] + = pCpu->au32TSCHistory[2] + = pCpu->au32TSCHistory[3] + = pCpu->au32TSCHistory[4] + = pCpu->au32TSCHistory[5] + = pCpu->au32TSCHistory[6] + = pCpu->au32TSCHistory[7] + = pCpu->u32UpdateIntervalTSC; +} + + +/** + * Initializes the GIP data. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param pGip Pointer to the read-write kernel mapping of the GIP. + * @param HCPhys The physical address of the GIP. + * @param u64NanoTS The current nanosecond timestamp. + * @param uUpdateHz The update frequency. + * @param uUpdateIntervalNS The update interval in nanoseconds. + * @param cCpus The CPU count. + * @param cbGipCpuGroups The supdrvOSGipGetGroupTableSize return value we + * used when allocating the GIP structure. + */ +static int supdrvGipInit(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, RTHCPHYS HCPhys, + uint64_t u64NanoTS, unsigned uUpdateHz, unsigned uUpdateIntervalNS, + unsigned cCpus, size_t cbGipCpuGroups) +{ + size_t const cbGip = RT_ALIGN_Z(RT_UOFFSETOF_DYN(SUPGLOBALINFOPAGE, aCPUs[cCpus]) + cbGipCpuGroups, PAGE_SIZE); + unsigned i; +#ifdef DEBUG_DARWIN_GIP + OSDBGPRINT(("supdrvGipInit: pGip=%p HCPhys=%lx u64NanoTS=%llu uUpdateHz=%d cCpus=%u\n", pGip, (long)HCPhys, u64NanoTS, uUpdateHz, cCpus)); +#else + LogFlow(("supdrvGipInit: pGip=%p HCPhys=%lx u64NanoTS=%llu uUpdateHz=%d cCpus=%u\n", pGip, (long)HCPhys, u64NanoTS, uUpdateHz, cCpus)); +#endif + + /* + * Initialize the structure. + */ + memset(pGip, 0, cbGip); + + pGip->u32Magic = SUPGLOBALINFOPAGE_MAGIC; + pGip->u32Version = SUPGLOBALINFOPAGE_VERSION; + pGip->u32Mode = supdrvGipInitDetermineTscMode(pDevExt); + if ( pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC + /*|| pGip->u32Mode == SUPGIPMODE_SYNC_TSC */) + pGip->enmUseTscDelta = supdrvOSAreTscDeltasInSync() /* Allow OS override (windows). */ + ? SUPGIPUSETSCDELTA_ZERO_CLAIMED : SUPGIPUSETSCDELTA_PRACTICALLY_ZERO /* downgrade later */; + else + pGip->enmUseTscDelta = SUPGIPUSETSCDELTA_NOT_APPLICABLE; + pGip->cCpus = (uint16_t)cCpus; + pGip->cPages = (uint16_t)(cbGip / PAGE_SIZE); + pGip->u32UpdateHz = uUpdateHz; + pGip->u32UpdateIntervalNS = uUpdateIntervalNS; + pGip->fGetGipCpu = SUPGIPGETCPU_APIC_ID; + RTCpuSetEmpty(&pGip->OnlineCpuSet); + RTCpuSetEmpty(&pGip->PresentCpuSet); + RTMpGetSet(&pGip->PossibleCpuSet); + pGip->cOnlineCpus = RTMpGetOnlineCount(); + pGip->cPresentCpus = RTMpGetPresentCount(); + pGip->cPossibleCpus = RTMpGetCount(); + pGip->cPossibleCpuGroups = 1; + pGip->idCpuMax = RTMpGetMaxCpuId(); + for (i = 0; i < RT_ELEMENTS(pGip->aiCpuFromApicId); i++) + pGip->aiCpuFromApicId[i] = UINT16_MAX; + for (i = 0; i < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx); i++) + pGip->aiCpuFromCpuSetIdx[i] = UINT16_MAX; + for (i = 0; i < RT_ELEMENTS(pGip->aoffCpuGroup); i++) + pGip->aoffCpuGroup[i] = UINT32_MAX; + for (i = 0; i < cCpus; i++) + supdrvGipInitCpu(pGip, &pGip->aCPUs[i], u64NanoTS, 0 /*uCpuHz*/); +#ifdef RT_OS_WINDOWS + int rc = supdrvOSInitGipGroupTable(pDevExt, pGip, cbGipCpuGroups); + AssertRCReturn(rc, rc); +#endif + + /* + * Link it to the device extension. + */ + pDevExt->pGip = pGip; + pDevExt->HCPhysGip = HCPhys; + pDevExt->cGipUsers = 0; + + return VINF_SUCCESS; +} + + +/** + * Creates the GIP. + * + * @returns VBox status code. + * @param pDevExt Instance data. GIP stuff may be updated. + */ +int VBOXCALL supdrvGipCreate(PSUPDRVDEVEXT pDevExt) +{ + PSUPGLOBALINFOPAGE pGip; + size_t cbGip; + size_t cbGipCpuGroups; + RTHCPHYS HCPhysGip; + uint32_t u32SystemResolution; + uint32_t u32Interval; + uint32_t u32MinInterval; + uint32_t uMod; + unsigned cCpus; + int rc; + + LogFlow(("supdrvGipCreate:\n")); + + /* + * Assert order. + */ + Assert(pDevExt->u32SystemTimerGranularityGrant == 0); + Assert(pDevExt->GipMemObj == NIL_RTR0MEMOBJ); + Assert(!pDevExt->pGipTimer); +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + Assert(pDevExt->mtxGip != NIL_RTSEMMUTEX); + Assert(pDevExt->mtxTscDelta != NIL_RTSEMMUTEX); +#else + Assert(pDevExt->mtxGip != NIL_RTSEMFASTMUTEX); + Assert(pDevExt->mtxTscDelta != NIL_RTSEMFASTMUTEX); +#endif + + /* + * Check the CPU count. + */ + cCpus = RTMpGetArraySize(); + if (cCpus > RT_MIN(RTCPUSET_MAX_CPUS, RT_ELEMENTS(pGip->aiCpuFromApicId))) + { + SUPR0Printf("VBoxDrv: Too many CPUs (%u) for the GIP (max %u)\n", cCpus, RT_MIN(RTCPUSET_MAX_CPUS, RT_ELEMENTS(pGip->aiCpuFromApicId))); + return VERR_TOO_MANY_CPUS; + } + + /* + * Allocate a contiguous set of pages with a default kernel mapping. + */ +#ifdef RT_OS_WINDOWS + cbGipCpuGroups = supdrvOSGipGetGroupTableSize(pDevExt); +#else + cbGipCpuGroups = 0; +#endif + cbGip = RT_UOFFSETOF_DYN(SUPGLOBALINFOPAGE, aCPUs[cCpus]) + cbGipCpuGroups; + rc = RTR0MemObjAllocCont(&pDevExt->GipMemObj, cbGip, false /*fExecutable*/); + if (RT_FAILURE(rc)) + { + OSDBGPRINT(("supdrvGipCreate: failed to allocate the GIP page. rc=%d\n", rc)); + return rc; + } + pGip = (PSUPGLOBALINFOPAGE)RTR0MemObjAddress(pDevExt->GipMemObj); AssertPtr(pGip); + HCPhysGip = RTR0MemObjGetPagePhysAddr(pDevExt->GipMemObj, 0); Assert(HCPhysGip != NIL_RTHCPHYS); + + /* + * Find a reasonable update interval and initialize the structure. + */ + supdrvGipRequestHigherTimerFrequencyFromSystem(pDevExt); + /** @todo figure out why using a 100Ms interval upsets timekeeping in VMs. + * See @bugref{6710}. */ + u32MinInterval = RT_NS_10MS; + u32SystemResolution = RTTimerGetSystemGranularity(); + u32Interval = u32MinInterval; + uMod = u32MinInterval % u32SystemResolution; + if (uMod) + u32Interval += u32SystemResolution - uMod; + + rc = supdrvGipInit(pDevExt, pGip, HCPhysGip, RTTimeSystemNanoTS(), RT_NS_1SEC / u32Interval /*=Hz*/, u32Interval, + cCpus, cbGipCpuGroups); + + /* + * Important sanity check... (Sets rc) + */ + if (RT_UNLIKELY( pGip->enmUseTscDelta == SUPGIPUSETSCDELTA_ZERO_CLAIMED + && pGip->u32Mode == SUPGIPMODE_ASYNC_TSC + && !supdrvOSGetForcedAsyncTscMode(pDevExt))) + { + OSDBGPRINT(("supdrvGipCreate: Host-OS/user claims the TSC-deltas are zero but we detected async. TSC! Bad.\n")); + rc = VERR_INTERNAL_ERROR_2; + } + + /* It doesn't make sense to do TSC-delta detection on systems we detect as async. */ + AssertStmt( pGip->u32Mode != SUPGIPMODE_ASYNC_TSC + || pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ZERO_CLAIMED, + rc = VERR_INTERNAL_ERROR_3); + + /* + * Do the TSC frequency measurements. + * + * If we're in invariant TSC mode, just to a quick preliminary measurement + * that the TSC-delta measurement code can use to yield cross calls. + * + * If we're in any of the other two modes, neither which require MP init, + * notifications or deltas for the job, do the full measurement now so + * that supdrvGipInitOnCpu() can populate the TSC interval and history + * array with more reasonable values. + */ + if (RT_SUCCESS(rc)) + { + if (pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC) + { + rc = supdrvGipInitMeasureTscFreq(pGip, true /*fRough*/); /* cannot fail */ + supdrvGipInitStartTimerForRefiningInvariantTscFreq(pDevExt); + } + else + rc = supdrvGipInitMeasureTscFreq(pGip, false /*fRough*/); + if (RT_SUCCESS(rc)) + { + /* + * Start TSC-delta measurement thread before we start getting MP + * events that will try kick it into action (includes the + * RTMpOnAll/supdrvGipInitOnCpu call below). + */ + RTCpuSetEmpty(&pDevExt->TscDeltaCpuSet); + RTCpuSetEmpty(&pDevExt->TscDeltaObtainedCpuSet); +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + rc = supdrvTscDeltaThreadInit(pDevExt); +#endif + if (RT_SUCCESS(rc)) + { + rc = RTMpNotificationRegister(supdrvGipMpEvent, pDevExt); + if (RT_SUCCESS(rc)) + { + /* + * Do GIP initialization on all online CPUs. Wake up the + * TSC-delta thread afterwards. + */ + rc = RTMpOnAll(supdrvGipInitOnCpu, pDevExt, pGip); + if (RT_SUCCESS(rc)) + { +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + supdrvTscDeltaThreadStartMeasurement(pDevExt, true /* fForceAll */); +#else + uint16_t iCpu; + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { + /* + * Measure the TSC deltas now that we have MP notifications. + */ + int cTries = 5; + do + { + rc = supdrvTscMeasureInitialDeltas(pDevExt); + if ( rc != VERR_TRY_AGAIN + && rc != VERR_CPU_OFFLINE) + break; + } while (--cTries > 0); + for (iCpu = 0; iCpu < pGip->cCpus; iCpu++) + Log(("supdrvTscDeltaInit: cpu[%u] delta %lld\n", iCpu, pGip->aCPUs[iCpu].i64TSCDelta)); + } + else + { + for (iCpu = 0; iCpu < pGip->cCpus; iCpu++) + AssertMsg(!pGip->aCPUs[iCpu].i64TSCDelta, ("iCpu=%u %lld mode=%d\n", iCpu, pGip->aCPUs[iCpu].i64TSCDelta, pGip->u32Mode)); + } + if (RT_SUCCESS(rc)) +#endif + { + /* + * Create the timer. + * If CPU_ALL isn't supported we'll have to fall back to synchronous mode. + */ + if (pGip->u32Mode == SUPGIPMODE_ASYNC_TSC) + { + rc = RTTimerCreateEx(&pDevExt->pGipTimer, u32Interval, RTTIMER_FLAGS_CPU_ALL, + supdrvGipAsyncTimer, pDevExt); + if (rc == VERR_NOT_SUPPORTED) + { + OSDBGPRINT(("supdrvGipCreate: omni timer not supported, falling back to synchronous mode\n")); + pGip->u32Mode = SUPGIPMODE_SYNC_TSC; + } + } + if (pGip->u32Mode != SUPGIPMODE_ASYNC_TSC) + rc = RTTimerCreateEx(&pDevExt->pGipTimer, u32Interval, 0 /* fFlags */, + supdrvGipSyncAndInvariantTimer, pDevExt); + if (RT_SUCCESS(rc)) + { + /* + * We're good. + */ + Log(("supdrvGipCreate: %u ns interval.\n", u32Interval)); + supdrvGipReleaseHigherTimerFrequencyFromSystem(pDevExt); + + g_pSUPGlobalInfoPage = pGip; + return VINF_SUCCESS; + } + + OSDBGPRINT(("supdrvGipCreate: failed create GIP timer at %u ns interval. rc=%Rrc\n", u32Interval, rc)); + Assert(!pDevExt->pGipTimer); + } + } + else + OSDBGPRINT(("supdrvGipCreate: RTMpOnAll failed. rc=%Rrc\n", rc)); + } + else + OSDBGPRINT(("supdrvGipCreate: failed to register MP event notfication. rc=%Rrc\n", rc)); + } + else + OSDBGPRINT(("supdrvGipCreate: supdrvTscDeltaInit failed. rc=%Rrc\n", rc)); + } + else + OSDBGPRINT(("supdrvGipCreate: supdrvTscMeasureInitialDeltas failed. rc=%Rrc\n", rc)); + } + + /* Releases timer frequency increase too. */ + supdrvGipDestroy(pDevExt); + return rc; +} + + +/** + * Invalidates the GIP data upon termination. + * + * @param pGip Pointer to the read-write kernel mapping of the GIP. + */ +static void supdrvGipTerm(PSUPGLOBALINFOPAGE pGip) +{ + unsigned i; + pGip->u32Magic = 0; + for (i = 0; i < pGip->cCpus; i++) + { + pGip->aCPUs[i].u64NanoTS = 0; + pGip->aCPUs[i].u64TSC = 0; + pGip->aCPUs[i].iTSCHistoryHead = 0; + pGip->aCPUs[i].u64TSCSample = 0; + pGip->aCPUs[i].i64TSCDelta = INT64_MAX; + } +} + + +/** + * Terminates the GIP. + * + * @param pDevExt Instance data. GIP stuff may be updated. + */ +void VBOXCALL supdrvGipDestroy(PSUPDRVDEVEXT pDevExt) +{ + int rc; +#ifdef DEBUG_DARWIN_GIP + OSDBGPRINT(("supdrvGipDestroy: pDevExt=%p pGip=%p pGipTimer=%p GipMemObj=%p\n", pDevExt, + pDevExt->GipMemObj != NIL_RTR0MEMOBJ ? RTR0MemObjAddress(pDevExt->GipMemObj) : NULL, + pDevExt->pGipTimer, pDevExt->GipMemObj)); +#endif + + /* + * Stop receiving MP notifications before tearing anything else down. + */ + RTMpNotificationDeregister(supdrvGipMpEvent, pDevExt); + +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + /* + * Terminate the TSC-delta measurement thread and resources. + */ + supdrvTscDeltaTerm(pDevExt); +#endif + + /* + * Destroy the TSC-refinement timer. + */ + if (pDevExt->pInvarTscRefineTimer) + { + RTTimerDestroy(pDevExt->pInvarTscRefineTimer); + pDevExt->pInvarTscRefineTimer = NULL; + } + + /* + * Invalid the GIP data. + */ + if (pDevExt->pGip) + { + supdrvGipTerm(pDevExt->pGip); + pDevExt->pGip = NULL; + } + g_pSUPGlobalInfoPage = NULL; + + /* + * Destroy the timer and free the GIP memory object. + */ + if (pDevExt->pGipTimer) + { + rc = RTTimerDestroy(pDevExt->pGipTimer); AssertRC(rc); + pDevExt->pGipTimer = NULL; + } + + if (pDevExt->GipMemObj != NIL_RTR0MEMOBJ) + { + rc = RTR0MemObjFree(pDevExt->GipMemObj, true /* free mappings */); AssertRC(rc); + pDevExt->GipMemObj = NIL_RTR0MEMOBJ; + } + + /* + * Finally, make sure we've release the system timer resolution request + * if one actually succeeded and is still pending. + */ + supdrvGipReleaseHigherTimerFrequencyFromSystem(pDevExt); +} + + + + +/* + * + * + * GIP Update Timer Related Code + * GIP Update Timer Related Code + * GIP Update Timer Related Code + * + * + */ + + +/** + * Worker routine for supdrvGipUpdate() and supdrvGipUpdatePerCpu() that + * updates all the per cpu data except the transaction id. + * + * @param pDevExt The device extension. + * @param pGipCpu Pointer to the per cpu data. + * @param u64NanoTS The current time stamp. + * @param u64TSC The current TSC. + * @param iTick The current timer tick. + * + * @remarks Can be called with interrupts disabled! + */ +static void supdrvGipDoUpdateCpu(PSUPDRVDEVEXT pDevExt, PSUPGIPCPU pGipCpu, uint64_t u64NanoTS, uint64_t u64TSC, uint64_t iTick) +{ + uint64_t u64TSCDelta; + bool fUpdateCpuHz; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + AssertPtrReturnVoid(pGip); + + /* Delta between this and the previous update. */ + ASMAtomicUoWriteU32(&pGipCpu->u32PrevUpdateIntervalNS, (uint32_t)(u64NanoTS - pGipCpu->u64NanoTS)); + + /* + * Update the NanoTS. + */ + ASMAtomicWriteU64(&pGipCpu->u64NanoTS, u64NanoTS); + + /* + * Calc TSC delta. + */ + u64TSCDelta = u64TSC - pGipCpu->u64TSC; + ASMAtomicWriteU64(&pGipCpu->u64TSC, u64TSC); + + /* + * Determine if we need to update the CPU (TSC) frequency calculation. + * + * We don't need to keep recalculating the frequency when it's invariant, + * unless the special tstGIP-2 testing mode is enabled. + */ + fUpdateCpuHz = pGip->u32Mode != SUPGIPMODE_INVARIANT_TSC; + if (!(pGip->fFlags & SUPGIP_FLAGS_TESTING)) + { /* likely*/ } + else + { + uint32_t fGipFlags = pGip->fFlags; + if (fGipFlags & (SUPGIP_FLAGS_TESTING_ENABLE | SUPGIP_FLAGS_TESTING_START)) + { + if (fGipFlags & SUPGIP_FLAGS_TESTING_START) + { + /* Cache the TSC frequency before forcing updates due to test mode. */ + if (!fUpdateCpuHz) + pDevExt->uGipTestModeInvariantCpuHz = pGip->aCPUs[0].u64CpuHz; + ASMAtomicAndU32(&pGip->fFlags, ~SUPGIP_FLAGS_TESTING_START); + } + fUpdateCpuHz = true; + } + else if (fGipFlags & SUPGIP_FLAGS_TESTING_STOP) + { + /* Restore the cached TSC frequency if any. */ + if (!fUpdateCpuHz) + { + Assert(pDevExt->uGipTestModeInvariantCpuHz); + ASMAtomicWriteU64(&pGip->aCPUs[0].u64CpuHz, pDevExt->uGipTestModeInvariantCpuHz); + } + ASMAtomicAndU32(&pGip->fFlags, ~(SUPGIP_FLAGS_TESTING_STOP | SUPGIP_FLAGS_TESTING)); + } + } + + /* + * Calculate the CPU (TSC) frequency if necessary. + */ + if (fUpdateCpuHz) + { + uint64_t u64CpuHz; + uint32_t u32UpdateIntervalTSC; + uint32_t u32UpdateIntervalTSCSlack; + uint32_t u32TransactionId; + unsigned iTSCHistoryHead; + + if (u64TSCDelta >> 32) + { + u64TSCDelta = pGipCpu->u32UpdateIntervalTSC; + pGipCpu->cErrors++; + } + + /* + * On the 2nd and 3rd callout, reset the history with the current TSC + * interval since the values entered by supdrvGipInit are totally off. + * The interval on the 1st callout completely unreliable, the 2nd is a bit + * better, while the 3rd should be most reliable. + */ + /** @todo Could we drop this now that we initializes the history + * with nominal TSC frequency values? */ + u32TransactionId = pGipCpu->u32TransactionId; + if (RT_UNLIKELY( ( u32TransactionId == 5 + || u32TransactionId == 7) + && ( iTick == 2 + || iTick == 3) )) + { + unsigned i; + for (i = 0; i < RT_ELEMENTS(pGipCpu->au32TSCHistory); i++) + ASMAtomicUoWriteU32(&pGipCpu->au32TSCHistory[i], (uint32_t)u64TSCDelta); + } + + /* + * Validate the NanoTS deltas between timer fires with an arbitrary threshold of 0.5%. + * Wait until we have at least one full history since the above history reset. The + * assumption is that the majority of the previous history values will be tolerable. + * See @bugref{6710#c67}. + */ + /** @todo Could we drop the fudging there now that we initializes the history + * with nominal TSC frequency values? */ + if ( u32TransactionId > 23 /* 7 + (8 * 2) */ + && pGip->u32Mode != SUPGIPMODE_ASYNC_TSC) + { + uint32_t uNanoTsThreshold = pGip->u32UpdateIntervalNS / 200; + if ( pGipCpu->u32PrevUpdateIntervalNS > pGip->u32UpdateIntervalNS + uNanoTsThreshold + || pGipCpu->u32PrevUpdateIntervalNS < pGip->u32UpdateIntervalNS - uNanoTsThreshold) + { + uint32_t u32; + u32 = pGipCpu->au32TSCHistory[0]; + u32 += pGipCpu->au32TSCHistory[1]; + u32 += pGipCpu->au32TSCHistory[2]; + u32 += pGipCpu->au32TSCHistory[3]; + u32 >>= 2; + u64TSCDelta = pGipCpu->au32TSCHistory[4]; + u64TSCDelta += pGipCpu->au32TSCHistory[5]; + u64TSCDelta += pGipCpu->au32TSCHistory[6]; + u64TSCDelta += pGipCpu->au32TSCHistory[7]; + u64TSCDelta >>= 2; + u64TSCDelta += u32; + u64TSCDelta >>= 1; + } + } + + /* + * TSC History. + */ + Assert(RT_ELEMENTS(pGipCpu->au32TSCHistory) == 8); + iTSCHistoryHead = (pGipCpu->iTSCHistoryHead + 1) & 7; + ASMAtomicWriteU32(&pGipCpu->iTSCHistoryHead, iTSCHistoryHead); + ASMAtomicWriteU32(&pGipCpu->au32TSCHistory[iTSCHistoryHead], (uint32_t)u64TSCDelta); + + /* + * UpdateIntervalTSC = average of last 8,2,1 intervals depending on update HZ. + * + * On Windows, we have an occasional (but recurring) sour value that messed up + * the history but taking only 1 interval reduces the precision overall. + */ + if ( pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC + || pGip->u32UpdateHz >= 1000) + { + uint32_t u32; + u32 = pGipCpu->au32TSCHistory[0]; + u32 += pGipCpu->au32TSCHistory[1]; + u32 += pGipCpu->au32TSCHistory[2]; + u32 += pGipCpu->au32TSCHistory[3]; + u32 >>= 2; + u32UpdateIntervalTSC = pGipCpu->au32TSCHistory[4]; + u32UpdateIntervalTSC += pGipCpu->au32TSCHistory[5]; + u32UpdateIntervalTSC += pGipCpu->au32TSCHistory[6]; + u32UpdateIntervalTSC += pGipCpu->au32TSCHistory[7]; + u32UpdateIntervalTSC >>= 2; + u32UpdateIntervalTSC += u32; + u32UpdateIntervalTSC >>= 1; + + /* Value chosen for a 2GHz Athlon64 running linux 2.6.10/11. */ + u32UpdateIntervalTSCSlack = u32UpdateIntervalTSC >> 14; + } + else if (pGip->u32UpdateHz >= 90) + { + u32UpdateIntervalTSC = (uint32_t)u64TSCDelta; + u32UpdateIntervalTSC += pGipCpu->au32TSCHistory[(iTSCHistoryHead - 1) & 7]; + u32UpdateIntervalTSC >>= 1; + + /* value chosen on a 2GHz thinkpad running windows */ + u32UpdateIntervalTSCSlack = u32UpdateIntervalTSC >> 7; + } + else + { + u32UpdateIntervalTSC = (uint32_t)u64TSCDelta; + + /* This value hasn't be checked yet.. waiting for OS/2 and 33Hz timers.. :-) */ + u32UpdateIntervalTSCSlack = u32UpdateIntervalTSC >> 6; + } + ASMAtomicWriteU32(&pGipCpu->u32UpdateIntervalTSC, u32UpdateIntervalTSC + u32UpdateIntervalTSCSlack); + + /* + * CpuHz. + */ + u64CpuHz = ASMMult2xU32RetU64(u32UpdateIntervalTSC, RT_NS_1SEC); + u64CpuHz /= pGip->u32UpdateIntervalNS; + ASMAtomicWriteU64(&pGipCpu->u64CpuHz, u64CpuHz); + } +} + + +/** + * Updates the GIP. + * + * @param pDevExt The device extension. + * @param u64NanoTS The current nanosecond timestamp. + * @param u64TSC The current TSC timestamp. + * @param idCpu The CPU ID. + * @param iTick The current timer tick. + * + * @remarks Can be called with interrupts disabled! + */ +static void supdrvGipUpdate(PSUPDRVDEVEXT pDevExt, uint64_t u64NanoTS, uint64_t u64TSC, RTCPUID idCpu, uint64_t iTick) +{ + /* + * Determine the relevant CPU data. + */ + PSUPGIPCPU pGipCpu; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + AssertPtrReturnVoid(pGip); + + if (pGip->u32Mode != SUPGIPMODE_ASYNC_TSC) + pGipCpu = &pGip->aCPUs[0]; + else + { + unsigned iCpu; + uint32_t idApic = supdrvGipGetApicId(pGip); + if (RT_LIKELY(idApic < RT_ELEMENTS(pGip->aiCpuFromApicId))) + { /* likely */ } + else + return; + iCpu = pGip->aiCpuFromApicId[idApic]; + if (RT_LIKELY(iCpu < pGip->cCpus)) + { /* likely */ } + else + return; + pGipCpu = &pGip->aCPUs[iCpu]; + if (RT_LIKELY(pGipCpu->idCpu == idCpu)) + { /* likely */ } + else + return; + } + + /* + * Start update transaction. + */ + if (!(ASMAtomicIncU32(&pGipCpu->u32TransactionId) & 1)) + { + /* this can happen on win32 if we're taking to long and there are more CPUs around. shouldn't happen though. */ + AssertMsgFailed(("Invalid transaction id, %#x, not odd!\n", pGipCpu->u32TransactionId)); + ASMAtomicIncU32(&pGipCpu->u32TransactionId); + pGipCpu->cErrors++; + return; + } + + /* + * Recalc the update frequency every 0x800th time. + */ + if ( pGip->u32Mode != SUPGIPMODE_INVARIANT_TSC /* cuz we're not recalculating the frequency on invariant hosts. */ + && !(pGipCpu->u32TransactionId & (GIP_UPDATEHZ_RECALC_FREQ * 2 - 2))) + { + if (pGip->u64NanoTSLastUpdateHz) + { +#ifdef RT_ARCH_AMD64 /** @todo fix 64-bit div here to work on x86 linux. */ + uint64_t u64Delta = u64NanoTS - pGip->u64NanoTSLastUpdateHz; + uint32_t u32UpdateHz = (uint32_t)((RT_NS_1SEC_64 * GIP_UPDATEHZ_RECALC_FREQ) / u64Delta); + if (u32UpdateHz <= 2000 && u32UpdateHz >= 30) + { + /** @todo r=ramshankar: Changing u32UpdateHz might screw up TSC frequency + * calculation on non-invariant hosts if it changes the history decision + * taken in supdrvGipDoUpdateCpu(). */ + uint64_t u64Interval = u64Delta / GIP_UPDATEHZ_RECALC_FREQ; + ASMAtomicWriteU32(&pGip->u32UpdateHz, u32UpdateHz); + ASMAtomicWriteU32(&pGip->u32UpdateIntervalNS, (uint32_t)u64Interval); + } +#endif + } + ASMAtomicWriteU64(&pGip->u64NanoTSLastUpdateHz, u64NanoTS | 1); + } + + /* + * Update the data. + */ + supdrvGipDoUpdateCpu(pDevExt, pGipCpu, u64NanoTS, u64TSC, iTick); + + /* + * Complete transaction. + */ + ASMAtomicIncU32(&pGipCpu->u32TransactionId); +} + + +/** + * Updates the per cpu GIP data for the calling cpu. + * + * @param pDevExt The device extension. + * @param u64NanoTS The current nanosecond timestamp. + * @param u64TSC The current TSC timesaver. + * @param idCpu The CPU ID. + * @param idApic The APIC id for the CPU index. + * @param iTick The current timer tick. + * + * @remarks Can be called with interrupts disabled! + */ +static void supdrvGipUpdatePerCpu(PSUPDRVDEVEXT pDevExt, uint64_t u64NanoTS, uint64_t u64TSC, + RTCPUID idCpu, uint8_t idApic, uint64_t iTick) +{ + uint32_t iCpu; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + + /* + * Avoid a potential race when a CPU online notification doesn't fire on + * the onlined CPU but the tick creeps in before the event notification is + * run. + */ + if (RT_LIKELY(iTick != 1)) + { /* likely*/ } + else + { + iCpu = supdrvGipFindOrAllocCpuIndexForCpuId(pGip, idCpu); + if (pGip->aCPUs[iCpu].enmState == SUPGIPCPUSTATE_OFFLINE) + supdrvGipMpEventOnlineOrInitOnCpu(pDevExt, idCpu); + } + + iCpu = pGip->aiCpuFromApicId[idApic]; + if (RT_LIKELY(iCpu < pGip->cCpus)) + { + PSUPGIPCPU pGipCpu = &pGip->aCPUs[iCpu]; + if (pGipCpu->idCpu == idCpu) + { + /* + * Start update transaction. + */ + if (!(ASMAtomicIncU32(&pGipCpu->u32TransactionId) & 1)) + { + AssertMsgFailed(("Invalid transaction id, %#x, not odd!\n", pGipCpu->u32TransactionId)); + ASMAtomicIncU32(&pGipCpu->u32TransactionId); + pGipCpu->cErrors++; + return; + } + + /* + * Update the data. + */ + supdrvGipDoUpdateCpu(pDevExt, pGipCpu, u64NanoTS, u64TSC, iTick); + + /* + * Complete transaction. + */ + ASMAtomicIncU32(&pGipCpu->u32TransactionId); + } + } +} + + +/** + * Timer callback function for the sync and invariant GIP modes. + * + * @param pTimer The timer. + * @param pvUser Opaque pointer to the device extension. + * @param iTick The timer tick. + */ +static DECLCALLBACK(void) supdrvGipSyncAndInvariantTimer(PRTTIMER pTimer, void *pvUser, uint64_t iTick) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + RTCCUINTREG fEFlags = ASMIntDisableFlags(); /* No interruptions please (real problem on S10). */ + uint64_t u64TSC = ASMReadTSC(); + uint64_t u64NanoTS = RTTimeSystemNanoTS(); + RT_NOREF1(pTimer); + + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_PRACTICALLY_ZERO) + { + /* + * The calculations in supdrvGipUpdate() is somewhat timing sensitive, + * missing timer ticks is not an option for GIP because the GIP users + * will end up incrementing the time in 1ns per time getter call until + * there is a complete timer update. So, if the delta has yet to be + * calculated, we just pretend it is zero for now (the GIP users + * probably won't have it for a wee while either and will do the same). + * + * We could maybe on some platforms try cross calling a CPU with a + * working delta here, but it's not worth the hassle since the + * likelihood of this happening is really low. On Windows, Linux, and + * Solaris timers fire on the CPU they were registered/started on. + * Darwin timers doesn't necessarily (they are high priority threads). + */ + uint32_t iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + uint16_t iGipCpu = RT_LIKELY(iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)) + ? pGip->aiCpuFromCpuSetIdx[iCpuSet] : UINT16_MAX; + Assert(!ASMIntAreEnabled()); + if (RT_LIKELY(iGipCpu < pGip->cCpus)) + { + int64_t iTscDelta = pGip->aCPUs[iGipCpu].i64TSCDelta; + if (iTscDelta != INT64_MAX) + u64TSC -= iTscDelta; + } + } + + supdrvGipUpdate(pDevExt, u64NanoTS, u64TSC, NIL_RTCPUID, iTick); + + ASMSetFlags(fEFlags); +} + + +/** + * Timer callback function for async GIP mode. + * @param pTimer The timer. + * @param pvUser Opaque pointer to the device extension. + * @param iTick The timer tick. + */ +static DECLCALLBACK(void) supdrvGipAsyncTimer(PRTTIMER pTimer, void *pvUser, uint64_t iTick) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + RTCCUINTREG fEFlags = ASMIntDisableFlags(); /* No interruptions please (real problem on S10). */ + RTCPUID idCpu = RTMpCpuId(); + uint64_t u64TSC = ASMReadTSC(); + uint64_t NanoTS = RTTimeSystemNanoTS(); + RT_NOREF1(pTimer); + + /** @todo reset the transaction number and whatnot when iTick == 1. */ + if (pDevExt->idGipMaster == idCpu) + supdrvGipUpdate(pDevExt, NanoTS, u64TSC, idCpu, iTick); + else + supdrvGipUpdatePerCpu(pDevExt, NanoTS, u64TSC, idCpu, supdrvGipGetApicId(pDevExt->pGip), iTick); + + ASMSetFlags(fEFlags); +} + + + + +/* + * + * + * TSC Delta Measurements And Related Code + * TSC Delta Measurements And Related Code + * TSC Delta Measurements And Related Code + * + * + */ + + +/* + * Select TSC delta measurement algorithm. + */ +#if 0 +# define GIP_TSC_DELTA_METHOD_1 +#else +# define GIP_TSC_DELTA_METHOD_2 +#endif + +/** For padding variables to keep them away from other cache lines. Better too + * large than too small! + * @remarks Current AMD64 and x86 CPUs seems to use 64 bytes. There are claims + * that NetBurst had 128 byte cache lines while the 486 thru Pentium + * III had 32 bytes cache lines. */ +#define GIP_TSC_DELTA_CACHE_LINE_SIZE 128 + + +/** + * TSC delta measurement algorithm \#2 result entry. + */ +typedef struct SUPDRVTSCDELTAMETHOD2ENTRY +{ + uint32_t iSeqMine; + uint32_t iSeqOther; + uint64_t uTsc; +} SUPDRVTSCDELTAMETHOD2ENTRY; + +/** + * TSC delta measurement algorithm \#2 Data. + */ +typedef struct SUPDRVTSCDELTAMETHOD2 +{ + /** Padding to make sure the iCurSeqNo is in its own cache line. */ + uint64_t au64CacheLinePaddingBefore[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t)]; + /** The current sequence number of this worker. */ + uint32_t volatile iCurSeqNo; + /** Padding to make sure the iCurSeqNo is in its own cache line. */ + uint32_t au64CacheLinePaddingAfter[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint32_t) - 1]; + /** Result table. */ + SUPDRVTSCDELTAMETHOD2ENTRY aResults[64]; +} SUPDRVTSCDELTAMETHOD2; +/** Pointer to the data for TSC delta measurement algorithm \#2 .*/ +typedef SUPDRVTSCDELTAMETHOD2 *PSUPDRVTSCDELTAMETHOD2; + + +/** + * The TSC delta synchronization struct, version 2. + * + * The synchronization variable is completely isolated in its own cache line + * (provided our max cache line size estimate is correct). + */ +typedef struct SUPTSCDELTASYNC2 +{ + /** Padding to make sure the uVar1 is in its own cache line. */ + uint64_t au64CacheLinePaddingBefore[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t)]; + + /** The synchronization variable, holds values GIP_TSC_DELTA_SYNC_*. */ + volatile uint32_t uSyncVar; + /** Sequence synchronizing variable used for post 'GO' synchronization. */ + volatile uint32_t uSyncSeq; + + /** Padding to make sure the uVar1 is in its own cache line. */ + uint64_t au64CacheLinePaddingAfter[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t) - 2]; + + /** Start RDTSC value. Put here mainly to save stack space. */ + uint64_t uTscStart; + /** Copy of SUPDRVGIPTSCDELTARGS::cMaxTscTicks. */ + uint64_t cMaxTscTicks; +} SUPTSCDELTASYNC2; +AssertCompileSize(SUPTSCDELTASYNC2, GIP_TSC_DELTA_CACHE_LINE_SIZE * 2 + sizeof(uint64_t)); +typedef SUPTSCDELTASYNC2 *PSUPTSCDELTASYNC2; + +/** Prestart wait. */ +#define GIP_TSC_DELTA_SYNC2_PRESTART_WAIT UINT32_C(0x0ffe) +/** Prestart aborted. */ +#define GIP_TSC_DELTA_SYNC2_PRESTART_ABORT UINT32_C(0x0fff) +/** Ready (on your mark). */ +#define GIP_TSC_DELTA_SYNC2_READY UINT32_C(0x1000) +/** Steady (get set). */ +#define GIP_TSC_DELTA_SYNC2_STEADY UINT32_C(0x1001) +/** Go! */ +#define GIP_TSC_DELTA_SYNC2_GO UINT32_C(0x1002) +/** Used by the verification test. */ +#define GIP_TSC_DELTA_SYNC2_GO_GO UINT32_C(0x1003) + +/** We reached the time limit. */ +#define GIP_TSC_DELTA_SYNC2_TIMEOUT UINT32_C(0x1ffe) +/** The other party won't touch the sync struct ever again. */ +#define GIP_TSC_DELTA_SYNC2_FINAL UINT32_C(0x1fff) + + +/** + * Argument package/state passed by supdrvTscMeasureDeltaOne() to the RTMpOn + * callback worker. + * @todo add + */ +typedef struct SUPDRVGIPTSCDELTARGS +{ + /** The device extension. */ + PSUPDRVDEVEXT pDevExt; + /** Pointer to the GIP CPU array entry for the worker. */ + PSUPGIPCPU pWorker; + /** Pointer to the GIP CPU array entry for the master. */ + PSUPGIPCPU pMaster; + /** The maximum number of ticks to spend in supdrvTscMeasureDeltaCallback. + * (This is what we need a rough TSC frequency for.) */ + uint64_t cMaxTscTicks; + /** Used to abort synchronization setup. */ + bool volatile fAbortSetup; + + /** Padding to make sure the master variables live in its own cache lines. */ + uint64_t au64CacheLinePaddingBefore[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t)]; + + /** @name Master + * @{ */ + /** The time the master spent in the MP worker. */ + uint64_t cElapsedMasterTscTicks; + /** The iTry value when stopped at. */ + uint32_t iTry; + /** Set if the run timed out. */ + bool volatile fTimedOut; + /** Pointer to the master's synchronization struct (on stack). */ + PSUPTSCDELTASYNC2 volatile pSyncMaster; + /** Master data union. */ + union + { + /** Data (master) for delta verification. */ + struct + { + /** Verification test TSC values for the master. */ + uint64_t volatile auTscs[32]; + } Verify; + /** Data (master) for measurement method \#2. */ + struct + { + /** Data and sequence number. */ + SUPDRVTSCDELTAMETHOD2 Data; + /** The lag setting for the next run. */ + bool fLag; + /** Number of hits. */ + uint32_t cHits; + } M2; + } uMaster; + /** The verifier verdict, VINF_SUCCESS if ok, VERR_OUT_OF_RANGE if not, + * VERR_TRY_AGAIN on timeout. */ + int32_t rcVerify; +#ifdef TSCDELTA_VERIFY_WITH_STATS + /** The maximum difference between TSC read during delta verification. */ + int64_t cMaxVerifyTscTicks; + /** The minimum difference between two TSC reads during verification. */ + int64_t cMinVerifyTscTicks; + /** The bad TSC diff, worker relative to master (= worker - master). + * Negative value means the worker is behind the master. */ + int64_t iVerifyBadTscDiff; +#endif + /** @} */ + + /** Padding to make sure the worker variables live is in its own cache line. */ + uint64_t au64CacheLinePaddingBetween[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t)]; + + /** @name Proletarian + * @{ */ + /** Pointer to the worker's synchronization struct (on stack). */ + PSUPTSCDELTASYNC2 volatile pSyncWorker; + /** The time the worker spent in the MP worker. */ + uint64_t cElapsedWorkerTscTicks; + /** Worker data union. */ + union + { + /** Data (worker) for delta verification. */ + struct + { + /** Verification test TSC values for the worker. */ + uint64_t volatile auTscs[32]; + } Verify; + /** Data (worker) for measurement method \#2. */ + struct + { + /** Data and sequence number. */ + SUPDRVTSCDELTAMETHOD2 Data; + /** The lag setting for the next run (set by master). */ + bool fLag; + } M2; + } uWorker; + /** @} */ + + /** Padding to make sure the above is in its own cache line. */ + uint64_t au64CacheLinePaddingAfter[GIP_TSC_DELTA_CACHE_LINE_SIZE / sizeof(uint64_t)]; +} SUPDRVGIPTSCDELTARGS; +typedef SUPDRVGIPTSCDELTARGS *PSUPDRVGIPTSCDELTARGS; + + +/** @name Macros that implements the basic synchronization steps common to + * the algorithms. + * + * Must be used from loop as the timeouts are implemented via 'break' statements + * at the moment. + * + * @{ + */ +#if defined(DEBUG_bird) /* || defined(VBOX_STRICT) */ +# define TSCDELTA_DBG_VARS() uint32_t iDbgCounter +# define TSCDELTA_DBG_START_LOOP() do { iDbgCounter = 0; } while (0) +# define TSCDELTA_DBG_CHECK_LOOP() \ + do { iDbgCounter++; if ((iDbgCounter & UINT32_C(0x01ffffff)) == 0) RT_BREAKPOINT(); } while (0) +#else +# define TSCDELTA_DBG_VARS() ((void)0) +# define TSCDELTA_DBG_START_LOOP() ((void)0) +# define TSCDELTA_DBG_CHECK_LOOP() ((void)0) +#endif +#if 0 +# define TSCDELTA_DBG_SYNC_MSG(a_Args) SUPR0Printf a_Args +#else +# define TSCDELTA_DBG_SYNC_MSG(a_Args) ((void)0) +#endif +#if 0 +# define TSCDELTA_DBG_SYNC_MSG2(a_Args) SUPR0Printf a_Args +#else +# define TSCDELTA_DBG_SYNC_MSG2(a_Args) ((void)0) +#endif +#if 0 +# define TSCDELTA_DBG_SYNC_MSG9(a_Args) SUPR0Printf a_Args +#else +# define TSCDELTA_DBG_SYNC_MSG9(a_Args) ((void)0) +#endif + + +static bool supdrvTscDeltaSync2_Before(PSUPTSCDELTASYNC2 pMySync, PSUPTSCDELTASYNC2 pOtherSync, + bool fIsMaster, PRTCCUINTREG pfEFlags, PSUPDRVGIPTSCDELTARGS pArgs) +{ + uint32_t iMySeq = fIsMaster ? 0 : 256; + uint32_t const iMaxSeq = iMySeq + 16; /* For the last loop, darn linux/freebsd C-ishness. */ + uint32_t u32Tmp; + uint32_t iSync2Loops = 0; + RTCCUINTREG fEFlags; + TSCDELTA_DBG_VARS(); + + *pfEFlags = X86_EFL_IF | X86_EFL_1; /* should shut up most nagging compilers. */ + + /* + * The master tells the worker to get on it's mark. + */ + if (fIsMaster) + { + if (RT_LIKELY(ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_STEADY, GIP_TSC_DELTA_SYNC2_READY))) + { /* likely*/ } + else + { + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #1 uSyncVar=%#x\n", fIsMaster ? "master" : "worker", pOtherSync->uSyncVar)); + return false; + } + } + + /* + * Wait for the on your mark signal (ack in the master case). We process timeouts here. + */ + ASMAtomicWriteU32(&(pMySync)->uSyncSeq, 0); + for (;;) + { + fEFlags = ASMIntDisableFlags(); + u32Tmp = ASMAtomicReadU32(&pMySync->uSyncVar); + if (u32Tmp == GIP_TSC_DELTA_SYNC2_STEADY) + break; + ASMSetFlags(fEFlags); + ASMNopPause(); + + /* Abort? */ + if (u32Tmp != GIP_TSC_DELTA_SYNC2_READY) + { + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #2 u32Tmp=%#x\n", fIsMaster ? "master" : "worker", u32Tmp)); + return false; + } + + /* Check for timeouts every so often (not every loop in case RDTSC is + trapping or something). Must check the first time around. */ +#if 0 /* For debugging the timeout paths. */ + static uint32_t volatile xxx; +#endif + if ( ( (iSync2Loops & 0x3ff) == 0 + && ASMReadTSC() - pMySync->uTscStart > pMySync->cMaxTscTicks) +#if 0 /* This is crazy, I know, but enable this code and the results are markedly better when enabled on the 1.4GHz AMD (debug). */ + || (!fIsMaster && (++xxx & 0xf) == 0) +#endif + ) + { + /* Try switch our own state into timeout mode so the master cannot tell us to 'GO', + ignore the timeout if we've got the go ahead already (simpler). */ + if (ASMAtomicCmpXchgU32(&pMySync->uSyncVar, GIP_TSC_DELTA_SYNC2_TIMEOUT, GIP_TSC_DELTA_SYNC2_READY)) + { + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: timeout\n", fIsMaster ? "master" : "worker")); + ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_TIMEOUT, GIP_TSC_DELTA_SYNC2_STEADY); + ASMAtomicWriteBool(&pArgs->fTimedOut, true); + return false; + } + } + iSync2Loops++; + } + + /* + * Interrupts are now disabled and will remain disabled until we do + * TSCDELTA_MASTER_SYNC_AFTER / TSCDELTA_OTHER_SYNC_AFTER. + */ + *pfEFlags = fEFlags; + + /* + * The worker tells the master that it is on its mark and that the master + * need to get into position as well. + */ + if (!fIsMaster) + { + if (RT_LIKELY(ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_STEADY, GIP_TSC_DELTA_SYNC2_READY))) + { /* likely */ } + else + { + ASMSetFlags(fEFlags); + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #3 uSyncVar=%#x\n", fIsMaster ? "master" : "worker", pOtherSync->uSyncVar)); + return false; + } + } + + /* + * The master sends the 'go' to the worker and wait for ACK. + */ + if (fIsMaster) + { + if (RT_LIKELY(ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO, GIP_TSC_DELTA_SYNC2_STEADY))) + { /* likely */ } + else + { + ASMSetFlags(fEFlags); + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #4 uSyncVar=%#x\n", fIsMaster ? "master" : "worker", pOtherSync->uSyncVar)); + return false; + } + } + + /* + * Wait for the 'go' signal (ack in the master case). + */ + TSCDELTA_DBG_START_LOOP(); + for (;;) + { + u32Tmp = ASMAtomicReadU32(&pMySync->uSyncVar); + if (u32Tmp == GIP_TSC_DELTA_SYNC2_GO) + break; + if (RT_LIKELY(u32Tmp == GIP_TSC_DELTA_SYNC2_STEADY)) + { /* likely */ } + else + { + ASMSetFlags(fEFlags); + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #5 u32Tmp=%#x\n", fIsMaster ? "master" : "worker", u32Tmp)); + return false; + } + + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + + /* + * The worker acks the 'go' (shouldn't fail). + */ + if (!fIsMaster) + { + if (RT_LIKELY(ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO, GIP_TSC_DELTA_SYNC2_STEADY))) + { /* likely */ } + else + { + ASMSetFlags(fEFlags); + TSCDELTA_DBG_SYNC_MSG(("sync/before/%s: #6 uSyncVar=%#x\n", fIsMaster ? "master" : "worker", pOtherSync->uSyncVar)); + return false; + } + } + + /* + * Try enter mostly lockstep execution with it. + */ + for (;;) + { + uint32_t iOtherSeq1, iOtherSeq2; + ASMCompilerBarrier(); + ASMSerializeInstruction(); + + ASMAtomicWriteU32(&pMySync->uSyncSeq, iMySeq); + ASMNopPause(); + iOtherSeq1 = ASMAtomicXchgU32(&pOtherSync->uSyncSeq, iMySeq); + ASMNopPause(); + iOtherSeq2 = ASMAtomicReadU32(&pMySync->uSyncSeq); + + ASMCompilerBarrier(); + if (iOtherSeq1 == iOtherSeq2) + return true; + + /* Did the other guy give up? Should we give up? */ + if ( iOtherSeq1 == UINT32_MAX + || iOtherSeq2 == UINT32_MAX) + return true; + if (++iMySeq >= iMaxSeq) + { + ASMAtomicWriteU32(&pMySync->uSyncSeq, UINT32_MAX); + return true; + } + ASMNopPause(); + } +} + +#define TSCDELTA_MASTER_SYNC_BEFORE(a_pMySync, a_pOtherSync, a_pfEFlags, a_pArgs) \ + if (RT_LIKELY(supdrvTscDeltaSync2_Before(a_pMySync, a_pOtherSync, true /*fIsMaster*/, a_pfEFlags, a_pArgs))) \ + { /*likely*/ } \ + else if (true) \ + { \ + TSCDELTA_DBG_SYNC_MSG9(("sync/before/master: #89\n")); \ + break; \ + } else do {} while (0) +#define TSCDELTA_OTHER_SYNC_BEFORE(a_pMySync, a_pOtherSync, a_pfEFlags, a_pArgs) \ + if (RT_LIKELY(supdrvTscDeltaSync2_Before(a_pMySync, a_pOtherSync, false /*fIsMaster*/, a_pfEFlags, a_pArgs))) \ + { /*likely*/ } \ + else if (true) \ + { \ + TSCDELTA_DBG_SYNC_MSG9(("sync/before/other: #89\n")); \ + break; \ + } else do {} while (0) + + +static bool supdrvTscDeltaSync2_After(PSUPTSCDELTASYNC2 pMySync, PSUPTSCDELTASYNC2 pOtherSync, + bool fIsMaster, RTCCUINTREG fEFlags) +{ + TSCDELTA_DBG_VARS(); + RT_NOREF1(pOtherSync); + + /* + * Wait for the 'ready' signal. In the master's case, this means the + * worker has completed its data collection, while in the worker's case it + * means the master is done processing the data and it's time for the next + * loop iteration (or whatever). + */ + ASMSetFlags(fEFlags); + TSCDELTA_DBG_START_LOOP(); + for (;;) + { + uint32_t u32Tmp = ASMAtomicReadU32(&pMySync->uSyncVar); + if ( u32Tmp == GIP_TSC_DELTA_SYNC2_READY + || (u32Tmp == GIP_TSC_DELTA_SYNC2_STEADY && !fIsMaster) /* kicked twice => race */ ) + return true; + ASMNopPause(); + if (RT_LIKELY(u32Tmp == GIP_TSC_DELTA_SYNC2_GO)) + { /* likely */} + else + { + TSCDELTA_DBG_SYNC_MSG(("sync/after/other: #1 u32Tmp=%#x\n", u32Tmp)); + return false; /* shouldn't ever happen! */ + } + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } +} + +#define TSCDELTA_MASTER_SYNC_AFTER(a_pMySync, a_pOtherSync, a_fEFlags) \ + if (RT_LIKELY(supdrvTscDeltaSync2_After(a_pMySync, a_pOtherSync, true /*fIsMaster*/, a_fEFlags))) \ + { /* likely */ } \ + else if (true) \ + { \ + TSCDELTA_DBG_SYNC_MSG9(("sync/after/master: #97\n")); \ + break; \ + } else do {} while (0) + +#define TSCDELTA_MASTER_KICK_OTHER_OUT_OF_AFTER(a_pMySync, a_pOtherSync) \ + /* \ + * Tell the worker that we're done processing the data and ready for the next round. \ + */ \ + if (RT_LIKELY(ASMAtomicCmpXchgU32(&(a_pOtherSync)->uSyncVar, GIP_TSC_DELTA_SYNC2_READY, GIP_TSC_DELTA_SYNC2_GO))) \ + { /* likely */ } \ + else if (true)\ + { \ + TSCDELTA_DBG_SYNC_MSG(("sync/after/master: #99 uSyncVar=%#x\n", (a_pOtherSync)->uSyncVar)); \ + break; \ + } else do {} while (0) + +#define TSCDELTA_OTHER_SYNC_AFTER(a_pMySync, a_pOtherSync, a_fEFlags) \ + if (true) { \ + /* \ + * Tell the master that we're done collecting data and wait for the next round to start. \ + */ \ + if (RT_LIKELY(ASMAtomicCmpXchgU32(&(a_pOtherSync)->uSyncVar, GIP_TSC_DELTA_SYNC2_READY, GIP_TSC_DELTA_SYNC2_GO))) \ + { /* likely */ } \ + else \ + { \ + ASMSetFlags(a_fEFlags); \ + TSCDELTA_DBG_SYNC_MSG(("sync/after/other: #0 uSyncVar=%#x\n", (a_pOtherSync)->uSyncVar)); \ + break; \ + } \ + if (RT_LIKELY(supdrvTscDeltaSync2_After(a_pMySync, a_pOtherSync, false /*fIsMaster*/, a_fEFlags))) \ + { /* likely */ } \ + else \ + { \ + TSCDELTA_DBG_SYNC_MSG9(("sync/after/other: #98\n")); \ + break; \ + } \ + } else do {} while (0) +/** @} */ + + +#ifdef GIP_TSC_DELTA_METHOD_1 +/** + * TSC delta measurement algorithm \#1 (GIP_TSC_DELTA_METHOD_1). + * + * + * We ignore the first few runs of the loop in order to prime the + * cache. Also, we need to be careful about using 'pause' instruction + * in critical busy-wait loops in this code - it can cause undesired + * behaviour with hyperthreading. + * + * We try to minimize the measurement error by computing the minimum + * read time of the compare statement in the worker by taking TSC + * measurements across it. + * + * It must be noted that the computed minimum read time is mostly to + * eliminate huge deltas when the worker is too early and doesn't by + * itself help produce more accurate deltas. We allow two times the + * computed minimum as an arbitrary acceptable threshold. Therefore, + * it is still possible to get negative deltas where there are none + * when the worker is earlier. As long as these occasional negative + * deltas are lower than the time it takes to exit guest-context and + * the OS to reschedule EMT on a different CPU, we won't expose a TSC + * that jumped backwards. It is due to the existence of the negative + * deltas that we don't recompute the delta with the master and + * worker interchanged to eliminate the remaining measurement error. + * + * + * @param pArgs The argument/state data. + * @param pMySync My synchronization structure. + * @param pOtherSync My partner's synchronization structure. + * @param fIsMaster Set if master, clear if worker. + * @param iTry The attempt number. + */ +static void supdrvTscDeltaMethod1Loop(PSUPDRVGIPTSCDELTARGS pArgs, PSUPTSCDELTASYNC2 pMySync, PSUPTSCDELTASYNC2 pOtherSync, + bool fIsMaster, uint32_t iTry) +{ + PSUPGIPCPU pGipCpuWorker = pArgs->pWorker; + PSUPGIPCPU pGipCpuMaster = pArgs->pMaster; + uint64_t uMinCmpReadTime = UINT64_MAX; + unsigned iLoop; + NOREF(iTry); + + for (iLoop = 0; iLoop < GIP_TSC_DELTA_LOOPS; iLoop++) + { + RTCCUINTREG fEFlags; + if (fIsMaster) + { + /* + * The master. + */ + AssertMsg(pGipCpuMaster->u64TSCSample == GIP_TSC_DELTA_RSVD, + ("%#llx idMaster=%#x idWorker=%#x (idGipMaster=%#x)\n", + pGipCpuMaster->u64TSCSample, pGipCpuMaster->idCpu, pGipCpuWorker->idCpu, pArgs->pDevExt->idGipMaster)); + TSCDELTA_MASTER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + + do + { + ASMSerializeInstruction(); + ASMAtomicWriteU64(&pGipCpuMaster->u64TSCSample, ASMReadTSC()); + } while (pGipCpuMaster->u64TSCSample == GIP_TSC_DELTA_RSVD); + + TSCDELTA_MASTER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + + /* Process the data. */ + if (iLoop > GIP_TSC_DELTA_PRIMER_LOOPS + GIP_TSC_DELTA_READ_TIME_LOOPS) + { + if (pGipCpuWorker->u64TSCSample != GIP_TSC_DELTA_RSVD) + { + int64_t iDelta = pGipCpuWorker->u64TSCSample + - (pGipCpuMaster->u64TSCSample - pGipCpuMaster->i64TSCDelta); + if ( iDelta >= GIP_TSC_DELTA_INITIAL_MASTER_VALUE + ? iDelta < pGipCpuWorker->i64TSCDelta + : iDelta > pGipCpuWorker->i64TSCDelta || pGipCpuWorker->i64TSCDelta == INT64_MAX) + pGipCpuWorker->i64TSCDelta = iDelta; + } + } + + /* Reset our TSC sample and tell the worker to move on. */ + ASMAtomicWriteU64(&pGipCpuMaster->u64TSCSample, GIP_TSC_DELTA_RSVD); + TSCDELTA_MASTER_KICK_OTHER_OUT_OF_AFTER(pMySync, pOtherSync); + } + else + { + /* + * The worker. + */ + uint64_t uTscWorker; + uint64_t uTscWorkerFlushed; + uint64_t uCmpReadTime; + + ASMAtomicReadU64(&pGipCpuMaster->u64TSCSample); /* Warm the cache line. */ + TSCDELTA_OTHER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + + /* + * Keep reading the TSC until we notice that the master has read his. Reading + * the TSC -after- the master has updated the memory is way too late. We thus + * compensate by trying to measure how long it took for the worker to notice + * the memory flushed from the master. + */ + do + { + ASMSerializeInstruction(); + uTscWorker = ASMReadTSC(); + } while (pGipCpuMaster->u64TSCSample == GIP_TSC_DELTA_RSVD); + ASMSerializeInstruction(); + uTscWorkerFlushed = ASMReadTSC(); + + uCmpReadTime = uTscWorkerFlushed - uTscWorker; + if (iLoop > GIP_TSC_DELTA_PRIMER_LOOPS + GIP_TSC_DELTA_READ_TIME_LOOPS) + { + /* This is totally arbitrary a.k.a I don't like it but I have no better ideas for now. */ + if (uCmpReadTime < (uMinCmpReadTime << 1)) + { + ASMAtomicWriteU64(&pGipCpuWorker->u64TSCSample, uTscWorker); + if (uCmpReadTime < uMinCmpReadTime) + uMinCmpReadTime = uCmpReadTime; + } + else + ASMAtomicWriteU64(&pGipCpuWorker->u64TSCSample, GIP_TSC_DELTA_RSVD); + } + else if (iLoop > GIP_TSC_DELTA_PRIMER_LOOPS) + { + if (uCmpReadTime < uMinCmpReadTime) + uMinCmpReadTime = uCmpReadTime; + } + + TSCDELTA_OTHER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + } + } + + TSCDELTA_DBG_SYNC_MSG9(("sync/method1loop/%s: #92 iLoop=%u MyState=%#x\n", fIsMaster ? "master" : "worker", iLoop, + pMySync->uSyncVar)); + + /* + * We must reset the worker TSC sample value in case it gets picked as a + * GIP master later on (it's trashed above, naturally). + */ + if (!fIsMaster) + ASMAtomicWriteU64(&pGipCpuWorker->u64TSCSample, GIP_TSC_DELTA_RSVD); +} +#endif /* GIP_TSC_DELTA_METHOD_1 */ + + +#ifdef GIP_TSC_DELTA_METHOD_2 +/* + * TSC delta measurement algorithm \#2 configuration and code - Experimental!! + */ + +# define GIP_TSC_DELTA_M2_LOOPS (7 + GIP_TSC_DELTA_M2_PRIMER_LOOPS) +# define GIP_TSC_DELTA_M2_PRIMER_LOOPS 0 + + +static void supdrvTscDeltaMethod2ProcessDataOnMaster(PSUPDRVGIPTSCDELTARGS pArgs) +{ + int64_t iMasterTscDelta = pArgs->pMaster->i64TSCDelta; + int64_t iBestDelta = pArgs->pWorker->i64TSCDelta; + uint32_t idxResult; + uint32_t cHits = 0; + + /* + * Look for matching entries in the master and worker tables. + */ + for (idxResult = 0; idxResult < RT_ELEMENTS(pArgs->uMaster.M2.Data.aResults); idxResult++) + { + uint32_t idxOther = pArgs->uMaster.M2.Data.aResults[idxResult].iSeqOther; + if (idxOther & 1) + { + idxOther >>= 1; + if (idxOther < RT_ELEMENTS(pArgs->uWorker.M2.Data.aResults)) + { + if (pArgs->uWorker.M2.Data.aResults[idxOther].iSeqOther == pArgs->uMaster.M2.Data.aResults[idxResult].iSeqMine) + { + int64_t iDelta; + iDelta = pArgs->uWorker.M2.Data.aResults[idxOther].uTsc + - (pArgs->uMaster.M2.Data.aResults[idxResult].uTsc - iMasterTscDelta); + if ( iDelta >= GIP_TSC_DELTA_INITIAL_MASTER_VALUE + ? iDelta < iBestDelta + : iDelta > iBestDelta || iBestDelta == INT64_MAX) + iBestDelta = iDelta; + cHits++; + } + } + } + } + + /* + * Save the results. + */ + if (cHits > 2) + pArgs->pWorker->i64TSCDelta = iBestDelta; + pArgs->uMaster.M2.cHits += cHits; +} + + +/** + * The core function of the 2nd TSC delta measurement algorithm. + * + * The idea here is that we have the two CPUs execute the exact same code + * collecting a largish set of TSC samples. The code has one data dependency on + * the other CPU which intention it is to synchronize the execution as well as + * help cross references the two sets of TSC samples (the sequence numbers). + * + * The @a fLag parameter is used to modify the execution a tiny bit on one or + * both of the CPUs. When @a fLag differs between the CPUs, it is thought that + * it will help with making the CPUs enter lock step execution occasionally. + * + */ +static void supdrvTscDeltaMethod2CollectData(PSUPDRVTSCDELTAMETHOD2 pMyData, uint32_t volatile *piOtherSeqNo, bool fLag) +{ + SUPDRVTSCDELTAMETHOD2ENTRY *pEntry = &pMyData->aResults[0]; + uint32_t cLeft = RT_ELEMENTS(pMyData->aResults); + + ASMAtomicWriteU32(&pMyData->iCurSeqNo, 0); + ASMSerializeInstruction(); + while (cLeft-- > 0) + { + uint64_t uTsc; + uint32_t iSeqMine = ASMAtomicIncU32(&pMyData->iCurSeqNo); + uint32_t iSeqOther = ASMAtomicReadU32(piOtherSeqNo); + ASMCompilerBarrier(); + ASMSerializeInstruction(); /* Way better result than with ASMMemoryFenceSSE2() in this position! */ + uTsc = ASMReadTSC(); + ASMAtomicIncU32(&pMyData->iCurSeqNo); + ASMCompilerBarrier(); + ASMSerializeInstruction(); + pEntry->iSeqMine = iSeqMine; + pEntry->iSeqOther = iSeqOther; + pEntry->uTsc = uTsc; + pEntry++; + ASMSerializeInstruction(); + if (fLag) + ASMNopPause(); + } +} + + +/** + * TSC delta measurement algorithm \#2 (GIP_TSC_DELTA_METHOD_2). + * + * See supdrvTscDeltaMethod2CollectData for algorithm details. + * + * @param pArgs The argument/state data. + * @param pMySync My synchronization structure. + * @param pOtherSync My partner's synchronization structure. + * @param fIsMaster Set if master, clear if worker. + * @param iTry The attempt number. + */ +static void supdrvTscDeltaMethod2Loop(PSUPDRVGIPTSCDELTARGS pArgs, PSUPTSCDELTASYNC2 pMySync, PSUPTSCDELTASYNC2 pOtherSync, + bool fIsMaster, uint32_t iTry) +{ + unsigned iLoop; + RT_NOREF1(iTry); + + for (iLoop = 0; iLoop < GIP_TSC_DELTA_M2_LOOPS; iLoop++) + { + RTCCUINTREG fEFlags; + if (fIsMaster) + { + /* + * Adjust the loop lag fudge. + */ +# if GIP_TSC_DELTA_M2_PRIMER_LOOPS > 0 + if (iLoop < GIP_TSC_DELTA_M2_PRIMER_LOOPS) + { + /* Lag during the priming to be nice to everyone.. */ + pArgs->uMaster.M2.fLag = true; + pArgs->uWorker.M2.fLag = true; + } + else +# endif + if (iLoop < (GIP_TSC_DELTA_M2_LOOPS - GIP_TSC_DELTA_M2_PRIMER_LOOPS) / 4) + { + /* 25 % of the body without lagging. */ + pArgs->uMaster.M2.fLag = false; + pArgs->uWorker.M2.fLag = false; + } + else if (iLoop < (GIP_TSC_DELTA_M2_LOOPS - GIP_TSC_DELTA_M2_PRIMER_LOOPS) / 4 * 2) + { + /* 25 % of the body with both lagging. */ + pArgs->uMaster.M2.fLag = true; + pArgs->uWorker.M2.fLag = true; + } + else + { + /* 50% of the body with alternating lag. */ + pArgs->uMaster.M2.fLag = (iLoop & 1) == 0; + pArgs->uWorker.M2.fLag= (iLoop & 1) == 1; + } + + /* + * Sync up with the worker and collect data. + */ + TSCDELTA_MASTER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + supdrvTscDeltaMethod2CollectData(&pArgs->uMaster.M2.Data, &pArgs->uWorker.M2.Data.iCurSeqNo, pArgs->uMaster.M2.fLag); + TSCDELTA_MASTER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + + /* + * Process the data. + */ +# if GIP_TSC_DELTA_M2_PRIMER_LOOPS > 0 + if (iLoop >= GIP_TSC_DELTA_M2_PRIMER_LOOPS) +# endif + supdrvTscDeltaMethod2ProcessDataOnMaster(pArgs); + + TSCDELTA_MASTER_KICK_OTHER_OUT_OF_AFTER(pMySync, pOtherSync); + } + else + { + /* + * The worker. + */ + TSCDELTA_OTHER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + supdrvTscDeltaMethod2CollectData(&pArgs->uWorker.M2.Data, &pArgs->uMaster.M2.Data.iCurSeqNo, pArgs->uWorker.M2.fLag); + TSCDELTA_OTHER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + } + } +} + +#endif /* GIP_TSC_DELTA_METHOD_2 */ + + + +static int supdrvTscDeltaVerify(PSUPDRVGIPTSCDELTARGS pArgs, PSUPTSCDELTASYNC2 pMySync, + PSUPTSCDELTASYNC2 pOtherSync, bool fIsMaster, int64_t iWorkerTscDelta) +{ + /*PSUPGIPCPU pGipCpuWorker = pArgs->pWorker; - unused */ + PSUPGIPCPU pGipCpuMaster = pArgs->pMaster; + uint32_t i; + TSCDELTA_DBG_VARS(); + + for (;;) + { + RTCCUINTREG fEFlags; + AssertCompile((RT_ELEMENTS(pArgs->uMaster.Verify.auTscs) & 1) == 0); + AssertCompile(RT_ELEMENTS(pArgs->uMaster.Verify.auTscs) == RT_ELEMENTS(pArgs->uWorker.Verify.auTscs)); + + if (fIsMaster) + { + uint64_t uTscWorker; + TSCDELTA_MASTER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + + /* + * Collect TSC, master goes first. + */ + for (i = 0; i < RT_ELEMENTS(pArgs->uMaster.Verify.auTscs); i += 2) + { + /* Read, kick & wait #1. */ + uint64_t uTsc = ASMReadTSC(); + ASMAtomicWriteU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO_GO); + ASMSerializeInstruction(); + pArgs->uMaster.Verify.auTscs[i] = uTsc; + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&pMySync->uSyncVar) == GIP_TSC_DELTA_SYNC2_GO) + { + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + + /* Read, kick & wait #2. */ + uTsc = ASMReadTSC(); + ASMAtomicWriteU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO); + ASMSerializeInstruction(); + pArgs->uMaster.Verify.auTscs[i + 1] = uTsc; + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&pMySync->uSyncVar) == GIP_TSC_DELTA_SYNC2_GO_GO) + { + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + } + + TSCDELTA_MASTER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + + /* + * Process the data. + */ +#ifdef TSCDELTA_VERIFY_WITH_STATS + pArgs->cMaxVerifyTscTicks = INT64_MIN; + pArgs->cMinVerifyTscTicks = INT64_MAX; + pArgs->iVerifyBadTscDiff = 0; +#endif + ASMAtomicWriteS32(&pArgs->rcVerify, VINF_SUCCESS); + uTscWorker = 0; + for (i = 0; i < RT_ELEMENTS(pArgs->uMaster.Verify.auTscs); i++) + { + /* Master vs previous worker entry. */ + uint64_t uTscMaster = pArgs->uMaster.Verify.auTscs[i] - pGipCpuMaster->i64TSCDelta; + int64_t iDiff; + if (i > 0) + { + iDiff = uTscMaster - uTscWorker; +#ifdef TSCDELTA_VERIFY_WITH_STATS + if (iDiff > pArgs->cMaxVerifyTscTicks) + pArgs->cMaxVerifyTscTicks = iDiff; + if (iDiff < pArgs->cMinVerifyTscTicks) + pArgs->cMinVerifyTscTicks = iDiff; +#endif + if (iDiff < 0) + { +#ifdef TSCDELTA_VERIFY_WITH_STATS + pArgs->iVerifyBadTscDiff = -iDiff; +#endif + ASMAtomicWriteS32(&pArgs->rcVerify, VERR_OUT_OF_RANGE); + break; + } + } + + /* Worker vs master. */ + uTscWorker = pArgs->uWorker.Verify.auTscs[i] - iWorkerTscDelta; + iDiff = uTscWorker - uTscMaster; +#ifdef TSCDELTA_VERIFY_WITH_STATS + if (iDiff > pArgs->cMaxVerifyTscTicks) + pArgs->cMaxVerifyTscTicks = iDiff; + if (iDiff < pArgs->cMinVerifyTscTicks) + pArgs->cMinVerifyTscTicks = iDiff; +#endif + if (iDiff < 0) + { +#ifdef TSCDELTA_VERIFY_WITH_STATS + pArgs->iVerifyBadTscDiff = iDiff; +#endif + ASMAtomicWriteS32(&pArgs->rcVerify, VERR_OUT_OF_RANGE); + break; + } + } + + /* Done. */ + TSCDELTA_MASTER_KICK_OTHER_OUT_OF_AFTER(pMySync, pOtherSync); + } + else + { + /* + * The worker, master leads. + */ + TSCDELTA_OTHER_SYNC_BEFORE(pMySync, pOtherSync, &fEFlags, pArgs); + + for (i = 0; i < RT_ELEMENTS(pArgs->uWorker.Verify.auTscs); i += 2) + { + uint64_t uTsc; + + /* Wait, Read and Kick #1. */ + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&pMySync->uSyncVar) == GIP_TSC_DELTA_SYNC2_GO) + { + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + uTsc = ASMReadTSC(); + ASMAtomicWriteU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO_GO); + ASMSerializeInstruction(); + pArgs->uWorker.Verify.auTscs[i] = uTsc; + + /* Wait, Read and Kick #2. */ + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&pMySync->uSyncVar) == GIP_TSC_DELTA_SYNC2_GO_GO) + { + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + uTsc = ASMReadTSC(); + ASMAtomicWriteU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_GO); + ASMSerializeInstruction(); + pArgs->uWorker.Verify.auTscs[i + 1] = uTsc; + } + + TSCDELTA_OTHER_SYNC_AFTER(pMySync, pOtherSync, fEFlags); + } + return pArgs->rcVerify; + } + + /* + * Timed out, please retry. + */ + ASMAtomicWriteS32(&pArgs->rcVerify, VERR_TRY_AGAIN); + return VERR_TIMEOUT; +} + + + +/** + * Handles the special abort procedure during synchronization setup in + * supdrvTscMeasureDeltaCallbackUnwrapped(). + * + * @returns 0 (dummy, ignored) + * @param pArgs Pointer to argument/state data. + * @param pMySync Pointer to my sync structure. + * @param fIsMaster Set if we're the master, clear if worker. + * @param fTimeout Set if it's a timeout. + */ +DECL_NO_INLINE(static, int) +supdrvTscMeasureDeltaCallbackAbortSyncSetup(PSUPDRVGIPTSCDELTARGS pArgs, PSUPTSCDELTASYNC2 pMySync, bool fIsMaster, bool fTimeout) +{ + PSUPTSCDELTASYNC2 volatile *ppMySync = fIsMaster ? &pArgs->pSyncMaster : &pArgs->pSyncWorker; + PSUPTSCDELTASYNC2 volatile *ppOtherSync = fIsMaster ? &pArgs->pSyncWorker : &pArgs->pSyncMaster; + TSCDELTA_DBG_VARS(); + RT_NOREF1(pMySync); + + /* + * Clear our sync pointer and make sure the abort flag is set. + */ + ASMAtomicWriteNullPtr(ppMySync); + ASMAtomicWriteBool(&pArgs->fAbortSetup, true); + if (fTimeout) + ASMAtomicWriteBool(&pArgs->fTimedOut, true); + + /* + * Make sure the other party is out of there and won't be touching our + * sync state again (would cause stack corruption). + */ + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadPtrT(ppOtherSync, PSUPTSCDELTASYNC2) != NULL) + { + ASMNopPause(); + ASMNopPause(); + ASMNopPause(); + TSCDELTA_DBG_CHECK_LOOP(); + } + + return 0; +} + + +/** + * This is used by supdrvTscMeasureInitialDeltas() to read the TSC on two CPUs + * and compute the delta between them. + * + * To reduce code size a good when timeout handling was added, a dummy return + * value had to be added (saves 1-3 lines per timeout case), thus this + * 'Unwrapped' function and the dummy 0 return value. + * + * @returns 0 (dummy, ignored) + * @param idCpu The CPU we are current scheduled on. + * @param pArgs Pointer to a parameter package. + * + * @remarks Measuring TSC deltas between the CPUs is tricky because we need to + * read the TSC at exactly the same time on both the master and the + * worker CPUs. Due to DMA, bus arbitration, cache locality, + * contention, SMI, pipelining etc. there is no guaranteed way of + * doing this on x86 CPUs. + */ +static int supdrvTscMeasureDeltaCallbackUnwrapped(RTCPUID idCpu, PSUPDRVGIPTSCDELTARGS pArgs) +{ + PSUPDRVDEVEXT pDevExt = pArgs->pDevExt; + PSUPGIPCPU pGipCpuWorker = pArgs->pWorker; + PSUPGIPCPU pGipCpuMaster = pArgs->pMaster; + bool const fIsMaster = idCpu == pGipCpuMaster->idCpu; + uint32_t iTry; + PSUPTSCDELTASYNC2 volatile *ppMySync = fIsMaster ? &pArgs->pSyncMaster : &pArgs->pSyncWorker; + PSUPTSCDELTASYNC2 volatile *ppOtherSync = fIsMaster ? &pArgs->pSyncWorker : &pArgs->pSyncMaster; + SUPTSCDELTASYNC2 MySync; + PSUPTSCDELTASYNC2 pOtherSync; + int rc; + TSCDELTA_DBG_VARS(); + + /* A bit of paranoia first. */ + if (!pGipCpuMaster || !pGipCpuWorker) + return 0; + + /* + * If the CPU isn't part of the measurement, return immediately. + */ + if ( !fIsMaster + && idCpu != pGipCpuWorker->idCpu) + return 0; + + /* + * Set up my synchronization stuff and wait for the other party to show up. + * + * We don't wait forever since the other party may be off fishing (offline, + * spinning with ints disables, whatever), we must play nice to the rest of + * the system as this context generally isn't one in which we will get + * preempted and we may hold up a number of lower priority interrupts. + */ + ASMAtomicWriteU32(&MySync.uSyncVar, GIP_TSC_DELTA_SYNC2_PRESTART_WAIT); + ASMAtomicWritePtr(ppMySync, &MySync); + MySync.uTscStart = ASMReadTSC(); + MySync.cMaxTscTicks = pArgs->cMaxTscTicks; + + /* Look for the partner, might not be here yet... Special abort considerations. */ + iTry = 0; + TSCDELTA_DBG_START_LOOP(); + while ((pOtherSync = ASMAtomicReadPtrT(ppOtherSync, PSUPTSCDELTASYNC2)) == NULL) + { + ASMNopPause(); + if ( ASMAtomicReadBool(&pArgs->fAbortSetup) + || !RTMpIsCpuOnline(fIsMaster ? pGipCpuWorker->idCpu : pGipCpuMaster->idCpu) ) + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, false /*fTimeout*/); + if ( (iTry++ & 0xff) == 0 + && ASMReadTSC() - MySync.uTscStart > pArgs->cMaxTscTicks) + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, true /*fTimeout*/); + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + + /* I found my partner, waiting to be found... Special abort considerations. */ + if (fIsMaster) + if (!ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_READY, GIP_TSC_DELTA_SYNC2_PRESTART_WAIT)) /* parnaoia */ + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, false /*fTimeout*/); + + iTry = 0; + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&MySync.uSyncVar) == GIP_TSC_DELTA_SYNC2_PRESTART_WAIT) + { + ASMNopPause(); + if (ASMAtomicReadBool(&pArgs->fAbortSetup)) + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, false /*fTimeout*/); + if ( (iTry++ & 0xff) == 0 + && ASMReadTSC() - MySync.uTscStart > pArgs->cMaxTscTicks) + { + if ( fIsMaster + && !ASMAtomicCmpXchgU32(&MySync.uSyncVar, GIP_TSC_DELTA_SYNC2_PRESTART_ABORT, GIP_TSC_DELTA_SYNC2_PRESTART_WAIT)) + break; /* race #1: slave has moved on, handle timeout in loop instead. */ + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, true /*fTimeout*/); + } + TSCDELTA_DBG_CHECK_LOOP(); + } + + if (!fIsMaster) + if (!ASMAtomicCmpXchgU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_READY, GIP_TSC_DELTA_SYNC2_PRESTART_WAIT)) /* race #1 */ + return supdrvTscMeasureDeltaCallbackAbortSyncSetup(pArgs, &MySync, fIsMaster, false /*fTimeout*/); + +/** @todo Add a resumable state to pArgs so we don't waste time if we time + * out or something. Timeouts are legit, any of the two CPUs may get + * interrupted. */ + + /* + * Start by seeing if we have a zero delta between the two CPUs. + * This should normally be the case. + */ + rc = supdrvTscDeltaVerify(pArgs, &MySync, pOtherSync, fIsMaster, GIP_TSC_DELTA_INITIAL_MASTER_VALUE); + if (RT_SUCCESS(rc)) + { + if (fIsMaster) + { + ASMAtomicWriteS64(&pGipCpuWorker->i64TSCDelta, GIP_TSC_DELTA_INITIAL_MASTER_VALUE); + RTCpuSetDelByIndex(&pDevExt->TscDeltaCpuSet, pGipCpuWorker->iCpuSet); + RTCpuSetAddByIndex(&pDevExt->TscDeltaObtainedCpuSet, pGipCpuWorker->iCpuSet); + } + } + /* + * If the verification didn't time out, do regular delta measurements. + * We retry this until we get a reasonable value. + */ + else if (rc != VERR_TIMEOUT) + { + Assert(pGipCpuWorker->i64TSCDelta == INT64_MAX); + for (iTry = 0; iTry < 12; iTry++) + { + /* + * Check the state before we start. + */ + uint32_t u32Tmp = ASMAtomicReadU32(&MySync.uSyncVar); + if ( u32Tmp != GIP_TSC_DELTA_SYNC2_READY + && (fIsMaster || u32Tmp != GIP_TSC_DELTA_SYNC2_STEADY) /* worker may be late prepping for the next round */ ) + { + TSCDELTA_DBG_SYNC_MSG(("sync/loop/%s: #0 iTry=%u MyState=%#x\n", fIsMaster ? "master" : "worker", iTry, u32Tmp)); + break; + } + + /* + * Do the measurements. + */ +#ifdef GIP_TSC_DELTA_METHOD_1 + supdrvTscDeltaMethod1Loop(pArgs, &MySync, pOtherSync, fIsMaster, iTry); +#elif defined(GIP_TSC_DELTA_METHOD_2) + supdrvTscDeltaMethod2Loop(pArgs, &MySync, pOtherSync, fIsMaster, iTry); +#else +# error "huh??" +#endif + + /* + * Check the state. + */ + u32Tmp = ASMAtomicReadU32(&MySync.uSyncVar); + if ( u32Tmp != GIP_TSC_DELTA_SYNC2_READY + && (fIsMaster || u32Tmp != GIP_TSC_DELTA_SYNC2_STEADY) /* worker may be late prepping for the next round */ ) + { + if (fIsMaster) + TSCDELTA_DBG_SYNC_MSG(("sync/loop/master: #1 iTry=%u MyState=%#x\n", iTry, u32Tmp)); + else + TSCDELTA_DBG_SYNC_MSG2(("sync/loop/worker: #1 iTry=%u MyState=%#x\n", iTry, u32Tmp)); + break; + } + + /* + * Success? If so, stop trying. Master decides. + */ + if (fIsMaster) + { + if (pGipCpuWorker->i64TSCDelta != INT64_MAX) + { + RTCpuSetDelByIndex(&pDevExt->TscDeltaCpuSet, pGipCpuWorker->iCpuSet); + RTCpuSetAddByIndex(&pDevExt->TscDeltaObtainedCpuSet, pGipCpuWorker->iCpuSet); + TSCDELTA_DBG_SYNC_MSG2(("sync/loop/master: #9 iTry=%u MyState=%#x\n", iTry, MySync.uSyncVar)); + break; + } + } + } + if (fIsMaster) + pArgs->iTry = iTry; + } + + /* + * End the synchronization dance. We tell the other that we're done, + * then wait for the same kind of reply. + */ + ASMAtomicWriteU32(&pOtherSync->uSyncVar, GIP_TSC_DELTA_SYNC2_FINAL); + ASMAtomicWriteNullPtr(ppMySync); + iTry = 0; + TSCDELTA_DBG_START_LOOP(); + while (ASMAtomicReadU32(&MySync.uSyncVar) != GIP_TSC_DELTA_SYNC2_FINAL) + { + iTry++; + if ( iTry == 0 + && !RTMpIsCpuOnline(fIsMaster ? pGipCpuWorker->idCpu : pGipCpuMaster->idCpu)) + break; /* this really shouldn't happen. */ + TSCDELTA_DBG_CHECK_LOOP(); + ASMNopPause(); + } + + /* + * Collect some runtime stats. + */ + if (fIsMaster) + pArgs->cElapsedMasterTscTicks = ASMReadTSC() - MySync.uTscStart; + else + pArgs->cElapsedWorkerTscTicks = ASMReadTSC() - MySync.uTscStart; + return 0; +} + +/** + * Callback used by supdrvTscMeasureInitialDeltas() to read the TSC on two CPUs + * and compute the delta between them. + * + * @param idCpu The CPU we are current scheduled on. + * @param pvUser1 Pointer to a parameter package (SUPDRVGIPTSCDELTARGS). + * @param pvUser2 Unused. + */ +static DECLCALLBACK(void) supdrvTscMeasureDeltaCallback(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + supdrvTscMeasureDeltaCallbackUnwrapped(idCpu, (PSUPDRVGIPTSCDELTARGS)pvUser1); + RT_NOREF1(pvUser2); +} + + +/** + * Measures the TSC delta between the master GIP CPU and one specified worker + * CPU. + * + * @returns VBox status code. + * @retval VERR_SUPDRV_TSC_DELTA_MEASUREMENT_FAILED on pure measurement + * failure. + * @param pDevExt Pointer to the device instance data. + * @param idxWorker The index of the worker CPU from the GIP's array of + * CPUs. + * + * @remarks This must be called with preemption enabled! + */ +static int supdrvTscMeasureDeltaOne(PSUPDRVDEVEXT pDevExt, uint32_t idxWorker) +{ + int rc; + int rc2; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + RTCPUID idMaster = pDevExt->idGipMaster; + PSUPGIPCPU pGipCpuWorker = &pGip->aCPUs[idxWorker]; + PSUPGIPCPU pGipCpuMaster; + uint32_t iGipCpuMaster; + uint32_t u32Tmp; + + /* Validate input a bit. */ + AssertReturn(pGip, VERR_INVALID_PARAMETER); + Assert(pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED); + Assert(RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + + /* + * Don't attempt measuring the delta for the GIP master. + */ + if (pGipCpuWorker->idCpu == idMaster) + { + if (pGipCpuWorker->i64TSCDelta == INT64_MAX) /* This shouldn't happen, but just in case. */ + ASMAtomicWriteS64(&pGipCpuWorker->i64TSCDelta, GIP_TSC_DELTA_INITIAL_MASTER_VALUE); + return VINF_SUCCESS; + } + + /* + * One measurement at a time, at least for now. We might be using + * broadcast IPIs so, so be nice to the rest of the system. + */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + rc = RTSemMutexRequest(pDevExt->mtxTscDelta, RT_INDEFINITE_WAIT); +#else + rc = RTSemFastMutexRequest(pDevExt->mtxTscDelta); +#endif + if (RT_FAILURE(rc)) + return rc; + + /* + * If the CPU has hyper-threading and the APIC IDs of the master and worker are adjacent, + * try pick a different master. (This fudge only works with multi core systems.) + * ASSUMES related threads have adjacent APIC IDs. ASSUMES two threads per core. + * + * We skip this on AMDs for now as their HTT is different from Intel's and + * it doesn't seem to have any favorable effect on the results. + * + * If the master is offline, we need a new master too, so share the code. + */ + iGipCpuMaster = supdrvGipFindCpuIndexForCpuId(pGip, idMaster); + AssertReturn(iGipCpuMaster < pGip->cCpus, VERR_INVALID_CPU_ID); + pGipCpuMaster = &pGip->aCPUs[iGipCpuMaster]; + if ( ( (pGipCpuMaster->idApic & ~1) == (pGipCpuWorker->idApic & ~1) + && pGip->cOnlineCpus > 2 + && ASMHasCpuId() + && RTX86IsValidStdRange(ASMCpuId_EAX(0)) + && (ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_HTT) + && ( !ASMIsAmdCpu() + || RTX86GetCpuFamily(u32Tmp = ASMCpuId_EAX(1)) > 0x15 + || ( RTX86GetCpuFamily(u32Tmp) == 0x15 /* Piledriver+, not bulldozer (FX-4150 didn't like it). */ + && RTX86GetCpuModelAMD(u32Tmp) >= 0x02) ) ) + || !RTMpIsCpuOnline(idMaster) ) + { + uint32_t i; + for (i = 0; i < pGip->cCpus; i++) + if ( i != iGipCpuMaster + && i != idxWorker + && pGip->aCPUs[i].enmState == SUPGIPCPUSTATE_ONLINE + && pGip->aCPUs[i].i64TSCDelta != INT64_MAX + && pGip->aCPUs[i].idCpu != NIL_RTCPUID + && pGip->aCPUs[i].idCpu != idMaster /* paranoia starts here... */ + && pGip->aCPUs[i].idCpu != pGipCpuWorker->idCpu + && pGip->aCPUs[i].idApic != pGipCpuWorker->idApic + && pGip->aCPUs[i].idApic != pGipCpuMaster->idApic + && RTMpIsCpuOnline(pGip->aCPUs[i].idCpu)) + { + iGipCpuMaster = i; + pGipCpuMaster = &pGip->aCPUs[i]; + idMaster = pGipCpuMaster->idCpu; + break; + } + } + + if (RTCpuSetIsMemberByIndex(&pGip->OnlineCpuSet, pGipCpuWorker->iCpuSet)) + { + /* + * Initialize data package for the RTMpOnPair callback. + */ + PSUPDRVGIPTSCDELTARGS pArgs = (PSUPDRVGIPTSCDELTARGS)RTMemAllocZ(sizeof(*pArgs)); + if (pArgs) + { + pArgs->pWorker = pGipCpuWorker; + pArgs->pMaster = pGipCpuMaster; + pArgs->pDevExt = pDevExt; + pArgs->pSyncMaster = NULL; + pArgs->pSyncWorker = NULL; + pArgs->cMaxTscTicks = ASMAtomicReadU64(&pGip->u64CpuHz) / 512; /* 1953 us */ + + /* + * Do the RTMpOnPair call. We reset i64TSCDelta first so we + * and supdrvTscMeasureDeltaCallback can use it as a success check. + */ + /** @todo Store the i64TSCDelta result in pArgs first? Perhaps deals with + * that when doing the restart loop reorg. */ + ASMAtomicWriteS64(&pGipCpuWorker->i64TSCDelta, INT64_MAX); + rc = RTMpOnPair(pGipCpuMaster->idCpu, pGipCpuWorker->idCpu, RTMPON_F_CONCURRENT_EXEC, + supdrvTscMeasureDeltaCallback, pArgs, NULL); + if (RT_SUCCESS(rc)) + { +#if 0 + SUPR0Printf("mponpair ticks: %9llu %9llu max: %9llu iTry: %u%s\n", pArgs->cElapsedMasterTscTicks, + pArgs->cElapsedWorkerTscTicks, pArgs->cMaxTscTicks, pArgs->iTry, + pArgs->fTimedOut ? " timed out" :""); +#endif +#if 0 + SUPR0Printf("rcVerify=%d iVerifyBadTscDiff=%lld cMinVerifyTscTicks=%lld cMaxVerifyTscTicks=%lld\n", + pArgs->rcVerify, pArgs->iVerifyBadTscDiff, pArgs->cMinVerifyTscTicks, pArgs->cMaxVerifyTscTicks); +#endif + if (RT_LIKELY(pGipCpuWorker->i64TSCDelta != INT64_MAX)) + { + /* + * Work the TSC delta applicability rating. It starts + * optimistic in supdrvGipInit, we downgrade it here. + */ + SUPGIPUSETSCDELTA enmRating; + if ( pGipCpuWorker->i64TSCDelta > GIP_TSC_DELTA_THRESHOLD_ROUGHLY_ZERO + || pGipCpuWorker->i64TSCDelta < -GIP_TSC_DELTA_THRESHOLD_ROUGHLY_ZERO) + enmRating = SUPGIPUSETSCDELTA_NOT_ZERO; + else if ( pGipCpuWorker->i64TSCDelta > GIP_TSC_DELTA_THRESHOLD_PRACTICALLY_ZERO + || pGipCpuWorker->i64TSCDelta < -GIP_TSC_DELTA_THRESHOLD_PRACTICALLY_ZERO) + enmRating = SUPGIPUSETSCDELTA_ROUGHLY_ZERO; + else + enmRating = SUPGIPUSETSCDELTA_PRACTICALLY_ZERO; + if (pGip->enmUseTscDelta < enmRating) + { + AssertCompile(sizeof(pGip->enmUseTscDelta) == sizeof(uint32_t)); + ASMAtomicWriteU32((uint32_t volatile *)&pGip->enmUseTscDelta, enmRating); + } + } + else + rc = VERR_SUPDRV_TSC_DELTA_MEASUREMENT_FAILED; + } + /** @todo return try-again if we get an offline CPU error. */ + + RTMemFree(pArgs); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_CPU_OFFLINE; + + /* + * We're done now. + */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + rc2 = RTSemMutexRelease(pDevExt->mtxTscDelta); AssertRC(rc2); +#else + rc2 = RTSemFastMutexRelease(pDevExt->mtxTscDelta); AssertRC(rc2); +#endif + return rc; +} + + +/** + * Resets the TSC-delta related TSC samples and optionally the deltas + * themselves. + * + * @param pDevExt Pointer to the device instance data. + * @param fResetTscDeltas Whether the TSC-deltas are also to be reset. + * + * @remarks This might be called while holding a spinlock! + */ +static void supdrvTscResetSamples(PSUPDRVDEVEXT pDevExt, bool fResetTscDeltas) +{ + unsigned iCpu; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + for (iCpu = 0; iCpu < pGip->cCpus; iCpu++) + { + PSUPGIPCPU pGipCpu = &pGip->aCPUs[iCpu]; + ASMAtomicWriteU64(&pGipCpu->u64TSCSample, GIP_TSC_DELTA_RSVD); + if (fResetTscDeltas) + { + RTCpuSetDelByIndex(&pDevExt->TscDeltaObtainedCpuSet, pGipCpu->iCpuSet); + ASMAtomicWriteS64(&pGipCpu->i64TSCDelta, INT64_MAX); + } + } +} + + +/** + * Picks an online CPU as the master TSC for TSC-delta computations. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param pidxMaster Where to store the CPU array index of the chosen + * master. Optional, can be NULL. + */ +static int supdrvTscPickMaster(PSUPDRVDEVEXT pDevExt, uint32_t *pidxMaster) +{ + /* + * Pick the first CPU online as the master TSC and make it the new GIP master based + * on the APIC ID. + * + * Technically we can simply use "idGipMaster" but doing this gives us master as CPU 0 + * in most cases making it nicer/easier for comparisons. It is safe to update the GIP + * master as this point since the sync/async timer isn't created yet. + */ + unsigned iCpu; + uint32_t idxMaster = UINT32_MAX; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + for (iCpu = 0; iCpu < RT_ELEMENTS(pGip->aiCpuFromApicId); iCpu++) + { + uint16_t idxCpu = pGip->aiCpuFromApicId[iCpu]; + if (idxCpu != UINT16_MAX) + { + PSUPGIPCPU pGipCpu = &pGip->aCPUs[idxCpu]; + if (RTCpuSetIsMemberByIndex(&pGip->OnlineCpuSet, pGipCpu->iCpuSet)) + { + idxMaster = idxCpu; + pGipCpu->i64TSCDelta = GIP_TSC_DELTA_INITIAL_MASTER_VALUE; + ASMAtomicWriteSize(&pDevExt->idGipMaster, pGipCpu->idCpu); + if (pidxMaster) + *pidxMaster = idxMaster; + return VINF_SUCCESS; + } + } + } + return VERR_CPU_OFFLINE; +} + + +/** + * Performs the initial measurements of the TSC deltas between CPUs. + * + * This is called by supdrvGipCreate(), supdrvGipPowerNotificationCallback() or + * triggered by it if threaded. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * + * @remarks Must be called only after supdrvGipInitOnCpu() as this function uses + * idCpu, GIP's online CPU set which are populated in + * supdrvGipInitOnCpu(). + */ +static int supdrvTscMeasureInitialDeltas(PSUPDRVDEVEXT pDevExt) +{ + PSUPGIPCPU pGipCpuMaster; + unsigned iCpu; + unsigned iOddEven; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + uint32_t idxMaster = UINT32_MAX; + uint32_t cMpOnOffEvents = ASMAtomicReadU32(&pDevExt->cMpOnOffEvents); + + Assert(pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED); + supdrvTscResetSamples(pDevExt, true /* fClearDeltas */); + int rc = supdrvTscPickMaster(pDevExt, &idxMaster); + if (RT_FAILURE(rc)) + { + SUPR0Printf("Failed to pick a CPU master for TSC-delta measurements rc=%Rrc\n", rc); + return rc; + } + AssertReturn(idxMaster < pGip->cCpus, VERR_INVALID_CPU_INDEX); + pGipCpuMaster = &pGip->aCPUs[idxMaster]; + Assert(pDevExt->idGipMaster == pGipCpuMaster->idCpu); + + /* + * If there is only a single CPU online we have nothing to do. + */ + if (pGip->cOnlineCpus <= 1) + { + AssertReturn(pGip->cOnlineCpus > 0, VERR_INTERNAL_ERROR_5); + return VINF_SUCCESS; + } + + /* + * Loop thru the GIP CPU array and get deltas for each CPU (except the + * master). We do the CPUs with the even numbered APIC IDs first so that + * we've got alternative master CPUs to pick from on hyper-threaded systems. + */ + for (iOddEven = 0; iOddEven < 2; iOddEven++) + { + for (iCpu = 0; iCpu < pGip->cCpus; iCpu++) + { + PSUPGIPCPU pGipCpuWorker = &pGip->aCPUs[iCpu]; + if ( iCpu != idxMaster + && (iOddEven > 0 || (pGipCpuWorker->idApic & 1) == 0) + && RTCpuSetIsMemberByIndex(&pDevExt->TscDeltaCpuSet, pGipCpuWorker->iCpuSet)) + { + rc = supdrvTscMeasureDeltaOne(pDevExt, iCpu); + if (RT_FAILURE(rc)) + { + SUPR0Printf("supdrvTscMeasureDeltaOne failed. rc=%d CPU[%u].idCpu=%u Master[%u].idCpu=%u\n", rc, iCpu, + pGipCpuWorker->idCpu, idxMaster, pDevExt->idGipMaster, pGipCpuMaster->idCpu); + break; + } + + if (ASMAtomicReadU32(&pDevExt->cMpOnOffEvents) != cMpOnOffEvents) + { + SUPR0Printf("One or more CPUs transitioned between online & offline states. I'm confused, retry...\n"); + rc = VERR_TRY_AGAIN; + break; + } + } + } + } + + return rc; +} + + +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + +/** + * Switches the TSC-delta measurement thread into the butchered state. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param fSpinlockHeld Whether the TSC-delta spinlock is held or not. + * @param pszFailed An error message to log. + * @param rcFailed The error code to exit the thread with. + */ +static int supdrvTscDeltaThreadButchered(PSUPDRVDEVEXT pDevExt, bool fSpinlockHeld, const char *pszFailed, int rcFailed) +{ + if (!fSpinlockHeld) + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Butchered; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + OSDBGPRINT(("supdrvTscDeltaThreadButchered: %s. rc=%Rrc\n", pszFailed, rcFailed)); + return rcFailed; +} + + +/** + * The TSC-delta measurement thread. + * + * @returns VBox status code. + * @param hThread The thread handle. + * @param pvUser Opaque pointer to the device instance data. + */ +static DECLCALLBACK(int) supdrvTscDeltaThread(RTTHREAD hThread, void *pvUser) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + int rc = VERR_INTERNAL_ERROR_2; + for (;;) + { + /* + * Switch on the current state. + */ + SUPDRVTSCDELTATHREADSTATE enmState; + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + enmState = pDevExt->enmTscDeltaThreadState; + switch (enmState) + { + case kTscDeltaThreadState_Creating: + { + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Listening; + rc = RTSemEventSignal(pDevExt->hTscDeltaEvent); + if (RT_FAILURE(rc)) + return supdrvTscDeltaThreadButchered(pDevExt, true /* fSpinlockHeld */, "RTSemEventSignal", rc); + RT_FALL_THRU(); + } + + case kTscDeltaThreadState_Listening: + { + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + + /* + * Linux counts uninterruptible sleeps as load, hence we shall do a + * regular, interruptible sleep here and ignore wake ups due to signals. + * See task_contributes_to_load() in include/linux/sched.h in the Linux sources. + */ + rc = RTThreadUserWaitNoResume(hThread, pDevExt->cMsTscDeltaTimeout); + if ( RT_FAILURE(rc) + && rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED) + return supdrvTscDeltaThreadButchered(pDevExt, false /* fSpinlockHeld */, "RTThreadUserWait", rc); + RTThreadUserReset(hThread); + break; + } + + case kTscDeltaThreadState_WaitAndMeasure: + { + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Measuring; + rc = RTSemEventSignal(pDevExt->hTscDeltaEvent); /* (Safe on windows as long as spinlock isn't IRQ safe.) */ + if (RT_FAILURE(rc)) + return supdrvTscDeltaThreadButchered(pDevExt, true /* fSpinlockHeld */, "RTSemEventSignal", rc); + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + RTThreadSleep(1); + RT_FALL_THRU(); + } + + case kTscDeltaThreadState_Measuring: + { + if (pDevExt->fTscThreadRecomputeAllDeltas) + { + int cTries = 8; + int cMsWaitPerTry = 10; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + Assert(pGip); + do + { + RTCpuSetCopy(&pDevExt->TscDeltaCpuSet, &pGip->OnlineCpuSet); + rc = supdrvTscMeasureInitialDeltas(pDevExt); + if ( RT_SUCCESS(rc) + || ( RT_FAILURE(rc) + && rc != VERR_TRY_AGAIN + && rc != VERR_CPU_OFFLINE)) + { + break; + } + RTThreadSleep(cMsWaitPerTry); + } while (cTries-- > 0); + pDevExt->fTscThreadRecomputeAllDeltas = false; + } + else + { + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + unsigned iCpu; + + /* Measure TSC-deltas only for the CPUs that are in the set. */ + rc = VINF_SUCCESS; + for (iCpu = 0; iCpu < pGip->cCpus; iCpu++) + { + PSUPGIPCPU pGipCpuWorker = &pGip->aCPUs[iCpu]; + if (RTCpuSetIsMemberByIndex(&pDevExt->TscDeltaCpuSet, pGipCpuWorker->iCpuSet)) + { + if (pGipCpuWorker->i64TSCDelta == INT64_MAX) + { + int rc2 = supdrvTscMeasureDeltaOne(pDevExt, iCpu); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + else + { + /* + * The thread/someone must've called SUPR0TscDeltaMeasureBySetIndex(), + * mark the delta as fine to get the timer thread off our back. + */ + RTCpuSetDelByIndex(&pDevExt->TscDeltaCpuSet, pGipCpuWorker->iCpuSet); + RTCpuSetAddByIndex(&pDevExt->TscDeltaObtainedCpuSet, pGipCpuWorker->iCpuSet); + } + } + } + } + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + if (pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_Measuring) + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Listening; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + Assert(rc != VERR_NOT_AVAILABLE); /* VERR_NOT_AVAILABLE is used as init value, see supdrvTscDeltaThreadInit(). */ + ASMAtomicWriteS32(&pDevExt->rcTscDelta, rc); + break; + } + + case kTscDeltaThreadState_Terminating: + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Destroyed; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + return VINF_SUCCESS; + + case kTscDeltaThreadState_Butchered: + default: + return supdrvTscDeltaThreadButchered(pDevExt, true /* fSpinlockHeld */, "Invalid state", VERR_INVALID_STATE); + } + } + /* not reached */ +} + + +/** + * Waits for the TSC-delta measurement thread to respond to a state change. + * + * @returns VINF_SUCCESS on success, VERR_TIMEOUT if it doesn't respond in time, + * other error code on internal error. + * + * @param pDevExt The device instance data. + * @param enmCurState The current state. + * @param enmNewState The new state we're waiting for it to enter. + */ +static int supdrvTscDeltaThreadWait(PSUPDRVDEVEXT pDevExt, SUPDRVTSCDELTATHREADSTATE enmCurState, + SUPDRVTSCDELTATHREADSTATE enmNewState) +{ + SUPDRVTSCDELTATHREADSTATE enmActualState; + int rc; + + /* + * Wait a short while for the expected state transition. + */ + RTSemEventWait(pDevExt->hTscDeltaEvent, RT_MS_1SEC); + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + enmActualState = pDevExt->enmTscDeltaThreadState; + if (enmActualState == enmNewState) + { + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + rc = VINF_SUCCESS; + } + else if (enmActualState == enmCurState) + { + /* + * Wait longer if the state has not yet transitioned to the one we want. + */ + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + rc = RTSemEventWait(pDevExt->hTscDeltaEvent, 50 * RT_MS_1SEC); + if ( RT_SUCCESS(rc) + || rc == VERR_TIMEOUT) + { + /* + * Check the state whether we've succeeded. + */ + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + enmActualState = pDevExt->enmTscDeltaThreadState; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + if (enmActualState == enmNewState) + rc = VINF_SUCCESS; + else if (enmActualState == enmCurState) + { + rc = VERR_TIMEOUT; + OSDBGPRINT(("supdrvTscDeltaThreadWait: timed out state transition. enmActualState=%d enmNewState=%d\n", + enmActualState, enmNewState)); + } + else + { + rc = VERR_INTERNAL_ERROR; + OSDBGPRINT(("supdrvTscDeltaThreadWait: invalid state transition from %d to %d, expected %d\n", enmCurState, + enmActualState, enmNewState)); + } + } + else + OSDBGPRINT(("supdrvTscDeltaThreadWait: RTSemEventWait failed. rc=%Rrc\n", rc)); + } + else + { + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + OSDBGPRINT(("supdrvTscDeltaThreadWait: invalid state %d when transitioning from %d to %d\n", + enmActualState, enmCurState, enmNewState)); + rc = VERR_INTERNAL_ERROR; + } + + return rc; +} + + +/** + * Signals the TSC-delta thread to start measuring TSC-deltas. + * + * @param pDevExt Pointer to the device instance data. + * @param fForceAll Force re-calculating TSC-deltas on all CPUs. + */ +static void supdrvTscDeltaThreadStartMeasurement(PSUPDRVDEVEXT pDevExt, bool fForceAll) +{ + if (pDevExt->hTscDeltaThread != NIL_RTTHREAD) + { + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + if ( pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_Listening + || pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_Measuring) + { + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_WaitAndMeasure; + if (fForceAll) + pDevExt->fTscThreadRecomputeAllDeltas = true; + } + else if ( pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_WaitAndMeasure + && fForceAll) + pDevExt->fTscThreadRecomputeAllDeltas = true; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + RTThreadUserSignal(pDevExt->hTscDeltaThread); + } +} + + +/** + * Terminates the actual thread running supdrvTscDeltaThread(). + * + * This is an internal worker function for supdrvTscDeltaThreadInit() and + * supdrvTscDeltaTerm(). + * + * @param pDevExt Pointer to the device instance data. + */ +static void supdrvTscDeltaThreadTerminate(PSUPDRVDEVEXT pDevExt) +{ + int rc; + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Terminating; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + RTThreadUserSignal(pDevExt->hTscDeltaThread); + rc = RTThreadWait(pDevExt->hTscDeltaThread, 50 * RT_MS_1SEC, NULL /* prc */); + if (RT_FAILURE(rc)) + { + /* Signal a few more times before giving up. */ + int cTriesLeft = 5; + while (--cTriesLeft > 0) + { + RTThreadUserSignal(pDevExt->hTscDeltaThread); + rc = RTThreadWait(pDevExt->hTscDeltaThread, 2 * RT_MS_1SEC, NULL /* prc */); + if (rc != VERR_TIMEOUT) + break; + } + } +} + + +/** + * Initializes and spawns the TSC-delta measurement thread. + * + * A thread is required for servicing re-measurement requests from events like + * CPUs coming online, suspend/resume etc. as it cannot be done synchronously + * under all contexts on all OSs. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * + * @remarks Must only be called -after- initializing GIP and setting up MP + * notifications! + */ +static int supdrvTscDeltaThreadInit(PSUPDRVDEVEXT pDevExt) +{ + int rc; + Assert(pDevExt->pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED); + rc = RTSpinlockCreate(&pDevExt->hTscDeltaSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "VBoxTscSpnLck"); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pDevExt->hTscDeltaEvent); + if (RT_SUCCESS(rc)) + { + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_Creating; + pDevExt->cMsTscDeltaTimeout = 60000; + rc = RTThreadCreate(&pDevExt->hTscDeltaThread, supdrvTscDeltaThread, pDevExt, 0 /* cbStack */, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "VBoxTscThread"); + if (RT_SUCCESS(rc)) + { + rc = supdrvTscDeltaThreadWait(pDevExt, kTscDeltaThreadState_Creating, kTscDeltaThreadState_Listening); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteS32(&pDevExt->rcTscDelta, VERR_NOT_AVAILABLE); + return rc; + } + + OSDBGPRINT(("supdrvTscDeltaInit: supdrvTscDeltaThreadWait failed. rc=%Rrc\n", rc)); + supdrvTscDeltaThreadTerminate(pDevExt); + } + else + OSDBGPRINT(("supdrvTscDeltaInit: RTThreadCreate failed. rc=%Rrc\n", rc)); + RTSemEventDestroy(pDevExt->hTscDeltaEvent); + pDevExt->hTscDeltaEvent = NIL_RTSEMEVENT; + } + else + OSDBGPRINT(("supdrvTscDeltaInit: RTSemEventCreate failed. rc=%Rrc\n", rc)); + RTSpinlockDestroy(pDevExt->hTscDeltaSpinlock); + pDevExt->hTscDeltaSpinlock = NIL_RTSPINLOCK; + } + else + OSDBGPRINT(("supdrvTscDeltaInit: RTSpinlockCreate failed. rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Terminates the TSC-delta measurement thread and cleanup. + * + * @param pDevExt Pointer to the device instance data. + */ +static void supdrvTscDeltaTerm(PSUPDRVDEVEXT pDevExt) +{ + if ( pDevExt->hTscDeltaSpinlock != NIL_RTSPINLOCK + && pDevExt->hTscDeltaEvent != NIL_RTSEMEVENT) + { + supdrvTscDeltaThreadTerminate(pDevExt); + } + + if (pDevExt->hTscDeltaSpinlock != NIL_RTSPINLOCK) + { + RTSpinlockDestroy(pDevExt->hTscDeltaSpinlock); + pDevExt->hTscDeltaSpinlock = NIL_RTSPINLOCK; + } + + if (pDevExt->hTscDeltaEvent != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pDevExt->hTscDeltaEvent); + pDevExt->hTscDeltaEvent = NIL_RTSEMEVENT; + } + + ASMAtomicWriteS32(&pDevExt->rcTscDelta, VERR_NOT_AVAILABLE); +} + +#endif /* SUPDRV_USE_TSC_DELTA_THREAD */ + +/** + * Measure the TSC delta for the CPU given by its CPU set index. + * + * @returns VBox status code. + * @retval VERR_INTERRUPTED if interrupted while waiting. + * @retval VERR_SUPDRV_TSC_DELTA_MEASUREMENT_FAILED if we were unable to get a + * measurement. + * @retval VERR_CPU_OFFLINE if the specified CPU is offline. + * + * @param pSession The caller's session. GIP must've been mapped. + * @param iCpuSet The CPU set index of the CPU to measure. + * @param fFlags Flags, SUP_TSCDELTA_MEASURE_F_XXX. + * @param cMsWaitRetry Number of milliseconds to wait between each retry. + * @param cMsWaitThread Number of milliseconds to wait for the thread to get + * ready. + * @param cTries Number of times to try, pass 0 for the default. + */ +SUPR0DECL(int) SUPR0TscDeltaMeasureBySetIndex(PSUPDRVSESSION pSession, uint32_t iCpuSet, uint32_t fFlags, + RTMSINTERVAL cMsWaitRetry, RTMSINTERVAL cMsWaitThread, uint32_t cTries) +{ + PSUPDRVDEVEXT pDevExt; + PSUPGLOBALINFOPAGE pGip; + uint16_t iGipCpu; + int rc; +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + uint64_t msTsStartWait; + uint32_t iWaitLoop; +#endif + + /* + * Validate and adjust the input. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (!pSession->fGipReferenced) + return VERR_WRONG_ORDER; + + pDevExt = pSession->pDevExt; + AssertReturn(SUP_IS_DEVEXT_VALID(pDevExt), VERR_INVALID_PARAMETER); + + pGip = pDevExt->pGip; + AssertPtrReturn(pGip, VERR_INTERNAL_ERROR_2); + + AssertReturn(iCpuSet < RTCPUSET_MAX_CPUS, VERR_INVALID_CPU_INDEX); + AssertReturn(iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx), VERR_INVALID_CPU_INDEX); + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + AssertReturn(iGipCpu < pGip->cCpus, VERR_INVALID_CPU_INDEX); + + if (fFlags & ~SUP_TSCDELTA_MEASURE_F_VALID_MASK) + return VERR_INVALID_FLAGS; + + /* + * The request is a noop if the TSC delta isn't being used. + */ + if (pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ZERO_CLAIMED) + return VINF_SUCCESS; + + if (cTries == 0) + cTries = 12; + else if (cTries > 256) + cTries = 256; + + if (cMsWaitRetry == 0) + cMsWaitRetry = 2; + else if (cMsWaitRetry > 1000) + cMsWaitRetry = 1000; + +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + /* + * Has the TSC already been measured and we're not forced to redo it? + */ + if ( pGip->aCPUs[iGipCpu].i64TSCDelta != INT64_MAX + && !(fFlags & SUP_TSCDELTA_MEASURE_F_FORCE)) + return VINF_SUCCESS; + + /* + * Asynchronous request? Forward it to the thread, no waiting. + */ + if (fFlags & SUP_TSCDELTA_MEASURE_F_ASYNC) + { + /** @todo Async. doesn't implement options like retries, waiting. We'll need + * to pass those options to the thread somehow and implement it in the + * thread. Check if anyone uses/needs fAsync before implementing this. */ + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + RTCpuSetAddByIndex(&pDevExt->TscDeltaCpuSet, iCpuSet); + if ( pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_Listening + || pDevExt->enmTscDeltaThreadState == kTscDeltaThreadState_Measuring) + { + pDevExt->enmTscDeltaThreadState = kTscDeltaThreadState_WaitAndMeasure; + rc = VINF_SUCCESS; + } + else if (pDevExt->enmTscDeltaThreadState != kTscDeltaThreadState_WaitAndMeasure) + rc = VERR_THREAD_IS_DEAD; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + RTThreadUserSignal(pDevExt->hTscDeltaThread); + return VINF_SUCCESS; + } + + /* + * If a TSC-delta measurement request is already being serviced by the thread, + * wait 'cTries' times if a retry-timeout is provided, otherwise bail as busy. + */ + msTsStartWait = RTTimeSystemMilliTS(); + for (iWaitLoop = 0;; iWaitLoop++) + { + uint64_t cMsElapsed; + SUPDRVTSCDELTATHREADSTATE enmState; + RTSpinlockAcquire(pDevExt->hTscDeltaSpinlock); + enmState = pDevExt->enmTscDeltaThreadState; + RTSpinlockRelease(pDevExt->hTscDeltaSpinlock); + + if (enmState == kTscDeltaThreadState_Measuring) + { /* Must wait, the thread is busy. */ } + else if (enmState == kTscDeltaThreadState_WaitAndMeasure) + { /* Must wait, this state only says what will happen next. */ } + else if (enmState == kTscDeltaThreadState_Terminating) + { /* Must wait, this state only says what should happen next. */ } + else + break; /* All other states, the thread is either idly listening or dead. */ + + /* Wait or fail. */ + if (cMsWaitThread == 0) + return VERR_SUPDRV_TSC_DELTA_MEASUREMENT_BUSY; + cMsElapsed = RTTimeSystemMilliTS() - msTsStartWait; + if (cMsElapsed >= cMsWaitThread) + return VERR_SUPDRV_TSC_DELTA_MEASUREMENT_BUSY; + + rc = RTThreadSleep(RT_MIN((RTMSINTERVAL)(cMsWaitThread - cMsElapsed), RT_MIN(iWaitLoop + 1, 10))); + if (rc == VERR_INTERRUPTED) + return rc; + } +#endif /* SUPDRV_USE_TSC_DELTA_THREAD */ + + /* + * Try measure the TSC delta the given number of times. + */ + for (;;) + { + /* Unless we're forced to measure the delta, check whether it's done already. */ + if ( !(fFlags & SUP_TSCDELTA_MEASURE_F_FORCE) + && pGip->aCPUs[iGipCpu].i64TSCDelta != INT64_MAX) + { + rc = VINF_SUCCESS; + break; + } + + /* Measure it. */ + rc = supdrvTscMeasureDeltaOne(pDevExt, iGipCpu); + if (rc != VERR_SUPDRV_TSC_DELTA_MEASUREMENT_FAILED) + { + Assert(pGip->aCPUs[iGipCpu].i64TSCDelta != INT64_MAX || RT_FAILURE_NP(rc)); + break; + } + + /* Retry? */ + if (cTries <= 1) + break; + cTries--; + + /* Always delay between retries (be nice to the rest of the system + and avoid the BSOD hounds). */ + rc = RTThreadSleep(cMsWaitRetry); + if (rc == VERR_INTERRUPTED) + break; + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0TscDeltaMeasureBySetIndex); + + +/** + * Service a TSC-delta measurement request. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param pSession The support driver session. + * @param pReq Pointer to the TSC-delta measurement request. + */ +int VBOXCALL supdrvIOCtl_TscDeltaMeasure(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPTSCDELTAMEASURE pReq) +{ + uint32_t cTries; + uint32_t iCpuSet; + uint32_t fFlags; + RTMSINTERVAL cMsWaitRetry; + RT_NOREF1(pDevExt); + + /* + * Validate and adjust/resolve the input so they can be passed onto SUPR0TscDeltaMeasureBySetIndex. + */ + AssertPtr(pDevExt); AssertPtr(pSession); AssertPtr(pReq); /* paranoia^2 */ + + if (pReq->u.In.idCpu == NIL_RTCPUID) + return VERR_INVALID_CPU_ID; + iCpuSet = RTMpCpuIdToSetIndex(pReq->u.In.idCpu); + if (iCpuSet >= RTCPUSET_MAX_CPUS) + return VERR_INVALID_CPU_ID; + + cTries = pReq->u.In.cRetries == 0 ? 0 : (uint32_t)pReq->u.In.cRetries + 1; + + cMsWaitRetry = RT_MAX(pReq->u.In.cMsWaitRetry, 5); + + fFlags = 0; + if (pReq->u.In.fAsync) + fFlags |= SUP_TSCDELTA_MEASURE_F_ASYNC; + if (pReq->u.In.fForce) + fFlags |= SUP_TSCDELTA_MEASURE_F_FORCE; + + return SUPR0TscDeltaMeasureBySetIndex(pSession, iCpuSet, fFlags, cMsWaitRetry, + cTries == 0 ? 5 * RT_MS_1SEC : cMsWaitRetry * cTries /*cMsWaitThread*/, + cTries); +} + + +/** + * Reads TSC with delta applied. + * + * Will try to resolve delta value INT64_MAX before applying it. This is the + * main purpose of this function, to handle the case where the delta needs to be + * determined. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param pSession The support driver session. + * @param pReq Pointer to the TSC-read request. + */ +int VBOXCALL supdrvIOCtl_TscRead(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPTSCREAD pReq) +{ + PSUPGLOBALINFOPAGE pGip; + int rc; + + /* + * Validate. We require the client to have mapped GIP (no asserting on + * ring-3 preconditions). + */ + AssertPtr(pDevExt); AssertPtr(pReq); AssertPtr(pSession); /* paranoia^2 */ + if (pSession->GipMapObjR3 == NIL_RTR0MEMOBJ) + return VERR_WRONG_ORDER; + pGip = pDevExt->pGip; + AssertReturn(pGip, VERR_INTERNAL_ERROR_2); + + /* + * We're usually here because we need to apply delta, but we shouldn't be + * upset if the GIP is some different mode. + */ + if (pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_ZERO_CLAIMED) + { + uint32_t cTries = 0; + for (;;) + { + /* + * Start by gathering the data, using CLI for disabling preemption + * while we do that. + */ + RTCCUINTREG fEFlags = ASMIntDisableFlags(); + int iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + int iGipCpu = 0; /* gcc maybe used uninitialized */ + if (RT_LIKELY( (unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + && (iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]) < pGip->cCpus )) + { + int64_t i64Delta = pGip->aCPUs[iGipCpu].i64TSCDelta; + pReq->u.Out.idApic = pGip->aCPUs[iGipCpu].idApic; + pReq->u.Out.u64AdjustedTsc = ASMReadTSC(); + ASMSetFlags(fEFlags); + + /* + * If we're lucky we've got a delta, but no predictions here + * as this I/O control is normally only used when the TSC delta + * is set to INT64_MAX. + */ + if (i64Delta != INT64_MAX) + { + pReq->u.Out.u64AdjustedTsc -= i64Delta; + rc = VINF_SUCCESS; + break; + } + + /* Give up after a few times. */ + if (cTries >= 4) + { + rc = VWRN_SUPDRV_TSC_DELTA_MEASUREMENT_FAILED; + break; + } + + /* Need to measure the delta an try again. */ + rc = supdrvTscMeasureDeltaOne(pDevExt, iGipCpu); + Assert(pGip->aCPUs[iGipCpu].i64TSCDelta != INT64_MAX || RT_FAILURE_NP(rc)); + /** @todo should probably delay on failure... dpc watchdogs */ + } + else + { + /* This really shouldn't happen. */ + AssertMsgFailed(("idCpu=%#x iCpuSet=%#x (%d)\n", RTMpCpuId(), iCpuSet, iCpuSet)); + pReq->u.Out.idApic = supdrvGipGetApicIdSlow(); + pReq->u.Out.u64AdjustedTsc = ASMReadTSC(); + ASMSetFlags(fEFlags); + rc = VERR_INTERNAL_ERROR_5; /** @todo change to warning. */ + break; + } + } + } + else + { + /* + * No delta to apply. Easy. Deal with preemption the lazy way. + */ + RTCCUINTREG fEFlags = ASMIntDisableFlags(); + int iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + int iGipCpu = 0; /* gcc may be used uninitialized */ + if (RT_LIKELY( (unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) + && (iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]) < pGip->cCpus )) + pReq->u.Out.idApic = pGip->aCPUs[iGipCpu].idApic; + else + pReq->u.Out.idApic = supdrvGipGetApicIdSlow(); + pReq->u.Out.u64AdjustedTsc = ASMReadTSC(); + ASMSetFlags(fEFlags); + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Worker for supdrvIOCtl_GipSetFlags. + * + * @returns VBox status code. + * @retval VERR_WRONG_ORDER if an enable-once-per-session flag is set again for + * a session. + * + * @param pDevExt Pointer to the device instance data. + * @param pSession The support driver session. + * @param fOrMask The OR mask of the GIP flags, see SUPGIP_FLAGS_XXX. + * @param fAndMask The AND mask of the GIP flags, see SUPGIP_FLAGS_XXX. + * + * @remarks Caller must own the GIP mutex. + * + * @remarks This function doesn't validate any of the flags. + */ +static int supdrvGipSetFlags(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t fOrMask, uint32_t fAndMask) +{ + uint32_t cRefs; + PSUPGLOBALINFOPAGE pGip = pDevExt->pGip; + AssertMsg((fOrMask & fAndMask) == fOrMask, ("%#x & %#x\n", fOrMask, fAndMask)); /* ASSUMED by code below */ + + /* + * Compute GIP test-mode flags. + */ + if (fOrMask & SUPGIP_FLAGS_TESTING_ENABLE) + { + if (!pSession->fGipTestMode) + { + Assert(pDevExt->cGipTestModeRefs < _64K); + pSession->fGipTestMode = true; + cRefs = ++pDevExt->cGipTestModeRefs; + if (cRefs == 1) + { + fOrMask |= SUPGIP_FLAGS_TESTING | SUPGIP_FLAGS_TESTING_START; + fAndMask &= ~SUPGIP_FLAGS_TESTING_STOP; + } + } + else + { + LogRelMax(10, ("supdrvGipSetFlags: SUPGIP_FLAGS_TESTING_ENABLE already set for this session\n")); + return VERR_WRONG_ORDER; + } + } + else if ( !(fAndMask & SUPGIP_FLAGS_TESTING_ENABLE) + && pSession->fGipTestMode) + { + Assert(pDevExt->cGipTestModeRefs > 0); + Assert(pDevExt->cGipTestModeRefs < _64K); + pSession->fGipTestMode = false; + cRefs = --pDevExt->cGipTestModeRefs; + if (!cRefs) + fOrMask |= SUPGIP_FLAGS_TESTING_STOP; + else + fAndMask |= SUPGIP_FLAGS_TESTING_ENABLE; + } + + /* + * Commit the flags. This should be done as atomically as possible + * since the flag consumers won't be holding the GIP mutex. + */ + ASMAtomicOrU32(&pGip->fFlags, fOrMask); + ASMAtomicAndU32(&pGip->fFlags, fAndMask); + + return VINF_SUCCESS; +} + + +/** + * Sets GIP test mode parameters. + * + * @returns VBox status code. + * @param pDevExt Pointer to the device instance data. + * @param pSession The support driver session. + * @param fOrMask The OR mask of the GIP flags, see SUPGIP_FLAGS_XXX. + * @param fAndMask The AND mask of the GIP flags, see SUPGIP_FLAGS_XXX. + */ +int VBOXCALL supdrvIOCtl_GipSetFlags(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t fOrMask, uint32_t fAndMask) +{ + PSUPGLOBALINFOPAGE pGip; + int rc; + + /* + * Validate. We require the client to have mapped GIP (no asserting on + * ring-3 preconditions). + */ + AssertPtr(pDevExt); AssertPtr(pSession); /* paranoia^2 */ + if (pSession->GipMapObjR3 == NIL_RTR0MEMOBJ) + return VERR_WRONG_ORDER; + pGip = pDevExt->pGip; + AssertReturn(pGip, VERR_INTERNAL_ERROR_3); + + if (fOrMask & ~SUPGIP_FLAGS_VALID_MASK) + return VERR_INVALID_PARAMETER; + if ((fAndMask & ~SUPGIP_FLAGS_VALID_MASK) != ~SUPGIP_FLAGS_VALID_MASK) + return VERR_INVALID_PARAMETER; + + /* + * Don't confuse supdrvGipSetFlags or anyone else by both setting + * and clearing the same flags. AND takes precedence. + */ + fOrMask &= fAndMask; + + /* + * Take the loader lock to avoid having to think about races between two + * clients changing the flags at the same time (state is not simple). + */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRequest(pDevExt->mtxGip, RT_INDEFINITE_WAIT); +#else + RTSemFastMutexRequest(pDevExt->mtxGip); +#endif + + rc = supdrvGipSetFlags(pDevExt, pSession, fOrMask, fAndMask); + +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSemMutexRelease(pDevExt->mtxGip); +#else + RTSemFastMutexRelease(pDevExt->mtxGip); +#endif + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/SUPDrvIDC.h b/src/VBox/HostDrivers/Support/SUPDrvIDC.h new file mode 100644 index 00000000..5e27c396 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvIDC.h @@ -0,0 +1,288 @@ +/* $Id: SUPDrvIDC.h $ */ +/** @file + * VirtualBox Support Driver - Inter-Driver Communication (IDC) definitions. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPDrvIDC_h +#define VBOX_INCLUDED_SRC_Support_SUPDrvIDC_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/types.h> + +/** @def SUP_IDC_CODE + * Creates IDC function code. + * + * @param Function The function number to encode, 1..255. + * + * @remarks We can take a slightly more relaxed attitude wrt to size encoding + * here since only windows will use standard I/O control function code. + * + * @{ + */ + +#ifdef RT_OS_WINDOWS +# define SUP_IDC_CODE(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, (Function) + 2542, METHOD_BUFFERED, FILE_WRITE_ACCESS) +#else +# define SUP_IDC_CODE(Function) ( UINT32_C(0xc0ffee00) | (uint32_t)(0x000000ff & (Function)) ) +#endif + + +#ifdef RT_ARCH_AMD64 +# pragma pack(8) /* paranoia. */ +#else +# pragma pack(4) /* paranoia. */ +#endif + + +/** + * An IDC request packet header. + * + * The main purpose of this header is to pass the session handle + * and status code in a generic manner in order to make things + * easier on the receiving end. + */ +typedef struct SUPDRVIDCREQHDR +{ + /** IN: The size of the request. */ + uint32_t cb; + /** OUT: Status code of the request. */ + int32_t rc; + /** IN: Pointer to the session handle. */ + PSUPDRVSESSION pSession; +#if ARCH_BITS == 32 + /** Padding the structure to 16-bytes. */ + uint32_t u32Padding; +#endif +} SUPDRVIDCREQHDR; +/** Pointer to an IDC request packet header. */ +typedef SUPDRVIDCREQHDR *PSUPDRVIDCREQHDR; +/** Pointer to a const IDC request packet header. */ +typedef SUPDRVIDCREQHDR const *PCSUPDRVIDCREQHDR; + + +/** + * SUPDRV IDC: Connect request. + * This request takes a SUPDRVIDCREQCONNECT packet. + */ +#define SUPDRV_IDC_REQ_CONNECT SUP_IDC_CODE(1) +/** A SUPDRV IDC connect request packet. */ +typedef struct SUPDRVIDCREQCONNECT +{ + /** The request header. */ + SUPDRVIDCREQHDR Hdr; + /** The payload union. */ + union + { + /** The input. */ + struct SUPDRVIDCREQCONNECTIN + { + /** The magic cookie (SUPDRVIDCREQ_CONNECT_MAGIC_COOKIE). */ + uint32_t u32MagicCookie; + /** The desired version of the IDC interface. */ + uint32_t uReqVersion; + /** The minimum version of the IDC interface. */ + uint32_t uMinVersion; + } In; + + /** The output. */ + struct SUPDRVIDCREQCONNECTOUT + { + /** The support driver session. (An opaque.) */ + PSUPDRVSESSION pSession; + /** The version of the IDC interface for this session. */ + uint32_t uSessionVersion; + /** The version of the IDC interface . */ + uint32_t uDriverVersion; + /** The SVN revision of the driver. + * This will be set to 0 if not compiled into the driver. */ + uint32_t uDriverRevision; + } Out; + } u; +} SUPDRVIDCREQCONNECT; +/** Pointer to a SUPDRV IDC connect request. */ +typedef SUPDRVIDCREQCONNECT *PSUPDRVIDCREQCONNECT; +/** Magic cookie value (SUPDRVIDCREQCONNECT::In.u32MagicCookie). ('tori') */ +#define SUPDRVIDCREQ_CONNECT_MAGIC_COOKIE UINT32_C(0x69726f74) + + +/** + * SUPDRV IDC: Disconnect request. + * This request only requires a SUPDRVIDCREQHDR. + */ +#define SUPDRV_IDC_REQ_DISCONNECT SUP_IDC_CODE(2) + + +/** + * SUPDRV IDC: Query a symbol address. + * This request takes a SUPDRVIDCREQGETSYM packet. + */ +#define SUPDRV_IDC_REQ_GET_SYMBOL SUP_IDC_CODE(3) +/** A SUPDRV IDC get symbol request packet. */ +typedef struct SUPDRVIDCREQGETSYM +{ + /** The request header. */ + SUPDRVIDCREQHDR Hdr; + /** The payload union. */ + union + { + /** The input. */ + struct SUPDRVIDCREQGETSYMIN + { + /** The module name. + * NULL is an alias for the support driver. */ + const char *pszModule; + /** The symbol name. */ + const char *pszSymbol; + } In; + + /** The output. */ + struct SUPDRVIDCREQGETSYMOUT + { + /** The symbol address. */ + PFNRT pfnSymbol; + } Out; + } u; +} SUPDRVIDCREQGETSYM; +/** Pointer to a SUPDRV IDC get symbol request. */ +typedef SUPDRVIDCREQGETSYM *PSUPDRVIDCREQGETSYM; + + +/** + * SUPDRV IDC: Request the registration of a component factory. + * This request takes a SUPDRVIDCREQCOMPREGFACTORY packet. + */ +#define SUPDRV_IDC_REQ_COMPONENT_REGISTER_FACTORY SUP_IDC_CODE(10) +/** A SUPDRV IDC register component factory request packet. */ +typedef struct SUPDRVIDCREQCOMPREGFACTORY +{ + /** The request header. */ + SUPDRVIDCREQHDR Hdr; + /** The payload union. */ + union + { + /** The input. */ + struct SUPDRVIDCREQCOMPREGFACTORYIN + { + /** Pointer to the factory. */ + PCSUPDRVFACTORY pFactory; + } In; + } u; +} SUPDRVIDCREQCOMPREGFACTORY; +/** Pointer to a SUPDRV IDC register component factory request. */ +typedef SUPDRVIDCREQCOMPREGFACTORY *PSUPDRVIDCREQCOMPREGFACTORY; + + +/** + * SUPDRV IDC: Deregister a component factory. + * This request takes a SUPDRVIDCREQCOMPDEREGFACTORY packet. + */ +#define SUPDRV_IDC_REQ_COMPONENT_DEREGISTER_FACTORY SUP_IDC_CODE(11) +/** A SUPDRV IDC deregister component factory request packet. */ +typedef struct SUPDRVIDCREQCOMPDEREGFACTORY +{ + /** The request header. */ + SUPDRVIDCREQHDR Hdr; + /** The payload union. */ + union + { + /** The input. */ + struct SUPDRVIDCREQCOMPDEREGFACTORYIN + { + /** Pointer to the factory. */ + PCSUPDRVFACTORY pFactory; + } In; + } u; +} SUPDRVIDCREQCOMPDEREGFACTORY; +/** Pointer to a SUPDRV IDC deregister component factory request. */ +typedef SUPDRVIDCREQCOMPDEREGFACTORY *PSUPDRVIDCREQCOMPDEREGFACTORY; + + +/* + * The OS specific prototypes. + * Most OSes uses + */ +RT_C_DECLS_BEGIN + +#if defined(RT_OS_DARWIN) +# ifdef IN_SUP_R0 +extern DECLEXPORT(int) VBOXCALL SUPDrvDarwinIDC(uint32_t iReq, PSUPDRVIDCREQHDR pReq); +# else +extern DECLIMPORT(int) VBOXCALL SUPDrvDarwinIDC(uint32_t iReq, PSUPDRVIDCREQHDR pReq); +# endif + +#elif defined(RT_OS_FREEBSD) +extern int VBOXCALL SUPDrvFreeBSDIDC(uint32_t iReq, PSUPDRVIDCREQHDR pReq); + +#elif defined(RT_OS_LINUX) +extern int VBOXCALL SUPDrvLinuxIDC(uint32_t iReq, PSUPDRVIDCREQHDR pReq); + +#elif defined(RT_OS_OS2) +/** @todo Port to OS/2. */ + +#elif defined(RT_OS_SOLARIS) +extern int VBOXCALL SUPDrvSolarisIDC(uint32_t iReq, PSUPDRVIDCREQHDR pReq); + +#elif defined(RT_OS_WINDOWS) +/* Nothing special for windows. */ + +#else +/* PORTME: OS specific IDC stuff goes here. */ +#endif + +RT_C_DECLS_END + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code indicating the validity of the session, request and + * the return data packet. The status of the request it self is found + * in the packet (specific to each request). + * + * @param pSession The session. (This is NULL for SUPDRV_IDC_REQ_CONNECT.) + * @param uReq The request number. + * @param pvReq Pointer to the request packet. Optional for some requests. + * @param cbReq The size of the request packet. + */ +/** @todo move this and change to function proto */ +typedef DECLCALLBACKTYPE(int, FNSUPDRVIDCENTRY,(PSUPDRVSESSION pSession, uint32_t uReq, void *pvReq, uint32_t cbReq)); + +/** @} */ + + +#pragma pack() /* paranoia */ + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPDrvIDC_h */ + diff --git a/src/VBox/HostDrivers/Support/SUPDrvIOC.h b/src/VBox/HostDrivers/Support/SUPDrvIOC.h new file mode 100644 index 00000000..3e3cd0b0 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvIOC.h @@ -0,0 +1,1725 @@ +/* $Id: SUPDrvIOC.h $ */ +/** @file + * VirtualBox Support Driver - IOCtl definitions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPDrvIOC_h +#define VBOX_INCLUDED_SRC_Support_SUPDrvIOC_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <VBox/sup.h> + +/* + * IOCtl numbers. + * We're using the Win32 type of numbers here, thus the macros below. + * The SUP_IOCTL_FLAG macro is used to separate requests from 32-bit + * and 64-bit processes. + */ +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_SPARC64) || defined(RT_ARCH_ARM64) +# define SUP_IOCTL_FLAG 128 +#elif defined(RT_ARCH_X86) || defined(RT_ARCH_SPARC) || defined(RT_ARCH_ARM32) +# define SUP_IOCTL_FLAG 0 +#else +# error "dunno which arch this is!" +#endif + +#ifdef RT_OS_WINDOWS +# ifndef CTL_CODE +# include <iprt/win/windows.h> +# endif + /* Automatic buffering, size not encoded. */ +# define SUP_CTL_CODE_SIZE(Function, Size) CTL_CODE(FILE_DEVICE_UNKNOWN, (Function) | SUP_IOCTL_FLAG, METHOD_BUFFERED, FILE_WRITE_ACCESS) +# define SUP_CTL_CODE_BIG(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, (Function) | SUP_IOCTL_FLAG, METHOD_BUFFERED, FILE_WRITE_ACCESS) +# define SUP_CTL_CODE_FAST(Function) CTL_CODE(FILE_DEVICE_UNKNOWN, (Function) | SUP_IOCTL_FLAG, METHOD_NEITHER, FILE_WRITE_ACCESS) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) (uIOCtl) + +# define SUP_NT_STATUS_BASE UINT32_C(0xe9860000) /**< STATUS_SEVERITY_ERROR + C-bit + facility 0x986. */ +# define SUP_NT_STATUS_IS_VBOX(a_rcNt) ( ((uint32_t)(a_rcNt) & 0xffff0000) == SUP_NT_STATUS_BASE ) +# define SUP_NT_STATUS_TO_VBOX(a_rcNt) ( (int)((uint32_t)(a_rcNt) | UINT32_C(0xffff0000)) ) + +/** NT device name for system access. */ +# define SUPDRV_NT_DEVICE_NAME_SYS L"\\Device\\VBoxDrv" +/** NT device name for user access. */ +# define SUPDRV_NT_DEVICE_NAME_USR L"\\Device\\VBoxDrvU" +# ifdef VBOX_WITH_HARDENING +/** NT device name for hardened stub access. */ +# define SUPDRV_NT_DEVICE_NAME_STUB L"\\Device\\VBoxDrvStub" +/** NT device name for getting error information for failed VBoxDrv or + * VBoxDrvStub open. */ +# define SUPDRV_NT_DEVICE_NAME_ERROR_INFO L"\\Device\\VBoxDrvErrorInfo" +# endif + + +#elif defined(RT_OS_SOLARIS) + /* No automatic buffering, size limited to 255 bytes. */ +# include <sys/ioccom.h> +# define SUP_CTL_CODE_SIZE(Function, Size) _IOWRN('V', (Function) | SUP_IOCTL_FLAG, sizeof(SUPREQHDR)) +# define SUP_CTL_CODE_BIG(Function) _IOWRN('V', (Function) | SUP_IOCTL_FLAG, sizeof(SUPREQHDR)) +# define SUP_CTL_CODE_FAST(Function) _IO( 'V', (Function) | SUP_IOCTL_FLAG) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) ((uintptr_t)(uIOCtl)) + +#elif defined(RT_OS_OS2) + /* No automatic buffering, size not encoded. */ +# define SUP_CTL_CATEGORY 0xc0 +# define SUP_CTL_CODE_SIZE(Function, Size) ((unsigned char)(Function)) +# define SUP_CTL_CODE_BIG(Function) ((unsigned char)(Function)) +# define SUP_CTL_CATEGORY_FAST 0xc1 +# define SUP_CTL_CODE_FAST(Function) ((unsigned char)(Function)) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) (uIOCtl) + +#elif defined(RT_OS_LINUX) + /* No automatic buffering, size limited to 16KB. */ +# include <linux/ioctl.h> +# define SUP_CTL_CODE_SIZE(Function, Size) _IOC(_IOC_READ | _IOC_WRITE, 'V', (Function) | SUP_IOCTL_FLAG, (Size)) +# define SUP_CTL_CODE_BIG(Function) _IO('V', (Function) | SUP_IOCTL_FLAG) +# define SUP_CTL_CODE_FAST(Function) _IO('V', (Function) | SUP_IOCTL_FLAG) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) ((uIOCtl) & ~IOCSIZE_MASK) + +#elif defined(RT_OS_L4) + /* Implemented in suplib, no worries. */ +# define SUP_CTL_CODE_SIZE(Function, Size) (Function) +# define SUP_CTL_CODE_BIG(Function) (Function) +# define SUP_CTL_CODE_FAST(Function) (Function) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) (uIOCtl) + +#else /* BSD Like */ + /* Automatic buffering, size limited to 4KB on *BSD and 8KB on Darwin - commands the limit, 4KB. */ +# include <sys/ioccom.h> +# define SUP_CTL_CODE_SIZE(Function, Size) _IOC(IOC_INOUT, 'V', (Function) | SUP_IOCTL_FLAG, (Size)) +# define SUP_CTL_CODE_BIG(Function) _IO('V', (Function) | SUP_IOCTL_FLAG) +# define SUP_CTL_CODE_FAST(Function) _IO('V', (Function) | SUP_IOCTL_FLAG) +# define SUP_CTL_CODE_NO_SIZE(uIOCtl) ( (uIOCtl) & ~_IOC(0,0,0,IOCPARM_MASK) ) +#endif + +/** @name Fast path I/O control codes. + * @note These must run parallel to SUP_VMMR0_DO_XXX + * @note Implementations ASSUMES up to 32 I/O controls codes in the fast range. + * @{ */ +/** Fast path IOCtl: VMMR0_DO_HM_RUN */ +#define SUP_IOCTL_FAST_DO_HM_RUN SUP_CTL_CODE_FAST(64) +/** Fast path IOCtl: VMMR0_DO_NEM_RUN */ +#define SUP_IOCTL_FAST_DO_NEM_RUN SUP_CTL_CODE_FAST(65) +/** Just a NOP call for profiling the latency of a fast ioctl call to VMMR0. */ +#define SUP_IOCTL_FAST_DO_NOP SUP_CTL_CODE_FAST(66) +/** First fast path IOCtl number. */ +#define SUP_IOCTL_FAST_DO_FIRST SUP_IOCTL_FAST_DO_HM_RUN +/** @} */ + + +#ifdef RT_OS_DARWIN +/** Cookie used to fend off some unwanted clients to the IOService. */ +# define SUP_DARWIN_IOSERVICE_COOKIE 0x64726962 /* 'bird' */ +#endif + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +#ifdef RT_ARCH_AMD64 +# pragma pack(8) /* paranoia. */ +#elif defined(RT_ARCH_X86) +# pragma pack(4) /* paranoia. */ +#endif + + +/** + * Common In/Out header. + */ +typedef struct SUPREQHDR +{ + /** Cookie. */ + uint32_t u32Cookie; + /** Session cookie. */ + uint32_t u32SessionCookie; + /** The size of the input. */ + uint32_t cbIn; + /** The size of the output. */ + uint32_t cbOut; + /** Flags. See SUPREQHDR_FLAGS_* for details and values. */ + uint32_t fFlags; + /** The VBox status code of the operation, out direction only. */ + int32_t rc; +} SUPREQHDR; +/** Pointer to a IOC header. */ +typedef SUPREQHDR *PSUPREQHDR; + +/** @name SUPREQHDR::fFlags values + * @{ */ +/** Masks out the magic value. */ +#define SUPREQHDR_FLAGS_MAGIC_MASK UINT32_C(0xff0000ff) +/** The generic mask. */ +#define SUPREQHDR_FLAGS_GEN_MASK UINT32_C(0x0000ff00) +/** The request specific mask. */ +#define SUPREQHDR_FLAGS_REQ_MASK UINT32_C(0x00ff0000) + +/** There is extra input that needs copying on some platforms. */ +#define SUPREQHDR_FLAGS_EXTRA_IN UINT32_C(0x00000100) +/** There is extra output that needs copying on some platforms. */ +#define SUPREQHDR_FLAGS_EXTRA_OUT UINT32_C(0x00000200) + +/** The magic value. */ +#define SUPREQHDR_FLAGS_MAGIC UINT32_C(0x42000042) +/** The default value. Use this when no special stuff is requested. */ +#define SUPREQHDR_FLAGS_DEFAULT SUPREQHDR_FLAGS_MAGIC +/** @} */ + + +/** @name SUP_IOCTL_COOKIE + * @{ + */ +/** Negotiate cookie. */ +#define SUP_IOCTL_COOKIE SUP_CTL_CODE_SIZE(1, SUP_IOCTL_COOKIE_SIZE) +/** The request size. */ +#define SUP_IOCTL_COOKIE_SIZE sizeof(SUPCOOKIE) +/** The SUPREQHDR::cbIn value. */ +#define SUP_IOCTL_COOKIE_SIZE_IN sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPCOOKIE, u.In) +/** The SUPREQHDR::cbOut value. */ +#define SUP_IOCTL_COOKIE_SIZE_OUT sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPCOOKIE, u.Out) +/** SUPCOOKIE_IN magic word. */ +#define SUPCOOKIE_MAGIC "The Magic Word!" +/** The initial cookie. */ +#define SUPCOOKIE_INITIAL_COOKIE 0x69726f74 /* 'tori' */ + +/** Current interface version. + * The upper 16-bit is the major version, the lower the minor version. + * When incompatible changes are made, the upper major number has to be changed. + * + * Update rules: + * -# Only update the major number when incompatible changes have been made to + * the IOC interface or the ABI provided via the functions returned by + * SUPQUERYFUNCS. + * -# When adding new features (new IOC number, new flags, new exports, ++) + * only update the minor number and change SUPLib.cpp to require the + * new IOC version. + * -# When incrementing the major number, clear the minor part and reset + * any IOC version requirements in SUPLib.cpp. + * -# When increment the major number, execute all pending work. + * + * @todo Pending work on next major version change: + * - nothing + */ +#define SUPDRV_IOC_VERSION 0x00330004 + +/** SUP_IOCTL_COOKIE. */ +typedef struct SUPCOOKIE +{ + /** The header. + * u32Cookie must be set to SUPCOOKIE_INITIAL_COOKIE. + * u32SessionCookie should be set to some random value. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Magic word. */ + char szMagic[16]; + /** The requested interface version number. */ + uint32_t u32ReqVersion; + /** The minimum interface version number. */ + uint32_t u32MinVersion; + } In; + struct + { + /** Cookie. */ + uint32_t u32Cookie; + /** Session cookie. */ + uint32_t u32SessionCookie; + /** Interface version for this session. */ + uint32_t u32SessionVersion; + /** The actual interface version in the driver. */ + uint32_t u32DriverVersion; + /** Number of functions available for the SUP_IOCTL_QUERY_FUNCS request. */ + uint32_t cFunctions; + /** Session handle. */ + R0PTRTYPE(PSUPDRVSESSION) pSession; + } Out; + } u; +} SUPCOOKIE, *PSUPCOOKIE; +/** @} */ + + +/** @name SUP_IOCTL_QUERY_FUNCS + * Query SUPR0 functions. + * @{ + */ +#define SUP_IOCTL_QUERY_FUNCS(cFuncs) SUP_CTL_CODE_BIG(2) +#define SUP_IOCTL_QUERY_FUNCS_SIZE(cFuncs) RT_UOFFSETOF_DYN(SUPQUERYFUNCS, u.Out.aFunctions[(cFuncs)]) +#define SUP_IOCTL_QUERY_FUNCS_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_QUERY_FUNCS_SIZE_OUT(cFuncs) SUP_IOCTL_QUERY_FUNCS_SIZE(cFuncs) + +/** A function. */ +typedef struct SUPFUNC +{ + /** Name - mangled. */ + char szName[47]; + /** For internal checking. Ignore. */ + uint8_t cArgs; + /** Address. */ + RTR0PTR pfn; +} SUPFUNC, *PSUPFUNC; + +typedef struct SUPQUERYFUNCS +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Number of functions returned. */ + uint32_t cFunctions; + /** Array of functions. */ + SUPFUNC aFunctions[1]; + } Out; + } u; +} SUPQUERYFUNCS, *PSUPQUERYFUNCS; +/** @} */ + + +/** @name SUP_IOCTL_LDR_OPEN + * Open an image. + * @{ + */ +#define SUP_IOCTL_LDR_OPEN SUP_CTL_CODE_SIZE(3, SUP_IOCTL_LDR_OPEN_SIZE) +#define SUP_IOCTL_LDR_OPEN_SIZE sizeof(SUPLDROPEN) +#define SUP_IOCTL_LDR_OPEN_SIZE_IN sizeof(SUPLDROPEN) +#define SUP_IOCTL_LDR_OPEN_SIZE_OUT (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPLDROPEN, u.Out)) +typedef struct SUPLDROPEN +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Size of the image we'll be loading (including all tables). + * Zero if the caller does not wish to prepare loading anything, then + * cbImageBits must be zero too ofc. */ + uint32_t cbImageWithEverything; + /** The size of the image bits. (Less or equal to cbImageWithTabs.) + * Zero if the caller does not wish to prepare loading anything. */ + uint32_t cbImageBits; + /** Image name. + * This is the NAME of the image, not the file name. It is used + * to share code with other processes. (Max len is 32 chars!) */ + char szName[32]; + /** Image file name. + * This can be used to load the image using a native loader. */ + char szFilename[260]; + } In; + struct + { + /** The base address of the image. */ + RTR0PTR pvImageBase; + /** Indicate whether or not the image requires loading. */ + bool fNeedsLoading; + /** Indicates that we're using the native ring-0 loader. */ + bool fNativeLoader; + } Out; + } u; +} SUPLDROPEN, *PSUPLDROPEN; +/** @} */ + + +/** @name SUP_IOCTL_LDR_LOAD + * Upload the image bits. + * @{ + */ +#define SUP_IOCTL_LDR_LOAD SUP_CTL_CODE_BIG(4) +#define SUP_IOCTL_LDR_LOAD_SIZE(cbImage) RT_MAX(RT_UOFFSETOF_DYN(SUPLDRLOAD, u.In.abImage[cbImage]), SUP_IOCTL_LDR_LOAD_SIZE_OUT) +#define SUP_IOCTL_LDR_LOAD_SIZE_IN(cbImage) RT_UOFFSETOF_DYN(SUPLDRLOAD, u.In.abImage[cbImage]) +#define SUP_IOCTL_LDR_LOAD_SIZE_OUT (RT_UOFFSETOF(SUPLDRLOAD, u.Out.szError) + RT_SIZEOFMEMB(SUPLDRLOAD, u.Out.szError)) + +/** + * Module initialization callback function. + * This is called once after the module has been loaded. + * + * @returns 0 on success. + * @returns Appropriate error code on failure. + * @param hMod Image handle for use in APIs. + */ +typedef DECLCALLBACKTYPE(int, FNR0MODULEINIT,(void *hMod)); +/** Pointer to a FNR0MODULEINIT(). */ +typedef R0PTRTYPE(FNR0MODULEINIT *) PFNR0MODULEINIT; + +/** + * Module termination callback function. + * This is called once right before the module is being unloaded. + * + * @param hMod Image handle for use in APIs. + */ +typedef DECLCALLBACKTYPE(void, FNR0MODULETERM,(void *hMod)); +/** Pointer to a FNR0MODULETERM(). */ +typedef R0PTRTYPE(FNR0MODULETERM *) PFNR0MODULETERM; + +/** + * Symbol table entry. + */ +typedef struct SUPLDRSYM +{ + /** Offset into of the string table. */ + uint32_t offName; + /** Offset of the symbol relative to the image load address. + * @remarks When used inside the SUPDrv to calculate real addresses, it + * must be cast to int32_t for the sake of native loader support + * on Solaris. (The loader puts the and data in different + * memory areans, and the text one is generally higher.) */ + uint32_t offSymbol; +} SUPLDRSYM; +/** Pointer to a symbol table entry. */ +typedef SUPLDRSYM *PSUPLDRSYM; +/** Pointer to a const symbol table entry. */ +typedef SUPLDRSYM const *PCSUPLDRSYM; + +#define SUPLDR_PROT_READ 1 /**< Grant read access (RTMEM_PROT_READ). */ +#define SUPLDR_PROT_WRITE 2 /**< Grant write access (RTMEM_PROT_WRITE). */ +#define SUPLDR_PROT_EXEC 4 /**< Grant execute access (RTMEM_PROT_EXEC). */ + +/** + * A segment table entry - chiefly for conveying memory protection. + */ +typedef struct SUPLDRSEG +{ + /** The RVA of the segment. */ + uint32_t off; + /** The size of the segment. */ + uint32_t cb : 28; + /** The segment protection (SUPLDR_PROT_XXX). */ + uint32_t fProt : 3; + /** MBZ. */ + uint32_t fUnused; +} SUPLDRSEG; +/** Pointer to a segment table entry. */ +typedef SUPLDRSEG *PSUPLDRSEG; +/** Pointer to a const segment table entry. */ +typedef SUPLDRSEG const *PCSUPLDRSEG; + +/** + * SUPLDRLOAD::u::In::EP type. + */ +typedef enum SUPLDRLOADEP +{ + SUPLDRLOADEP_NOTHING = 0, + SUPLDRLOADEP_VMMR0, + SUPLDRLOADEP_SERVICE, + SUPLDRLOADEP_32BIT_HACK = 0x7fffffff +} SUPLDRLOADEP; + +typedef struct SUPLDRLOAD +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The address of module initialization function. Similar to _DLL_InitTerm(hmod, 0). */ + RTR0PTR pfnModuleInit; + /** The address of module termination function. Similar to _DLL_InitTerm(hmod, 1). */ + RTR0PTR pfnModuleTerm; + /** Special entry points. */ + union + { + /** SUPLDRLOADEP_VMMR0. */ + struct + { + /** Address of VMMR0EntryFast function. */ + RTR0PTR pvVMMR0EntryFast; + /** Address of VMMR0EntryEx function. */ + RTR0PTR pvVMMR0EntryEx; + } VMMR0; + /** SUPLDRLOADEP_SERVICE. */ + struct + { + /** The service request handler. + * (PFNR0SERVICEREQHANDLER isn't defined yet.) */ + RTR0PTR pfnServiceReq; + /** Reserved, must be NIL. */ + RTR0PTR apvReserved[3]; + } Service; + } EP; + /** Address. */ + RTR0PTR pvImageBase; + /** Entry point type. */ + SUPLDRLOADEP eEPType; + /** The size of the image bits (starting at offset 0 and + * approaching offSymbols). */ + uint32_t cbImageBits; + /** The offset of the symbol table (SUPLDRSYM array). */ + uint32_t offSymbols; + /** The number of entries in the symbol table. */ + uint32_t cSymbols; + /** The offset of the string table. */ + uint32_t offStrTab; + /** Size of the string table. */ + uint32_t cbStrTab; + /** Offset to the segment table (SUPLDRSEG array). */ + uint32_t offSegments; + /** Number of segments. */ + uint32_t cSegments; + /** Size of image data in achImage. */ + uint32_t cbImageWithEverything; + /** Flags (SUPLDRLOAD_F_XXX). */ + uint32_t fFlags; + /** The image data. */ + uint8_t abImage[1]; + } In; + struct + { + /** Magic value indicating whether extended error information is + * present or not (SUPLDRLOAD_ERROR_MAGIC). */ + uint64_t uErrorMagic; + /** Extended error information. */ + char szError[2048]; + } Out; + } u; +} SUPLDRLOAD, *PSUPLDRLOAD; +/** Magic value that indicates that there is a valid error information string + * present on SUP_IOCTL_LDR_LOAD failure. + * @remarks The value is choosen to be an unlikely init and term address. */ +#define SUPLDRLOAD_ERROR_MAGIC UINT64_C(0xabcdefef0feddcb9) +/** The module depends on VMMR0. */ +#define SUPLDRLOAD_F_DEP_VMMR0 RT_BIT_32(0) +/** Valid flag mask. */ +#define SUPLDRLOAD_F_VALID_MASK UINT32_C(0x00000001) +/** @} */ + + +/** @name SUP_IOCTL_LDR_FREE + * Free an image. + * @{ + */ +#define SUP_IOCTL_LDR_FREE SUP_CTL_CODE_SIZE(5, SUP_IOCTL_LDR_FREE_SIZE) +#define SUP_IOCTL_LDR_FREE_SIZE sizeof(SUPLDRFREE) +#define SUP_IOCTL_LDR_FREE_SIZE_IN sizeof(SUPLDRFREE) +#define SUP_IOCTL_LDR_FREE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPLDRFREE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Address. */ + RTR0PTR pvImageBase; + } In; + } u; +} SUPLDRFREE, *PSUPLDRFREE; +/** @} */ + + +/** @name SUP_IOCTL_LDR_LOCK_DOWN + * Lock down the image loader interface. + * @{ + */ +#define SUP_IOCTL_LDR_LOCK_DOWN SUP_CTL_CODE_SIZE(38, SUP_IOCTL_LDR_LOCK_DOWN_SIZE) +#define SUP_IOCTL_LDR_LOCK_DOWN_SIZE sizeof(SUPREQHDR) +#define SUP_IOCTL_LDR_LOCK_DOWN_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_LDR_LOCK_DOWN_SIZE_OUT sizeof(SUPREQHDR) +/** @} */ + + +/** @name SUP_IOCTL_LDR_GET_SYMBOL + * Get address of a symbol within an image. + * @{ + */ +#define SUP_IOCTL_LDR_GET_SYMBOL SUP_CTL_CODE_SIZE(6, SUP_IOCTL_LDR_GET_SYMBOL_SIZE) +#define SUP_IOCTL_LDR_GET_SYMBOL_SIZE sizeof(SUPLDRGETSYMBOL) +#define SUP_IOCTL_LDR_GET_SYMBOL_SIZE_IN sizeof(SUPLDRGETSYMBOL) +#define SUP_IOCTL_LDR_GET_SYMBOL_SIZE_OUT (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPLDRGETSYMBOL, u.Out)) +typedef struct SUPLDRGETSYMBOL +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Address. */ + RTR0PTR pvImageBase; + /** The symbol name. */ + char szSymbol[64]; + } In; + struct + { + /** The symbol address. */ + RTR0PTR pvSymbol; + } Out; + } u; +} SUPLDRGETSYMBOL, *PSUPLDRGETSYMBOL; +/** @} */ + + +/** @name SUP_IOCTL_CALL_VMMR0 + * Call the R0 VMM Entry point. + * @{ + */ +#define SUP_IOCTL_CALL_VMMR0(cbReq) SUP_CTL_CODE_SIZE(7, SUP_IOCTL_CALL_VMMR0_SIZE(cbReq)) +#define SUP_IOCTL_CALL_VMMR0_NO_SIZE() SUP_CTL_CODE_SIZE(7, 0) +#define SUP_IOCTL_CALL_VMMR0_SIZE(cbReq) RT_UOFFSETOF_DYN(SUPCALLVMMR0, abReqPkt[cbReq]) +#define SUP_IOCTL_CALL_VMMR0_SIZE_IN(cbReq) SUP_IOCTL_CALL_VMMR0_SIZE(cbReq) +#define SUP_IOCTL_CALL_VMMR0_SIZE_OUT(cbReq) SUP_IOCTL_CALL_VMMR0_SIZE(cbReq) +typedef struct SUPCALLVMMR0 +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The VM handle. */ + PVMR0 pVMR0; + /** VCPU id. */ + uint32_t idCpu; + /** Which operation to execute. */ + uint32_t uOperation; + /** Argument to use when no request packet is supplied. */ + uint64_t u64Arg; + } In; + } u; + /** The VMMR0Entry request packet. */ + uint8_t abReqPkt[1]; +} SUPCALLVMMR0, *PSUPCALLVMMR0; +/** @} */ + + +/** @name SUP_IOCTL_CALL_VMMR0_BIG + * Version of SUP_IOCTL_CALL_VMMR0 for dealing with large requests. + * @{ + */ +#define SUP_IOCTL_CALL_VMMR0_BIG SUP_CTL_CODE_BIG(27) +#define SUP_IOCTL_CALL_VMMR0_BIG_SIZE(cbReq) RT_UOFFSETOF_DYN(SUPCALLVMMR0, abReqPkt[cbReq]) +#define SUP_IOCTL_CALL_VMMR0_BIG_SIZE_IN(cbReq) SUP_IOCTL_CALL_VMMR0_SIZE(cbReq) +#define SUP_IOCTL_CALL_VMMR0_BIG_SIZE_OUT(cbReq) SUP_IOCTL_CALL_VMMR0_SIZE(cbReq) +/** @} */ + + +/** @name SUP_IOCTL_LOW_ALLOC + * Allocate memory below 4GB (physically). + * @{ + */ +#define SUP_IOCTL_LOW_ALLOC SUP_CTL_CODE_BIG(8) +#define SUP_IOCTL_LOW_ALLOC_SIZE(cPages) ((uint32_t)RT_UOFFSETOF_DYN(SUPLOWALLOC, u.Out.aPages[cPages])) +#define SUP_IOCTL_LOW_ALLOC_SIZE_IN (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPLOWALLOC, u.In)) +#define SUP_IOCTL_LOW_ALLOC_SIZE_OUT(cPages) SUP_IOCTL_LOW_ALLOC_SIZE(cPages) +typedef struct SUPLOWALLOC +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Number of pages to allocate. */ + uint32_t cPages; + } In; + struct + { + /** The ring-3 address of the allocated memory. */ + RTR3PTR pvR3; + /** The ring-0 address of the allocated memory. */ + RTR0PTR pvR0; + /** Array of pages. */ + RTHCPHYS aPages[1]; + } Out; + } u; +} SUPLOWALLOC, *PSUPLOWALLOC; +/** @} */ + + +/** @name SUP_IOCTL_LOW_FREE + * Free low memory. + * @{ + */ +#define SUP_IOCTL_LOW_FREE SUP_CTL_CODE_SIZE(9, SUP_IOCTL_LOW_FREE_SIZE) +#define SUP_IOCTL_LOW_FREE_SIZE sizeof(SUPLOWFREE) +#define SUP_IOCTL_LOW_FREE_SIZE_IN sizeof(SUPLOWFREE) +#define SUP_IOCTL_LOW_FREE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPLOWFREE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The ring-3 address of the memory to free. */ + RTR3PTR pvR3; + } In; + } u; +} SUPLOWFREE, *PSUPLOWFREE; +/** @} */ + + +/** @name SUP_IOCTL_PAGE_ALLOC_EX + * Allocate memory and map it into kernel and/or user space. The memory is of + * course locked. The result should be freed using SUP_IOCTL_PAGE_FREE. + * + * @remarks Allocations without a kernel mapping may fail with + * VERR_NOT_SUPPORTED on some platforms. + * + * @{ + */ +#define SUP_IOCTL_PAGE_ALLOC_EX SUP_CTL_CODE_BIG(10) +#define SUP_IOCTL_PAGE_ALLOC_EX_SIZE(cPages) RT_UOFFSETOF_DYN(SUPPAGEALLOCEX, u.Out.aPages[cPages]) +#define SUP_IOCTL_PAGE_ALLOC_EX_SIZE_IN (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPPAGEALLOCEX, u.In)) +#define SUP_IOCTL_PAGE_ALLOC_EX_SIZE_OUT(cPages) SUP_IOCTL_PAGE_ALLOC_EX_SIZE(cPages) +typedef struct SUPPAGEALLOCEX +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Number of pages to allocate */ + uint32_t cPages; + /** Whether it should have kernel mapping. */ + bool fKernelMapping; + /** Whether it should have a user mapping. */ + bool fUserMapping; + /** Reserved. Must be false. */ + bool fReserved0; + /** Reserved. Must be false. */ + bool fReserved1; + } In; + struct + { + /** Returned ring-3 address. */ + RTR3PTR pvR3; + /** Returned ring-0 address. */ + RTR0PTR pvR0; + /** The physical addresses of the allocated pages. */ + RTHCPHYS aPages[1]; + } Out; + } u; +} SUPPAGEALLOCEX, *PSUPPAGEALLOCEX; +/** @} */ + + +/** @name SUP_IOCTL_PAGE_MAP_KERNEL + * Maps a portion of memory allocated by SUP_IOCTL_PAGE_ALLOC_EX / + * SUPR0PageAllocEx into kernel space for use by a device or similar. + * + * The mapping will be freed together with the ring-3 mapping when + * SUP_IOCTL_PAGE_FREE or SUPR0PageFree is called. + * + * @remarks Not necessarily supported on all platforms. + * + * @{ + */ +#define SUP_IOCTL_PAGE_MAP_KERNEL SUP_CTL_CODE_SIZE(11, SUP_IOCTL_PAGE_MAP_KERNEL_SIZE) +#define SUP_IOCTL_PAGE_MAP_KERNEL_SIZE sizeof(SUPPAGEMAPKERNEL) +#define SUP_IOCTL_PAGE_MAP_KERNEL_SIZE_IN sizeof(SUPPAGEMAPKERNEL) +#define SUP_IOCTL_PAGE_MAP_KERNEL_SIZE_OUT sizeof(SUPPAGEMAPKERNEL) +typedef struct SUPPAGEMAPKERNEL +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The pointer of to the previously allocated memory. */ + RTR3PTR pvR3; + /** The offset to start mapping from. */ + uint32_t offSub; + /** Size of the section to map. */ + uint32_t cbSub; + /** Flags reserved for future fun. */ + uint32_t fFlags; + } In; + struct + { + /** The ring-0 address corresponding to pvR3 + offSub. */ + RTR0PTR pvR0; + } Out; + } u; +} SUPPAGEMAPKERNEL, *PSUPPAGEMAPKERNEL; +/** @} */ + + +/** @name SUP_IOCTL_PAGE_PROTECT + * Changes the page level protection of the user and/or kernel mappings of + * memory previously allocated by SUPR0PageAllocEx. + * + * @remarks Not necessarily supported on all platforms. + * + * @{ + */ +#define SUP_IOCTL_PAGE_PROTECT SUP_CTL_CODE_SIZE(12, SUP_IOCTL_PAGE_PROTECT_SIZE) +#define SUP_IOCTL_PAGE_PROTECT_SIZE sizeof(SUPPAGEPROTECT) +#define SUP_IOCTL_PAGE_PROTECT_SIZE_IN sizeof(SUPPAGEPROTECT) +#define SUP_IOCTL_PAGE_PROTECT_SIZE_OUT sizeof(SUPPAGEPROTECT) +typedef struct SUPPAGEPROTECT +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The pointer of to the previously allocated memory. + * Pass NIL_RTR3PTR if the ring-0 mapping should remain unaffected. */ + RTR3PTR pvR3; + /** The pointer of to the previously allocated memory. + * Pass NIL_RTR0PTR if the ring-0 mapping should remain unaffected. */ + RTR0PTR pvR0; + /** The offset to start changing protection at. */ + uint32_t offSub; + /** Size of the portion that should be changed. */ + uint32_t cbSub; + /** Protection flags, RTMEM_PROT_*. */ + uint32_t fProt; + } In; + } u; +} SUPPAGEPROTECT, *PSUPPAGEPROTECT; +/** @} */ + + +/** @name SUP_IOCTL_PAGE_FREE + * Free memory allocated with SUP_IOCTL_PAGE_ALLOC_EX. + * @{ + */ +#define SUP_IOCTL_PAGE_FREE SUP_CTL_CODE_SIZE(13, SUP_IOCTL_PAGE_FREE_SIZE_IN) +#define SUP_IOCTL_PAGE_FREE_SIZE sizeof(SUPPAGEFREE) +#define SUP_IOCTL_PAGE_FREE_SIZE_IN sizeof(SUPPAGEFREE) +#define SUP_IOCTL_PAGE_FREE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPPAGEFREE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Address of memory range to free. */ + RTR3PTR pvR3; + } In; + } u; +} SUPPAGEFREE, *PSUPPAGEFREE; +/** @} */ + + + + +/** @name SUP_IOCTL_PAGE_LOCK + * Pin down physical pages. + * @{ + */ +#define SUP_IOCTL_PAGE_LOCK SUP_CTL_CODE_BIG(14) +#define SUP_IOCTL_PAGE_LOCK_SIZE(cPages) (RT_MAX((size_t)SUP_IOCTL_PAGE_LOCK_SIZE_IN, (size_t)SUP_IOCTL_PAGE_LOCK_SIZE_OUT(cPages))) +#define SUP_IOCTL_PAGE_LOCK_SIZE_IN (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPPAGELOCK, u.In)) +#define SUP_IOCTL_PAGE_LOCK_SIZE_OUT(cPages) RT_UOFFSETOF_DYN(SUPPAGELOCK, u.Out.aPages[cPages]) +typedef struct SUPPAGELOCK +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Start of page range. Must be PAGE aligned. */ + RTR3PTR pvR3; + /** The range size given as a page count. */ + uint32_t cPages; + } In; + + struct + { + /** Array of pages. */ + RTHCPHYS aPages[1]; + } Out; + } u; +} SUPPAGELOCK, *PSUPPAGELOCK; +/** @} */ + + +/** @name SUP_IOCTL_PAGE_UNLOCK + * Unpin physical pages. + * @{ */ +#define SUP_IOCTL_PAGE_UNLOCK SUP_CTL_CODE_SIZE(15, SUP_IOCTL_PAGE_UNLOCK_SIZE) +#define SUP_IOCTL_PAGE_UNLOCK_SIZE sizeof(SUPPAGEUNLOCK) +#define SUP_IOCTL_PAGE_UNLOCK_SIZE_IN sizeof(SUPPAGEUNLOCK) +#define SUP_IOCTL_PAGE_UNLOCK_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPPAGEUNLOCK +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Start of page range of a range previously pinned. */ + RTR3PTR pvR3; + } In; + } u; +} SUPPAGEUNLOCK, *PSUPPAGEUNLOCK; +/** @} */ + + +/** @name SUP_IOCTL_CONT_ALLOC + * Allocate continuous memory. + * @{ + */ +#define SUP_IOCTL_CONT_ALLOC SUP_CTL_CODE_SIZE(16, SUP_IOCTL_CONT_ALLOC_SIZE) +#define SUP_IOCTL_CONT_ALLOC_SIZE sizeof(SUPCONTALLOC) +#define SUP_IOCTL_CONT_ALLOC_SIZE_IN (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPCONTALLOC, u.In)) +#define SUP_IOCTL_CONT_ALLOC_SIZE_OUT sizeof(SUPCONTALLOC) +typedef struct SUPCONTALLOC +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The allocation size given as a page count. */ + uint32_t cPages; + } In; + + struct + { + /** The address of the ring-0 mapping of the allocated memory. */ + RTR0PTR pvR0; + /** The address of the ring-3 mapping of the allocated memory. */ + RTR3PTR pvR3; + /** The physical address of the allocation. */ + RTHCPHYS HCPhys; + } Out; + } u; +} SUPCONTALLOC, *PSUPCONTALLOC; +/** @} */ + + +/** @name SUP_IOCTL_CONT_FREE Input. + * @{ + */ +/** Free continuous memory. */ +#define SUP_IOCTL_CONT_FREE SUP_CTL_CODE_SIZE(17, SUP_IOCTL_CONT_FREE_SIZE) +#define SUP_IOCTL_CONT_FREE_SIZE sizeof(SUPCONTFREE) +#define SUP_IOCTL_CONT_FREE_SIZE_IN sizeof(SUPCONTFREE) +#define SUP_IOCTL_CONT_FREE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPCONTFREE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The ring-3 address of the memory to free. */ + RTR3PTR pvR3; + } In; + } u; +} SUPCONTFREE, *PSUPCONTFREE; +/** @} */ + + +/** @name SUP_IOCTL_GET_PAGING_MODE + * Get the host paging mode. + * @{ + */ +#define SUP_IOCTL_GET_PAGING_MODE SUP_CTL_CODE_SIZE(18, SUP_IOCTL_GET_PAGING_MODE_SIZE) +#define SUP_IOCTL_GET_PAGING_MODE_SIZE sizeof(SUPGETPAGINGMODE) +#define SUP_IOCTL_GET_PAGING_MODE_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_GET_PAGING_MODE_SIZE_OUT sizeof(SUPGETPAGINGMODE) +typedef struct SUPGETPAGINGMODE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The paging mode. */ + SUPPAGINGMODE enmMode; + } Out; + } u; +} SUPGETPAGINGMODE, *PSUPGETPAGINGMODE; +/** @} */ + + +/** @name SUP_IOCTL_SET_VM_FOR_FAST + * Set the VM handle for doing fast call ioctl calls. + * @{ + */ +#define SUP_IOCTL_SET_VM_FOR_FAST SUP_CTL_CODE_SIZE(19, SUP_IOCTL_SET_VM_FOR_FAST_SIZE) +#define SUP_IOCTL_SET_VM_FOR_FAST_SIZE sizeof(SUPSETVMFORFAST) +#define SUP_IOCTL_SET_VM_FOR_FAST_SIZE_IN sizeof(SUPSETVMFORFAST) +#define SUP_IOCTL_SET_VM_FOR_FAST_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPSETVMFORFAST +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The ring-0 VM handle (pointer). */ + PVMR0 pVMR0; + } In; + } u; +} SUPSETVMFORFAST, *PSUPSETVMFORFAST; +/** @} */ + + +/** @name SUP_IOCTL_GIP_MAP + * Map the GIP into user space. + * @{ + */ +#define SUP_IOCTL_GIP_MAP SUP_CTL_CODE_SIZE(20, SUP_IOCTL_GIP_MAP_SIZE) +#define SUP_IOCTL_GIP_MAP_SIZE sizeof(SUPGIPMAP) +#define SUP_IOCTL_GIP_MAP_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_GIP_MAP_SIZE_OUT sizeof(SUPGIPMAP) +typedef struct SUPGIPMAP +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The physical address of the GIP. */ + RTHCPHYS HCPhysGip; + /** Pointer to the read-only usermode GIP mapping for this session. */ + R3PTRTYPE(PSUPGLOBALINFOPAGE) pGipR3; + /** Pointer to the supervisor mode GIP mapping. */ + R0PTRTYPE(PSUPGLOBALINFOPAGE) pGipR0; + } Out; + } u; +} SUPGIPMAP, *PSUPGIPMAP; +/** @} */ + + +/** @name SUP_IOCTL_GIP_UNMAP + * Unmap the GIP. + * @{ + */ +#define SUP_IOCTL_GIP_UNMAP SUP_CTL_CODE_SIZE(21, SUP_IOCTL_GIP_UNMAP_SIZE) +#define SUP_IOCTL_GIP_UNMAP_SIZE sizeof(SUPGIPUNMAP) +#define SUP_IOCTL_GIP_UNMAP_SIZE_IN sizeof(SUPGIPUNMAP) +#define SUP_IOCTL_GIP_UNMAP_SIZE_OUT sizeof(SUPGIPUNMAP) +typedef struct SUPGIPUNMAP +{ + /** The header. */ + SUPREQHDR Hdr; +} SUPGIPUNMAP, *PSUPGIPUNMAP; +/** @} */ + + +/** @name SUP_IOCTL_CALL_SERVICE + * Call the a ring-0 service. + * + * @todo Might have to convert this to a big request, just like + * SUP_IOCTL_CALL_VMMR0 + * @{ + */ +#define SUP_IOCTL_CALL_SERVICE(cbReq) SUP_CTL_CODE_SIZE(22, SUP_IOCTL_CALL_SERVICE_SIZE(cbReq)) +#define SUP_IOCTL_CALL_SERVICE_NO_SIZE() SUP_CTL_CODE_SIZE(22, 0) +#define SUP_IOCTL_CALL_SERVICE_SIZE(cbReq) RT_UOFFSETOF_DYN(SUPCALLSERVICE, abReqPkt[cbReq]) +#define SUP_IOCTL_CALL_SERVICE_SIZE_IN(cbReq) SUP_IOCTL_CALL_SERVICE_SIZE(cbReq) +#define SUP_IOCTL_CALL_SERVICE_SIZE_OUT(cbReq) SUP_IOCTL_CALL_SERVICE_SIZE(cbReq) +typedef struct SUPCALLSERVICE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The service name. */ + char szName[28]; + /** Which operation to execute. */ + uint32_t uOperation; + /** Argument to use when no request packet is supplied. */ + uint64_t u64Arg; + } In; + } u; + /** The request packet passed to SUP. */ + uint8_t abReqPkt[1]; +} SUPCALLSERVICE, *PSUPCALLSERVICE; +/** @} */ + + +/** @name SUP_IOCTL_LOGGER_SETTINGS + * Changes the ring-0 release or debug logger settings. + * @{ + */ +#define SUP_IOCTL_LOGGER_SETTINGS(cbStrTab) SUP_CTL_CODE_SIZE(23, SUP_IOCTL_LOGGER_SETTINGS_SIZE(cbStrTab)) +#define SUP_IOCTL_LOGGER_SETTINGS_NO_SIZE() SUP_CTL_CODE_SIZE(23, 0) +#define SUP_IOCTL_LOGGER_SETTINGS_SIZE(cbStrTab) RT_UOFFSETOF_DYN(SUPLOGGERSETTINGS, u.In.szStrings[cbStrTab]) +#define SUP_IOCTL_LOGGER_SETTINGS_SIZE_IN(cbStrTab) RT_UOFFSETOF_DYN(SUPLOGGERSETTINGS, u.In.szStrings[cbStrTab]) +#define SUP_IOCTL_LOGGER_SETTINGS_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPLOGGERSETTINGS +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Which logger. */ + uint32_t fWhich; + /** What to do with it. */ + uint32_t fWhat; + /** Offset of the flags setting string. */ + uint32_t offFlags; + /** Offset of the groups setting string. */ + uint32_t offGroups; + /** Offset of the destination setting string. */ + uint32_t offDestination; + /** The string table. */ + char szStrings[1]; + } In; + } u; +} SUPLOGGERSETTINGS, *PSUPLOGGERSETTINGS; + +/** Debug logger. */ +#define SUPLOGGERSETTINGS_WHICH_DEBUG 0 +/** Release logger. */ +#define SUPLOGGERSETTINGS_WHICH_RELEASE 1 + +/** Change the settings. */ +#define SUPLOGGERSETTINGS_WHAT_SETTINGS 0 +/** Create the logger instance. */ +#define SUPLOGGERSETTINGS_WHAT_CREATE 1 +/** Destroy the logger instance. */ +#define SUPLOGGERSETTINGS_WHAT_DESTROY 2 + +/** @} */ + + +/** @name Semaphore Types + * @{ */ +#define SUP_SEM_TYPE_EVENT 0 +#define SUP_SEM_TYPE_EVENT_MULTI 1 +/** @} */ + + +/** @name SUP_IOCTL_SEM_OP2 + * Semaphore operations. + * @remarks This replaces the old SUP_IOCTL_SEM_OP interface. + * @{ + */ +#define SUP_IOCTL_SEM_OP2 SUP_CTL_CODE_SIZE(24, SUP_IOCTL_SEM_OP2_SIZE) +#define SUP_IOCTL_SEM_OP2_SIZE sizeof(SUPSEMOP2) +#define SUP_IOCTL_SEM_OP2_SIZE_IN sizeof(SUPSEMOP2) +#define SUP_IOCTL_SEM_OP2_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPSEMOP2 +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The semaphore type. */ + uint32_t uType; + /** The semaphore handle. */ + uint32_t hSem; + /** The operation. */ + uint32_t uOp; + /** Reserved, must be zero. */ + uint32_t uReserved; + /** The number of milliseconds to wait if it's a wait operation. */ + union + { + /** Absolute timeout (RTTime[System]NanoTS). + * Used by SUPSEMOP2_WAIT_NS_ABS. */ + uint64_t uAbsNsTimeout; + /** Relative nanosecond timeout. + * Used by SUPSEMOP2_WAIT_NS_REL. */ + uint64_t cRelNsTimeout; + /** Relative millisecond timeout. + * Used by SUPSEMOP2_WAIT_MS_REL. */ + uint32_t cRelMsTimeout; + /** Generic 64-bit accessor. + * ASSUMES little endian! */ + uint64_t u64; + } uArg; + } In; + } u; +} SUPSEMOP2, *PSUPSEMOP2; + +/** Wait for a number of milliseconds. */ +#define SUPSEMOP2_WAIT_MS_REL 0 +/** Wait until the specified deadline is reached. */ +#define SUPSEMOP2_WAIT_NS_ABS 1 +/** Wait for a number of nanoseconds. */ +#define SUPSEMOP2_WAIT_NS_REL 2 +/** Signal the semaphore. */ +#define SUPSEMOP2_SIGNAL 3 +/** Reset the semaphore (only applicable to SUP_SEM_TYPE_EVENT_MULTI). */ +#define SUPSEMOP2_RESET 4 +/** Close the semaphore handle. */ +#define SUPSEMOP2_CLOSE 5 +/** @} */ + + +/** @name SUP_IOCTL_SEM_OP3 + * Semaphore operations. + * @{ + */ +#define SUP_IOCTL_SEM_OP3 SUP_CTL_CODE_SIZE(25, SUP_IOCTL_SEM_OP3_SIZE) +#define SUP_IOCTL_SEM_OP3_SIZE sizeof(SUPSEMOP3) +#define SUP_IOCTL_SEM_OP3_SIZE_IN sizeof(SUPSEMOP3) +#define SUP_IOCTL_SEM_OP3_SIZE_OUT sizeof(SUPSEMOP3) +typedef struct SUPSEMOP3 +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The semaphore type. */ + uint32_t uType; + /** The semaphore handle. */ + uint32_t hSem; + /** The operation. */ + uint32_t uOp; + /** Reserved, must be zero. */ + uint32_t u32Reserved; + /** Reserved for future use. */ + uint64_t u64Reserved; + } In; + union + { + /** The handle of the created semaphore. + * Used by SUPSEMOP3_CREATE. */ + uint32_t hSem; + /** The semaphore resolution in nano seconds. + * Used by SUPSEMOP3_GET_RESOLUTION. */ + uint32_t cNsResolution; + /** The 32-bit view. */ + uint32_t u32; + /** Reserved some space for later expansion. */ + uint64_t u64Reserved; + } Out; + } u; +} SUPSEMOP3, *PSUPSEMOP3; + +/** Get the wait resolution. */ +#define SUPSEMOP3_CREATE 0 +/** Get the wait resolution. */ +#define SUPSEMOP3_GET_RESOLUTION 1 +/** @} */ + + +/** @name SUP_IOCTL_VT_CAPS + * Get the VT-x/AMD-V capabilities. + * + * @todo Intended for main, which means we need to relax the privilege requires + * when accessing certain vboxdrv functions. + * + * @{ + */ +#define SUP_IOCTL_VT_CAPS SUP_CTL_CODE_SIZE(26, SUP_IOCTL_VT_CAPS_SIZE) +#define SUP_IOCTL_VT_CAPS_SIZE sizeof(SUPVTCAPS) +#define SUP_IOCTL_VT_CAPS_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_VT_CAPS_SIZE_OUT sizeof(SUPVTCAPS) +typedef struct SUPVTCAPS +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The VT capability dword. */ + uint32_t fCaps; + } Out; + } u; +} SUPVTCAPS, *PSUPVTCAPS; +/** @} */ + + +/** @name SUP_IOCTL_TRACER_OPEN + * Open the tracer. + * + * Should be matched by an SUP_IOCTL_TRACER_CLOSE call. + * + * @{ + */ +#define SUP_IOCTL_TRACER_OPEN SUP_CTL_CODE_SIZE(28, SUP_IOCTL_TRACER_OPEN_SIZE) +#define SUP_IOCTL_TRACER_OPEN_SIZE sizeof(SUPTRACEROPEN) +#define SUP_IOCTL_TRACER_OPEN_SIZE_IN sizeof(SUPTRACEROPEN) +#define SUP_IOCTL_TRACER_OPEN_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPTRACEROPEN +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Tracer cookie. Used to make sure we only open a matching tracer. */ + uint32_t uCookie; + /** Tracer specific argument. */ + RTHCUINTPTR uArg; + } In; + } u; +} SUPTRACEROPEN, *PSUPTRACEROPEN; +/** @} */ + + +/** @name SUP_IOCTL_TRACER_CLOSE + * Close the tracer. + * + * Must match a SUP_IOCTL_TRACER_OPEN call. + * + * @{ + */ +#define SUP_IOCTL_TRACER_CLOSE SUP_CTL_CODE_SIZE(29, SUP_IOCTL_TRACER_CLOSE_SIZE) +#define SUP_IOCTL_TRACER_CLOSE_SIZE sizeof(SUPREQHDR) +#define SUP_IOCTL_TRACER_CLOSE_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_TRACER_CLOSE_SIZE_OUT sizeof(SUPREQHDR) +/** @} */ + + +/** @name SUP_IOCTL_TRACER_IOCTL + * Speak UNIX ioctl() with the tracer. + * + * The session must have opened the tracer prior to issuing this request. + * + * @{ + */ +#define SUP_IOCTL_TRACER_IOCTL SUP_CTL_CODE_SIZE(30, SUP_IOCTL_TRACER_IOCTL_SIZE) +#define SUP_IOCTL_TRACER_IOCTL_SIZE sizeof(SUPTRACERIOCTL) +#define SUP_IOCTL_TRACER_IOCTL_SIZE_IN sizeof(SUPTRACERIOCTL) +#define SUP_IOCTL_TRACER_IOCTL_SIZE_OUT (RT_UOFFSETOF(SUPTRACERIOCTL, u.Out.iRetVal) + sizeof(int32_t)) +typedef struct SUPTRACERIOCTL +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The command. */ + RTHCUINTPTR uCmd; + /** Argument to the command. */ + RTHCUINTPTR uArg; + } In; + + struct + { + /** The return value. */ + int32_t iRetVal; + } Out; + } u; +} SUPTRACERIOCTL, *PSUPTRACERIOCTL; +/** @} */ + + +/** @name SUP_IOCTL_TRACER_UMOD_REG + * Registers tracepoints in a user mode module. + * + * @{ + */ +#define SUP_IOCTL_TRACER_UMOD_REG SUP_CTL_CODE_SIZE(31, SUP_IOCTL_TRACER_UMOD_REG_SIZE) +#define SUP_IOCTL_TRACER_UMOD_REG_SIZE sizeof(SUPTRACERUMODREG) +#define SUP_IOCTL_TRACER_UMOD_REG_SIZE_IN sizeof(SUPTRACERUMODREG) +#define SUP_IOCTL_TRACER_UMOD_REG_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPTRACERUMODREG +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The address at which the VTG header actually resides. + * This will differ from R3PtrVtgHdr for raw-mode context + * modules. */ + RTUINTPTR uVtgHdrAddr; + /** The ring-3 pointer of the VTG header. */ + RTR3PTR R3PtrVtgHdr; + /** The ring-3 pointer of the probe location string table. */ + RTR3PTR R3PtrStrTab; + /** The size of the string table. */ + uint32_t cbStrTab; + /** Future flags, MBZ. */ + uint32_t fFlags; + /** The module name. */ + char szName[64]; + } In; + } u; +} SUPTRACERUMODREG, *PSUPTRACERUMODREG; +/** @} */ + + +/** @name SUP_IOCTL_TRACER_UMOD_DEREG + * Deregisters tracepoints in a user mode module. + * + * @{ + */ +#define SUP_IOCTL_TRACER_UMOD_DEREG SUP_CTL_CODE_SIZE(32, SUP_IOCTL_TRACER_UMOD_DEREG_SIZE) +#define SUP_IOCTL_TRACER_UMOD_DEREG_SIZE sizeof(SUPTRACERUMODDEREG) +#define SUP_IOCTL_TRACER_UMOD_DEREG_SIZE_IN sizeof(SUPTRACERUMODDEREG) +#define SUP_IOCTL_TRACER_UMOD_DEREG_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPTRACERUMODDEREG +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Pointer to the VTG header. */ + RTR3PTR pVtgHdr; + } In; + } u; +} SUPTRACERUMODDEREG, *PSUPTRACERUMODDEREG; +/** @} */ + + +/** @name SUP_IOCTL_TRACER_UMOD_FIRE_PROBE + * Fire a probe in a user tracepoint module. + * + * @{ + */ +#define SUP_IOCTL_TRACER_UMOD_FIRE_PROBE SUP_CTL_CODE_SIZE(33, SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE) +#define SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE sizeof(SUPTRACERUMODFIREPROBE) +#define SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE_IN sizeof(SUPTRACERUMODFIREPROBE) +#define SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPTRACERUMODFIREPROBE +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + SUPDRVTRACERUSRCTX In; + } u; +} SUPTRACERUMODFIREPROBE, *PSUPTRACERUMODFIREPROBE; +/** @} */ + + +/** @name SUP_IOCTL_MSR_PROBER + * MSR probing interface, not available in normal builds. + * + * @{ + */ +#define SUP_IOCTL_MSR_PROBER SUP_CTL_CODE_SIZE(34, SUP_IOCTL_MSR_PROBER_SIZE) +#define SUP_IOCTL_MSR_PROBER_SIZE sizeof(SUPMSRPROBER) +#define SUP_IOCTL_MSR_PROBER_SIZE_IN sizeof(SUPMSRPROBER) +#define SUP_IOCTL_MSR_PROBER_SIZE_OUT sizeof(SUPMSRPROBER) + +typedef enum SUPMSRPROBEROP +{ + SUPMSRPROBEROP_INVALID = 0, /**< The customary invalid zero value. */ + SUPMSRPROBEROP_READ, /**< Read an MSR. */ + SUPMSRPROBEROP_WRITE, /**< Write a value to an MSR (use with care!). */ + SUPMSRPROBEROP_MODIFY, /**< Read-modify-restore-flushall. */ + SUPMSRPROBEROP_MODIFY_FASTER, /**< Read-modify-restore, skip the flushing. */ + SUPMSRPROBEROP_END, /**< End of valid values. */ + SUPMSRPROBEROP_32BIT_HACK = 0x7fffffff /**< The customary 32-bit type hack. */ +} SUPMSRPROBEROP; + +typedef struct SUPMSRPROBER +{ + /** The header. */ + SUPREQHDR Hdr; + + /** Input/output union. */ + union + { + /** Inputs. */ + struct + { + /** The operation. */ + SUPMSRPROBEROP enmOp; + /** The MSR to test. */ + uint32_t uMsr; + /** The CPU to perform the operation on. + * Use UINT32_MAX to indicate that any CPU will do. */ + uint32_t idCpu; + /** Alignment padding. */ + uint32_t u32Padding; + /** Operation specific arguments. */ + union + { + /* SUPMSRPROBEROP_READ takes no extra arguments. */ + + /** For SUPMSRPROBEROP_WRITE. */ + struct + { + /** The value to write. */ + uint64_t uToWrite; + } Write; + + /** For SUPMSRPROBEROP_MODIFY and SUPMSRPROBEROP_MODIFY_FASTER. */ + struct + { + /** The value to AND the current MSR value with to construct the value to + * write. This applied first. */ + uint64_t fAndMask; + /** The value to OR the result of the above mentioned AND operation with + * attempting to modify the MSR. */ + uint64_t fOrMask; + } Modify; + + /** Reserve space for the future. */ + uint64_t auPadding[3]; + } uArgs; + } In; + + /** Outputs. */ + struct + { + /** Operation specific results. */ + union + { + /** For SUPMSRPROBEROP_READ. */ + struct + { + /** The value we've read. */ + uint64_t uValue; + /** Set if we GPed while reading it. */ + bool fGp; + } Read; + + /** For SUPMSRPROBEROP_WRITE. */ + struct + { + /** Set if we GPed while writing it. */ + bool fGp; + } Write; + + /** For SUPMSRPROBEROP_MODIFY and SUPMSRPROBEROP_MODIFY_FASTER. */ + SUPMSRPROBERMODIFYRESULT Modify; + + /** Size padding/aligning. */ + uint64_t auPadding[5]; + } uResults; + } Out; + } u; +} SUPMSRPROBER, *PSUPMSRPROBER; +AssertCompileMemberAlignment(SUPMSRPROBER, u, 8); +AssertCompileMemberAlignment(SUPMSRPROBER, u.In.uArgs, 8); +AssertCompileMembersSameSizeAndOffset(SUPMSRPROBER, u.In, SUPMSRPROBER, u.Out); +/** @} */ + +/** @name SUP_IOCTL_RESUME_SUSPENDED_KBDS + * Resume suspended keyboard devices if any found in the system. + * + * @{ + */ +#define SUP_IOCTL_RESUME_SUSPENDED_KBDS SUP_CTL_CODE_SIZE(35, SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE) +#define SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE sizeof(SUPREQHDR) +#define SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE_OUT sizeof(SUPREQHDR) +/** @} */ + + +/** @name SUP_IOCTL_TSC_DELTA_MEASURE + * Measure the TSC-delta between the specified CPU and the master TSC. + * + * To call this I/O control, the client must first have mapped the GIP. + * + * @{ + */ +#define SUP_IOCTL_TSC_DELTA_MEASURE SUP_CTL_CODE_SIZE(36, SUP_IOCTL_TSC_DELTA_MEASURE_SIZE) +#define SUP_IOCTL_TSC_DELTA_MEASURE_SIZE sizeof(SUPTSCDELTAMEASURE) +#define SUP_IOCTL_TSC_DELTA_MEASURE_SIZE_IN sizeof(SUPTSCDELTAMEASURE) +#define SUP_IOCTL_TSC_DELTA_MEASURE_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPTSCDELTAMEASURE +{ + /** The header. */ + SUPREQHDR Hdr; + + /** Input/output union. */ + union + { + struct + { + /** Which CPU to take the TSC-delta measurement for. */ + RTCPUID idCpu; + /** Number of times to retry on failure (specify 0 for default). */ + uint8_t cRetries; + /** Number of milliseconds to wait before each retry. */ + uint8_t cMsWaitRetry; + /** Whether to force taking a measurement if one exists already. */ + bool fForce; + /** Whether to do the measurement asynchronously (if possible). */ + bool fAsync; + } In; + } u; +} SUPTSCDELTAMEASURE, *PSUPTSCDELTAMEASURE; +AssertCompileMemberAlignment(SUPTSCDELTAMEASURE, u, 8); +AssertCompileSize(SUPTSCDELTAMEASURE, 6*4 + 4+1+1+1+1); +/** @} */ + + +/** @name SUP_IOCTL_TSC_READ + * Reads the TSC and apply TSC-delta if applicable, determining the delta if + * necessary (i64TSCDelta = INT64_MAX). + * + * This latter function is the primary use case of this I/O control. To call + * this I/O control, the client must first have mapped the GIP. + * + * @{ + */ +#define SUP_IOCTL_TSC_READ SUP_CTL_CODE_SIZE(37, SUP_IOCTL_TSC_READ_SIZE) +#define SUP_IOCTL_TSC_READ_SIZE sizeof(SUPTSCREAD) +#define SUP_IOCTL_TSC_READ_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_TSC_READ_SIZE_OUT sizeof(SUPTSCREAD) +typedef struct SUPTSCREAD +{ + /** The header. */ + SUPREQHDR Hdr; + + /** Input/output union. */ + union + { + struct + { + /** The TSC after applying the relevant delta. */ + uint64_t u64AdjustedTsc; + /** The APIC Id of the CPU where the TSC was read. */ + uint16_t idApic; + /** Explicit alignment padding. */ + uint16_t auPadding[3]; + } Out; + } u; +} SUPTSCREAD, *PSUPTSCREAD; +AssertCompileMemberAlignment(SUPTSCREAD, u, 8); +AssertCompileSize(SUPTSCREAD, 6*4 + 2*8); +/** @} */ + + +/** @name SUP_IOCTL_GIP_SET_FLAGS + * Set GIP flags. + * + * @{ + */ +#define SUP_IOCTL_GIP_SET_FLAGS SUP_CTL_CODE_SIZE(39, SUP_IOCTL_GIP_SET_FLAGS_SIZE) +#define SUP_IOCTL_GIP_SET_FLAGS_SIZE sizeof(SUPGIPSETFLAGS) +#define SUP_IOCTL_GIP_SET_FLAGS_SIZE_IN sizeof(SUPGIPSETFLAGS) +#define SUP_IOCTL_GIP_SET_FLAGS_SIZE_OUT sizeof(SUPREQHDR) +typedef struct SUPGIPSETFLAGS +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The AND flags mask, see SUPGIP_FLAGS_XXX. */ + uint32_t fAndMask; + /** The OR flags mask, see SUPGIP_FLAGS_XXX. */ + uint32_t fOrMask; + } In; + } u; +} SUPGIPSETFLAGS, *PSUPGIPSETFLAGS; +/** @} */ + + +/** @name SUP_IOCTL_UCODE_REV + * Get the CPU microcode revision. + * + * @{ + */ +#define SUP_IOCTL_UCODE_REV SUP_CTL_CODE_SIZE(40, SUP_IOCTL_UCODE_REV_SIZE) +#define SUP_IOCTL_UCODE_REV_SIZE sizeof(SUPUCODEREV) +#define SUP_IOCTL_UCODE_REV_SIZE_IN sizeof(SUPREQHDR) +#define SUP_IOCTL_UCODE_REV_SIZE_OUT sizeof(SUPUCODEREV) +typedef struct SUPUCODEREV +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** The microcode revision dword. */ + uint32_t MicrocodeRev; + } Out; + } u; +} SUPUCODEREV, *PSUPUCODEREV; +/** @} */ + + +/** @name SUP_IOCTL_HWVIRT_MSRS + * Get hardware-virtualization MSRs. + * + * This queries a lot more information than merely VT-x/AMD-V basic capabilities + * provided by SUP_IOCTL_VT_CAPS. + * + * @{ + */ +#define SUP_IOCTL_GET_HWVIRT_MSRS SUP_CTL_CODE_SIZE(41, SUP_IOCTL_GET_HWVIRT_MSRS_SIZE) +#define SUP_IOCTL_GET_HWVIRT_MSRS_SIZE sizeof(SUPGETHWVIRTMSRS) +#define SUP_IOCTL_GET_HWVIRT_MSRS_SIZE_IN (sizeof(SUPREQHDR) + RT_SIZEOFMEMB(SUPGETHWVIRTMSRS, u.In)) +#define SUP_IOCTL_GET_HWVIRT_MSRS_SIZE_OUT sizeof(SUPGETHWVIRTMSRS) + +typedef struct SUPGETHWVIRTMSRS +{ + /** The header. */ + SUPREQHDR Hdr; + union + { + struct + { + /** Whether to force re-querying of MSRs. */ + bool fForce; + /** Reserved. Must be false. */ + bool fReserved0; + /** Reserved. Must be false. */ + bool fReserved1; + /** Reserved. Must be false. */ + bool fReserved2; + } In; + + struct + { + /** Hardware-virtualization MSRs. */ + SUPHWVIRTMSRS HwvirtMsrs; + } Out; + } u; +} SUPGETHWVIRTMSRS, *PSUPGETHWVIRTMSRS; +/** @} */ + + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# pragma pack() /* paranoia */ +#endif + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPDrvIOC_h */ + diff --git a/src/VBox/HostDrivers/Support/SUPDrvInternal.h b/src/VBox/HostDrivers/Support/SUPDrvInternal.h new file mode 100644 index 00000000..78ad95d8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvInternal.h @@ -0,0 +1,1296 @@ +/* $Id: SUPDrvInternal.h $ */ +/** @file + * VirtualBox Support Driver - Internal header. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPDrvInternal_h +#define VBOX_INCLUDED_SRC_Support_SUPDrvInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/sup.h> + +#include <iprt/assert.h> +#include <iprt/list.h> +#include <iprt/memobj.h> +#include <iprt/time.h> +#include <iprt/timer.h> +#include <iprt/string.h> +#include <iprt/err.h> + +#if defined(SUPDRV_AGNOSTIC) && !defined(RT_OS_LINUX) +/* do nothing */ + +#elif defined(RT_OS_WINDOWS) +# include <iprt/nt/nt.h> +# include <memory.h> + +#elif defined(RT_OS_LINUX) +# include <iprt/linux/version.h> +# if RTLNX_VER_MIN(2,6,33) +# include <generated/autoconf.h> +# else +# ifndef AUTOCONF_INCLUDED +# include <linux/autoconf.h> +# endif +# endif +# if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS) +# define MODVERSIONS +# if RTLNX_VER_MAX(2,5,71) +# include <linux/modversions.h> +# endif +# endif +# ifndef KBUILD_STR +# if RTLNX_VER_MAX(2,6,16) +# define KBUILD_STR(s) s +# else +# define KBUILD_STR(s) #s +# endif +# endif +# ifndef SUPDRV_AGNOSTIC +# include <linux/string.h> +# include <linux/spinlock.h> +# include <linux/slab.h> +# if RTLNX_VER_MIN(2,6,27) +# include <linux/semaphore.h> +# else /* older kernels */ +# include <asm/semaphore.h> +# endif /* older kernels */ +# include <linux/timer.h> +# endif +# if RTLNX_VER_MIN(3,2,0) +# include <linux/export.h> +# else +# include <linux/module.h> +# if (defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64)) && defined(SUPDRV_AGNOSTIC) /* fix conflicts with iprt/x86.h */ +# undef CS +# undef DS +# undef ES +# undef FS +# undef GS +# undef SS +# undef EFLAGS +# undef R15 +# undef R14 +# undef R13 +# undef R12 +# undef R11 +# undef R10 +# undef R9 +# undef R8 +# undef RDI +# undef RSI +# undef RBP +# undef RSP +# undef RBX +# undef RDX +# undef RCX +# undef RAX +# undef MSR_CORE_PERF_LIMIT_REASONS +# undef MSR_DRAM_ENERGY_STATUS +# undef MSR_DRAM_PERF_STATUS +# undef MSR_DRAM_POWER_INFO +# undef MSR_DRAM_POWER_LIMIT +# undef MSR_IA32_APERF +# undef MSR_IA32_ARCH_CAPABILITIES +# undef MSR_IA32_CR_PAT +# undef MSR_IA32_DS_AREA +# undef MSR_IA32_FEATURE_CONTROL +# undef MSR_IA32_FLUSH_CMD +# undef MSR_IA32_MC0_CTL +# undef MSR_IA32_MC0_STATUS +# undef MSR_IA32_MCG_CAP +# undef MSR_IA32_MCG_STATUS +# undef MSR_IA32_MISC_ENABLE +# undef MSR_IA32_MISC_ENABLE_BTS_UNAVAIL +# undef MSR_IA32_MISC_ENABLE_LIMIT_CPUID +# undef MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL +# undef MSR_IA32_MISC_ENABLE_TCC +# undef MSR_IA32_MISC_ENABLE_XD_DISABLE +# undef MSR_IA32_MPERF +# undef MSR_IA32_PEBS_ENABLE +# undef MSR_IA32_PERF_CTL +# undef MSR_IA32_PERF_STATUS +# undef MSR_IA32_PLATFORM_ID +# undef MSR_IA32_PMC0 +# undef MSR_IA32_PRED_CMD +# undef MSR_IA32_RTIT_CTL +# undef MSR_IA32_SMBASE +# undef MSR_IA32_SMM_MONITOR_CTL +# undef MSR_IA32_SPEC_CTRL +# undef MSR_IA32_THERM_STATUS +# undef MSR_IA32_TSC +# undef MSR_IA32_TSC_ADJUST +# undef MSR_IA32_TSX_CTRL +# undef MSR_IA32_VMX_BASIC +# undef MSR_IA32_VMX_CR0_FIXED0 +# undef MSR_IA32_VMX_CR0_FIXED1 +# undef MSR_IA32_VMX_CR4_FIXED0 +# undef MSR_IA32_VMX_CR4_FIXED1 +# undef MSR_IA32_VMX_ENTRY_CTLS +# undef MSR_IA32_VMX_EPT_VPID_CAP +# undef MSR_IA32_VMX_EXIT_CTLS +# undef MSR_IA32_VMX_MISC +# undef MSR_IA32_VMX_PINBASED_CTLS +# undef MSR_IA32_VMX_PROCBASED_CTLS +# undef MSR_IA32_VMX_PROCBASED_CTLS2 +# undef MSR_IA32_VMX_TRUE_ENTRY_CTLS +# undef MSR_IA32_VMX_TRUE_EXIT_CTLS +# undef MSR_IA32_VMX_TRUE_PINBASED_CTLS +# undef MSR_IA32_VMX_TRUE_PROCBASED_CTLS +# undef MSR_IA32_VMX_VMCS_ENUM +# undef MSR_IA32_VMX_VMFUNC +# undef MSR_K6_PFIR +# undef MSR_K6_PSOR +# undef MSR_K6_UWCCR +# undef MSR_K6_WHCR +# undef MSR_K7_EVNTSEL0 +# undef MSR_K7_EVNTSEL1 +# undef MSR_K7_EVNTSEL2 +# undef MSR_K7_EVNTSEL3 +# undef MSR_K7_PERFCTR0 +# undef MSR_K7_PERFCTR1 +# undef MSR_K7_PERFCTR2 +# undef MSR_K7_PERFCTR3 +# undef MSR_K8_SYSCFG +# undef MSR_K8_TOP_MEM1 +# undef MSR_K8_TOP_MEM2 +# undef MSR_OFFCORE_RSP_0 +# undef MSR_OFFCORE_RSP_1 +# undef MSR_PKG_C10_RESIDENCY +# undef MSR_PKG_C2_RESIDENCY +# undef MSR_PKG_CST_CONFIG_CONTROL +# undef MSR_PKG_ENERGY_STATUS +# undef MSR_PKG_PERF_STATUS +# undef MSR_PKG_POWER_INFO +# undef MSR_PKG_POWER_LIMIT +# undef MSR_PKGC3_IRTL +# undef MSR_PP0_ENERGY_STATUS +# undef MSR_PP1_ENERGY_STATUS +# undef MSR_RAPL_POWER_UNIT +# undef MSR_TURBO_ACTIVATION_RATIO +# undef VMX_BASIC_MEM_TYPE_WB +# undef X86_CR0_AM +# undef X86_CR0_CD +# undef X86_CR0_EM +# undef X86_CR0_ET +# undef X86_CR0_MP +# undef X86_CR0_NE +# undef X86_CR0_NW +# undef X86_CR0_PE +# undef X86_CR0_PG +# undef X86_CR0_TS +# undef X86_CR0_WP +# undef X86_CR3_PCD +# undef X86_CR3_PWT +# undef X86_CR4_DE +# undef X86_CR4_FSGSBASE +# undef X86_CR4_MCE +# undef X86_CR4_OSFXSR +# undef X86_CR4_OSXSAVE +# undef X86_CR4_PAE +# undef X86_CR4_PCE +# undef X86_CR4_PCIDE +# undef X86_CR4_PGE +# undef X86_CR4_PKE +# undef X86_CR4_PSE +# undef X86_CR4_PVI +# undef X86_CR4_SMAP +# undef X86_CR4_SMEP +# undef X86_CR4_SMXE +# undef X86_CR4_TSD +# undef X86_CR4_UMIP +# undef X86_CR4_VME +# undef X86_CR4_VMXE +# endif +# endif +# define SUPR0_EXPORT_SYMBOL(a_Name) EXPORT_SYMBOL(a_Name) + +#elif defined(RT_OS_DARWIN) +# include <libkern/libkern.h> +# include <iprt/string.h> + +#elif defined(RT_OS_OS2) + +#elif defined(RT_OS_FREEBSD) +# define memset libkern_memset /** @todo these are just hacks to get it compiling, check out later. */ +# define memcmp libkern_memcmp +# define strchr libkern_strchr +# define strrchr libkern_strrchr +# define ffsl libkern_ffsl +# define fls libkern_fls +# define flsl libkern_flsl +# include <sys/libkern.h> +# undef memset +# undef memcmp +# undef strchr +# undef strrchr +# undef ffs +# undef ffsl +# undef fls +# undef flsl +# include <iprt/string.h> + +#elif defined(RT_OS_SOLARIS) +# include <sys/cmn_err.h> +# include <iprt/string.h> + +#else +# error "unsupported OS." +#endif + +#include "SUPDrvIOC.h" +#include "SUPDrvIDC.h" + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Hardcoded cookies. + */ +#define BIRD 0x64726962 /* 'bird' */ +#define BIRD_INV 0x62697264 /* 'drib' */ + + +#ifdef RT_OS_WINDOWS +/** Use a normal mutex for the loader so we remain at the same IRQL after + * taking it. + * @todo fix the mutex implementation on linux and make this the default. */ +# define SUPDRV_USE_MUTEX_FOR_LDR + +/** Use a normal mutex for the GIP so we remain at the same IRQL after + * taking it. + * @todo fix the mutex implementation on linux and make this the default. */ +# define SUPDRV_USE_MUTEX_FOR_GIP +#endif + +#ifndef SUPR0_EXPORT_SYMBOL +# define SUPR0_EXPORT_SYMBOL(a_Name) extern int g_supDrvExportSymbolDummyVariable +#endif + +/** + * OS debug print macro. + */ +#define OSDBGPRINT(a) SUPR0Printf a + + +/** @name Context values for the per-session handle tables. + * The context value is used to distinguish between the different kinds of + * handles, making the handle table API do all the work. + * @{ */ +/** Handle context value for single release event handles. */ +#define SUPDRV_HANDLE_CTX_EVENT ((void *)(uintptr_t)(SUPDRVOBJTYPE_SEM_EVENT)) +/** Handle context value for multiple release event handles. */ +#define SUPDRV_HANDLE_CTX_EVENT_MULTI ((void *)(uintptr_t)(SUPDRVOBJTYPE_SEM_EVENT_MULTI)) +/** @} */ + + +/** + * Validates a session pointer. + * + * @returns true/false accordingly. + * @param pSession The session. + */ +#define SUP_IS_SESSION_VALID(pSession) \ + ( RT_VALID_PTR(pSession) \ + && pSession->u32Cookie == BIRD_INV) + +/** + * Validates a device extension pointer. + * + * @returns true/false accordingly. + * @param pDevExt The device extension. + */ +#define SUP_IS_DEVEXT_VALID(pDevExt) \ + ( RT_VALID_PTR(pDevExt) \ + && pDevExt->u32Cookie == BIRD) + + +/** @def SUPDRV_WITH_MSR_PROBER + * Enables the SUP_IOCTL_MSR_PROBER function. + * By default, only enabled in DEBUG builds as it's a sensitive feature. + */ +#if defined(DEBUG) && !defined(SUPDRV_WITH_MSR_PROBER) && !defined(SUPDRV_WITHOUT_MSR_PROBER) +# define SUPDRV_WITH_MSR_PROBER +#endif + +/** @def SUPDRV_WITHOUT_MSR_PROBER + * Executive overide for disabling the SUP_IOCTL_MSR_PROBER function. + */ +#ifdef SUPDRV_WITHOUT_MSR_PROBER +# undef SUPDRV_WITH_MSR_PROBER +#endif + +#ifdef DOXYGEN_RUNNING +# define SUPDRV_WITH_MSR_PROBER +# define SUPDRV_WITHOUT_MSR_PROBER +#endif + +#if 1 +/** @def SUPDRV_USE_TSC_DELTA_THREAD + * Use a dedicated kernel thread to service TSC-delta measurement requests. + * @todo Test on servers with many CPUs and sockets. */ +# define SUPDRV_USE_TSC_DELTA_THREAD +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the device extension. */ +typedef struct SUPDRVDEVEXT *PSUPDRVDEVEXT; + +#ifdef SUPDRV_USE_TSC_DELTA_THREAD +/** + * TSC-delta measurement thread state machine. + */ +typedef enum SUPDRVTSCDELTATHREADSTATE +{ + /** Uninitialized/invalid value. */ + kTscDeltaThreadState_Invalid = 0, + /** The thread is being created. + * Next state: Listening, Butchered, Terminating */ + kTscDeltaThreadState_Creating, + /** The thread is listening for events. + * Previous state: Creating, Measuring + * Next state: WaitAndMeasure, Butchered, Terminated */ + kTscDeltaThreadState_Listening, + /** The thread is sleeping before starting a measurement. + * Previous state: Listening, Measuring + * Next state: Measuring, Butchered, Terminating + * @remarks The thread won't enter this state on its own, it is put into this + * state by the GIP timer, the CPU online callback and by the + * SUP_IOCTL_TSC_DELTA_MEASURE code. */ + kTscDeltaThreadState_WaitAndMeasure, + /** The thread is currently servicing a measurement request. + * Previous state: WaitAndMeasure + * Next state: Listening, WaitAndMeasure, Terminate */ + kTscDeltaThreadState_Measuring, + /** The thread is terminating. + * @remarks The thread won't enter this state on its own, is put into this state + * by supdrvTscDeltaTerm. */ + kTscDeltaThreadState_Terminating, + /** The thread is butchered due to an unexpected error. + * Previous State: Creating, Listening, WaitAndMeasure */ + kTscDeltaThreadState_Butchered, + /** The thread is destroyed (final). + * Previous state: Terminating */ + kTscDeltaThreadState_Destroyed, + /** The usual 32-bit blowup hack. */ + kTscDeltaThreadState_32BitHack = 0x7fffffff +} SUPDRVTSCDELTATHREADSTATE; +#endif /* SUPDRV_USE_TSC_DELTA_THREAD */ + +/** + * Memory reference types. + */ +typedef enum +{ + /** Unused entry */ + MEMREF_TYPE_UNUSED = 0, + /** Locked memory (r3 mapping only). */ + MEMREF_TYPE_LOCKED, + /** Continuous memory block (r3 and r0 mapping). */ + MEMREF_TYPE_CONT, + /** Low memory block (r3 and r0 mapping). */ + MEMREF_TYPE_LOW, + /** Memory block (r3 and r0 mapping). */ + MEMREF_TYPE_MEM, + /** Locked memory (r3 mapping only) allocated by the support driver. */ + MEMREF_TYPE_PAGE, + /** Blow the type up to 32-bit and mark the end. */ + MEMREF_TYPE_32BIT_HACK = 0x7fffffff +} SUPDRVMEMREFTYPE, *PSUPDRVMEMREFTYPE; + + +/** + * Structure used for tracking memory a session + * references in one way or another. + */ +typedef struct SUPDRVMEMREF +{ + /** The memory object handle. */ + RTR0MEMOBJ MemObj; + /** The ring-3 mapping memory object handle. */ + RTR0MEMOBJ MapObjR3; + /** Type of memory. */ + SUPDRVMEMREFTYPE eType; +} SUPDRVMEMREF, *PSUPDRVMEMREF; + + +/** + * Bundle of locked memory ranges. + */ +typedef struct SUPDRVBUNDLE +{ + /** Pointer to the next bundle. */ + struct SUPDRVBUNDLE * volatile pNext; + /** Referenced memory. */ + SUPDRVMEMREF aMem[64]; + /** Number of entries used. */ + uint32_t volatile cUsed; +} SUPDRVBUNDLE, *PSUPDRVBUNDLE; + + +/** + * Loaded image. + */ +typedef struct SUPDRVLDRIMAGE +{ + /** Next in chain. */ + struct SUPDRVLDRIMAGE * volatile pNext; + /** Pointer to the image. */ + void *pvImage; + /** The memory object for the module allocation. */ + RTR0MEMOBJ hMemObjImage; + /** Magic value (SUPDRVLDRIMAGE_MAGIC). */ + uint32_t uMagic; + /** Size of the image including the tables. This is mainly for verification + * of the load request. */ + uint32_t cbImageWithEverything; + /** Size of the image. */ + uint32_t cbImageBits; + /** The number of entries in the symbol table. */ + uint32_t cSymbols; + /** Pointer to the symbol table. */ + PSUPLDRSYM paSymbols; + /** The offset of the string table. */ + char *pachStrTab; + /** Size of the string table. */ + uint32_t cbStrTab; + /** Number of segments. */ + uint32_t cSegments; + /** Segments (for memory protection). */ + PSUPLDRSEG paSegments; + /** Pointer to the optional module initialization callback. */ + PFNR0MODULEINIT pfnModuleInit; + /** Pointer to the optional module termination callback. */ + PFNR0MODULETERM pfnModuleTerm; + /** Service request handler. This is NULL for non-service modules. */ + PFNSUPR0SERVICEREQHANDLER pfnServiceReqHandler; + /** The ldr image state. (IOCtl code of last operation.) */ + uint32_t uState; + /** Usage count. */ + uint32_t volatile cImgUsage; + /** Pointer to the device extension. */ + struct SUPDRVDEVEXT *pDevExt; + /** Image (VMMR0.r0) containing functions/data that this one uses. */ + struct SUPDRVLDRIMAGE *pImageImport; +#ifdef RT_OS_WINDOWS + /** The section object for the loaded image (fNative=true). */ + void *pvNtSectionObj; + /** Lock object. */ + RTR0MEMOBJ hMemLock; +#endif +#if defined(RT_OS_SOLARIS) && defined(VBOX_WITH_NATIVE_SOLARIS_LOADING) + /** The Solaris module ID. */ + int idSolMod; + /** Pointer to the module control structure. */ + struct modctl *pSolModCtl; +#endif +#ifdef RT_OS_LINUX + /** Hack for seeing the module in perf, dtrace and other stack crawlers. */ + struct module *pLnxModHack; + /** The wrapper module. */ + struct module *pLnxWrapperModule; + /** Set if we're holding a reference to the wrapper module. */ + bool fLnxWrapperRef; +#endif +#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION) + /** Load module handle. */ + RTLDRMOD hLdrMod; + /** Allocate object. */ + RTR0MEMOBJ hMemAlloc; +#endif + /** This points to the module info if the image is a wrapped up in a native one. */ + PCSUPLDRWRAPPEDMODULE pWrappedModInfo; + /** OS specific information for wrapped modules. */ + void *pvWrappedNative; + /** Whether it's loaded by the native loader or not. */ + bool fNative; + /** Image name. */ + char szName[32]; +} SUPDRVLDRIMAGE, *PSUPDRVLDRIMAGE; + +/** Magic value for SUPDRVLDRIMAGE::uMagic (Charlotte Bronte). */ +#define SUPDRVLDRIMAGE_MAGIC UINT32_C(0x18160421) +/** Magic value for SUPDRVLDRIMAGE::uMagic when freed. */ +#define SUPDRVLDRIMAGE_MAGIC_DEAD UINT32_C(0x18550331) + + +/** Image usage record. */ +typedef struct SUPDRVLDRUSAGE +{ + /** Next in chain. */ + struct SUPDRVLDRUSAGE * volatile pNext; + /** The image. */ + PSUPDRVLDRIMAGE pImage; + /** Load count (ring-3). */ + uint32_t volatile cRing3Usage; + /** Ring-0 usage counter. */ + uint32_t volatile cRing0Usage; +} SUPDRVLDRUSAGE, *PSUPDRVLDRUSAGE; + + +/** + * Component factory registration record. + */ +typedef struct SUPDRVFACTORYREG +{ + /** Pointer to the next registration. */ + struct SUPDRVFACTORYREG *pNext; + /** Pointer to the registered factory. */ + PCSUPDRVFACTORY pFactory; + /** The session owning the factory. + * Used for deregistration and session cleanup. */ + PSUPDRVSESSION pSession; + /** Length of the name. */ + size_t cchName; +} SUPDRVFACTORYREG; +/** Pointer to a component factory registration record. */ +typedef SUPDRVFACTORYREG *PSUPDRVFACTORYREG; +/** Pointer to a const component factory registration record. */ +typedef SUPDRVFACTORYREG const *PCSUPDRVFACTORYREG; + + +/** + * Registered object. + * This takes care of reference counting and tracking data for access checks. + */ +typedef struct SUPDRVOBJ +{ + /** Magic value (SUPDRVOBJ_MAGIC). */ + uint32_t u32Magic; + /** The object type. */ + SUPDRVOBJTYPE enmType; + /** Pointer to the next in the global list. */ + struct SUPDRVOBJ * volatile pNext; + /** Pointer to the object destructor. + * This may be set to NULL if the image containing the destructor get unloaded. */ + PFNSUPDRVDESTRUCTOR pfnDestructor; + /** User argument 1. */ + void *pvUser1; + /** User argument 2. */ + void *pvUser2; + /** The total sum of all per-session usage. */ + uint32_t volatile cUsage; + /** The creator user id. */ + RTUID CreatorUid; + /** The creator group id. */ + RTGID CreatorGid; + /** The creator process id. */ + RTPROCESS CreatorProcess; +} SUPDRVOBJ, *PSUPDRVOBJ; + +/** Magic number for SUPDRVOBJ::u32Magic. (Dame Agatha Mary Clarissa Christie). */ +#define SUPDRVOBJ_MAGIC UINT32_C(0x18900915) +/** Dead number magic for SUPDRVOBJ::u32Magic. */ +#define SUPDRVOBJ_MAGIC_DEAD UINT32_C(0x19760112) + +/** + * The per-session object usage record. + */ +typedef struct SUPDRVUSAGE +{ + /** Pointer to the next in the list. */ + struct SUPDRVUSAGE * volatile pNext; + /** Pointer to the object we're recording usage for. */ + PSUPDRVOBJ pObj; + /** The usage count. */ + uint32_t volatile cUsage; +} SUPDRVUSAGE, *PSUPDRVUSAGE; + + +/** + * I/O control context. + */ +typedef struct SUPR0IOCTLCTX +{ + /** Magic value (SUPR0IOCTLCTX_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; +#ifdef RT_OS_WINDOWS +# ifndef SUPDRV_AGNOSTIC + /** The file object, referenced. */ + PFILE_OBJECT pFileObject; + /** The device object, not referenced. */ + PDEVICE_OBJECT pDeviceObject; + /** Pointer to fast I/O routine if available. */ + FAST_IO_DEVICE_CONTROL *pfnFastIoDeviceControl; +# else + void *apvPadding[3]; +# endif +#endif +} SUPR0IOCTLCTX; +/** Magic value for SUPR0IOCTLCTX (Ahmad Jamal). */ +#define SUPR0IOCTLCTX_MAGIC UINT32_C(0x19300702) + + +/** + * Per session data. + * This is mainly for memory tracking. + */ +typedef struct SUPDRVSESSION +{ + /** Pointer to the device extension. */ + PSUPDRVDEVEXT pDevExt; + /** Session Cookie. */ + uint32_t u32Cookie; + /** Set if is an unrestricted session, clear if restricted. */ + bool fUnrestricted; + + /** 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 GVM associated with the session. + * This is set by VMMR0. */ + PGVM pSessionGVM; + /** The VM associated with the session. + * This is set by VMMR0. */ + PVM pSessionVM; + /** Set to pSessionVM if fast I/O controlls are enabled. */ + PVM pFastIoCtrlVM; + /** Handle table for IPRT semaphore wrapper APIs. + * This takes care of its own locking in an IRQ safe manner. */ + RTHANDLETABLE hHandleTable; + /** Load usage records (LIFO!). (protected by SUPDRVDEVEXT::mtxLdr) */ + PSUPDRVLDRUSAGE volatile pLdrUsage; + + /** 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; + /** Set if the session is using the GIP. */ + uint32_t fGipReferenced; + /** Bundle of locked memory objects. */ + SUPDRVBUNDLE Bundle; + /** List of generic usage records. (protected by SUPDRVDEVEXT::SpinLock) */ + PSUPDRVUSAGE volatile pUsage; + + /** The user id of the session - set by the OS part or NIL_RTUID. + * This should be unique accross namespace/zones/whatever. */ + RTUID Uid; + /** The group id of the session - set by the OS part or NIL_RTGID. + * This should be unique accross namespace/zones/whatever. */ + RTGID Gid; + /** Per session tracer specfic data. */ + uintptr_t uTracerData; + /** The thread currently actively talking to the tracer. (One at the time!) */ + RTNATIVETHREAD hTracerCaller; + /** List of tracepoint providers associated with the session + * (SUPDRVTPPROVIDER). */ + RTLISTANCHOR TpProviders; + /** The number of providers in TpProviders. */ + uint32_t cTpProviders; + /** The number of threads active in supdrvIOCtl_TracerUmodProbeFire or + * SUPR0TracerUmodProbeFire. */ + uint32_t volatile cTpProbesFiring; + /** User tracepoint modules (PSUPDRVTRACKERUMOD). */ + RTLISTANCHOR TpUmods; + /** The user tracepoint module lookup table. */ + struct SUPDRVTRACERUMOD *apTpLookupTable[32]; + /** Whether this is a GIP test-mode client session or not. */ + bool fGipTestMode; +#ifndef SUPDRV_AGNOSTIC +# if defined(RT_OS_DARWIN) + /** Pointer to the associated org_virtualbox_SupDrvClient object. */ + void *pvSupDrvClient; + /** Whether this session has been opened or not. */ + bool fOpened; +# endif +# if defined(RT_OS_OS2) + /** The system file number of this session. */ + uint16_t sfn; + uint16_t Alignment; /**< Alignment */ +# endif +# if defined(RT_OS_DARWIN) || defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) + /** 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 +# if defined(RT_OS_WINDOWS) + /** Reference to the user ID structure corresponding to the Uid member. */ + struct SUPDRVNTUSERID *pNtUserId; +# endif +#endif /* !SUPDRV_AGNOSTIC */ +} SUPDRVSESSION; + + +/** + * Device extension. + */ +typedef struct SUPDRVDEVEXT +{ + /** Global cookie. */ + uint32_t u32Cookie; + /** The actual size of SUPDRVSESSION. (SUPDRV_AGNOSTIC) */ + uint32_t cbSession; + + /** Spinlock to serialize the initialization, usage counting and objects. + * This is IRQ safe because we want to be able signal semaphores from the + * special HM context (and later maybe interrupt handlers), so we must be able + * to reference and dereference handles when IRQs are disabled. */ + RTSPINLOCK Spinlock; + + /** List of registered objects. Protected by the spinlock. */ + PSUPDRVOBJ volatile pObjs; + /** List of free object usage records. */ + PSUPDRVUSAGE volatile pUsageFree; + + /** Loader mutex. + * This protects pvVMMR0, pvVMMR0Entry, pImages and SUPDRVSESSION::pLdrUsage. */ +#ifdef SUPDRV_USE_MUTEX_FOR_LDR + RTSEMMUTEX mtxLdr; +#else + RTSEMFASTMUTEX mtxLdr; +#endif + + /** VMM Module 'handle'. + * 0 if the code VMM isn't loaded and Idt are nops. */ + void * volatile pvVMMR0; + /** VMMR0EntryFast() pointer. */ + DECLR0CALLBACKMEMBER(void, pfnVMMR0EntryFast, (PGVM pGVM, PVM pVM, VMCPUID idCpu, uint32_t uOperation)); + /** VMMR0EntryEx() pointer. */ + DECLR0CALLBACKMEMBER(int, pfnVMMR0EntryEx, (PGVM pGVM, PVM pVM, VMCPUID idCpu, uint32_t uOperation, + PSUPVMMR0REQHDR pReq, uint64_t u64Arg, PSUPDRVSESSION pSession)); + + /** Linked list of loaded code. */ + PSUPDRVLDRIMAGE volatile pLdrImages; + /** Set if the image loading interface got disabled after loading all needed images */ + bool fLdrLockedDown; + + /** @name These members for detecting whether an API caller is in ModuleInit. + * Certain APIs are only permitted from ModuleInit, like for instance tracepoint + * registration. + * @{ */ + /** The image currently executing its ModuleInit. */ + PSUPDRVLDRIMAGE volatile pLdrInitImage; + /** The thread currently executing a ModuleInit function. */ + RTNATIVETHREAD volatile hLdrInitThread; + /** The thread currently executing a ModuleTerm function. */ + RTNATIVETHREAD volatile hLdrTermThread; + /** @} */ + + /** Number of times someone reported bad execution context via SUPR0BadContext. + * (This is times EFLAGS.AC is zero when we expected it to be 1.) */ + uint32_t volatile cBadContextCalls; + + /** GIP mutex. + * Any changes to any of the GIP members requires ownership of this mutex, + * except on driver init and termination. */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSEMMUTEX mtxGip; +#else + RTSEMFASTMUTEX mtxGip; +#endif + /** GIP spinlock protecting GIP members during Mp events. + * This is IRQ safe since be may get MP callbacks in contexts where IRQs are + * disabled (on some platforms). */ + RTSPINLOCK hGipSpinlock; + /** Pointer to the Global Info Page (GIP). */ + PSUPGLOBALINFOPAGE pGip; + /** The physical address of the GIP. */ + RTHCPHYS HCPhysGip; + /** Number of processes using the GIP. + * (The updates are suspend while cGipUsers is 0.)*/ + uint32_t volatile cGipUsers; + /** The ring-0 memory object handle for the GIP page. */ + RTR0MEMOBJ GipMemObj; + /** The GIP timer handle. */ + PRTTIMER pGipTimer; + /** If non-zero we've successfully called RTTimerRequestSystemGranularity(). */ + uint32_t u32SystemTimerGranularityGrant; + /** The CPU id of the GIP master. + * This CPU is responsible for the updating the common GIP data and it is + * the one used to calculate TSC deltas relative to. + * (The initial master will have a 0 zero value, but it it goes offline the + * new master may have a non-zero value.) */ + RTCPUID volatile idGipMaster; + + /** Component factory mutex. + * This protects pComponentFactoryHead and component factory querying. */ + RTSEMFASTMUTEX mtxComponentFactory; + /** The head of the list of registered component factories. */ + PSUPDRVFACTORYREG pComponentFactoryHead; + + /** Lock protecting The tracer members. */ + RTSEMFASTMUTEX mtxTracer; + /** List of tracer providers (SUPDRVTPPROVIDER). */ + RTLISTANCHOR TracerProviderList; + /** List of zombie tracer providers (SUPDRVTPPROVIDER). */ + RTLISTANCHOR TracerProviderZombieList; + /** Pointer to the tracer registration record. */ + PCSUPDRVTRACERREG pTracerOps; + /** The ring-0 session of a native tracer provider. */ + PSUPDRVSESSION pTracerSession; + /** The image containing the tracer. */ + PSUPDRVLDRIMAGE pTracerImage; + /** The tracer helpers. */ + SUPDRVTRACERHLP TracerHlp; + /** The number of session having opened the tracer currently. */ + uint32_t cTracerOpens; + /** The number of threads currently calling into the tracer. */ + uint32_t volatile cTracerCallers; + /** Set if the tracer is being unloaded. */ + bool fTracerUnloading; + /** 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; + /** @} */ + + /** @name Invariant TSC frequency refinement. + * @{ */ + /** Nanosecond timestamp at the start of the TSC frequency refinement phase. */ + uint64_t nsStartInvarTscRefine; + /** TSC reading at the start of the TSC frequency refinement phase. */ + uint64_t uTscStartInvarTscRefine; + /** The CPU id of the CPU that u64TscAnchor was measured on. */ + RTCPUID idCpuInvarTscRefine; + /** Pointer to the timer used to refine the TSC frequency. */ + PRTTIMER pInvarTscRefineTimer; + /** Stop the timer on the next tick because we saw a power event. */ + bool volatile fInvTscRefinePowerEvent; + /** @} */ + + /** @name TSC-delta measurement. + * @{ */ + /** Number of online/offline events, incremented each time a CPU goes online + * or offline. */ + uint32_t volatile cMpOnOffEvents; + /** TSC-delta measurement mutext. + * At the moment, we don't want to have more than one measurement going on at + * any one time. We might be using broadcast IPIs which are heavy and could + * perhaps get in each others way. */ +#ifdef SUPDRV_USE_MUTEX_FOR_GIP + RTSEMMUTEX mtxTscDelta; +#else + RTSEMFASTMUTEX mtxTscDelta; +#endif + /** The set of CPUs we need to take measurements for. */ + RTCPUSET TscDeltaCpuSet; + /** The set of CPUs we have completed taken measurements for. */ + RTCPUSET TscDeltaObtainedCpuSet; + /** @} */ + +#ifdef SUPDRV_USE_TSC_DELTA_THREAD + /** @name TSC-delta measurement thread. + * @{ */ + /** Spinlock protecting enmTscDeltaThreadState. */ + RTSPINLOCK hTscDeltaSpinlock; + /** TSC-delta measurement thread. */ + RTTHREAD hTscDeltaThread; + /** The event signalled during state changes to the TSC-delta thread. */ + RTSEMEVENT hTscDeltaEvent; + /** The state of the TSC-delta measurement thread. */ + SUPDRVTSCDELTATHREADSTATE enmTscDeltaThreadState; + /** Thread timeout time before rechecking state in ms. */ + RTMSINTERVAL cMsTscDeltaTimeout; + /** Whether the TSC-delta measurement was successful. */ + int32_t volatile rcTscDelta; + /** Tell the thread we want TSC-deltas for all CPUs with retries. */ + bool fTscThreadRecomputeAllDeltas; + /** @} */ +#endif + + /** @name GIP test mode. + * @{ */ + /** Reference counter for GIP test-mode sessions. */ + uint32_t cGipTestModeRefs; + /** Cache of TSC frequency before enabling test-mode on invariant GIP systems. */ + uint64_t uGipTestModeInvariantCpuHz; + /** @} */ + + /* + * Note! The non-agnostic bits must be at the very end of the structure! + */ +#ifndef SUPDRV_AGNOSTIC +# ifdef RT_OS_WINDOWS + /** Callback object returned by ExCreateCallback. */ + PCALLBACK_OBJECT pObjPowerCallback; + /** Callback handle returned by ExRegisterCallback. */ + PVOID hPowerCallback; +# elif defined(RT_OS_DARWIN) && defined(VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION) + /** Trusted root certificates for code signing validation. */ + RTCRSTORE hRootStore; + /** Intermedite certificates for code signing validation. */ + RTCRSTORE hAdditionalStore; +# endif +#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); + +/** + * Called during GIP initializtion to calc the CPU group table size. + * + * This is currently only implemented on windows [lazy bird]. + * + * @returns Number of bytes needed for SUPGIPCPUGROUP structures. + * @param pDevExt The device globals. + */ +size_t VBOXCALL supdrvOSGipGetGroupTableSize(PSUPDRVDEVEXT pDevExt); + +/** + * Called during GIP initialization to set up the group table and group count. + * + * This is currently only implemented on windows [lazy bird]. + * + * @param pDevExt The device globals. + * @param pGip The GIP which group table needs initialization. + * It's only partially initialized at this point. + * @param cbGipCpuGroups What supdrvOSGipGetGroupTableSize returned. + */ +int VBOXCALL supdrvOSInitGipGroupTable(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, size_t cbGipCpuGroups); + +/** + * Initializes the group related members when a CPU is added to the GIP. + * + * This is called both during GIP initalization and during an CPU online event. + * + * This is currently only implemented on windows [lazy bird]. + * + * @param pDevExt The device globals. + * @param pGip The GIP. + * @param pGipCpu The GIP CPU structure being initialized. + */ +void VBOXCALL supdrvOSGipInitGroupBitsForCpu(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pGipCpu); + +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession); +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc); +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt); +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void); +bool VBOXCALL supdrvOSAreTscDeltasInSync(void); +int VBOXCALL supdrvOSEnableVTx(bool fEnabled); +RTCCUINTREG VBOXCALL supdrvOSChangeCR4(RTCCUINTREG fOrMask, RTCCUINTREG fAndMask); +bool VBOXCALL supdrvOSSuspendVTxOnCpu(void); +void VBOXCALL supdrvOSResumeVTxOnCpu(bool fSuspended); +int VBOXCALL supdrvOSGetCurrentGdtRw(RTHCUINTPTR *pGdtRw); + +/** + * Try open the image using the native loader. + * + * @returns IPRT status code. + * @retval VERR_NOT_SUPPORTED if native loading isn't supported. + * + * @param pDevExt The device globals. + * @param pImage The image handle. pvImage should be set on + * success, pvImageAlloc can also be set if + * appropriate. + * @param pszFilename The file name - UTF-8, may containing UNIX + * slashes on non-UNIX systems. + */ +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename); + +/** + * Notification call indicating that a image is being opened for the first time. + * + * Called for both native and non-native images (after supdrvOSLdrOpen). Can be + * used to log the load address of the image or inform the kernel about the + * alien image. + * + * @param pDevExt The device globals. + * @param pImage The image handle. + * @param pszFilename The file name - UTF-8, may containing UNIX + * slashes on non-UNIX systems. + */ +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename); + +/** + * Validates an entry point address. + * + * Called before supdrvOSLdrLoad. + * + * @returns IPRT status code. + * @param pDevExt The device globals. + * @param pImage The image data (still in the open state). + * @param pv The address within the image. + * @param pbImageBits The image bits as loaded by ring-3. + * @param pszSymbol The name of the entrypoint being checked. + */ +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + void *pv, const uint8_t *pbImageBits, const char *pszSymbol); + +/** + * Load the image. + * + * @returns IPRT status code. + * @param pDevExt The device globals. + * @param pImage The image data (up to date). Adjust entrypoints + * and exports if necessary. + * @param pbImageBits The image bits as loaded by ring-3. + * @param pReq Pointer to the request packet so that the VMMR0 + * entry points can be adjusted. + */ +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq); + +/** + * Unload the image (only called if supdrvOSLdrOpen returned success). + * + * @param pDevExt The device globals. + * @param pImage The image data (mostly still valid). + */ +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); + +/** + * Notification call indicating that a image is being unloaded. + * + * Called for both native and non-native images. In the former case, it's + * called after supdrvOSLdrUnload. + * + * @param pDevExt The device globals. + * @param pImage The image handle. + */ +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); + +/** + * Queries a symbol address is a native module. + * + * @returns IPRT status code. + * @param pDevExt The device globals. + * @param pImage The image to search. + * @param pszSymbol The symbol to search for. + * @param cchSymbol The length of the symbol. + * @param ppvSymbol Where to return the symbol address if found. + */ +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol); + +/** + * Retains a native wrapper module when it is first being used. + * + * This will be call when pImage->cImgUsage is incremented to 2. + * + * @param pDevExt The device globals. + * @param pImage The wrapped image. + */ +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); + +/** + * Release a native wrapper module when it is no longer being used. + * + * This will be call when pImage->cImgUsage is decremented to 1. + * + * @param pDevExt The device globals. + * @param pImage The wrapped image. + */ +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); + +#ifdef SUPDRV_WITH_MSR_PROBER + +/** + * Tries to read an MSR. + * + * @returns One of the listed VBox status codes. + * @retval VINF_SUCCESS if read successfully, value in *puValue. + * @retval VERR_ACCESS_DENIED if we couldn't read it (GP). + * @retval VERR_NOT_SUPPORTED if not supported. + * + * @param uMsr The MSR to read from. + * @param idCpu The CPU to read the MSR on. NIL_RTCPUID + * indicates any suitable CPU. + * @param puValue Where to return the value. + */ +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue); + +/** + * Tries to write an MSR. + * + * @returns One of the listed VBox status codes. + * @retval VINF_SUCCESS if written successfully. + * @retval VERR_ACCESS_DENIED if we couldn't write the value to it (GP). + * @retval VERR_NOT_SUPPORTED if not supported. + * + * @param uMsr The MSR to write to. + * @param idCpu The CPU to write the MSR on. NIL_RTCPUID + * indicates any suitable CPU. + * @param uValue The value to write. + */ +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue); + +/** + * Tries to modify an MSR value. + * + * @returns One of the listed VBox status codes. + * @retval VINF_SUCCESS if succeeded. + * @retval VERR_NOT_SUPPORTED if not supported. + * + * @param idCpu The CPU to modify the MSR on. NIL_RTCPUID + * indicates any suitable CPU. + * @param pReq The request packet with input arguments and + * where to store the results. + */ +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq); + +#endif /* SUPDRV_WITH_MSR_PROBER */ + +#if defined(RT_OS_DARWIN) +int VBOXCALL supdrvDarwinResumeSuspendedKbds(void); +#endif + + +/********************************************************************************************************************************* +* Shared Functions * +*********************************************************************************************************************************/ +/* SUPDrv.c */ +int VBOXCALL supdrvIOCtl(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPREQHDR pReqHdr, size_t cbReq); +int VBOXCALL supdrvIOCtlFast(uintptr_t uOperation, VMCPUID idCpu, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession); +int VBOXCALL supdrvIDC(uintptr_t uIOCtl, PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVIDCREQHDR pReqHdr); +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); +void VBOXCALL supdrvBadContext(PSUPDRVDEVEXT pDevExt, const char *pszFile, uint32_t uLine, const char *pszExtra); +int VBOXCALL supdrvQueryVTCapsInternal(uint32_t *pfCaps); +int VBOXCALL supdrvLdrLoadError(int rc, PSUPLDRLOAD pReq, const char *pszFormat, ...); +int VBOXCALL supdrvLdrGetExportedSymbol(const char *pszSymbol, uintptr_t *puValue); +int VBOXCALL supdrvLdrRegisterWrappedModule(PSUPDRVDEVEXT pDevExt, PCSUPLDRWRAPPEDMODULE pWrappedModInfo, + void *pvNative, void **phMod); +int VBOXCALL supdrvLdrDeregisterWrappedModule(PSUPDRVDEVEXT pDevExt, PCSUPLDRWRAPPEDMODULE pWrappedModInfo, void **phMod); + + +/* SUPDrvGip.cpp */ +int VBOXCALL supdrvGipCreate(PSUPDRVDEVEXT pDevExt); +void VBOXCALL supdrvGipDestroy(PSUPDRVDEVEXT pDevExt); +int VBOXCALL supdrvIOCtl_TscDeltaMeasure(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPTSCDELTAMEASURE pReq); +int VBOXCALL supdrvIOCtl_TscRead(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPTSCREAD pReq); +int VBOXCALL supdrvIOCtl_GipSetFlags(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t fOrMask, uint32_t fAndMask); + + +/* SUPDrvTracer.cpp */ +int VBOXCALL supdrvTracerInit(PSUPDRVDEVEXT pDevExt); +void VBOXCALL supdrvTracerTerm(PSUPDRVDEVEXT pDevExt); +void VBOXCALL supdrvTracerModuleUnloading(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage); +void VBOXCALL supdrvTracerCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession); +int VBOXCALL supdrvIOCtl_TracerUmodRegister(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, + RTR3PTR R3PtrVtgHdr, RTUINTPTR uVtgHdrAddr, + RTR3PTR R3PtrStrTab, uint32_t cbStrTab, + const char *pszModName, uint32_t fFlags); +int VBOXCALL supdrvIOCtl_TracerUmodDeregister(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, RTR3PTR R3PtrVtgHdr); +void VBOXCALL supdrvIOCtl_TracerUmodProbeFire(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVTRACERUSRCTX pCtx); +int VBOXCALL supdrvIOCtl_TracerOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t uCookie, uintptr_t uArg); +int VBOXCALL supdrvIOCtl_TracerClose(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession); +int VBOXCALL supdrvIOCtl_TracerIOCtl(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uintptr_t uCmd, uintptr_t uArg, int32_t *piRetVal); +extern PFNRT g_pfnSupdrvProbeFireKernel; +DECLASM(void) supdrvTracerProbeFireStub(void); + +#ifdef VBOX_WITH_NATIVE_DTRACE +const SUPDRVTRACERREG * VBOXCALL supdrvDTraceInit(void); +void VBOXCALL supdrvDTraceFini(void); +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPDrvInternal_h */ + diff --git a/src/VBox/HostDrivers/Support/SUPDrvSem.cpp b/src/VBox/HostDrivers/Support/SUPDrvSem.cpp new file mode 100644 index 00000000..48b65ca3 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvSem.cpp @@ -0,0 +1,451 @@ +/* $Id: SUPDrvSem.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Common OS agnostic. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#define SUPDRV_AGNOSTIC +#include "SUPDrvInternal.h" + +/** @todo trim this down. */ +#include <iprt/param.h> +#include <iprt/alloc.h> +#include <iprt/cpuset.h> +#include <iprt/handletable.h> +#include <iprt/mp.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/thread.h> +#include <iprt/uuid.h> + +#include <VBox/param.h> +#include <VBox/log.h> +#include <iprt/errcore.h> + + +/** + * Destructor for objects created by SUPSemEventCreate. + * + * @param pvObj The object handle. + * @param pvUser1 The IPRT event handle. + * @param pvUser2 NULL. + */ +static DECLCALLBACK(void) supR0SemEventDestructor(void *pvObj, void *pvUser1, void *pvUser2) +{ + Assert(pvUser2 == NULL); + RT_NOREF2(pvObj, pvUser2); + RTSemEventDestroy((RTSEMEVENT)pvUser1); +} + + +SUPDECL(int) SUPSemEventCreate(PSUPDRVSESSION pSession, PSUPSEMEVENT phEvent) +{ + int rc; + RTSEMEVENT hEventReal; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(phEvent, VERR_INVALID_POINTER); + + /* + * Create the event semaphore object. + */ + rc = RTSemEventCreate(&hEventReal); + if (RT_SUCCESS(rc)) + { + void *pvObj = SUPR0ObjRegister(pSession, SUPDRVOBJTYPE_SEM_EVENT, supR0SemEventDestructor, hEventReal, NULL); + if (pvObj) + { + uint32_t h32; + rc = RTHandleTableAllocWithCtx(pSession->hHandleTable, pvObj, SUPDRV_HANDLE_CTX_EVENT, &h32); + if (RT_SUCCESS(rc)) + { + *phEvent = (SUPSEMEVENT)(uintptr_t)h32; + return VINF_SUCCESS; + } + SUPR0ObjRelease(pvObj, pSession); + } + else + RTSemEventDestroy(hEventReal); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPSemEventCreate); + + +SUPDECL(int) SUPSemEventClose(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent) +{ + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (hEvent == NIL_SUPSEMEVENT) + return VINF_SUCCESS; + h32 = (uint32_t)(uintptr_t)hEvent; + if (h32 != (uintptr_t)hEvent) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + pObj = (PSUPDRVOBJ)RTHandleTableFreeWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT); + if (!pObj) + return VERR_INVALID_HANDLE; + + Assert(pObj->cUsage >= 2); + SUPR0ObjRelease(pObj, pSession); /* The free call above. */ + return SUPR0ObjRelease(pObj, pSession); /* The handle table reference. */ +} +SUPR0_EXPORT_SYMBOL(SUPSemEventClose); + + +SUPDECL(int) SUPSemEventSignal(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent) +{ + int rc; + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + h32 = (uint32_t)(uintptr_t)hEvent; + if (h32 != (uintptr_t)hEvent) + return VERR_INVALID_HANDLE; + pObj = (PSUPDRVOBJ)RTHandleTableLookupWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT); + if (!pObj) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + rc = RTSemEventSignal((RTSEMEVENT)pObj->pvUser1); + + SUPR0ObjRelease(pObj, pSession); + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPSemEventSignal); + + +static int supR0SemEventWaitEx(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint32_t fFlags, uint64_t uTimeout) +{ + int rc; + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + h32 = (uint32_t)(uintptr_t)hEvent; + if (h32 != (uintptr_t)hEvent) + return VERR_INVALID_HANDLE; + pObj = (PSUPDRVOBJ)RTHandleTableLookupWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT); + if (!pObj) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + rc = RTSemEventWaitEx((RTSEMEVENT)pObj->pvUser1, fFlags, uTimeout); + + SUPR0ObjRelease(pObj, pSession); + return rc; +} + + +SUPDECL(int) SUPSemEventWait(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint32_t cMillies) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE; + if (cMillies == RT_INDEFINITE_WAIT) + fFlags |= RTSEMWAIT_FLAGS_INDEFINITE; + return supR0SemEventWaitEx(pSession, hEvent, fFlags, cMillies); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventWait); + + +SUPDECL(int) SUPSemEventWaitNoResume(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint32_t cMillies) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + if (cMillies == RT_INDEFINITE_WAIT) + fFlags |= RTSEMWAIT_FLAGS_INDEFINITE; + return supR0SemEventWaitEx(pSession, hEvent, fFlags, cMillies); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventWaitNoResume); + + +SUPDECL(int) SUPSemEventWaitNsAbsIntr(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint64_t uNsTimeout) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_ABSOLUTE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + return supR0SemEventWaitEx(pSession, hEvent, fFlags, uNsTimeout); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventWaitNsAbsIntr); + + +SUPDECL(int) SUPSemEventWaitNsRelIntr(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint64_t cNsTimeout) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + return supR0SemEventWaitEx(pSession, hEvent, fFlags, cNsTimeout); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventWaitNsRelIntr); + + +SUPDECL(uint32_t) SUPSemEventGetResolution(PSUPDRVSESSION pSession) +{ + RT_NOREF1(pSession); + Assert(SUP_IS_SESSION_VALID(pSession)); + return RTSemEventGetResolution(); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventGetResolution); + + +/** + * Destructor for objects created by SUPSemEventMultiCreate. + * + * @param pvObj The object handle. + * @param pvUser1 The IPRT event handle. + * @param pvUser2 NULL. + */ +static DECLCALLBACK(void) supR0SemEventMultiDestructor(void *pvObj, void *pvUser1, void *pvUser2) +{ + Assert(pvUser2 == NULL); + RT_NOREF2(pvObj, pvUser2); + RTSemEventMultiDestroy((RTSEMEVENTMULTI)pvUser1); +} + + +SUPDECL(int) SUPSemEventMultiCreate(PSUPDRVSESSION pSession, PSUPSEMEVENTMULTI phEventMulti) +{ + int rc; + RTSEMEVENTMULTI hEventMultReal; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(phEventMulti, VERR_INVALID_POINTER); + + /* + * Create the event semaphore object. + */ + rc = RTSemEventMultiCreate(&hEventMultReal); + if (RT_SUCCESS(rc)) + { + void *pvObj = SUPR0ObjRegister(pSession, SUPDRVOBJTYPE_SEM_EVENT_MULTI, supR0SemEventMultiDestructor, hEventMultReal, NULL); + if (pvObj) + { + uint32_t h32; + rc = RTHandleTableAllocWithCtx(pSession->hHandleTable, pvObj, SUPDRV_HANDLE_CTX_EVENT_MULTI, &h32); + if (RT_SUCCESS(rc)) + { + *phEventMulti = (SUPSEMEVENTMULTI)(uintptr_t)h32; + return VINF_SUCCESS; + } + SUPR0ObjRelease(pvObj, pSession); + } + else + RTSemEventMultiDestroy(hEventMultReal); + } + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiCreate); + + +SUPDECL(int) SUPSemEventMultiClose(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + if (hEventMulti == NIL_SUPSEMEVENTMULTI) + return VINF_SUCCESS; + h32 = (uint32_t)(uintptr_t)hEventMulti; + if (h32 != (uintptr_t)hEventMulti) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + pObj = (PSUPDRVOBJ)RTHandleTableFreeWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT_MULTI); + if (!pObj) + return VERR_INVALID_HANDLE; + + Assert(pObj->cUsage >= 2); + SUPR0ObjRelease(pObj, pSession); /* The free call above. */ + return SUPR0ObjRelease(pObj, pSession); /* The handle table reference. */ +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiClose); + + +SUPDECL(int) SUPSemEventMultiSignal(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + int rc; + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + h32 = (uint32_t)(uintptr_t)hEventMulti; + if (h32 != (uintptr_t)hEventMulti) + return VERR_INVALID_HANDLE; + pObj = (PSUPDRVOBJ)RTHandleTableLookupWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT_MULTI); + if (!pObj) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + rc = RTSemEventMultiSignal((RTSEMEVENTMULTI)pObj->pvUser1); + + SUPR0ObjRelease(pObj, pSession); + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiSignal); + + +SUPDECL(int) SUPSemEventMultiReset(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + int rc; + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + h32 = (uint32_t)(uintptr_t)hEventMulti; + if (h32 != (uintptr_t)hEventMulti) + return VERR_INVALID_HANDLE; + pObj = (PSUPDRVOBJ)RTHandleTableLookupWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT_MULTI); + if (!pObj) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + rc = RTSemEventMultiReset((RTSEMEVENTMULTI)pObj->pvUser1); + + SUPR0ObjRelease(pObj, pSession); + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiReset); + + +static int supR0SemEventMultiWaitEx(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint32_t fFlags, uint64_t uTimeout) +{ + int rc; + uint32_t h32; + PSUPDRVOBJ pObj; + + /* + * Input validation. + */ + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + h32 = (uint32_t)(uintptr_t)hEventMulti; + if (h32 != (uintptr_t)hEventMulti) + return VERR_INVALID_HANDLE; + pObj = (PSUPDRVOBJ)RTHandleTableLookupWithCtx(pSession->hHandleTable, h32, SUPDRV_HANDLE_CTX_EVENT_MULTI); + if (!pObj) + return VERR_INVALID_HANDLE; + + /* + * Do the job. + */ + rc = RTSemEventMultiWaitEx((RTSEMEVENTMULTI)pObj->pvUser1, fFlags, uTimeout); + + SUPR0ObjRelease(pObj, pSession); + return rc; +} + + +SUPDECL(int) SUPSemEventMultiWait(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint32_t cMillies) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE; + if (cMillies == RT_INDEFINITE_WAIT) + fFlags |= RTSEMWAIT_FLAGS_INDEFINITE; + return supR0SemEventMultiWaitEx(pSession, hEventMulti, fFlags, cMillies); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiWait); + + + +SUPDECL(int) SUPSemEventMultiWaitNoResume(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint32_t cMillies) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_MILLISECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + if (cMillies == RT_INDEFINITE_WAIT) + fFlags |= RTSEMWAIT_FLAGS_INDEFINITE; + return supR0SemEventMultiWaitEx(pSession, hEventMulti, fFlags, cMillies); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiWaitNoResume); + + +SUPDECL(int) SUPSemEventMultiWaitNsAbsIntr(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint64_t uNsTimeout) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_ABSOLUTE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + return supR0SemEventMultiWaitEx(pSession, hEventMulti, fFlags, uNsTimeout); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiWaitNsAbsIntr); + + +SUPDECL(int) SUPSemEventMultiWaitNsRelIntr(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint64_t cNsTimeout) +{ + uint32_t fFlags = RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_INTERRUPTIBLE; + return supR0SemEventMultiWaitEx(pSession, hEventMulti, fFlags, cNsTimeout); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiWaitNsRelIntr); + + +SUPDECL(uint32_t) SUPSemEventMultiGetResolution(PSUPDRVSESSION pSession) +{ + RT_NOREF1(pSession); + Assert(SUP_IS_SESSION_VALID(pSession)); + return RTSemEventMultiGetResolution(); +} +SUPR0_EXPORT_SYMBOL(SUPSemEventMultiGetResolution); diff --git a/src/VBox/HostDrivers/Support/SUPDrvTracer.cpp b/src/VBox/HostDrivers/Support/SUPDrvTracer.cpp new file mode 100644 index 00000000..7b25b5ce --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvTracer.cpp @@ -0,0 +1,2494 @@ +/* $Id: SUPDrvTracer.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Tracer Interface. + */ + +/* + * Copyright (C) 2012-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#define SUPDRV_AGNOSTIC +#include "SUPDrvInternal.h" + +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/VBoxTpG.h> + +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/param.h> +#include <iprt/uuid.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a user tracer module registration record. */ +typedef struct SUPDRVTRACERUMOD *PSUPDRVTRACERUMOD; + +/** + * Data for a tracepoint provider. + */ +typedef struct SUPDRVTPPROVIDER +{ + /** The entry in the provider list for this image. */ + RTLISTNODE ListEntry; + /** The entry in the per session provider list for this image. */ + RTLISTNODE SessionListEntry; + + /** The core structure. */ + SUPDRVVDTPROVIDERCORE Core; + + /** Pointer to the image this provider resides in. NULL if it's a + * driver. */ + PSUPDRVLDRIMAGE pImage; + /** The session this provider is associated with if registered via + * SUPR0VtgRegisterDrv. NULL if pImage is set. */ + PSUPDRVSESSION pSession; + /** The user tracepoint module associated with this provider. NULL if + * pImage is set. */ + PSUPDRVTRACERUMOD pUmod; + + /** Used to indicate that we've called pfnProviderDeregistered already and it + * failed because the provider was busy. Next time we must try + * pfnProviderDeregisterZombie. + * + * @remarks This does not necessiarly mean the provider is in the zombie + * list. See supdrvTracerCommonDeregisterImpl. */ + bool fZombie; + /** Set if the provider has been successfully registered with the + * tracer. */ + bool fRegistered; + /** The provider name (for logging purposes). */ + char szName[1]; +} SUPDRVTPPROVIDER; +/** Pointer to the data for a tracepoint provider. */ +typedef SUPDRVTPPROVIDER *PSUPDRVTPPROVIDER; + + +/** + * User tracer module VTG data copy. + */ +typedef struct SUPDRVVTGCOPY +{ + /** Magic (SUDPRVVTGCOPY_MAGIC). */ + uint32_t u32Magic; + /** Refernece counter (we expect to share a lot of these). */ + uint32_t cRefs; + /** The size of the */ + uint32_t cbStrTab; + /** Image type flags. */ + uint32_t fFlags; + /** Hash list entry (SUPDRVDEVEXT::aTrackerUmodHash). */ + RTLISTNODE ListEntry; + /** The VTG object header. + * The rest of the data follows immediately afterwards. First the object, + * then the probe locations and finally the probe location string table. All + * pointers are fixed up to point within this data. */ + VTGOBJHDR Hdr; +} SUPDRVVTGCOPY; +/** Pointer to a VTG object copy. */ +typedef SUPDRVVTGCOPY *PSUPDRVVTGCOPY; +/** Magic value for SUPDRVVTGCOPY. */ +#define SUDPRVVTGCOPY_MAGIC UINT32_C(0x00080386) + + +/** + * User tracer module registration record. + */ +typedef struct SUPDRVTRACERUMOD +{ + /** Magic (SUPDRVTRACERUMOD_MAGIC). */ + uint32_t u32Magic; + /** List entry. This is anchored in SUPDRVSESSION::UmodList. */ + RTLISTNODE ListEntry; + /** The address of the ring-3 VTG header. */ + RTR3PTR R3PtrVtgHdr; + /** Pointer to the ring-0 copy of the VTG data. */ + PSUPDRVVTGCOPY pVtgCopy; + /** The memory object that locks down the user memory. */ + RTR0MEMOBJ hMemObjLock; + /** The memory object that maps the locked memory into kernel space. */ + RTR0MEMOBJ hMemObjMap; + /** Pointer to the probe enabled-count array within the mapping. */ + uint32_t *pacProbeEnabled; + /** Pointer to the probe location array within the mapping. */ + void *pvProbeLocs; + /** The address of the ring-3 probe locations. */ + RTR3PTR R3PtrProbeLocs; + /** The lookup table index. */ + uint8_t iLookupTable; + /** The module bit count. */ + uint8_t cBits; + /** The size of a probe location record. */ + uint8_t cbProbeLoc; + /** The number of probe locations. */ + uint32_t cProbeLocs; + /** Ring-0 probe location info. */ + SUPDRVPROBELOC aProbeLocs[1]; +} SUPDRVTRACERUMOD; +/** Magic value for SUPDRVVTGCOPY. */ +#define SUPDRVTRACERUMOD_MAGIC UINT32_C(0x00080486) + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Simple SUPR0Printf-style logging. */ +#ifdef DEBUG_bird +# define LOG_TRACER(a_Args) SUPR0Printf a_Args +#else +# define LOG_TRACER(a_Args) do { } while (0) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The address of the current probe fire routine for kernel mode. */ +PFNRT g_pfnSupdrvProbeFireKernel = supdrvTracerProbeFireStub; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void supdrvVtgReleaseObjectCopy(PSUPDRVDEVEXT pDevExt, PSUPDRVVTGCOPY pThis); + + + +/** + * Validates a VTG string against length and characterset limitations. + * + * @returns VINF_SUCCESS, VERR_SUPDRV_VTG_BAD_STRING or + * VERR_SUPDRV_VTG_STRING_TOO_LONG. + * @param psz The string. + */ +static int supdrvVtgValidateString(const char *psz) +{ + size_t off = 0; + while (off < _4K) + { + char const ch = psz[off++]; + if (!ch) + return VINF_SUCCESS; + if ( !RTLocCIsAlNum(ch) + && ch != ' ' + && ch != '_' + && ch != '-' + && ch != '(' + && ch != ')' + && ch != ',' + && ch != '*' + && ch != '&' + ) + { + /*RTAssertMsg2("off=%u '%s'\n", off, psz);*/ + return VERR_SUPDRV_VTG_BAD_STRING; + } + } + return VERR_SUPDRV_VTG_STRING_TOO_LONG; +} + + +/** Used by the validation code below. */ +#define MY_CHECK_RET(a_Expr, a_rc) \ + MY_CHECK_MSG_RET(a_Expr, ("%s: Validation failed on line " RT_XSTR(__LINE__) ": " #a_Expr "\n", __FUNCTION__), a_rc) + +/** Used by the validation code below. */ +#define MY_CHECK_MSG_RET(a_Expr, a_PrintfArgs, a_rc) \ + do { if (RT_UNLIKELY(!(a_Expr))) { SUPR0Printf a_PrintfArgs; return (a_rc); } } while (0) + +/** Used by the validation code below. */ +#define MY_WITHIN_IMAGE(p, rc) \ + do { \ + if (pbImage) \ + { \ + if ((uintptr_t)(p) - (uintptr_t)pbImage > cbImage) \ + { \ + SUPR0Printf("supdrvVtgValidate: " #rc " - p=%p pbImage=%p cbImage=%#zxline=%u %s\n", \ + p, pbImage, cbImage, #p); \ + return (rc); \ + } \ + } \ + else if (!RT_VALID_PTR(p)) \ + return (rc); \ + } while (0) + + +/** + * Validates the VTG object header. + * + * @returns VBox status code. + * @param pVtgHdr The header. + * @param uVtgHdrAddr The address where the header is actually + * loaded. + * @param pbImage The image base, if available. + * @param cbImage The image size, if available. + * @param fUmod Whether this is a user module. + */ +static int supdrvVtgValidateHdr(PVTGOBJHDR pVtgHdr, RTUINTPTR uVtgHdrAddr, const uint8_t *pbImage, size_t cbImage, bool fUmod) +{ + struct VTGAREAS + { + uint32_t off; + uint32_t cb; + } const *paAreas; + unsigned cAreas; + unsigned i; + uint32_t cbVtgObj; + uint32_t off; + +#define MY_VALIDATE_SIZE(cb, cMin, cMax, cbUnit, rcBase) \ + do { \ + if ((cb) < (cMin) * (cbUnit)) \ + { \ + SUPR0Printf("supdrvVtgValidateHdr: " #rcBase "_TOO_FEW - cb=%#zx cMin=%#zx cbUnit=%#zx line=%u %s\n", \ + (size_t)(cb), (size_t)(cMin), (size_t)cbUnit, __LINE__, #cb); \ + return rcBase ## _TOO_FEW; \ + } \ + if ((cb) >= (cMax) * (cbUnit)) \ + { \ + SUPR0Printf("supdrvVtgValidateHdr: " #rcBase "_TOO_MUCH - cb=%#zx cMax=%#zx cbUnit=%#zx line=%u %s\n", \ + (size_t)(cb), (size_t)(cMax), (size_t)cbUnit, __LINE__, #cb); \ + return rcBase ## _TOO_MUCH; \ + } \ + if ((cb) / (cbUnit) * (cbUnit) != (cb)) \ + { \ + SUPR0Printf("supdrvVtgValidateHdr: " #rcBase "_NOT_MULTIPLE - cb=%#zx cbUnit=%#zx line=%u %s\n", \ + (size_t)(cb), (size_t)cbUnit, __LINE__, #cb); \ + return rcBase ## _NOT_MULTIPLE; \ + } \ + } while (0) + +#define MY_VALIDATE_OFF(off, cb, cMin, cMax, cbUnit, cbAlign, rcBase) \ + do { \ + if ( (cb) >= cbVtgObj \ + || off > cbVtgObj - (cb) ) \ + { \ + SUPR0Printf("supdrvVtgValidateHdr: " #rcBase "_OFF - off=%#x cb=%#x pVtgHdr=%p cbVtgHdr=%#zx line=%u %s\n", \ + (off), (cb), pVtgHdr, cbVtgObj, __LINE__, #off); \ + return rcBase ## _OFF; \ + } \ + if (RT_ALIGN(off, cbAlign) != (off)) \ + { \ + SUPR0Printf("supdrvVtgValidateHdr: " #rcBase "_OFF - off=%#x align=%#zx line=%u %s\n", \ + (off), (size_t)(cbAlign), __LINE__, #off); \ + return rcBase ## _OFF; \ + } \ + MY_VALIDATE_SIZE(cb, cMin, cMax, cbUnit, rcBase); \ + } while (0) + + /* + * Make sure both pbImage and cbImage are NULL/0 if one if of them is. + */ + if (!pbImage || !cbImage) + { + pbImage = NULL; + cbImage = 0; + cbVtgObj = pVtgHdr->cbObj; + } + else + { + MY_WITHIN_IMAGE(pVtgHdr, VERR_SUPDRV_VTG_BAD_HDR_PTR); + cbVtgObj = pVtgHdr->cbObj; + MY_WITHIN_IMAGE((uint8_t *)pVtgHdr + cbVtgObj - 1, VERR_SUPDRV_VTG_BAD_HDR_PTR); + } + + if (cbVtgObj > _1M) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_TRACER_TOO_LARGE - cbVtgObj=%#x\n", cbVtgObj); + return VERR_SUPDRV_TRACER_TOO_LARGE; + } + + /* + * Set the probe location array offset and size members. + */ + if (!pVtgHdr->offProbeLocs) + { + uint64_t u64Tmp = pVtgHdr->uProbeLocsEnd.u64 - pVtgHdr->uProbeLocs.u64; + if (u64Tmp >= UINT32_MAX) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_TOO_MUCH - u64Tmp=%#llx ProbeLocs=%#llx ProbeLocsEnd=%#llx\n", + u64Tmp, pVtgHdr->uProbeLocs.u64, pVtgHdr->uProbeLocsEnd.u64); + return VERR_SUPDRV_VTG_BAD_HDR_TOO_MUCH; + } + /*SUPR0Printf("supdrvVtgValidateHdr: cbProbeLocs %#x -> %#x\n", pVtgHdr->cbProbeLocs, (uint32_t)u64Tmp);*/ + pVtgHdr->cbProbeLocs = (uint32_t)u64Tmp; + + u64Tmp = pVtgHdr->uProbeLocs.u64 - uVtgHdrAddr; +#ifdef RT_OS_DARWIN + /* The loader and/or ld64-97.17 seems not to generate fixups for our + __VTGObj section. Detect this by comparing them with the + u64VtgObjSectionStart member and assume max image size of 4MB. + Seems to be worked around by the __VTGPrLc.End and __VTGPrLc.Begin + padding fudge, meaning that the linker misplaced the relocations. */ + if ( (int64_t)u64Tmp != (int32_t)u64Tmp + && pVtgHdr->u64VtgObjSectionStart != uVtgHdrAddr + && pVtgHdr->u64VtgObjSectionStart < _4M + && pVtgHdr->uProbeLocsEnd.u64 < _4M + && !fUmod) + { + uint64_t offDelta = uVtgHdrAddr - pVtgHdr->u64VtgObjSectionStart; + /*SUPR0Printf("supdrvVtgValidateHdr: offDelta=%#llx\n", offDelta);*/ + pVtgHdr->uProbeLocs.u64 += offDelta; + pVtgHdr->uProbeLocsEnd.u64 += offDelta; + u64Tmp += offDelta; + } +#endif + if ((int64_t)u64Tmp != (int32_t)u64Tmp) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_PTR - u64Tmp=%#llx uProbeLocs=%#llx uVtgHdrAddr=%RTptr\n", + u64Tmp, pVtgHdr->uProbeLocs.u64, uVtgHdrAddr); + return VERR_SUPDRV_VTG_BAD_HDR_PTR; + } + /*SUPR0Printf("supdrvVtgValidateHdr: offProbeLocs %#x -> %#x\n", pVtgHdr->offProbeLocs, (int32_t)u64Tmp);*/ + pVtgHdr->offProbeLocs = (int32_t)u64Tmp; + } + + /* + * The non-area description fields. + */ + if (memcmp(pVtgHdr->szMagic, VTGOBJHDR_MAGIC, sizeof(pVtgHdr->szMagic))) + { + SUPR0Printf("supdrvVtgValidateHdr: %p: %.16Rhxs\n", pVtgHdr, pVtgHdr->szMagic); + return VERR_SUPDRV_VTG_MAGIC; + } + if ( pVtgHdr->cBits != ARCH_BITS + && ( !fUmod + || ( pVtgHdr->cBits != 32 + && pVtgHdr->cBits != 64)) ) + return VERR_SUPDRV_VTG_BITS; + MY_CHECK_RET(pVtgHdr->au32Reserved1[0] == 0, VERR_SUPDRV_VTG_BAD_HDR_MISC); + MY_CHECK_RET(pVtgHdr->au32Reserved1[1] == 0, VERR_SUPDRV_VTG_BAD_HDR_MISC); + MY_CHECK_RET(!RTUuidIsNull(&pVtgHdr->Uuid), VERR_SUPDRV_VTG_BAD_HDR_MISC); + + /* + * Check the individual area descriptors. + */ + MY_VALIDATE_OFF(pVtgHdr->offStrTab, pVtgHdr->cbStrTab, 4, _1M, sizeof(char), sizeof(uint8_t), VERR_SUPDRV_VTG_BAD_HDR); + MY_VALIDATE_OFF(pVtgHdr->offArgLists, pVtgHdr->cbArgLists, 1, _32K, sizeof(uint32_t), sizeof(uint32_t), VERR_SUPDRV_VTG_BAD_HDR); + MY_VALIDATE_OFF(pVtgHdr->offProbes, pVtgHdr->cbProbes, 1, _32K, sizeof(VTGDESCPROBE), sizeof(uint32_t), VERR_SUPDRV_VTG_BAD_HDR); + MY_VALIDATE_OFF(pVtgHdr->offProviders, pVtgHdr->cbProviders, 1, 16, sizeof(VTGDESCPROVIDER), sizeof(uint32_t), VERR_SUPDRV_VTG_BAD_HDR); + MY_VALIDATE_OFF(pVtgHdr->offProbeEnabled, pVtgHdr->cbProbeEnabled, 1, _32K, sizeof(uint32_t), sizeof(uint32_t), VERR_SUPDRV_VTG_BAD_HDR); + if (!fUmod) + { + MY_WITHIN_IMAGE(pVtgHdr->uProbeLocs.p, VERR_SUPDRV_VTG_BAD_HDR_PTR); + MY_WITHIN_IMAGE(pVtgHdr->uProbeLocsEnd.p, VERR_SUPDRV_VTG_BAD_HDR_PTR); + MY_VALIDATE_SIZE( pVtgHdr->cbProbeLocs, 1, _128K, sizeof(VTGPROBELOC), VERR_SUPDRV_VTG_BAD_HDR); + } + else + { + if (pVtgHdr->cBits == 32) + MY_VALIDATE_SIZE( pVtgHdr->cbProbeLocs, 1, _8K, sizeof(VTGPROBELOC32), VERR_SUPDRV_VTG_BAD_HDR); + else + MY_VALIDATE_SIZE( pVtgHdr->cbProbeLocs, 1, _8K, sizeof(VTGPROBELOC64), VERR_SUPDRV_VTG_BAD_HDR); + /* Will check later that offProbeLocs are following closely on the + enable count array, so no need to validate the offset here. */ + } + + /* + * Some additional consistency checks. + */ + if ( pVtgHdr->uProbeLocsEnd.u64 - pVtgHdr->uProbeLocs.u64 != pVtgHdr->cbProbeLocs + || (int64_t)(pVtgHdr->uProbeLocs.u64 - uVtgHdrAddr) != pVtgHdr->offProbeLocs) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_MISC - uProbeLocs=%#llx uProbeLocsEnd=%#llx offProbeLocs=%#llx cbProbeLocs=%#x uVtgHdrAddr=%RTptr\n", + pVtgHdr->uProbeLocs.u64, pVtgHdr->uProbeLocsEnd.u64, pVtgHdr->offProbeLocs, pVtgHdr->cbProbeLocs, uVtgHdrAddr); + return VERR_SUPDRV_VTG_BAD_HDR_MISC; + } + + if (pVtgHdr->cbProbes / sizeof(VTGDESCPROBE) != pVtgHdr->cbProbeEnabled / sizeof(uint32_t)) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_MISC - cbProbeEnabled=%#zx cbProbes=%#zx\n", + pVtgHdr->cbProbeEnabled, pVtgHdr->cbProbes); + return VERR_SUPDRV_VTG_BAD_HDR_MISC; + } + + /* + * Check that there are no overlapping areas. This is a little bit ugly... + */ + paAreas = (struct VTGAREAS const *)&pVtgHdr->offStrTab; + cAreas = pVtgHdr->offProbeLocs >= 0 ? 6 : 5; + off = sizeof(VTGOBJHDR); + for (i = 0; i < cAreas; i++) + { + if (paAreas[i].off < off) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_MISC - overlapping areas %d and %d\n", i, i-1); + return VERR_SUPDRV_VTG_BAD_HDR_MISC; + } + off = paAreas[i].off + paAreas[i].cb; + } + if ( pVtgHdr->offProbeLocs > 0 + && (uint32_t)-pVtgHdr->offProbeLocs < pVtgHdr->cbProbeLocs) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_MISC - probe locations overlaps the header\n"); + return VERR_SUPDRV_VTG_BAD_HDR_MISC; + } + + /* + * Check that the object size is correct. + */ + if (pVtgHdr->cbObj != pVtgHdr->offProbeEnabled + pVtgHdr->cbProbeEnabled) + { + SUPR0Printf("supdrvVtgValidateHdr: VERR_SUPDRV_VTG_BAD_HDR_MISC - bad header size %#x, expected %#x\n", + pVtgHdr->cbObj, pVtgHdr->offProbeEnabled + pVtgHdr->cbProbeEnabled); + return VERR_SUPDRV_VTG_BAD_HDR_MISC; + } + + + return VINF_SUCCESS; +#undef MY_VALIDATE_OFF +#undef MY_VALIDATE_SIZE +} + + +/** + * Validates the VTG data. + * + * @returns VBox status code. + * @param pVtgHdr The VTG object header of the data to validate. + * @param uVtgHdrAddr The address where the header is actually + * loaded. + * @param pbImage The image base. For validating the probe + * locations. + * @param cbImage The image size to go with @a pbImage. + * @param fUmod Whether this is a user module. + */ +static int supdrvVtgValidate(PVTGOBJHDR pVtgHdr, RTUINTPTR uVtgHdrAddr, const uint8_t *pbImage, size_t cbImage, bool fUmod) +{ + uintptr_t offTmp; + uintptr_t i; + uintptr_t cProviders; + int rc; + + if (!pbImage || !cbImage) + { + pbImage = NULL; + cbImage = 0; + } + +#define MY_VALIDATE_STR(a_offStrTab) \ + do { \ + if ((a_offStrTab) >= pVtgHdr->cbStrTab) \ + return VERR_SUPDRV_VTG_STRTAB_OFF; \ + rc = supdrvVtgValidateString((char *)pVtgHdr + pVtgHdr->offStrTab + (a_offStrTab)); \ + if (rc != VINF_SUCCESS) \ + return rc; \ + } while (0) +#define MY_VALIDATE_ATTR(Attr) \ + do { \ + if ((Attr).u8Code <= (uint8_t)kVTGStability_Invalid || (Attr).u8Code >= (uint8_t)kVTGStability_End) \ + return VERR_SUPDRV_VTG_BAD_ATTR; \ + if ((Attr).u8Data <= (uint8_t)kVTGStability_Invalid || (Attr).u8Data >= (uint8_t)kVTGStability_End) \ + return VERR_SUPDRV_VTG_BAD_ATTR; \ + if ((Attr).u8DataDep <= (uint8_t)kVTGClass_Invalid || (Attr).u8DataDep >= (uint8_t)kVTGClass_End) \ + return VERR_SUPDRV_VTG_BAD_ATTR; \ + } while (0) + + /* + * The header. + */ + rc = supdrvVtgValidateHdr(pVtgHdr, uVtgHdrAddr, pbImage, cbImage, fUmod); + if (RT_FAILURE(rc)) + return rc; + + /* + * Validate the providers. + */ + cProviders = i = pVtgHdr->cbProviders / sizeof(VTGDESCPROVIDER); + while (i-- > 0) + { + PCVTGDESCPROVIDER pProvider = (PCVTGDESCPROVIDER)((uintptr_t)pVtgHdr + pVtgHdr->offProviders) + i; + + MY_VALIDATE_STR(pProvider->offName); + MY_CHECK_RET(pProvider->iFirstProbe < pVtgHdr->cbProbeEnabled / sizeof(uint32_t), VERR_SUPDRV_VTG_BAD_PROVIDER); + MY_CHECK_RET((uint32_t)pProvider->iFirstProbe + pProvider->cProbes <= pVtgHdr->cbProbeEnabled / sizeof(uint32_t), + VERR_SUPDRV_VTG_BAD_PROVIDER); + MY_VALIDATE_ATTR(pProvider->AttrSelf); + MY_VALIDATE_ATTR(pProvider->AttrModules); + MY_VALIDATE_ATTR(pProvider->AttrFunctions); + MY_VALIDATE_ATTR(pProvider->AttrNames); + MY_VALIDATE_ATTR(pProvider->AttrArguments); + MY_CHECK_RET(pProvider->bReserved == 0, VERR_SUPDRV_VTG_BAD_PROVIDER); + MY_CHECK_RET(pProvider->cProbesEnabled == 0, VERR_SUPDRV_VTG_BAD_PROVIDER); + MY_CHECK_RET(pProvider->uSettingsSerialNo == 0, VERR_SUPDRV_VTG_BAD_PROVIDER); + } + + /* + * Validate probes. + */ + i = pVtgHdr->cbProbes / sizeof(VTGDESCPROBE); + while (i-- > 0) + { + PCVTGDESCPROBE pProbe = (PCVTGDESCPROBE)( (uintptr_t)pVtgHdr + pVtgHdr->offProbes) + i; + PCVTGDESCPROVIDER pProvider = (PCVTGDESCPROVIDER)((uintptr_t)pVtgHdr + pVtgHdr->offProviders) + pProbe->idxProvider; + PCVTGDESCARGLIST pArgList = (PCVTGDESCARGLIST)( (uintptr_t)pVtgHdr + pVtgHdr->offArgLists + pProbe->offArgList ); + unsigned iArg; + bool fHaveLargeArgs; + + + MY_VALIDATE_STR(pProbe->offName); + MY_CHECK_RET(pProbe->offArgList < pVtgHdr->cbArgLists, VERR_SUPDRV_VTG_BAD_PROBE); + MY_CHECK_RET((pProbe->offArgList & 3) == 0, VERR_SUPDRV_VTG_BAD_PROBE); + MY_CHECK_RET(pProbe->idxEnabled == i, VERR_SUPDRV_VTG_BAD_PROBE); /* The lists are parallel. */ + MY_CHECK_RET(pProbe->idxProvider < cProviders, VERR_SUPDRV_VTG_BAD_PROBE); + MY_CHECK_RET(i - pProvider->iFirstProbe < pProvider->cProbes, VERR_SUPDRV_VTG_BAD_PROBE); + if (pProbe->offObjHdr != (intptr_t)pVtgHdr - (intptr_t)pProbe) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_VTG_BAD_PROBE - iProbe=%u offObjHdr=%d expected %zd\n", + i, pProbe->offObjHdr, (intptr_t)pVtgHdr - (intptr_t)pProbe); + return VERR_SUPDRV_VTG_BAD_PROBE; + } + + /* The referenced argument list. */ + if (pArgList->cArgs > 16) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_VTG_BAD_ARGLIST - iProbe=%u cArgs=%u\n", i, pArgList->cArgs); + return VERR_SUPDRV_VTG_BAD_ARGLIST; + } + if (pArgList->fHaveLargeArgs >= 2) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_VTG_BAD_ARGLIST - iProbe=%u fHaveLargeArgs=%d\n", i, pArgList->fHaveLargeArgs); + return VERR_SUPDRV_VTG_BAD_ARGLIST; + } + if ( pArgList->abReserved[0] + || pArgList->abReserved[1]) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_VTG_BAD_ARGLIST - reserved MBZ iProbe=%u\n", i); + return VERR_SUPDRV_VTG_BAD_ARGLIST; + } + fHaveLargeArgs = false; + iArg = pArgList->cArgs; + while (iArg-- > 0) + { + uint32_t const fType = pArgList->aArgs[iArg].fType; + if (fType & ~VTG_TYPE_VALID_MASK) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_TRACER_BAD_ARG_FLAGS - fType=%#x iArg=%u iProbe=%u (#0)\n", fType, iArg, i); + return VERR_SUPDRV_TRACER_BAD_ARG_FLAGS; + } + + switch (pArgList->aArgs[iArg].fType & VTG_TYPE_SIZE_MASK) + { + case 0: + if (pArgList->aArgs[iArg].fType & VTG_TYPE_FIXED_SIZED) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_TRACER_BAD_ARG_FLAGS - fType=%#x iArg=%u iProbe=%u (#1)\n", fType, iArg, i); + return VERR_SUPDRV_TRACER_BAD_ARG_FLAGS; + } + break; + case 1: case 2: case 4: case 8: + break; + default: + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_TRACER_BAD_ARG_FLAGS - fType=%#x iArg=%u iProbe=%u (#2)\n", fType, iArg, i); + return VERR_SUPDRV_TRACER_BAD_ARG_FLAGS; + } + if (VTG_TYPE_IS_LARGE(pArgList->aArgs[iArg].fType)) + fHaveLargeArgs = true; + + MY_VALIDATE_STR(pArgList->aArgs[iArg].offType); + } + if ((uint8_t)fHaveLargeArgs != pArgList->fHaveLargeArgs) + { + SUPR0Printf("supdrvVtgValidate: VERR_SUPDRV_TRACER_BAD_ARG_FLAGS - iProbe=%u fHaveLargeArgs=%d expected %d\n", + i, pArgList->fHaveLargeArgs, fHaveLargeArgs); + return VERR_SUPDRV_VTG_BAD_PROBE; + } + } + + /* + * Check that pacProbeEnabled is all zeros. + */ + { + uint32_t const *pcProbeEnabled = (uint32_t const *)((uintptr_t)pVtgHdr + pVtgHdr->offProbeEnabled); + i = pVtgHdr->cbProbeEnabled / sizeof(uint32_t); + while (i-- > 0) + MY_CHECK_RET(pcProbeEnabled[0] == 0, VERR_SUPDRV_VTG_BAD_PROBE_ENABLED); + } + + /* + * Probe locations. + */ + { + PVTGPROBELOC paProbeLocs = (PVTGPROBELOC)((intptr_t)pVtgHdr + pVtgHdr->offProbeLocs); + i = pVtgHdr->cbProbeLocs / sizeof(VTGPROBELOC); + while (i-- > 0) + { + MY_CHECK_RET(paProbeLocs[i].uLine < _1G, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + MY_CHECK_RET(paProbeLocs[i].fEnabled == false, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + MY_CHECK_RET(paProbeLocs[i].idProbe == 0, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + offTmp = (uintptr_t)paProbeLocs[i].pProbe - (uintptr_t)pVtgHdr->offProbes - (uintptr_t)pVtgHdr; +#ifdef RT_OS_DARWIN /* See header validation code. */ + if ( offTmp >= pVtgHdr->cbProbes + && pVtgHdr->u64VtgObjSectionStart != uVtgHdrAddr + && pVtgHdr->u64VtgObjSectionStart < _4M + && (uintptr_t)paProbeLocs[i].pProbe < _4M + && !fUmod ) + { + uint64_t offDelta = uVtgHdrAddr - pVtgHdr->u64VtgObjSectionStart; + + paProbeLocs[i].pProbe = (PVTGDESCPROBE)((uintptr_t)paProbeLocs[i].pProbe + offDelta); + if ((uintptr_t)paProbeLocs[i].pszFunction < _4M) + paProbeLocs[i].pszFunction = (const char *)((uintptr_t)paProbeLocs[i].pszFunction + offDelta); + + offTmp += offDelta; + } +#endif + MY_CHECK_RET(offTmp < pVtgHdr->cbProbes, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + MY_CHECK_RET(offTmp / sizeof(VTGDESCPROBE) * sizeof(VTGDESCPROBE) == offTmp, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + MY_WITHIN_IMAGE(paProbeLocs[i].pszFunction, VERR_SUPDRV_VTG_BAD_PROBE_LOC); + } + } + + return VINF_SUCCESS; +} + +#undef MY_VALIDATE_STR +#undef MY_VALIDATE_ATTR +#undef MY_WITHIN_IMAGE + + +/** + * Gets a string from the string table. + * + * @returns Pointer to the string. + * @param pVtgHdr The VTG object header. + * @param offStrTab The string table offset. + */ +static const char *supdrvVtgGetString(PVTGOBJHDR pVtgHdr, uint32_t offStrTab) +{ + Assert(offStrTab < pVtgHdr->cbStrTab); + return (char *)pVtgHdr + pVtgHdr->offStrTab + offStrTab; +} + + +/** + * Frees the provider structure and associated resources. + * + * @param pProv The provider to free. + */ +static void supdrvTracerFreeProvider(PSUPDRVTPPROVIDER pProv) +{ + LOG_TRACER(("Freeing tracepoint provider '%s' / %p\n", pProv->szName, pProv->Core.TracerData.DTrace.idProvider)); + pProv->fRegistered = false; + pProv->fZombie = true; + pProv->Core.pDesc = NULL; + pProv->Core.pHdr = NULL; + pProv->Core.paProbeLocsRO = NULL; + pProv->Core.pvProbeLocsEn = NULL; + pProv->Core.pacProbeEnabled = NULL; + pProv->Core.paR0ProbeLocs = NULL; + pProv->Core.paR0Probes = NULL; + RT_ZERO(pProv->Core.TracerData); + RTMemFree(pProv); +} + + +/** + * Unlinks and deregisters a provider. + * + * If the provider is still busy, it will be put in the zombie list. + * + * @param pDevExt The device extension. + * @param pProv The provider. + * + * @remarks The caller owns mtxTracer. + */ +static void supdrvTracerDeregisterVtgObj(PSUPDRVDEVEXT pDevExt, PSUPDRVTPPROVIDER pProv) +{ + int rc; + + RTListNodeRemove(&pProv->ListEntry); + if (pProv->pSession) + { + RTListNodeRemove(&pProv->SessionListEntry); + RTListInit(&pProv->SessionListEntry); + pProv->pSession->cTpProviders--; + } + + if (!pProv->fRegistered || !pDevExt->pTracerOps) + rc = VINF_SUCCESS; + else + rc = pDevExt->pTracerOps->pfnProviderDeregister(pDevExt->pTracerOps, &pProv->Core); + if (RT_SUCCESS(rc)) + { + supdrvTracerFreeProvider(pProv); + return; + } + + pProv->fZombie = true; + pProv->pImage = NULL; + pProv->pSession = NULL; + pProv->pUmod = NULL; + pProv->Core.pDesc = NULL; + pProv->Core.pHdr = NULL; + pProv->Core.paProbeLocsRO = NULL; + pProv->Core.pvProbeLocsEn = NULL; + pProv->Core.pacProbeEnabled = NULL; + pProv->Core.paR0ProbeLocs = NULL; + + RTListAppend(&pDevExt->TracerProviderZombieList, &pProv->ListEntry); + LOG_TRACER(("Invalidated provider '%s' / %p and put it on the zombie list (rc=%Rrc)\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc)); +} + + +/** + * Processes the zombie list. + * + * @param pDevExt The device extension. + */ +static void supdrvTracerProcessZombies(PSUPDRVDEVEXT pDevExt) +{ + PSUPDRVTPPROVIDER pProv, pProvNext; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListForEachSafe(&pDevExt->TracerProviderZombieList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + int rc = pDevExt->pTracerOps->pfnProviderDeregisterZombie(pDevExt->pTracerOps, &pProv->Core); + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pProv->ListEntry); + supdrvTracerFreeProvider(pProv); + } + } + RTSemFastMutexRelease(pDevExt->mtxTracer); +} + + +/** + * Unregisters all providers, including zombies, waiting for busy providers to + * go idle and unregister smoothly. + * + * This may block. + * + * @param pDevExt The device extension. + */ +static void supdrvTracerRemoveAllProviders(PSUPDRVDEVEXT pDevExt) +{ + uint32_t i; + PSUPDRVTPPROVIDER pProv; + PSUPDRVTPPROVIDER pProvNext; + + /* + * Unregister all probes (there should only be one). + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListForEachSafe(&pDevExt->TracerProviderList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + + /* + * Try unregister zombies now, sleep on busy ones and tracer opens. + */ + for (i = 0; ; i++) + { + bool fEmpty; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + + /* Zombies */ + RTListForEachSafe(&pDevExt->TracerProviderZombieList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + int rc; + LOG_TRACER(("supdrvTracerRemoveAllProviders: Attemting to unregister '%s' / %p...\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider)); + + if (pDevExt->pTracerOps) + rc = pDevExt->pTracerOps->pfnProviderDeregisterZombie(pDevExt->pTracerOps, &pProv->Core); + else + rc = VINF_SUCCESS; + if (!rc) + { + RTListNodeRemove(&pProv->ListEntry); + supdrvTracerFreeProvider(pProv); + } + else if (!(i & 0xf)) + SUPR0Printf("supdrvTracerRemoveAllProviders: Waiting on busy provider '%s' / %p (rc=%d)\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc); + else + LOG_TRACER(("supdrvTracerRemoveAllProviders: Failed to unregister provider '%s' / %p - rc=%d\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc)); + } + + fEmpty = RTListIsEmpty(&pDevExt->TracerProviderZombieList); + + /* Tracer opens. */ + if ( pDevExt->cTracerOpens + && pDevExt->pTracerOps) + { + fEmpty = false; + if (!(i & 0xf)) + SUPR0Printf("supdrvTracerRemoveAllProviders: Waiting on %u opens\n", pDevExt->cTracerOpens); + else + LOG_TRACER(("supdrvTracerRemoveAllProviders: Waiting on %u opens\n", pDevExt->cTracerOpens)); + } + + RTSemFastMutexRelease(pDevExt->mtxTracer); + + if (fEmpty) + break; + + /* Delay...*/ + RTThreadSleep(1000); + } +} + + +/** + * Registers the VTG tracepoint providers of a driver. + * + * @returns VBox status code. + * @param pDevExt The device instance data. + * @param pVtgHdr The VTG object header. + * @param pImage The image if applicable. + * @param pSession The session if applicable. + * @param pUmod The associated user tracepoint module if + * applicable. + * @param pszModName The module name. + */ +static int supdrvTracerRegisterVtgObj(PSUPDRVDEVEXT pDevExt, PVTGOBJHDR pVtgHdr, PSUPDRVLDRIMAGE pImage, + PSUPDRVSESSION pSession, PSUPDRVTRACERUMOD pUmod, const char *pszModName) +{ + int rc; + uintptr_t i; + PSUPDRVTPPROVIDER pProv; + size_t cchModName; + + /* + * Validate input. + */ + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + AssertPtrReturn(pVtgHdr, VERR_INVALID_POINTER); + AssertPtrNullReturn(pImage, VERR_INVALID_POINTER); + AssertPtrNullReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pszModName, VERR_INVALID_POINTER); + cchModName = strlen(pszModName); + + if (pImage) + rc = supdrvVtgValidate(pVtgHdr, (uintptr_t)pVtgHdr, + (const uint8_t *)pImage->pvImage, pImage->cbImageBits, + false /*fUmod*/); + else + rc = supdrvVtgValidate(pVtgHdr, (uintptr_t)pVtgHdr, NULL, 0, pUmod != NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check that there aren't any obvious duplicates. + * (Yes, this isn't race free, but it's good enough for now.) + */ + rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_FAILURE(rc)) + return rc; + if (pImage || !pSession || pSession->R0Process == NIL_RTPROCESS) + { + RTListForEach(&pDevExt->TracerProviderList, pProv, SUPDRVTPPROVIDER, ListEntry) + { + if (pProv->Core.pHdr == pVtgHdr) + { + rc = VERR_SUPDRV_VTG_ALREADY_REGISTERED; + break; + } + + if ( pProv->pSession == pSession + && pProv->pImage == pImage) + { + rc = VERR_SUPDRV_VTG_ONLY_ONCE_PER_SESSION; + break; + } + } + } + else + { + RTListForEach(&pSession->TpProviders, pProv, SUPDRVTPPROVIDER, SessionListEntry) + { + if (pProv->Core.pHdr == pVtgHdr) + { + rc = VERR_SUPDRV_VTG_ALREADY_REGISTERED; + break; + } + } + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register the providers. + */ + i = pVtgHdr->cbProviders / sizeof(VTGDESCPROVIDER); + while (i-- > 0) + { + PVTGDESCPROVIDER pDesc = (PVTGDESCPROVIDER)((uintptr_t)pVtgHdr + pVtgHdr->offProviders) + i; + const char *pszName = supdrvVtgGetString(pVtgHdr, pDesc->offName); + size_t const cchName = strlen(pszName) + (pUmod ? 16 : 0); + + pProv = (PSUPDRVTPPROVIDER)RTMemAllocZ(RT_UOFFSETOF_DYN(SUPDRVTPPROVIDER, szName[cchName + 1 + cchModName + 1])); + if (pProv) + { + pProv->Core.pszName = &pProv->szName[0]; + pProv->Core.pszModName = &pProv->szName[cchName + 1]; + pProv->Core.pDesc = pDesc; + pProv->Core.pHdr = pVtgHdr; + pProv->Core.paProbeLocsRO = (PCVTGPROBELOC )((uintptr_t)pVtgHdr + pVtgHdr->offProbeLocs); + if (!pUmod) + { + pProv->Core.pvProbeLocsEn = (void *)((uintptr_t)pVtgHdr + pVtgHdr->offProbeLocs); + pProv->Core.pacProbeEnabled = (uint32_t *)((uintptr_t)pVtgHdr + pVtgHdr->offProbeEnabled); + pProv->Core.paR0ProbeLocs = NULL; + pProv->Core.paR0Probes = NULL; + pProv->Core.cbProbeLocsEn = sizeof(VTGPROBELOC); + pProv->Core.cBits = ARCH_BITS; + pProv->Core.fUmod = false; + } + else + { + pProv->Core.pvProbeLocsEn = pUmod->pvProbeLocs; + pProv->Core.pacProbeEnabled = pUmod->pacProbeEnabled; + pProv->Core.paR0ProbeLocs = &pUmod->aProbeLocs[0]; + pProv->Core.paR0Probes = (PSUPDRVPROBEINFO)&pUmod->aProbeLocs[pUmod->cProbeLocs]; + pProv->Core.cbProbeLocsEn = pUmod->cbProbeLoc; + pProv->Core.cBits = pUmod->cBits; + pProv->Core.fUmod = true; + } + pProv->pImage = pImage; + pProv->pSession = pSession; + pProv->pUmod = pUmod; + pProv->fZombie = false; + pProv->fRegistered = true; + + if (!pUmod) + RT_BCOPY_UNFORTIFIED(pProv->szName, pszName, cchName + 1); + else + RTStrPrintf(pProv->szName, cchName + 1, "%s%u", pszName, (uint32_t)pSession->Process); + RT_BCOPY_UNFORTIFIED((void *)pProv->Core.pszModName, pszModName, cchModName + 1); + + /* + * Do the actual registration and list manipulations while holding + * down the lock. + */ + rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_SUCCESS(rc)) + { + if ( pDevExt->pTracerOps + && !pDevExt->fTracerUnloading) + rc = pDevExt->pTracerOps->pfnProviderRegister(pDevExt->pTracerOps, &pProv->Core); + else + { + pProv->fRegistered = false; + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + RTListAppend(&pDevExt->TracerProviderList, &pProv->ListEntry); + if (pSession) + { + RTListAppend(&pSession->TpProviders, &pProv->SessionListEntry); + pSession->cTpProviders++; + } + else + RTListInit(&pProv->SessionListEntry); + RTSemFastMutexRelease(pDevExt->mtxTracer); + LOG_TRACER(("Registered tracepoint provider '%s' in '%s' -> %p\n", + pProv->szName, pszModName, pProv->Core.TracerData.DTrace.idProvider)); + } + else + { + RTSemFastMutexRelease(pDevExt->mtxTracer); + LOG_TRACER(("Failed to register tracepoint provider '%s' in '%s' -> %Rrc\n", + pProv->szName, pszModName, rc)); + } + } + } + else + rc = VERR_NO_MEMORY; + + /* + * In case of failure, we have to undo any providers we already + * managed to register. + */ + if (RT_FAILURE(rc)) + { + PSUPDRVTPPROVIDER pProvNext; + + if (pProv) + supdrvTracerFreeProvider(pProv); + + RTSemFastMutexRequest(pDevExt->mtxTracer); + if (pImage) + { + RTListForEachReverseSafe(&pDevExt->TracerProviderList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + if (pProv->Core.pHdr == pVtgHdr) + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + } + else + { + RTListForEachSafe(&pSession->TpProviders, pProv, pProvNext, SUPDRVTPPROVIDER, SessionListEntry) + { + if (pProv->Core.pHdr == pVtgHdr) + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + return rc; + } + } + + return VINF_SUCCESS; +} + + +/** + * Registers the VTG tracepoint providers of a driver. + * + * @returns VBox status code. + * @param pSession The support driver session handle. + * @param pVtgHdr The VTG header. + * @param pszName The driver name. + */ +SUPR0DECL(int) SUPR0TracerRegisterDrv(PSUPDRVSESSION pSession, PVTGOBJHDR pVtgHdr, const char *pszName) +{ + int rc; + + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertPtrReturn(pVtgHdr, VERR_INVALID_POINTER); + AssertReturn(pSession->R0Process == NIL_RTR0PROCESS, VERR_INVALID_PARAMETER); + LOG_TRACER(("SUPR0TracerRegisterDrv: pSession=%p pVtgHdr=%p pszName=%s\n", pSession, pVtgHdr, pszName)); + + rc = supdrvTracerRegisterVtgObj(pSession->pDevExt, pVtgHdr, NULL /*pImage*/, pSession, NULL /*pUmod*/, pszName); + + /* + * Try unregister zombies while we have a chance. + */ + supdrvTracerProcessZombies(pSession->pDevExt); + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerRegisterDrv); + + +/** + * Deregister the VTG tracepoint providers of a driver. + * + * @param pSession The support driver session handle. + */ +SUPR0DECL(void) SUPR0TracerDeregisterDrv(PSUPDRVSESSION pSession) +{ + PSUPDRVTPPROVIDER pProv, pProvNext; + PSUPDRVDEVEXT pDevExt; + AssertReturnVoid(SUP_IS_SESSION_VALID(pSession)); + AssertReturnVoid(pSession->R0Process == NIL_RTR0PROCESS); + LOG_TRACER(("SUPR0TracerDeregisterDrv: pSession=%p\n", pSession)); + + pDevExt = pSession->pDevExt; + + /* + * Search for providers belonging to this driver session. + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListForEachSafe(&pSession->TpProviders, pProv, pProvNext, SUPDRVTPPROVIDER, SessionListEntry) + { + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + + /* + * Try unregister zombies while we have a chance. + */ + supdrvTracerProcessZombies(pDevExt); +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerDeregisterDrv); + + +/** + * Registers the VTG tracepoint providers of a module loaded by + * the support driver. + * + * This should be called from the ModuleInit code. + * + * @returns VBox status code. + * @param hMod The module handle. + * @param pVtgHdr The VTG header. + */ +SUPR0DECL(int) SUPR0TracerRegisterModule(void *hMod, PVTGOBJHDR pVtgHdr) +{ + PSUPDRVLDRIMAGE pImage = (PSUPDRVLDRIMAGE)hMod; + PSUPDRVDEVEXT pDevExt; + int rc; + + LOG_TRACER(("SUPR0TracerRegisterModule: %p\n", pVtgHdr)); + + /* + * Validate input and context. + */ + AssertPtrReturn(pImage, VERR_INVALID_HANDLE); + AssertPtrReturn(pVtgHdr, VERR_INVALID_POINTER); + + AssertPtrReturn(pImage, VERR_INVALID_POINTER); + pDevExt = pImage->pDevExt; + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + AssertReturn(pDevExt->pLdrInitImage == pImage, VERR_WRONG_ORDER); + AssertReturn(pDevExt->hLdrInitThread == RTThreadNativeSelf(), VERR_WRONG_ORDER); + AssertReturn((uintptr_t)pVtgHdr - (uintptr_t)pImage->pvImage < pImage->cbImageBits, VERR_INVALID_PARAMETER); + + /* + * Do the job. + */ + rc = supdrvTracerRegisterVtgObj(pDevExt, pVtgHdr, pImage, NULL /*pSession*/, NULL /*pUmod*/, pImage->szName); + LOG_TRACER(("SUPR0TracerRegisterModule: rc=%d\n", rc)); + + /* + * Try unregister zombies while we have a chance. + */ + supdrvTracerProcessZombies(pDevExt); + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerRegisterModule); + + +/** + * Registers the tracer implementation. + * + * This should be called from the ModuleInit code or from a ring-0 session. + * + * @returns VBox status code. + * @param hMod The module handle. + * @param pSession Ring-0 session handle. + * @param pReg Pointer to the tracer registration structure. + * @param ppHlp Where to return the tracer helper method table. + */ +SUPR0DECL(int) SUPR0TracerRegisterImpl(void *hMod, PSUPDRVSESSION pSession, PCSUPDRVTRACERREG pReg, PCSUPDRVTRACERHLP *ppHlp) +{ + PSUPDRVLDRIMAGE pImage = (PSUPDRVLDRIMAGE)hMod; + PSUPDRVDEVEXT pDevExt; + PSUPDRVTPPROVIDER pProv; + int rc; + int rc2; + + /* + * Validate input and context. + */ + AssertPtrReturn(ppHlp, VERR_INVALID_POINTER); + *ppHlp = NULL; + AssertPtrReturn(pReg, VERR_INVALID_HANDLE); + + if (pImage) + { + AssertPtrReturn(pImage, VERR_INVALID_POINTER); + AssertReturn(pSession == NULL, VERR_INVALID_PARAMETER); + pDevExt = pImage->pDevExt; + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + AssertReturn(pDevExt->pLdrInitImage == pImage, VERR_WRONG_ORDER); + AssertReturn(pDevExt->hLdrInitThread == RTThreadNativeSelf(), VERR_WRONG_ORDER); + } + else + { + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process == NIL_RTR0PROCESS, VERR_INVALID_PARAMETER); + pDevExt = pSession->pDevExt; + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + } + + AssertReturn(pReg->u32Magic == SUPDRVTRACERREG_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pReg->u32Version == SUPDRVTRACERREG_VERSION, VERR_VERSION_MISMATCH); + AssertReturn(pReg->uEndMagic == SUPDRVTRACERREG_MAGIC, VERR_VERSION_MISMATCH); + AssertPtrReturn(pReg->pfnProbeFireKernel, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnProbeFireUser, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnTracerOpen, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnTracerIoCtl, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnTracerClose, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnProviderRegister, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnProviderDeregister, VERR_INVALID_POINTER); + AssertPtrReturn(pReg->pfnProviderDeregisterZombie, VERR_INVALID_POINTER); + + /* + * Do the job. + */ + rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_SUCCESS(rc)) + { + if (!pDevExt->pTracerOps) + { + LOG_TRACER(("SUPR0TracerRegisterImpl: pReg=%p\n", pReg)); + pDevExt->pTracerOps = pReg; + pDevExt->pTracerSession = pSession; + pDevExt->pTracerImage = pImage; + + g_pfnSupdrvProbeFireKernel = (PFNRT)pDevExt->pTracerOps->pfnProbeFireKernel; + + *ppHlp = &pDevExt->TracerHlp; + rc = VINF_SUCCESS; + + /* + * Iterate the already loaded modules and register their providers. + */ + RTListForEach(&pDevExt->TracerProviderList, pProv, SUPDRVTPPROVIDER, ListEntry) + { + Assert(!pProv->fRegistered); + pProv->fRegistered = true; + rc2 = pDevExt->pTracerOps->pfnProviderRegister(pDevExt->pTracerOps, &pProv->Core); + if (RT_FAILURE(rc2)) + { + pProv->fRegistered = false; + SUPR0Printf("SUPR0TracerRegisterImpl: Failed to register provider %s::%s - rc=%d\n", + pProv->Core.pszModName, pProv->szName, rc2); + } + } + } + else + rc = VERR_SUPDRV_TRACER_ALREADY_REGISTERED; + RTSemFastMutexRelease(pDevExt->mtxTracer); + } + + return rc; + +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerRegisterImpl); + + +/** + * Common tracer implementation deregistration code. + * + * The caller sets fTracerUnloading prior to calling this function. + * + * @param pDevExt The device extension structure. + */ +static void supdrvTracerCommonDeregisterImpl(PSUPDRVDEVEXT pDevExt) +{ + uint32_t i; + PSUPDRVTPPROVIDER pProv; + PSUPDRVTPPROVIDER pProvNext; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + + /* + * Reinstall the stub probe-fire function. + */ + g_pfnSupdrvProbeFireKernel = supdrvTracerProbeFireStub; + + /* + * Disassociate the tracer implementation from all providers. + * We will have to wait on busy providers. + */ + for (i = 0; ; i++) + { + uint32_t cZombies = 0; + + /* Live providers. */ + RTListForEachSafe(&pDevExt->TracerProviderList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + int rc; + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Attemting to unregister '%s' / %p...\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider)); + + if (!pProv->fRegistered) + continue; + if (!pProv->fZombie) + { + rc = pDevExt->pTracerOps->pfnProviderDeregister(pDevExt->pTracerOps, &pProv->Core); + if (RT_FAILURE(rc)) + pProv->fZombie = true; + } + else + rc = pDevExt->pTracerOps->pfnProviderDeregisterZombie(pDevExt->pTracerOps, &pProv->Core); + if (RT_SUCCESS(rc)) + pProv->fZombie = pProv->fRegistered = false; + else + { + cZombies++; + if (!(i & 0xf)) + SUPR0Printf("supdrvTracerCommonDeregisterImpl: Waiting on busy provider '%s' / %p (rc=%d)\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc); + else + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Failed to unregister provider '%s' / %p - rc=%d\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc)); + } + } + + /* Zombies providers. */ + RTListForEachSafe(&pDevExt->TracerProviderZombieList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + int rc; + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Attemting to unregister '%s' / %p (zombie)...\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider)); + + rc = pDevExt->pTracerOps->pfnProviderDeregisterZombie(pDevExt->pTracerOps, &pProv->Core); + if (RT_SUCCESS(rc)) + { + RTListNodeRemove(&pProv->ListEntry); + supdrvTracerFreeProvider(pProv); + } + else + { + cZombies++; + if (!(i & 0xf)) + SUPR0Printf("supdrvTracerCommonDeregisterImpl: Waiting on busy provider '%s' / %p (rc=%d)\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc); + else + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Failed to unregister provider '%s' / %p - rc=%d\n", + pProv->szName, pProv->Core.TracerData.DTrace.idProvider, rc)); + } + } + + /* Tracer opens. */ + if (pDevExt->cTracerOpens) + { + cZombies++; + if (!(i & 0xf)) + SUPR0Printf("supdrvTracerCommonDeregisterImpl: Waiting on %u opens\n", pDevExt->cTracerOpens); + else + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Waiting on %u opens\n", pDevExt->cTracerOpens)); + } + + /* Tracer calls. */ + if (pDevExt->cTracerCallers) + { + cZombies++; + if (!(i & 0xf)) + SUPR0Printf("supdrvTracerCommonDeregisterImpl: Waiting on %u callers\n", pDevExt->cTracerCallers); + else + LOG_TRACER(("supdrvTracerCommonDeregisterImpl: Waiting on %u callers\n", pDevExt->cTracerCallers)); + } + + /* Done? */ + if (cZombies == 0) + break; + + /* Delay...*/ + RTSemFastMutexRelease(pDevExt->mtxTracer); + RTThreadSleep(1000); + RTSemFastMutexRequest(pDevExt->mtxTracer); + } + + /* + * Deregister the tracer implementation. + */ + pDevExt->pTracerImage = NULL; + pDevExt->pTracerSession = NULL; + pDevExt->pTracerOps = NULL; + pDevExt->fTracerUnloading = false; + + RTSemFastMutexRelease(pDevExt->mtxTracer); +} + + +/** + * Deregister a tracer implementation. + * + * This should be called from the ModuleTerm code or from a ring-0 session. + * + * @returns VBox status code. + * @param hMod The module handle. + * @param pSession Ring-0 session handle. + */ +SUPR0DECL(int) SUPR0TracerDeregisterImpl(void *hMod, PSUPDRVSESSION pSession) +{ + PSUPDRVLDRIMAGE pImage = (PSUPDRVLDRIMAGE)hMod; + PSUPDRVDEVEXT pDevExt; + int rc; + + /* + * Validate input and context. + */ + if (pImage) + { + AssertPtrReturn(pImage, VERR_INVALID_POINTER); + AssertReturn(pSession == NULL, VERR_INVALID_PARAMETER); + pDevExt = pImage->pDevExt; + } + else + { + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(pSession->R0Process == NIL_RTR0PROCESS, VERR_INVALID_PARAMETER); + pDevExt = pSession->pDevExt; + } + AssertPtrReturn(pDevExt, VERR_INVALID_POINTER); + + /* + * Do the job. + */ + rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_SUCCESS(rc)) + { + if ( pImage + ? pDevExt->pTracerImage == pImage + : pDevExt->pTracerSession == pSession) + { + LOG_TRACER(("SUPR0TracerDeregisterImpl: Unloading ...\n")); + pDevExt->fTracerUnloading = true; + RTSemFastMutexRelease(pDevExt->mtxTracer); + supdrvTracerCommonDeregisterImpl(pDevExt); + LOG_TRACER(("SUPR0TracerDeregisterImpl: ... done.\n")); + } + else + { + rc = VERR_SUPDRV_TRACER_NOT_REGISTERED; + RTSemFastMutexRelease(pDevExt->mtxTracer); + } + } + + return rc; +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerDeregisterImpl); + + +/* + * The probe function is a bit more fun since we need tail jump optimizating. + * + * Since we cannot ship yasm sources for linux and freebsd, owing to the cursed + * rebuilding of the kernel module from scratch at install time, we have to + * deploy some ugly gcc inline assembly here. + */ +#if defined(__GNUC__) && (defined(RT_OS_FREEBSD) || defined(RT_OS_LINUX)) +__asm__("\ + .section .text \n\ + \n\ + .p2align 4 \n\ + .global SUPR0TracerFireProbe \n\ + .type SUPR0TracerFireProbe, @function \n\ +SUPR0TracerFireProbe: \n\ +"); +# if defined(RT_ARCH_AMD64) +__asm__("\ + movq g_pfnSupdrvProbeFireKernel(%rip), %rax \n\ + jmp *%rax \n\ +"); +# elif defined(RT_ARCH_X86) +__asm__("\ + movl g_pfnSupdrvProbeFireKernel, %eax \n\ + jmp *%eax \n\ +"); +# else +# error "Which arch is this?" +# endif +__asm__("\ + .size SUPR0TracerFireProbe, . - SUPR0TracerFireProbe \n\ + \n\ + .type supdrvTracerProbeFireStub,@function \n\ + .global supdrvTracerProbeFireStub \n\ +supdrvTracerProbeFireStub: \n\ + ret \n\ + .size supdrvTracerProbeFireStub, . - supdrvTracerProbeFireStub \n\ + \n\ + .previous \n\ +"); +# if 0 /* Slickedit on windows highlighting fix */ + ) +# endif +#endif +SUPR0_EXPORT_SYMBOL(SUPR0TracerFireProbe); + + +/** + * Module unloading hook, called after execution in the module have ceased. + * + * @param pDevExt The device extension structure. + * @param pImage The image being unloaded. + */ +void VBOXCALL supdrvTracerModuleUnloading(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + PSUPDRVTPPROVIDER pProv, pProvNext; + AssertPtrReturnVoid(pImage); /* paranoia */ + + RTSemFastMutexRequest(pDevExt->mtxTracer); + + /* + * If it is the tracer image, we have to unload all the providers. + */ + if (pDevExt->pTracerImage == pImage) + { + LOG_TRACER(("supdrvTracerModuleUnloading: Unloading tracer ...\n")); + pDevExt->fTracerUnloading = true; + RTSemFastMutexRelease(pDevExt->mtxTracer); + supdrvTracerCommonDeregisterImpl(pDevExt); + LOG_TRACER(("supdrvTracerModuleUnloading: ... done.\n")); + } + else + { + /* + * Unregister all providers belonging to this image. + */ + RTListForEachSafe(&pDevExt->TracerProviderList, pProv, pProvNext, SUPDRVTPPROVIDER, ListEntry) + { + if (pProv->pImage == pImage) + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + + RTSemFastMutexRelease(pDevExt->mtxTracer); + + /* + * Try unregister zombies while we have a chance. + */ + supdrvTracerProcessZombies(pDevExt); + } +} + + +/** + * Called when a session is being cleaned up. + * + * @param pDevExt The device extension structure. + * @param pSession The session that is being torn down. + */ +void VBOXCALL supdrvTracerCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + /* + * Deregister all providers. + */ + SUPDRVTPPROVIDER *pProvNext; + SUPDRVTPPROVIDER *pProv; + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListForEachSafe(&pSession->TpProviders, pProv, pProvNext, SUPDRVTPPROVIDER, SessionListEntry) + { + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + + /* + * Clean up instance data the trace may have associated with the session. + */ + if (pSession->uTracerData) + supdrvIOCtl_TracerClose(pDevExt, pSession); + + /* + * Deregister any tracer implementation. + */ + if (pSession->R0Process == NIL_RTR0PROCESS) + (void)SUPR0TracerDeregisterImpl(NULL, pSession); + + if (pSession->R0Process != NIL_RTR0PROCESS) + { + /* + * Free any lingering user modules. We don't bother holding the lock + * here as there shouldn't be anyone messing with the session at this + * point. + */ + PSUPDRVTRACERUMOD pUmodNext; + PSUPDRVTRACERUMOD pUmod; + RTListForEachSafe(&pSession->TpUmods, pUmod, pUmodNext, SUPDRVTRACERUMOD, ListEntry) + { + RTR0MemObjFree(pUmod->hMemObjMap, false /*fFreeMappings*/); + RTR0MemObjFree(pUmod->hMemObjLock, false /*fFreeMappings*/); + supdrvVtgReleaseObjectCopy(pDevExt, pUmod->pVtgCopy); + RTMemFree(pUmod); + } + } +} + + +static void supdrvVtgReleaseObjectCopy(PSUPDRVDEVEXT pDevExt, PSUPDRVVTGCOPY pThis) +{ + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + { + RTSemFastMutexRequest(pDevExt->mtxTracer); + pThis->u32Magic = ~SUDPRVVTGCOPY_MAGIC; + RTListNodeRemove(&pThis->ListEntry); + RTSemFastMutexRelease(pDevExt->mtxTracer); + + RTMemFree(pThis); + } +} + + +/** + * Finds a matching VTG object copy, caller owns the lock already. + * + * @returns Copy with reference. NULL if not found. + * @param pHashList The hash list to search. + * @param pHdr The VTG header (valid). + * @param cbStrTab The string table size. + * @param fFlags The user module flags. + */ +static PSUPDRVVTGCOPY supdrvVtgFindObjectCopyLocked(PRTLISTANCHOR pHashList, PCVTGOBJHDR pHdr, uint32_t cbStrTab, uint32_t fFlags) +{ + PSUPDRVVTGCOPY pCur; + + fFlags &= SUP_TRACER_UMOD_FLAGS_TYPE_MASK; + RTListForEach(pHashList, pCur, SUPDRVVTGCOPY, ListEntry) + { +#define HDR_EQUALS(member) pCur->Hdr.member == pHdr->member + if ( HDR_EQUALS(Uuid.au32[0]) + && HDR_EQUALS(Uuid.au32[1]) + && HDR_EQUALS(Uuid.au32[2]) + && HDR_EQUALS(Uuid.au32[3]) + && HDR_EQUALS(cbObj) + && HDR_EQUALS(cBits) + && pCur->cbStrTab == cbStrTab + && pCur->fFlags == fFlags + ) + { + if (RT_LIKELY( HDR_EQUALS(offStrTab) + && HDR_EQUALS(cbStrTab) + && HDR_EQUALS(offArgLists) + && HDR_EQUALS(cbArgLists) + && HDR_EQUALS(offProbes) + && HDR_EQUALS(cbProbes) + && HDR_EQUALS(offProviders) + && HDR_EQUALS(cbProviders) + && HDR_EQUALS(offProbeEnabled) + && HDR_EQUALS(cbProbeEnabled) + && HDR_EQUALS(offProbeLocs) + && HDR_EQUALS(cbProbeLocs) + ) + ) + { + Assert(pCur->cRefs > 0); + Assert(pCur->cRefs < _1M); + pCur->cRefs++; + return pCur; + } + } +#undef HDR_EQUALS + } + + return NULL; +} + + +/** + * Finds a matching VTG object copy. + * + * @returns Copy with reference. NULL if not found. + * @param pDevExt The device extension. + * @param pHdr The VTG header (valid). + * @param cbStrTab The string table size. + * @param fFlags The user module flags. + */ +static PSUPDRVVTGCOPY supdrvVtgFindObjectCopy(PSUPDRVDEVEXT pDevExt, PCVTGOBJHDR pHdr, uint32_t cbStrTab, uint32_t fFlags) +{ + PRTLISTANCHOR pHashList = &pDevExt->aTrackerUmodHash[pHdr->Uuid.au8[3] % RT_ELEMENTS(pDevExt->aTrackerUmodHash)]; + PSUPDRVVTGCOPY pRet; + + int rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + AssertRCReturn(rc, NULL); + + pRet = supdrvVtgFindObjectCopyLocked(pHashList, pHdr, cbStrTab, fFlags); + + RTSemFastMutexRelease(pDevExt->mtxTracer); + return pRet; +} + + +/** + * Makes a shared copy of the VTG object. + * + * @returns VBox status code. + * @param pDevExt The device extension. + * @param pVtgHdr The VTG header (valid). + * @param R3PtrVtgHdr The ring-3 VTG header address. + * @param uVtgHdrAddr The address of the VTG header in the context + * where it is actually used. + * @param R3PtrStrTab The ring-3 address of the probe location string + * table. The probe location array have offsets + * into this instead of funciton name pointers. + * @param cbStrTab The size of the probe location string table. + * @param fFlags The user module flags. + * @param pUmod The structure we've allocated to track the + * module. This have a valid kernel mapping of the + * probe location array. Upon successful return, + * the pVtgCopy member will hold the address of our + * copy (with a referenced of course). + */ +static int supdrvVtgCreateObjectCopy(PSUPDRVDEVEXT pDevExt, PCVTGOBJHDR pVtgHdr, RTR3PTR R3PtrVtgHdr, RTUINTPTR uVtgHdrAddr, + RTR3PTR R3PtrStrTab, uint32_t cbStrTab, uint32_t fFlags, PSUPDRVTRACERUMOD pUmod) +{ + /* + * Calculate the space required, allocate and copy in the data. + */ + int rc; + uint32_t const cProbeLocs = pVtgHdr->cbProbeLocs / (pVtgHdr->cBits == 32 ? sizeof(VTGPROBELOC32) : sizeof(VTGPROBELOC64)); + uint32_t const cbProbeLocs = cProbeLocs * sizeof(VTGPROBELOC); + uint32_t const offProbeLocs = RT_ALIGN(pVtgHdr->cbObj, 8); + size_t const cb = offProbeLocs + cbProbeLocs + cbStrTab + 1; + PSUPDRVVTGCOPY pThis = (PSUPDRVVTGCOPY)RTMemAlloc(RT_UOFFSETOF(SUPDRVVTGCOPY, Hdr) + cb); + if (!pThis) + return VERR_NO_MEMORY; + + pThis->u32Magic = SUDPRVVTGCOPY_MAGIC; + pThis->cRefs = 1; + pThis->cbStrTab = cbStrTab; + pThis->fFlags = fFlags & SUP_TRACER_UMOD_FLAGS_TYPE_MASK; + RTListInit(&pThis->ListEntry); + + rc = RTR0MemUserCopyFrom(&pThis->Hdr, R3PtrVtgHdr, pVtgHdr->cbObj); + if (RT_SUCCESS(rc)) + { + char *pchStrTab = (char *)&pThis->Hdr + offProbeLocs + cbProbeLocs; + rc = RTR0MemUserCopyFrom(pchStrTab, R3PtrStrTab, cbStrTab); + if (RT_SUCCESS(rc)) + { + PVTGPROBELOC paDst = (PVTGPROBELOC)((char *)&pThis->Hdr + offProbeLocs); + uint32_t i; + + /* + * Some paranoia: Overwrite the header with the copy we've already + * validated and zero terminate the string table. + */ + pThis->Hdr = *pVtgHdr; + pchStrTab[cbStrTab] = '\0'; + + /* + * Set the probe location array related header members since we're + * making our own copy in a different location. + */ + pThis->Hdr.uProbeLocs.u64 = (uintptr_t)paDst; + pThis->Hdr.uProbeLocsEnd.u64 = (uintptr_t)paDst + cbProbeLocs; + pThis->Hdr.offProbeLocs = offProbeLocs; + pThis->Hdr.cbProbeLocs = cbProbeLocs; + pThis->Hdr.cBits = ARCH_BITS; + + /* + * Copy, convert and fix up the probe location table. + */ + if (pVtgHdr->cBits == 32) + { + uintptr_t const offDelta = (uintptr_t)&pThis->Hdr - uVtgHdrAddr; + PCVTGPROBELOC32 paSrc = (PCVTGPROBELOC32)pUmod->pvProbeLocs; + + for (i = 0; i < cProbeLocs; i++) + { + paDst[i].uLine = paSrc[i].uLine; + paDst[i].fEnabled = paSrc[i].fEnabled; + paDst[i].idProbe = paSrc[i].idProbe; + if (paSrc[i].pszFunction > cbStrTab) + { + rc = VERR_SUPDRV_TRACER_UMOD_STRTAB_OFF_BAD; + break; + } + paDst[i].pszFunction = pchStrTab + paSrc[i].pszFunction; + paDst[i].pProbe = (PVTGDESCPROBE)(uintptr_t)(paSrc[i].pProbe + offDelta); + } + } + else + { + uint64_t const offDelta = (uintptr_t)&pThis->Hdr - uVtgHdrAddr; + PCVTGPROBELOC64 paSrc = (PCVTGPROBELOC64)pUmod->pvProbeLocs; + + for (i = 0; i < cProbeLocs; i++) + { + paDst[i].uLine = paSrc[i].uLine; + paDst[i].fEnabled = paSrc[i].fEnabled; + paDst[i].idProbe = paSrc[i].idProbe; + if (paSrc[i].pszFunction > cbStrTab) + { + rc = VERR_SUPDRV_TRACER_UMOD_STRTAB_OFF_BAD; + break; + } + paDst[i].pszFunction = pchStrTab + (uintptr_t)paSrc[i].pszFunction; + paDst[i].pProbe = (PVTGDESCPROBE)(uintptr_t)(paSrc[i].pProbe + offDelta); + } + } + + /* + * Validate it + * + * Note! fUmod is false as this is a kernel copy with all native + * structures. + */ + if (RT_SUCCESS(rc)) + rc = supdrvVtgValidate(&pThis->Hdr, (uintptr_t)&pThis->Hdr, (uint8_t *)&pThis->Hdr, cb, false /*fUmod*/); + if (RT_SUCCESS(rc)) + { + /* + * Add it to the hash list, making sure nobody raced us. + */ + PRTLISTANCHOR pHashList = &pDevExt->aTrackerUmodHash[ pVtgHdr->Uuid.au8[3] + % RT_ELEMENTS(pDevExt->aTrackerUmodHash)]; + + rc = RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_SUCCESS(rc)) + { + pUmod->pVtgCopy = supdrvVtgFindObjectCopyLocked(pHashList, pVtgHdr, cbStrTab, fFlags); + if (!pUmod->pVtgCopy) + { + pUmod->pVtgCopy = pThis; + RTListAppend(pHashList, &pThis->ListEntry); + RTSemFastMutexRelease(pDevExt->mtxTracer); + return rc; + } + + /* + * Someone raced us, free our copy and return the existing + * one instead. + */ + RTSemFastMutexRelease(pDevExt->mtxTracer); + } + } + } + } + RTMemFree(pThis); + return rc; +} + + +/** + * Undoes what supdrvTracerUmodSetProbeIds did. + * + * @param pDevExt The device extension. + * @param pSession The current session. + * @param pUmod The user tracepoint module. + */ +static void supdrvTracerUmodClearProbeIds(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVTRACERUMOD pUmod) +{ + uint32_t i; + + AssertReturnVoid(pUmod->iLookupTable < RT_ELEMENTS(pSession->apTpLookupTable)); + AssertReturnVoid(pSession->apTpLookupTable[pUmod->iLookupTable] == pUmod); + + /* + * Clear the probe IDs and disable the probes. + */ + i = pUmod->cProbeLocs; + if (pUmod->cBits == 32) + { + PVTGPROBELOC32 paProbeLocs = (PVTGPROBELOC32)pUmod->pvProbeLocs; + while (i-- > 0) + paProbeLocs[i].idProbe = 0; + } + else + { + PVTGPROBELOC64 paProbeLocs = (PVTGPROBELOC64)pUmod->pvProbeLocs; + while (i-- > 0) + paProbeLocs[i].idProbe = 0; + } + + /* + * Free the lookup table entry. We'll have to wait for the table to go + * idle to make sure there are no current users of pUmod. + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + if (pSession->apTpLookupTable[pUmod->iLookupTable] == pUmod) + { + if (pSession->cTpProbesFiring > 0) + { + i = 0; + while (pSession->cTpProbesFiring > 0) + { + RTSemFastMutexRelease(pDevExt->mtxTracer); + i++; + if (!(i & 0xff)) + SUPR0Printf("supdrvTracerUmodClearProbeIds: waiting for lookup table to go idle (i=%u)\n", i); + RTThreadSleep(10); + RTSemFastMutexRequest(pDevExt->mtxTracer); + } + } + ASMAtomicWriteNullPtr(&pSession->apTpLookupTable[pUmod->iLookupTable]); + } + RTSemFastMutexRelease(pDevExt->mtxTracer); +} + + +/** + * Allocates a lookup table entry for the Umod and sets the + * VTGPROBELOC::idProbe fields in user mode. + * + * @returns VINF_SUCCESS or VERR_SUPDRV_TRACER_TOO_MANY_PROVIDERS. + * @param pDevExt The device extension. + * @param pSession The current session. + * @param pUmod The user tracepoint module. + */ +static int supdrvTracerUmodSetProbeIds(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVTRACERUMOD pUmod) +{ + uint32_t iBase; + uint32_t i; + + /* + * Allocate a lookup table entry. + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + for (i = 0; i < RT_ELEMENTS(pSession->apTpLookupTable); i++) + { + if (!pSession->apTpLookupTable[i]) + { + pSession->apTpLookupTable[i] = pUmod; + pUmod->iLookupTable = i; + break; + } + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + if (i >= RT_ELEMENTS(pSession->apTpLookupTable)) + return VERR_SUPDRV_TRACER_TOO_MANY_PROVIDERS; + + /* + * Set probe IDs of the usermode probe location to indicate our lookup + * table entry as well as the probe location array entry. + */ + iBase = (uint32_t)pUmod->iLookupTable << 24; + i = pUmod->cProbeLocs; + if (pUmod->cBits == 32) + { + PVTGPROBELOC32 paProbeLocs = (PVTGPROBELOC32)pUmod->pvProbeLocs; + while (i-- > 0) + paProbeLocs[i].idProbe = iBase | i; + } + else + { + PVTGPROBELOC64 paProbeLocs = (PVTGPROBELOC64)pUmod->pvProbeLocs; + while (i-- > 0) + paProbeLocs[i].idProbe = iBase | i; + } + + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvIOCtl_TracerUmodRegister(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, + RTR3PTR R3PtrVtgHdr, RTUINTPTR uVtgHdrAddr, + RTR3PTR R3PtrStrTab, uint32_t cbStrTab, + const char *pszModName, uint32_t fFlags) +{ + VTGOBJHDR Hdr; + PSUPDRVTRACERUMOD pUmod; + RTR3PTR R3PtrLock; + size_t cbLock; + uint32_t cProbeLocs; + int rc; + + /* + * Validate input. + */ + if (pSession->R0Process == NIL_RTR0PROCESS) + return VERR_INVALID_CONTEXT; + if ( fFlags != SUP_TRACER_UMOD_FLAGS_EXE + && fFlags != SUP_TRACER_UMOD_FLAGS_SHARED) + return VERR_INVALID_PARAMETER; + + if (pSession->cTpProviders >= RT_ELEMENTS(pSession->apTpLookupTable)) + return VERR_SUPDRV_TRACER_TOO_MANY_PROVIDERS; + + if ( cbStrTab < 2 + || cbStrTab > _1M) + return VERR_SUPDRV_TRACER_UMOD_STRTAB_TOO_BIG; + + /* + * Read the VTG header into a temporary buffer and perform some simple + * validations to make sure we aren't wasting our time here. + */ + rc = RTR0MemUserCopyFrom(&Hdr, R3PtrVtgHdr, sizeof(Hdr)); + if (RT_FAILURE(rc)) + return rc; + rc = supdrvVtgValidateHdr(&Hdr, uVtgHdrAddr, NULL, 0, true /*fUmod*/); + if (RT_FAILURE(rc)) + return rc; + if (Hdr.cbProviders / sizeof(VTGDESCPROVIDER) > 2) + return VERR_SUPDRV_TRACER_TOO_MANY_PROVIDERS; + + /* + * Check how much needs to be locked down and how many probe locations + * there are. + */ + if ( Hdr.offProbeLocs <= 0 + || Hdr.offProbeEnabled > (uint32_t)Hdr.offProbeLocs + || (uint32_t)Hdr.offProbeLocs - Hdr.offProbeEnabled - Hdr.cbProbeEnabled > 128) + return VERR_SUPDRV_TRACER_UMOD_NOT_ADJACENT; + R3PtrLock = R3PtrVtgHdr + Hdr.offProbeEnabled; + cbLock = Hdr.offProbeLocs + Hdr.cbProbeLocs - Hdr.offProbeEnabled + (R3PtrLock & PAGE_OFFSET_MASK); + R3PtrLock &= ~(RTR3PTR)PAGE_OFFSET_MASK; + if (cbLock > _64K) + return VERR_SUPDRV_TRACER_UMOD_TOO_MANY_PROBES; + + cProbeLocs = Hdr.cbProbeLocs / (Hdr.cBits == 32 ? sizeof(VTGPROBELOC32) : sizeof(VTGPROBELOC64)); + + /* + * Allocate the tracker data we keep in the session. + */ + pUmod = (PSUPDRVTRACERUMOD)RTMemAllocZ( RT_UOFFSETOF_DYN(SUPDRVTRACERUMOD, aProbeLocs[cProbeLocs]) + + (Hdr.cbProbeEnabled / sizeof(uint32_t) * sizeof(SUPDRVPROBEINFO)) ); + if (!pUmod) + return VERR_NO_MEMORY; + pUmod->u32Magic = SUPDRVTRACERUMOD_MAGIC; + RTListInit(&pUmod->ListEntry); + pUmod->R3PtrVtgHdr = R3PtrVtgHdr; + pUmod->pVtgCopy = NULL; + pUmod->hMemObjLock = NIL_RTR0MEMOBJ; + pUmod->hMemObjMap = NIL_RTR0MEMOBJ; + pUmod->R3PtrProbeLocs = (RTR3INTPTR)R3PtrVtgHdr + Hdr.offProbeLocs; + pUmod->iLookupTable = UINT8_MAX; + pUmod->cBits = Hdr.cBits; + pUmod->cbProbeLoc = Hdr.cBits == 32 ? sizeof(VTGPROBELOC32) : sizeof(VTGPROBELOC64); + pUmod->cProbeLocs = cProbeLocs; + + /* + * Lock down and map the user-mode structures. + */ + rc = RTR0MemObjLockUser(&pUmod->hMemObjLock, R3PtrLock, cbLock, RTMEM_PROT_READ | RTMEM_PROT_WRITE, NIL_RTR0PROCESS); + if (RT_SUCCESS(rc)) + { + rc = RTR0MemObjMapKernel(&pUmod->hMemObjMap, pUmod->hMemObjLock, (void *)-1, 0, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + { + pUmod->pacProbeEnabled = (uint32_t *)( (uintptr_t)RTR0MemObjAddress(pUmod->hMemObjMap) + + ((uintptr_t)(R3PtrVtgHdr + Hdr.offProbeEnabled) & PAGE_OFFSET_MASK)); + pUmod->pvProbeLocs = (uint8_t *)pUmod->pacProbeEnabled + Hdr.offProbeLocs - Hdr.offProbeEnabled; + + /* + * Does some other process use the same module already? If so, + * share the VTG data with it. Otherwise, make a ring-0 copy it. + */ + pUmod->pVtgCopy = supdrvVtgFindObjectCopy(pDevExt, &Hdr, cbStrTab, fFlags); + if (!pUmod->pVtgCopy) + rc = supdrvVtgCreateObjectCopy(pDevExt, &Hdr, R3PtrVtgHdr, uVtgHdrAddr, R3PtrStrTab, cbStrTab, fFlags, pUmod); + if (RT_SUCCESS(rc)) + { + AssertPtr(pUmod->pVtgCopy); + + /* + * Grabe a place in apTpLookupTable and set the probe IDs + * accordingly. + */ + rc = supdrvTracerUmodSetProbeIds(pDevExt, pSession, pUmod); + if (RT_SUCCESS(rc)) + { + /* + * Register the providers. + */ + rc = supdrvTracerRegisterVtgObj(pDevExt, &pUmod->pVtgCopy->Hdr, + NULL /*pImage*/, pSession, pUmod, pszModName); + if (RT_SUCCESS(rc)) + { + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListAppend(&pSession->TpUmods, &pUmod->ListEntry); + RTSemFastMutexRelease(pDevExt->mtxTracer); + + return VINF_SUCCESS; + } + + /* bail out. */ + supdrvTracerUmodClearProbeIds(pDevExt, pSession, pUmod); + } + supdrvVtgReleaseObjectCopy(pDevExt, pUmod->pVtgCopy); + } + RTR0MemObjFree(pUmod->hMemObjMap, false /*fFreeMappings*/); + } + RTR0MemObjFree(pUmod->hMemObjLock, false /*fFreeMappings*/); + } + pUmod->u32Magic = ~SUPDRVTRACERUMOD_MAGIC; + RTMemFree(pUmod); + return rc; +} + + +int VBOXCALL supdrvIOCtl_TracerUmodDeregister(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, RTR3PTR R3PtrVtgHdr) +{ + PSUPDRVTRACERUMOD pUmod = NULL; + uint32_t i; + int rc; + + /* + * Validate the request. + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + for (i = 0; i < RT_ELEMENTS(pSession->apTpLookupTable); i++) + { + pUmod = pSession->apTpLookupTable[i]; + if ( pUmod + && pUmod->u32Magic == SUPDRVTRACERUMOD_MAGIC + && pUmod->R3PtrVtgHdr == R3PtrVtgHdr) + break; + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + if (pUmod) + { + SUPDRVTPPROVIDER *pProvNext; + SUPDRVTPPROVIDER *pProv; + + /* + * Remove ourselves from the lookup table and clean up the ring-3 bits + * we've dirtied. We do this first to make sure no probes are firing + * when we're destroying the providers in the next step. + */ + supdrvTracerUmodClearProbeIds(pDevExt, pSession, pUmod); + + /* + * Deregister providers related to the VTG object. + */ + RTSemFastMutexRequest(pDevExt->mtxTracer); + RTListForEachSafe(&pSession->TpProviders, pProv, pProvNext, SUPDRVTPPROVIDER, SessionListEntry) + { + if (pProv->pUmod == pUmod) + supdrvTracerDeregisterVtgObj(pDevExt, pProv); + } + RTSemFastMutexRelease(pDevExt->mtxTracer); + + /* + * Destroy the Umod object. + */ + pUmod->u32Magic = ~SUPDRVTRACERUMOD_MAGIC; + supdrvVtgReleaseObjectCopy(pDevExt, pUmod->pVtgCopy); + RTR0MemObjFree(pUmod->hMemObjMap, false /*fFreeMappings*/); + RTR0MemObjFree(pUmod->hMemObjLock, false /*fFreeMappings*/); + RTMemFree(pUmod); + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; + return rc; +} + + +/** + * Implementation of supdrvIOCtl_TracerUmodProbeFire and + * SUPR0TracerUmodProbeFire. + * + * @param pDevExt The device extension. + * @param pSession The calling session. + * @param pCtx The context record. + */ +static void supdrvTracerUmodProbeFire(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVTRACERUSRCTX pCtx) +{ + /* + * We cannot trust user mode to hand us the right bits nor not calling us + * when disabled. So, we have to check for our selves. + */ + PSUPDRVTRACERUMOD pUmod; + uint32_t const iLookupTable = pCtx->idProbe >> 24; + uint32_t const iProbeLoc = pCtx->idProbe & UINT32_C(0x00ffffff); + + if (RT_UNLIKELY( !pDevExt->pTracerOps + || pDevExt->fTracerUnloading)) + return; + if (RT_UNLIKELY(iLookupTable >= RT_ELEMENTS(pSession->apTpLookupTable))) + return; + if (RT_UNLIKELY( pCtx->cBits != 32 + && pCtx->cBits != 64)) + return; + + ASMAtomicIncU32(&pSession->cTpProviders); + + pUmod = pSession->apTpLookupTable[iLookupTable]; + if (RT_LIKELY(pUmod)) + { + if (RT_LIKELY( pUmod->u32Magic == SUPDRVTRACERUMOD_MAGIC + && iProbeLoc < pUmod->cProbeLocs + && pCtx->cBits == pUmod->cBits)) + { +#if 0 /* This won't work for RC modules. */ + RTR3PTR R3PtrProbeLoc = pUmod->R3PtrProbeLocs + iProbeLoc * pUmod->cbProbeLoc; + if (RT_LIKELY( (pCtx->cBits == 32 ? (RTR3PTR)pCtx->u.X86.uVtgProbeLoc : pCtx->u.Amd64.uVtgProbeLoc) + == R3PtrProbeLoc)) +#endif + { + if (RT_LIKELY(pUmod->aProbeLocs[iProbeLoc].fEnabled)) + { + PSUPDRVVTGCOPY pVtgCopy; + ASMAtomicIncU32(&pDevExt->cTracerCallers); + pVtgCopy = pUmod->pVtgCopy; + if (RT_LIKELY( pDevExt->pTracerOps + && !pDevExt->fTracerUnloading + && pVtgCopy)) + { + PCVTGPROBELOC pProbeLocRO; + pProbeLocRO = (PCVTGPROBELOC)((uintptr_t)&pVtgCopy->Hdr + pVtgCopy->Hdr.offProbeLocs) + iProbeLoc; + + pCtx->idProbe = pUmod->aProbeLocs[iProbeLoc].idProbe; + pDevExt->pTracerOps->pfnProbeFireUser(pDevExt->pTracerOps, pSession, pCtx, &pVtgCopy->Hdr, pProbeLocRO); + } + ASMAtomicDecU32(&pDevExt->cTracerCallers); + } + } + } + } + + ASMAtomicDecU32(&pSession->cTpProviders); +} + + +SUPR0DECL(void) SUPR0TracerUmodProbeFire(PSUPDRVSESSION pSession, PSUPDRVTRACERUSRCTX pCtx) +{ + AssertReturnVoid(SUP_IS_SESSION_VALID(pSession)); + AssertPtrReturnVoid(pCtx); + + supdrvTracerUmodProbeFire(pSession->pDevExt, pSession, pCtx); +} +SUPR0_EXPORT_SYMBOL(SUPR0TracerUmodProbeFire); + + +void VBOXCALL supdrvIOCtl_TracerUmodProbeFire(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PSUPDRVTRACERUSRCTX pCtx) +{ + supdrvTracerUmodProbeFire(pDevExt, pSession, pCtx); +} + + +/** + * Open the tracer. + * + * @returns VBox status code + * @param pDevExt The device extension structure. + * @param pSession The current session. + * @param uCookie The tracer cookie. + * @param uArg The tracer open argument. + */ +int VBOXCALL supdrvIOCtl_TracerOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uint32_t uCookie, uintptr_t uArg) +{ + RTNATIVETHREAD hNativeSelf = RTThreadNativeSelf(); + int rc; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + + if (!pSession->uTracerData) + { + if (pDevExt->pTracerOps) + { + if (pDevExt->pTracerSession != pSession) + { + if (!pDevExt->fTracerUnloading) + { + if (pSession->hTracerCaller == NIL_RTNATIVETHREAD) + { + pDevExt->cTracerOpens++; + pSession->uTracerData = ~(uintptr_t)0; + pSession->hTracerCaller = hNativeSelf; + RTSemFastMutexRelease(pDevExt->mtxTracer); + + rc = pDevExt->pTracerOps->pfnTracerOpen(pDevExt->pTracerOps, pSession, uCookie, uArg, &pSession->uTracerData); + + RTSemFastMutexRequest(pDevExt->mtxTracer); + if (RT_FAILURE(rc)) + { + pDevExt->cTracerOpens--; + pSession->uTracerData = 0; + } + pSession->hTracerCaller = NIL_RTNATIVETHREAD; + } + else + rc = VERR_SUPDRV_TRACER_SESSION_BUSY; + } + else + rc = VERR_SUPDRV_TRACER_UNLOADING; + } + else + rc = VERR_SUPDRV_TRACER_CANNOT_OPEN_SELF; + } + else + rc = VERR_SUPDRV_TRACER_NOT_PRESENT; + } + else + rc = VERR_SUPDRV_TRACER_ALREADY_OPENED; + + RTSemFastMutexRelease(pDevExt->mtxTracer); + return rc; +} + + +/** + * Closes the tracer. + * + * @returns VBox status code. + * @param pDevExt The device extension structure. + * @param pSession The current session. + */ +int VBOXCALL supdrvIOCtl_TracerClose(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ + RTNATIVETHREAD hNativeSelf = RTThreadNativeSelf(); + int rc; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + + if (pSession->uTracerData) + { + Assert(pDevExt->cTracerOpens > 0); + + if (pDevExt->pTracerOps) + { + if (pSession->hTracerCaller == NIL_RTNATIVETHREAD) + { + uintptr_t uTracerData = pSession->uTracerData; + pSession->uTracerData = 0; + pSession->hTracerCaller = hNativeSelf; + RTSemFastMutexRelease(pDevExt->mtxTracer); + + pDevExt->pTracerOps->pfnTracerClose(pDevExt->pTracerOps, pSession, uTracerData); + rc = VINF_SUCCESS; + + RTSemFastMutexRequest(pDevExt->mtxTracer); + pSession->hTracerCaller = NIL_RTNATIVETHREAD; + Assert(pDevExt->cTracerOpens > 0); + pDevExt->cTracerOpens--; + } + else + rc = VERR_SUPDRV_TRACER_SESSION_BUSY; + } + else + { + rc = VERR_SUPDRV_TRACER_NOT_PRESENT; + pSession->uTracerData = 0; + Assert(pDevExt->cTracerOpens > 0); + pDevExt->cTracerOpens--; + } + } + else + rc = VERR_SUPDRV_TRACER_NOT_OPENED; + + RTSemFastMutexRelease(pDevExt->mtxTracer); + return rc; +} + + +/** + * Performs a tracer I/O control request. + * + * @returns VBox status code. + * @param pDevExt The device extension structure. + * @param pSession The current session. + * @param uCmd The tracer command. + * @param uArg The tracer argument. + * @param piRetVal Where to store the tracer specific return value. + */ +int VBOXCALL supdrvIOCtl_TracerIOCtl(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, uintptr_t uCmd, uintptr_t uArg, int32_t *piRetVal) +{ + RTNATIVETHREAD hNativeSelf = RTThreadNativeSelf(); + int rc; + + *piRetVal = 0; + RTSemFastMutexRequest(pDevExt->mtxTracer); + + if (pSession->uTracerData) + { + Assert(pDevExt->cTracerOpens > 0); + if (pDevExt->pTracerOps) + { + if (!pDevExt->fTracerUnloading) + { + if (pSession->hTracerCaller == NIL_RTNATIVETHREAD) + { + uintptr_t uTracerData = pSession->uTracerData; + pDevExt->cTracerOpens++; + pSession->hTracerCaller = hNativeSelf; + RTSemFastMutexRelease(pDevExt->mtxTracer); + + rc = pDevExt->pTracerOps->pfnTracerIoCtl(pDevExt->pTracerOps, pSession, uTracerData, uCmd, uArg, piRetVal); + + RTSemFastMutexRequest(pDevExt->mtxTracer); + pSession->hTracerCaller = NIL_RTNATIVETHREAD; + Assert(pDevExt->cTracerOpens > 0); + pDevExt->cTracerOpens--; + } + else + rc = VERR_SUPDRV_TRACER_SESSION_BUSY; + } + else + rc = VERR_SUPDRV_TRACER_UNLOADING; + } + else + rc = VERR_SUPDRV_TRACER_NOT_PRESENT; + } + else + rc = VERR_SUPDRV_TRACER_NOT_OPENED; + + RTSemFastMutexRelease(pDevExt->mtxTracer); + return rc; +} + + +/** + * Early module initialization hook. + * + * @returns VBox status code. + * @param pDevExt The device extension structure. + */ +int VBOXCALL supdrvTracerInit(PSUPDRVDEVEXT pDevExt) +{ + /* + * Initialize the tracer. + */ + int rc = RTSemFastMutexCreate(&pDevExt->mtxTracer); + if (RT_SUCCESS(rc)) + { + uint32_t i; + + pDevExt->TracerHlp.uVersion = SUPDRVTRACERHLP_VERSION; + /** @todo */ + pDevExt->TracerHlp.uEndVersion = SUPDRVTRACERHLP_VERSION; + RTListInit(&pDevExt->TracerProviderList); + RTListInit(&pDevExt->TracerProviderZombieList); + for (i = 0; i < RT_ELEMENTS(pDevExt->aTrackerUmodHash); i++) + RTListInit(&pDevExt->aTrackerUmodHash[i]); + +#ifdef VBOX_WITH_NATIVE_DTRACE + pDevExt->pTracerOps = supdrvDTraceInit(); + if (pDevExt->pTracerOps) + g_pfnSupdrvProbeFireKernel = (PFNRT)pDevExt->pTracerOps->pfnProbeFireKernel; +#endif + + /* + * Register the provider for this module, if compiled in. + */ +#ifdef VBOX_WITH_DTRACE_R0DRV + rc = supdrvTracerRegisterVtgObj(pDevExt, &g_VTGObjHeader, NULL /*pImage*/, NULL /*pSession*/, NULL /*pUmod*/, "vboxdrv"); + if (RT_SUCCESS(rc)) + return rc; + SUPR0Printf("supdrvTracerInit: supdrvTracerRegisterVtgObj failed with rc=%d\n", rc); + RTSemFastMutexDestroy(pDevExt->mtxTracer); +#else + + return VINF_SUCCESS; +#endif + } + pDevExt->mtxTracer = NIL_RTSEMFASTMUTEX; + return rc; +} + + +/** + * Late module termination hook. + * + * @param pDevExt The device extension structure. + */ +void VBOXCALL supdrvTracerTerm(PSUPDRVDEVEXT pDevExt) +{ + LOG_TRACER(("supdrvTracerTerm\n")); + + supdrvTracerRemoveAllProviders(pDevExt); +#ifdef VBOX_WITH_NATIVE_DTRACE + supdrvDTraceFini(); +#endif + RTSemFastMutexDestroy(pDevExt->mtxTracer); + pDevExt->mtxTracer = NIL_RTSEMFASTMUTEX; + LOG_TRACER(("supdrvTracerTerm: Done\n")); +} + diff --git a/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm new file mode 100644 index 00000000..58fe94ea --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm @@ -0,0 +1,65 @@ +; $Id: SUPDrvTracerA.asm $ +;; @file +; VirtualBox Support Driver - Tracer Interface, Assembly bits. +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + + +; External data. +extern NAME(g_pfnSupdrvProbeFireKernel) + + +BEGINCODE + +;; Dummy stub function that just returns. +BEGINPROC supdrvTracerProbeFireStub + ret +ENDPROC supdrvTracerProbeFireStub + + +;; Tail jump function. +EXPORTEDNAME SUPR0TracerFireProbe +%ifdef RT_ARCH_AMD64 + mov rax, [NAME(g_pfnSupdrvProbeFireKernel) wrt rip] + jmp rax +%else + mov eax, [NAME(g_pfnSupdrvProbeFireKernel)] + jmp eax +%endif +ENDPROC SUPR0TracerFireProbe + diff --git a/src/VBox/HostDrivers/Support/SUPLib.cpp b/src/VBox/HostDrivers/Support/SUPLib.cpp new file mode 100644 index 00000000..8f5835ff --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLib.cpp @@ -0,0 +1,2438 @@ +/* $Id: SUPLib.cpp $ */ +/** @file + * VirtualBox Support Library - Common code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_sup SUP - The Support Library + * + * The support library is responsible for providing facilities to load + * VMM Host Ring-0 code, to call Host VMM Ring-0 code from Ring-3 Host + * code, to pin down physical memory, and more. + * + * The VMM Host Ring-0 code can be combined in the support driver if + * permitted by kernel module license policies. If it is not combined + * it will be externalized in a .r0 module that will be loaded using + * the IPRT loader. + * + * The Ring-0 calling is done thru a generic SUP interface which will + * transfer an argument set and call a predefined entry point in the Host + * VMM Ring-0 code. + * + * See @ref grp_sup "SUP - Support APIs" for API details. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <VBox/sup.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/log.h> +#include <VBox/VBoxTpG.h> + +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/alloca.h> +#include <iprt/ldr.h> +#include <iprt/asm.h> +#include <iprt/mp.h> +#include <iprt/cpuset.h> +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/env.h> +#include <iprt/rand.h> +#include <iprt/x86.h> + +#include "SUPDrvIOC.h" +#include "SUPLibInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** R0 VMM module name. */ +#define VMMR0_NAME "VMMR0" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef DECLCALLBACKTYPE(int, FNCALLVMMR0,(PVMR0 pVMR0, unsigned uOperation, void *pvArg)); +typedef FNCALLVMMR0 *PFNCALLVMMR0; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init counter. */ +static uint32_t g_cInits = 0; +/** Whether we've been preinitied. */ +static bool g_fPreInited = false; +/** The SUPLib instance data. + * Well, at least parts of it, specifically the parts that are being handed over + * via the pre-init mechanism from the hardened executable stub. */ +DECL_HIDDEN_DATA(SUPLIBDATA) g_supLibData = +{ + /*.hDevice = */ SUP_HDEVICE_NIL, + /*.fUnrestricted = */ true, + /*.fDriverless = */ false +#if defined(RT_OS_DARWIN) + ,/* .uConnection = */ 0 +#elif defined(RT_OS_LINUX) + ,/* .fSysMadviseWorks = */ false +#endif +}; + +/** Pointer to the Global Information Page. + * + * This pointer is valid as long as SUPLib has a open session. Anyone using + * the page must treat this pointer as highly volatile and not trust it beyond + * one transaction. + * + * @todo This will probably deserve it's own session or some other good solution... + */ +DECLEXPORT(PSUPGLOBALINFOPAGE) g_pSUPGlobalInfoPage; +/** Address of the ring-0 mapping of the GIP. */ +PSUPGLOBALINFOPAGE g_pSUPGlobalInfoPageR0; +/** The physical address of the GIP. */ +static RTHCPHYS g_HCPhysSUPGlobalInfoPage = NIL_RTHCPHYS; + +/** The negotiated cookie. */ +DECL_HIDDEN_DATA(uint32_t) g_u32Cookie = 0; +/** The negotiated session cookie. */ +DECL_HIDDEN_DATA(uint32_t) g_u32SessionCookie; +/** The session version. */ +DECL_HIDDEN_DATA(uint32_t) g_uSupSessionVersion = 0; +/** Session handle. */ +DECL_HIDDEN_DATA(PSUPDRVSESSION) g_pSession; +/** R0 SUP Functions used for resolving referenced to the SUPR0 module. */ +DECL_HIDDEN_DATA(PSUPQUERYFUNCS) g_pSupFunctions; + +/** PAGE_ALLOC_EX sans kernel mapping support indicator. */ +static bool g_fSupportsPageAllocNoKernel = true; +/** Fake mode indicator. (~0 at first, 0 or 1 after first test) */ +DECL_HIDDEN_DATA(uint32_t) g_uSupFakeMode = UINT32_MAX; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int supInitFake(PSUPDRVSESSION *ppSession); + + +/** Touch a range of pages. */ +DECLINLINE(void) supR3TouchPages(void *pv, size_t cPages) +{ + uint32_t volatile *pu32 = (uint32_t volatile *)pv; + while (cPages-- > 0) + { + ASMAtomicCmpXchgU32(pu32, 0, 0); + pu32 += PAGE_SIZE / sizeof(uint32_t); + } +} + + +SUPR3DECL(int) SUPR3Install(void) +{ + return suplibOsInstall(); +} + + +SUPR3DECL(int) SUPR3Uninstall(void) +{ + return suplibOsUninstall(); +} + + +DECL_NOTHROW(DECLEXPORT(int)) supR3PreInit(PSUPPREINITDATA pPreInitData, uint32_t fFlags) +{ + /* + * The caller is kind of trustworthy, just perform some basic checks. + * + * Note! Do not do any fancy stuff here because IPRT has NOT been + * initialized at this point. + */ + if (!RT_VALID_PTR(pPreInitData)) + return VERR_INVALID_POINTER; + if (g_fPreInited || g_cInits > 0) + return VERR_WRONG_ORDER; + + if ( pPreInitData->u32Magic != SUPPREINITDATA_MAGIC + || pPreInitData->u32EndMagic != SUPPREINITDATA_MAGIC) + return VERR_INVALID_MAGIC; + if ( !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + && pPreInitData->Data.hDevice == SUP_HDEVICE_NIL + && !pPreInitData->Data.fDriverless) + return VERR_INVALID_HANDLE; + if ( ( (fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + || pPreInitData->Data.fDriverless) + && pPreInitData->Data.hDevice != SUP_HDEVICE_NIL) + return VERR_INVALID_PARAMETER; + + /* + * Hand out the data. + */ + int rc = supR3HardenedRecvPreInitData(pPreInitData); + if (RT_FAILURE(rc)) + return rc; + + /** @todo This may need some small restructuring later, it doesn't quite work with a root service flag... */ + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + { + g_supLibData = pPreInitData->Data; + g_fPreInited = true; + } + + return VINF_SUCCESS; +} + + +SUPR3DECL(int) SUPR3InitEx(uint32_t fFlags, PSUPDRVSESSION *ppSession) +{ + /* + * Perform some sanity checks. + * (Got some trouble with compile time member alignment assertions.) + */ + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, u64NanoTSLastUpdateHz) & 0x7)); + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, aCPUs) & 0x1f)); + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, aCPUs[1]) & 0x1f)); + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, aCPUs[0].u64NanoTS) & 0x7)); + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, aCPUs[0].u64TSC) & 0x7)); + Assert(!(RT_UOFFSETOF(SUPGLOBALINFOPAGE, aCPUs[0].u64CpuHz) & 0x7)); + +#ifdef VBOX_WITH_DRIVERLESS_FORCED + fFlags |= SUPR3INIT_F_DRIVERLESS; + fFlags &= ~SUPR3INIT_F_UNRESTRICTED; +#endif + + /* + * Check if already initialized. + */ + if (ppSession) + *ppSession = g_pSession; + if (g_cInits++ > 0) + { + if ( (fFlags & SUPR3INIT_F_UNRESTRICTED) + && !g_supLibData.fUnrestricted + && !g_supLibData.fDriverless) + { + g_cInits--; + if (ppSession) + *ppSession = NIL_RTR0PTR; + return VERR_VM_DRIVER_NOT_ACCESSIBLE; /** @todo different status code? */ + } + return VINF_SUCCESS; + } + + /* + * Check for fake mode. + * + * Fake mode is used when we're doing smoke testing and debugging. + * It's also useful on platforms where we haven't root access or which + * we haven't ported the support driver to. + */ + if (g_uSupFakeMode == ~0U) + { + const char *psz = RTEnvGet("VBOX_SUPLIB_FAKE"); + if (psz && !strcmp(psz, "fake")) + ASMAtomicCmpXchgU32(&g_uSupFakeMode, 1, ~0U); + else + ASMAtomicCmpXchgU32(&g_uSupFakeMode, 0, ~0U); + } + if (RT_UNLIKELY(g_uSupFakeMode)) + return supInitFake(ppSession); + + /* + * Open the support driver. + */ + SUPINITOP enmWhat = kSupInitOp_Driver; + int rc = suplibOsInit(&g_supLibData, g_fPreInited, fFlags, &enmWhat, NULL); + if (RT_SUCCESS(rc) && !g_supLibData.fDriverless) + { + /* + * Negotiate the cookie. + */ + SUPCOOKIE CookieReq; + memset(&CookieReq, 0xff, sizeof(CookieReq)); + CookieReq.Hdr.u32Cookie = SUPCOOKIE_INITIAL_COOKIE; + CookieReq.Hdr.u32SessionCookie = RTRandU32(); + CookieReq.Hdr.cbIn = SUP_IOCTL_COOKIE_SIZE_IN; + CookieReq.Hdr.cbOut = SUP_IOCTL_COOKIE_SIZE_OUT; + CookieReq.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + CookieReq.Hdr.rc = VERR_INTERNAL_ERROR; + strcpy(CookieReq.u.In.szMagic, SUPCOOKIE_MAGIC); + CookieReq.u.In.u32ReqVersion = SUPDRV_IOC_VERSION; + const uint32_t uMinVersion = (SUPDRV_IOC_VERSION & 0xffff0000) == 0x00330000 + ? 0x00330004 + : SUPDRV_IOC_VERSION & 0xffff0000; + CookieReq.u.In.u32MinVersion = uMinVersion; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_COOKIE, &CookieReq, SUP_IOCTL_COOKIE_SIZE); + if ( RT_SUCCESS(rc) + && RT_SUCCESS(CookieReq.Hdr.rc)) + { + g_uSupSessionVersion = CookieReq.u.Out.u32SessionVersion; + if ( (CookieReq.u.Out.u32SessionVersion & 0xffff0000) == (SUPDRV_IOC_VERSION & 0xffff0000) + && CookieReq.u.Out.u32SessionVersion >= uMinVersion) + { + /* + * Query the functions. + */ + PSUPQUERYFUNCS pFuncsReq = NULL; + if (g_supLibData.fUnrestricted) + { + pFuncsReq = (PSUPQUERYFUNCS)RTMemAllocZ(SUP_IOCTL_QUERY_FUNCS_SIZE(CookieReq.u.Out.cFunctions)); + if (pFuncsReq) + { + pFuncsReq->Hdr.u32Cookie = CookieReq.u.Out.u32Cookie; + pFuncsReq->Hdr.u32SessionCookie = CookieReq.u.Out.u32SessionCookie; + pFuncsReq->Hdr.cbIn = SUP_IOCTL_QUERY_FUNCS_SIZE_IN; + pFuncsReq->Hdr.cbOut = SUP_IOCTL_QUERY_FUNCS_SIZE_OUT(CookieReq.u.Out.cFunctions); + pFuncsReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pFuncsReq->Hdr.rc = VERR_INTERNAL_ERROR; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_QUERY_FUNCS(CookieReq.u.Out.cFunctions), pFuncsReq, + SUP_IOCTL_QUERY_FUNCS_SIZE(CookieReq.u.Out.cFunctions)); + if (RT_SUCCESS(rc)) + rc = pFuncsReq->Hdr.rc; + if (RT_SUCCESS(rc)) + { + /* + * Map the GIP into userspace. + */ + Assert(!g_pSUPGlobalInfoPage); + SUPGIPMAP GipMapReq; + GipMapReq.Hdr.u32Cookie = CookieReq.u.Out.u32Cookie; + GipMapReq.Hdr.u32SessionCookie = CookieReq.u.Out.u32SessionCookie; + GipMapReq.Hdr.cbIn = SUP_IOCTL_GIP_MAP_SIZE_IN; + GipMapReq.Hdr.cbOut = SUP_IOCTL_GIP_MAP_SIZE_OUT; + GipMapReq.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + GipMapReq.Hdr.rc = VERR_INTERNAL_ERROR; + GipMapReq.u.Out.HCPhysGip = NIL_RTHCPHYS; + GipMapReq.u.Out.pGipR0 = NIL_RTR0PTR; + GipMapReq.u.Out.pGipR3 = NULL; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_GIP_MAP, &GipMapReq, SUP_IOCTL_GIP_MAP_SIZE); + if (RT_SUCCESS(rc)) + rc = GipMapReq.Hdr.rc; + if (RT_SUCCESS(rc)) + { + /* + * Set the GIP globals. + */ + AssertRelease(GipMapReq.u.Out.pGipR3->u32Magic == SUPGLOBALINFOPAGE_MAGIC); + AssertRelease(GipMapReq.u.Out.pGipR3->u32Version >= SUPGLOBALINFOPAGE_VERSION); + + ASMAtomicXchgSize(&g_HCPhysSUPGlobalInfoPage, GipMapReq.u.Out.HCPhysGip); + ASMAtomicCmpXchgPtr((void * volatile *)&g_pSUPGlobalInfoPage, GipMapReq.u.Out.pGipR3, NULL); + ASMAtomicCmpXchgPtr((void * volatile *)&g_pSUPGlobalInfoPageR0, (void *)GipMapReq.u.Out.pGipR0, NULL); + } + } + } + else + rc = VERR_NO_MEMORY; + } + + if (RT_SUCCESS(rc)) + { + /* + * Set the globals and return success. + */ + g_u32Cookie = CookieReq.u.Out.u32Cookie; + g_u32SessionCookie = CookieReq.u.Out.u32SessionCookie; + g_pSession = CookieReq.u.Out.pSession; + g_pSupFunctions = pFuncsReq; + if (ppSession) + *ppSession = CookieReq.u.Out.pSession; + return VINF_SUCCESS; + } + + /* bailout */ + RTMemFree(pFuncsReq); + } + else + { + LogRel(("Support driver version mismatch: SessionVersion=%#x DriverVersion=%#x ClientVersion=%#x MinVersion=%#x\n", + CookieReq.u.Out.u32SessionVersion, CookieReq.u.Out.u32DriverVersion, SUPDRV_IOC_VERSION, uMinVersion)); + rc = VERR_VM_DRIVER_VERSION_MISMATCH; + } + } + else + { + if (RT_SUCCESS(rc)) + { + rc = CookieReq.Hdr.rc; + LogRel(("Support driver version mismatch: DriverVersion=%#x ClientVersion=%#x rc=%Rrc\n", + CookieReq.u.Out.u32DriverVersion, SUPDRV_IOC_VERSION, rc)); + if (rc != VERR_VM_DRIVER_VERSION_MISMATCH) + rc = VERR_VM_DRIVER_VERSION_MISMATCH; + } + else + { + /* for pre 0x00060000 drivers */ + LogRel(("Support driver version mismatch: DriverVersion=too-old ClientVersion=%#x\n", SUPDRV_IOC_VERSION)); + rc = VERR_VM_DRIVER_VERSION_MISMATCH; + } + } + + suplibOsTerm(&g_supLibData); + } + else if (RT_SUCCESS(rc)) + { + /* + * Driverless initialization. + */ + Assert(fFlags & SUPR3INIT_F_DRIVERLESS_MASK); + LogRel(("SUP: In driverless mode.\n")); + return VINF_SUCCESS; + } + + g_cInits--; + + return rc; +} + + +SUPR3DECL(int) SUPR3Init(PSUPDRVSESSION *ppSession) +{ +#ifndef VBOX_WITH_DRIVERLESS_FORCED + return SUPR3InitEx(SUPR3INIT_F_UNRESTRICTED, ppSession); +#else + return SUPR3InitEx(SUPR3INIT_F_DRIVERLESS, ppSession); +#endif +} + +/** + * Fake mode init. + */ +static int supInitFake(PSUPDRVSESSION *ppSession) +{ + Log(("SUP: Fake mode!\n")); + static const SUPFUNC s_aFakeFunctions[] = + { + /* name 0, function */ + { "SUPR0AbsIs64bit", 0, 0 }, + { "SUPR0Abs64bitKernelCS", 0, 0 }, + { "SUPR0Abs64bitKernelSS", 0, 0 }, + { "SUPR0Abs64bitKernelDS", 0, 0 }, + { "SUPR0AbsKernelCS", 0, 8 }, + { "SUPR0AbsKernelSS", 0, 16 }, + { "SUPR0AbsKernelDS", 0, 16 }, + { "SUPR0AbsKernelES", 0, 16 }, + { "SUPR0AbsKernelFS", 0, 24 }, + { "SUPR0AbsKernelGS", 0, 32 }, + { "SUPR0ComponentRegisterFactory", 0, 0xefeefffd }, + { "SUPR0ComponentDeregisterFactory", 0, 0xefeefffe }, + { "SUPR0ComponentQueryFactory", 0, 0xefeeffff }, + { "SUPR0ObjRegister", 0, 0xefef0000 }, + { "SUPR0ObjAddRef", 0, 0xefef0001 }, + { "SUPR0ObjAddRefEx", 0, 0xefef0001 }, + { "SUPR0ObjRelease", 0, 0xefef0002 }, + { "SUPR0ObjVerifyAccess", 0, 0xefef0003 }, + { "SUPR0LockMem", 0, 0xefef0004 }, + { "SUPR0UnlockMem", 0, 0xefef0005 }, + { "SUPR0ContAlloc", 0, 0xefef0006 }, + { "SUPR0ContFree", 0, 0xefef0007 }, + { "SUPR0MemAlloc", 0, 0xefef0008 }, + { "SUPR0MemGetPhys", 0, 0xefef0009 }, + { "SUPR0MemFree", 0, 0xefef000a }, + { "SUPR0Printf", 0, 0xefef000b }, + { "SUPR0GetPagingMode", 0, 0xefef000c }, + { "SUPR0EnableVTx", 0, 0xefef000e }, + { "RTMemAlloc", 0, 0xefef000f }, + { "RTMemAllocZ", 0, 0xefef0010 }, + { "RTMemFree", 0, 0xefef0011 }, + { "RTR0MemObjAddress", 0, 0xefef0012 }, + { "RTR0MemObjAddressR3", 0, 0xefef0013 }, + { "RTR0MemObjAllocPage", 0, 0xefef0014 }, + { "RTR0MemObjAllocPhysNC", 0, 0xefef0015 }, + { "RTR0MemObjAllocLow", 0, 0xefef0016 }, + { "RTR0MemObjEnterPhys", 0, 0xefef0017 }, + { "RTR0MemObjFree", 0, 0xefef0018 }, + { "RTR0MemObjGetPagePhysAddr", 0, 0xefef0019 }, + { "RTR0MemObjMapUser", 0, 0xefef001a }, + { "RTR0MemObjMapKernel", 0, 0xefef001b }, + { "RTR0MemObjMapKernelEx", 0, 0xefef001c }, + { "RTMpGetArraySize", 0, 0xefef001c }, + { "RTProcSelf", 0, 0xefef001d }, + { "RTR0ProcHandleSelf", 0, 0xefef001e }, + { "RTSemEventCreate", 0, 0xefef001f }, + { "RTSemEventSignal", 0, 0xefef0020 }, + { "RTSemEventWait", 0, 0xefef0021 }, + { "RTSemEventWaitNoResume", 0, 0xefef0022 }, + { "RTSemEventDestroy", 0, 0xefef0023 }, + { "RTSemEventMultiCreate", 0, 0xefef0024 }, + { "RTSemEventMultiSignal", 0, 0xefef0025 }, + { "RTSemEventMultiReset", 0, 0xefef0026 }, + { "RTSemEventMultiWait", 0, 0xefef0027 }, + { "RTSemEventMultiWaitNoResume", 0, 0xefef0028 }, + { "RTSemEventMultiDestroy", 0, 0xefef0029 }, + { "RTSemFastMutexCreate", 0, 0xefef002a }, + { "RTSemFastMutexDestroy", 0, 0xefef002b }, + { "RTSemFastMutexRequest", 0, 0xefef002c }, + { "RTSemFastMutexRelease", 0, 0xefef002d }, + { "RTSpinlockCreate", 0, 0xefef002e }, + { "RTSpinlockDestroy", 0, 0xefef002f }, + { "RTSpinlockAcquire", 0, 0xefef0030 }, + { "RTSpinlockRelease", 0, 0xefef0031 }, + { "RTSpinlockAcquireNoInts", 0, 0xefef0032 }, + { "RTTimeNanoTS", 0, 0xefef0034 }, + { "RTTimeMillieTS", 0, 0xefef0035 }, + { "RTTimeSystemNanoTS", 0, 0xefef0036 }, + { "RTTimeSystemMillieTS", 0, 0xefef0037 }, + { "RTThreadNativeSelf", 0, 0xefef0038 }, + { "RTThreadSleep", 0, 0xefef0039 }, + { "RTThreadYield", 0, 0xefef003a }, + { "RTTimerCreate", 0, 0xefef003a }, + { "RTTimerCreateEx", 0, 0xefef003a }, + { "RTTimerDestroy", 0, 0xefef003a }, + { "RTTimerStart", 0, 0xefef003a }, + { "RTTimerStop", 0, 0xefef003a }, + { "RTTimerChangeInterval", 0, 0xefef003a }, + { "RTTimerGetSystemGranularity", 0, 0xefef003a }, + { "RTTimerRequestSystemGranularity", 0, 0xefef003a }, + { "RTTimerReleaseSystemGranularity", 0, 0xefef003a }, + { "RTTimerCanDoHighResolution", 0, 0xefef003a }, + { "RTLogDefaultInstance", 0, 0xefef003b }, + { "RTLogRelGetDefaultInstance", 0, 0xefef003c }, + { "RTLogSetDefaultInstanceThread", 0, 0xefef003d }, + { "RTLogLogger", 0, 0xefef003e }, + { "RTLogLoggerEx", 0, 0xefef003f }, + { "RTLogLoggerExV", 0, 0xefef0040 }, + { "RTAssertMsg1", 0, 0xefef0041 }, + { "RTAssertMsg2", 0, 0xefef0042 }, + { "RTAssertMsg2V", 0, 0xefef0043 }, + { "SUPR0QueryVTCaps", 0, 0xefef0044 }, + }; + + /* fake r0 functions. */ + g_pSupFunctions = (PSUPQUERYFUNCS)RTMemAllocZ(SUP_IOCTL_QUERY_FUNCS_SIZE(RT_ELEMENTS(s_aFakeFunctions))); + if (g_pSupFunctions) + { + g_pSupFunctions->u.Out.cFunctions = RT_ELEMENTS(s_aFakeFunctions); + memcpy(&g_pSupFunctions->u.Out.aFunctions[0], &s_aFakeFunctions[0], sizeof(s_aFakeFunctions)); + g_pSession = (PSUPDRVSESSION)(void *)g_pSupFunctions; + if (ppSession) + *ppSession = g_pSession; + + /* fake the GIP. */ + g_pSUPGlobalInfoPage = (PSUPGLOBALINFOPAGE)RTMemPageAllocZ(PAGE_SIZE); + if (g_pSUPGlobalInfoPage) + { + g_pSUPGlobalInfoPageR0 = g_pSUPGlobalInfoPage; + g_HCPhysSUPGlobalInfoPage = NIL_RTHCPHYS & ~(RTHCPHYS)PAGE_OFFSET_MASK; + /* the page is supposed to be invalid, so don't set the magic. */ + return VINF_SUCCESS; + } + + RTMemFree(g_pSupFunctions); + g_pSupFunctions = NULL; + } + return VERR_NO_MEMORY; +} + + +SUPR3DECL(int) SUPR3Term(bool fForced) +{ + /* + * Verify state. + */ + AssertMsg(g_cInits > 0, ("SUPR3Term() is called before SUPR3Init()!\n")); + if (g_cInits == 0) + return VERR_WRONG_ORDER; + if (g_cInits == 1 || fForced) + { + /* + * NULL the GIP pointer. + */ + if (g_pSUPGlobalInfoPage) + { + ASMAtomicWriteNullPtr((void * volatile *)&g_pSUPGlobalInfoPage); + ASMAtomicWriteNullPtr((void * volatile *)&g_pSUPGlobalInfoPageR0); + ASMAtomicWriteU64(&g_HCPhysSUPGlobalInfoPage, NIL_RTHCPHYS); + /* just a little safe guard against threads using the page. */ + RTThreadSleep(50); + } + + /* + * Close the support driver. + */ + int rc = suplibOsTerm(&g_supLibData); + if (rc) + return rc; + + g_supLibData.hDevice = SUP_HDEVICE_NIL; + g_supLibData.fUnrestricted = true; + g_supLibData.fDriverless = false; + g_u32Cookie = 0; + g_u32SessionCookie = 0; + g_cInits = 0; + } + else + g_cInits--; + + return 0; +} + + +SUPR3DECL(bool) SUPR3IsDriverless(void) +{ + /* Assert(g_cInits > 0); - tstSSM does not initialize SUP, but SSM calls to + check status, so return driverless if not initialized. */ + return g_supLibData.fDriverless || g_cInits == 0; +} + + +SUPR3DECL(SUPPAGINGMODE) SUPR3GetPagingMode(void) +{ + /* + * Deal with driverless first. + */ + if (g_supLibData.fDriverless) +#if defined(RT_ARCH_AMD64) + return SUPPAGINGMODE_AMD64_GLOBAL_NX; +#elif defined(RT_ARCH_X86) + return SUPPAGINGMODE_32_BIT_GLOBAL; +#else + return SUPPAGINGMODE_INVALID; +#endif + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPGETPAGINGMODE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_GET_PAGING_MODE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_GET_PAGING_MODE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_GET_PAGING_MODE, &Req, SUP_IOCTL_GET_PAGING_MODE_SIZE); + if ( RT_FAILURE(rc) + || RT_FAILURE(Req.Hdr.rc)) + { + LogRel(("SUPR3GetPagingMode: %Rrc %Rrc\n", rc, Req.Hdr.rc)); + Req.u.Out.enmMode = SUPPAGINGMODE_INVALID; + } + + return Req.u.Out.enmMode; +} + + +/** + * For later. + */ +static int supCallVMMR0ExFake(PVMR0 pVMR0, unsigned uOperation, uint64_t u64Arg, PSUPVMMR0REQHDR pReqHdr) +{ + AssertMsgFailed(("%d\n", uOperation)); NOREF(pVMR0); NOREF(uOperation); NOREF(u64Arg); NOREF(pReqHdr); + return VERR_NOT_SUPPORTED; +} + + +SUPR3DECL(int) SUPR3CallVMMR0Fast(PVMR0 pVMR0, unsigned uOperation, VMCPUID idCpu) +{ + NOREF(pVMR0); + static const uintptr_t s_auFunctions[3] = + { + SUP_IOCTL_FAST_DO_HM_RUN, + SUP_IOCTL_FAST_DO_NEM_RUN, + SUP_IOCTL_FAST_DO_NOP, + }; + AssertCompile(SUP_VMMR0_DO_HM_RUN == 0); + AssertCompile(SUP_VMMR0_DO_NEM_RUN == 1); + AssertCompile(SUP_VMMR0_DO_NOP == 2); + AssertMsgReturn(uOperation < RT_ELEMENTS(s_auFunctions), ("%#x\n", uOperation), VERR_INTERNAL_ERROR); + return suplibOsIOCtlFast(&g_supLibData, s_auFunctions[uOperation], idCpu); +} + + +SUPR3DECL(int) SUPR3CallVMMR0Ex(PVMR0 pVMR0, VMCPUID idCpu, unsigned uOperation, uint64_t u64Arg, PSUPVMMR0REQHDR pReqHdr) +{ + /* + * The following operations don't belong here. + */ + AssertMsgReturn( uOperation != SUP_VMMR0_DO_HM_RUN + && uOperation != SUP_VMMR0_DO_NEM_RUN + && uOperation != SUP_VMMR0_DO_NOP, + ("%#x\n", uOperation), + VERR_INTERNAL_ERROR); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return supCallVMMR0ExFake(pVMR0, uOperation, u64Arg, pReqHdr); + + int rc; + if (!pReqHdr) + { + /* no data. */ + SUPCALLVMMR0 Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_CALL_VMMR0_SIZE_IN(0); + Req.Hdr.cbOut = SUP_IOCTL_CALL_VMMR0_SIZE_OUT(0); + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pVMR0 = pVMR0; + Req.u.In.idCpu = idCpu; + Req.u.In.uOperation = uOperation; + Req.u.In.u64Arg = u64Arg; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CALL_VMMR0(0), &Req, SUP_IOCTL_CALL_VMMR0_SIZE(0)); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + } + else if (SUP_IOCTL_CALL_VMMR0_SIZE(pReqHdr->cbReq) < _4K) /* FreeBSD won't copy more than 4K. */ + { + AssertPtrReturn(pReqHdr, VERR_INVALID_POINTER); + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + const size_t cbReq = pReqHdr->cbReq; + + PSUPCALLVMMR0 pReq = (PSUPCALLVMMR0)alloca(SUP_IOCTL_CALL_VMMR0_SIZE(cbReq)); + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_CALL_VMMR0_SIZE_IN(cbReq); + pReq->Hdr.cbOut = SUP_IOCTL_CALL_VMMR0_SIZE_OUT(cbReq); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.pVMR0 = pVMR0; + pReq->u.In.idCpu = idCpu; + pReq->u.In.uOperation = uOperation; + pReq->u.In.u64Arg = u64Arg; + memcpy(&pReq->abReqPkt[0], pReqHdr, cbReq); + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CALL_VMMR0(cbReq), pReq, SUP_IOCTL_CALL_VMMR0_SIZE(cbReq)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + memcpy(pReqHdr, &pReq->abReqPkt[0], cbReq); + } + else if (pReqHdr->cbReq <= _512K) + { + AssertPtrReturn(pReqHdr, VERR_INVALID_POINTER); + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + const size_t cbReq = pReqHdr->cbReq; + + PSUPCALLVMMR0 pReq = (PSUPCALLVMMR0)RTMemTmpAlloc(SUP_IOCTL_CALL_VMMR0_BIG_SIZE(cbReq)); + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_CALL_VMMR0_BIG_SIZE_IN(cbReq); + pReq->Hdr.cbOut = SUP_IOCTL_CALL_VMMR0_BIG_SIZE_OUT(cbReq); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.pVMR0 = pVMR0; + pReq->u.In.idCpu = idCpu; + pReq->u.In.uOperation = uOperation; + pReq->u.In.u64Arg = u64Arg; + memcpy(&pReq->abReqPkt[0], pReqHdr, cbReq); + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CALL_VMMR0_BIG, pReq, SUP_IOCTL_CALL_VMMR0_BIG_SIZE(cbReq)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + memcpy(pReqHdr, &pReq->abReqPkt[0], cbReq); + RTMemTmpFree(pReq); + } + else + AssertMsgFailedReturn(("cbReq=%#x\n", pReqHdr->cbReq), VERR_OUT_OF_RANGE); + return rc; +} + + +SUPR3DECL(int) SUPR3CallVMMR0(PVMR0 pVMR0, VMCPUID idCpu, unsigned uOperation, void *pvArg) +{ + /* + * The following operations don't belong here. + */ + AssertMsgReturn( uOperation != SUP_VMMR0_DO_HM_RUN + && uOperation != SUP_VMMR0_DO_NEM_RUN + && uOperation != SUP_VMMR0_DO_NOP, + ("%#x\n", uOperation), + VERR_INTERNAL_ERROR); + return SUPR3CallVMMR0Ex(pVMR0, idCpu, uOperation, (uintptr_t)pvArg, NULL); +} + + +SUPR3DECL(int) SUPR3SetVMForFastIOCtl(PVMR0 pVMR0) +{ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + SUPSETVMFORFAST Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_SET_VM_FOR_FAST_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_SET_VM_FOR_FAST_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pVMR0 = pVMR0; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_SET_VM_FOR_FAST, &Req, SUP_IOCTL_SET_VM_FOR_FAST_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3CallR0Service(const char *pszService, size_t cchService, uint32_t uOperation, uint64_t u64Arg, PSUPR0SERVICEREQHDR pReqHdr) +{ + AssertReturn(cchService < RT_SIZEOFMEMB(SUPCALLSERVICE, u.In.szName), VERR_INVALID_PARAMETER); + Assert(strlen(pszService) == cchService); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VERR_NOT_SUPPORTED; + + int rc; + if (!pReqHdr) + { + /* no data. */ + SUPCALLSERVICE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_CALL_SERVICE_SIZE_IN(0); + Req.Hdr.cbOut = SUP_IOCTL_CALL_SERVICE_SIZE_OUT(0); + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + memcpy(Req.u.In.szName, pszService, cchService); + Req.u.In.szName[cchService] = '\0'; + Req.u.In.uOperation = uOperation; + Req.u.In.u64Arg = u64Arg; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CALL_SERVICE(0), &Req, SUP_IOCTL_CALL_SERVICE_SIZE(0)); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + } + else if (SUP_IOCTL_CALL_SERVICE_SIZE(pReqHdr->cbReq) < _4K) /* FreeBSD won't copy more than 4K. */ + { + AssertPtrReturn(pReqHdr, VERR_INVALID_POINTER); + AssertReturn(pReqHdr->u32Magic == SUPR0SERVICEREQHDR_MAGIC, VERR_INVALID_MAGIC); + const size_t cbReq = pReqHdr->cbReq; + + PSUPCALLSERVICE pReq = (PSUPCALLSERVICE)alloca(SUP_IOCTL_CALL_SERVICE_SIZE(cbReq)); + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_CALL_SERVICE_SIZE_IN(cbReq); + pReq->Hdr.cbOut = SUP_IOCTL_CALL_SERVICE_SIZE_OUT(cbReq); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + memcpy(pReq->u.In.szName, pszService, cchService); + pReq->u.In.szName[cchService] = '\0'; + pReq->u.In.uOperation = uOperation; + pReq->u.In.u64Arg = u64Arg; + memcpy(&pReq->abReqPkt[0], pReqHdr, cbReq); + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CALL_SERVICE(cbReq), pReq, SUP_IOCTL_CALL_SERVICE_SIZE(cbReq)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + memcpy(pReqHdr, &pReq->abReqPkt[0], cbReq); + } + else /** @todo may have to remove the size limits one this request... */ + AssertMsgFailedReturn(("cbReq=%#x\n", pReqHdr->cbReq), VERR_INTERNAL_ERROR); + return rc; +} + + +/** + * Worker for the SUPR3Logger* APIs. + * + * @returns VBox status code. + * @param enmWhich Which logger. + * @param fWhat What to do with the logger. + * @param pszFlags The flags settings. + * @param pszGroups The groups settings. + * @param pszDest The destination specificier. + */ +static int supR3LoggerSettings(SUPLOGGER enmWhich, uint32_t fWhat, const char *pszFlags, const char *pszGroups, const char *pszDest) +{ + uint32_t const cchFlags = pszFlags ? (uint32_t)strlen(pszFlags) : 0; + uint32_t const cchGroups = pszGroups ? (uint32_t)strlen(pszGroups) : 0; + uint32_t const cchDest = pszDest ? (uint32_t)strlen(pszDest) : 0; + uint32_t const cbStrTab = cchFlags + !!cchFlags + + cchGroups + !!cchGroups + + cchDest + !!cchDest + + (!cchFlags && !cchGroups && !cchDest); + + PSUPLOGGERSETTINGS pReq = (PSUPLOGGERSETTINGS)alloca(SUP_IOCTL_LOGGER_SETTINGS_SIZE(cbStrTab)); + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_LOGGER_SETTINGS_SIZE_IN(cbStrTab); + pReq->Hdr.cbOut = SUP_IOCTL_LOGGER_SETTINGS_SIZE_OUT; + pReq->Hdr.fFlags= SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + switch (enmWhich) + { + case SUPLOGGER_DEBUG: pReq->u.In.fWhich = SUPLOGGERSETTINGS_WHICH_DEBUG; break; + case SUPLOGGER_RELEASE: pReq->u.In.fWhich = SUPLOGGERSETTINGS_WHICH_RELEASE; break; + default: + return VERR_INVALID_PARAMETER; + } + pReq->u.In.fWhat = fWhat; + + uint32_t off = 0; + if (cchFlags) + { + pReq->u.In.offFlags = off; + memcpy(&pReq->u.In.szStrings[off], pszFlags, cchFlags + 1); + off += cchFlags + 1; + } + else + pReq->u.In.offFlags = cbStrTab - 1; + + if (cchGroups) + { + pReq->u.In.offGroups = off; + memcpy(&pReq->u.In.szStrings[off], pszGroups, cchGroups + 1); + off += cchGroups + 1; + } + else + pReq->u.In.offGroups = cbStrTab - 1; + + if (cchDest) + { + pReq->u.In.offDestination = off; + memcpy(&pReq->u.In.szStrings[off], pszDest, cchDest + 1); + off += cchDest + 1; + } + else + pReq->u.In.offDestination = cbStrTab - 1; + + if (!off) + { + pReq->u.In.szStrings[0] = '\0'; + off++; + } + Assert(off == cbStrTab); + Assert(pReq->u.In.szStrings[cbStrTab - 1] == '\0'); + + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LOGGER_SETTINGS(cbStrTab), pReq, SUP_IOCTL_LOGGER_SETTINGS_SIZE(cbStrTab)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3LoggerSettings(SUPLOGGER enmWhich, const char *pszFlags, const char *pszGroups, const char *pszDest) +{ + return supR3LoggerSettings(enmWhich, SUPLOGGERSETTINGS_WHAT_SETTINGS, pszFlags, pszGroups, pszDest); +} + + +SUPR3DECL(int) SUPR3LoggerCreate(SUPLOGGER enmWhich, const char *pszFlags, const char *pszGroups, const char *pszDest) +{ + return supR3LoggerSettings(enmWhich, SUPLOGGERSETTINGS_WHAT_CREATE, pszFlags, pszGroups, pszDest); +} + + +SUPR3DECL(int) SUPR3LoggerDestroy(SUPLOGGER enmWhich) +{ + return supR3LoggerSettings(enmWhich, SUPLOGGERSETTINGS_WHAT_DESTROY, NULL, NULL, NULL); +} + + +SUPR3DECL(int) SUPR3PageAlloc(size_t cPages, uint32_t fFlags, void **ppvPages) +{ + /* + * Validate. + */ + AssertPtrReturn(ppvPages, VERR_INVALID_POINTER); + *ppvPages = NULL; + AssertReturn(cPages > 0, VERR_PAGE_COUNT_OUT_OF_RANGE); + AssertReturn(!(fFlags & ~SUP_PAGE_ALLOC_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* + * Call OS specific worker. + */ + return suplibOsPageAlloc(&g_supLibData, cPages, fFlags, ppvPages); +} + + +SUPR3DECL(int) SUPR3PageFree(void *pvPages, size_t cPages) +{ + /* + * Validate. + */ + AssertPtrReturn(pvPages, VERR_INVALID_POINTER); + AssertReturn(cPages > 0, VERR_PAGE_COUNT_OUT_OF_RANGE); + + /* + * Call OS specific worker. + */ + return suplibOsPageFree(&g_supLibData, pvPages, cPages); +} + + +/** + * Locks down the physical memory backing a virtual memory + * range in the current process. + * + * @returns VBox status code. + * @param pvStart Start of virtual memory range. + * Must be page aligned. + * @param cPages Number of pages. + * @param paPages Where to store the physical page addresses returned. + * On entry this will point to an array of with cbMemory >> PAGE_SHIFT entries. + */ +SUPR3DECL(int) supR3PageLock(void *pvStart, size_t cPages, PSUPPAGE paPages) +{ + /* + * Validate. + */ + AssertPtr(pvStart); + AssertMsg(RT_ALIGN_P(pvStart, PAGE_SIZE) == pvStart, ("pvStart (%p) must be page aligned\n", pvStart)); + AssertPtr(paPages); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + RTHCPHYS Phys = (uintptr_t)pvStart + PAGE_SIZE * 1024; + size_t iPage = cPages; + while (iPage-- > 0) + paPages[iPage].Phys = Phys + (iPage << PAGE_SHIFT); + return VINF_SUCCESS; + } + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + int rc; + PSUPPAGELOCK pReq = (PSUPPAGELOCK)RTMemTmpAllocZ(SUP_IOCTL_PAGE_LOCK_SIZE(cPages)); + if (RT_LIKELY(pReq)) + { + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_PAGE_LOCK_SIZE_IN; + pReq->Hdr.cbOut = SUP_IOCTL_PAGE_LOCK_SIZE_OUT(cPages); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_MAGIC | SUPREQHDR_FLAGS_EXTRA_OUT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.pvR3 = pvStart; + pReq->u.In.cPages = (uint32_t)cPages; AssertRelease(pReq->u.In.cPages == cPages); + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_LOCK, pReq, SUP_IOCTL_PAGE_LOCK_SIZE(cPages)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + if (RT_SUCCESS(rc)) + { + for (uint32_t iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage].uReserved = 0; + paPages[iPage].Phys = pReq->u.Out.aPages[iPage]; + Assert(!(paPages[iPage].Phys & ~X86_PTE_PAE_PG_MASK)); + } + } + RTMemTmpFree(pReq); + } + else + rc = VERR_NO_TMP_MEMORY; + + return rc; +} + + +/** + * Releases locked down pages. + * + * @returns VBox status code. + * @param pvStart Start of virtual memory range previously locked + * down by SUPPageLock(). + */ +SUPR3DECL(int) supR3PageUnlock(void *pvStart) +{ + /* + * Validate. + */ + AssertPtr(pvStart); + AssertMsg(RT_ALIGN_P(pvStart, PAGE_SIZE) == pvStart, ("pvStart (%p) must be page aligned\n", pvStart)); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPPAGEUNLOCK Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_PAGE_UNLOCK_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_PAGE_UNLOCK_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pvStart; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_UNLOCK, &Req, SUP_IOCTL_PAGE_UNLOCK_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3LockDownLoader(PRTERRINFO pErrInfo) +{ + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Lock down the module loader interface. + */ + SUPREQHDR ReqHdr; + ReqHdr.u32Cookie = g_u32Cookie; + ReqHdr.u32SessionCookie = g_u32SessionCookie; + ReqHdr.cbIn = SUP_IOCTL_LDR_LOCK_DOWN_SIZE_IN; + ReqHdr.cbOut = SUP_IOCTL_LDR_LOCK_DOWN_SIZE_OUT; + ReqHdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + ReqHdr.rc = VERR_INTERNAL_ERROR; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_LOCK_DOWN, &ReqHdr, SUP_IOCTL_LDR_LOCK_DOWN_SIZE); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, + "SUPR3LockDownLoader: SUP_IOCTL_LDR_LOCK_DOWN ioctl returned %Rrc", rc); + + return ReqHdr.rc; +} + + +/** + * Fallback for SUPR3PageAllocEx on systems where RTR0MemObjPhysAllocNC isn't + * supported. + */ +static int supPagePageAllocNoKernelFallback(size_t cPages, void **ppvPages, PSUPPAGE paPages) +{ + int rc = suplibOsPageAlloc(&g_supLibData, cPages, 0, ppvPages); + if (RT_SUCCESS(rc)) + { + Assert(ASMMemIsZero(*ppvPages, cPages << PAGE_SHIFT)); + if (!paPages) + paPages = (PSUPPAGE)alloca(sizeof(paPages[0]) * cPages); + rc = supR3PageLock(*ppvPages, cPages, paPages); + if (RT_FAILURE(rc)) + suplibOsPageFree(&g_supLibData, *ppvPages, cPages); + } + return rc; +} + + +SUPR3DECL(int) SUPR3PageAllocEx(size_t cPages, uint32_t fFlags, void **ppvPages, PRTR0PTR pR0Ptr, PSUPPAGE paPages) +{ + /* + * Validate. + */ + AssertPtrReturn(ppvPages, VERR_INVALID_POINTER); + *ppvPages = NULL; + AssertPtrNullReturn(pR0Ptr, VERR_INVALID_POINTER); + if (pR0Ptr) + *pR0Ptr = NIL_RTR0PTR; + AssertPtrNullReturn(paPages, VERR_INVALID_POINTER); + AssertMsgReturn(cPages > 0 && cPages <= VBOX_MAX_ALLOC_PAGE_COUNT, ("cPages=%zu\n", cPages), VERR_PAGE_COUNT_OUT_OF_RANGE); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Deal with driverless mode first. + */ + if (g_supLibData.fDriverless) + { + int rc = SUPR3PageAlloc(cPages, 0 /*fFlags*/, ppvPages); + Assert(RT_FAILURE(rc) || ASMMemIsZero(*ppvPages, cPages << PAGE_SHIFT)); + if (pR0Ptr) + *pR0Ptr = NIL_RTR0PTR; + if (paPages) + for (size_t iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage].uReserved = 0; + paPages[iPage].Phys = NIL_RTHCPHYS; + } + return rc; + } + + /* Check that we've got a kernel connection so rtMemSaferSupR3AllocPages + can do fallback without first having to hit assertions. */ + if (g_supLibData.hDevice != SUP_HDEVICE_NIL) + { /* likely */ } + else + return VERR_WRONG_ORDER; + + /* + * Use fallback for non-R0 mapping? + */ + if ( !pR0Ptr + && !g_fSupportsPageAllocNoKernel) + return supPagePageAllocNoKernelFallback(cPages, ppvPages, paPages); + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + int rc; + PSUPPAGEALLOCEX pReq = (PSUPPAGEALLOCEX)RTMemTmpAllocZ(SUP_IOCTL_PAGE_ALLOC_EX_SIZE(cPages)); + if (pReq) + { + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_PAGE_ALLOC_EX_SIZE_IN; + pReq->Hdr.cbOut = SUP_IOCTL_PAGE_ALLOC_EX_SIZE_OUT(cPages); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_MAGIC | SUPREQHDR_FLAGS_EXTRA_OUT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.cPages = (uint32_t)cPages; AssertRelease(pReq->u.In.cPages == cPages); + pReq->u.In.fKernelMapping = pR0Ptr != NULL; + pReq->u.In.fUserMapping = true; + pReq->u.In.fReserved0 = false; + pReq->u.In.fReserved1 = false; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_ALLOC_EX, pReq, SUP_IOCTL_PAGE_ALLOC_EX_SIZE(cPages)); + if (RT_SUCCESS(rc)) + { + rc = pReq->Hdr.rc; + if (RT_SUCCESS(rc)) + { + *ppvPages = pReq->u.Out.pvR3; + if (pR0Ptr) + { + *pR0Ptr = pReq->u.Out.pvR0; + Assert(ASMMemIsZero(pReq->u.Out.pvR3, cPages << PAGE_SHIFT)); +#ifdef RT_OS_DARWIN /* HACK ALERT! */ + supR3TouchPages(pReq->u.Out.pvR3, cPages); +#endif + } + else + RT_BZERO(pReq->u.Out.pvR3, cPages << PAGE_SHIFT); + + if (paPages) + for (size_t iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage].uReserved = 0; + paPages[iPage].Phys = pReq->u.Out.aPages[iPage]; + Assert(!(paPages[iPage].Phys & ~X86_PTE_PAE_PG_MASK)); + } + } + else if ( rc == VERR_NOT_SUPPORTED + && !pR0Ptr) + { + g_fSupportsPageAllocNoKernel = false; + rc = supPagePageAllocNoKernelFallback(cPages, ppvPages, paPages); + } + } + + RTMemTmpFree(pReq); + } + else + rc = VERR_NO_TMP_MEMORY; + return rc; + +} + + +SUPR3DECL(int) SUPR3PageMapKernel(void *pvR3, uint32_t off, uint32_t cb, uint32_t fFlags, PRTR0PTR pR0Ptr) +{ + /* + * Validate. + */ + AssertPtrReturn(pvR3, VERR_INVALID_POINTER); + AssertPtrReturn(pR0Ptr, VERR_INVALID_POINTER); + Assert(!(off & PAGE_OFFSET_MASK)); + Assert(!(cb & PAGE_OFFSET_MASK) && cb); + Assert(!fFlags); + *pR0Ptr = NIL_RTR0PTR; + + /* + * Not a valid operation in driverless mode. + */ + AssertReturn(g_supLibData.fDriverless, VERR_SUP_DRIVERLESS); + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPPAGEMAPKERNEL Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_PAGE_MAP_KERNEL_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_PAGE_MAP_KERNEL_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pvR3; + Req.u.In.offSub = off; + Req.u.In.cbSub = cb; + Req.u.In.fFlags = fFlags; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_MAP_KERNEL, &Req, SUP_IOCTL_PAGE_MAP_KERNEL_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + *pR0Ptr = Req.u.Out.pvR0; + return rc; +} + + +SUPR3DECL(int) SUPR3PageProtect(void *pvR3, RTR0PTR R0Ptr, uint32_t off, uint32_t cb, uint32_t fProt) +{ + /* + * Validate. + */ + AssertPtrReturn(pvR3, VERR_INVALID_POINTER); + Assert(!(off & PAGE_OFFSET_MASK)); + Assert(!(cb & PAGE_OFFSET_MASK) && cb); + AssertReturn(!(fProt & ~(RTMEM_PROT_NONE | RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC)), VERR_INVALID_PARAMETER); + + /* + * Deal with driverless mode first. + */ + if (g_supLibData.fDriverless) + return RTMemProtect((uint8_t *)pvR3 + off, cb, fProt); + + /* + * Some OSes can do this from ring-3, so try that before we + * issue the IOCtl to the SUPDRV kernel module. + * (Yea, this isn't very nice, but just try get the job done for now.) + */ +#if !defined(RT_OS_SOLARIS) + RTMemProtect((uint8_t *)pvR3 + off, cb, fProt); +#endif + + SUPPAGEPROTECT Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_PAGE_PROTECT_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_PAGE_PROTECT_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pvR3; + Req.u.In.pvR0 = R0Ptr; + Req.u.In.offSub = off; + Req.u.In.cbSub = cb; + Req.u.In.fProt = fProt; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_PROTECT, &Req, SUP_IOCTL_PAGE_PROTECT_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3PageFreeEx(void *pvPages, size_t cPages) +{ + /* + * Validate. + */ + AssertPtrReturn(pvPages, VERR_INVALID_POINTER); + AssertReturn(cPages > 0, VERR_PAGE_COUNT_OUT_OF_RANGE); + + /* + * Deal with driverless mode first. + */ + if (g_supLibData.fDriverless) + { + SUPR3PageFree(pvPages, cPages); + return VINF_SUCCESS; + } + + /* + * Try normal free first, then if it fails check if we're using the fallback + * for the allocations without kernel mappings and attempt unlocking it. + */ + NOREF(cPages); + SUPPAGEFREE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_PAGE_FREE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_PAGE_FREE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pvPages; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_PAGE_FREE, &Req, SUP_IOCTL_PAGE_FREE_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + if ( rc == VERR_INVALID_PARAMETER + && !g_fSupportsPageAllocNoKernel) + { + int rc2 = supR3PageUnlock(pvPages); + if (RT_SUCCESS(rc2)) + rc = suplibOsPageFree(&g_supLibData, pvPages, cPages); + } + } + return rc; +} + + +SUPR3DECL(void *) SUPR3ContAlloc(size_t cPages, PRTR0PTR pR0Ptr, PRTHCPHYS pHCPhys) +{ + /* + * Validate. + */ + AssertPtrReturn(pHCPhys, NULL); + *pHCPhys = NIL_RTHCPHYS; + AssertPtrNullReturn(pR0Ptr, NULL); + if (pR0Ptr) + *pR0Ptr = NIL_RTR0PTR; + AssertPtrNullReturn(pHCPhys, NULL); + AssertMsgReturn(cPages > 0 && cPages < 256, ("cPages=%d must be > 0 and < 256\n", cPages), NULL); + + /* + * Deal with driverless mode first. + */ + if (g_supLibData.fDriverless) + { + void *pvPages = NULL; + int rc = SUPR3PageAlloc(cPages, 0 /*fFlags*/, &pvPages); + if (pR0Ptr) + *pR0Ptr = NIL_RTR0PTR; + if (pHCPhys) + *pHCPhys = NIL_RTHCPHYS; + return RT_SUCCESS(rc) ? pvPages : NULL; + } + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPCONTALLOC Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_CONT_ALLOC_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_CONT_ALLOC_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.cPages = (uint32_t)cPages; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CONT_ALLOC, &Req, SUP_IOCTL_CONT_ALLOC_SIZE); + if ( RT_SUCCESS(rc) + && RT_SUCCESS(Req.Hdr.rc)) + { + *pHCPhys = Req.u.Out.HCPhys; + if (pR0Ptr) + *pR0Ptr = Req.u.Out.pvR0; +#ifdef RT_OS_DARWIN /* HACK ALERT! */ + supR3TouchPages(Req.u.Out.pvR3, cPages); +#endif + return Req.u.Out.pvR3; + } + + return NULL; +} + + +SUPR3DECL(int) SUPR3ContFree(void *pv, size_t cPages) +{ + /* + * Validate. + */ + if (!pv) + return VINF_SUCCESS; + AssertPtrReturn(pv, VERR_INVALID_POINTER); + AssertReturn(cPages > 0, VERR_PAGE_COUNT_OUT_OF_RANGE); + + /* + * Deal with driverless mode first. + */ + if (g_supLibData.fDriverless) + return SUPR3PageFree(pv, cPages); + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPCONTFREE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_CONT_FREE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_CONT_FREE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pv; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_CONT_FREE, &Req, SUP_IOCTL_CONT_FREE_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3LowAlloc(size_t cPages, void **ppvPages, PRTR0PTR ppvPagesR0, PSUPPAGE paPages) +{ + /* + * Validate. + */ + AssertPtrReturn(ppvPages, VERR_INVALID_POINTER); + *ppvPages = NULL; + AssertPtrReturn(paPages, VERR_INVALID_POINTER); + AssertMsgReturn(cPages > 0 && cPages < 256, ("cPages=%d must be > 0 and < 256\n", cPages), VERR_PAGE_COUNT_OUT_OF_RANGE); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + *ppvPages = RTMemPageAllocZ((size_t)cPages * PAGE_SIZE); + if (!*ppvPages) + return VERR_NO_LOW_MEMORY; + + /* fake physical addresses. */ + RTHCPHYS Phys = (uintptr_t)*ppvPages + PAGE_SIZE * 1024; + size_t iPage = cPages; + while (iPage-- > 0) + paPages[iPage].Phys = Phys + (iPage << PAGE_SHIFT); + return VINF_SUCCESS; + } + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + int rc; + PSUPLOWALLOC pReq = (PSUPLOWALLOC)RTMemTmpAllocZ(SUP_IOCTL_LOW_ALLOC_SIZE(cPages)); + if (pReq) + { + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_LOW_ALLOC_SIZE_IN; + pReq->Hdr.cbOut = SUP_IOCTL_LOW_ALLOC_SIZE_OUT(cPages); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_MAGIC | SUPREQHDR_FLAGS_EXTRA_OUT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.cPages = (uint32_t)cPages; AssertRelease(pReq->u.In.cPages == cPages); + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LOW_ALLOC, pReq, SUP_IOCTL_LOW_ALLOC_SIZE(cPages)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + if (RT_SUCCESS(rc)) + { + *ppvPages = pReq->u.Out.pvR3; + if (ppvPagesR0) + *ppvPagesR0 = pReq->u.Out.pvR0; + if (paPages) + for (size_t iPage = 0; iPage < cPages; iPage++) + { + paPages[iPage].uReserved = 0; + paPages[iPage].Phys = pReq->u.Out.aPages[iPage]; + Assert(!(paPages[iPage].Phys & ~X86_PTE_PAE_PG_MASK)); + Assert(paPages[iPage].Phys <= UINT32_C(0xfffff000)); + } +#ifdef RT_OS_DARWIN /* HACK ALERT! */ + supR3TouchPages(pReq->u.Out.pvR3, cPages); +#endif + } + RTMemTmpFree(pReq); + } + else + rc = VERR_NO_TMP_MEMORY; + + return rc; +} + + +SUPR3DECL(int) SUPR3LowFree(void *pv, size_t cPages) +{ + /* + * Validate. + */ + if (!pv) + return VINF_SUCCESS; + AssertPtrReturn(pv, VERR_INVALID_POINTER); + AssertReturn(cPages > 0, VERR_PAGE_COUNT_OUT_OF_RANGE); + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + RTMemPageFree(pv, cPages * PAGE_SIZE); + return VINF_SUCCESS; + } + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPCONTFREE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_LOW_FREE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_LOW_FREE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvR3 = pv; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LOW_FREE, &Req, SUP_IOCTL_LOW_FREE_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3HardenedVerifyInit(void) +{ +#ifdef RT_OS_WINDOWS + if (g_cInits == 0) + return suplibOsHardenedVerifyInit(); +#endif + return VINF_SUCCESS; +} + + +SUPR3DECL(int) SUPR3HardenedVerifyTerm(void) +{ +#ifdef RT_OS_WINDOWS + if (g_cInits == 0) + return suplibOsHardenedVerifyTerm(); +#endif + return VINF_SUCCESS; +} + + +SUPR3DECL(int) SUPR3HardenedVerifyFile(const char *pszFilename, const char *pszMsg, PRTFILE phFile) +{ + /* + * Quick input validation. + */ + AssertPtr(pszFilename); + AssertPtr(pszMsg); + AssertReturn(!phFile, VERR_NOT_IMPLEMENTED); /** @todo Implement this. The deal is that we make sure the + file is the same we verified after opening it. */ + RT_NOREF2(pszFilename, pszMsg); + + /* + * Only do the actual check in hardened builds. + */ +#ifdef VBOX_WITH_HARDENING + int rc = supR3HardenedVerifyFixedFile(pszFilename, false /* fFatal */); + if (RT_FAILURE(rc)) + LogRel(("SUPR3HardenedVerifyFile: %s: Verification of \"%s\" failed, rc=%Rrc\n", pszMsg, pszFilename, rc)); + return rc; +#else + return VINF_SUCCESS; +#endif +} + + +SUPR3DECL(int) SUPR3HardenedVerifySelf(const char *pszArgv0, bool fInternal, PRTERRINFO pErrInfo) +{ + /* + * Quick input validation. + */ + AssertPtr(pszArgv0); + RTErrInfoClear(pErrInfo); + + /* + * Get the executable image path as we need it for all the tests here. + */ + char szExecPath[RTPATH_MAX]; + if (!RTProcGetExecutablePath(szExecPath, sizeof(szExecPath))) + return RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_2, "RTProcGetExecutablePath failed"); + + int rc; + if (fInternal) + { + /* + * Internal applications must be launched directly without any PATH + * searching involved. + */ + if (RTPathCompare(pszArgv0, szExecPath) != 0) + return RTErrInfoSetF(pErrInfo, VERR_SUPLIB_INVALID_ARGV0_INTERNAL, + "argv[0] does not match the executable image path: '%s' != '%s'", pszArgv0, szExecPath); + + /* + * Internal applications must reside in or under the + * RTPathAppPrivateArch directory. + */ + char szAppPrivateArch[RTPATH_MAX]; + rc = RTPathAppPrivateArch(szAppPrivateArch, sizeof(szAppPrivateArch)); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, VERR_SUPLIB_INVALID_ARGV0_INTERNAL, + "RTPathAppPrivateArch failed with rc=%Rrc", rc); + size_t cchAppPrivateArch = strlen(szAppPrivateArch); + if ( cchAppPrivateArch >= strlen(szExecPath) + || !RTPATH_IS_SLASH(szExecPath[cchAppPrivateArch])) + return RTErrInfoSet(pErrInfo, VERR_SUPLIB_INVALID_INTERNAL_APP_DIR, + "Internal executable does reside under RTPathAppPrivateArch"); + szExecPath[cchAppPrivateArch] = '\0'; + if (RTPathCompare(szExecPath, szAppPrivateArch) != 0) + return RTErrInfoSet(pErrInfo, VERR_SUPLIB_INVALID_INTERNAL_APP_DIR, + "Internal executable does reside under RTPathAppPrivateArch"); + szExecPath[cchAppPrivateArch] = RTPATH_SLASH; + } + +#ifdef VBOX_WITH_HARDENING + /* + * Verify that the image file and parent directories are sane. + */ + rc = supR3HardenedVerifyFile(szExecPath, RTHCUINTPTR_MAX, false /*fMaybe3rdParty*/, pErrInfo); + if (RT_FAILURE(rc)) + return rc; +#endif + + return VINF_SUCCESS; +} + + +SUPR3DECL(int) SUPR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo) +{ + /* + * Quick input validation + */ + AssertPtr(pszDirPath); + RTErrInfoClear(pErrInfo); + + /* + * Only do the actual check in hardened builds. + */ +#ifdef VBOX_WITH_HARDENING + int rc = supR3HardenedVerifyDir(pszDirPath, fRecursive, fCheckFiles, pErrInfo); + if (RT_FAILURE(rc) && !RTErrInfoIsSet(pErrInfo)) + LogRel(("supR3HardenedVerifyDir: Verification of \"%s\" failed, rc=%Rrc\n", pszDirPath, rc)); + return rc; +#else + NOREF(pszDirPath); NOREF(fRecursive); NOREF(fCheckFiles); + return VINF_SUCCESS; +#endif +} + + +SUPR3DECL(int) SUPR3HardenedVerifyPlugIn(const char *pszFilename, PRTERRINFO pErrInfo) +{ + /* + * Quick input validation + */ + AssertPtr(pszFilename); + RTErrInfoClear(pErrInfo); + + /* + * Only do the actual check in hardened builds. + */ +#ifdef VBOX_WITH_HARDENING + 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; +#else + RT_NOREF1(pszFilename); + return VINF_SUCCESS; +#endif +} + + +SUPR3DECL(int) SUPR3GipGetPhys(PRTHCPHYS pHCPhys) +{ + if (g_pSUPGlobalInfoPage) + { + *pHCPhys = g_HCPhysSUPGlobalInfoPage; + return VINF_SUCCESS; + } + *pHCPhys = NIL_RTHCPHYS; + return VERR_WRONG_ORDER; +} + + +SUPR3DECL(int) SUPR3QueryVTxSupported(const char **ppszWhy) +{ + *ppszWhy = NULL; +#ifdef RT_OS_LINUX + return suplibOsQueryVTxSupported(ppszWhy); +#else + return VINF_SUCCESS; +#endif +} + + +SUPR3DECL(int) SUPR3QueryVTCaps(uint32_t *pfCaps) +{ + AssertPtrReturn(pfCaps, VERR_INVALID_POINTER); + + *pfCaps = 0; + + int rc; + if (!g_supLibData.fDriverless) + { + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPVTCAPS Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_VT_CAPS_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_VT_CAPS_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.Out.fCaps = 0; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_VT_CAPS, &Req, SUP_IOCTL_VT_CAPS_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + *pfCaps = Req.u.Out.fCaps; + } + } + /* + * Fail this call in driverless mode. + */ + else + rc = VERR_SUP_DRIVERLESS; + return rc; +} + + +SUPR3DECL(bool) SUPR3IsNemSupportedWhenNoVtxOrAmdV(void) +{ +#ifdef RT_OS_WINDOWS + return suplibOsIsNemSupportedWhenNoVtxOrAmdV(); +#else + return false; +#endif +} + + +SUPR3DECL(int) SUPR3QueryMicrocodeRev(uint32_t *uMicrocodeRev) +{ + AssertPtrReturn(uMicrocodeRev, VERR_INVALID_POINTER); + + *uMicrocodeRev = 0; + + int rc; + if (!g_supLibData.fDriverless) + { + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPUCODEREV Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_UCODE_REV_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_UCODE_REV_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.Out.MicrocodeRev = 0; + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_UCODE_REV, &Req, SUP_IOCTL_UCODE_REV_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + *uMicrocodeRev = Req.u.Out.MicrocodeRev; + } + } + /* + * Just fail the call in driverless mode. + */ + else + rc = VERR_SUP_DRIVERLESS; + return rc; +} + + +SUPR3DECL(int) SUPR3TracerOpen(uint32_t uCookie, uintptr_t uArg) +{ + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPTRACEROPEN Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie= g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TRACER_OPEN_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TRACER_OPEN_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.uCookie = uCookie; + Req.u.In.uArg = uArg; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_OPEN, &Req, SUP_IOCTL_TRACER_OPEN_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3TracerClose(void) +{ + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPREQHDR Req; + Req.u32Cookie = g_u32Cookie; + Req.u32SessionCookie= g_u32SessionCookie; + Req.cbIn = SUP_IOCTL_TRACER_OPEN_SIZE_IN; + Req.cbOut = SUP_IOCTL_TRACER_OPEN_SIZE_OUT; + Req.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.rc = VERR_INTERNAL_ERROR; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_CLOSE, &Req, SUP_IOCTL_TRACER_CLOSE_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3TracerIoCtl(uintptr_t uCmd, uintptr_t uArg, int32_t *piRetVal) +{ + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + *piRetVal = -1; + return VERR_NOT_SUPPORTED; + } + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPTRACERIOCTL Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie= g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TRACER_IOCTL_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TRACER_IOCTL_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.uCmd = uCmd; + Req.u.In.uArg = uArg; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_IOCTL, &Req, SUP_IOCTL_TRACER_IOCTL_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + *piRetVal = Req.u.Out.iRetVal; + } + return rc; +} + + + +typedef struct SUPDRVTRACERSTRTAB +{ + /** Pointer to the string table. */ + char *pchStrTab; + /** The actual string table size. */ + uint32_t cbStrTab; + /** The original string pointers. */ + RTUINTPTR apszOrgFunctions[1]; +} SUPDRVTRACERSTRTAB, *PSUPDRVTRACERSTRTAB; + + +/** + * Destroys a string table, restoring the original pszFunction member valus. + * + * @param pThis The string table structure. + * @param paProbeLocs32 The probe location array, 32-bit type variant. + * @param paProbeLocs64 The probe location array, 64-bit type variant. + * @param cProbeLocs The number of elements in the array. + * @param f32Bit Set if @a paProbeLocs32 should be used, when + * clear use @a paProbeLocs64. + */ +static void supr3TracerDestroyStrTab(PSUPDRVTRACERSTRTAB pThis, PVTGPROBELOC32 paProbeLocs32, PVTGPROBELOC64 paProbeLocs64, + uint32_t cProbeLocs, bool f32Bit) +{ + /* Restore. */ + size_t i = cProbeLocs; + if (f32Bit) + while (i--) + paProbeLocs32[i].pszFunction = (uint32_t)pThis->apszOrgFunctions[i]; + else + while (i--) + paProbeLocs64[i].pszFunction = pThis->apszOrgFunctions[i]; + + /* Free. */ + RTMemFree(pThis->pchStrTab); + RTMemFree(pThis); +} + + +/** + * Creates a string table for the pszFunction members in the probe location + * array. + * + * This will save and replace the pszFunction members with offsets. + * + * @returns Pointer to a string table structure. NULL on failure. + * @param paProbeLocs32 The probe location array, 32-bit type variant. + * @param paProbeLocs64 The probe location array, 64-bit type variant. + * @param cProbeLocs The number of elements in the array. + * @param offDelta Relocation offset for the string pointers. + * @param f32Bit Set if @a paProbeLocs32 should be used, when + * clear use @a paProbeLocs64. + */ +static PSUPDRVTRACERSTRTAB supr3TracerCreateStrTab(PVTGPROBELOC32 paProbeLocs32, + PVTGPROBELOC64 paProbeLocs64, + uint32_t cProbeLocs, + RTUINTPTR offDelta, + bool f32Bit) +{ + if (cProbeLocs > _128K) + return NULL; + + /* + * Allocate the string table structures. + */ + size_t cbThis = RT_UOFFSETOF_DYN(SUPDRVTRACERSTRTAB, apszOrgFunctions[cProbeLocs]); + PSUPDRVTRACERSTRTAB pThis = (PSUPDRVTRACERSTRTAB)RTMemAlloc(cbThis); + if (!pThis) + return NULL; + + uint32_t const cHashBits = cProbeLocs * 2 - 1; + uint32_t *pbmHash = (uint32_t *)RTMemAllocZ(RT_ALIGN_32(cHashBits, 64) / 8 ); + if (!pbmHash) + { + RTMemFree(pThis); + return NULL; + } + + /* + * Calc the max string table size and save the orignal pointers so we can + * replace them later. + */ + size_t cbMax = 1; + for (uint32_t i = 0; i < cProbeLocs; i++) + { + pThis->apszOrgFunctions[i] = f32Bit ? paProbeLocs32[i].pszFunction : paProbeLocs64[i].pszFunction; + const char *pszFunction = (const char *)(uintptr_t)(pThis->apszOrgFunctions[i] + offDelta); + size_t cch = strlen(pszFunction); + if (cch > _1K) + { + cbMax = 0; + break; + } + cbMax += cch + 1; + } + + /* Alloc space for it. */ + if (cbMax > 0) + pThis->pchStrTab = (char *)RTMemAlloc(cbMax); + else + pThis->pchStrTab = NULL; + if (!pThis->pchStrTab) + { + RTMemFree(pbmHash); + RTMemFree(pThis); + return NULL; + } + + /* + * Create the string table. + */ + uint32_t off = 0; + uint32_t offPrev = 0; + + for (uint32_t i = 0; i < cProbeLocs; i++) + { + const char * const psz = (const char *)(uintptr_t)(pThis->apszOrgFunctions[i] + offDelta); + size_t const cch = strlen(psz); + uint32_t const iHashBit = RTStrHash1(psz) % cHashBits; + if (ASMBitTestAndSet(pbmHash, iHashBit)) + { + /* Often it's the most recent string. */ + if ( off - offPrev < cch + 1 + || memcmp(&pThis->pchStrTab[offPrev], psz, cch + 1)) + { + /* It wasn't, search the entire string table. (lazy bird) */ + offPrev = 0; + while (offPrev < off) + { + size_t cchCur = strlen(&pThis->pchStrTab[offPrev]); + if ( cchCur == cch + && !memcmp(&pThis->pchStrTab[offPrev], psz, cch + 1)) + break; + offPrev += (uint32_t)cchCur + 1; + } + } + } + else + offPrev = off; + + /* Add the string to the table. */ + if (offPrev >= off) + { + memcpy(&pThis->pchStrTab[off], psz, cch + 1); + offPrev = off; + off += (uint32_t)cch + 1; + } + + /* Update the entry */ + if (f32Bit) + paProbeLocs32[i].pszFunction = offPrev; + else + paProbeLocs64[i].pszFunction = offPrev; + } + + pThis->cbStrTab = off; + RTMemFree(pbmHash); + return pThis; +} + + + +SUPR3DECL(int) SUPR3TracerRegisterModule(uintptr_t hModNative, const char *pszModule, struct VTGOBJHDR *pVtgHdr, + RTUINTPTR uVtgHdrAddr, uint32_t fFlags) +{ + /* Validate input. */ + NOREF(hModNative); + AssertPtrReturn(pVtgHdr, VERR_INVALID_POINTER); + AssertReturn(!memcmp(pVtgHdr->szMagic, VTGOBJHDR_MAGIC, sizeof(pVtgHdr->szMagic)), VERR_SUPDRV_VTG_MAGIC); + AssertPtrReturn(pszModule, VERR_INVALID_POINTER); + size_t cchModule = strlen(pszModule); + AssertReturn(cchModule < RT_SIZEOFMEMB(SUPTRACERUMODREG, u.In.szName), VERR_FILENAME_TOO_LONG); + AssertReturn(!RTPathHavePath(pszModule), VERR_INVALID_PARAMETER); + AssertReturn(fFlags == SUP_TRACER_UMOD_FLAGS_EXE || fFlags == SUP_TRACER_UMOD_FLAGS_SHARED, VERR_INVALID_PARAMETER); + + /* + * Set the probe location array offset and size members. If the size is + * zero, don't bother ring-0 with it. + */ + if (!pVtgHdr->offProbeLocs) + { + uint64_t u64Tmp = pVtgHdr->uProbeLocsEnd.u64 - pVtgHdr->uProbeLocs.u64; + if (u64Tmp >= UINT32_MAX) + return VERR_SUPDRV_VTG_BAD_HDR_TOO_MUCH; + pVtgHdr->cbProbeLocs = (uint32_t)u64Tmp; + + u64Tmp = pVtgHdr->uProbeLocs.u64 - uVtgHdrAddr; + if ((int64_t)u64Tmp != (int32_t)u64Tmp) + { + LogRel(("SUPR3TracerRegisterModule: VERR_SUPDRV_VTG_BAD_HDR_PTR - u64Tmp=%#llx uProbeLocs=%#llx uVtgHdrAddr=%RTptr\n", + u64Tmp, pVtgHdr->uProbeLocs.u64, uVtgHdrAddr)); + return VERR_SUPDRV_VTG_BAD_HDR_PTR; + } + pVtgHdr->offProbeLocs = (int32_t)u64Tmp; + } + + if ( !pVtgHdr->cbProbeLocs + || !pVtgHdr->cbProbes) + return VINF_SUCCESS; + + /* + * Fake out. + */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Create a string table for the function names in the location array. + * It's somewhat easier to do that here than from ring-0. + */ + uint32_t const cProbeLocs = pVtgHdr->cbProbeLocs + / (pVtgHdr->cBits == 32 ? sizeof(VTGPROBELOC32) : sizeof(VTGPROBELOC64)); + PVTGPROBELOC paProbeLocs = (PVTGPROBELOC)((uintptr_t)pVtgHdr + pVtgHdr->offProbeLocs); + PSUPDRVTRACERSTRTAB pStrTab = supr3TracerCreateStrTab((PVTGPROBELOC32)paProbeLocs, + (PVTGPROBELOC64)paProbeLocs, + cProbeLocs, (uintptr_t)pVtgHdr - uVtgHdrAddr, + pVtgHdr->cBits == 32); + if (!pStrTab) + return VERR_NO_MEMORY; + + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPTRACERUMODREG Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie= g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TRACER_UMOD_REG_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TRACER_UMOD_REG_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.uVtgHdrAddr = uVtgHdrAddr; + Req.u.In.R3PtrVtgHdr = pVtgHdr; + Req.u.In.R3PtrStrTab = pStrTab->pchStrTab; + Req.u.In.cbStrTab = pStrTab->cbStrTab; + Req.u.In.fFlags = fFlags; + + memcpy(Req.u.In.szName, pszModule, cchModule + 1); + if (!RTPathHasSuffix(Req.u.In.szName)) + { + /* Add the default suffix if none is given. */ + switch (fFlags & SUP_TRACER_UMOD_FLAGS_TYPE_MASK) + { +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + case SUP_TRACER_UMOD_FLAGS_EXE: + if (cchModule + sizeof(".exe") <= sizeof(Req.u.In.szName)) + strcpy(&Req.u.In.szName[cchModule], ".exe"); + break; +#endif + + case SUP_TRACER_UMOD_FLAGS_SHARED: + { + const char *pszSuff = RTLdrGetSuff(); + size_t cchSuff = strlen(pszSuff); + if (cchModule + cchSuff < sizeof(Req.u.In.szName)) + memcpy(&Req.u.In.szName[cchModule], pszSuff, cchSuff + 1); + break; + } + } + } + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_UMOD_REG, &Req, SUP_IOCTL_TRACER_UMOD_REG_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + + supr3TracerDestroyStrTab(pStrTab, (PVTGPROBELOC32)paProbeLocs, (PVTGPROBELOC64)paProbeLocs, + cProbeLocs, pVtgHdr->cBits == 32); + return rc; +} + + +SUPR3DECL(int) SUPR3TracerDeregisterModule(struct VTGOBJHDR *pVtgHdr) +{ + /* Validate input. */ + AssertPtrReturn(pVtgHdr, VERR_INVALID_POINTER); + AssertReturn(!memcmp(pVtgHdr->szMagic, VTGOBJHDR_MAGIC, sizeof(pVtgHdr->szMagic)), VERR_SUPDRV_VTG_MAGIC); + + /* + * Don't bother if the object is empty. + */ + if ( !pVtgHdr->cbProbeLocs + || !pVtgHdr->cbProbes) + return VINF_SUCCESS; + + /* + * Fake out. + */ + if (RT_UNLIKELY(g_uSupFakeMode)) + return VINF_SUCCESS; + + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPTRACERUMODDEREG Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie= g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TRACER_UMOD_REG_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TRACER_UMOD_REG_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pVtgHdr = pVtgHdr; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_UMOD_DEREG, &Req, SUP_IOCTL_TRACER_UMOD_DEREG_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +DECLASM(void) suplibTracerFireProbe(PVTGPROBELOC pProbeLoc, PSUPTRACERUMODFIREPROBE pReq) +{ + RT_NOREF1(pProbeLoc); + + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + Assert(pReq->Hdr.cbIn == SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE_IN); + Assert(pReq->Hdr.cbOut == SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE_OUT); + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VINF_SUCCESS; + + suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TRACER_UMOD_FIRE_PROBE, pReq, SUP_IOCTL_TRACER_UMOD_FIRE_PROBE_SIZE); +} + + +SUPR3DECL(int) SUPR3MsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue, bool *pfGp) +{ + SUPMSRPROBER Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_MSR_PROBER_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_MSR_PROBER_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.enmOp = SUPMSRPROBEROP_READ; + Req.u.In.uMsr = uMsr; + Req.u.In.idCpu = idCpu == NIL_RTCPUID ? UINT32_MAX : idCpu; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_MSR_PROBER, &Req, SUP_IOCTL_MSR_PROBER_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + { + if (puValue) + *puValue = Req.u.Out.uResults.Read.uValue; + if (pfGp) + *pfGp = Req.u.Out.uResults.Read.fGp; + } + + return rc; +} + + +SUPR3DECL(int) SUPR3MsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue, bool *pfGp) +{ + SUPMSRPROBER Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_MSR_PROBER_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_MSR_PROBER_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.enmOp = SUPMSRPROBEROP_WRITE; + Req.u.In.uMsr = uMsr; + Req.u.In.idCpu = idCpu == NIL_RTCPUID ? UINT32_MAX : idCpu; + Req.u.In.uArgs.Write.uToWrite = uValue; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_MSR_PROBER, &Req, SUP_IOCTL_MSR_PROBER_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc) && pfGp) + *pfGp = Req.u.Out.uResults.Write.fGp; + + return rc; +} + + +SUPR3DECL(int) SUPR3MsrProberModify(uint32_t uMsr, RTCPUID idCpu, uint64_t fAndMask, uint64_t fOrMask, + PSUPMSRPROBERMODIFYRESULT pResult) +{ + return SUPR3MsrProberModifyEx(uMsr, idCpu, fAndMask, fOrMask, false /*fFaster*/, pResult); +} + + +SUPR3DECL(int) SUPR3MsrProberModifyEx(uint32_t uMsr, RTCPUID idCpu, uint64_t fAndMask, uint64_t fOrMask, bool fFaster, + PSUPMSRPROBERMODIFYRESULT pResult) +{ + SUPMSRPROBER Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_MSR_PROBER_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_MSR_PROBER_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.enmOp = fFaster ? SUPMSRPROBEROP_MODIFY_FASTER : SUPMSRPROBEROP_MODIFY; + Req.u.In.uMsr = uMsr; + Req.u.In.idCpu = idCpu == NIL_RTCPUID ? UINT32_MAX : idCpu; + Req.u.In.uArgs.Modify.fAndMask = fAndMask; + Req.u.In.uArgs.Modify.fOrMask = fOrMask; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_MSR_PROBER, &Req, SUP_IOCTL_MSR_PROBER_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + *pResult = Req.u.Out.uResults.Modify; + + return rc; +} + + +SUPR3DECL(int) SUPR3ResumeSuspendedKeyboards(void) +{ +#ifdef RT_OS_DARWIN + /* + * Issue IOCtl to the SUPDRV kernel module. + */ + SUPREQHDR Req; + Req.u32Cookie = g_u32Cookie; + Req.u32SessionCookie= g_u32SessionCookie; + Req.cbIn = SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE_IN; + Req.cbOut = SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE_OUT; + Req.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.rc = VERR_INTERNAL_ERROR; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_RESUME_SUSPENDED_KBDS, &Req, SUP_IOCTL_RESUME_SUSPENDED_KBDS_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.rc; + return rc; +#else /* !RT_OS_DARWIN */ + return VERR_NOT_SUPPORTED; +#endif +} + + +SUPR3DECL(int) SUPR3TscDeltaMeasure(RTCPUID idCpu, bool fAsync, bool fForce, uint8_t cRetries, uint8_t cMsWaitRetry) +{ + SUPTSCDELTAMEASURE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TSC_DELTA_MEASURE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TSC_DELTA_MEASURE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.cRetries = cRetries; + Req.u.In.fAsync = fAsync; + Req.u.In.fForce = fForce; + Req.u.In.idCpu = idCpu; + Req.u.In.cMsWaitRetry = cMsWaitRetry; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TSC_DELTA_MEASURE, &Req, SUP_IOCTL_TSC_DELTA_MEASURE_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3ReadTsc(uint64_t *puTsc, uint16_t *pidApic) +{ + AssertReturn(puTsc, VERR_INVALID_PARAMETER); + + SUPTSCREAD Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_TSC_READ_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_TSC_READ_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_TSC_READ, &Req, SUP_IOCTL_TSC_READ_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + *puTsc = Req.u.Out.u64AdjustedTsc; + if (pidApic) + *pidApic = Req.u.Out.idApic; + } + return rc; +} + + +SUPR3DECL(int) SUPR3GipSetFlags(uint32_t fOrMask, uint32_t fAndMask) +{ + AssertMsgReturn(!(fOrMask & ~SUPGIP_FLAGS_VALID_MASK), + ("fOrMask=%#x ValidMask=%#x\n", fOrMask, SUPGIP_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + AssertMsgReturn((fAndMask & ~SUPGIP_FLAGS_VALID_MASK) == ~SUPGIP_FLAGS_VALID_MASK, + ("fAndMask=%#x ValidMask=%#x\n", fAndMask, SUPGIP_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + SUPGIPSETFLAGS Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_GIP_SET_FLAGS_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_GIP_SET_FLAGS_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.fAndMask = fAndMask; + Req.u.In.fOrMask = fOrMask; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_GIP_SET_FLAGS, &Req, SUP_IOCTL_GIP_SET_FLAGS_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + return rc; +} + + +SUPR3DECL(int) SUPR3GetHwvirtMsrs(PSUPHWVIRTMSRS pHwvirtMsrs, bool fForceRequery) +{ + AssertReturn(pHwvirtMsrs, VERR_INVALID_PARAMETER); + + SUPGETHWVIRTMSRS Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_GET_HWVIRT_MSRS_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_GET_HWVIRT_MSRS_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + + Req.u.In.fForce = fForceRequery; + Req.u.In.fReserved0 = false; + Req.u.In.fReserved1 = false; + Req.u.In.fReserved2 = false; + + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_GET_HWVIRT_MSRS, &Req, SUP_IOCTL_GET_HWVIRT_MSRS_SIZE); + if (RT_SUCCESS(rc)) + { + rc = Req.Hdr.rc; + *pHwvirtMsrs = Req.u.Out.HwvirtMsrs; + } + else + RT_ZERO(*pHwvirtMsrs); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/SUPLibAll.cpp b/src/VBox/HostDrivers/Support/SUPLibAll.cpp new file mode 100644 index 00000000..939c4188 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLibAll.cpp @@ -0,0 +1,429 @@ +/* $Id: SUPLibAll.cpp $ */ +/** @file + * VirtualBox Support Library - All Contexts Code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#ifdef IN_RC +# include <VBox/vmm/vm.h> +# include <VBox/vmm/vmm.h> +#endif +#ifdef IN_RING0 +# include <iprt/mp.h> +#endif +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/asm-amd64-x86.h> +#endif +#include <iprt/errcore.h> +#if defined(IN_RING0) && defined(RT_OS_LINUX) +# include "SUPDrvInternal.h" +#endif + + + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +/** + * The slow case for SUPReadTsc where we need to apply deltas. + * + * Must only be called when deltas are applicable, so please do not call it + * directly. + * + * @returns TSC with delta applied. + * @param pGip Pointer to the GIP. + * + * @remarks May be called with interrupts disabled in ring-0! This is why the + * ring-0 code doesn't attempt to figure the delta. + * + * @internal + */ +SUPDECL(uint64_t) SUPReadTscWithDelta(PSUPGLOBALINFOPAGE pGip) +{ + uint64_t uTsc; + uint16_t iGipCpu; + AssertCompile(RT_IS_POWER_OF_TWO(RTCPUSET_MAX_CPUS)); + AssertCompile(RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx) >= RTCPUSET_MAX_CPUS); + Assert(pGip->enmUseTscDelta > SUPGIPUSETSCDELTA_PRACTICALLY_ZERO); + + /* + * Read the TSC and get the corresponding aCPUs index. + */ +#ifdef IN_RING3 + if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS) + { + /* RDTSCP gives us all we need, no loops/cli. */ + uint32_t iCpuSet; + uTsc = ASMReadTscWithAux(&iCpuSet); + iCpuSet &= RTCPUSET_MAX_CPUS - 1; + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS) + { + /* Storing the IDTR is normally very quick, but we need to loop. */ + uint32_t cTries = 0; + for (;;) + { + uint16_t cbLim = ASMGetIdtrLimit(); + uTsc = ASMReadTSC(); + if (RT_LIKELY(ASMGetIdtrLimit() == cbLim)) + { + uint16_t iCpuSet = cbLim - 256 * (ARCH_BITS == 64 ? 16 : 8); + iCpuSet &= RTCPUSET_MAX_CPUS - 1; + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + break; + } + if (cTries >= 16) + { + iGipCpu = UINT16_MAX; + break; + } + cTries++; + } + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B) + { + /* Get APIC ID / 0x1b via the slow CPUID instruction, requires looping. */ + uint32_t cTries = 0; + for (;;) + { + uint32_t idApic = ASMGetApicIdExt0B(); + uTsc = ASMReadTSC(); + if (RT_LIKELY(ASMGetApicIdExt0B() == idApic)) + { + iGipCpu = pGip->aiCpuFromApicId[idApic]; + break; + } + if (cTries >= 16) + { + iGipCpu = UINT16_MAX; + break; + } + cTries++; + } + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E) + { + /* Get APIC ID / 0x8000001e via the slow CPUID instruction, requires looping. */ + uint32_t cTries = 0; + for (;;) + { + uint32_t idApic = ASMGetApicIdExt8000001E(); + uTsc = ASMReadTSC(); + if (RT_LIKELY(ASMGetApicIdExt8000001E() == idApic)) + { + iGipCpu = pGip->aiCpuFromApicId[idApic]; + break; + } + if (cTries >= 16) + { + iGipCpu = UINT16_MAX; + break; + } + cTries++; + } + } + else + { + /* Get APIC ID via the slow CPUID instruction, requires looping. */ + uint32_t cTries = 0; + for (;;) + { + uint8_t idApic = ASMGetApicId(); + uTsc = ASMReadTSC(); + if (RT_LIKELY(ASMGetApicId() == idApic)) + { + iGipCpu = pGip->aiCpuFromApicId[idApic]; + break; + } + if (cTries >= 16) + { + iGipCpu = UINT16_MAX; + break; + } + cTries++; + } + } +#elif defined(IN_RING0) + /* Ring-0: Use use RTMpCpuId(), no loops. */ + RTCCUINTREG uFlags = ASMIntDisableFlags(); + int iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + if (RT_LIKELY((unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + else + iGipCpu = UINT16_MAX; + uTsc = ASMReadTSC(); + ASMSetFlags(uFlags); + +# elif defined(IN_RC) + /* Raw-mode context: We can get the host CPU set index via VMCPU, no loops. */ + RTCCUINTREG uFlags = ASMIntDisableFlags(); /* Are already disable, but play safe. */ + uint32_t iCpuSet = VMMGetCpu(&g_VM)->iHostCpuSet; + if (RT_LIKELY(iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + else + iGipCpu = UINT16_MAX; + uTsc = ASMReadTSC(); + ASMSetFlags(uFlags); +#else +# error "IN_RING3, IN_RC or IN_RING0 must be defined!" +#endif + + /* + * If the delta is valid, apply it. + */ + if (RT_LIKELY(iGipCpu < pGip->cCpus)) + { + int64_t iTscDelta = pGip->aCPUs[iGipCpu].i64TSCDelta; + if (RT_LIKELY(iTscDelta != INT64_MAX)) + return uTsc - iTscDelta; + +# ifdef IN_RING3 + /* + * The delta needs calculating, call supdrv to get the TSC. + */ + int rc = SUPR3ReadTsc(&uTsc, NULL); + if (RT_SUCCESS(rc)) + return uTsc; + AssertMsgFailed(("SUPR3ReadTsc -> %Rrc\n", rc)); + uTsc = ASMReadTSC(); +# endif /* IN_RING3 */ + } + + /* + * This shouldn't happen, especially not in ring-3 and raw-mode context. + * But if it does, return something that's half useful. + */ + AssertMsgFailed(("iGipCpu=%d (%#x) cCpus=%d fGetGipCpu=%#x\n", iGipCpu, iGipCpu, pGip->cCpus, pGip->fGetGipCpu)); + return uTsc; +} +# ifdef SUPR0_EXPORT_SYMBOL +SUPR0_EXPORT_SYMBOL(SUPReadTscWithDelta); +# endif +#endif /* RT_ARCH_AMD64 || RT_ARCH_X86 */ + + +/** + * Internal worker for getting the GIP CPU array index for the calling CPU. + * + * @returns Index into SUPGLOBALINFOPAGE::aCPUs or UINT16_MAX. + * @param pGip The GIP. + */ +DECLINLINE(uint16_t) supGetGipCpuIndex(PSUPGLOBALINFOPAGE pGip) +{ + uint16_t iGipCpu; +#ifdef IN_RING3 +# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS) + { + /* Storing the IDTR is normally very fast. */ + uint16_t cbLim = ASMGetIdtrLimit(); + uint16_t iCpuSet = cbLim - 256 * (ARCH_BITS == 64 ? 16 : 8); + iCpuSet &= RTCPUSET_MAX_CPUS - 1; + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS) + { + /* RDTSCP gives us what need need and more. */ + uint32_t iCpuSet; + ASMReadTscWithAux(&iCpuSet); + iCpuSet &= RTCPUSET_MAX_CPUS - 1; + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B) + { + /* Get APIC ID via the slow CPUID/0000000B instruction. */ + uint32_t idApic = ASMGetApicIdExt0B(); + iGipCpu = pGip->aiCpuFromApicId[idApic]; + } + else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E) + { + /* Get APIC ID via the slow CPUID/8000001E instruction. */ + uint32_t idApic = ASMGetApicIdExt8000001E(); + iGipCpu = pGip->aiCpuFromApicId[idApic]; + } + else + { + /* Get APIC ID via the slow CPUID instruction. */ + uint8_t idApic = ASMGetApicId(); + iGipCpu = pGip->aiCpuFromApicId[idApic]; + } + +# else + int iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + if (RT_LIKELY((unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + else + iGipCpu = UINT16_MAX; +# endif + +#elif defined(IN_RING0) + /* Ring-0: Use use RTMpCpuId() (disables cli to avoid host OS assertions about unsafe CPU number usage). */ + RTCCUINTREG uFlags = ASMIntDisableFlags(); + int iCpuSet = RTMpCpuIdToSetIndex(RTMpCpuId()); + if (RT_LIKELY((unsigned)iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + else + iGipCpu = UINT16_MAX; + ASMSetFlags(uFlags); + +# elif defined(IN_RC) + /* Raw-mode context: We can get the host CPU set index via VMCPU. */ + uint32_t iCpuSet = VMMGetCpu(&g_VM)->iHostCpuSet; + if (RT_LIKELY(iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx))) + iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet]; + else + iGipCpu = UINT16_MAX; + +#else +# error "IN_RING3, IN_RC or IN_RING0 must be defined!" +#endif + return iGipCpu; +} + + +/** + * Slow path in SUPGetTscDelta, don't call directly. + * + * @returns See SUPGetTscDelta. + * @param pGip The GIP. + * @internal + */ +SUPDECL(int64_t) SUPGetTscDeltaSlow(PSUPGLOBALINFOPAGE pGip) +{ + uint16_t iGipCpu = supGetGipCpuIndex(pGip); + if (RT_LIKELY(iGipCpu < pGip->cCpus)) + { + int64_t iTscDelta = pGip->aCPUs[iGipCpu].i64TSCDelta; + if (iTscDelta != INT64_MAX) + return iTscDelta; + } + AssertFailed(); + return 0; +} + + +/** + * SLow path in SUPGetGipCpuPtr, don't call directly. + * + * @returns Pointer to the CPU entry for the caller, NULL on failure. + * @param pGip The GIP. + */ +SUPDECL(PSUPGIPCPU) SUPGetGipCpuPtrForAsyncMode(PSUPGLOBALINFOPAGE pGip) +{ + uint16_t iGipCpu = supGetGipCpuIndex(pGip); + if (RT_LIKELY(iGipCpu < pGip->cCpus)) + return &pGip->aCPUs[iGipCpu]; + AssertFailed(); + return NULL; +} + + +/** + * Slow path in SUPGetCpuHzFromGip, don't call directly. + * + * @returns See SUPGetCpuHzFromGip. + * @param pGip The GIP. + * @internal + */ +SUPDECL(uint64_t) SUPGetCpuHzFromGipForAsyncMode(PSUPGLOBALINFOPAGE pGip) +{ + uint16_t iGipCpu = supGetGipCpuIndex(pGip); + if (RT_LIKELY(iGipCpu < pGip->cCpus)) + return pGip->aCPUs[iGipCpu].u64CpuHz; + AssertFailed(); + return pGip->u64CpuHz; +} + + + +/** + * Worker for SUPIsTscFreqCompatible(). + * + * @returns true if it's compatible, false otherwise. + * @param uBaseCpuHz The reference CPU frequency of the system. + * @param uCpuHz The CPU frequency to compare with the base. + * @param fRelax Whether to use a more relaxed threshold (like + * for when running in a virtualized environment). + * + * @remarks Don't use directly, use SUPIsTscFreqCompatible() instead. This is + * to be used by tstGIP-2 or the like. + */ +SUPDECL(bool) SUPIsTscFreqCompatibleEx(uint64_t uBaseCpuHz, uint64_t uCpuHz, bool fRelax) +{ + if (uBaseCpuHz != uCpuHz) + { + /* Arbitrary tolerance threshold, tweak later if required, perhaps + more tolerance on lower frequencies and less tolerance on higher. */ + uint16_t uFact = !fRelax ? 666 /* 0.15% */ : 125 /* 0.8% */; + uint64_t uThr = uBaseCpuHz / uFact; + uint64_t uLo = uBaseCpuHz - uThr; + uint64_t uHi = uBaseCpuHz + uThr; + if ( uCpuHz < uLo + || uCpuHz > uHi) + return false; + } + return true; +} + + +/** + * Checks if the provided TSC frequency is close enough to the computed TSC + * frequency of the host. + * + * @returns true if it's compatible, false otherwise. + * @param uCpuHz The TSC frequency to check. + * @param puGipCpuHz Where to store the GIP TSC frequency used + * during the compatibility test - optional. + * @param fRelax Whether to use a more relaxed threshold (like + * for when running in a virtualized environment). + */ +SUPDECL(bool) SUPIsTscFreqCompatible(uint64_t uCpuHz, uint64_t *puGipCpuHz, bool fRelax) +{ + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; + bool fCompat = false; + uint64_t uGipCpuHz = 0; + if ( pGip + && pGip->u32Mode != SUPGIPMODE_ASYNC_TSC) + { + uGipCpuHz = pGip->u64CpuHz; + fCompat = SUPIsTscFreqCompatibleEx(uGipCpuHz, uCpuHz, fRelax); + } + if (puGipCpuHz) + *puGipCpuHz = uGipCpuHz; + return fCompat; +} + diff --git a/src/VBox/HostDrivers/Support/SUPLibInternal.h b/src/VBox/HostDrivers/Support/SUPLibInternal.h new file mode 100644 index 00000000..4c788fac --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLibInternal.h @@ -0,0 +1,524 @@ +/* $Id: SUPLibInternal.h $ */ +/** @file + * VirtualBox Support Library - Internal header. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPLibInternal_h +#define VBOX_INCLUDED_SRC_Support_SUPLibInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <iprt/stdarg.h> + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +/** @def SUPLIB_DLL_SUFF + * The (typical) DLL/DYLIB/SO suffix. */ +#if defined(RT_OS_DARWIN) +# define SUPLIB_DLL_SUFF ".dylib" +#elif defined(RT_OS_L4) +# define SUPLIB_DLL_SUFF ".s.so" +#elif defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +# define SUPLIB_DLL_SUFF ".dll" +#else +# define SUPLIB_DLL_SUFF ".so" +#endif + +#ifdef RT_OS_SOLARIS +/** Number of dummy files to open (2:ip4, 1:ip6, 1:extra) see + * @bugref{4650}. */ +# define SUPLIB_FLT_DUMMYFILES 4 +#endif + +/** @def SUPLIB_EXE_SUFF + * The (typical) executable suffix. */ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) +# define SUPLIB_EXE_SUFF ".exe" +#else +# define SUPLIB_EXE_SUFF "" +#endif + +/** @def SUP_HARDENED_SUID + * Whether we're employing set-user-ID-on-execute in the hardening. + */ +#if (!defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_L4)) || defined(DOXYGEN_RUNNING) +# define SUP_HARDENED_SUID +#else +# undef SUP_HARDENED_SUID +#endif + +#ifdef IN_SUP_HARDENED_R3 +/** @name Make the symbols in SUPR3HardenedStatic different from the VBoxRT ones. + * We cannot rely on DECLHIDDEN to make this separation for us since it doesn't + * work with all GCC versions. So, we resort to old fashion precompiler hacking. + * @{ + */ +# define supR3HardenedPathAppPrivateNoArch supR3HardenedStaticPathAppPrivateNoArch +# define supR3HardenedPathAppPrivateArch supR3HardenedStaticPathAppPrivateArch +# define supR3HardenedPathAppSharedLibs supR3HardenedStaticPathAppSharedLibs +# define supR3HardenedPathAppDocs supR3HardenedStaticPathAppDocs +# define supR3HardenedPathAppBin supR3HardenedStaticPathAppBin +# define supR3HardenedPathFilename supR3HardenedStaticPathFilename +# define supR3HardenedFatalV supR3HardenedStaticFatalV +# define supR3HardenedFatal supR3HardenedStaticFatal +# define supR3HardenedFatalMsgV supR3HardenedStaticFatalMsgV +# define supR3HardenedFatalMsg supR3HardenedStaticFatalMsg +# define supR3HardenedErrorV supR3HardenedStaticErrorV +# define supR3HardenedError supR3HardenedStaticError +# define supR3HardenedOpenLog supR3HardenedStaticOpenLog +# define supR3HardenedLogV supR3HardenedStaticLogV +# define supR3HardenedLog supR3HardenedStaticLog +# define supR3HardenedLogFlush supR3HardenedStaticLogFlush +# define supR3HardenedVerifyAll supR3HardenedStaticVerifyAll +# define supR3HardenedVerifyFixedDir supR3HardenedStaticVerifyFixedDir +# define supR3HardenedVerifyFixedFile supR3HardenedStaticVerifyFixedFile +# define supR3HardenedVerifyDir supR3HardenedStaticVerifyDir +# define supR3HardenedVerifyFile supR3HardenedStaticVerifyFile +# define supR3HardenedGetPreInitData supR3HardenedStaticGetPreInitData +# define supR3HardenedRecvPreInitData supR3HardenedStaticRecvPreInitData +/** @} */ +#endif /* IN_SUP_HARDENED_R3 */ + + +/** @name CRT function mappings (not using CRT on Windows). + * @{ + */ +#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); +DECLHIDDEN(int) suplibHardenedStrCmp(const char *psz1, const char *psz2); +DECLHIDDEN(int) suplibHardenedStrNCmp(const char *psz1, const char *psz2, size_t cchMax); +#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 +# define suplibHardenedStrCmp strcmp +# define suplibHardenedStrNCmp strncmp +#endif +DECLHIDDEN(DECL_NO_RETURN(void)) suplibHardenedExit(RTEXITCODE rcExit); +DECLHIDDEN(void) suplibHardenedPrintF(const char *pszFormat, ...); +DECLHIDDEN(void) suplibHardenedPrintFV(const char *pszFormat, va_list va); + +/** @} */ + +/** Debug output macro. */ +#ifdef IN_SUP_HARDENED_R3 +# if defined(DEBUG_bird) && defined(RT_OS_WINDOWS) +# define SUP_DPRINTF(a) do { supR3HardenedStaticLog a; suplibHardenedPrintF a; } while (0) +# else +# define SUP_DPRINTF(a) do { supR3HardenedStaticLog a; } while (0) +# endif +#else +# if defined(DEBUG_bird) && defined(RT_OS_WINDOWS) +# define SUP_DPRINTF(a) RTLogPrintf a +# else +# define SUP_DPRINTF(a) do { } while (0) +# endif +#endif + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * The type of an installed file. + */ +typedef enum SUPINSTFILETYPE +{ + kSupIFT_Invalid = 0, + kSupIFT_Exe, + kSupIFT_Dll, + kSupIFT_Rc, + kSupIFT_Sys, + kSupIFT_Script, + kSupIFT_Data, + kSupIFT_TestExe, + kSupIFT_TestDll, + kSupIFT_End +} SUPINSTFILETYPE; + +/** + * Installation directory specifier. + */ +typedef enum SUPINSTDIR +{ + kSupID_Invalid = 0, + kSupID_AppBin, + kSupID_AppSharedLib, + kSupID_AppPrivArch, + kSupID_AppPrivArchComp, + kSupID_AppPrivNoArch, + kSupID_Testcase, +#ifdef RT_OS_DARWIN + kSupID_AppMacHelper, +#endif + kSupID_End +} SUPINSTDIR; + +/** + * Installed file. + */ +typedef struct SUPINSTFILE +{ + /** File type. */ + SUPINSTFILETYPE enmType; + /** Install directory. */ + SUPINSTDIR enmDir; + /** Optional (true) or mandatory (false. */ + bool fOptional; + /** File name. */ + const char *pszFile; +} SUPINSTFILE; +typedef SUPINSTFILE *PSUPINSTFILE; +typedef SUPINSTFILE const *PCSUPINSTFILE; + +/** + * Status data for a verified file. + */ +typedef struct SUPVERIFIEDFILE +{ + /** The file handle or descriptor. -1 if not open. */ + intptr_t hFile; + /** Whether the file has been validated. */ + bool fValidated; +#ifdef RT_OS_WINDOWS + /** Whether we've checked the signature of the file. */ + bool fCheckedSignature; +#endif +} SUPVERIFIEDFILE; +typedef SUPVERIFIEDFILE *PSUPVERIFIEDFILE; +typedef SUPVERIFIEDFILE const *PCSUPVERIFIEDFILE; + +/** + * Status data for a verified directory. + */ +typedef struct SUPVERIFIEDDIR +{ + /** The directory handle or descriptor. -1 if not open. */ + intptr_t hDir; + /** Whether the directory has been validated. */ + bool fValidated; +} SUPVERIFIEDDIR; +typedef SUPVERIFIEDDIR *PSUPVERIFIEDDIR; +typedef SUPVERIFIEDDIR const *PCSUPVERIFIEDDIR; + + +/** + * SUPLib instance data. + * + * This is data that is passed from the static to the dynamic SUPLib + * in a hardened setup. + */ +typedef struct SUPLIBDATA +{ + /** The device handle. */ +#if defined(RT_OS_WINDOWS) + void *hDevice; +#else + int hDevice; +#endif + /** Indicates whether we have unrestricted (true) or restricted access to the + * support device. */ + bool fUnrestricted; + /** Set if we're in driverless mode. */ + bool fDriverless; +#if defined(RT_OS_DARWIN) + /** The connection to the VBoxSupDrv service. */ + uintptr_t uConnection; +#elif defined(RT_OS_LINUX) + /** Indicates whether madvise(,,MADV_DONTFORK) works. */ + bool fSysMadviseWorks; +#elif defined(RT_OS_SOLARIS) + /** Extra dummy file descriptors to prevent growing file-descriptor table on + * clean up (see @bugref{4650}). */ + int ahDummy[SUPLIB_FLT_DUMMYFILES]; +#elif defined(RT_OS_WINDOWS) +#endif +} SUPLIBDATA; +/** Pointer to the pre-init data. */ +typedef SUPLIBDATA *PSUPLIBDATA; +/** Pointer to const pre-init data. */ +typedef SUPLIBDATA const *PCSUPLIBDATA; + +/** The NIL value of SUPLIBDATA::hDevice. */ +#if defined(RT_OS_WINDOWS) +# define SUP_HDEVICE_NIL NULL +#else +# define SUP_HDEVICE_NIL (-1) +#endif + + +/** + * Pre-init data that is handed over from the hardened executable stub. + */ +typedef struct SUPPREINITDATA +{ + /** Magic value (SUPPREINITDATA_MAGIC). */ + uint32_t u32Magic; + /** The SUPLib instance data. */ + SUPLIBDATA Data; + /** The number of entries in paInstallFiles and paVerifiedFiles. */ + size_t cInstallFiles; + /** g_aSupInstallFiles. */ + PCSUPINSTFILE paInstallFiles; + /** g_aSupVerifiedFiles. */ + PCSUPVERIFIEDFILE paVerifiedFiles; + /** The number of entries in paVerifiedDirs. */ + size_t cVerifiedDirs; + /** g_aSupVerifiedDirs. */ + PCSUPVERIFIEDDIR paVerifiedDirs; + /** Magic value (SUPPREINITDATA_MAGIC). */ + uint32_t u32EndMagic; +} SUPPREINITDATA; +typedef SUPPREINITDATA *PSUPPREINITDATA; +typedef SUPPREINITDATA const *PCSUPPREINITDATA; + +/** Magic value for SUPPREINITDATA::u32Magic and SUPPREINITDATA::u32EndMagic. */ +#define SUPPREINITDATA_MAGIC UINT32_C(0xbeef0001) + +/** @copydoc supR3PreInit */ +typedef DECLCALLBACKTYPE(int, FNSUPR3PREINIT,(PSUPPREINITDATA pPreInitData, uint32_t fFlags)); +/** Pointer to supR3PreInit. */ +typedef FNSUPR3PREINIT *PFNSUPR3PREINIT; + +/** The current SUPR3HardenedMain state / location. */ +typedef enum SUPR3HARDENEDMAINSTATE +{ + SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED = 0, + SUPR3HARDENEDMAINSTATE_WIN_EARLY_INIT_CALLED, + SUPR3HARDENEDMAINSTATE_WIN_EARLY_IMPORTS_RESOLVED, + SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED, + SUPR3HARDENEDMAINSTATE_WIN_EARLY_REAL_DEVICE_OPENED, + SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED, + SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED, + SUPR3HARDENEDMAINSTATE_WIN_VERSION_INITIALIZED, + SUPR3HARDENEDMAINSTATE_WIN_VERIFY_TRUST_READY, + SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED, + SUPR3HARDENEDMAINSTATE_INIT_RUNTIME, + SUPR3HARDENEDMAINSTATE_GET_TRUSTED_MAIN, + SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN, + SUPR3HARDENEDMAINSTATE_END, + SUPR3HARDENEDMAINSTATE_32BIT_HACK = 0x7fffffff +} SUPR3HARDENEDMAINSTATE; + + +/******************************************************************************* +* Global Variables * +*******************************************************************************/ +extern DECL_HIDDEN_DATA(uint32_t) g_u32Cookie; +extern DECL_HIDDEN_DATA(uint32_t) g_u32SessionCookie; +extern DECL_HIDDEN_DATA(uint32_t) g_uSupSessionVersion; +extern DECL_HIDDEN_DATA(SUPLIBDATA) g_supLibData; +extern DECL_HIDDEN_DATA(uint32_t) g_uSupFakeMode; +extern DECL_HIDDEN_DATA(PSUPGLOBALINFOPAGE) g_pSUPGlobalInfoPageR0; +#ifdef VBOX_INCLUDED_SRC_Support_SUPDrvIOC_h +extern DECL_HIDDEN_DATA(PSUPQUERYFUNCS) g_pSupFunctions; +#endif +extern DECL_HIDDEN_DATA(SUPR3HARDENEDMAINSTATE) g_enmSupR3HardenedMainState; +#ifdef RT_OS_WINDOWS +extern DECL_HIDDEN_DATA(bool) g_fSupEarlyProcessInit; +#endif + + +/******************************************************************************* +* OS Specific Function * +*******************************************************************************/ +RT_C_DECLS_BEGIN +DECLHIDDEN(int) suplibOsInstall(void); +DECLHIDDEN(int) suplibOsUninstall(void); +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo); +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis); +DECLHIDDEN(int) suplibOsHardenedVerifyInit(void); +DECLHIDDEN(int) suplibOsHardenedVerifyTerm(void); +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq); +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu); +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages); +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t cPages); +DECLHIDDEN(int) suplibOsQueryVTxSupported(const char **ppszWhy); +DECLHIDDEN(bool) suplibOsIsNemSupportedWhenNoVtxOrAmdV(void); + + +/** + * Performs the pre-initialization of the support library. + * + * This is dynamically resolved and invoked by the static library before it + * calls RTR3InitEx and thereby SUPR3Init. + * + * @returns IPRT status code. + * @param pPreInitData The pre init data. + * @param fFlags The SUPR3HardenedMain flags. + */ +DECL_NOTHROW(DECLEXPORT(int)) supR3PreInit(PSUPPREINITDATA pPreInitData, uint32_t fFlags); + + +/** @copydoc RTPathAppPrivateNoArch */ +DECLHIDDEN(int) supR3HardenedPathAppPrivateNoArch(char *pszPath, size_t cchPath); +/** @copydoc RTPathAppPrivateArch */ +DECLHIDDEN(int) supR3HardenedPathAppPrivateArch(char *pszPath, size_t cchPath); +/** @copydoc RTPathSharedLibs */ +DECLHIDDEN(int) supR3HardenedPathAppSharedLibs(char *pszPath, size_t cchPath); +/** @copydoc RTPathAppDocs */ +DECLHIDDEN(int) supR3HardenedPathAppDocs(char *pszPath, size_t cchPath); +/** @copydoc RTPathExecDir */ +DECLHIDDEN(int) supR3HardenedPathAppBin(char *pszPath, size_t cchPath); +/** @copydoc RTPathFilename */ +DECLHIDDEN(char *) supR3HardenedPathFilename(const char *pszPath); + +/** + * Display a fatal error and try call TrustedError or quit. + */ +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsgV(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, va_list va); + +/** + * Display a fatal error and try call TrustedError or quit. + */ +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsg(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, ...); + +/** + * Display a fatal error and quit. + */ +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalV(const char *pszFormat, va_list va); + +/** + * Display a fatal error and quit. + */ +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatal(const char *pszFormat, ...); + +/** + * Display an error which may or may not be fatal. + */ +DECLHIDDEN(int) supR3HardenedErrorV(int rc, bool fFatal, const char *pszFormat, va_list va); + +/** + * Display an error which may or may not be fatal. + */ +DECLHIDDEN(int) supR3HardenedError(int rc, bool fFatal, const char *pszFormat, ...); + +/** + * Open any startup log file specified in the argument. + */ +DECLHIDDEN(void) supR3HardenedOpenLog(int *pcArgs, char **papszArgs); + +/** + * Write to the startup log file. + */ +DECLHIDDEN(void) supR3HardenedLogV(const char *pszFormat, va_list va); + +/** + * Write to the startup log file. + */ +DECLHIDDEN(void) supR3HardenedLog(const char *pszFormat, ...); + +/** + * Flushes the log file. + */ +DECLHIDDEN(void) supR3HardenedLogFlush(void); + + +DECLHIDDEN(int) supR3HardenedVerifyAll(bool fFatal, const char *pszProgName, const char *pszExePath, uint32_t fMainFlags); +DECLHIDDEN(int) supR3HardenedVerifyFixedDir(SUPINSTDIR enmDir, bool fFatal, PCSUPINSTFILE pFile); +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, bool fMaybe3rdParty, + PRTERRINFO pErrInfo); +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +DECLHIDDEN(int) supR3HardenedVerifyFileFollowSymlinks(const char *pszFilename, RTHCUINTPTR hNativeFile, + bool fMaybe3rdParty, PRTERRINFO pErrInfo); +#endif +DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData); +DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData); + +#ifdef RT_OS_WINDOWS +DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags, bool fAvastKludge); +DECLHIDDEN(void) supR3HardenedWinInitAppBin(uint32_t fFlags); +DECLHIDDEN(void) supR3HardenedWinInitVersion(bool fEarlyInit); +DECLHIDDEN(void) supR3HardenedWinInitImports(void); +DECLHIDDEN(void) supR3HardenedWinModifyDllSearchPath(uint32_t fFlags, const char *pszAppBinPath); +# ifdef IPRT_INCLUDED_nt_nt_h +DECLHIDDEN(void) supR3HardenedWinGetVeryEarlyImports(uintptr_t uNtDllAddr, PFNNTWAITFORSINGLEOBJECT *ppfnNtWaitForSingleObject, + PFNNTSETEVENT *ppfnNtSetEvent); +# endif +DECLHIDDEN(void) supR3HardenedWinInitImportsEarly(uintptr_t uNtDllAddr); +DECLHIDDEN(void) supR3HardenedWinInitSyscalls(bool fReportErrors, PRTERRINFO pErrInfo); +DECLHIDDEN(PFNRT) supR3HardenedWinGetRealDllSymbol(const char *pszDll, const char *pszProcedure); +DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void); +DECLHIDDEN(void) supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(const char *pszProgName); +DECLHIDDEN(void) supR3HardenedWinFlushLoaderCache(); +DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int iWhich, int cArgs, char **papszArgs); +DECLHIDDEN(int) supR3HardenedWinReSpawn(int iWhich); +# ifdef _WINDEF_ +DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT); +# endif +DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags); +extern RTUTF16 g_wszSupLibHardenedExePath[1024]; +# ifdef RTPATH_MAX +extern char g_szSupLibHardenedExePath[RTPATH_MAX]; +# endif +DECLHIDDEN(void) supR3HardenedWinCompactHeaps(void); +DECLHIDDEN(void) supR3HardenedMainOpenDevice(void); +DECLHIDDEN(char *) supR3HardenedWinReadErrorInfoDevice(char *pszErrorInfo, size_t cbErrorInfo, const char *pszPrefix); +DECLHIDDEN(void) supR3HardenedWinReportErrorToParent(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszFormat, va_list va); +#else /* !RT_OS_WINDOWS */ +# if !defined(RT_OS_DARWIN) +DECLHIDDEN(void) supR3HardenedPosixInit(void); +# else /* !RT_OS_DARWIN */ +DECLHIDDEN(void) supR3HardenedDarwinInit(void); +#endif /* !RT_OS_DARWIN */ +#endif /* !RT_OS_WINDOWS */ + +SUPR3DECL(int) supR3PageLock(void *pvStart, size_t cPages, PSUPPAGE paPages); +SUPR3DECL(int) supR3PageUnlock(void *pvStart); + +RT_C_DECLS_END + + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPLibInternal_h */ + diff --git a/src/VBox/HostDrivers/Support/SUPLibLdr.cpp b/src/VBox/HostDrivers/Support/SUPLibLdr.cpp new file mode 100644 index 00000000..2a778fec --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLibLdr.cpp @@ -0,0 +1,1153 @@ +/* $Id: SUPLibLdr.cpp $ */ +/** @file + * VirtualBox Support Library - Loader related bits. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <VBox/sup.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/log.h> +#include <VBox/VBoxTpG.h> + +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/alloca.h> +#include <iprt/ldr.h> +#include <iprt/asm.h> +#include <iprt/mp.h> +#include <iprt/cpuset.h> +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/env.h> +#include <iprt/rand.h> +#include <iprt/x86.h> + +#include "SUPDrvIOC.h" +#include "SUPLibInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** R0 VMM module name. */ +#define VMMR0_NAME "VMMR0" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef DECLCALLBACKTYPE(int, FNCALLVMMR0,(PVMR0 pVMR0, unsigned uOperation, void *pvArg)); +typedef FNCALLVMMR0 *PFNCALLVMMR0; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** VMMR0 Load Address. */ +static RTR0PTR g_pvVMMR0 = NIL_RTR0PTR; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int supLoadModule(const char *pszFilename, const char *pszModule, const char *pszSrvReqHandler, + PRTERRINFO pErrInfo, void **ppvImageBase); +static DECLCALLBACK(int) supLoadModuleResolveImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, + unsigned uSymbol, RTUINTPTR *pValue, void *pvUser); + + +SUPR3DECL(int) SUPR3LoadModule(const char *pszFilename, const char *pszModule, void **ppvImageBase, PRTERRINFO pErrInfo) +{ + /* + * Check that the module can be trusted. + */ + int rc = SUPR3HardenedVerifyPlugIn(pszFilename, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = supLoadModule(pszFilename, pszModule, NULL, pErrInfo, ppvImageBase); + if (RT_FAILURE(rc) && !RTErrInfoIsSet(pErrInfo)) + RTErrInfoSetF(pErrInfo, rc, "SUPR3LoadModule: supLoadModule returned %Rrc", rc); + } + return rc; +} + + +SUPR3DECL(int) SUPR3LoadServiceModule(const char *pszFilename, const char *pszModule, + const char *pszSrvReqHandler, void **ppvImageBase) +{ + AssertPtrReturn(pszSrvReqHandler, VERR_INVALID_PARAMETER); + + /* + * Check that the module can be trusted. + */ + int rc = SUPR3HardenedVerifyPlugIn(pszFilename, NULL /*pErrInfo*/); + if (RT_SUCCESS(rc)) + rc = supLoadModule(pszFilename, pszModule, pszSrvReqHandler, NULL /*pErrInfo*/, ppvImageBase); + else + LogRel(("SUPR3LoadServiceModule: Verification of \"%s\" failed, rc=%Rrc\n", pszFilename, rc)); + return rc; +} + + +/** + * Argument package for supLoadModuleResolveImport. + */ +typedef struct SUPLDRRESIMPARGS +{ + const char *pszModule; + PRTERRINFO pErrInfo; + uint32_t fLoadReq; /**< SUPLDRLOAD_F_XXX */ +} SUPLDRRESIMPARGS, *PSUPLDRRESIMPARGS; + +/** + * Resolve an external symbol during RTLdrGetBits(). + * + * @returns VBox status code. + * @param hLdrMod The loader module handle. + * @param pszModule Module name. + * @param pszSymbol Symbol name, NULL if uSymbol should be used. + * @param uSymbol Symbol ordinal, ~0 if pszSymbol should be used. + * @param pValue Where to store the symbol value (address). + * @param pvUser User argument. + */ +static DECLCALLBACK(int) supLoadModuleResolveImport(RTLDRMOD hLdrMod, const char *pszModule, + const char *pszSymbol, unsigned uSymbol, RTUINTPTR *pValue, void *pvUser) +{ + NOREF(hLdrMod); NOREF(uSymbol); + AssertPtr(pValue); + AssertPtr(pvUser); + PSUPLDRRESIMPARGS pArgs = (PSUPLDRRESIMPARGS)pvUser; + + /* + * Only SUPR0 and VMMR0.r0 + */ + if ( pszModule + && *pszModule + && strcmp(pszModule, "VBoxSup.sys") + && strcmp(pszModule, "VBoxDrv.sys") /* old name */ + && strcmp(pszModule, "VMMR0.r0")) + { +#if defined(RT_OS_WINDOWS) && 0 /* Useful for VMMR0 hacking, not for production use. See also SUPDrv-win.cpp */ + if (strcmp(pszModule, "ntoskrnl.exe") == 0) + { + *pValue = 42; /* Non-zero so ring-0 can find the end of the IAT and exclude it when comparing. */ + return VINF_SUCCESS; + } +#endif + AssertMsgFailed(("%s is importing from %s! (expected 'SUPR0.dll' or 'VMMR0.r0', case-sensitive)\n", pArgs->pszModule, pszModule)); + return RTErrInfoSetF(pArgs->pErrInfo, VERR_SYMBOL_NOT_FOUND, + "Unexpected import module '%s' in '%s'", pszModule, pArgs->pszModule); + } + + /* + * No ordinals. + */ + if (uSymbol != ~0U) + { + AssertMsgFailed(("%s is importing by ordinal (ord=%d)\n", pArgs->pszModule, uSymbol)); + return RTErrInfoSetF(pArgs->pErrInfo, VERR_SYMBOL_NOT_FOUND, + "Unexpected ordinal import (%#x) in '%s'", uSymbol, pArgs->pszModule); + } + + /* + * Lookup symbol. + */ + /* Skip the 64-bit ELF import prefix first. */ + /** @todo is this actually used??? */ + if (!strncmp(pszSymbol, RT_STR_TUPLE("SUPR0$"))) + pszSymbol += sizeof("SUPR0$") - 1; + + /* + * Check the VMMR0.r0 module if loaded. + */ + if (g_pvVMMR0 != NIL_RTR0PTR) + { + void *pvValue; + if (!SUPR3GetSymbolR0((void *)g_pvVMMR0, pszSymbol, &pvValue)) + { + *pValue = (uintptr_t)pvValue; + pArgs->fLoadReq |= SUPLDRLOAD_F_DEP_VMMR0; + return VINF_SUCCESS; + } + } + + /* iterate the function table. */ + int c = g_pSupFunctions->u.Out.cFunctions; + PSUPFUNC pFunc = &g_pSupFunctions->u.Out.aFunctions[0]; + while (c-- > 0) + { + if (!strcmp(pFunc->szName, pszSymbol)) + { + *pValue = (uintptr_t)pFunc->pfn; + return VINF_SUCCESS; + } + pFunc++; + } + + /* + * The GIP. + */ + if ( pszSymbol + && g_pSUPGlobalInfoPage + && g_pSUPGlobalInfoPageR0 + && !strcmp(pszSymbol, "g_SUPGlobalInfoPage") + ) + { + *pValue = (uintptr_t)g_pSUPGlobalInfoPageR0; + return VINF_SUCCESS; + } + + /* + * Symbols that are undefined by convention. + */ +#ifdef RT_OS_SOLARIS + static const char * const s_apszConvSyms[] = + { + "", "mod_getctl", + "", "mod_install", + "", "mod_remove", + "", "mod_info", + "", "mod_miscops", + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_apszConvSyms); i += 2) + { + if ( !RTStrCmp(s_apszConvSyms[i], pszModule) + && !RTStrCmp(s_apszConvSyms[i + 1], pszSymbol)) + { + *pValue = ~(uintptr_t)0; + return VINF_SUCCESS; + } + } +#endif + + /* + * Despair. + */ + c = g_pSupFunctions->u.Out.cFunctions; + pFunc = &g_pSupFunctions->u.Out.aFunctions[0]; + while (c-- > 0) + { + RTAssertMsg2Weak("%d: %s\n", g_pSupFunctions->u.Out.cFunctions - c, pFunc->szName); + pFunc++; + } + RTAssertMsg2Weak("%s is importing %s which we couldn't find\n", pArgs->pszModule, pszSymbol); + + AssertLogRelMsgFailed(("%s is importing %s which we couldn't find\n", pArgs->pszModule, pszSymbol)); + if (g_uSupFakeMode) + { + *pValue = 0xdeadbeef; + return VINF_SUCCESS; + } + return RTErrInfoSetF(pArgs->pErrInfo, VERR_SYMBOL_NOT_FOUND, + "Unable to locate imported symbol '%s%s%s' for module '%s'", + pszModule ? pszModule : "", + pszModule && *pszModule ? "." : "", + pszSymbol, + pArgs->pszModule); +} + + +/** Argument package for supLoadModuleCalcSizeCB. */ +typedef struct SUPLDRCALCSIZEARGS +{ + size_t cbStrings; + uint32_t cSymbols; + size_t cbImage; +} SUPLDRCALCSIZEARGS, *PSUPLDRCALCSIZEARGS; + +/** + * Callback used to calculate the image size. + * @return VINF_SUCCESS + */ +static DECLCALLBACK(int) supLoadModuleCalcSizeCB(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol, RTUINTPTR Value, void *pvUser) +{ + PSUPLDRCALCSIZEARGS pArgs = (PSUPLDRCALCSIZEARGS)pvUser; + if ( pszSymbol != NULL + && *pszSymbol + && Value <= pArgs->cbImage) + { + pArgs->cSymbols++; + pArgs->cbStrings += strlen(pszSymbol) + 1; + } + NOREF(hLdrMod); NOREF(uSymbol); + return VINF_SUCCESS; +} + + +/** Argument package for supLoadModuleCreateTabsCB. */ +typedef struct SUPLDRCREATETABSARGS +{ + size_t cbImage; + PSUPLDRSYM pSym; + char *pszBase; + char *psz; +} SUPLDRCREATETABSARGS, *PSUPLDRCREATETABSARGS; + +/** + * Callback used to calculate the image size. + * @return VINF_SUCCESS + */ +static DECLCALLBACK(int) supLoadModuleCreateTabsCB(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol, RTUINTPTR Value, void *pvUser) +{ + PSUPLDRCREATETABSARGS pArgs = (PSUPLDRCREATETABSARGS)pvUser; + if ( pszSymbol != NULL + && *pszSymbol + && Value <= pArgs->cbImage) + { + pArgs->pSym->offSymbol = (uint32_t)Value; + pArgs->pSym->offName = pArgs->psz - pArgs->pszBase; + pArgs->pSym++; + + size_t cbCopy = strlen(pszSymbol) + 1; + memcpy(pArgs->psz, pszSymbol, cbCopy); + pArgs->psz += cbCopy; + } + NOREF(hLdrMod); NOREF(uSymbol); + return VINF_SUCCESS; +} + + +/** Argument package for supLoadModuleCompileSegmentsCB. */ +typedef struct SUPLDRCOMPSEGTABARGS +{ + uint32_t uStartRva; + uint32_t uEndRva; + uint32_t fProt; + uint32_t iSegs; + uint32_t cSegsAlloc; + PSUPLDRSEG paSegs; + PRTERRINFO pErrInfo; +} SUPLDRCOMPSEGTABARGS, *PSUPLDRCOMPSEGTABARGS; + +/** + * @callback_method_impl{FNRTLDRENUMSEGS, + * Compile list of segments with the same memory protection.} + */ +static DECLCALLBACK(int) supLoadModuleCompileSegmentsCB(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser) +{ + PSUPLDRCOMPSEGTABARGS pArgs = (PSUPLDRCOMPSEGTABARGS)pvUser; + AssertCompile(RTMEM_PROT_READ == SUPLDR_PROT_READ); + AssertCompile(RTMEM_PROT_WRITE == SUPLDR_PROT_WRITE); + AssertCompile(RTMEM_PROT_EXEC == SUPLDR_PROT_EXEC); + RT_NOREF(hLdrMod); + + Log2(("supLoadModuleCompileSegmentsCB: %RTptr/%RTptr LB %RTptr/%RTptr prot %#x %s\n", + pSeg->LinkAddress, pSeg->RVA, pSeg->cbMapped, pSeg->cb, pSeg->fProt, pSeg->pszName)); + + /* Ignore segments not part of the loaded image. */ + if (pSeg->RVA == NIL_RTLDRADDR || pSeg->cbMapped == 0) + { + Log2(("supLoadModuleCompileSegmentsCB: -> skipped\n")); + return VINF_SUCCESS; + } + + /* We currently ASSUME that all relevant segments are in ascending RVA order. */ + AssertReturn(pSeg->RVA >= pArgs->uEndRva, + RTERRINFO_LOG_REL_SET_F(pArgs->pErrInfo, VERR_BAD_EXE_FORMAT, "Out of order segment: %p LB %#zx #%.*s", + pSeg->RVA, pSeg->cb, pSeg->cchName, pSeg->pszName)); + + /* We ASSUME the cbMapped field is implemented. */ + AssertReturn(pSeg->cbMapped != NIL_RTLDRADDR, VERR_INTERNAL_ERROR_2); + AssertReturn(pSeg->cbMapped < _1G, VERR_INTERNAL_ERROR_4); + uint32_t cbMapped = (uint32_t)pSeg->cbMapped; + AssertReturn(pSeg->RVA < _1G, VERR_INTERNAL_ERROR_3); + uint32_t uRvaSeg = (uint32_t)pSeg->RVA; + + /* + * If the protection is the same as the previous segment, + * just update uEndRva and continue. + */ + uint32_t fProt = pSeg->fProt; +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + if (fProt & RTMEM_PROT_EXEC) + fProt |= fProt & RTMEM_PROT_READ; +#endif + if (pSeg->fProt == pArgs->fProt) + { + pArgs->uEndRva = uRvaSeg + cbMapped; + Log2(("supLoadModuleCompileSegmentsCB: -> merged, end %#x\n", pArgs->uEndRva)); + return VINF_SUCCESS; + } + + /* + * The protection differs, so commit current segment and start a new one. + * However, if the new segment and old segment share a page, this becomes + * a little more complicated... + */ + if (pArgs->uStartRva < pArgs->uEndRva) + { + if (((pArgs->uEndRva - 1) >> PAGE_SHIFT) != (uRvaSeg >> PAGE_SHIFT)) + { + /* No common page, so make the new segment start on a page boundrary. */ + cbMapped += uRvaSeg & PAGE_OFFSET_MASK; + uRvaSeg &= ~(uint32_t)PAGE_OFFSET_MASK; + Assert(pArgs->uEndRva <= uRvaSeg); + Log2(("supLoadModuleCompileSegmentsCB: -> new, no common\n")); + } + else if ((fProt & pArgs->fProt) == fProt) + { + /* The current segment includes the memory protections of the + previous, so include the common page in it: */ + uint32_t const cbCommon = PAGE_SIZE - (uRvaSeg & PAGE_OFFSET_MASK); + if (cbCommon >= cbMapped) + { + pArgs->uEndRva = uRvaSeg + cbMapped; + Log2(("supLoadModuleCompileSegmentsCB: -> merge, %#x common, upgrading prot to %#x, end %#x\n", + cbCommon, pArgs->fProt, pArgs->uEndRva)); + return VINF_SUCCESS; /* New segment was smaller than a page. */ + } + cbMapped -= cbCommon; + uRvaSeg += cbCommon; + Assert(pArgs->uEndRva <= uRvaSeg); + Log2(("supLoadModuleCompileSegmentsCB: -> new, %#x common into previous\n", cbCommon)); + } + else if ((fProt & pArgs->fProt) == pArgs->fProt) + { + /* The new segment includes the memory protections of the + previous, so include the common page in it: */ + cbMapped += uRvaSeg & PAGE_OFFSET_MASK; + uRvaSeg &= ~(uint32_t)PAGE_OFFSET_MASK; + if (uRvaSeg == pArgs->uStartRva) + { + pArgs->fProt = fProt; + pArgs->uEndRva = uRvaSeg + cbMapped; + Log2(("supLoadModuleCompileSegmentsCB: -> upgrade current protection, end %#x\n", pArgs->uEndRva)); + return VINF_SUCCESS; /* Current segment was smaller than a page. */ + } + Log2(("supLoadModuleCompileSegmentsCB: -> new, %#x common into new\n", (uint32_t)(pSeg->RVA & PAGE_OFFSET_MASK))); + } + else + { + /* Create a new segment for the common page with the combined protection. */ + Log2(("supLoadModuleCompileSegmentsCB: -> it's complicated...\n")); + pArgs->uEndRva &= ~(uint32_t)PAGE_OFFSET_MASK; + if (pArgs->uEndRva > pArgs->uStartRva) + { + Log2(("supLoadModuleCompileSegmentsCB: SUP Seg #%u: %#x LB %#x prot %#x\n", + pArgs->iSegs, pArgs->uStartRva, pArgs->uEndRva - pArgs->uStartRva, pArgs->fProt)); + if (pArgs->paSegs) + { + AssertReturn(pArgs->iSegs < pArgs->cSegsAlloc, VERR_INTERNAL_ERROR_5); + pArgs->paSegs[pArgs->iSegs].off = pArgs->uStartRva; + pArgs->paSegs[pArgs->iSegs].cb = pArgs->uEndRva - pArgs->uStartRva; + pArgs->paSegs[pArgs->iSegs].fProt = pArgs->fProt; + pArgs->paSegs[pArgs->iSegs].fUnused = 0; + } + pArgs->iSegs++; + pArgs->uStartRva = pArgs->uEndRva; + } + pArgs->fProt |= fProt; + + uint32_t const cbCommon = PAGE_SIZE - (uRvaSeg & PAGE_OFFSET_MASK); + if (cbCommon >= cbMapped) + { + fProt |= pArgs->fProt; + pArgs->uEndRva = uRvaSeg + cbMapped; + return VINF_SUCCESS; /* New segment was smaller than a page. */ + } + cbMapped -= cbCommon; + uRvaSeg += cbCommon; + Assert(uRvaSeg - pArgs->uStartRva == PAGE_SIZE); + } + + /* The current segment should end where the new one starts, no gaps. */ + pArgs->uEndRva = uRvaSeg; + + /* Emit the current segment */ + Log2(("supLoadModuleCompileSegmentsCB: SUP Seg #%u: %#x LB %#x prot %#x\n", + pArgs->iSegs, pArgs->uStartRva, pArgs->uEndRva - pArgs->uStartRva, pArgs->fProt)); + if (pArgs->paSegs) + { + AssertReturn(pArgs->iSegs < pArgs->cSegsAlloc, VERR_INTERNAL_ERROR_5); + pArgs->paSegs[pArgs->iSegs].off = pArgs->uStartRva; + pArgs->paSegs[pArgs->iSegs].cb = pArgs->uEndRva - pArgs->uStartRva; + pArgs->paSegs[pArgs->iSegs].fProt = pArgs->fProt; + pArgs->paSegs[pArgs->iSegs].fUnused = 0; + } + pArgs->iSegs++; + } + /* else: current segment is empty */ + + /* Start the new segment. */ + Assert(!(uRvaSeg & PAGE_OFFSET_MASK)); + pArgs->fProt = fProt; + pArgs->uStartRva = uRvaSeg; + pArgs->uEndRva = uRvaSeg + cbMapped; + return VINF_SUCCESS; +} + + +/** + * Worker for supLoadModule(). + */ +static int supLoadModuleInner(RTLDRMOD hLdrMod, PSUPLDRLOAD pLoadReq, uint32_t cbImageWithEverything, + RTR0PTR uImageBase, size_t cbImage, const char *pszModule, const char *pszFilename, + bool fNativeLoader, bool fIsVMMR0, const char *pszSrvReqHandler, + uint32_t offSymTab, uint32_t cSymbols, + uint32_t offStrTab, size_t cbStrTab, + uint32_t offSegTab, uint32_t cSegments, + PRTERRINFO pErrInfo) +{ + /* + * Get the image bits. + */ + SUPLDRRESIMPARGS Args = { pszModule, pErrInfo, 0 }; + int rc = RTLdrGetBits(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, supLoadModuleResolveImport, &Args); + if (RT_FAILURE(rc)) + { + LogRel(("SUP: RTLdrGetBits failed for %s (%s). rc=%Rrc\n", pszModule, pszFilename, rc)); + if (!RTErrInfoIsSet(pErrInfo)) + RTErrInfoSetF(pErrInfo, rc, "RTLdrGetBits failed"); + return rc; + } + + /* + * Get the entry points. + */ + RTUINTPTR VMMR0EntryFast = 0; + RTUINTPTR VMMR0EntryEx = 0; + RTUINTPTR SrvReqHandler = 0; + RTUINTPTR ModuleInit = 0; + RTUINTPTR ModuleTerm = 0; + const char *pszEp = NULL; + if (fIsVMMR0) + { + rc = RTLdrGetSymbolEx(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, + UINT32_MAX, pszEp = "VMMR0EntryFast", &VMMR0EntryFast); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbolEx(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, + UINT32_MAX, pszEp = "VMMR0EntryEx", &VMMR0EntryEx); + } + else if (pszSrvReqHandler) + rc = RTLdrGetSymbolEx(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, + UINT32_MAX, pszEp = pszSrvReqHandler, &SrvReqHandler); + if (RT_SUCCESS(rc)) + { + int rc2 = RTLdrGetSymbolEx(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, + UINT32_MAX, pszEp = "ModuleInit", &ModuleInit); + if (RT_FAILURE(rc2)) + ModuleInit = 0; + + rc2 = RTLdrGetSymbolEx(hLdrMod, &pLoadReq->u.In.abImage[0], uImageBase, + UINT32_MAX, pszEp = "ModuleTerm", &ModuleTerm); + if (RT_FAILURE(rc2)) + ModuleTerm = 0; + } + if (RT_FAILURE(rc)) + { + LogRel(("SUP: Failed to get entry point '%s' for %s (%s) rc=%Rrc\n", pszEp, pszModule, pszFilename, rc)); + return RTErrInfoSetF(pErrInfo, rc, "Failed to resolve entry point '%s'", pszEp); + } + + /* + * Create the symbol and string tables. + */ + SUPLDRCREATETABSARGS CreateArgs; + CreateArgs.cbImage = cbImage; + CreateArgs.pSym = (PSUPLDRSYM)&pLoadReq->u.In.abImage[offSymTab]; + CreateArgs.pszBase = (char *)&pLoadReq->u.In.abImage[offStrTab]; + CreateArgs.psz = CreateArgs.pszBase; + rc = RTLdrEnumSymbols(hLdrMod, 0, NULL, 0, supLoadModuleCreateTabsCB, &CreateArgs); + if (RT_FAILURE(rc)) + { + LogRel(("SUP: RTLdrEnumSymbols failed for %s (%s) rc=%Rrc\n", pszModule, pszFilename, rc)); + return RTErrInfoSetF(pErrInfo, rc, "RTLdrEnumSymbols #2 failed"); + } + AssertRelease((size_t)(CreateArgs.psz - CreateArgs.pszBase) <= cbStrTab); + AssertRelease((size_t)(CreateArgs.pSym - (PSUPLDRSYM)&pLoadReq->u.In.abImage[offSymTab]) <= cSymbols); + + /* + * Create the segment table. + */ + SUPLDRCOMPSEGTABARGS SegArgs; + SegArgs.uStartRva = 0; + SegArgs.uEndRva = 0; + SegArgs.fProt = RTMEM_PROT_READ; + SegArgs.iSegs = 0; + SegArgs.cSegsAlloc = cSegments; + SegArgs.paSegs = (PSUPLDRSEG)&pLoadReq->u.In.abImage[offSegTab]; + SegArgs.pErrInfo = pErrInfo; + rc = RTLdrEnumSegments(hLdrMod, supLoadModuleCompileSegmentsCB, &SegArgs); + if (RT_FAILURE(rc)) + { + LogRel(("SUP: RTLdrEnumSegments failed for %s (%s) rc=%Rrc\n", pszModule, pszFilename, rc)); + return RTErrInfoSetF(pErrInfo, rc, "RTLdrEnumSegments #2 failed"); + } + SegArgs.uEndRva = (uint32_t)cbImage; + AssertReturn(SegArgs.uEndRva == cbImage, VERR_OUT_OF_RANGE); + if (SegArgs.uEndRva > SegArgs.uStartRva) + { + SegArgs.paSegs[SegArgs.iSegs].off = SegArgs.uStartRva; + SegArgs.paSegs[SegArgs.iSegs].cb = SegArgs.uEndRva - SegArgs.uStartRva; + SegArgs.paSegs[SegArgs.iSegs].fProt = SegArgs.fProt; + SegArgs.paSegs[SegArgs.iSegs].fUnused = 0; + SegArgs.iSegs++; + } + for (uint32_t i = 0; i < SegArgs.iSegs; i++) + LogRel(("SUP: seg #%u: %c%c%c %#010RX32 LB %#010RX32\n", i, /** @todo LogRel2 */ + SegArgs.paSegs[i].fProt & SUPLDR_PROT_READ ? 'R' : ' ', + SegArgs.paSegs[i].fProt & SUPLDR_PROT_WRITE ? 'W' : ' ', + SegArgs.paSegs[i].fProt & SUPLDR_PROT_EXEC ? 'X' : ' ', + SegArgs.paSegs[i].off, SegArgs.paSegs[i].cb)); + AssertRelease(SegArgs.iSegs == cSegments); + AssertRelease(SegArgs.cSegsAlloc == cSegments); + + /* + * Upload the image. + */ + pLoadReq->Hdr.u32Cookie = g_u32Cookie; + pLoadReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pLoadReq->Hdr.cbIn = SUP_IOCTL_LDR_LOAD_SIZE_IN(cbImageWithEverything); + pLoadReq->Hdr.cbOut = SUP_IOCTL_LDR_LOAD_SIZE_OUT; + pLoadReq->Hdr.fFlags = SUPREQHDR_FLAGS_MAGIC | SUPREQHDR_FLAGS_EXTRA_IN; + pLoadReq->Hdr.rc = VERR_INTERNAL_ERROR; + + pLoadReq->u.In.pfnModuleInit = (RTR0PTR)ModuleInit; + pLoadReq->u.In.pfnModuleTerm = (RTR0PTR)ModuleTerm; + if (fIsVMMR0) + { + pLoadReq->u.In.eEPType = SUPLDRLOADEP_VMMR0; + pLoadReq->u.In.EP.VMMR0.pvVMMR0EntryFast = (RTR0PTR)VMMR0EntryFast; + pLoadReq->u.In.EP.VMMR0.pvVMMR0EntryEx = (RTR0PTR)VMMR0EntryEx; + } + else if (pszSrvReqHandler) + { + pLoadReq->u.In.eEPType = SUPLDRLOADEP_SERVICE; + pLoadReq->u.In.EP.Service.pfnServiceReq = (RTR0PTR)SrvReqHandler; + pLoadReq->u.In.EP.Service.apvReserved[0] = NIL_RTR0PTR; + pLoadReq->u.In.EP.Service.apvReserved[1] = NIL_RTR0PTR; + pLoadReq->u.In.EP.Service.apvReserved[2] = NIL_RTR0PTR; + } + else + pLoadReq->u.In.eEPType = SUPLDRLOADEP_NOTHING; + pLoadReq->u.In.offStrTab = offStrTab; + pLoadReq->u.In.cbStrTab = (uint32_t)cbStrTab; + AssertRelease(pLoadReq->u.In.cbStrTab == cbStrTab); + pLoadReq->u.In.cbImageBits = (uint32_t)cbImage; + pLoadReq->u.In.offSymbols = offSymTab; + pLoadReq->u.In.cSymbols = cSymbols; + pLoadReq->u.In.offSegments = offSegTab; + pLoadReq->u.In.cSegments = cSegments; + pLoadReq->u.In.cbImageWithEverything = cbImageWithEverything; + pLoadReq->u.In.pvImageBase = uImageBase; + pLoadReq->u.In.fFlags = Args.fLoadReq; + if (!g_uSupFakeMode) + { + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_LOAD, pLoadReq, SUP_IOCTL_LDR_LOAD_SIZE(cbImageWithEverything)); + if (RT_SUCCESS(rc)) + rc = pLoadReq->Hdr.rc; + else + LogRel(("SUP: SUP_IOCTL_LDR_LOAD ioctl for %s (%s) failed rc=%Rrc\n", pszModule, pszFilename, rc)); + } + else + rc = VINF_SUCCESS; + if ( RT_SUCCESS(rc) + || rc == VERR_ALREADY_LOADED /* A competing process. */ + ) + { + LogRel(("SUP: Loaded %s (%s) at %#RKv - ModuleInit at %RKv and ModuleTerm at %RKv%s\n", + pszModule, pszFilename, uImageBase, (RTR0PTR)ModuleInit, (RTR0PTR)ModuleTerm, + fNativeLoader ? " using the native ring-0 loader" : "")); + if (fIsVMMR0) + { + g_pvVMMR0 = uImageBase; + LogRel(("SUP: VMMR0EntryEx located at %RKv and VMMR0EntryFast at %RKv\n", (RTR0PTR)VMMR0EntryEx, (RTR0PTR)VMMR0EntryFast)); + } +#ifdef RT_OS_WINDOWS + LogRel(("SUP: windbg> .reload /f %s=%#RKv\n", pszFilename, uImageBase)); +#endif + return VINF_SUCCESS; + } + + /* + * Failed, bail out. + */ + LogRel(("SUP: Loading failed for %s (%s) rc=%Rrc\n", pszModule, pszFilename, rc)); + if ( pLoadReq->u.Out.uErrorMagic == SUPLDRLOAD_ERROR_MAGIC + && pLoadReq->u.Out.szError[0] != '\0') + { + LogRel(("SUP: %s\n", pLoadReq->u.Out.szError)); + return RTErrInfoSet(pErrInfo, rc, pLoadReq->u.Out.szError); + } + return RTErrInfoSet(pErrInfo, rc, "SUP_IOCTL_LDR_LOAD failed"); +} + + +/** + * Worker for SUPR3LoadModule(). + * + * @returns VBox status code. + * @param pszFilename Name of the VMMR0 image file + * @param pszModule The modulen name. + * @param pszSrvReqHandler The service request handler symbol name, + * optional. + * @param pErrInfo Where to store detailed error info. Optional. + * @param ppvImageBase Where to return the load address. + */ +static int supLoadModule(const char *pszFilename, const char *pszModule, const char *pszSrvReqHandler, + PRTERRINFO pErrInfo, void **ppvImageBase) +{ + SUPLDROPEN OpenReq; + + /* + * Validate input. + */ + AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszModule, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppvImageBase, VERR_INVALID_PARAMETER); + AssertReturn(strlen(pszModule) < sizeof(OpenReq.u.In.szName), VERR_FILENAME_TOO_LONG); + + const bool fIsVMMR0 = !strcmp(pszModule, "VMMR0.r0"); + AssertReturn(!pszSrvReqHandler || !fIsVMMR0, VERR_INTERNAL_ERROR); + *ppvImageBase = NULL; + + /* + * First try open it w/o preparing a binary for loading. + * + * This will be a lot faster if it's already loaded, and it will + * avoid fixup issues when using wrapped binaries. With wrapped + * ring-0 binaries not all binaries need to be wrapped, so trying + * to load it ourselves is not a bug, but intentional behaviour + * (even it it asserts in the loader code). + */ + OpenReq.Hdr.u32Cookie = g_u32Cookie; + OpenReq.Hdr.u32SessionCookie = g_u32SessionCookie; + OpenReq.Hdr.cbIn = SUP_IOCTL_LDR_OPEN_SIZE_IN; + OpenReq.Hdr.cbOut = SUP_IOCTL_LDR_OPEN_SIZE_OUT; + OpenReq.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + OpenReq.Hdr.rc = VERR_INTERNAL_ERROR; + OpenReq.u.In.cbImageWithEverything = 0; + OpenReq.u.In.cbImageBits = 0; + strcpy(OpenReq.u.In.szName, pszModule); + int rc = RTPathAbs(pszFilename, OpenReq.u.In.szFilename, sizeof(OpenReq.u.In.szFilename)); + if (RT_FAILURE(rc)) + return rc; + if ( (SUPDRV_IOC_VERSION & 0xffff0000) != 0x00300000 + || g_uSupSessionVersion >= 0x00300001) + { + if (!g_uSupFakeMode) + { + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_OPEN, &OpenReq, SUP_IOCTL_LDR_OPEN_SIZE); + if (RT_SUCCESS(rc)) + rc = OpenReq.Hdr.rc; + } + else + { + OpenReq.u.Out.fNeedsLoading = true; + OpenReq.u.Out.pvImageBase = 0xef423420; + } + *ppvImageBase = (void *)OpenReq.u.Out.pvImageBase; + if (rc != VERR_MODULE_NOT_FOUND) + { + if (fIsVMMR0) + g_pvVMMR0 = OpenReq.u.Out.pvImageBase; + LogRel(("SUP: Opened %s (%s) at %#RKv%s.\n", pszModule, pszFilename, OpenReq.u.Out.pvImageBase, + OpenReq.u.Out.fNativeLoader ? " loaded by the native ring-0 loader" : "")); +#ifdef RT_OS_WINDOWS + LogRel(("SUP: windbg> .reload /f %s=%#RKv\n", pszFilename, OpenReq.u.Out.pvImageBase)); +#endif + return rc; + } + } + + /* + * Open image file and figure its size. + */ + RTLDRMOD hLdrMod; + rc = RTLdrOpenEx(OpenReq.u.In.szFilename, 0 /*fFlags*/, RTLDRARCH_HOST, &hLdrMod, pErrInfo); + if (RT_FAILURE(rc)) + { + LogRel(("SUP: RTLdrOpen failed for %s (%s) %Rrc\n", pszModule, OpenReq.u.In.szFilename, rc)); + return rc; + } + + SUPLDRCALCSIZEARGS CalcArgs; + CalcArgs.cbStrings = 0; + CalcArgs.cSymbols = 0; + CalcArgs.cbImage = RTLdrSize(hLdrMod); + rc = RTLdrEnumSymbols(hLdrMod, 0, NULL, 0, supLoadModuleCalcSizeCB, &CalcArgs); + if (RT_SUCCESS(rc)) + { + /* + * Figure out the number of segments needed first. + */ + SUPLDRCOMPSEGTABARGS SegArgs; + SegArgs.uStartRva = 0; + SegArgs.uEndRva = 0; + SegArgs.fProt = RTMEM_PROT_READ; + SegArgs.iSegs = 0; + SegArgs.cSegsAlloc = 0; + SegArgs.paSegs = NULL; + SegArgs.pErrInfo = pErrInfo; + rc = RTLdrEnumSegments(hLdrMod, supLoadModuleCompileSegmentsCB, &SegArgs); + if (RT_SUCCESS(rc)) + { + Assert(SegArgs.uEndRva <= RTLdrSize(hLdrMod)); + SegArgs.uEndRva = (uint32_t)CalcArgs.cbImage; /* overflow is checked later */ + if (SegArgs.uEndRva > SegArgs.uStartRva) + { + Log2(("supLoadModule: SUP Seg #%u: %#x LB %#x prot %#x\n", + SegArgs.iSegs, SegArgs.uStartRva, SegArgs.uEndRva - SegArgs.uStartRva, SegArgs.fProt)); + SegArgs.iSegs++; + } + + const uint32_t offSymTab = RT_ALIGN_32(CalcArgs.cbImage, 8); + const uint32_t offStrTab = offSymTab + CalcArgs.cSymbols * sizeof(SUPLDRSYM); + const uint32_t offSegTab = RT_ALIGN_32(offStrTab + CalcArgs.cbStrings, 8); + const uint32_t cbImageWithEverything = RT_ALIGN_32(offSegTab + sizeof(SUPLDRSEG) * SegArgs.iSegs, 8); + + /* + * Open the R0 image. + */ + OpenReq.Hdr.u32Cookie = g_u32Cookie; + OpenReq.Hdr.u32SessionCookie = g_u32SessionCookie; + OpenReq.Hdr.cbIn = SUP_IOCTL_LDR_OPEN_SIZE_IN; + OpenReq.Hdr.cbOut = SUP_IOCTL_LDR_OPEN_SIZE_OUT; + OpenReq.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + OpenReq.Hdr.rc = VERR_INTERNAL_ERROR; + OpenReq.u.In.cbImageWithEverything = cbImageWithEverything; + OpenReq.u.In.cbImageBits = (uint32_t)CalcArgs.cbImage; + strcpy(OpenReq.u.In.szName, pszModule); + rc = RTPathAbs(pszFilename, OpenReq.u.In.szFilename, sizeof(OpenReq.u.In.szFilename)); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (!g_uSupFakeMode) + { + rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_OPEN, &OpenReq, SUP_IOCTL_LDR_OPEN_SIZE); + if (RT_SUCCESS(rc)) + rc = OpenReq.Hdr.rc; + } + else + { + OpenReq.u.Out.fNeedsLoading = true; + OpenReq.u.Out.pvImageBase = 0xef423420; + } + } + *ppvImageBase = (void *)OpenReq.u.Out.pvImageBase; + if ( RT_SUCCESS(rc) + && OpenReq.u.Out.fNeedsLoading) + { + /* + * We need to load it. + * + * Allocate the request and pass it to an inner work function + * that populates it and sends it off to the driver. + */ + const uint32_t cbLoadReq = SUP_IOCTL_LDR_LOAD_SIZE(cbImageWithEverything); + PSUPLDRLOAD pLoadReq = (PSUPLDRLOAD)RTMemTmpAlloc(cbLoadReq); + if (pLoadReq) + { + rc = supLoadModuleInner(hLdrMod, pLoadReq, cbImageWithEverything, OpenReq.u.Out.pvImageBase, CalcArgs.cbImage, + pszModule, pszFilename, OpenReq.u.Out.fNativeLoader, fIsVMMR0, pszSrvReqHandler, + offSymTab, CalcArgs.cSymbols, + offStrTab, CalcArgs.cbStrings, + offSegTab, SegArgs.iSegs, + pErrInfo); + RTMemTmpFree(pLoadReq); + } + else + { + AssertMsgFailed(("failed to allocated %u bytes for SUPLDRLOAD_IN structure!\n", SUP_IOCTL_LDR_LOAD_SIZE(cbImageWithEverything))); + rc = RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %u bytes for the load request", + SUP_IOCTL_LDR_LOAD_SIZE(cbImageWithEverything)); + } + } + /* + * Already loaded? + */ + else if (RT_SUCCESS(rc)) + { + if (fIsVMMR0) + g_pvVMMR0 = OpenReq.u.Out.pvImageBase; + LogRel(("SUP: Opened %s (%s) at %#RKv%s.\n", pszModule, pszFilename, OpenReq.u.Out.pvImageBase, + OpenReq.u.Out.fNativeLoader ? " loaded by the native ring-0 loader" : "")); +#ifdef RT_OS_WINDOWS + LogRel(("SUP: windbg> .reload /f %s=%#RKv\n", pszFilename, OpenReq.u.Out.pvImageBase)); +#endif + } + /* + * No, failed. + */ + else + RTErrInfoSet(pErrInfo, rc, "SUP_IOCTL_LDR_OPEN failed"); + } + else if (!RTErrInfoIsSet(pErrInfo) && pErrInfo) + RTErrInfoSetF(pErrInfo, rc, "RTLdrEnumSegments #1 failed"); + } + else + RTErrInfoSetF(pErrInfo, rc, "RTLdrEnumSymbols #1 failed"); + RTLdrClose(hLdrMod); + return rc; +} + + +SUPR3DECL(int) SUPR3FreeModule(void *pvImageBase) +{ + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + g_pvVMMR0 = NIL_RTR0PTR; + return VINF_SUCCESS; + } + + /* + * Free the requested module. + */ + SUPLDRFREE Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_LDR_FREE_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_LDR_FREE_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvImageBase = (RTR0PTR)pvImageBase; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_FREE, &Req, SUP_IOCTL_LDR_FREE_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if ( RT_SUCCESS(rc) + && (RTR0PTR)pvImageBase == g_pvVMMR0) + g_pvVMMR0 = NIL_RTR0PTR; + return rc; +} + + +SUPR3DECL(int) SUPR3GetSymbolR0(void *pvImageBase, const char *pszSymbol, void **ppvValue) +{ + *ppvValue = NULL; + + /* fake */ + if (RT_UNLIKELY(g_uSupFakeMode)) + { + *ppvValue = (void *)(uintptr_t)0xdeadf00d; + return VINF_SUCCESS; + } + + /* + * Do ioctl. + */ + SUPLDRGETSYMBOL Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_LDR_GET_SYMBOL_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_LDR_GET_SYMBOL_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.pvImageBase = (RTR0PTR)pvImageBase; + size_t cchSymbol = strlen(pszSymbol); + if (cchSymbol >= sizeof(Req.u.In.szSymbol)) + return VERR_SYMBOL_NOT_FOUND; + memcpy(Req.u.In.szSymbol, pszSymbol, cchSymbol + 1); + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_LDR_GET_SYMBOL, &Req, SUP_IOCTL_LDR_GET_SYMBOL_SIZE); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + if (RT_SUCCESS(rc)) + *ppvValue = (void *)Req.u.Out.pvSymbol; + return rc; +} + + +SUPR3DECL(int) SUPR3LoadVMM(const char *pszFilename, PRTERRINFO pErrInfo) +{ + void *pvImageBase; + return SUPR3LoadModule(pszFilename, "VMMR0.r0", &pvImageBase, pErrInfo); +} + + +SUPR3DECL(int) SUPR3UnloadVMM(void) +{ + return SUPR3FreeModule((void*)g_pvVMMR0); +} + + +/** + * Worker for SUPR3HardenedLdrLoad and SUPR3HardenedLdrLoadAppPriv. + * + * @returns iprt status code. + * @param pszFilename The full file name. + * @param phLdrMod Where to store the handle to the loaded module. + * @param fFlags See RTLDFLAGS_. + * @param pErrInfo Where to return extended error information. + * Optional. + * + */ +static int supR3HardenedLdrLoadIt(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo) +{ +#ifdef VBOX_WITH_HARDENING + /* + * Verify the image file. + */ + int rc = SUPR3HardenedVerifyInit(); + if (RT_FAILURE(rc)) + rc = supR3HardenedVerifyFixedFile(pszFilename, false /* fFatal */); + if (RT_FAILURE(rc)) + { + LogRel(("supR3HardenedLdrLoadIt: Verification of \"%s\" failed, rc=%Rrc\n", pszFilename, rc)); + return RTErrInfoSet(pErrInfo, rc, "supR3HardenedVerifyFixedFile failed"); + } +#endif + + /* + * Try load it. + */ + return RTLdrLoadEx(pszFilename, phLdrMod, fFlags, pErrInfo); +} + + +SUPR3DECL(int) SUPR3HardenedLdrLoad(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + /* + * Validate input. + */ + RTErrInfoClear(pErrInfo); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(phLdrMod, VERR_INVALID_POINTER); + *phLdrMod = NIL_RTLDRMOD; + AssertReturn(RTPathHavePath(pszFilename), VERR_INVALID_PARAMETER); + + /* + * Add the default extension if it's missing. + */ + if (!RTPathHasSuffix(pszFilename)) + { + const char *pszSuff = RTLdrGetSuff(); + size_t cchSuff = strlen(pszSuff); + size_t cchFilename = strlen(pszFilename); + char *psz = (char *)alloca(cchFilename + cchSuff + 1); + AssertReturn(psz, VERR_NO_TMP_MEMORY); + memcpy(psz, pszFilename, cchFilename); + memcpy(psz + cchFilename, pszSuff, cchSuff + 1); + pszFilename = psz; + } + + /* + * Pass it on to the common library loader. + */ + return supR3HardenedLdrLoadIt(pszFilename, phLdrMod, fFlags, pErrInfo); +} + + +SUPR3DECL(int) SUPR3HardenedLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + LogFlow(("SUPR3HardenedLdrLoadAppPriv: pszFilename=%p:{%s} phLdrMod=%p fFlags=%08x pErrInfo=%p\n", pszFilename, pszFilename, phLdrMod, fFlags, pErrInfo)); + + /* + * Validate input. + */ + RTErrInfoClear(pErrInfo); + AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER); + *phLdrMod = NIL_RTLDRMOD; + AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER); + AssertMsgReturn(!RTPathHavePath(pszFilename), ("%s\n", pszFilename), VERR_INVALID_PARAMETER); + + /* + * Check the filename. + */ + size_t cchFilename = strlen(pszFilename); + AssertMsgReturn(cchFilename < (RTPATH_MAX / 4) * 3, ("%zu\n", cchFilename), VERR_INVALID_PARAMETER); + + const char *pszExt = ""; + size_t cchExt = 0; + if (!RTPathHasSuffix(pszFilename)) + { + pszExt = RTLdrGetSuff(); + cchExt = strlen(pszExt); + } + + /* + * Construct the private arch path and check if the file exists. + */ + char szPath[RTPATH_MAX]; + int rc = RTPathAppPrivateArch(szPath, sizeof(szPath) - 1 - cchExt - cchFilename); + AssertRCReturn(rc, rc); + + char *psz = strchr(szPath, '\0'); + *psz++ = RTPATH_SLASH; + memcpy(psz, pszFilename, cchFilename); + psz += cchFilename; + memcpy(psz, pszExt, cchExt + 1); + + if (!RTPathExists(szPath)) + { + LogRel(("SUPR3HardenedLdrLoadAppPriv: \"%s\" not found\n", szPath)); + return VERR_FILE_NOT_FOUND; + } + + /* + * Pass it on to SUPR3HardenedLdrLoad. + */ + rc = SUPR3HardenedLdrLoad(szPath, phLdrMod, fFlags, pErrInfo); + + LogFlow(("SUPR3HardenedLdrLoadAppPriv: returns %Rrc\n", rc)); + return rc; +} + + +SUPR3DECL(int) SUPR3HardenedLdrLoadPlugIn(const char *pszFilename, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + /* + * Validate input. + */ + RTErrInfoClear(pErrInfo); + AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER); + *phLdrMod = NIL_RTLDRMOD; + AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER); + AssertReturn(RTPathStartsWithRoot(pszFilename), VERR_INVALID_PARAMETER); + +#ifdef VBOX_WITH_HARDENING + /* + * Verify the image file. + */ + int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, true /*fMaybe3rdParty*/, pErrInfo); + if (RT_FAILURE(rc)) + { + if (!RTErrInfoIsSet(pErrInfo)) + LogRel(("supR3HardenedVerifyFile: Verification of \"%s\" failed, rc=%Rrc\n", pszFilename, rc)); + return rc; + } +#endif + + /* + * Try load it. + */ + return RTLdrLoadEx(pszFilename, phLdrMod, RTLDRLOAD_FLAGS_LOCAL, pErrInfo); +} + diff --git a/src/VBox/HostDrivers/Support/SUPLibSem.cpp b/src/VBox/HostDrivers/Support/SUPLibSem.cpp new file mode 100644 index 00000000..df11ffde --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLibSem.cpp @@ -0,0 +1,366 @@ +/* $Id: SUPLibSem.cpp $ */ +/** @file + * VirtualBox Support Library - Semaphores, ring-3 implementation. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <VBox/sup.h> + +#include <iprt/errcore.h> +#include <VBox/param.h> +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> + +#include "SUPLibInternal.h" +#include "SUPDrvIOC.h" + + +/** + * Worker that makes a SUP_IOCTL_SEM_OP2 request. + * + * @returns VBox status code. + * @param pSession The session handle. + * @param uType The semaphore type. + * @param hSem The semaphore handle. + * @param uOp The operation. + * @param u64Arg The argument if applicable, otherwise 0. + */ +DECLINLINE(int) supSemOp2(PSUPDRVSESSION pSession, uint32_t uType, uintptr_t hSem, uint32_t uOp, uint64_t u64Arg) +{ + NOREF(pSession); + SUPSEMOP2 Req; + Req.Hdr.u32Cookie = g_u32Cookie; + Req.Hdr.u32SessionCookie = g_u32SessionCookie; + Req.Hdr.cbIn = SUP_IOCTL_SEM_OP2_SIZE_IN; + Req.Hdr.cbOut = SUP_IOCTL_SEM_OP2_SIZE_OUT; + Req.Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + Req.Hdr.rc = VERR_INTERNAL_ERROR; + Req.u.In.uType = uType; + Req.u.In.hSem = (uint32_t)hSem; + AssertReturn(Req.u.In.hSem == hSem, VERR_INVALID_HANDLE); + Req.u.In.uOp = uOp; + Req.u.In.uReserved = 0; + Req.u.In.uArg.u64 = u64Arg; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_SEM_OP2, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + rc = Req.Hdr.rc; + + return rc; +} + + +/** + * Worker that makes a SUP_IOCTL_SEM_OP3 request. + * + * @returns VBox status code. + * @param pSession The session handle. + * @param uType The semaphore type. + * @param hSem The semaphore handle. + * @param uOp The operation. + * @param pReq The request structure. The caller should pick + * the output data from it himself. + */ +DECLINLINE(int) supSemOp3(PSUPDRVSESSION pSession, uint32_t uType, uintptr_t hSem, uint32_t uOp, PSUPSEMOP3 pReq) +{ + NOREF(pSession); + pReq->Hdr.u32Cookie = g_u32Cookie; + pReq->Hdr.u32SessionCookie = g_u32SessionCookie; + pReq->Hdr.cbIn = SUP_IOCTL_SEM_OP3_SIZE_IN; + pReq->Hdr.cbOut = SUP_IOCTL_SEM_OP3_SIZE_OUT; + pReq->Hdr.fFlags = SUPREQHDR_FLAGS_DEFAULT; + pReq->Hdr.rc = VERR_INTERNAL_ERROR; + pReq->u.In.uType = uType; + pReq->u.In.hSem = (uint32_t)hSem; + AssertReturn(pReq->u.In.hSem == hSem, VERR_INVALID_HANDLE); + pReq->u.In.uOp = uOp; + pReq->u.In.u32Reserved = 0; + pReq->u.In.u64Reserved = 0; + int rc = suplibOsIOCtl(&g_supLibData, SUP_IOCTL_SEM_OP3, pReq, sizeof(*pReq)); + if (RT_SUCCESS(rc)) + rc = pReq->Hdr.rc; + + return rc; +} + + +SUPDECL(int) SUPSemEventCreate(PSUPDRVSESSION pSession, PSUPSEMEVENT phEvent) +{ + AssertPtrReturn(phEvent, VERR_INVALID_POINTER); + + int rc; + if (!g_supLibData.fDriverless) + { + SUPSEMOP3 Req; + rc = supSemOp3(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)NIL_SUPSEMEVENT, SUPSEMOP3_CREATE, &Req); + if (RT_SUCCESS(rc)) + *phEvent = (SUPSEMEVENT)(uintptr_t)Req.u.Out.hSem; + } + else + { + RTSEMEVENT hEvent; + rc = RTSemEventCreate(&hEvent); + if (RT_SUCCESS(rc)) + *phEvent = (SUPSEMEVENT)hEvent; + } + return rc; +} + + +SUPDECL(int) SUPSemEventClose(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent) +{ + if (hEvent == NIL_SUPSEMEVENT) + return VINF_SUCCESS; + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)hEvent, SUPSEMOP2_CLOSE, 0); + else + rc = RTSemEventDestroy((RTSEMEVENT)hEvent); + return rc; +} + + +SUPDECL(int) SUPSemEventSignal(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)hEvent, SUPSEMOP2_SIGNAL, 0); + else + rc = RTSemEventSignal((RTSEMEVENT)hEvent); + return rc; +} + + +SUPDECL(int) SUPSemEventWaitNoResume(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint32_t cMillies) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)hEvent, SUPSEMOP2_WAIT_MS_REL, cMillies); + else + rc = RTSemEventWaitNoResume((RTSEMEVENT)hEvent, cMillies); + return rc; +} + + +SUPDECL(int) SUPSemEventWaitNsAbsIntr(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint64_t uNsTimeout) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)hEvent, SUPSEMOP2_WAIT_NS_ABS, uNsTimeout); + else + { +#if 0 + rc = RTSemEventWaitEx((RTSEMEVENT)hEvent, + RTSEMWAIT_FLAGS_ABSOLUTE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_NORESUME, uNsTimeout); +#else + uint64_t nsNow = RTTimeNanoTS(); + if (nsNow < uNsTimeout) + rc = RTSemEventWaitNoResume((RTSEMEVENT)hEvent, (uNsTimeout - nsNow + RT_NS_1MS - 1) / RT_NS_1MS); + else + rc = VERR_TIMEOUT; +#endif + } + return rc; +} + + +SUPDECL(int) SUPSemEventWaitNsRelIntr(PSUPDRVSESSION pSession, SUPSEMEVENT hEvent, uint64_t cNsTimeout) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)hEvent, SUPSEMOP2_WAIT_NS_REL, cNsTimeout); + else + { +#if 0 + rc = RTSemEventWaitEx((RTSEMEVENT)hEvent, + RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_NORESUME, cNsTimeout); +#else + rc = RTSemEventWaitNoResume((RTSEMEVENT)hEvent, (cNsTimeout + RT_NS_1MS - 1) / RT_NS_1MS); +#endif + } + return rc; +} + + +SUPDECL(uint32_t) SUPSemEventGetResolution(PSUPDRVSESSION pSession) +{ + if (!g_supLibData.fDriverless) + { + SUPSEMOP3 Req; + int rc = supSemOp3(pSession, SUP_SEM_TYPE_EVENT, (uintptr_t)NIL_SUPSEMEVENT, SUPSEMOP3_GET_RESOLUTION, &Req); + if (RT_SUCCESS(rc)) + return Req.u.Out.cNsResolution; + return 1000 / 100; + } +#if 0 + return RTSemEventGetResolution(); +#else + return RT_NS_1MS; +#endif +} + + + + + +SUPDECL(int) SUPSemEventMultiCreate(PSUPDRVSESSION pSession, PSUPSEMEVENTMULTI phEventMulti) +{ + AssertPtrReturn(phEventMulti, VERR_INVALID_POINTER); + + int rc; + if (!g_supLibData.fDriverless) + { + SUPSEMOP3 Req; + rc = supSemOp3(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)NIL_SUPSEMEVENTMULTI, SUPSEMOP3_CREATE, &Req); + if (RT_SUCCESS(rc)) + *phEventMulti = (SUPSEMEVENTMULTI)(uintptr_t)Req.u.Out.hSem; + } + else + { + RTSEMEVENTMULTI hEventMulti; + rc = RTSemEventMultiCreate(&hEventMulti); + if (RT_SUCCESS(rc)) + *phEventMulti = (SUPSEMEVENTMULTI)hEventMulti; + } + return rc; +} + + +SUPDECL(int) SUPSemEventMultiClose(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + if (hEventMulti == NIL_SUPSEMEVENTMULTI) + return VINF_SUCCESS; + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_CLOSE, 0); + else + rc = RTSemEventMultiDestroy((RTSEMEVENTMULTI)hEventMulti); + return rc; +} + + +SUPDECL(int) SUPSemEventMultiSignal(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_SIGNAL, 0); + else + rc = RTSemEventMultiSignal((RTSEMEVENTMULTI)hEventMulti); + return rc; +} + + +SUPDECL(int) SUPSemEventMultiReset(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_RESET, 0); + else + rc = RTSemEventMultiReset((RTSEMEVENTMULTI)hEventMulti); + return rc; +} + + +SUPDECL(int) SUPSemEventMultiWaitNoResume(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint32_t cMillies) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_WAIT_MS_REL, cMillies); + else + rc = RTSemEventMultiWaitNoResume((RTSEMEVENTMULTI)hEventMulti, cMillies); + return rc; +} + + +SUPDECL(int) SUPSemEventMultiWaitNsAbsIntr(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint64_t uNsTimeout) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_WAIT_NS_ABS, uNsTimeout); + else + { +#if 0 + rc = RTSemEventMultiWaitEx((RTSEMEVENTMULTI)hEventMulti, + RTSEMWAIT_FLAGS_ABSOLUTE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_NORESUME, uNsTimeout); +#else + uint64_t nsNow = RTTimeNanoTS(); + if (nsNow < uNsTimeout) + rc = RTSemEventMultiWaitNoResume((RTSEMEVENTMULTI)hEventMulti, (uNsTimeout - nsNow + RT_NS_1MS - 1) / RT_NS_1MS); + else + rc = VERR_TIMEOUT; +#endif + } + return rc; +} + + +SUPDECL(int) SUPSemEventMultiWaitNsRelIntr(PSUPDRVSESSION pSession, SUPSEMEVENTMULTI hEventMulti, uint64_t cNsTimeout) +{ + int rc; + if (!g_supLibData.fDriverless) + rc = supSemOp2(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)hEventMulti, SUPSEMOP2_WAIT_NS_REL, cNsTimeout); + else + { +#if 0 + rc = RTSemEventMultiWaitEx((RTSEMEVENTMULTI)hEventMulti, + RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_NORESUME, cNsTimeout); +#else + rc = RTSemEventMultiWaitNoResume((RTSEMEVENTMULTI)hEventMulti, (cNsTimeout + RT_NS_1MS - 1) / RT_NS_1MS); +#endif + } + return rc; +} + + +SUPDECL(uint32_t) SUPSemEventMultiGetResolution(PSUPDRVSESSION pSession) +{ + if (!g_supLibData.fDriverless) + { + SUPSEMOP3 Req; + int rc = supSemOp3(pSession, SUP_SEM_TYPE_EVENT_MULTI, (uintptr_t)NIL_SUPSEMEVENTMULTI, SUPSEMOP3_GET_RESOLUTION, &Req); + if (RT_SUCCESS(rc)) + return Req.u.Out.cNsResolution; + return 1000 / 100; + } +#if 0 + return RTSemEventMultiGetResolution(); +#else + return RT_NS_1MS; +#endif +} + diff --git a/src/VBox/HostDrivers/Support/SUPLibTracerA.asm b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm new file mode 100644 index 00000000..09c7e198 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm @@ -0,0 +1,232 @@ +; $Id: SUPLibTracerA.asm $ +;; @file +; VirtualBox Support Library - Tracer Interface, Assembly bits. +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" +%include "VBox/sup.mac" + +; This should go into asmdefs.mac +%ifdef PIC + %ifdef ASM_FORMAT_ELF + %define RT_ASM_USE_GOT + %define RT_ASM_USE_PLT + %endif +%endif + + +;******************************************************************************* +;* Structures and Typedefs * +;******************************************************************************* +struc SUPREQHDR + .u32Cookie resd 1 + .u32SessionCookie resd 1 + .cbIn resd 1 + .cbOut resd 1 + .fFlags resd 1 + .rc resd 1 +endstruc + +struc SUPTRACERUMODFIREPROBE + .Hdr resb SUPREQHDR_size + .In resb SUPDRVTRACERUSRCTX64_size +endstruc + + +extern NAME(suplibTracerFireProbe) + + +BEGINCODE + + +;; +; Set up a SUPTRACERUMODFIREPROBE request package on the stack and a C helper +; function in SUPLib.cpp to do the rest. +; +EXPORTEDNAME SUPTracerFireProbe + push xBP + mov xBP, xSP + + ; + ; Allocate package and set the sizes (the helper does the rest of + ; the header). Setting the sizes here allows the helper to verify our + ; idea of the request sizes. + ; + lea xSP, [xBP - SUPTRACERUMODFIREPROBE_size - 8] + + mov dword [xSP + SUPTRACERUMODFIREPROBE.Hdr + SUPREQHDR.cbIn], SUPTRACERUMODFIREPROBE_size + mov dword [xSP + SUPTRACERUMODFIREPROBE.Hdr + SUPREQHDR.cbOut], SUPREQHDR_size + +%ifdef RT_ARCH_AMD64 + ; + ; Save the AMD64 context. + ; + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rax], rax + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rcx], rcx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rdx], rdx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rbx], rbx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rsi], rsi + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rdi], rdi + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r8 ], r8 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r9 ], r9 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r10], r10 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r11], r11 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r12], r12 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r13], r13 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r14], r14 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.r15], r15 + pushf + pop xAX + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rflags], xAX + mov xAX, [xBP + xCB] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rip], xAX + mov xAX, [xBP] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rbp], xAX + lea xAX, [xBP + xCB*2] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.rsp], xAX + %ifdef ASM_CALL64_MSC + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.uVtgProbeLoc], rcx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*0], rdx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*1], r8 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*2], r9 + mov xAX, [xBP + xCB*2 + 0x20 + xCB*0] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*3], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*1] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*4], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*2] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*5], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*3] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*6], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*4] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*7], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*5] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*8], xAX + mov xAX, [xBP + xCB*2 + 0x20 + xCB*6] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*9], xAX + mov eax, [xCX + 4] ; VTGPROBELOC::idProbe. + %else + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.uVtgProbeLoc], rdi + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*0], rsi + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*1], rdx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*2], rcx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*3], r8 + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*4], r9 + mov xAX, [xBP + xCB*2 + xCB*0] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*5], xAX + mov xAX, [xBP + xCB*2 + xCB*1] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*6], xAX + mov xAX, [xBP + xCB*2 + xCB*2] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*7], xAX + mov xAX, [xBP + xCB*2 + xCB*3] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*8], xAX + mov xAX, [xBP + xCB*2 + xCB*4] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.u.Amd64.aArgs + xCB*9], xAX + mov eax, [xDI + 4] ; VTGPROBELOC::idProbe. + %endif + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.idProbe], eax + mov dword [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX64.cBits], 64 + + ; + ; Call the helper. + ; + %ifdef ASM_CALL64_MSC + mov xDX, xSP + sub xSP, 0x20 + call NAME(suplibTracerFireProbe) + %else + mov xSI, xSP + %ifdef RT_ASM_USE_PLT + call NAME(suplibTracerFireProbe) wrt ..plt + %else + call NAME(suplibTracerFireProbe) + %endif + %endif + +%elifdef RT_ARCH_X86 + ; + ; Save the X86 context. + ; + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.eax], eax + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.ecx], ecx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.edx], edx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.ebx], ebx + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.esi], esi + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.edi], edi + pushf + pop xAX + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.eflags], xAX + mov xAX, [xBP + xCB] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.eip], xAX + mov xAX, [xBP] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.ebp], xAX + lea xAX, [xBP + xCB*2] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.esp], xAX + + mov xCX, [xBP + xCB*2 + xCB*0] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.uVtgProbeLoc], xCX ; keep, used below. + + mov edx, 20 +.more: + dec edx + mov xAX, [xBP + xCB*2 + xCB*xDX] + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.u.X86.aArgs + xCB*xDX], xAX + jnz .more + + mov eax, [xCX + 4] ; VTGPROBELOC::idProbe. + mov [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.idProbe], eax + mov dword [xSP + SUPTRACERUMODFIREPROBE.In + SUPDRVTRACERUSRCTX32.cBits], 32 + + ; + ; Call the helper. + ; + mov xDX, xSP + push xDX + push xCX + %ifdef RT_ASM_USE_PLT + call NAME(suplibTracerFireProbe) wrt ..plt + %else + call NAME(suplibTracerFireProbe) + %endif +%else + %error "Arch not supported (or correctly defined)." +%endif + + + leave + ret +ENDPROC SUPTracerFireProbe + diff --git a/src/VBox/HostDrivers/Support/SUPR0-asm-files.sed b/src/VBox/HostDrivers/Support/SUPR0-asm-files.sed new file mode 100644 index 00000000..6cdccf0a --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0-asm-files.sed @@ -0,0 +1,66 @@ +# $Id: SUPR0-asm-files.sed $ +## @file +# IPRT - SED script for generating a list of assembly files for make inclusion. +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Header and footer. +1b header +$b footer + +# Drop all lines from the start of the file until the SED: START marker. +1,/SED: START/d + +# Drop all lines from the SED: END marker and till the end of the file. +/SED: END/,$d + +# We are only interested in the SUPEXP_STK_BACK lines. +/^ *SUPEXP_STK_BACKF*(/!d +s/^ *SUPEXP_STK_BACKF*( *[0-9][0-9]* *, *\([^)][^)]*\)),.*$*/ \$(SUPR0_0_OUTDIR)\/StkBack_\1.asm \\/ +b end + +:header +i\# +i\# Autogenerated. DO NOT EDIT! +i\# +i\SUPR0_VBOX_FILES = \\ +d + + +:footer +i +i\# The end. +i +d + +:end diff --git a/src/VBox/HostDrivers/Support/SUPR0-asm.sed b/src/VBox/HostDrivers/Support/SUPR0-asm.sed new file mode 100644 index 00000000..bf278c9b --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0-asm.sed @@ -0,0 +1,62 @@ +# $Id: SUPR0-asm.sed $ +## @file +# IPRT - SED script for generating a list of assembly files for make inclusion. +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Header and footer. +1b header +$b footer + +# Drop all lines from the start of the file until the SED: START marker. +1,/SED: START/d + +# Drop all lines from the SED: END marker and till the end of the file. +/SED: END/,$d + +# We are only interested in the SUPEXP_STK_BACK lines. +/^ *SUPEXP_STK_BACKF*(/!d +s/^ *SUPEXP_STK_BACKF*( *\([0-9][0-9]*\) *, *\([^)][^)]*\)),.*$/\/\/ ##### BEGINFILE \"StkBack_\2.asm\"\n%include "VBox\/SUPR0StackWrapper.mac"\nSUPR0StackWrapperGeneric \2, \1\n\/\/ ##### ENDFILE/ +b end + +:header +i\# +i\# Autogenerated. DO NOT EDIT! +i\# +d + + +:footer +d + +:end diff --git a/src/VBox/HostDrivers/Support/SUPR0-def-lx.sed b/src/VBox/HostDrivers/Support/SUPR0-def-lx.sed new file mode 100644 index 00000000..a46751a4 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0-def-lx.sed @@ -0,0 +1,91 @@ +# $Id: SUPR0-def-lx.sed $ +## @file +# IPRT - SED script for generating SUPR0.def - OS/2 LX. +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Header and footer. +1b header +$b footer + +# Drop all lines from the start of the file until the SED: START marker. +1,/SED: START/d + +# Drop all lines from the SED: END marker and till the end of the file. +/SED: END/,$d + +# Drop all lines not specifying an export. +/^ SUPEXP_/!d + +# Extract the export name from these type of statements: +# SUPEXP_CUSTOM( 0, g_pSUPGlobalInfoPage, &g_pSUPGlobalInfoPage), /* SED: DATA */ +# SUPEXP_STK_OKAY(0, SUPGetGIP), +# SUPEXP_STK_BACK(22, SUPReadTscWithDelta), +# Will be transformed to: +# _g_pSUPGlobalInfoPage /* SED: DATA */ +# _SUPGetGIP +# _SUPReadTscWithDelta +s/SUPEXP_CUSTOM( *[0-9][0-9]* *, *\([^),][^),]*\), [^)]*), */_\1 / +s/SUPEXP_STK_OKAY( *[0-9][0-9]* *, *\([^)][^)]*\)), */_\1 / +s/SUPEXP_STK_BACKF*( *[0-9][0-9]* *, *\([^)][^)]*\)), */_\1 / + +# Handle trailing selection comment (/* solaris-only, os2-only */). +/\*\/ *$/!b transform +/only-os2/b transform +/only-/!b transform +d + +# Remove trailing comments. +:transform +s, */\*.*\*/ *$,, +s, *$,, +b end + +:header +i\; +i\; Autogenerated. DO NOT EDIT! +i\; +i +i\LIBRARY VBoxDrv.sys +i +i\EXPORTS +d + + +:footer +i +i\; The end. +i +d + +:end diff --git a/src/VBox/HostDrivers/Support/SUPR0-def-pe.sed b/src/VBox/HostDrivers/Support/SUPR0-def-pe.sed new file mode 100644 index 00000000..64208ce2 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0-def-pe.sed @@ -0,0 +1,92 @@ +# $Id: SUPR0-def-pe.sed $ +## @file +# IPRT - SED script for generating SUPR0.def - Windows PE. +# + +# +# Copyright (C) 2012-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Header and footer. +1b header +$b footer + +# Drop all lines from the start of the file until the SED: START marker. +1,/SED: START/d + +# Drop all lines from the SED: END marker and till the end of the file. +/SED: END/,$d + +# Drop all lines not specifying an export. +/^ SUPEXP_/!d + +# Extract the export name from these type of statements: +# SUPEXP_CUSTOM( 0, g_pSUPGlobalInfoPage, &g_pSUPGlobalInfoPage), /* SED: DATA */ +# SUPEXP_STK_OKAY(0, SUPGetGIP), +# SUPEXP_STK_BACK(22, SUPReadTscWithDelta), +# Will be transformed to: +# g_pSUPGlobalInfoPage /* SED: DATA */ +# SUPGetGIP +# SUPReadTscWithDelta +s/SUPEXP_CUSTOM( *[0-9][0-9]* *, *\([^),][^),]*\), [^)]*), */\1 / +s/SUPEXP_STK_OKAY( *[0-9][0-9]* *, *\([^)][^)]*\)), */\1 / +s/SUPEXP_STK_BACKF*( *[0-9][0-9]* *, *\([^)][^)]*\)), */\1 / + +# Handle trailing selection comment (/* solaris-only, windows-only */). +/\*\/ *$/!b transform +/only-windows/b transform +/only-/!b transform +d + +# Deal with special /* SED: DATA */ comment. +:transform +s,/\* SED: \([A-Z]*\) \*/, \1, +s, */\*.*\*/ *$,, +s, *$,, +b end + +:header +i\; +i\; Autogenerated. DO NOT EDIT! +i\; +i +i\LIBRARY VBoxSup.sys +i +i\EXPORTS +d + + +:footer +i +i\; The end. +i +d + +:end diff --git a/src/VBox/HostDrivers/Support/SUPR0IdcClient.c b/src/VBox/HostDrivers/Support/SUPR0IdcClient.c new file mode 100644 index 00000000..7f93c473 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0IdcClient.c @@ -0,0 +1,222 @@ +/* $Id: SUPR0IdcClient.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Core. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> +#include <iprt/asm.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static PSUPDRVIDCHANDLE volatile g_pMainHandle = NULL; + + +/** + * Opens the IDC interface of the support driver. + * + * This will perform basic version negotiations and fail if the + * minimum requirements aren't met. + * + * @returns VBox status code. + * @param pHandle The handle structure (output). + * @param uReqVersion The requested version. Pass 0 for default. + * @param uMinVersion The minimum required version. Pass 0 for default. + * @param puSessionVersion Where to store the session version. Optional. + * @param puDriverVersion Where to store the session version. Optional. + * @param puDriverRevision Where to store the SVN revision of the driver. Optional. + */ +SUPR0DECL(int) SUPR0IdcOpen(PSUPDRVIDCHANDLE pHandle, uint32_t uReqVersion, uint32_t uMinVersion, + uint32_t *puSessionVersion, uint32_t *puDriverVersion, uint32_t *puDriverRevision) +{ + unsigned uDefaultMinVersion; + SUPDRVIDCREQCONNECT Req; + int rc; + + /* + * Validate and set failure return values. + */ + AssertPtrReturn(pHandle, VERR_INVALID_POINTER); + pHandle->s.pSession = NULL; + + AssertPtrNullReturn(puSessionVersion, VERR_INVALID_POINTER); + if (puSessionVersion) + *puSessionVersion = 0; + + AssertPtrNullReturn(puDriverVersion, VERR_INVALID_POINTER); + if (puDriverVersion) + *puDriverVersion = 0; + + AssertPtrNullReturn(puDriverRevision, VERR_INVALID_POINTER); + if (puDriverRevision) + *puDriverRevision = 0; + + AssertReturn(!uMinVersion || (uMinVersion & UINT32_C(0xffff0000)) == (SUPDRV_IDC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER); + AssertReturn(!uReqVersion || (uReqVersion & UINT32_C(0xffff0000)) == (SUPDRV_IDC_VERSION & UINT32_C(0xffff0000)), VERR_INVALID_PARAMETER); + + /* + * Handle default version input and enforce minimum requirements made + * by this library. + * + * The clients will pass defaults (0), and only in the case that some + * special API feature was just added will they set an actual version. + * So, this is the place where can easily enforce a minimum IDC version + * on bugs and similar. It corresponds a bit to what SUPR3Init is + * responsible for. + */ + uDefaultMinVersion = SUPDRV_IDC_VERSION & UINT32_C(0xffff0000); + if (!uMinVersion || uMinVersion < uDefaultMinVersion) + uMinVersion = uDefaultMinVersion; + if (!uReqVersion || uReqVersion < uDefaultMinVersion) + uReqVersion = uDefaultMinVersion; + + /* + * Setup the connect request packet and call the OS specific function. + */ + Req.Hdr.cb = sizeof(Req); + Req.Hdr.rc = VERR_WRONG_ORDER; + Req.Hdr.pSession = NULL; + Req.u.In.u32MagicCookie = SUPDRVIDCREQ_CONNECT_MAGIC_COOKIE; + Req.u.In.uMinVersion = uMinVersion; + Req.u.In.uReqVersion = uReqVersion; + rc = supR0IdcNativeOpen(pHandle, &Req); + if (RT_SUCCESS(rc)) + { + pHandle->s.pSession = Req.u.Out.pSession; + if (puSessionVersion) + *puSessionVersion = Req.u.Out.uSessionVersion; + if (puDriverVersion) + *puDriverVersion = Req.u.Out.uDriverVersion; + if (puDriverRevision) + *puDriverRevision = Req.u.Out.uDriverRevision; + + /* + * We don't really trust anyone, make sure the returned + * session and version values actually makes sense. + */ + if ( RT_VALID_PTR(Req.u.Out.pSession) + && Req.u.Out.uSessionVersion >= uMinVersion + && (Req.u.Out.uSessionVersion & UINT32_C(0xffff0000)) == (SUPDRV_IDC_VERSION & UINT32_C(0xffff0000))) + { + ASMAtomicCmpXchgPtr(&g_pMainHandle, pHandle, NULL); + return rc; + } + + AssertMsgFailed(("pSession=%p uSessionVersion=0x%x (r%u)\n", Req.u.Out.pSession, Req.u.Out.uSessionVersion, Req.u.Out.uDriverRevision)); + rc = VERR_VERSION_MISMATCH; + SUPR0IdcClose(pHandle); + } + + return rc; +} + + +/** + * Closes a IDC connection established by SUPR0IdcOpen. + * + * @returns VBox status code. + * @param pHandle The IDC handle. + */ +SUPR0DECL(int) SUPR0IdcClose(PSUPDRVIDCHANDLE pHandle) +{ + SUPDRVIDCREQHDR Req; + int rc; + + /* + * Catch closed handles and check that the session is valid. + */ + AssertPtrReturn(pHandle, VERR_INVALID_POINTER); + if (!pHandle->s.pSession) + return VERR_INVALID_HANDLE; + AssertPtrReturn(pHandle->s.pSession, VERR_INVALID_HANDLE); + + /* + * Create the request and hand it to the OS specific code. + */ + Req.cb = sizeof(Req); + Req.rc = VERR_WRONG_ORDER; + Req.pSession = pHandle->s.pSession; + rc = supR0IdcNativeClose(pHandle, &Req); + if (RT_SUCCESS(rc)) + { + pHandle->s.pSession = NULL; + ASMAtomicCmpXchgPtr(&g_pMainHandle, NULL, pHandle); + } + return rc; +} + + +/** + * Get the SUPDRV session for the IDC connection. + * + * This is for use with SUPDRV and component APIs that requires a valid + * session handle. + * + * @returns The session handle on success, NULL if the IDC handle is invalid. + * + * @param pHandle The IDC handle. + */ +SUPR0DECL(PSUPDRVSESSION) SUPR0IdcGetSession(PSUPDRVIDCHANDLE pHandle) +{ + PSUPDRVSESSION pSession; + AssertPtrReturn(pHandle, NULL); + pSession = pHandle->s.pSession; + AssertPtrReturn(pSession, NULL); + return pSession; +} + + +/** + * Looks up a IDC handle by session. + * + * @returns The IDC handle on success, NULL on failure. + * @param pSession The session to lookup. + * + * @internal + */ +PSUPDRVIDCHANDLE supR0IdcGetHandleFromSession(PSUPDRVSESSION pSession) +{ + PSUPDRVIDCHANDLE pHandle = ASMAtomicUoReadPtrT(&g_pMainHandle, PSUPDRVIDCHANDLE); + if ( RT_VALID_PTR(pHandle) + && pHandle->s.pSession == pSession) + return pHandle; + return NULL; +} + diff --git a/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c b/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c new file mode 100644 index 00000000..98e732c9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c @@ -0,0 +1,101 @@ +/* $Id: SUPR0IdcClientComponent.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Component APIs. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +/** + * Registers a component factory with SUPDRV. + * + * @returns VBox status code. + * @param pHandle The IDC handle. + * @param pFactory The factory to register. + */ +SUPR0DECL(int) SUPR0IdcComponentRegisterFactory(PSUPDRVIDCHANDLE pHandle, PCSUPDRVFACTORY pFactory) +{ + SUPDRVIDCREQCOMPREGFACTORY Req; + + /* + * Validate the handle before we access it. + */ + AssertPtrReturn(pHandle, VERR_INVALID_HANDLE); + AssertPtrReturn(pHandle->s.pSession, VERR_INVALID_HANDLE); + + /* + * Construct and fire off the request. + */ + Req.Hdr.cb = sizeof(Req); + Req.Hdr.rc = VERR_WRONG_ORDER; + Req.Hdr.pSession = pHandle->s.pSession; + Req.u.In.pFactory = pFactory; + + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_COMPONENT_REGISTER_FACTORY, &Req.Hdr); +} + + +/** + * Deregisters a component factory with SUPDRV. + * + * @returns VBox status code. + * @param pHandle The IDC handle. + * @param pFactory The factory to register. + */ +SUPR0DECL(int) SUPR0IdcComponentDeregisterFactory(PSUPDRVIDCHANDLE pHandle, PCSUPDRVFACTORY pFactory) +{ + SUPDRVIDCREQCOMPDEREGFACTORY Req; + + /* + * Validate the handle before we access it. + */ + AssertPtrReturn(pHandle, VERR_INVALID_HANDLE); + AssertPtrReturn(pHandle->s.pSession, VERR_INVALID_HANDLE); + + /* + * Construct and fire off the request. + */ + Req.Hdr.cb = sizeof(Req); + Req.Hdr.rc = VERR_WRONG_ORDER; + Req.Hdr.pSession = pHandle->s.pSession; + Req.u.In.pFactory = pFactory; + + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_COMPONENT_DEREGISTER_FACTORY, &Req.Hdr); +} + diff --git a/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h b/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h new file mode 100644 index 00000000..578ce22e --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h @@ -0,0 +1,80 @@ +/* $Id: SUPR0IdcClientInternal.h $ */ +/** @file + * VirtualBox Support Driver - Internal header for the IDC client library. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPR0IdcClientInternal_h +#define VBOX_INCLUDED_SRC_Support_SUPR0IdcClientInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/types.h> +#include <iprt/assert.h> + +#ifdef RT_OS_WINDOWS +# include <iprt/nt/ntddk.h> +#endif + + +/** + * The hidden part of SUPDRVIDCHANDLE. + */ +struct SUPDRVIDCHANDLEPRIVATE +{ + /** Pointer to the session handle. */ + PSUPDRVSESSION pSession; +# ifdef RT_OS_WINDOWS + /** Pointer to the NT device object. */ + PDEVICE_OBJECT pDeviceObject; + /** Pointer to the NT file object. */ + PFILE_OBJECT pFileObject; +# endif +}; +/** Indicate that the structure is present. */ +#define SUPDRVIDCHANDLEPRIVATE_DECLARED 1 + +#include <VBox/sup.h> +#include "SUPDrvIDC.h" +AssertCompile(RT_SIZEOFMEMB(SUPDRVIDCHANDLE, apvPadding) >= sizeof(struct SUPDRVIDCHANDLEPRIVATE)); + +RT_C_DECLS_BEGIN +PSUPDRVIDCHANDLE supR0IdcGetHandleFromSession(PSUPDRVSESSION pSession); +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq); +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq); +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq); +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPR0IdcClientInternal_h */ + diff --git a/src/VBox/HostDrivers/Support/SUPR0IdcClientStubs.c b/src/VBox/HostDrivers/Support/SUPR0IdcClientStubs.c new file mode 100644 index 00000000..1ca6769f --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR0IdcClientStubs.c @@ -0,0 +1,153 @@ +/* $Id: SUPR0IdcClientStubs.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Stubs for SUPR0 APIs. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> +#include <iprt/asm.h> + + +/** + * Resolves a symbol. + * + * @param pHandle The IDC handle. + * @param ppfn Where to return the address of the symbol. + * @param pszName The name of the symbol. + */ +static void supR0IdcGetSymbol(PSUPDRVIDCHANDLE pHandle, PFNRT *ppfn, const char *pszName) +{ + SUPDRVIDCREQGETSYM Req; + int rc; + + /* + * Create and send a get symbol request. + */ + Req.Hdr.cb = sizeof(Req); + Req.Hdr.rc = VERR_WRONG_ORDER; + Req.Hdr.pSession = pHandle->s.pSession; + Req.u.In.pszSymbol = pszName; + Req.u.In.pszModule = NULL; + rc = supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_GET_SYMBOL, &Req.Hdr); + if (RT_SUCCESS(rc)) + ASMAtomicWritePtr((void * volatile *)ppfn, (void *)(uintptr_t)Req.u.Out.pfnSymbol); +} + + +/** + * Resolves a symbol. + * + * @param pSession The IDC session. + * @param ppfn Where to return the address of the symbol. + * @param pszName The name of the symbol. + */ +static void supR0IdcGetSymbolBySession(PSUPDRVSESSION pSession, PFNRT *ppfn, const char *pszName) +{ + PSUPDRVIDCHANDLE pHandle = supR0IdcGetHandleFromSession(pSession); + if (pHandle) + supR0IdcGetSymbol(pHandle, ppfn, pszName); +} + + +SUPR0DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType, PFNSUPDRVDESTRUCTOR pfnDestructor, void *pvUser1, void *pvUser2) +{ + static DECLCALLBACKPTR(void *, s_pfn,(PSUPDRVSESSION /* pSession */, SUPDRVOBJTYPE /* enmType */, PFNSUPDRVDESTRUCTOR /* pfnDestructor */, void * /* pvUser1 */, void * /* pvUser2 */)); + DECLCALLBACKPTR(void *, pfn,(PSUPDRVSESSION /* pSession */, SUPDRVOBJTYPE /* enmType */, PFNSUPDRVDESTRUCTOR /* pfnDestructor */, void * /* pvUser1 */, void * /* pvUser2 */)); + pfn = s_pfn; + if (!pfn) + { + supR0IdcGetSymbolBySession(pSession, (PFNRT *)&s_pfn, "SUPR0ObjRegister"); + pfn = s_pfn; + if (!pfn) + return NULL; + } + + return pfn(pSession, enmType, pfnDestructor, pvUser1, pvUser2); +} + + +SUPR0DECL(int) SUPR0ObjAddRef(void *pvObj, PSUPDRVSESSION pSession) +{ + static DECLCALLBACKPTR(int, s_pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */)); + DECLCALLBACKPTR(int, pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */)); + pfn = s_pfn; + if (!pfn) + { + supR0IdcGetSymbolBySession(pSession, (PFNRT *)&s_pfn, "SUPR0ObjAddRef"); + pfn = s_pfn; + if (!pfn) + return VERR_NOT_SUPPORTED; + } + + return pfn(pvObj, pSession); +} + + +SUPR0DECL(int) SUPR0ObjRelease(void *pvObj, PSUPDRVSESSION pSession) +{ + static DECLCALLBACKPTR(int, s_pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */)); + DECLCALLBACKPTR(int, pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */)); + pfn = s_pfn; + if (!pfn) + { + supR0IdcGetSymbolBySession(pSession, (PFNRT *)&s_pfn, "SUPR0ObjRelease"); + pfn = s_pfn; + if (!pfn) + return VERR_NOT_SUPPORTED; + } + + return pfn(pvObj, pSession); +} + + +SUPR0DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, const char *pszObjName) +{ + static DECLCALLBACKPTR(int, s_pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */, const char * /* pszObjName */)); + DECLCALLBACKPTR(int, pfn,(void * /* pvObj */, PSUPDRVSESSION /* pSession */, const char * /* pszObjName */)); + pfn = s_pfn; + if (!pfn) + { + supR0IdcGetSymbolBySession(pSession, (PFNRT *)&s_pfn, "SUPR0ObjVerifyAccess"); + pfn = s_pfn; + if (!pfn) + return VERR_NOT_SUPPORTED; + } + + return pfn(pvObj, pSession, pszObjName); +} + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedIPRT.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedIPRT.cpp new file mode 100644 index 00000000..74fca0fc --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedIPRT.cpp @@ -0,0 +1,152 @@ +/* $Id: SUPR3HardenedIPRT.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened Support Routines using IPRT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <VBox/log.h> +#include <iprt/string.h> +#include <iprt/stdarg.h> +#include <iprt/assert.h> +#include <iprt/path.h> +#include <iprt/param.h> + +#include "SUPLibInternal.h" + + +DECLHIDDEN(char *) supR3HardenedPathFilename(const char *pszPath) +{ + return RTPathFilename(pszPath); +} + + +DECLHIDDEN(int) supR3HardenedPathAppPrivateNoArch(char *pszPath, size_t cchPath) +{ + return RTPathAppPrivateNoArch(pszPath, cchPath); +} + + +DECLHIDDEN(int) supR3HardenedPathAppPrivateArch(char *pszPath, size_t cchPath) +{ + return RTPathAppPrivateArch(pszPath, cchPath); +} + + +DECLHIDDEN(int) supR3HardenedPathAppSharedLibs(char *pszPath, size_t cchPath) +{ + return RTPathSharedLibs(pszPath, cchPath); +} + + +DECLHIDDEN(int) supR3HardenedPathAppDocs(char *pszPath, size_t cchPath) +{ + return RTPathAppDocs(pszPath, cchPath); +} + + +DECLHIDDEN(int) supR3HardenedPathAppBin(char *pszPath, size_t cchPath) +{ + return RTPathExecDir(pszPath, cchPath); +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsgV(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, va_list va) +{ + va_list vaCopy; + va_copy(vaCopy, va); + AssertFatalMsgFailed(("%s (rc=%Rrc): %N", pszWhere, rc, pszMsgFmt, &vaCopy)); + NOREF(enmWhat); + /* not reached */ +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsg(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, ...) +{ + va_list va; + va_start(va, pszMsgFmt); + supR3HardenedFatalMsgV(pszWhere, enmWhat, rc, pszMsgFmt, va); + /* not reached */ +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalV(const char *pszFormat, va_list va) +{ + va_list vaCopy; + va_copy(vaCopy, va); + AssertFatalMsgFailed(("%N", pszFormat, &vaCopy)); + /* not reached */ +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatal(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supR3HardenedFatalV(pszFormat, va); + /* not reached */ +} + + +DECLHIDDEN(int) supR3HardenedErrorV(int rc, bool fFatal, const char *pszFormat, va_list va) +{ + if (fFatal) + supR3HardenedFatalV(pszFormat, va); + + va_list vaCopy; + va_copy(vaCopy, va); + AssertLogRelMsgFailed(("%N", pszFormat, &vaCopy)); /** @todo figure out why this ain't working, or at seems to be that way... */ + va_end(vaCopy); + + RTLogRelPrintfV(pszFormat, va); + return rc; +} + + +DECLHIDDEN(int) supR3HardenedError(int rc, bool fFatal, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supR3HardenedErrorV(rc, fFatal, pszFormat, va); + va_end(va); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp new file mode 100644 index 00000000..99b4278a --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp @@ -0,0 +1,2687 @@ +/* $Id: SUPR3HardenedMain.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_hardening %VirtualBox %VM Process Hardening + * + * The %VM process hardening is to prevent malicious software from using + * %VirtualBox as a vehicle to obtain kernel level access. + * + * The %VirtualBox %VMM requires supervisor (kernel) level access to the CPU. + * For both practical and historical reasons, part of the %VMM is realized in + * ring-3, with a rich interface to the kernel part. While the device + * emulations can be executed exclusively in ring-3, we have performance + * optimizations that loads device emulation code into ring-0 and our special + * raw-mode execution context (none VT-x/AMD-V mode) for handling frequent + * operations a lot more efficiently. These share data between all three + * context (ring-3, ring-0 and raw-mode). All this poses a rather broad attack + * surface, which the hardening protects. + * + * The hardening focuses primarily on restricting access to the support driver, + * VBoxDrv or vboxdrv depending on the OS, as it is ultimately the link and + * instigator of the communication between ring-3 and the ring-0 and raw-mode + * contexts. A secondary focus is to make sure malicious code cannot be loaded + * and executed in the %VM process. Exactly how we go about this depends a lot + * on the host OS. + * + * @section sec_hardening_supdrv The Support Driver Interfaces + * + * The support driver has several interfaces thru which it can be accessed: + * - /dev/vboxdrv (win: \\Device\\VBoxDrv) for full unrestricted access. + * Offers a rich I/O control interface, which needs protecting. + * - /dev/vboxdrvu (win: \\Device\\VBoxDrvU) for restricted access, which + * VBoxSVC uses to query VT-x and AMD-V capabilities. This does not + * require protecting, though we limit it to the vboxgroup on some + * systems. + * - \\Device\\VBoxDrvStub on Windows for protecting the second stub + * process and its child, the %VM process. This is an open+close + * interface, only available to partially verified stub processes. + * - \\Device\\VBoxDrvErrorInfo on Windows for obtaining detailed error + * information on a previous attempt to open \\Device\\VBoxDrv or + * \\Device\\VBoxDrvStub. Open, read and close only interface. + * + * The rest of VBox accesses the device interface thru the support library, + * @ref grp_sup "SUPR3" / sup.h. + * + * The support driver also exposes a set of functions and data that other VBox + * ring-0 modules can import from. This includes much of the IPRT we need in + * the ring-0 part of the %VMM and device emulations. + * + * The ring-0 part of the %VMM and device emulations are loaded via the + * #SUPR3LoadModule and #SUPR3LoadServiceModule support library function, which + * both translates to a sequence of I/O controls against /dev/vboxdrv. On + * Windows we use the native kernel loader to load the module, while on the + * other systems ring-3 prepares the bits with help from the IPRT loader code. + * + * + * @section sec_hardening_unix Hardening on UNIX-like OSes + * + * On UNIX-like systems (Solaris, Linux, darwin, freebsd, ...) we put our trust + * in root and that root knows what he/she/it is doing. + * + * We only allow root to get full unrestricted access to the support driver. + * The device node corresponding to unrestricted access (/dev/vboxdrv) is own by + * root and has a 0600 access mode (i.e. only accessible to the owner, root). In + * addition to this file system level restriction, the support driver also + * checks that the effective user ID (EUID) is root when it is being opened. + * + * The %VM processes temporarily assume root privileges using the set-uid-bit on + * the executable with root as owner. In fact, all the files and directories we + * install are owned by root and the wheel (or equivalent gid = 0) group, + * including extension pack files. + * + * The executable with the set-uid-to-root-bit set is a stub binary that has no + * unnecessary library dependencies (only libc, pthreads, dynamic linker) and + * simply calls #SUPR3HardenedMain. It does the following: + * 1. Validate the VirtualBox installation (#supR3HardenedVerifyAll): + * - Check that the executable file of the process is one of the known + * VirtualBox executables. + * - Check that all mandatory files are present. + * - Check that all installed files and directories (both optional and + * mandatory ones) are owned by root:wheel and are not writable by + * anyone except root. + * - Check that all the parent directories, all the way up to the root + * if possible, only permits root (or system admin) to change them. + * This is that to rule out unintentional rename races. + * - On some systems we may also validate the cryptographic signtures + * of executable images. + * + * 2. Open a file descriptor for the support device driver + * (#supR3HardenedMainOpenDevice). + * + * 3. Grab ICMP capabilities for NAT ping support, if required by the OS + * (#supR3HardenedMainGrabCapabilites). + * + * 4. Correctly drop the root privileges + * (#supR3HardenedMainDropPrivileges). + * + * 5. Load the VBoxRT dynamic link library and hand over the file + * descriptor to the support library code in it + * (#supR3HardenedMainInitRuntime). + * + * 6. Load the dynamic library containing the actual %VM front end code and + * run it (tail of #SUPR3HardenedMain). + * + * The set-uid-to-root stub executable is paired with a dynamic link library + * which export one TrustedMain entry point (see #FNSUPTRUSTEDMAIN) that we + * call. In case of error reporting, the library may also export a TrustedError + * function (#FNSUPTRUSTEDERROR). + * + * That the set-uid-to-root-bit modifies the dynamic linker behavior on all + * systems, even after we've dropped back to the real user ID, is something we + * take advantage of. The dynamic linkers takes special care to prevent users + * from using clever tricks to inject their own code into set-uid processes and + * causing privilege escalation issues. This is the exact help we need. + * + * The VirtualBox installation location is hardcoded, which means the any + * dynamic linker paths embedded or inferred from the executable and dynamic + * libraries are also hardcoded. This helps eliminating search path attack + * vectors at the cost of being inflexible regarding installation location. + * + * In addition to what the dynamic linker does for us, the VirtualBox code will + * not directly be calling either RTLdrLoad or dlopen to load dynamic link + * libraries into the process. Instead it will call #SUPR3HardenedLdrLoad, + * #SUPR3HardenedLdrLoadAppPriv and #SUPR3HardenedLdrLoadPlugIn to do the + * loading. These functions will perform the same validations on the file being + * loaded as #SUPR3HardenedMain did in its validation step. So, anything we + * load must be installed with root/wheel as owner/group, the directory we load + * it from must also be owned by root:wheel and now allow for renaming the file. + * Similar ownership restrictions applies to all the parent directories (except + * on darwin). + * + * So, we place the responsibility of not installing malicious software on the + * root user on UNIX-like systems. Which is fair enough, in our opinion. + * + * + * @section sec_hardening_win Hardening on Windows + * + * On Windows we cannot put the same level or trust in the Administrator user(s) + * (equivalent of root/wheel on unix) as on the UNIX-like systems, which + * complicates things greatly. + * + * Some of the blame for this can be given to Windows being a descendant / + * replacement for a set of single user systems: DOS, Windows 1.0-3.11 Windows + * 95-ME, and OS/2. Users of NT 3.1 and later was inclined to want to always + * run it with full root/administrator privileges like they had done on the + * predecessors, while Microsoft didn't provide much incentive for more secure + * alternatives. Bad idea, security wise, but execellent for the security + * software industry. For this reason using a set-uid-to-root approach is + * pointless, even if Windows had one. + * + * So, in order to protect access to the support driver and protect the %VM + * process while it's running we have to do a lot more work. A keystone in the + * defences is cryptographic code signing. Here's the short version of what we + * do: + * - Minimal stub executable, signed with the same certificate as the + * kernel driver. + * + * - The stub executable respawns itself twice, hooking the NTDLL init + * routine to perform protection tasks as early as possible. The parent + * stub helps keep in the child clean for verification as does the + * support driver. + * + * - In order to protect against loading unwanted code into the process, + * the stub processes installs DLL load hooks with NTDLL as well as + * directly intercepting the LdrLoadDll and NtCreateSection APIs. + * + * - The support driver will verify all but the initial process very + * thoroughly before allowing them protection and in the final case full + * unrestricted access. + * + * + * @subsection sec_hardening_win_protsoft 3rd Party "Protection" Software + * + * What makes our life REALLY difficult on Windows is this 3rd party "security" + * software which is more or less required to keep a Windows system safe for + * normal users and all corporate IT departments rightly insists on installing. + * After the kernel patching clampdown in Vista, anti-* software has to do a + * lot more mucking about in user mode to get their job (kind of) done. So, it + * is common practice to patch a lot of NTDLL, KERNEL32, the executable import + * table, load extra DLLs into the process, allocate executable memory in the + * process (classic code injection) and more. + * + * The BIG problem with all this is that it is indistinguishable from what + * malicious software would be doing in order to intercept process activity + * (network sniffing, maybe password snooping) or gain a level of kernel access + * via the support driver. So, the "protection" software is what is currently + * forcing us to do the pre-NTDLL initialization. + * + * + * @subsection sec_hardening_win_1st_stub The Initial Stub Process + * + * We share the stub executable approach with the UNIX-like systems, so there's + * the #SUPR3HardenedMain calling stub executable with its partner DLL exporting + * TrustedMain and TrustedError. However, the stub executable does a lot more, + * while doing it in a more bare metal fashion: + * - It does not use the Microsoft CRT, what we need of CRT functions comes + * from IPRT. + * - It does not statically import anything. This is to avoid having an + * import table that can be patched to intercept our calls or extended to + * load additional DLLs. + * - Direct NT system calls. System calls normally going thru NTDLL, but + * since there is so much software out there which wants to patch known + * NTDLL entry points to control our software (either for good or + * malicious reasons), we do it ourselves. + * + * The initial stub process is not really to be trusted, though we try our best + * to limit potential harm (user mode debugger checks, disable thread creation). + * So, when it enters #SUPR3HardenedMain we only call #supR3HardenedVerifyAll to + * verify the installation (known executables and DLLs, checking their code + * signing signatures, keeping them all open to deny deletion and replacing) and + * does a respawn via #supR3HardenedWinReSpawn. + * + * + * @subsection sec_hardening_win_2nd_stub The Second Stub Process + * + * The second stub process will be created in suspended state, i.e. the main + * thread is suspended before it executes a single instruction. It is also + * created with a less generous ACLs, though this doesn't protect us from admin + * users. In order for #SUPR3HardenedMain to figure that it is the second stub + * process, the zeroth command line argument has been replaced by a known magic + * string (UUID). + * + * Now, before the process starts executing, the parent (initial stub) will + * patch the LdrInitializeThunk entry point in NTDLL to call + * #supR3HardenedEarlyProcessInit via #supR3HardenedEarlyProcessInitThunk. The + * parent will also plant some synchronization stuff via #g_ProcParams (NTDLL + * location, inherited event handles and associated ping-pong equipment). + * + * The LdrInitializeThunk entry point of NTDLL is where the kernel sets up + * process execution to start executing (via a user alert, so it is not subject + * to SetThreadContext). LdrInitializeThunk performs process, NTDLL and + * sub-system client (kernel32) initialization. A lot of "protection" software + * uses triggers in this initialization sequence (like the KERNEL32.DLL load + * event), so we avoid quite a bit of problems by getting our stuff done early + * on. + * + * However, there are also those that uses events that triggers immediately when + * the process is created or/and starts executing the first instruction. But we + * can easily counter these as we have a known process state we can restore. So, + * the first thing that #supR3HardenedEarlyProcessInit does is to signal the + * parent to perform a child purification, so the potentially evil influences + * can be exorcised. + * + * What the parent does during the purification is very similar to what the + * kernel driver will do later on when verifying the second stub and the %VM + * processes, except that instead of failing when encountering an shortcoming it + * will take corrective actions: + * - Executable memory regions not belonging to a DLL mapping will be + * attempted freed, and we'll only fail if we can't evict them. + * - All pages in the executable images in the process (should be just the + * stub executable and NTDLL) will be compared to the pristine fixed-up + * copy prepared by the IPRT PE loader code, restoring any bytes which + * appears differently in the child. (#g_ProcParams is exempted, + * LdrInitializeThunk is set to call NtTerminateThread.) + * - Unwanted DLLs will be unloaded (we have a set of DLLs we like). + * + * Before signalling the second stub process that it has been purified and should + * get on with it, the parent will close all handles with unrestricted access to + * the process and thread so that the initial stub process no longer can + * influence the child in any really harmful way. (The caller of CreateProcess + * usually receives handles with unrestricted access to the child process and + * its main thread. These could in theory be used with DuplicateHandle or + * WriteProcessMemory to get at the %VM process if we're not careful.) + * + * #supR3HardenedEarlyProcessInit will continue with opening the log file + * (requires command line parsing). It will continue to initialize a bunch of + * global variables, system calls and trustworthy/harmless NTDLL imports. + * #supR3HardenedWinInit is then called to setup image verification, that is: + * - Hook the NtCreateSection entry point in NTDLL so we can check all + * executable mappings before they're created and can be mapped. The + * NtCreateSection code jumps to #supR3HardenedMonitor_NtCreateSection. + * - Hook (ditto) the LdrLoadDll entry point in NTDLL so we can + * pre-validate all images that gets loaded the normal way (partly + * because the NtCreateSection context is restrictive because the NTDLL + * loader lock is usually held, which prevents us from safely calling + * WinVerityTrust). The LdrLoadDll code jumps to + * #supR3HardenedMonitor_LdrLoadDll. + * + * The image/DLL verification hooks are at this point able to verify DLLs + * containing embedded code signing signatures, and will restrict the locations + * from which DLLs will be loaded. When #SUPR3HardenedMain gets going later on, + * they will start insisting on everything having valid signatures, either + * embedded or in a signed installer catalog file. + * + * The function also irrevocably disables debug notifications related to the + * current thread, just to make attaching a debugging that much more difficult + * and less useful. + * + * Now, the second stub process will open the so called stub device + * (\\Device\\VBoxDrvStub), that is a special support driver device node that + * tells the support driver to: + * - Protect the process against the OpenProcess and OpenThread attack + * vectors by stripping risky access rights. + * - Check that the process isn't being debugged. + * - Check that the process contains exactly one thread. + * - Check that the process doesn't have any unknown DLLs loaded into it. + * - Check that the process doesn't have any executable memory (other than + * DLL sections) in it. + * - Check that the process executable is a known VBox executable which may + * access the support driver. + * - Check that the process executable is signed with the same code signing + * certificate as the driver and that the on disk image is valid + * according to its embedded signature. + * - Check all the signature of all DLLs in the process (NTDLL) if they are + * signed, and only accept unsigned ones in versions where they are known + * not to be signed. + * - Check that the code and readonly parts of the executable and DLLs + * mapped into the process matches the on disk content (no patches other + * than our own two in NTDLL are allowed). + * + * Once granted access to the stub device, #supR3HardenedEarlyProcessInit will + * restore the LdrInitializeThunk code and let the process perform normal + * initialization. Leading us to #SUPR3HardenedMain where we detect that this + * is the 2nd stub process and does another respawn. + * + * + * @subsection sec_hardening_win_3rd_stub The Final Stub / VM Process + * + * The third stub process is what becomes the %VM process. Because the parent + * has opened \\Device\\VBoxDrvSub, it is protected from malicious OpenProcess & + * OpenThread calls from the moment of inception, practically speaking. + * + * It goes thru the same suspended creation, patching, purification and such as + * its parent (the second stub process). However, instead of opening + * \\Device\\VBoxDrvStub from #supR3HardenedEarlyProcessInit, it opens the + * support driver for full unrestricted access, i.e. \\Device\\VBoxDrv. + * + * The support driver will perform the same checks as it did when + * \\Device\\VBoxDrvStub was opened, but in addition it will: + * - Check that the process is the first child of a process that opened + * \\Device\\VBoxDrvStub. + * - Check that the parent process is still alive. + * - Scan all open handles in the system for potentially harmful ones to + * the process or the primary thread. + * + * Knowing that the process is genuinly signed with the same certificate as the + * kernel driver, and the exectuable code in the process is either shipped by us + * or Microsoft, the support driver will trust it with full access and to keep + * the handle secure. + * + * We also trust the protection the support driver gives the process to keep out + * malicious ring-3 code, and therefore any code, patching or other mysterious + * stuff that enteres the process must be from kernel mode and that we can trust + * it (the alternative interpretation is that the kernel has been breanched + * already, which isn't our responsibility). This means that, the anti-software + * products can do whatever they like from this point on. However, should they + * do unrevertable changes to the process before this point, VirtualBox won't + * work. + * + * As in the second stub process, we'll now do normal process initialization and + * #SUPR3HardenedMain will take control. It will detect that it is being called + * by the 3rd stub process because of a different magic string starting the + * command line, and not respawn itself any more. #SUPR3HardenedMain will + * recheck the VirtualBox installation, keeping all known files open just like + * in two previous stub processes. + * + * It will then load the Windows cryptographic API and load the trusted root + * certificates from the Windows store. The API enables using installation + * catalog files for signature checking as well as providing a second + * verification in addition to our own implementation (IPRT). The certificates + * allows our signature validation implementation to validate all embedded + * signatures, not just the microsoft ones and the one signed by our own + * certificate. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_OS2) +# define INCL_BASE +# define INCL_ERRORS +# include <os2.h> +# include <stdio.h> +# include <stdlib.h> +# include <dlfcn.h> +# include <unistd.h> + +#elif RT_OS_WINDOWS +# include <iprt/nt/nt-and-windows.h> + +#else /* UNIXes */ +# ifdef RT_OS_DARWIN +# define _POSIX_C_SOURCE 1 /* pick the correct prototype for unsetenv. */ +# endif +# include <iprt/types.h> /* stdint fun on darwin. */ + +# include <stdio.h> +# include <stdlib.h> +# include <dlfcn.h> +# include <limits.h> +# include <errno.h> +# include <unistd.h> +# include <sys/stat.h> +# include <sys/time.h> +# include <sys/types.h> +# if defined(RT_OS_LINUX) +# undef USE_LIB_PCAP /* don't depend on libcap as we had to depend on either + libcap1 or libcap2 */ + +# undef _POSIX_SOURCE +# include <linux/types.h> /* sys/capabilities from uek-headers require this */ +# include <sys/capability.h> +# include <sys/prctl.h> +# ifndef CAP_TO_MASK +# define CAP_TO_MASK(cap) RT_BIT(cap) +# endif +# elif defined(RT_OS_FREEBSD) +# include <sys/param.h> +# include <sys/sysctl.h> +# elif defined(RT_OS_SOLARIS) +# include <priv.h> +# endif +# include <pwd.h> +# ifdef RT_OS_DARWIN +# include <mach-o/dyld.h> +# endif + +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#ifdef RT_OS_WINDOWS +# include <VBox/version.h> +# include <iprt/utf16.h> +#endif +#include <iprt/ctype.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> + +#include "SUPLibInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* This mess is temporary after eliminating a define duplicated in SUPLibInternal.h. */ +#if !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_L4) +# ifndef SUP_HARDENED_SUID +# error "SUP_HARDENED_SUID is NOT defined?!?" +# endif +#else +# ifdef SUP_HARDENED_SUID +# error "SUP_HARDENED_SUID is defined?!?" +# endif +#endif + +/** @def SUP_HARDENED_SYM + * Decorate a symbol that's resolved dynamically. + */ +#ifdef RT_OS_OS2 +# define SUP_HARDENED_SYM(sym) "_" sym +#else +# define SUP_HARDENED_SYM(sym) sym +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** @see RTR3InitEx */ +typedef DECLCALLBACKTYPE(int, FNRTR3INITEX,(uint32_t iVersion, uint32_t fFlags, int cArgs, + char **papszArgs, const char *pszProgramPath)); +typedef FNRTR3INITEX *PFNRTR3INITEX; + +/** @see RTLogRelPrintf */ +typedef DECLCALLBACKTYPE(void, FNRTLOGRELPRINTF,(const char *pszFormat, ...)); +typedef FNRTLOGRELPRINTF *PFNRTLOGRELPRINTF; + + +/** + * Descriptor of an environment variable to purge. + */ +typedef struct SUPENVPURGEDESC +{ + /** Name of the environment variable to purge. */ + const char *pszEnv; + /** The length of the variable name. */ + uint8_t cchEnv; + /** Flag whether a failure in purging the variable leads to + * a fatal error resulting in an process exit. */ + bool fPurgeErrFatal; +} SUPENVPURGEDESC; +/** Pointer to a environment variable purge descriptor. */ +typedef SUPENVPURGEDESC *PSUPENVPURGEDESC; +/** Pointer to a const environment variable purge descriptor. */ +typedef const SUPENVPURGEDESC *PCSUPENVPURGEDESC; + +/** + * Descriptor of an command line argument to purge. + */ +typedef struct SUPARGPURGEDESC +{ + /** Name of the argument to purge. */ + const char *pszArg; + /** The length of the argument name. */ + uint8_t cchArg; + /** Flag whether the argument is followed by an extra argument + * which must be purged too */ + bool fTakesValue; +} SUPARGPURGEDESC; +/** Pointer to a environment variable purge descriptor. */ +typedef SUPARGPURGEDESC *PSUPARGPURGEDESC; +/** Pointer to a const environment variable purge descriptor. */ +typedef const SUPARGPURGEDESC *PCSUPARGPURGEDESC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The pre-init data we pass on to SUPR3 (residing in VBoxRT). */ +static SUPPREINITDATA g_SupPreInitData; +/** The program executable path. */ +#ifndef RT_OS_WINDOWS +static +#endif +char g_szSupLibHardenedExePath[RTPATH_MAX]; +/** The application bin directory path. */ +static char g_szSupLibHardenedAppBinPath[RTPATH_MAX]; +/** The offset into g_szSupLibHardenedExePath of the executable name. */ +static size_t g_offSupLibHardenedExecName; +/** The length of the executable name in g_szSupLibHardenedExePath. */ +static size_t g_cchSupLibHardenedExecName; + +/** The program name. */ +static const char *g_pszSupLibHardenedProgName; +/** The flags passed to SUPR3HardenedMain - SUPSECMAIN_FLAGS_XXX. */ +static uint32_t g_fSupHardenedMain; + +#ifdef SUP_HARDENED_SUID +/** The real UID at startup. */ +static uid_t g_uid; +/** The real GID at startup. */ +static gid_t g_gid; +# ifdef RT_OS_LINUX +static uint32_t g_uCaps; +static uint32_t g_uCapsVersion; +# endif +#endif + +/** The startup log file. */ +#ifdef RT_OS_WINDOWS +static HANDLE g_hStartupLog = NULL; +#else +static int g_hStartupLog = -1; +#endif +/** The number of bytes we've written to the startup log. */ +static uint32_t volatile g_cbStartupLog = 0; + +/** The current SUPR3HardenedMain state / location. */ +SUPR3HARDENEDMAINSTATE g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED; +AssertCompileSize(g_enmSupR3HardenedMainState, sizeof(uint32_t)); + +#ifdef RT_OS_WINDOWS +/** Pointer to VBoxRT's RTLogRelPrintf function so we can write errors to the + * release log at runtime. */ +static PFNRTLOGRELPRINTF g_pfnRTLogRelPrintf = NULL; +/** Log volume name (for attempting volume flush). */ +static RTUTF16 g_wszStartupLogVol[16]; +#endif + +/** Environment variables to purge from the process because + * they are known to be harmful. */ +static const SUPENVPURGEDESC g_aSupEnvPurgeDescs[] = +{ + /* pszEnv fPurgeErrFatal */ + /* Qt related environment variables: */ + { RT_STR_TUPLE("QT_QPA_PLATFORM_PLUGIN_PATH"), true }, + { RT_STR_TUPLE("QT_PLUGIN_PATH"), true }, + /* ALSA related environment variables: */ + { RT_STR_TUPLE("ALSA_MIXER_SIMPLE_MODULES"), true }, + { RT_STR_TUPLE("LADSPA_PATH"), true }, +}; + +/** Arguments to purge from the argument vector because + * they are known to be harmful. */ +static const SUPARGPURGEDESC g_aSupArgPurgeDescs[] = +{ + /* pszArg fTakesValue */ + /* Qt related environment variables: */ + { RT_STR_TUPLE("-platformpluginpath"), true }, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef SUP_HARDENED_SUID +static void supR3HardenedMainDropPrivileges(void); +#endif +static PFNSUPTRUSTEDERROR supR3HardenedMainGetTrustedError(const char *pszProgName); + + +/** + * Safely copy one or more strings into the given buffer. + * + * @returns VINF_SUCCESS or VERR_BUFFER_OVERFLOW. + * @param pszDst The destionation buffer. + * @param cbDst The size of the destination buffer. + * @param ... One or more zero terminated strings, ending with + * a NULL. + */ +static int suplibHardenedStrCopyEx(char *pszDst, size_t cbDst, ...) +{ + int rc = VINF_SUCCESS; + + if (cbDst == 0) + return VERR_BUFFER_OVERFLOW; + + va_list va; + va_start(va, cbDst); + for (;;) + { + const char *pszSrc = va_arg(va, const char *); + if (!pszSrc) + break; + + size_t cchSrc = suplibHardenedStrLen(pszSrc); + if (cchSrc < cbDst) + { + suplibHardenedMemCopy(pszDst, pszSrc, cchSrc); + pszDst += cchSrc; + cbDst -= cchSrc; + } + else + { + rc = VERR_BUFFER_OVERFLOW; + if (cbDst > 1) + { + suplibHardenedMemCopy(pszDst, pszSrc, cbDst - 1); + pszDst += cbDst - 1; + cbDst = 1; + } + } + *pszDst = '\0'; + } + va_end(va); + + return rc; +} + + +/** + * Exit current process in the quickest possible fashion. + * + * @param rcExit The exit code. + */ +DECLHIDDEN(DECL_NO_RETURN(void)) suplibHardenedExit(RTEXITCODE rcExit) +{ + for (;;) + { +#ifdef RT_OS_WINDOWS + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + ExitProcess(rcExit); + if (RtlExitUserProcess != NULL) + RtlExitUserProcess(rcExit); + NtTerminateProcess(NtCurrentProcess(), rcExit); +#else + _Exit(rcExit); +#endif + } +} + + +/** + * Writes a substring to standard error. + * + * @param pch The start of the substring. + * @param cch The length of the substring. + */ +static void suplibHardenedPrintStrN(const char *pch, size_t cch) +{ +#ifdef RT_OS_WINDOWS + HANDLE hStdOut = NtCurrentPeb()->ProcessParameters->StandardOutput; + if (hStdOut != NULL) + { + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + { + DWORD cbWritten; + WriteFile(hStdOut, pch, (DWORD)cch, &cbWritten, NULL); + } + /* Windows 7 and earlier uses fake handles, with the last two bits set ((hStdOut & 3) == 3). */ + else if (NtWriteFile != NULL && ((uintptr_t)hStdOut & 3) == 0) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NtWriteFile(hStdOut, NULL /*Event*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, (PVOID)pch, (ULONG)cch, NULL /*ByteOffset*/, NULL /*Key*/); + } + } +#else + int res = write(2, pch, cch); + NOREF(res); +#endif +} + + +/** + * Writes a string to standard error. + * + * @param psz The string. + */ +static void suplibHardenedPrintStr(const char *psz) +{ + suplibHardenedPrintStrN(psz, suplibHardenedStrLen(psz)); +} + + +/** + * Writes a char to standard error. + * + * @param ch The character value to write. + */ +static void suplibHardenedPrintChr(char ch) +{ + suplibHardenedPrintStrN(&ch, 1); +} + +#ifndef IPRT_NO_CRT + +/** + * Writes a decimal number to stdard error. + * + * @param uValue The value. + */ +static void suplibHardenedPrintDecimal(uint64_t uValue) +{ + char szBuf[64]; + char *pszEnd = &szBuf[sizeof(szBuf) - 1]; + char *psz = pszEnd; + + *psz-- = '\0'; + + do + { + *psz-- = '0' + (uValue % 10); + uValue /= 10; + } while (uValue > 0); + + psz++; + suplibHardenedPrintStrN(psz, pszEnd - psz); +} + + +/** + * Writes a hexadecimal or octal number to standard error. + * + * @param uValue The value. + * @param uBase The base (16 or 8). + * @param fFlags Format flags. + */ +static void suplibHardenedPrintHexOctal(uint64_t uValue, unsigned uBase, uint32_t fFlags) +{ + static char const s_achDigitsLower[17] = "0123456789abcdef"; + static char const s_achDigitsUpper[17] = "0123456789ABCDEF"; + const char *pchDigits = !(fFlags & RTSTR_F_CAPITAL) ? s_achDigitsLower : s_achDigitsUpper; + unsigned cShift = uBase == 16 ? 4 : 3; + unsigned fDigitMask = uBase == 16 ? 0xf : 7; + char szBuf[64]; + char *pszEnd = &szBuf[sizeof(szBuf) - 1]; + char *psz = pszEnd; + + *psz-- = '\0'; + + do + { + *psz-- = pchDigits[uValue & fDigitMask]; + uValue >>= cShift; + } while (uValue > 0); + + if ((fFlags & RTSTR_F_SPECIAL) && uBase == 16) + { + *psz-- = !(fFlags & RTSTR_F_CAPITAL) ? 'x' : 'X'; + *psz-- = '0'; + } + + psz++; + suplibHardenedPrintStrN(psz, pszEnd - psz); +} + + +/** + * 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); + } + } +} + +#else /* 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 + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + 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. + * @param va Arguments to format. + */ +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. + */ + char ch; + const char *pszLast = pszFormat; + for (;;) + { + ch = *pszFormat; + if (!ch) + break; + pszFormat++; + + if (ch == '%') + { + /* + * Format argument. + */ + + /* Flush unwritten bits. */ + if (pszLast != pszFormat - 1) + suplibHardenedPrintStrN(pszLast, pszFormat - pszLast - 1); + pszLast = pszFormat; + ch = *pszFormat++; + + /* flags. */ + uint32_t fFlags = 0; + for (;;) + { + if (ch == '#') fFlags |= RTSTR_F_SPECIAL; + else if (ch == '-') fFlags |= RTSTR_F_LEFT; + else if (ch == '+') fFlags |= RTSTR_F_PLUS; + else if (ch == ' ') fFlags |= RTSTR_F_BLANK; + else if (ch == '0') fFlags |= RTSTR_F_ZEROPAD; + else if (ch == '\'') fFlags |= RTSTR_F_THOUSAND_SEP; + else break; + ch = *pszFormat++; + } + + /* Width and precision - ignored. */ + while (RT_C_IS_DIGIT(ch)) + ch = *pszFormat++; + if (ch == '*') + va_arg(va, int); + if (ch == '.') + { + do ch = *pszFormat++; + while (RT_C_IS_DIGIT(ch)); + if (ch == '*') + va_arg(va, int); + } + + /* Size. */ + char chArgSize = 0; + switch (ch) + { + case 'z': + case 'L': + case 'j': + case 't': + chArgSize = ch; + ch = *pszFormat++; + break; + + case 'l': + chArgSize = ch; + ch = *pszFormat++; + if (ch == 'l') + { + chArgSize = 'L'; + ch = *pszFormat++; + } + break; + + case 'h': + chArgSize = ch; + ch = *pszFormat++; + if (ch == 'h') + { + chArgSize = 'H'; + ch = *pszFormat++; + } + break; + } + + /* + * Do type specific formatting. + */ + switch (ch) + { + case 'c': + ch = (char)va_arg(va, int); + suplibHardenedPrintChr(ch); + break; + + case 's': + 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': + { + int64_t iValue; + if (chArgSize == 'L' || chArgSize == 'j') + iValue = va_arg(va, int64_t); + else if (chArgSize == 'l') + iValue = va_arg(va, signed long); + else if (chArgSize == 'z' || chArgSize == 't') + iValue = va_arg(va, intptr_t); + else + iValue = va_arg(va, signed int); + if (iValue < 0) + { + suplibHardenedPrintChr('-'); + iValue = -iValue; + } + suplibHardenedPrintDecimal(iValue); + break; + } + + case 'p': + case 'x': + case 'X': + case 'u': + case 'o': + { + unsigned uBase = 10; + uint64_t uValue; + + switch (ch) + { + case 'p': + fFlags |= RTSTR_F_ZEROPAD; /* Note not standard behaviour (but I like it this way!) */ + uBase = 16; + break; + case 'X': + fFlags |= RTSTR_F_CAPITAL; + RT_FALL_THRU(); + case 'x': + uBase = 16; + break; + case 'u': + uBase = 10; + break; + case 'o': + uBase = 8; + break; + } + + if (ch == 'p' || chArgSize == 'z' || chArgSize == 't') + uValue = va_arg(va, uintptr_t); + else if (chArgSize == 'L' || chArgSize == 'j') + uValue = va_arg(va, uint64_t); + else if (chArgSize == 'l') + uValue = va_arg(va, unsigned long); + else + uValue = va_arg(va, unsigned int); + + if (uBase == 10) + suplibHardenedPrintDecimal(uValue); + else + suplibHardenedPrintHexOctal(uValue, uBase, fFlags); + 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; + } + RT_FALL_THRU(); + + /* + * Custom format. + */ + default: + suplibHardenedPrintStr("[bad format: "); + suplibHardenedPrintStrN(pszLast, pszFormat - pszLast); + suplibHardenedPrintChr(']'); + break; + } + + /* continue */ + pszLast = pszFormat; + } + } + + /* Flush the last bits of the string. */ + if (pszLast != pszFormat) + suplibHardenedPrintStrN(pszLast, pszFormat - pszLast); +#endif /* !IPRT_NO_CRT */ +} + + +/** + * Prints to standard error. + * + * @param pszFormat The format string. + * @param ... Arguments to format. + */ +DECLHIDDEN(void) suplibHardenedPrintF(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + suplibHardenedPrintFV(pszFormat, va); + va_end(va); +} + + +/** + * @copydoc RTPathStripFilename + */ +static void suplibHardenedPathStripFilename(char *pszPath) +{ + char *psz = pszPath; + char *pszLastSep = pszPath; + + for (;; psz++) + { + switch (*psz) + { + /* handle separators. */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + case ':': + pszLastSep = psz + 1; + break; + + case '\\': +#endif + case '/': + pszLastSep = psz; + break; + + /* the end */ + case '\0': + if (pszLastSep == pszPath) + *pszLastSep++ = '.'; + *pszLastSep = '\0'; + return; + } + } + /* will never get here */ +} + + +DECLHIDDEN(char *) supR3HardenedPathFilename(const char *pszPath) +{ + const char *psz = pszPath; + const char *pszLastComp = pszPath; + + for (;; psz++) + { + switch (*psz) + { + /* handle separators. */ +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + case ':': + pszLastComp = psz + 1; + break; + + case '\\': +#endif + case '/': + pszLastComp = psz + 1; + break; + + /* the end */ + case '\0': + if (*pszLastComp) + return (char *)(void *)pszLastComp; + return NULL; + } + } + + /* will never get here */ +} + + +DECLHIDDEN(int) supR3HardenedPathAppPrivateNoArch(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE) + const char *pszSrcPath = RTPATH_APP_PRIVATE; + size_t cchPathPrivateNoArch = suplibHardenedStrLen(pszSrcPath); + if (cchPathPrivateNoArch >= cchPath) + supR3HardenedFatal("supR3HardenedPathAppPrivateNoArch: Buffer overflow, %zu >= %zu\n", cchPathPrivateNoArch, cchPath); + suplibHardenedMemCopy(pszPath, pszSrcPath, cchPathPrivateNoArch + 1); + return VINF_SUCCESS; + +#else + return supR3HardenedPathAppBin(pszPath, cchPath); +#endif +} + + +DECLHIDDEN(int) supR3HardenedPathAppPrivateArch(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_PRIVATE_ARCH) + const char *pszSrcPath = RTPATH_APP_PRIVATE_ARCH; + size_t cchPathPrivateArch = suplibHardenedStrLen(pszSrcPath); + if (cchPathPrivateArch >= cchPath) + supR3HardenedFatal("supR3HardenedPathAppPrivateArch: Buffer overflow, %zu >= %zu\n", cchPathPrivateArch, cchPath); + suplibHardenedMemCopy(pszPath, pszSrcPath, cchPathPrivateArch + 1); + return VINF_SUCCESS; + +#else + return supR3HardenedPathAppBin(pszPath, cchPath); +#endif +} + + +DECLHIDDEN(int) supR3HardenedPathAppSharedLibs(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_SHARED_LIBS) + const char *pszSrcPath = RTPATH_SHARED_LIBS; + size_t cchPathSharedLibs = suplibHardenedStrLen(pszSrcPath); + if (cchPathSharedLibs >= cchPath) + supR3HardenedFatal("supR3HardenedPathAppSharedLibs: Buffer overflow, %zu >= %zu\n", cchPathSharedLibs, cchPath); + suplibHardenedMemCopy(pszPath, pszSrcPath, cchPathSharedLibs + 1); + return VINF_SUCCESS; + +#else + return supR3HardenedPathAppBin(pszPath, cchPath); +#endif +} + + +DECLHIDDEN(int) supR3HardenedPathAppDocs(char *pszPath, size_t cchPath) +{ +#if !defined(RT_OS_WINDOWS) && defined(RTPATH_APP_DOCS) + const char *pszSrcPath = RTPATH_APP_DOCS; + size_t cchPathAppDocs = suplibHardenedStrLen(pszSrcPath); + if (cchPathAppDocs >= cchPath) + supR3HardenedFatal("supR3HardenedPathAppDocs: Buffer overflow, %zu >= %zu\n", cchPathAppDocs, cchPath); + suplibHardenedMemCopy(pszPath, pszSrcPath, cchPathAppDocs + 1); + return VINF_SUCCESS; + +#else + return supR3HardenedPathAppBin(pszPath, cchPath); +#endif +} + + +/** + * Returns the full path to the executable in g_szSupLibHardenedExePath. + */ +static void supR3HardenedGetFullExePath(void) +{ + /* + * Get the program filename. + * + * Most UNIXes have no API for obtaining the executable path, but provides a symbolic + * link in the proc file system that tells who was exec'ed. The bad thing about this + * is that we have to use readlink, one of the weirder UNIX APIs. + * + * Darwin, OS/2 and Windows all have proper APIs for getting the program file name. + */ +#if defined(RT_OS_LINUX) || defined(RT_OS_FREEBSD) || defined(RT_OS_SOLARIS) +# ifdef RT_OS_LINUX + int cchLink = readlink("/proc/self/exe", &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath) - 1); + +# elif defined(RT_OS_SOLARIS) + char szFileBuf[PATH_MAX + 1]; + sprintf(szFileBuf, "/proc/%ld/path/a.out", (long)getpid()); + int cchLink = readlink(szFileBuf, &g_szSupLibHardenedExePath[0], sizeof(g_szSupLibHardenedExePath) - 1); + +# else /* RT_OS_FREEBSD */ + int aiName[4]; + aiName[0] = CTL_KERN; + aiName[1] = KERN_PROC; + aiName[2] = KERN_PROC_PATHNAME; + aiName[3] = getpid(); + + size_t cbPath = sizeof(g_szSupLibHardenedExePath); + if (sysctl(aiName, RT_ELEMENTS(aiName), g_szSupLibHardenedExePath, &cbPath, NULL, 0) < 0) + supR3HardenedFatal("supR3HardenedExecDir: sysctl failed\n"); + g_szSupLibHardenedExePath[sizeof(g_szSupLibHardenedExePath) - 1] = '\0'; + int cchLink = suplibHardenedStrLen(g_szSupLibHardenedExePath); /* paranoid? can't we use cbPath? */ + +# endif + if (cchLink < 0 || cchLink == sizeof(g_szSupLibHardenedExePath) - 1) + supR3HardenedFatal("supR3HardenedExecDir: couldn't read \"%s\", errno=%d cchLink=%d\n", + g_szSupLibHardenedExePath, errno, cchLink); + g_szSupLibHardenedExePath[cchLink] = '\0'; + +#elif defined(RT_OS_OS2) || defined(RT_OS_L4) + _execname(g_szSupLibHardenedExePath, sizeof(g_szSupLibHardenedExePath)); + +#elif defined(RT_OS_DARWIN) + const char *pszImageName = _dyld_get_image_name(0); + if (!pszImageName) + supR3HardenedFatal("supR3HardenedExecDir: _dyld_get_image_name(0) failed\n"); + size_t cchImageName = suplibHardenedStrLen(pszImageName); + if (!cchImageName || cchImageName >= sizeof(g_szSupLibHardenedExePath)) + supR3HardenedFatal("supR3HardenedExecDir: _dyld_get_image_name(0) failed, cchImageName=%d\n", cchImageName); + suplibHardenedMemCopy(g_szSupLibHardenedExePath, pszImageName, cchImageName + 1); + /** @todo abspath the string or this won't work: + * cd /Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/ && ./VirtualBoxVM --startvm name */ + +#elif defined(RT_OS_WINDOWS) + char *pszDst = g_szSupLibHardenedExePath; + int rc = RTUtf16ToUtf8Ex(g_wszSupLibHardenedExePath, RTSTR_MAX, &pszDst, sizeof(g_szSupLibHardenedExePath), NULL); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedExecDir: RTUtf16ToUtf8Ex failed, rc=%Rrc\n", rc); +#else +# error needs porting. +#endif + + /* + * Determine the application binary directory location. + */ + suplibHardenedStrCopy(g_szSupLibHardenedAppBinPath, g_szSupLibHardenedExePath); + suplibHardenedPathStripFilename(g_szSupLibHardenedAppBinPath); + + g_offSupLibHardenedExecName = suplibHardenedStrLen(g_szSupLibHardenedAppBinPath); + while (RTPATH_IS_SEP(g_szSupLibHardenedExePath[g_offSupLibHardenedExecName])) + g_offSupLibHardenedExecName++; + g_cchSupLibHardenedExecName = suplibHardenedStrLen(&g_szSupLibHardenedExePath[g_offSupLibHardenedExecName]); + + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED) + supR3HardenedFatal("supR3HardenedExecDir: Called before SUPR3HardenedMain! (%d)\n", g_enmSupR3HardenedMainState); + switch (g_fSupHardenedMain & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_APP_BIN: + break; + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + suplibHardenedPathStripFilename(g_szSupLibHardenedAppBinPath); + break; +#ifdef RT_OS_DARWIN + case SUPSECMAIN_FLAGS_LOC_OSX_HLP_APP: + { + /* We must ascend to the parent bundle's Contents directory then decend into its MacOS: */ + static const RTSTRTUPLE s_aComponentsToSkip[] = + { { RT_STR_TUPLE("MacOS") }, { RT_STR_TUPLE("Contents") }, { NULL /*some.app*/, 0 }, { RT_STR_TUPLE("Resources") } }; + size_t cchPath = suplibHardenedStrLen(g_szSupLibHardenedAppBinPath); + for (uintptr_t i = 0; i < RT_ELEMENTS(s_aComponentsToSkip); i++) + { + while (cchPath > 1 && g_szSupLibHardenedAppBinPath[cchPath - 1] == '/') + cchPath--; + size_t const cchMatch = s_aComponentsToSkip[i].cch; + if (cchMatch > 0) + { + if ( cchPath >= cchMatch + sizeof("VirtualBox.app/Contents") + && g_szSupLibHardenedAppBinPath[cchPath - cchMatch - 1] == '/' + && suplibHardenedMemComp(&g_szSupLibHardenedAppBinPath[cchPath - cchMatch], + s_aComponentsToSkip[i].psz, cchMatch) == 0) + cchPath -= cchMatch; + else + supR3HardenedFatal("supR3HardenedExecDir: Bad helper app path (tail component #%u '%s'): %s\n", + i, s_aComponentsToSkip[i].psz, g_szSupLibHardenedAppBinPath); + } + else if ( cchPath > g_cchSupLibHardenedExecName + sizeof("VirtualBox.app/Contents/Resources/.app") + && suplibHardenedMemComp(&g_szSupLibHardenedAppBinPath[cchPath - 4], ".app", 4) == 0 + && suplibHardenedMemComp(&g_szSupLibHardenedAppBinPath[cchPath - 4 - g_cchSupLibHardenedExecName], + &g_szSupLibHardenedExePath[g_offSupLibHardenedExecName], + g_cchSupLibHardenedExecName) == 0) + cchPath -= g_cchSupLibHardenedExecName + 4; + else + supR3HardenedFatal("supR3HardenedExecDir: Bad helper app path (tail component #%u '%s.app'): %s\n", + i, &g_szSupLibHardenedExePath[g_offSupLibHardenedExecName], g_szSupLibHardenedAppBinPath); + } + suplibHardenedMemCopy(&g_szSupLibHardenedAppBinPath[cchPath], "MacOS", sizeof("MacOS")); + break; + } +#endif /* RT_OS_DARWIN */ + default: + supR3HardenedFatal("supR3HardenedExecDir: Unknown program binary location: %#x\n", g_fSupHardenedMain); + } +} + + +#ifdef RT_OS_LINUX +/** + * Checks if we can read /proc/self/exe. + * + * This is used on linux to see if we have to call init + * with program path or not. + * + * @returns true / false. + */ +static bool supR3HardenedMainIsProcSelfExeAccssible(void) +{ + char szPath[RTPATH_MAX]; + int cchLink = readlink("/proc/self/exe", szPath, sizeof(szPath)); + return cchLink != -1; +} +#endif /* RT_OS_LINUX */ + + + +/** + * @remarks not quite like RTPathExecDir actually... + */ +DECLHIDDEN(int) supR3HardenedPathAppBin(char *pszPath, size_t cchPath) +{ + /* + * Lazy init (probably not required). + */ + if (!g_szSupLibHardenedAppBinPath[0]) + supR3HardenedGetFullExePath(); + + /* + * Calc the length and check if there is space before copying. + */ + size_t cch = suplibHardenedStrLen(g_szSupLibHardenedAppBinPath) + 1; + if (cch <= cchPath) + { + suplibHardenedMemCopy(pszPath, g_szSupLibHardenedAppBinPath, cch + 1); + return VINF_SUCCESS; + } + + supR3HardenedFatal("supR3HardenedPathAppBin: Buffer too small (%u < %u)\n", cchPath, cch); + /* not reached */ +} + + +#ifdef RT_OS_WINDOWS +extern "C" uint32_t g_uNtVerCombined; +#endif + +DECLHIDDEN(void) supR3HardenedOpenLog(int *pcArgs, char **papszArgs) +{ + static const char s_szLogOption[] = "--sup-hardening-log="; + + /* + * Scan the argument vector. + */ + int cArgs = *pcArgs; + for (int iArg = 1; iArg < cArgs; iArg++) + if (strncmp(papszArgs[iArg], s_szLogOption, sizeof(s_szLogOption) - 1) == 0) + { +#ifdef RT_OS_WINDOWS + const char *pszLogFile = &papszArgs[iArg][sizeof(s_szLogOption) - 1]; +#endif + + /* + * Drop the argument from the vector (has trailing NULL entry). + */ +// memmove(&papszArgs[iArg], &papszArgs[iArg + 1], (cArgs - iArg) * sizeof(papszArgs[0])); + *pcArgs -= 1; + cArgs -= 1; + + /* + * Open the log file, unless we've already opened one. + * First argument takes precedence + */ +#ifdef RT_OS_WINDOWS + if (g_hStartupLog == NULL) + { + int rc = RTNtPathOpen(pszLogFile, + GENERIC_WRITE | SYNCHRONIZE, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN_IF, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + OBJ_CASE_INSENSITIVE, + &g_hStartupLog, + NULL); + if (RT_SUCCESS(rc)) + { +// SUP_DPRINTF(("Log file opened: " VBOX_VERSION_STRING "r%u g_hStartupLog=%p g_uNtVerCombined=%#x\n", +// VBOX_SVN_REV, g_hStartupLog, g_uNtVerCombined)); + + /* + * If the path contains a drive volume, save it so we can + * use it to flush the volume containing the log file. + */ + if (RT_C_IS_ALPHA(pszLogFile[0]) && pszLogFile[1] == ':') + { +// RTUtf16CopyAscii(g_wszStartupLogVol, RT_ELEMENTS(g_wszStartupLogVol), "\\??\\"); + g_wszStartupLogVol[sizeof("\\??\\") - 1] = RT_C_TO_UPPER(pszLogFile[0]); + g_wszStartupLogVol[sizeof("\\??\\") + 0] = ':'; + g_wszStartupLogVol[sizeof("\\??\\") + 1] = '\0'; + } + } + else + g_hStartupLog = NULL; + } +#else + /* Just some mumbo jumbo to shut up the compiler. */ + g_hStartupLog -= 1; + g_cbStartupLog += 1; + //g_hStartupLog = open() +#endif + } +} + + +DECLHIDDEN(void) supR3HardenedLogV(const char *pszFormat, va_list va) +{ +#ifdef RT_OS_WINDOWS + if ( g_hStartupLog != NULL + && g_cbStartupLog < 16*_1M) + { + char szBuf[5120]; + PCLIENT_ID pSelfId = &((PTEB)NtCurrentTeb())->ClientId; + size_t cchPrefix = RTStrPrintf(szBuf, sizeof(szBuf), "%x.%x: ", pSelfId->UniqueProcess, pSelfId->UniqueThread); + size_t cch = RTStrPrintfV(&szBuf[cchPrefix], sizeof(szBuf) - cchPrefix, pszFormat, va) + cchPrefix; + + if ((size_t)cch >= sizeof(szBuf)) + cch = sizeof(szBuf) - 1; + + if (!cch || szBuf[cch - 1] != '\n') + szBuf[cch++] = '\n'; + + ASMAtomicAddU32(&g_cbStartupLog, (uint32_t)cch); + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + LARGE_INTEGER Offset; + Offset.QuadPart = -1; /* Write to end of file. */ + NtWriteFile(g_hStartupLog, NULL /*Event*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, + &Ios, szBuf, (ULONG)cch, &Offset, NULL /*Key*/); + } +#else + RT_NOREF(pszFormat, va); + /* later */ +#endif +} + + +DECLHIDDEN(void) supR3HardenedLog(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supR3HardenedLogV(pszFormat, va); + va_end(va); +} + + +DECLHIDDEN(void) supR3HardenedLogFlush(void) +{ +#ifdef RT_OS_WINDOWS + if ( g_hStartupLog != NULL + && g_cbStartupLog < 16*_1M) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtFlushBuffersFile(g_hStartupLog, &Ios); + + /* + * Try flush the volume containing the log file too. + */ + if (g_wszStartupLogVol[0]) + { + HANDLE hLogVol = RTNT_INVALID_HANDLE_VALUE; + UNICODE_STRING NtName; + NtName.Buffer = g_wszStartupLogVol; + NtName.Length = (USHORT)(RTUtf16Len(g_wszStartupLogVol) * sizeof(RTUTF16)); + NtName.MaximumLength = NtName.Length + 1; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtCreateFile(&hLogVol, + GENERIC_WRITE | GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + 0 /*FileAttributes*/, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 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)) + { + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtFlushBuffersFile(hLogVol, &Ios); + NtClose(hLogVol); + } + else + { + /* This may have sideeffects similar to what we want... */ + hLogVol = RTNT_INVALID_HANDLE_VALUE; + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtCreateFile(&hLogVol, + GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES, + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + 0 /*FileAttributes*/, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + NtClose(hLogVol); + } + } + } +#else + /* later */ +#endif +} + + +/** + * Prints the message prefix. + */ +static void suplibHardenedPrintPrefix(void) +{ + if (g_pszSupLibHardenedProgName) + suplibHardenedPrintStr(g_pszSupLibHardenedProgName); + suplibHardenedPrintStr(": "); +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsgV(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, va_list va) +{ + /* + * First to the log. + */ + supR3HardenedLog("Error %d in %s! (enmWhat=%d)\n", rc, pszWhere, enmWhat); + va_list vaCopy; + va_copy(vaCopy, va); + supR3HardenedLogV(pszMsgFmt, vaCopy); + va_end(vaCopy); + +#ifdef RT_OS_WINDOWS + /* + * The release log. + */ + if (g_pfnRTLogRelPrintf) + { + va_copy(vaCopy, va); + g_pfnRTLogRelPrintf("supR3HardenedFatalMsgV: %s enmWhat=%d rc=%Rrc (%#x)\n", pszWhere, enmWhat, rc); + g_pfnRTLogRelPrintf("supR3HardenedFatalMsgV: %N\n", pszMsgFmt, &vaCopy); + va_end(vaCopy); + } +#endif + + /* + * Then to the console. + */ + suplibHardenedPrintPrefix(); + suplibHardenedPrintF("Error %d in %s!\n", rc, pszWhere); + + suplibHardenedPrintPrefix(); + va_copy(vaCopy, va); + suplibHardenedPrintFV(pszMsgFmt, vaCopy); + va_end(vaCopy); + suplibHardenedPrintChr('\n'); + + switch (enmWhat) + { + case kSupInitOp_Driver: + suplibHardenedPrintChr('\n'); + suplibHardenedPrintPrefix(); + 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: + suplibHardenedPrintChr('\n'); + suplibHardenedPrintPrefix(); + suplibHardenedPrintStr("Tip! It may help to reinstall VirtualBox.\n"); + break; + + default: + /* no hints here */ + break; + } + + /* + * Finally, TrustedError if appropriate. + */ + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + { +#ifdef SUP_HARDENED_SUID + /* Drop any root privileges we might be holding, this won't return + if it fails but end up calling supR3HardenedFatal[V]. */ + supR3HardenedMainDropPrivileges(); +#endif + /* Close the driver, if we succeeded opening it. Both because + TrustedError may be untrustworthy and because the driver deosn't + like us if we fork(). @bugref{8838} */ + suplibOsTerm(&g_SupPreInitData.Data); + + /* + * Now try resolve and call the TrustedError entry point if we can find it. + * Note! Loader involved, so we must guard against loader hooks calling us. + */ + static volatile bool s_fRecursive = false; + if (!s_fRecursive) + { + s_fRecursive = true; + + PFNSUPTRUSTEDERROR pfnTrustedError = supR3HardenedMainGetTrustedError(g_pszSupLibHardenedProgName); + if (pfnTrustedError) + { + /* We'll fork before we make the call because that way the session management + in main will see us exiting immediately (if it's involved with us) and possibly + get an error back to the API / user. */ +#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) && /* @bugref{10170}: */ !defined(RT_OS_DARWIN) + int pid = fork(); + if (pid <= 0) +#endif + { + pfnTrustedError(pszWhere, enmWhat, rc, pszMsgFmt, va); + } + } + + s_fRecursive = false; + } + } +#if defined(RT_OS_WINDOWS) + /* + * Report the error to the parent if this happens during early VM init. + */ + else if ( g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED + && g_enmSupR3HardenedMainState != SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED) + supR3HardenedWinReportErrorToParent(pszWhere, enmWhat, rc, pszMsgFmt, va); +#endif + + /* + * Quit + */ + suplibHardenedExit(RTEXITCODE_FAILURE); +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalMsg(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszMsgFmt, ...) +{ + va_list va; + va_start(va, pszMsgFmt); + supR3HardenedFatalMsgV(pszWhere, enmWhat, rc, pszMsgFmt, va); + /* not reached */ +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatalV(const char *pszFormat, va_list va) +{ + supR3HardenedLog("Fatal error:\n"); + va_list vaCopy; + va_copy(vaCopy, va); + supR3HardenedLogV(pszFormat, vaCopy); + va_end(vaCopy); + +#if defined(RT_OS_WINDOWS) + /* + * Report the error to the parent if this happens during early VM init. + */ + if ( g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED + && g_enmSupR3HardenedMainState != SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED) + supR3HardenedWinReportErrorToParent(NULL, kSupInitOp_Invalid, VERR_INTERNAL_ERROR, pszFormat, va); + else +#endif + { +#ifdef RT_OS_WINDOWS + if (g_pfnRTLogRelPrintf) + { + va_copy(vaCopy, va); + g_pfnRTLogRelPrintf("supR3HardenedFatalV: %N", pszFormat, &vaCopy); + va_end(vaCopy); + } +#endif + + suplibHardenedPrintPrefix(); + suplibHardenedPrintFV(pszFormat, va); + } + + suplibHardenedExit(RTEXITCODE_FAILURE); +} + + +DECL_NO_RETURN(DECLHIDDEN(void)) supR3HardenedFatal(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supR3HardenedFatalV(pszFormat, va); + /* not reached */ +} + + +DECLHIDDEN(int) supR3HardenedErrorV(int rc, bool fFatal, const char *pszFormat, va_list va) +{ + if (fFatal) + supR3HardenedFatalV(pszFormat, va); + + supR3HardenedLog("Error (rc=%d):\n", rc); + va_list vaCopy; + va_copy(vaCopy, va); + supR3HardenedLogV(pszFormat, vaCopy); + va_end(vaCopy); + +#ifdef RT_OS_WINDOWS + if (g_pfnRTLogRelPrintf) + { + va_copy(vaCopy, va); + g_pfnRTLogRelPrintf("supR3HardenedErrorV: %N", pszFormat, &vaCopy); + va_end(vaCopy); + } +#endif + + suplibHardenedPrintPrefix(); + suplibHardenedPrintFV(pszFormat, va); + + return rc; +} + + +DECLHIDDEN(int) supR3HardenedError(int rc, bool fFatal, const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supR3HardenedErrorV(rc, fFatal, pszFormat, va); + va_end(va); + return rc; +} + + + +/** + * Attempts to open /dev/vboxdrv (or equvivalent). + * + * @remarks This function will not return on failure. + */ +DECLHIDDEN(void) supR3HardenedMainOpenDevice(void) +{ + RTERRINFOSTATIC ErrInfo; + SUPINITOP enmWhat = kSupInitOp_Driver; + uint32_t fFlags = SUPR3INIT_F_UNRESTRICTED; + if (g_fSupHardenedMain & SUPSECMAIN_FLAGS_DRIVERLESS) + fFlags |= SUPR3INIT_F_DRIVERLESS; + if (g_fSupHardenedMain & SUPSECMAIN_FLAGS_DRIVERLESS_IEM_ALLOWED) + fFlags |= SUPR3INIT_F_DRIVERLESS_IEM_ALLOWED; +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + if (g_fSupHardenedMain & SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK) + fFlags |= SUPR3INIT_F_DRIVERLESS_NEM_FALLBACK; +#endif + int rc = suplibOsInit(&g_SupPreInitData.Data, false /*fPreInit*/, fFlags, &enmWhat, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + return; + + if (RTErrInfoIsSet(&ErrInfo.Core)) + supR3HardenedFatalMsg("suplibOsInit", enmWhat, rc, "%s", ErrInfo.szMsg); + + switch (rc) + { + /** @todo better messages! */ + case VERR_VM_DRIVER_NOT_INSTALLED: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Kernel driver not installed"); + case VERR_VM_DRIVER_NOT_ACCESSIBLE: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Kernel driver not accessible"); + case VERR_VM_DRIVER_LOAD_ERROR: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_VM_DRIVER_LOAD_ERROR"); + case VERR_VM_DRIVER_OPEN_ERROR: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_VM_DRIVER_OPEN_ERROR"); + case VERR_VM_DRIVER_VERSION_MISMATCH: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Kernel driver version mismatch"); + case VERR_ACCESS_DENIED: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "VERR_ACCESS_DENIED"); + case VERR_NO_MEMORY: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Kernel memory allocation/mapping failed"); + case VERR_SUPDRV_HARDENING_EVIL_HANDLE: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Integrity, rc, "VERR_SUPDRV_HARDENING_EVIL_HANDLE"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Integrity, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Integrity, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1"); + case VERR_SUPLIB_NT_PROCESS_UNTRUSTED_2: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Integrity, rc, "VERR_SUPLIB_NT_PROCESS_UNTRUSTED_2"); + default: + supR3HardenedFatalMsg("suplibOsInit", kSupInitOp_Driver, rc, "Unknown rc=%d (%Rrc)", rc, rc); + } +} + + +#ifdef SUP_HARDENED_SUID + +/** + * Grabs extra non-root capabilities / privileges that we might require. + * + * This is currently only used for being able to do ICMP from the NAT engine + * and for being able to raise thread scheduling priority + * + * @note We still have root privileges at the time of this call. + */ +static void supR3HardenedMainGrabCapabilites(void) +{ +# if defined(RT_OS_LINUX) + /* + * We are about to drop all our privileges. Remove all capabilities but + * keep the cap_net_raw capability for ICMP sockets for the NAT stack, + * also keep cap_sys_nice capability for priority tweaking. + */ + if (g_uCaps != 0) + { +# ifdef USE_LIB_PCAP + /* XXX cap_net_bind_service */ + if (!cap_set_proc(cap_from_text("all-eip cap_net_raw+ep cap_sys_nice+ep"))) + prctl(PR_SET_KEEPCAPS, 1 /*keep=*/, 0, 0, 0); + prctl(PR_SET_DUMPABLE, 1 /*dump*/, 0, 0, 0); +# else + cap_user_header_t hdr = (cap_user_header_t)alloca(sizeof(*hdr)); + cap_user_data_t cap = (cap_user_data_t)alloca(2 /*_LINUX_CAPABILITY_U32S_3*/ * sizeof(*cap)); + memset(hdr, 0, sizeof(*hdr)); + capget(hdr, NULL); + if ( hdr->version != 0x19980330 /* _LINUX_CAPABILITY_VERSION_1, _LINUX_CAPABILITY_U32S_1 = 1 */ + && hdr->version != 0x20071026 /* _LINUX_CAPABILITY_VERSION_2, _LINUX_CAPABILITY_U32S_2 = 2 */ + && hdr->version != 0x20080522 /* _LINUX_CAPABILITY_VERSION_3, _LINUX_CAPABILITY_U32S_3 = 2 */) + hdr->version = _LINUX_CAPABILITY_VERSION; + g_uCapsVersion = hdr->version; + memset(cap, 0, 2 /* _LINUX_CAPABILITY_U32S_3 */ * sizeof(*cap)); + cap->effective = g_uCaps; + cap->permitted = g_uCaps; + if (!capset(hdr, cap)) + prctl(PR_SET_KEEPCAPS, 1 /*keep*/, 0, 0, 0); + prctl(PR_SET_DUMPABLE, 1 /*dump*/, 0, 0, 0); +# endif /* !USE_LIB_PCAP */ + } + +# elif defined(RT_OS_SOLARIS) + /* + * Add net_icmpaccess privilege to effective privileges and limit + * permitted privileges before completely dropping root privileges. + * This requires dropping root privileges temporarily to get the normal + * user's privileges. + */ + seteuid(g_uid); + priv_set_t *pPrivEffective = priv_allocset(); + priv_set_t *pPrivNew = priv_allocset(); + if (pPrivEffective && pPrivNew) + { + int rc = getppriv(PRIV_EFFECTIVE, pPrivEffective); + seteuid(0); + if (!rc) + { + priv_copyset(pPrivEffective, pPrivNew); + rc = priv_addset(pPrivNew, PRIV_NET_ICMPACCESS); + if (!rc) + { + /* Order is important, as one can't set a privilege which is + * not in the permitted privilege set. */ + rc = setppriv(PRIV_SET, PRIV_EFFECTIVE, pPrivNew); + if (rc) + supR3HardenedError(rc, false, "SUPR3HardenedMain: failed to set effective privilege set.\n"); + rc = setppriv(PRIV_SET, PRIV_PERMITTED, pPrivNew); + if (rc) + supR3HardenedError(rc, false, "SUPR3HardenedMain: failed to set permitted privilege set.\n"); + } + else + supR3HardenedError(rc, false, "SUPR3HardenedMain: failed to add NET_ICMPACCESS privilege.\n"); + } + } + else + { + /* for memory allocation failures just continue */ + seteuid(0); + } + + if (pPrivEffective) + priv_freeset(pPrivEffective); + if (pPrivNew) + priv_freeset(pPrivNew); +# endif +} + +/* + * Look at the environment for some special options. + */ +static void supR3GrabOptions(void) +{ +# ifdef RT_OS_LINUX + g_uCaps = 0; + + /* + * Do _not_ perform any capability-related system calls for root processes + * (leaving g_uCaps at 0). + * (Hint: getuid gets the real user id, not the effective.) + */ + if (getuid() != 0) + { + /* + * CAP_NET_RAW. + * Default: enabled. + * Can be disabled with 'export VBOX_HARD_CAP_NET_RAW=0'. + */ + const char *pszOpt = getenv("VBOX_HARD_CAP_NET_RAW"); + if ( !pszOpt + || memcmp(pszOpt, "0", sizeof("0")) != 0) + g_uCaps = CAP_TO_MASK(CAP_NET_RAW); + + /* + * CAP_NET_BIND_SERVICE. + * Default: disabled. + * Can be enabled with 'export VBOX_HARD_CAP_NET_BIND_SERVICE=1'. + */ + pszOpt = getenv("VBOX_HARD_CAP_NET_BIND_SERVICE"); + if ( pszOpt + && memcmp(pszOpt, "0", sizeof("0")) != 0) + g_uCaps |= CAP_TO_MASK(CAP_NET_BIND_SERVICE); + + /* + * CAP_SYS_NICE. + * Default: enabled. + * Can be disabled with 'export VBOX_HARD_CAP_SYS_NICE=0'. + */ + pszOpt = getenv("VBOX_HARD_CAP_SYS_NICE"); + if ( !pszOpt + || memcmp(pszOpt, "0", sizeof("0")) != 0) + g_uCaps |= CAP_TO_MASK(CAP_SYS_NICE); + } +# endif +} + +/** + * Drop any root privileges we might be holding. + */ +static void supR3HardenedMainDropPrivileges(void) +{ + /* + * Try use setre[ug]id since this will clear the save uid/gid and thus + * leave fewer traces behind that libs like GTK+ may pick up. + */ + uid_t euid, ruid, suid; + gid_t egid, rgid, sgid; +# if defined(RT_OS_DARWIN) + /* The really great thing here is that setreuid isn't available on + OS X 10.4, libc emulates it. While 10.4 have a slightly different and + non-standard setuid implementation compared to 10.5, the following + works the same way with both version since we're super user (10.5 req). + The following will set all three variants of the group and user IDs. */ + setgid(g_gid); + setuid(g_uid); + euid = geteuid(); + ruid = suid = getuid(); + egid = getegid(); + rgid = sgid = getgid(); + +# elif defined(RT_OS_SOLARIS) + /* Solaris doesn't have setresuid, but the setreuid interface is BSD + compatible and will set the saved uid to euid when we pass it a ruid + that isn't -1 (which we do). */ + setregid(g_gid, g_gid); + setreuid(g_uid, g_uid); + euid = geteuid(); + ruid = suid = getuid(); + egid = getegid(); + rgid = sgid = getgid(); + +# else + /* This is the preferred one, full control no questions about semantics. + PORTME: If this isn't work, try join one of two other gangs above. */ + int res = setresgid(g_gid, g_gid, g_gid); + NOREF(res); + res = setresuid(g_uid, g_uid, g_uid); + NOREF(res); + if (getresuid(&ruid, &euid, &suid) != 0) + { + euid = geteuid(); + ruid = suid = getuid(); + } + if (getresgid(&rgid, &egid, &sgid) != 0) + { + egid = getegid(); + rgid = sgid = getgid(); + } +# endif + + + /* Check that it worked out all right. */ + if ( euid != g_uid + || ruid != g_uid + || suid != g_uid + || egid != g_gid + || rgid != g_gid + || sgid != g_gid) + supR3HardenedFatal("SUPR3HardenedMain: failed to drop root privileges!" + " (euid=%d ruid=%d suid=%d egid=%d rgid=%d sgid=%d; wanted uid=%d and gid=%d)\n", + euid, ruid, suid, egid, rgid, sgid, g_uid, g_gid); + +# if RT_OS_LINUX + /* + * Re-enable the cap_net_raw and cap_sys_nice capabilities which were disabled during setresuid. + */ + if (g_uCaps != 0) + { +# ifdef USE_LIB_PCAP + /** @todo Warn if that does not work? */ + /* XXX cap_net_bind_service */ + cap_set_proc(cap_from_text("cap_net_raw+ep cap_sys_nice+ep")); +# else + cap_user_header_t hdr = (cap_user_header_t)alloca(sizeof(*hdr)); + cap_user_data_t cap = (cap_user_data_t)alloca(2 /* _LINUX_CAPABILITY_U32S_3 */ * sizeof(*cap)); + memset(hdr, 0, sizeof(*hdr)); + hdr->version = g_uCapsVersion; + memset(cap, 0, 2 /* _LINUX_CAPABILITY_U32S_3 */ * sizeof(*cap)); + cap->effective = g_uCaps; + cap->permitted = g_uCaps; + /** @todo Warn if that does not work? */ + capset(hdr, cap); +# endif /* !USE_LIB_PCAP */ + } +# endif +} + +#endif /* SUP_HARDENED_SUID */ + +/** + * Purge the process environment from any environment vairable which can lead + * to loading untrusted binaries compromising the process address space. + * + * @param envp The initial environment vector. (Can be NULL.) + */ +static void supR3HardenedMainPurgeEnvironment(char **envp) +{ + for (unsigned i = 0; i < RT_ELEMENTS(g_aSupEnvPurgeDescs); i++) + { + /* + * Update the initial environment vector, just in case someone actually cares about it. + */ + if (envp) + { + const char * const pszEnv = g_aSupEnvPurgeDescs[i].pszEnv; + size_t const cchEnv = g_aSupEnvPurgeDescs[i].cchEnv; + unsigned iSrc = 0; + unsigned iDst = 0; + char *pszTmp; + + while ((pszTmp = envp[iSrc]) != NULL) + { + if ( memcmp(pszTmp, pszEnv, cchEnv) != 0 + || (pszTmp[cchEnv] != '=' && pszTmp[cchEnv] != '\0')) + { + if (iDst != iSrc) + envp[iDst] = pszTmp; + iDst++; + } + else + SUP_DPRINTF(("supR3HardenedMainPurgeEnvironment: dropping envp[%d]=%s\n", iSrc, pszTmp)); + iSrc++; + } + + if (iDst != iSrc) + while (iDst <= iSrc) + envp[iDst++] = NULL; + } + + /* + * Remove from the process environment if present. + */ +#ifndef RT_OS_WINDOWS + const char *pszTmp = getenv(g_aSupEnvPurgeDescs[i].pszEnv); + if (pszTmp != NULL) + { + if (unsetenv((char *)g_aSupEnvPurgeDescs[i].pszEnv) == 0) + SUP_DPRINTF(("supR3HardenedMainPurgeEnvironment: dropped %s\n", pszTmp)); + else + if (g_aSupEnvPurgeDescs[i].fPurgeErrFatal) + supR3HardenedFatal("SUPR3HardenedMain: failed to purge %s environment variable! (errno=%d %s)\n", + g_aSupEnvPurgeDescs[i].pszEnv, errno, strerror(errno)); + else + SUP_DPRINTF(("supR3HardenedMainPurgeEnvironment: dropping %s failed! errno=%d\n", pszTmp, errno)); + } +#else + /** @todo Call NT API to do the same. */ +#endif + } +} + + +/** + * Returns the argument purge descriptor of the given argument if available. + * + * @retval 0 if it should not be purged. + * @retval 1 if it only the current argument should be purged. + * @retval 2 if the argument and the following (if present) should be purged. + * @param pszArg The argument to look for. + */ +static unsigned supR3HardenedMainShouldPurgeArg(const char *pszArg) +{ + for (unsigned i = 0; i < RT_ELEMENTS(g_aSupArgPurgeDescs); i++) + { + size_t const cchPurge = g_aSupArgPurgeDescs[i].cchArg; + if (!memcmp(pszArg, g_aSupArgPurgeDescs[i].pszArg, cchPurge)) + { + if (pszArg[cchPurge] == '\0') + return 1 + g_aSupArgPurgeDescs[i].fTakesValue; + if ( g_aSupArgPurgeDescs[i].fTakesValue + && (pszArg[cchPurge] == ':' || pszArg[cchPurge] == '=')) + return 1; + } + } + + return 0; +} + + +/** + * Purges any command line arguments considered harmful. + * + * @param cArgsOrig The original number of arguments. + * @param papszArgsOrig The original argument vector. + * @param pcArgsNew Where to store the new number of arguments on success. + * @param ppapszArgsNew Where to store the pointer to the purged argument vector. + */ +static void supR3HardenedMainPurgeArgs(int cArgsOrig, char **papszArgsOrig, int *pcArgsNew, char ***ppapszArgsNew) +{ + int iDst = 0; +#ifdef RT_OS_WINDOWS + char **papszArgsNew = papszArgsOrig; /* We allocated this, no need to allocate again. */ +#else + char **papszArgsNew = (char **)malloc((cArgsOrig + 1) * sizeof(char *)); +#endif + if (papszArgsNew) + { + for (int iSrc = 0; iSrc < cArgsOrig; iSrc++) + { + unsigned cPurgedArgs = supR3HardenedMainShouldPurgeArg(papszArgsOrig[iSrc]); + if (!cPurgedArgs) + papszArgsNew[iDst++] = papszArgsOrig[iSrc]; + else + iSrc += cPurgedArgs - 1; + } + + papszArgsNew[iDst] = NULL; /* The array is NULL terminated, just like envp. */ + } + else + supR3HardenedFatal("SUPR3HardenedMain: failed to allocate memory for purged command line!\n"); + *pcArgsNew = iDst; + *ppapszArgsNew = papszArgsNew; + +#ifdef RT_OS_WINDOWS + /** @todo Update command line pointers in PEB, wont really work without it. */ +#endif +} + + +/** + * Loads the VBoxRT DLL/SO/DYLIB, hands it the open driver, + * and calls RTR3InitEx. + * + * @param fFlags The SUPR3HardenedMain fFlags argument, passed to supR3PreInit. + * + * @remarks VBoxRT contains both IPRT and SUPR3. + * @remarks This function will not return on failure. + */ +static void supR3HardenedMainInitRuntime(uint32_t fFlags) +{ + /* + * Construct the name. + */ + char szPath[RTPATH_MAX]; + supR3HardenedPathAppSharedLibs(szPath, sizeof(szPath) - sizeof("/VBoxRT" SUPLIB_DLL_SUFF)); + suplibHardenedStrCat(szPath, "/VBoxRT" SUPLIB_DLL_SUFF); + + /* + * Open it and resolve the symbols. + */ +#if defined(RT_OS_WINDOWS) + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/, g_fSupHardenedMain); + if (!hMod) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_MODULE_NOT_FOUND, + "LoadLibrary \"%s\" failed (rc=%d)", + szPath, RtlGetLastWin32Error()); + PFNRTR3INITEX pfnRTInitEx = (PFNRTR3INITEX)GetProcAddress(hMod, SUP_HARDENED_SYM("RTR3InitEx")); + if (!pfnRTInitEx) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_SYMBOL_NOT_FOUND, + "Entrypoint \"RTR3InitEx\" not found in \"%s\" (rc=%d)", + szPath, RtlGetLastWin32Error()); + + PFNSUPR3PREINIT pfnSUPPreInit = (PFNSUPR3PREINIT)GetProcAddress(hMod, SUP_HARDENED_SYM("supR3PreInit")); + if (!pfnSUPPreInit) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_SYMBOL_NOT_FOUND, + "Entrypoint \"supR3PreInit\" not found in \"%s\" (rc=%d)", + szPath, RtlGetLastWin32Error()); + + g_pfnRTLogRelPrintf = (PFNRTLOGRELPRINTF)GetProcAddress(hMod, SUP_HARDENED_SYM("RTLogRelPrintf")); + Assert(g_pfnRTLogRelPrintf); /* Not fatal in non-strict builds. */ + +#else + /* the dlopen crowd */ + void *pvMod = dlopen(szPath, RTLD_NOW | RTLD_GLOBAL); + if (!pvMod) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_MODULE_NOT_FOUND, + "dlopen(\"%s\",) failed: %s", + szPath, dlerror()); + PFNRTR3INITEX pfnRTInitEx = (PFNRTR3INITEX)(uintptr_t)dlsym(pvMod, SUP_HARDENED_SYM("RTR3InitEx")); + if (!pfnRTInitEx) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_SYMBOL_NOT_FOUND, + "Entrypoint \"RTR3InitEx\" not found in \"%s\"!\ndlerror: %s", + szPath, dlerror()); + PFNSUPR3PREINIT pfnSUPPreInit = (PFNSUPR3PREINIT)(uintptr_t)dlsym(pvMod, SUP_HARDENED_SYM("supR3PreInit")); + if (!pfnSUPPreInit) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, VERR_SYMBOL_NOT_FOUND, + "Entrypoint \"supR3PreInit\" not found in \"%s\"!\ndlerror: %s", + szPath, dlerror()); +#endif + + /* + * Make the calls. + */ + supR3HardenedGetPreInitData(&g_SupPreInitData); + int rc = pfnSUPPreInit(&g_SupPreInitData, fFlags); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, rc, + "supR3PreInit failed with rc=%d", rc); + + /* Get the executable path for the IPRT init on linux if /proc/self/exe isn't accessible. */ + const char *pszExePath = NULL; +#ifdef RT_OS_LINUX + if (!supR3HardenedMainIsProcSelfExeAccssible()) + pszExePath = g_szSupLibHardenedExePath; +#endif + + /* Assemble the IPRT init flags. We could probably just pass RTR3INIT_FLAGS_TRY_SUPLIB + here and be done with it, but it's not too much hazzle to convert fFlags 1:1. */ + uint32_t fRtInit = 0; + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + { + if (fFlags & SUPSECMAIN_FLAGS_DRIVERLESS) + fRtInit |= (SUPR3INIT_F_DRIVERLESS << RTR3INIT_FLAGS_SUPLIB_SHIFT) | RTR3INIT_FLAGS_TRY_SUPLIB; + if (fFlags & SUPSECMAIN_FLAGS_DRIVERLESS_IEM_ALLOWED) + fRtInit |= (SUPR3INIT_F_DRIVERLESS_IEM_ALLOWED << RTR3INIT_FLAGS_SUPLIB_SHIFT) | RTR3INIT_FLAGS_TRY_SUPLIB; +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + if (fFlags & SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK) + fRtInit |= (SUPR3INIT_F_DRIVERLESS_NEM_FALLBACK << RTR3INIT_FLAGS_SUPLIB_SHIFT) | RTR3INIT_FLAGS_TRY_SUPLIB; +#endif + if (!(fRtInit & RTR3INIT_FLAGS_TRY_SUPLIB)) + fRtInit |= RTR3INIT_FLAGS_SUPLIB; + } + + /* Now do the IPRT init. */ + rc = pfnRTInitEx(RTR3INIT_VER_CUR, fRtInit, 0 /*cArgs*/, NULL /*papszArgs*/, pszExePath); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedMainInitRuntime", kSupInitOp_IPRT, rc, + "RTR3InitEx failed with rc=%d (fRtFlags=%#x)", rc, fRtInit); + +#if defined(RT_OS_WINDOWS) + /* + * Windows: Create thread that terminates the process when the parent stub + * process terminates (VBoxNetDHCP, Ctrl-C, etc). + */ + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + supR3HardenedWinCreateParentWatcherThread(hMod); +#endif +} + + +/** + * Construct the path to the DLL/SO/DYLIB containing the actual program. + * + * @returns VBox status code. + * @param pszProgName The program name. + * @param fMainFlags The flags passed to SUPR3HardenedMain. + * @param pszPath The output buffer. + * @param cbPath The size of the output buffer, in bytes. Must be at + * least 128 bytes! + */ +static int supR3HardenedMainGetTrustedLib(const char *pszProgName, uint32_t fMainFlags, char *pszPath, size_t cbPath) +{ + supR3HardenedPathAppPrivateArch(pszPath, sizeof(cbPath) - 10); + const char *pszSubDirSlash; + switch (g_fSupHardenedMain & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_APP_BIN: +#ifdef RT_OS_DARWIN + case SUPSECMAIN_FLAGS_LOC_OSX_HLP_APP: +#endif + pszSubDirSlash = "/"; + break; + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + pszSubDirSlash = "/testcase/"; + break; + default: + pszSubDirSlash = "/"; + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: Unknown program binary location: %#x\n", g_fSupHardenedMain); + } +#ifdef RT_OS_DARWIN + if (fMainFlags & SUPSECMAIN_FLAGS_OSX_VM_APP) + pszProgName = "VirtualBox"; +#else + RT_NOREF1(fMainFlags); +#endif + size_t cch = suplibHardenedStrLen(pszPath); + return suplibHardenedStrCopyEx(&pszPath[cch], cbPath - cch, pszSubDirSlash, pszProgName, SUPLIB_DLL_SUFF, NULL); +} + + +/** + * Loads the DLL/SO/DYLIB containing the actual program and + * resolves the TrustedError symbol. + * + * This is very similar to supR3HardenedMainGetTrustedMain(). + * + * @returns Pointer to the trusted error symbol if it is exported, NULL + * and no error messages otherwise. + * @param pszProgName The program name. + */ +static PFNSUPTRUSTEDERROR supR3HardenedMainGetTrustedError(const char *pszProgName) +{ + /* + * Don't bother if the main() function didn't advertise any TrustedError + * export. It's both a waste of time and may trigger additional problems, + * confusing or obscuring the original issue. + */ + if (!(g_fSupHardenedMain & SUPSECMAIN_FLAGS_TRUSTED_ERROR)) + return NULL; + + /* + * Construct the name. + */ + char szPath[RTPATH_MAX]; + supR3HardenedMainGetTrustedLib(pszProgName, g_fSupHardenedMain, szPath, sizeof(szPath)); + + /* + * Open it and resolve the symbol. + */ +#if defined(RT_OS_WINDOWS) + supR3HardenedWinEnableThreadCreation(); + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/, 0 /*fMainFlags*/); + if (!hMod) + return NULL; + FARPROC pfn = GetProcAddress(hMod, SUP_HARDENED_SYM("TrustedError")); + if (!pfn) + return NULL; + return (PFNSUPTRUSTEDERROR)pfn; + +#else + /* the dlopen crowd */ + void *pvMod = dlopen(szPath, RTLD_NOW | RTLD_GLOBAL); + if (!pvMod) + return NULL; + void *pvSym = dlsym(pvMod, SUP_HARDENED_SYM("TrustedError")); + if (!pvSym) + return NULL; + return (PFNSUPTRUSTEDERROR)(uintptr_t)pvSym; +#endif +} + + +/** + * Loads the DLL/SO/DYLIB containing the actual program and + * resolves the TrustedMain symbol. + * + * @returns Pointer to the trusted main of the actual program. + * @param pszProgName The program name. + * @param fMainFlags The flags passed to SUPR3HardenedMain. + * @remarks This function will not return on failure. + */ +static PFNSUPTRUSTEDMAIN supR3HardenedMainGetTrustedMain(const char *pszProgName, uint32_t fMainFlags) +{ + /* + * Construct the name. + */ + char szPath[RTPATH_MAX]; + supR3HardenedMainGetTrustedLib(pszProgName, fMainFlags, szPath, sizeof(szPath)); + + /* + * Open it and resolve the symbol. + */ +#if defined(RT_OS_WINDOWS) + HMODULE hMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, false /*fSystem32Only*/, 0 /*fMainFlags*/); + if (!hMod) + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: LoadLibrary \"%s\" failed, rc=%d\n", + szPath, RtlGetLastWin32Error()); + FARPROC pfn = GetProcAddress(hMod, SUP_HARDENED_SYM("TrustedMain")); + if (!pfn) + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: Entrypoint \"TrustedMain\" not found in \"%s\" (rc=%d)\n", + szPath, RtlGetLastWin32Error()); + return (PFNSUPTRUSTEDMAIN)pfn; + +#else + /* the dlopen crowd */ + void *pvMod = dlopen(szPath, RTLD_NOW | RTLD_GLOBAL); + if (!pvMod) + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: dlopen(\"%s\",) failed: %s\n", + szPath, dlerror()); + void *pvSym = dlsym(pvMod, SUP_HARDENED_SYM("TrustedMain")); + if (!pvSym) + supR3HardenedFatal("supR3HardenedMainGetTrustedMain: Entrypoint \"TrustedMain\" not found in \"%s\"!\ndlerror: %s\n", + szPath, dlerror()); + return (PFNSUPTRUSTEDMAIN)(uintptr_t)pvSym; +#endif +} + + +DECLHIDDEN(int) SUPR3HardenedMain(const char *pszProgName, uint32_t fFlags, int argc, char **argv, char **envp) +{ + SUP_DPRINTF(("SUPR3HardenedMain: pszProgName=%s fFlags=%#x\n", pszProgName, fFlags)); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED; + + /* + * Note! At this point there is no IPRT, so we will have to stick + * to basic CRT functions that everyone agree upon. + */ + g_pszSupLibHardenedProgName = pszProgName; + g_fSupHardenedMain = fFlags; + g_SupPreInitData.u32Magic = SUPPREINITDATA_MAGIC; + g_SupPreInitData.u32EndMagic = SUPPREINITDATA_MAGIC; +#ifdef RT_OS_WINDOWS + if (!g_fSupEarlyProcessInit) +#endif + g_SupPreInitData.Data.hDevice = SUP_HDEVICE_NIL; + + /* + * Determine the full exe path as we'll be needing it for the verify all + * call(s) below. (We have to do this early on Linux because we * *might* + * not be able to access /proc/self/exe after the seteuid call.) + */ + supR3HardenedGetFullExePath(); +#ifdef RT_OS_WINDOWS + supR3HardenedWinInitAppBin(fFlags); +#endif + +#ifdef SUP_HARDENED_SUID + /* + * Grab any options from the environment. + */ + supR3GrabOptions(); + + /* + * Check that we're root, if we aren't then the installation is butchered. + */ + g_uid = getuid(); + g_gid = getgid(); + if (geteuid() != 0 /* root */) + supR3HardenedFatalMsg("SUPR3HardenedMain", kSupInitOp_RootCheck, VERR_PERMISSION_DENIED, + "Effective UID is not root (euid=%d egid=%d uid=%d gid=%d)", + geteuid(), getegid(), g_uid, g_gid); +#endif /* SUP_HARDENED_SUID */ + +#ifdef RT_OS_WINDOWS + /* + * Windows: First respawn. On Windows we will respawn the process twice to establish + * something we can put some kind of reliable trust in. The first respawning aims + * at dropping compatibility layers and process "security" solutions. + */ + if ( !g_fSupEarlyProcessInit + && !(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV) + && supR3HardenedWinIsReSpawnNeeded(1 /*iWhich*/, argc, argv)) + { + SUP_DPRINTF(("SUPR3HardenedMain: Respawn #1\n")); + supR3HardenedWinInit(SUPSECMAIN_FLAGS_DONT_OPEN_DEV | SUPSECMAIN_FLAGS_FIRST_PROCESS, false /*fAvastKludge*/); + supR3HardenedVerifyAll(true /* fFatal */, pszProgName, g_szSupLibHardenedExePath, fFlags); + return supR3HardenedWinReSpawn(1 /*iWhich*/); + } + + /* + * Windows: Initialize the image verification global data so we can verify the + * signature of the process image and hook the core of the DLL loader API so we + * can check the signature of all DLLs mapped into the process. (Already done + * by early VM process init.) + */ + if (!g_fSupEarlyProcessInit) + supR3HardenedWinInit(fFlags, true /*fAvastKludge*/); +#endif /* RT_OS_WINDOWS */ + + /* + * Validate the installation. + */ + supR3HardenedVerifyAll(true /* fFatal */, pszProgName, g_szSupLibHardenedExePath, fFlags); + + /* + * The next steps are only taken if we actually need to access the support + * driver. (Already done by early process init.) + */ + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + { +#ifdef RT_OS_WINDOWS + /* + * Windows: Must have done early process init if we get here. + */ + if (!g_fSupEarlyProcessInit) + supR3HardenedFatalMsg("SUPR3HardenedMain", kSupInitOp_Integrity, VERR_WRONG_ORDER, + "Early process init was somehow skipped."); + + /* + * Windows: The second respawn. This time we make a special arrangement + * with vboxdrv to monitor access to the new process from its inception. + */ + if (supR3HardenedWinIsReSpawnNeeded(2 /* iWhich*/, argc, argv)) + { + SUP_DPRINTF(("SUPR3HardenedMain: Respawn #2\n")); + return supR3HardenedWinReSpawn(2 /* iWhich*/); + } + SUP_DPRINTF(("SUPR3HardenedMain: Final process, opening VBoxDrv...\n")); + supR3HardenedWinFlushLoaderCache(); + +#else + /* + * Open the vboxdrv device. + */ + supR3HardenedMainOpenDevice(); +#endif /* !RT_OS_WINDOWS */ + } + +#ifdef RT_OS_WINDOWS + /* + * Windows: Enable the use of windows APIs to verify images at load time. + */ + supR3HardenedWinEnableThreadCreation(); + supR3HardenedWinFlushLoaderCache(); + supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(g_pszSupLibHardenedProgName); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_VERIFY_TRUST_READY; +#else /* !RT_OS_WINDOWS */ +# if defined(RT_OS_DARWIN) + supR3HardenedDarwinInit(); +# elif !defined(RT_OS_FREEBSD) /** @todo Portme. */ + /* + * Posix: Hook the load library interface interface. + */ + supR3HardenedPosixInit(); +# endif +#endif /* !RT_OS_WINDOWS */ + +#ifdef SUP_HARDENED_SUID + /* + * Grab additional capabilities / privileges. + */ + supR3HardenedMainGrabCapabilites(); + + /* + * Drop any root privileges we might be holding (won't return on failure) + */ + supR3HardenedMainDropPrivileges(); +#endif + + /* + * Purge any environment variables and command line arguments considered harmful. + */ + /** @todo May need to move this to a much earlier stage on windows. */ + supR3HardenedMainPurgeEnvironment(envp); + supR3HardenedMainPurgeArgs(argc, argv, &argc, &argv); + + /* + * Load the IPRT, hand the SUPLib part the open driver and + * call RTR3InitEx. + */ + SUP_DPRINTF(("SUPR3HardenedMain: Load Runtime...\n")); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_INIT_RUNTIME; + supR3HardenedMainInitRuntime(fFlags); +#ifdef RT_OS_WINDOWS + supR3HardenedWinModifyDllSearchPath(fFlags, g_szSupLibHardenedAppBinPath); +#endif + + /* + * Load the DLL/SO/DYLIB containing the actual program + * and pass control to it. + */ + SUP_DPRINTF(("SUPR3HardenedMain: Load TrustedMain...\n")); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_GET_TRUSTED_MAIN; + PFNSUPTRUSTEDMAIN pfnTrustedMain = supR3HardenedMainGetTrustedMain(pszProgName, fFlags); + + SUP_DPRINTF(("SUPR3HardenedMain: Calling TrustedMain (%p)...\n", pfnTrustedMain)); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN; + return pfnTrustedMain(argc, argv, envp); +} + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplate.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplate.cpp new file mode 100644 index 00000000..1c36e386 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplate.cpp @@ -0,0 +1,44 @@ +/* $Id: SUPR3HardenedMainTemplate.cpp $ */ +/** @file + * Hardened main() template. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain(PROGRAM_NAME_STR, 0, argc, argv, envp); +} + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplateTestcase.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplateTestcase.cpp new file mode 100644 index 00000000..c01ea868 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplateTestcase.cpp @@ -0,0 +1,44 @@ +/* $Id: SUPR3HardenedMainTemplateTestcase.cpp $ */ +/** @file + * Hardened main() template for testcases (in testcase subdir). + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <VBox/sup.h> + + +int main(int argc, char **argv, char **envp) +{ + return SUPR3HardenedMain(PROGRAM_NAME_STR, SUPSECMAIN_FLAGS_LOC_TESTCASE, argc, argv, envp); +} + diff --git a/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp b/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp new file mode 100644 index 00000000..ce456e3b --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp @@ -0,0 +1,189 @@ +/* $Id: SUPR3HardenedNoCrt.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main() no-crt routines. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if RT_OS_WINDOWS +# include <iprt/win/windows.h> +#endif + +#include <VBox/sup.h> + +#include "SUPLibInternal.h" + + +#ifdef SUP_HARDENED_NEED_CRT_FUNCTIONS /** @todo this crap is obsolete. */ + +/** 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; +} + + +/** 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 new file mode 100644 index 00000000..e78a397c --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp @@ -0,0 +1,2149 @@ +/* $Id: SUPR3HardenedVerify.cpp $ */ +/** @file + * VirtualBox Support Library - Verification of Hardened Installation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#if defined(RT_OS_OS2) +# define INCL_BASE +# define INCL_ERRORS +# include <os2.h> +# include <stdio.h> +# include <stdlib.h> +# include <unistd.h> +# include <sys/fcntl.h> +# include <sys/errno.h> +# include <sys/syslimits.h> + +#elif defined(RT_OS_WINDOWS) +# include <iprt/nt/nt-and-windows.h> +# ifndef IN_SUP_HARDENED_R3 +# include <stdio.h> +# endif + +#else /* UNIXes */ +# include <sys/types.h> +# include <stdio.h> +# include <stdlib.h> +# include <dirent.h> +# include <dlfcn.h> +# include <fcntl.h> +# include <limits.h> +# include <errno.h> +# include <unistd.h> +# include <sys/stat.h> +# include <sys/time.h> +# include <sys/fcntl.h> +# include <pwd.h> +# ifdef RT_OS_DARWIN +# include <mach-o/dyld.h> +# endif + +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "SUPLibInternal.h" +#if defined(RT_OS_WINDOWS) && defined(VBOX_WITH_HARDENING) +# define SUPHNTVI_NO_NT_STUFF +# include "win/SUPHardenedVerify-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max path length acceptable for a trusted path. */ +#define SUPR3HARDENED_MAX_PATH 260U + +/** Enable to resolve symlinks using realpath() instead of cooking our own stuff. */ +#define SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH 1 + +#ifdef RT_OS_SOLARIS +# 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 RTStrICmp +#else +# define SUP_COMP_FILENAME suplibHardenedStrCmp +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * The files that gets verified. + * + * @todo This needs reviewing against the linux packages. + * @todo The excessive use of kSupID_AppSharedLib needs to be reviewed at some point. For + * the time being we're building the linux packages with SharedLib pointing to + * AppPrivArch (lazy bird). + * + * @remarks If you add executables here, you might need to update + * g_apszSupNtVpAllowedVmExes in SUPHardenedVerifyProcess-win.cpp. + */ +static SUPINSTFILE const g_aSupInstallFiles[] = +{ + /* type, dir, fOpt, "pszFile" */ + /* ---------------------------------------------------------------------- */ + { kSupIFT_Dll, kSupID_AppPrivArch, false, "VMMR0.r0" }, + { kSupIFT_Dll, kSupID_AppPrivArch, false, "VBoxDDR0.r0" }, + +#ifdef VBOX_WITH_RAW_MODE + { kSupIFT_Rc, kSupID_AppPrivArch, false, "VMMRC.rc" }, + { kSupIFT_Rc, kSupID_AppPrivArch, false, "VBoxDDRC.rc" }, +#endif + + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxRT" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxVMM" SUPLIB_DLL_SUFF }, +#if HC_ARCH_BITS == 32 + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM32" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxREM64" SUPLIB_DLL_SUFF }, +#endif + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDD2" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxDDU" SUPLIB_DLL_SUFF }, + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxVMMPreload" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVMMPreload" SUPLIB_DLL_SUFF }, + +//#ifdef VBOX_WITH_DEBUGGER_GUI + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxDbg3" SUPLIB_DLL_SUFF }, +//#endif + +//#ifdef VBOX_WITH_SHARED_CLIPBOARD + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedClipboard" SUPLIB_DLL_SUFF }, +//#endif +//#ifdef VBOX_WITH_SHARED_FOLDERS + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedFolders" SUPLIB_DLL_SUFF }, +//#endif +//#ifdef VBOX_WITH_DRAG_AND_DROP + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxDragAndDropSvc" SUPLIB_DLL_SUFF }, +//#endif +//#ifdef VBOX_WITH_GUEST_PROPS + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestPropSvc" SUPLIB_DLL_SUFF }, +//#endif +//#ifdef VBOX_WITH_GUEST_CONTROL + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxGuestControlSvc" SUPLIB_DLL_SUFF }, +//#endif + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHostChannel" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSharedCrOpenGL" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhostcrutil" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLhosterrorspu" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxOGLrenderspu" SUPLIB_DLL_SUFF }, + + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxManage" SUPLIB_EXE_SUFF }, + +#ifdef VBOX_WITH_MAIN + { kSupIFT_Exe, kSupID_AppBin, false, "VBoxSVC" SUPLIB_EXE_SUFF }, + #ifdef RT_OS_WINDOWS + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxC" SUPLIB_DLL_SUFF }, + #else + { kSupIFT_Exe, kSupID_AppPrivArch, false, "VBoxXPCOMIPCD" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, false, "VBoxXPCOM" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxXPCOMIPCC" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxC" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArchComp, false, "VBoxSVCM" SUPLIB_DLL_SUFF }, + { kSupIFT_Data, kSupID_AppPrivArchComp, false, "VBoxXPCOMBase.xpt" }, + #endif +#endif + + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VRDPAuth" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxAuth" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxVRDP" SUPLIB_DLL_SUFF }, + +//#ifdef VBOX_WITH_HEADLESS + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxHeadless" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxHeadless" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxVideoRecFB" SUPLIB_DLL_SUFF }, +//#endif + +//#ifdef VBOX_WITH_QTGUI + { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBox" SUPLIB_EXE_SUFF }, +# ifdef RT_OS_DARWIN + { kSupIFT_Exe, kSupID_AppMacHelper, true, "VirtualBoxVM" SUPLIB_EXE_SUFF }, +# else + { kSupIFT_Exe, kSupID_AppBin, true, "VirtualBoxVM" SUPLIB_EXE_SUFF }, +# endif + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VirtualBoxVM" SUPLIB_DLL_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "UICommon" SUPLIB_DLL_SUFF }, +# if !defined(RT_OS_DARWIN) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) + { kSupIFT_Dll, kSupID_AppSharedLib, true, "VBoxKeyboard" SUPLIB_DLL_SUFF }, +# endif +//#endif + +//#ifdef VBOX_WITH_VBOXSDL + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxSDL" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxSDL" SUPLIB_DLL_SUFF }, +//#endif + +//#ifdef VBOX_WITH_WEBSERVICES + { kSupIFT_Exe, kSupID_AppBin, true, "vboxwebsrv" SUPLIB_EXE_SUFF }, +//#endif + +#ifdef RT_OS_LINUX + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxTunctl" SUPLIB_EXE_SUFF }, +#endif + +//#ifdef VBOX_WITH_NETFLT + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxNetDHCP" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxNetDHCP" SUPLIB_DLL_SUFF }, +//#endif + +//#ifdef VBOX_WITH_LWIP_NAT + { kSupIFT_Exe, kSupID_AppBin, true, "VBoxNetNAT" SUPLIB_EXE_SUFF }, + { kSupIFT_Dll, kSupID_AppPrivArch, true, "VBoxNetNAT" SUPLIB_DLL_SUFF }, +//#endif +#if defined(VBOX_WITH_HARDENING) && defined(RT_OS_WINDOWS) +# define HARDENED_TESTCASE_BIN_ENTRY(a_szName) \ + { kSupIFT_TestExe, kSupID_AppBin, true, a_szName SUPLIB_EXE_SUFF }, \ + { kSupIFT_TestDll, kSupID_AppBin, true, a_szName SUPLIB_DLL_SUFF } + HARDENED_TESTCASE_BIN_ENTRY("tstMicro"), + HARDENED_TESTCASE_BIN_ENTRY("tstPDMAsyncCompletion"), + HARDENED_TESTCASE_BIN_ENTRY("tstPDMAsyncCompletionStress"), + HARDENED_TESTCASE_BIN_ENTRY("tstVMM"), + HARDENED_TESTCASE_BIN_ENTRY("tstVMREQ"), +# define HARDENED_TESTCASE_ENTRY(a_szName) \ + { kSupIFT_TestExe, kSupID_Testcase, true, a_szName SUPLIB_EXE_SUFF }, \ + { kSupIFT_TestDll, kSupID_Testcase, true, a_szName SUPLIB_DLL_SUFF } + HARDENED_TESTCASE_ENTRY("tstCFGM"), + HARDENED_TESTCASE_ENTRY("tstGIP-2"), + HARDENED_TESTCASE_ENTRY("tstIntNet-1"), + HARDENED_TESTCASE_ENTRY("tstMMHyperHeap"), + HARDENED_TESTCASE_ENTRY("tstRTR0ThreadPreemptionDriver"), + HARDENED_TESTCASE_ENTRY("tstRTR0MemUserKernelDriver"), + HARDENED_TESTCASE_ENTRY("tstRTR0SemMutexDriver"), + HARDENED_TESTCASE_ENTRY("tstRTR0TimerDriver"), + HARDENED_TESTCASE_ENTRY("tstSSM"), +#endif +}; + + +/** Array parallel to g_aSupInstallFiles containing per-file status info. */ +static SUPVERIFIEDFILE g_aSupVerifiedFiles[RT_ELEMENTS(g_aSupInstallFiles)]; + +/** Array index by install directory specifier containing info about verified directories. */ +static SUPVERIFIEDDIR g_aSupVerifiedDirs[kSupID_End]; + + +/** + * Assembles the path to a directory. + * + * @returns VINF_SUCCESS on success, some error code on failure (fFatal + * decides whether it returns or not). + * + * @param enmDir The directory. + * @param pszDst Where to assemble the path. + * @param cchDst The size of the buffer. + * @param fFatal Whether failures should be treated as fatal (true) or not (false). + * @param pFile The file (for darwin helper app paths). + */ +static int supR3HardenedMakePath(SUPINSTDIR enmDir, char *pszDst, size_t cchDst, bool fFatal, PCSUPINSTFILE pFile) +{ + int rc; + switch (enmDir) + { + case kSupID_AppBin: + rc = supR3HardenedPathAppBin(pszDst, cchDst); + break; + case kSupID_AppSharedLib: + rc = supR3HardenedPathAppSharedLibs(pszDst, cchDst); + break; + case kSupID_AppPrivArch: + rc = supR3HardenedPathAppPrivateArch(pszDst, cchDst); + break; + case kSupID_AppPrivArchComp: + rc = supR3HardenedPathAppPrivateArch(pszDst, cchDst); + if (RT_SUCCESS(rc)) + { + size_t off = suplibHardenedStrLen(pszDst); + if (cchDst - off >= sizeof("/components")) + suplibHardenedMemCopy(&pszDst[off], "/components", sizeof("/components")); + else + rc = VERR_BUFFER_OVERFLOW; + } + break; + case kSupID_AppPrivNoArch: + rc = supR3HardenedPathAppPrivateNoArch(pszDst, cchDst); + break; + case kSupID_Testcase: + rc = supR3HardenedPathAppBin(pszDst, cchDst); + if (RT_SUCCESS(rc)) + { + size_t off = suplibHardenedStrLen(pszDst); + if (cchDst - off >= sizeof("/testcase")) + suplibHardenedMemCopy(&pszDst[off], "/testcase", sizeof("/testcase")); + else + rc = VERR_BUFFER_OVERFLOW; + } + break; +#ifdef RT_OS_DARWIN + case kSupID_AppMacHelper: + rc = supR3HardenedPathAppBin(pszDst, cchDst); + if (RT_SUCCESS(rc)) + { + /* Up one level from the VirtualBox.app/Contents/MacOS directory: */ + size_t offDst = suplibHardenedStrLen(pszDst); + while (offDst > 1 && pszDst[offDst - 1] == '/') + offDst--; + while (offDst > 1 && pszDst[offDst - 1] != '/') + offDst--; + + /* Construct the path to the helper application's Contents/MacOS directory: */ + size_t cchFile = suplibHardenedStrLen(pFile->pszFile); + if (offDst + cchFile + sizeof("Resources/.app/Contents/MacOS") <= cchDst) + { + suplibHardenedMemCopy(&pszDst[offDst], RT_STR_TUPLE("Resources/")); + offDst += sizeof("Resources/") - 1; + suplibHardenedMemCopy(&pszDst[offDst], pFile->pszFile, cchFile); + offDst += cchFile; + suplibHardenedMemCopy(&pszDst[offDst], RT_STR_TUPLE(".app/Contents/MacOS") + 1); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + break; +#endif + default: + return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedMakePath: enmDir=%d\n", enmDir); + } + if (RT_FAILURE(rc)) + supR3HardenedError(rc, fFatal, + "supR3HardenedMakePath: enmDir=%d rc=%d\n", enmDir, rc); + NOREF(pFile); + return rc; +} + + + +/** + * Assembles the path to a file table entry, with or without the actual filename. + * + * @returns VINF_SUCCESS on success, some error code on failure (fFatal + * decides whether it returns or not). + * + * @param pFile The file table entry. + * @param pszDst Where to assemble the path. + * @param cchDst The size of the buffer. + * @param fWithFilename If set, the filename is included, otherwise it is omitted (no trailing slash). + * @param fFatal Whether failures should be treated as fatal (true) or not (false). + */ +static int supR3HardenedMakeFilePath(PCSUPINSTFILE pFile, char *pszDst, size_t cchDst, bool fWithFilename, bool fFatal) +{ + /* + * Combine supR3HardenedMakePath and the filename. + */ + int rc = supR3HardenedMakePath(pFile->enmDir, pszDst, cchDst, fFatal, pFile); + if (RT_SUCCESS(rc) && fWithFilename) + { + size_t cchFile = suplibHardenedStrLen(pFile->pszFile); + size_t off = suplibHardenedStrLen(pszDst); + if (cchDst - off >= cchFile + 2) + { + pszDst[off++] = '/'; + suplibHardenedMemCopy(&pszDst[off], pFile->pszFile, cchFile + 1); + } + else + rc = supR3HardenedError(VERR_BUFFER_OVERFLOW, fFatal, + "supR3HardenedMakeFilePath: pszFile=%s off=%lu\n", + pFile->pszFile, (long)off); + } + return rc; +} + + +/** + * Verifies a directory. + * + * @returns VINF_SUCCESS on success. On failure, an error code is returned if + * fFatal is clear and if it's set the function wont return. + * @param enmDir The directory specifier. + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + * @param pFile The file (for darwin helper app paths). + */ +DECLHIDDEN(int) supR3HardenedVerifyFixedDir(SUPINSTDIR enmDir, bool fFatal, PCSUPINSTFILE pFile) +{ + /* + * Validate the index just to be on the safe side... + */ + if (enmDir <= kSupID_Invalid || enmDir >= kSupID_End) + return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyDir: enmDir=%d\n", enmDir); + + /* + * Already validated? + */ + if (g_aSupVerifiedDirs[enmDir].fValidated) + return VINF_SUCCESS; /** @todo revalidate? */ + + /* initialize the entry. */ + if (g_aSupVerifiedDirs[enmDir].hDir != 0) + supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyDir: hDir=%p enmDir=%d\n", + (void *)g_aSupVerifiedDirs[enmDir].hDir, enmDir); + g_aSupVerifiedDirs[enmDir].hDir = -1; + g_aSupVerifiedDirs[enmDir].fValidated = false; + + /* + * Make the path and open the directory. + */ + char szPath[RTPATH_MAX]; + int rc = supR3HardenedMakePath(enmDir, szPath, sizeof(szPath), fFatal, pFile); + if (RT_SUCCESS(rc)) + { +#if defined(RT_OS_WINDOWS) + PRTUTF16 pwszPath; + rc = RTStrToUtf16(szPath, &pwszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hDir = CreateFileW(pwszPath, + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hDir != INVALID_HANDLE_VALUE) + { + /** @todo check the type */ + /* That's all on windows, for now at least... */ + g_aSupVerifiedDirs[enmDir].hDir = (intptr_t)hDir; + g_aSupVerifiedDirs[enmDir].fValidated = true; + } + else if (enmDir == kSupID_Testcase) + { + g_aSupVerifiedDirs[enmDir].fValidated = true; + rc = VINF_SUCCESS; /* Optional directory, ignore if missing. */ + } + else + { + int err = RtlGetLastWin32Error(); + rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal, + "supR3HardenedVerifyDir: Failed to open \"%s\": err=%d\n", + szPath, err); + } + RTUtf16Free(pwszPath); + } + else + rc = supR3HardenedError(rc, fFatal, + "supR3HardenedVerifyDir: Failed to convert \"%s\" to UTF-16: err=%d\n", szPath, rc); + +#else /* UNIXY */ + int fd = open(szPath, O_RDONLY, 0); + if (fd >= 0) + { + /* + * On unixy systems we'll make sure the directory is owned by root + * and not writable by the group and user. + */ + struct stat st; + if (!fstat(fd, &st)) + { + + if ( st.st_uid == 0 + && !(st.st_mode & (S_IWGRP | S_IWOTH)) + && S_ISDIR(st.st_mode)) + { + g_aSupVerifiedDirs[enmDir].hDir = fd; + g_aSupVerifiedDirs[enmDir].fValidated = true; + } + else + { + if (!S_ISDIR(st.st_mode)) + rc = supR3HardenedError(VERR_NOT_A_DIRECTORY, fFatal, + "supR3HardenedVerifyDir: \"%s\" is not a directory\n", + szPath, (long)st.st_uid); + else if (st.st_uid) + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyDir: Cannot trust the directory \"%s\": not owned by root (st_uid=%ld)\n", + szPath, (long)st.st_uid); + else + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyDir: Cannot trust the directory \"%s\": group and/or other writable (st_mode=0%lo)\n", + szPath, (long)st.st_mode); + close(fd); + } + } + else + { + int err = errno; + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyDir: Failed to fstat \"%s\": %s (%d)\n", + szPath, strerror(err), err); + close(fd); + } + } + else if (enmDir == kSupID_Testcase) + { + g_aSupVerifiedDirs[enmDir].fValidated = true; + rc = VINF_SUCCESS; /* Optional directory, ignore if missing. */ + } + else + { + int err = errno; + rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal, + "supR3HardenedVerifyDir: Failed to open \"%s\": %s (%d)\n", + szPath, strerror(err), err); + } +#endif /* UNIXY */ + } + + return rc; +} + + +#ifdef RT_OS_WINDOWS +/** + * Opens the file for verification. + * + * @returns VINF_SUCCESS on success. On failure, an error code is returned if + * fFatal is clear and if it's set the function wont return. + * @param pFile The file entry. + * @param fFatal Whether validation failures should be treated as + * kl fatal (true) or not (false). + * @param phFile The file handle, set to -1 if we failed to open + * the file. The function may return VINF_SUCCESS + * and a -1 handle if the file is optional. + */ +static int supR3HardenedVerifyFileOpen(PCSUPINSTFILE pFile, bool fFatal, intptr_t *phFile) +{ + *phFile = -1; + + char szPath[RTPATH_MAX]; + int rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal); + if (RT_SUCCESS(rc)) + { + PRTUTF16 pwszPath; + rc = RTStrToUtf16(szPath, &pwszPath); + if (RT_SUCCESS(rc)) + { + HANDLE hFile = CreateFileW(pwszPath, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + *phFile = (intptr_t)hFile; + rc = VINF_SUCCESS; + } + else + { + int err = RtlGetLastWin32Error(); + if ( !pFile->fOptional + || ( err != ERROR_FILE_NOT_FOUND + && (err != ERROR_PATH_NOT_FOUND || pFile->enmDir != kSupID_Testcase) ) ) + rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal, + "supR3HardenedVerifyFileInternal: Failed to open '%s': err=%d\n", szPath, err); + } + RTUtf16Free(pwszPath); + } + else + rc = supR3HardenedError(rc, fFatal, "supR3HardenedVerifyFileInternal: Failed to convert '%s' to UTF-16: %Rrc\n", + szPath, rc); + } + return rc; +} + + +/** + * Worker for supR3HardenedVerifyFileInternal. + * + * @returns VINF_SUCCESS on success. On failure, an error code is returned if + * fFatal is clear and if it's set the function wont return. + * @param pFile The file entry. + * @param pVerified The verification record. + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + * @param fLeaveFileOpen Whether the file should be left open. + */ +static int supR3HardenedVerifyFileSignature(PCSUPINSTFILE pFile, PSUPVERIFIEDFILE pVerified, bool fFatal, bool fLeaveFileOpen) +{ +# if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_R3_STATIC) /* Latter: Not in VBoxCpuReport and friends. */ + + /* + * Open the file if we have to. + */ + int rc; + intptr_t hFileOpened; + intptr_t hFile = pVerified->hFile; + if (hFile != -1) + hFileOpened = -1; + else + { + rc = supR3HardenedVerifyFileOpen(pFile, fFatal, &hFileOpened); + if (RT_FAILURE(rc)) + return rc; + hFile = hFileOpened; + } + + /* + * Verify the signature. + */ + 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((HANDLE)hFile, fFlags, &ErrInfo); + if (RT_SUCCESS(rc)) + pVerified->fCheckedSignature = true; + else + { + pVerified->fCheckedSignature = false; + rc = supR3HardenedError(rc, fFatal, "supR3HardenedVerifyFileInternal: '%s': Image verify error rc=%Rrc: %s\n", + pFile->pszFile, rc, szErr); + + } + + /* + * Close the handle if we opened the file and we should close it. + */ + if (hFileOpened != -1) + { + if (fLeaveFileOpen && RT_SUCCESS(rc)) + pVerified->hFile = hFileOpened; + else + NtClose((HANDLE)hFileOpened); + } + + return rc; + +# else /* Not checking signatures. */ + RT_NOREF4(pFile, pVerified, fFatal, fLeaveFileOpen); + return VINF_SUCCESS; +# endif /* Not checking signatures. */ +} +#endif + + +/** + * Verifies a file entry. + * + * @returns VINF_SUCCESS on success. On failure, an error code is returned if + * fFatal is clear and if it's set the function wont return. + * + * @param iFile The file table index of the file to be verified. + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + * @param fLeaveFileOpen Whether the file should be left open. + * @param fVerifyAll Set if this is an verify all call and we will + * postpone signature checking. + */ +static int supR3HardenedVerifyFileInternal(int iFile, bool fFatal, bool fLeaveFileOpen, bool fVerifyAll) +{ +#ifndef RT_OS_WINDOWS + RT_NOREF1(fVerifyAll); +#endif + PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile]; + PSUPVERIFIEDFILE pVerified = &g_aSupVerifiedFiles[iFile]; + + /* + * Already done validation? Do signature validation if we haven't yet. + */ + if (pVerified->fValidated) + { + /** @todo revalidate? Check that the file hasn't been replace or similar. */ +#ifdef RT_OS_WINDOWS + if (!pVerified->fCheckedSignature && !fVerifyAll) + return supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen); +#endif + return VINF_SUCCESS; + } + + + /* initialize the entry. */ + if (pVerified->hFile != 0) + supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyFileInternal: hFile=%p (%s)\n", + (void *)pVerified->hFile, pFile->pszFile); + pVerified->hFile = -1; + pVerified->fValidated = false; +#ifdef RT_OS_WINDOWS + pVerified->fCheckedSignature = false; +#endif + + /* + * Verify the directory then proceed to open it. + * (This'll make sure the directory is opened and that we can (later) + * use openat if we wish.) + */ + int rc = supR3HardenedVerifyFixedDir(pFile->enmDir, fFatal, pFile); + if (RT_SUCCESS(rc)) + { +#if defined(RT_OS_WINDOWS) + rc = supR3HardenedVerifyFileOpen(pFile, fFatal, &pVerified->hFile); + if (RT_SUCCESS(rc)) + { + if (!fVerifyAll) + rc = supR3HardenedVerifyFileSignature(pFile, pVerified, fFatal, fLeaveFileOpen); + if (RT_SUCCESS(rc)) + { + pVerified->fValidated = true; + if (!fLeaveFileOpen) + { + NtClose((HANDLE)pVerified->hFile); + pVerified->hFile = -1; + } + } + } +#else /* !RT_OS_WINDOWS */ + char szPath[RTPATH_MAX]; + rc = supR3HardenedMakeFilePath(pFile, szPath, sizeof(szPath), true /*fWithFilename*/, fFatal); + if (RT_SUCCESS(rc)) + { + int fd = open(szPath, O_RDONLY, 0); + if (fd >= 0) + { + /* + * On unixy systems we'll make sure the file is owned by root + * and not writable by the group and user. + */ + struct stat st; + if (!fstat(fd, &st)) + { + if ( st.st_uid == 0 + && !(st.st_mode & (S_IWGRP | S_IWOTH)) + && S_ISREG(st.st_mode)) + { + /* it's valid. */ + if (fLeaveFileOpen) + pVerified->hFile = fd; + else + close(fd); + pVerified->fValidated = true; + } + else + { + if (!S_ISREG(st.st_mode)) + rc = supR3HardenedError(VERR_IS_A_DIRECTORY, fFatal, + "supR3HardenedVerifyFileInternal: \"%s\" is not a regular file\n", + szPath, (long)st.st_uid); + else if (st.st_uid) + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": not owned by root (st_uid=%ld)\n", + szPath, (long)st.st_uid); + else + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyFileInternal: Cannot trust the file \"%s\": group and/or other writable (st_mode=0%lo)\n", + szPath, (long)st.st_mode); + close(fd); + } + } + else + { + int err = errno; + rc = supR3HardenedError(VERR_ACCESS_DENIED, fFatal, + "supR3HardenedVerifyFileInternal: Failed to fstat \"%s\": %s (%d)\n", + szPath, strerror(err), err); + close(fd); + } + } + else + { + int err = errno; + if (!pFile->fOptional || err != ENOENT) + rc = supR3HardenedError(VERR_PATH_NOT_FOUND, fFatal, + "supR3HardenedVerifyFileInternal: Failed to open \"%s\": %s (%d)\n", + szPath, strerror(err), err); + } + } +#endif /* !RT_OS_WINDOWS */ + } + + return rc; +} + + +/** + * Verifies that the specified table entry matches the given filename. + * + * @returns VINF_SUCCESS if matching. On mismatch fFatal indicates whether an + * error is returned or we terminate the application. + * + * @param iFile The file table index. + * @param pszFilename The filename. + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + */ +static int supR3HardenedVerifySameFile(int iFile, const char *pszFilename, bool fFatal) +{ + PCSUPINSTFILE pFile = &g_aSupInstallFiles[iFile]; + + /* + * Construct the full path for the file table entry + * and compare it with the specified file. + */ + char szName[RTPATH_MAX]; + int rc = supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal); + if (RT_FAILURE(rc)) + return rc; + if (SUP_COMP_FILENAME(szName, pszFilename)) + { + /* + * Normalize the two paths and compare again. + */ + rc = VERR_NOT_SAME_DEVICE; +#if defined(RT_OS_WINDOWS) + LPSTR pszIgnored; + char szName2[RTPATH_MAX]; /** @todo Must use UTF-16 here! Code is mixing UTF-8 and native. */ + if ( GetFullPathName(szName, RT_ELEMENTS(szName2), &szName2[0], &pszIgnored) + && GetFullPathName(pszFilename, RT_ELEMENTS(szName), &szName[0], &pszIgnored)) + 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 (!SUP_COMP_FILENAME(szName2, szName)) + rc = VINF_SUCCESS; +#endif + + if (RT_FAILURE(rc)) + { + supR3HardenedMakeFilePath(pFile, szName, sizeof(szName), true /*fWithFilename*/, fFatal); + return supR3HardenedError(rc, fFatal, + "supR3HardenedVerifySameFile: \"%s\" isn't the same as \"%s\"\n", + pszFilename, szName); + } + } + + /* + * Check more stuff like the stat info if it's an already open file? + */ + + + + return VINF_SUCCESS; +} + + +/** + * Verifies a file. + * + * @returns VINF_SUCCESS on success. + * VERR_NOT_FOUND if the file isn't in the table, this isn't ever a fatal error. + * On verification failure, an error code will be returned when fFatal is clear, + * otherwise the program will be terminated. + * + * @param pszFilename The filename. + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + */ +DECLHIDDEN(int) supR3HardenedVerifyFixedFile(const char *pszFilename, bool fFatal) +{ + /* + * Lookup the file and check if it's the same file. + */ + const char *pszName = supR3HardenedPathFilename(pszFilename); + for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++) + if (!SUP_COMP_FILENAME(pszName, g_aSupInstallFiles[iFile].pszFile)) + { + int rc = supR3HardenedVerifySameFile(iFile, pszFilename, fFatal); + if (RT_SUCCESS(rc)) + rc = supR3HardenedVerifyFileInternal(iFile, fFatal, false /* fLeaveFileOpen */, false /* fVerifyAll */); + return rc; + } + + return VERR_NOT_FOUND; +} + + +/** + * Verifies a program, worker for supR3HardenedVerifyAll. + * + * @returns See supR3HardenedVerifyAll. + * @param pszProgName See supR3HardenedVerifyAll. + * @param pszExePath The path to the executable. + * @param fFatal See supR3HardenedVerifyAll. + * @param fLeaveOpen The leave open setting used by + * supR3HardenedVerifyAll. + * @param fMainFlags Flags supplied to SUPR3HardenedMain. + */ +static int supR3HardenedVerifyProgram(const char *pszProgName, const char *pszExePath, bool fFatal, + bool fLeaveOpen, uint32_t fMainFlags) +{ + /* + * Search the table looking for the executable and the DLL/DYLIB/SO. + * Note! On darwin we have a hack in place for VirtualBoxVM helper app + * to share VirtualBox.dylib with the VirtualBox app. This ASSUMES + * that cchProgNameDll is equal or shorter to the exe name. + */ + int rc = VINF_SUCCESS; + bool fExe = false; + bool fDll = false; + size_t const cchProgNameExe = suplibHardenedStrLen(pszProgName); +#ifndef RT_OS_DARWIN + size_t const cchProgNameDll = cchProgNameExe; + NOREF(fMainFlags); +#else + size_t const cchProgNameDll = fMainFlags & SUPSECMAIN_FLAGS_OSX_VM_APP + ? sizeof("VirtualBox") - 1 + : cchProgNameExe; + if (cchProgNameDll > cchProgNameExe) + return supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyProgram: SUPSECMAIN_FLAGS_OSX_VM_APP + '%s'", pszProgName); +#endif + for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++) + if (!suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameDll)) + { + if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Dll + || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestDll) + && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameDll], SUPLIB_DLL_SUFF)) + { + /* This only has to be found (once). */ + if (fDll) + rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyProgram: duplicate DLL entry for \"%s\"\n", pszProgName); + else + rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, + true /* fVerifyAll - check sign later, only final process need check it on load. */); + fDll = true; + } + else if ( ( g_aSupInstallFiles[iFile].enmType == kSupIFT_Exe + || g_aSupInstallFiles[iFile].enmType == kSupIFT_TestExe) + && ( cchProgNameExe == cchProgNameDll + || !suplibHardenedStrNCmp(pszProgName, g_aSupInstallFiles[iFile].pszFile, cchProgNameExe)) + && !suplibHardenedStrCmp(&g_aSupInstallFiles[iFile].pszFile[cchProgNameExe], SUPLIB_EXE_SUFF)) + { + /* Here we'll have to check that the specific program is the same as the entry. */ + if (fExe) + rc = supR3HardenedError(VERR_INTERNAL_ERROR, fFatal, + "supR3HardenedVerifyProgram: duplicate EXE entry for \"%s\"\n", pszProgName); + else + rc = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, false /* fVerifyAll */); + fExe = true; + + supR3HardenedVerifySameFile(iFile, pszExePath, fFatal); + } + } + + /* + * Check the findings. + */ + if (RT_SUCCESS(rc)) + { + if (!fDll && !fExe) + rc = supR3HardenedError(VERR_NOT_FOUND, fFatal, + "supR3HardenedVerifyProgram: Couldn't find the program \"%s\"\n", pszProgName); + else if (!fExe) + rc = supR3HardenedError(VERR_NOT_FOUND, fFatal, + "supR3HardenedVerifyProgram: Couldn't find the EXE entry for \"%s\"\n", pszProgName); + else if (!fDll) + rc = supR3HardenedError(VERR_NOT_FOUND, fFatal, + "supR3HardenedVerifyProgram: Couldn't find the DLL entry for \"%s\"\n", pszProgName); + } + return rc; +} + + +/** + * Verifies all the known files (called from SUPR3HardenedMain). + * + * @returns VINF_SUCCESS on success. + * On verification failure, an error code will be returned when fFatal is clear, + * otherwise the program will be terminated. + * + * @param fFatal Whether validation failures should be treated as + * fatal (true) or not (false). + * @param pszProgName The program name. This is used to verify that + * both the executable and corresponding + * DLL/DYLIB/SO are valid. + * @param pszExePath The path to the executable. + * @param fMainFlags Flags supplied to SUPR3HardenedMain. + */ +DECLHIDDEN(int) supR3HardenedVerifyAll(bool fFatal, const char *pszProgName, const char *pszExePath, uint32_t fMainFlags) +{ + /* + * On windows + */ +#if defined(RT_OS_WINDOWS) + bool fLeaveOpen = true; +#else + bool fLeaveOpen = false; +#endif + + /* + * The verify all the files. + */ + int rc = VINF_SUCCESS; + for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++) + { + int rc2 = supR3HardenedVerifyFileInternal(iFile, fFatal, fLeaveOpen, true /* fVerifyAll */); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + + /* + * Verify the program name, that is to say, check that it's in the table + * (thus verified above) and verify the signature on platforms where we + * sign things. + */ + int rc2 = supR3HardenedVerifyProgram(pszProgName, pszExePath, fFatal, fLeaveOpen, fMainFlags); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc2 = rc; + + return rc; +} + + +/** + * Copies the N messages into the error buffer and returns @a rc. + * + * @returns Returns @a rc + * @param rc The return code. + * @param pErrInfo The error info structure. + * @param cMsgs The number of messages in the ellipsis. + * @param ... Message parts. + */ +static int supR3HardenedSetErrorN(int rc, PRTERRINFO pErrInfo, unsigned cMsgs, ...) +{ + if (pErrInfo) + { + size_t cbErr = pErrInfo->cbMsg; + char *pszErr = pErrInfo->pszMsg; + + va_list va; + va_start(va, cMsgs); + while (cMsgs-- > 0 && cbErr > 0) + { + const char *pszMsg = va_arg(va, const char *); + size_t cchMsg = RT_VALID_PTR(pszMsg) ? suplibHardenedStrLen(pszMsg) : 0; + if (cchMsg >= cbErr) + cchMsg = cbErr - 1; + suplibHardenedMemCopy(pszErr, pszMsg, cchMsg); + pszErr[cchMsg] = '\0'; + pszErr += cchMsg; + cbErr -= cchMsg; + } + va_end(va); + + pErrInfo->rc = rc; + pErrInfo->fFlags |= RTERRINFO_FLAGS_SET; + } + + return rc; +} + + +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/** + * Copies the four messages into the error buffer and returns @a rc. + * + * @returns Returns @a rc + * @param rc The return code. + * @param pErrInfo The error info structure. + * @param pszMsg1 The first message part. + * @param pszMsg2 The second message part. + * @param pszMsg3 The third message part. + * @param pszMsg4 The fourth message part. + */ +static int supR3HardenedSetError4(int rc, PRTERRINFO pErrInfo, const char *pszMsg1, + const char *pszMsg2, const char *pszMsg3, const char *pszMsg4) +{ + return supR3HardenedSetErrorN(rc, pErrInfo, 4, pszMsg1, pszMsg2, pszMsg3, pszMsg4); +} +#endif + + +/** + * Copies the three messages into the error buffer and returns @a rc. + * + * @returns Returns @a rc + * @param rc The return code. + * @param pErrInfo The error info structure. + * @param pszMsg1 The first message part. + * @param pszMsg2 The second message part. + * @param pszMsg3 The third message part. + */ +static int supR3HardenedSetError3(int rc, PRTERRINFO pErrInfo, const char *pszMsg1, + const char *pszMsg2, const char *pszMsg3) +{ + return supR3HardenedSetErrorN(rc, pErrInfo, 3, pszMsg1, pszMsg2, pszMsg3); +} + + +#ifdef SOME_UNUSED_FUNCTION +/** + * Copies the two messages into the error buffer and returns @a rc. + * + * @returns Returns @a rc + * @param rc The return code. + * @param pErrInfo The error info structure. + * @param pszMsg1 The first message part. + * @param pszMsg2 The second message part. + */ +static int supR3HardenedSetError2(int rc, PRTERRINFO pErrInfo, const char *pszMsg1, + const char *pszMsg2) +{ + return supR3HardenedSetErrorN(rc, pErrInfo, 2, pszMsg1, pszMsg2); +} +#endif + + +#ifndef SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH +# if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/** + * Copies the error message to the error buffer and returns @a rc. + * + * @returns Returns @a rc + * @param rc The return code. + * @param pErrInfo The error info structure. + * @param pszMsg The message. + */ +static int supR3HardenedSetError(int rc, PRTERRINFO pErrInfo, const char *pszMsg) +{ + return supR3HardenedSetErrorN(rc, pErrInfo, 1, pszMsg); +} +# endif +#endif + + +/** + * Output from a successfull supR3HardenedVerifyPathSanity call. + */ +typedef struct SUPR3HARDENEDPATHINFO +{ + /** The length of the path in szCopy. */ + uint16_t cch; + /** The number of path components. */ + uint16_t cComponents; + /** Set if the path ends with slash, indicating that it's a directory + * reference and not a file reference. The slash has been removed from + * the copy. */ + bool fDirSlash; + /** The offset where each path component starts, i.e. the char after the + * slash. The array has cComponents + 1 entries, where the final one is + * cch + 1 so that one can always terminate the current component by + * szPath[aoffComponent[i] - 1] = '\0'. */ + uint16_t aoffComponents[32+1]; + /** A normalized copy of the path. + * Reserve some extra space so we can be more relaxed about overflow + * checks and terminator paddings, especially when recursing. */ + char szPath[SUPR3HARDENED_MAX_PATH * 2]; +} SUPR3HARDENEDPATHINFO; +/** Pointer to a parsed path. */ +typedef SUPR3HARDENEDPATHINFO *PSUPR3HARDENEDPATHINFO; + + +/** + * Verifies that the path is absolutely sane, it also parses the path. + * + * A sane path starts at the root (w/ drive letter on DOS derived systems) and + * does not have any relative bits (/../) or unnecessary slashes (/bin//ls). + * Sane paths are less or equal to SUPR3HARDENED_MAX_PATH bytes in length. UNC + * paths are not supported. + * + * @returns VBox status code. + * @param pszPath The path to check. + * @param pErrInfo The error info structure. + * @param pInfo Where to return a copy of the path along with + * parsing information. + */ +static int supR3HardenedVerifyPathSanity(const char *pszPath, PRTERRINFO pErrInfo, PSUPR3HARDENEDPATHINFO pInfo) +{ + const char *pszSrc = pszPath; + char *pszDst = pInfo->szPath; + + /* + * 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]) + || pszSrc[1] != ':' + || !RTPATH_IS_SLASH(pszSrc[2])) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'"); + + *pszDst++ = RT_C_TO_UPPER(pszSrc[0]); + *pszDst++ = ':'; + *pszDst++ = RTPATH_SLASH; + pszSrc += 3; + +#else + if (!RTPATH_IS_SLASH(pszSrc[0])) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, "The path is not absolute: '", pszPath, "'"); + + *pszDst++ = RTPATH_SLASH; + pszSrc += 1; +#endif + + /* + * No path specifying the root or something very shortly thereafter will + * be approved of. + */ + if (pszSrc[0] == '\0') + return supR3HardenedSetError3(VERR_SUPLIB_PATH_IS_ROOT, pErrInfo, "The path is root: '", pszPath, "'"); + if ( pszSrc[1] == '\0' + || pszSrc[2] == '\0') + return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_SHORT, pErrInfo, "The path is too short: '", pszPath, "'"); + +#if RTPATH_STYLE == RTPATH_STR_F_STYLE_UNIX + /* + * Skip double slashes. + */ + while (RTPATH_IS_SLASH(*pszSrc)) + pszSrc++; +#else + /* + * The root slash should be alone to avoid UNC confusion. + */ + if (RTPATH_IS_SLASH(pszSrc[0])) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_CLEAN, pErrInfo, + "The path is not clean of leading double slashes: '", pszPath, "'"); +#endif + /* + * Check each component. No parent references. + */ + pInfo->cComponents = 0; + pInfo->fDirSlash = false; + while (pszSrc[0]) + { + /* Sanity checks. */ + if ( pszSrc[0] == '.' + && pszSrc[1] == '.' + && RTPATH_IS_SLASH(pszSrc[2])) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_NOT_ABSOLUTE, pErrInfo, + "The path is not absolute: '", pszPath, "'"); + + /* Record the start of the component. */ + if (pInfo->cComponents >= RT_ELEMENTS(pInfo->aoffComponents) - 1) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_MANY_COMPONENTS, pErrInfo, + "The path has too many components: '", pszPath, "'"); + pInfo->aoffComponents[pInfo->cComponents++] = pszDst - &pInfo->szPath[0]; + + /* Traverse to the end of the component, copying it as we go along. */ + while (pszSrc[0]) + { + if (RTPATH_IS_SLASH(pszSrc[0])) + { + pszSrc++; + if (*pszSrc) + *pszDst++ = RTPATH_SLASH; + else + pInfo->fDirSlash = true; + break; + } + *pszDst++ = *pszSrc++; + if ((uintptr_t)(pszDst - &pInfo->szPath[0]) >= SUPR3HARDENED_MAX_PATH) + return supR3HardenedSetError3(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, + "The path is too long: '", pszPath, "'"); + } + + /* Skip double slashes. */ + while (RTPATH_IS_SLASH(*pszSrc)) + pszSrc++; + } + + /* Terminate the string and enter its length. */ + pszDst[0] = '\0'; + pszDst[1] = '\0'; /* for aoffComponents */ + pInfo->cch = (uint16_t)(pszDst - &pInfo->szPath[0]); + pInfo->aoffComponents[pInfo->cComponents] = pInfo->cch + 1; + + return VINF_SUCCESS; +} + + +/** + * The state information collected by supR3HardenedVerifyFsObject. + * + * This can be used to verify that a directory we've opened for enumeration is + * the same as the one that supR3HardenedVerifyFsObject just verified. It can + * equally be used to verify a native specfied by the user. + */ +typedef struct SUPR3HARDENEDFSOBJSTATE +{ +#ifdef RT_OS_WINDOWS + /** Not implemented for windows yet. */ + char chTodo; +#else + /** The stat output. */ + struct stat Stat; +#endif +} SUPR3HARDENEDFSOBJSTATE; +/** Pointer to a file system object state. */ +typedef SUPR3HARDENEDFSOBJSTATE *PSUPR3HARDENEDFSOBJSTATE; +/** Pointer to a const file system object state. */ +typedef SUPR3HARDENEDFSOBJSTATE const *PCSUPR3HARDENEDFSOBJSTATE; + + +/** + * Query information about a file system object by path. + * + * @returns VBox status code, error buffer filled on failure. + * @param pszPath The path to the object. + * @param pFsObjState Where to return the state information. + * @param pErrInfo The error info structure. + */ +static int supR3HardenedQueryFsObjectByPath(char const *pszPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState, PRTERRINFO pErrInfo) +{ +#if defined(RT_OS_WINDOWS) + /** @todo Windows hardening. */ + pFsObjState->chTodo = 0; + RT_NOREF2(pszPath, pErrInfo); + return VINF_SUCCESS; + +#else + /* + * Stat the object, do not follow links. + */ + if (lstat(pszPath, &pFsObjState->Stat) != 0) + { + /* Ignore access errors */ + if (errno != EACCES) + return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo, + 5, "stat failed with ", strerror(errno), " on: '", pszPath, "'"); + } + + /* + * Read ACLs. + */ + /** @todo */ + + return VINF_SUCCESS; +#endif +} + + +/** + * Query information about a file system object by native handle. + * + * @returns VBox status code, error buffer filled on failure. + * @param hNative The native handle to the object @a pszPath + * specifies and this should be verified to be the + * same file system object. + * @param pFsObjState Where to return the state information. + * @param pszPath The path to the object. (For the error message + * only.) + * @param pErrInfo The error info structure. + */ +static int supR3HardenedQueryFsObjectByHandle(RTHCUINTPTR hNative, PSUPR3HARDENEDFSOBJSTATE pFsObjState, + char const *pszPath, PRTERRINFO pErrInfo) +{ +#if defined(RT_OS_WINDOWS) + /** @todo Windows hardening. */ + pFsObjState->chTodo = 0; + RT_NOREF3(hNative, pszPath, pErrInfo); + return VINF_SUCCESS; + +#else + /* + * Stat the object, do not follow links. + */ + if (fstat((int)hNative, &pFsObjState->Stat) != 0) + return supR3HardenedSetErrorN(VERR_SUPLIB_STAT_FAILED, pErrInfo, + 5, "fstat failed with ", strerror(errno), " on '", pszPath, "'"); + + /* + * Read ACLs. + */ + /** @todo */ + + return VINF_SUCCESS; +#endif +} + + +/** + * Verifies that the file system object indicated by the native handle is the + * same as the one @a pFsObjState indicates. + * + * @returns VBox status code, error buffer filled on failure. + * @param pFsObjState1 File system object information/state by path. + * @param pFsObjState2 File system object information/state by handle. + * @param pszPath The path to the object @a pFsObjState + * describes. (For the error message.) + * @param pErrInfo The error info structure. + */ +static int supR3HardenedIsSameFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState1, PCSUPR3HARDENEDFSOBJSTATE pFsObjState2, + const char *pszPath, PRTERRINFO pErrInfo) +{ +#if defined(RT_OS_WINDOWS) + /** @todo Windows hardening. */ + RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo); + return VINF_SUCCESS; + +#elif defined(RT_OS_OS2) + RT_NOREF4(pFsObjState1, pFsObjState2, pszPath, pErrInfo); + return VINF_SUCCESS; + +#else + /* + * Compare the ino+dev, then the uid+gid and finally the important mode + * bits. Technically the first one should be enough, but we're paranoid. + */ + if ( pFsObjState1->Stat.st_ino != pFsObjState2->Stat.st_ino + || pFsObjState1->Stat.st_dev != pFsObjState2->Stat.st_dev) + return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo, + "The native handle is not the same as '", pszPath, "' (ino/dev)"); + if ( pFsObjState1->Stat.st_uid != pFsObjState2->Stat.st_uid + || pFsObjState1->Stat.st_gid != pFsObjState2->Stat.st_gid) + return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo, + "The native handle is not the same as '", pszPath, "' (uid/gid)"); + if ( (pFsObjState1->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH)) + != (pFsObjState2->Stat.st_mode & (S_IFMT | S_IWUSR | S_IWGRP | S_IWOTH))) + return supR3HardenedSetError3(VERR_SUPLIB_NOT_SAME_OBJECT, pErrInfo, + "The native handle is not the same as '", pszPath, "' (mode)"); + return VINF_SUCCESS; +#endif +} + + +/** + * Verifies a file system object (file or directory). + * + * @returns VBox status code, error buffer filled on failure. + * @param pFsObjState The file system object information/state to be + * verified. + * @param fDir Whether this is a directory or a file. + * @param fRelaxed Whether we can be more relaxed about this + * directory (only used for grand parent + * directories). + * @param fSymlinksAllowed Flag whether symlinks are allowed or not. + * If allowed the symlink object is verified not the target. + * @param pszPath The path to the object. For error messages and + * securing a couple of hacks. + * @param pErrInfo The error info structure. + */ +static int supR3HardenedVerifyFsObject(PCSUPR3HARDENEDFSOBJSTATE pFsObjState, bool fDir, bool fRelaxed, + bool fSymlinksAllowed, const char *pszPath, PRTERRINFO pErrInfo) +{ +#if defined(RT_OS_WINDOWS) + /** @todo Windows hardening. */ + RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo); + return VINF_SUCCESS; + +#elif defined(RT_OS_OS2) + /* No hardening here - it's a single user system. */ + RT_NOREF(pFsObjState, fDir, fRelaxed, fSymlinksAllowed, pszPath, pErrInfo); + return VINF_SUCCESS; + +#else + /* + * The owner must be root. + * + * This can be extended to include predefined system users if necessary. + */ + if (pFsObjState->Stat.st_uid != 0) + return supR3HardenedSetError3(VERR_SUPLIB_OWNER_NOT_ROOT, pErrInfo, "The owner is not root: '", pszPath, "'"); + + /* + * The object type must be directory or file. It can be a symbolic link + * if explicitely allowed. Otherwise this and other risky stuff is not allowed + * (sorry dude, but we're paranoid on purpose here). + */ + if ( !S_ISLNK(pFsObjState->Stat.st_mode) + || !fSymlinksAllowed) + { + + if ( !S_ISDIR(pFsObjState->Stat.st_mode) + && !S_ISREG(pFsObjState->Stat.st_mode)) + { + if (S_ISLNK(pFsObjState->Stat.st_mode)) + return supR3HardenedSetError3(VERR_SUPLIB_SYMLINKS_ARE_NOT_PERMITTED, pErrInfo, + "Symlinks are not permitted: '", pszPath, "'"); + return supR3HardenedSetError3(VERR_SUPLIB_NOT_DIR_NOT_FILE, pErrInfo, + "Not regular file or directory: '", pszPath, "'"); + } + if (fDir != !!S_ISDIR(pFsObjState->Stat.st_mode)) + { + if (S_ISDIR(pFsObjState->Stat.st_mode)) + return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo, + "Expected file but found directory: '", pszPath, "'"); + return supR3HardenedSetError3(VERR_SUPLIB_IS_FILE, pErrInfo, + "Expected directory but found file: '", pszPath, "'"); + } + } + + /* + * The group does not matter if it does not have write access, if it has + * write access it must be group 0 (root/wheel/whatever). + * + * This can be extended to include predefined system groups or groups that + * only root is member of. + */ + if ( (pFsObjState->Stat.st_mode & S_IWGRP) + && pFsObjState->Stat.st_gid != 0) + { +# ifdef RT_OS_DARWIN + /* HACK ALERT: On Darwin /Applications is root:admin with admin having + full access. So, to work around we relax the hardening a bit and + permit grand parents and beyond to be group writable by admin. */ + /** @todo dynamically resolve the admin group? */ + bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 80 /*admin*/ || suplibHardenedStrCmp(pszPath, "/Applications"); + +# elif defined(RT_OS_FREEBSD) + /* HACK ALERT: PC-BSD 9 has group-writable /usr/pib directory which is + similar to /Applications on OS X (see above). + On FreeBSD root is normally the only member of this group, on + PC-BSD the default user is a member. */ + /** @todo dynamically resolve the operator group? */ + bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 5 /*operator*/ || suplibHardenedStrCmp(pszPath, "/usr/pbi"); + NOREF(fRelaxed); +# elif defined(RT_OS_SOLARIS) + /* HACK ALERT: Solaris has group-writable /usr/lib/iconv directory from + which the appropriate module is loaded. + By default only root and daemon are part of that group. + . */ + /** @todo dynamically resolve the bin group? */ + bool fBad = !fRelaxed || pFsObjState->Stat.st_gid != 2 /*bin*/ || suplibHardenedStrCmp(pszPath, "/usr/lib/iconv"); +# else + NOREF(fRelaxed); + bool fBad = true; +# endif + if (fBad) + return supR3HardenedSetError3(VERR_SUPLIB_WRITE_NON_SYS_GROUP, pErrInfo, + "An unknown (and thus untrusted) group has write access to '", pszPath, + "' and we therefore cannot trust the directory content or that of any subdirectory"); + } + + /* + * World must not have write access. There is no relaxing this rule. + * Linux exception: Symbolic links are always give permission 0777, there + * is no lchmod or lchown APIs. The permissions on parent + * directory that contains the symbolic link is what is + * decising wrt to modifying it. (Caller is expected not + * to allow symbolic links in the first path component.) + */ + if ( (pFsObjState->Stat.st_mode & S_IWOTH) +# ifdef RT_OS_LINUX + && ( !S_ISLNK(pFsObjState->Stat.st_mode) + || !fSymlinksAllowed /* paranoia */) +# endif + ) + return supR3HardenedSetError3(VERR_SUPLIB_WORLD_WRITABLE, pErrInfo, + "World writable: '", pszPath, "'"); + + /* + * Check the ACLs. + */ + /** @todo */ + + return VINF_SUCCESS; +#endif +} + + +/** + * Verifies that the file system object indicated by the native handle is the + * same as the one @a pFsObjState indicates. + * + * @returns VBox status code, error buffer filled on failure. + * @param hNative The native handle to the object @a pszPath + * specifies and this should be verified to be the + * same file system object. + * @param pFsObjState The information/state returned by a previous + * query call. + * @param pszPath The path to the object @a pFsObjState + * describes. (For the error message.) + * @param pErrInfo The error info structure. + */ +static int supR3HardenedVerifySameFsObject(RTHCUINTPTR hNative, PCSUPR3HARDENEDFSOBJSTATE pFsObjState, + const char *pszPath, PRTERRINFO pErrInfo) +{ + SUPR3HARDENEDFSOBJSTATE FsObjState2; + int rc = supR3HardenedQueryFsObjectByHandle(hNative, &FsObjState2, pszPath, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supR3HardenedIsSameFsObject(pFsObjState, &FsObjState2, pszPath, pErrInfo); + return rc; +} + + +/** + * Does the recursive directory enumeration. + * + * @returns VBox status code, error buffer filled on failure. + * @param pszDirPath The path buffer containing the subdirectory to + * enumerate followed by a slash (this is never + * the root slash). The buffer is RTPATH_MAX in + * size and anything starting at @a cchDirPath + * - 1 and beyond is scratch space. + * @param cchDirPath The length of the directory path + slash. + * @param pFsObjState Pointer to the file system object state buffer. + * On input this will hold the stats for + * the directory @a pszDirPath indicates and will + * be used to verified that we're opening the same + * thing. + * @param fRecursive Whether to recurse into subdirectories. + * @param pErrInfo The error info structure. + */ +static int supR3HardenedVerifyDirRecursive(char *pszDirPath, size_t cchDirPath, PSUPR3HARDENEDFSOBJSTATE pFsObjState, + bool fRecursive, PRTERRINFO pErrInfo) +{ +#if defined(RT_OS_WINDOWS) + /** @todo Windows hardening. */ + RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo); + return VINF_SUCCESS; + +#elif defined(RT_OS_OS2) + /* No hardening here - it's a single user system. */ + RT_NOREF5(pszDirPath, cchDirPath, pFsObjState, fRecursive, pErrInfo); + return VINF_SUCCESS; + +#else + /* + * Open the directory. Now, we could probably eliminate opendir here + * and go down on kernel API level (open + getdents for instance), however + * that's not very portable and hopefully not necessary. + */ + DIR *pDir = opendir(pszDirPath); + if (!pDir) + { + /* Ignore access errors. */ + if (errno == EACCES) + return VINF_SUCCESS; + return supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo, + 5, "opendir failed with ", strerror(errno), " on '", pszDirPath, "'"); + } + if (dirfd(pDir) != -1) + { + int rc = supR3HardenedVerifySameFsObject(dirfd(pDir), pFsObjState, pszDirPath, pErrInfo); + if (RT_FAILURE(rc)) + { + closedir(pDir); + return rc; + } + } + + /* + * Enumerate the directory, check all the requested bits. + */ + int rc = VINF_SUCCESS; + for (;;) + { + pszDirPath[cchDirPath] = '\0'; /* for error messages. */ + + struct dirent Entry; + struct dirent *pEntry; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + int iErr = readdir_r(pDir, &Entry, &pEntry); +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + if (iErr) + { + rc = supR3HardenedSetErrorN(VERR_SUPLIB_DIR_ENUM_FAILED, pErrInfo, + 5, "readdir_r failed with ", strerror(iErr), " in '", pszDirPath, "'"); + break; + } + if (!pEntry) + break; + + /* + * Check the length and copy it into the path buffer so it can be + * stat()'ed. + */ + size_t cchName = suplibHardenedStrLen(pEntry->d_name); + if (cchName + cchDirPath > SUPR3HARDENED_MAX_PATH) + { + rc = supR3HardenedSetErrorN(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, + 4, "Path grew too long during recursion: '", pszDirPath, pEntry->d_name, "'"); + break; + } + suplibHardenedMemCopy(&pszDirPath[cchDirPath], pEntry->d_name, cchName + 1); + + /* + * Query the information about the entry and verify it. + * (We don't bother skipping '.' and '..' at this point, a little bit + * of extra checks doesn't hurt and neither requires relaxed handling.) + */ + rc = supR3HardenedQueryFsObjectByPath(pszDirPath, pFsObjState, pErrInfo); + if (RT_SUCCESS(rc)) + break; + rc = supR3HardenedVerifyFsObject(pFsObjState, S_ISDIR(pFsObjState->Stat.st_mode), false /*fRelaxed*/, + false /*fSymlinksAllowed*/, pszDirPath, pErrInfo); + if (RT_FAILURE(rc)) + break; + + /* + * Recurse into subdirectories if requested. + */ + if ( fRecursive + && S_ISDIR(pFsObjState->Stat.st_mode) + && suplibHardenedStrCmp(pEntry->d_name, ".") + && suplibHardenedStrCmp(pEntry->d_name, "..")) + { + pszDirPath[cchDirPath + cchName] = RTPATH_SLASH; + pszDirPath[cchDirPath + cchName + 1] = '\0'; + + rc = supR3HardenedVerifyDirRecursive(pszDirPath, cchDirPath + cchName + 1, pFsObjState, + fRecursive, pErrInfo); + if (RT_FAILURE(rc)) + break; + } + } + + closedir(pDir); + return rc; +#endif +} + + +/** + * Worker for SUPR3HardenedVerifyDir. + * + * @returns See SUPR3HardenedVerifyDir. + * @param pszDirPath See SUPR3HardenedVerifyDir. + * @param fRecursive See SUPR3HardenedVerifyDir. + * @param fCheckFiles See SUPR3HardenedVerifyDir. + * @param pErrInfo See SUPR3HardenedVerifyDir. + */ +DECLHIDDEN(int) supR3HardenedVerifyDir(const char *pszDirPath, bool fRecursive, bool fCheckFiles, PRTERRINFO pErrInfo) +{ + /* + * Validate the input path and parse it. + */ + SUPR3HARDENEDPATHINFO Info; + int rc = supR3HardenedVerifyPathSanity(pszDirPath, pErrInfo, &Info); + if (RT_FAILURE(rc)) + return rc; + + /* + * Verify each component from the root up. + */ + SUPR3HARDENEDFSOBJSTATE FsObjState; + uint32_t const cComponents = Info.cComponents; + for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++) + { + bool fRelaxed = iComponent + 2 < cComponents; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0'; + rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supR3HardenedVerifyFsObject(&FsObjState, true /*fDir*/, fRelaxed, + false /*fSymlinksAllowed*/, Info.szPath, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = iComponent + 1 != cComponents ? RTPATH_SLASH : '\0'; + } + + /* + * Check files and subdirectories if requested. + */ + if (fCheckFiles || fRecursive) + { + Info.szPath[Info.cch] = RTPATH_SLASH; + Info.szPath[Info.cch + 1] = '\0'; + return supR3HardenedVerifyDirRecursive(Info.szPath, Info.cch + 1, &FsObjState, + fRecursive, pErrInfo); + } + + return VINF_SUCCESS; +} + + +/** + * Verfies a file. + * + * @returns VBox status code, error buffer filled on failure. + * @param pszFilename The file to verify. + * @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, + bool fMaybe3rdParty, PRTERRINFO pErrInfo) +{ + /* + * Validate the input path and parse it. + */ + SUPR3HARDENEDPATHINFO Info; + int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info); + if (RT_FAILURE(rc)) + return rc; + if (Info.fDirSlash) + return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo, + "The file path specifies a directory: '", pszFilename, "'"); + + /* + * Verify each component from the root up. + */ + SUPR3HARDENEDFSOBJSTATE FsObjState; + uint32_t const cComponents = Info.cComponents; + for (uint32_t iComponent = 0; iComponent < cComponents; iComponent++) + { + bool fFinal = iComponent + 1 == cComponents; + bool fRelaxed = iComponent + 2 < cComponents; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0'; + rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed, + false /*fSymlinksAllowed*/, Info.szPath, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0'; + } + + /* + * Verify the file handle against the last component, if specified. + */ + if (hNativeFile != RTHCUINTPTR_MAX) + { + 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. + */ + rc = VINF_SUCCESS; + HANDLE hVerify; + if (hNativeFile == RTHCUINTPTR_MAX) + { + PRTUTF16 pwszPath; + rc = RTStrToUtf16(pszFilename, &pwszPath); + if (RT_SUCCESS(rc)) + { + hVerify = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + RTUtf16Free(pwszPath); + } + else + { + rc = RTErrInfoSetF(pErrInfo, rc, "Error converting '%s' to UTF-16: %Rrc", pszFilename, rc); + hVerify = INVALID_HANDLE_VALUE; + } + } + else + { + NTSTATUS rcNt = NtDuplicateObject(NtCurrentProcess(), (HANDLE)hNativeFile, NtCurrentProcess(), &hVerify, + GENERIC_READ, 0 /*HandleAttributes*/, 0 /*Options*/); + if (!NT_SUCCESS(rcNt)) + hVerify = INVALID_HANDLE_VALUE; + } + if (hVerify != INVALID_HANDLE_VALUE) + { +# ifdef VBOX_WITH_HARDENING + 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 +# else + RT_NOREF1(fMaybe3rdParty); +# endif + NtClose(hVerify); + } + else if (RT_SUCCESS(rc)) + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "Error %u trying to open (or duplicate handle for) '%s'", RtlGetLastWin32Error(), pszFilename); + if (RT_FAILURE(rc)) + return rc; +#else + RT_NOREF1(fMaybe3rdParty); +#endif + + return VINF_SUCCESS; +} + + +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/** + * Verfies a file following symlinks. + * + * @returns VBox status code, error buffer filled on failure. + * @param pszFilename The file to verify. + * @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. + * + * @note This is only used on OS X for libraries loaded with dlopen() because + * the frameworks use symbolic links to point to the relevant library. + * + * @sa supR3HardenedVerifyFile + */ +DECLHIDDEN(int) supR3HardenedVerifyFileFollowSymlinks(const char *pszFilename, RTHCUINTPTR hNativeFile, bool fMaybe3rdParty, + PRTERRINFO pErrInfo) +{ + RT_NOREF1(fMaybe3rdParty); + + /* + * Validate the input path and parse it. + */ + SUPR3HARDENEDPATHINFO Info; + int rc = supR3HardenedVerifyPathSanity(pszFilename, pErrInfo, &Info); + if (RT_FAILURE(rc)) + return rc; + if (Info.fDirSlash) + return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo, + "The file path specifies a directory: '", pszFilename, "'"); + + /* + * Verify each component from the root up. + */ +#ifndef SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH + uint32_t iLoops = 0; +#endif + SUPR3HARDENEDFSOBJSTATE FsObjState; + uint32_t iComponent = 0; + while (iComponent < Info.cComponents) + { + bool fFinal = iComponent + 1 == Info.cComponents; + bool fRelaxed = iComponent + 2 < Info.cComponents; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = '\0'; + rc = supR3HardenedQueryFsObjectByPath(Info.szPath, &FsObjState, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * In case the component is a symlink expand it and start from the beginning after + * verifying it has the proper access rights. + * Furthermore only allow symlinks which don't contain any .. or . in the target + * (enforced by supR3HardenedVerifyPathSanity). + */ + rc = supR3HardenedVerifyFsObject(&FsObjState, !fFinal /*fDir*/, fRelaxed, + true /*fSymlinksAllowed*/, Info.szPath, pErrInfo); + if ( RT_SUCCESS(rc) + && S_ISLNK(FsObjState.Stat.st_mode)) + { +#if SUP_HARDENED_VERIFY_FOLLOW_SYMLINKS_USE_REALPATH /* Another approach using realpath() and verifying the result when encountering a symlink. */ + char *pszFilenameResolved = realpath(pszFilename, NULL); + if (pszFilenameResolved) + { + rc = supR3HardenedVerifyFile(pszFilenameResolved, hNativeFile, fMaybe3rdParty, pErrInfo); + free(pszFilenameResolved); + return rc; + } + else + { + int iErr = errno; + supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/, + "supR3HardenedVerifyFileFollowSymlinks: Failed to resolve the real path '%s': %s (%d)\n", + pszFilename, strerror(iErr), iErr); + return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo, + "realpath failed for '", pszFilename, "': ", strerror(iErr)); + } +#else + /* Don't loop forever. */ + iLoops++; + if (iLoops < 8) + { + /* + * Construct new path by replacing the current component by the symlink value. + * Note! readlink() is a weird API that doesn't necessarily indicates if the + * buffer is too small. + */ + char szPath[RTPATH_MAX]; + size_t const cchBefore = Info.aoffComponents[iComponent]; /* includes slash */ + size_t const cchAfter = fFinal ? 0 : 1 /*slash*/ + Info.cch - Info.aoffComponents[iComponent + 1]; + if (sizeof(szPath) > cchBefore + cchAfter + 2) + { + ssize_t cchTarget = readlink(Info.szPath, szPath, sizeof(szPath) - 1); + if (cchTarget > 0) + { + /* Some serious paranoia against embedded zero terminator and weird return values. */ + szPath[cchTarget] = '\0'; + size_t cchLink = strlen(szPath); + + /* Strip trailing dirslashes of non-final link. */ + if (!fFinal) + while (cchLink > 1 and szPath[cchLink - 1] == '/') + cchLink--; + + /* Check link value sanity and buffer size. */ + if (cchLink == 0) + return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo, + "Bad readlink return for '", Info.szPath, "'"); + if (szPath[0] == '/') + return supR3HardenedSetError3(VERR_ACCESS_DENIED, pErrInfo, + "Absolute symbolic link not allowed: '", szPath, "'"); + if (cchBefore + cchLink + cchAfter + 1 /*terminator*/ > sizeof(szPath)) + return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, + "Symlinks causing too long path!"); + + /* Construct the new path. */ + if (cchBefore) + memmove(&szPath[cchBefore], &szPath[0], cchLink); + memcpy(&szPath[0], Info.szPath, cchBefore); + if (!cchAfter) + szPath[cchBefore + cchLink] = '\0'; + else + { + szPath[cchBefore + cchLink] = RTPATH_SLASH; + memcpy(&szPath[cchBefore + cchLink + 1], + &Info.szPath[Info.aoffComponents[iComponent + 1]], + cchAfter); /* cchAfter includes a zero terminator */ + } + + /* Parse, copy and check the sanity (no '..' or '.') of the altered path. */ + rc = supR3HardenedVerifyPathSanity(szPath, pErrInfo, &Info); + if (RT_FAILURE(rc)) + return rc; + if (Info.fDirSlash) + return supR3HardenedSetError3(VERR_SUPLIB_IS_DIRECTORY, pErrInfo, + "The file path specifies a directory: '", szPath, "'"); + + /* Restart from the current component. */ + continue; + } + int iErr = errno; + supR3HardenedError(VERR_ACCESS_DENIED, false /*fFatal*/, + "supR3HardenedVerifyFileFollowSymlinks: Failed to readlink '%s': %s (%d)\n", + Info.szPath, strerror(iErr), iErr); + return supR3HardenedSetError4(VERR_ACCESS_DENIED, pErrInfo, + "readlink failed for '", Info.szPath, "': ", strerror(iErr)); + } + return supR3HardenedSetError(VERR_SUPLIB_PATH_TOO_LONG, pErrInfo, "Path too long for symlink replacing!"); + } + else + return supR3HardenedSetError3(VERR_TOO_MANY_SYMLINKS, pErrInfo, + "Too many symbolic links: '", pszFilename, "'"); +#endif + } + } + if (RT_FAILURE(rc)) + return rc; + Info.szPath[Info.aoffComponents[iComponent + 1] - 1] = !fFinal ? RTPATH_SLASH : '\0'; + iComponent++; + } + + /* + * Verify the file handle against the last component, if specified. + */ + if (hNativeFile != RTHCUINTPTR_MAX) + { + rc = supR3HardenedVerifySameFsObject(hNativeFile, &FsObjState, Info.szPath, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} +#endif /* RT_OS_DARWIN || RT_OS_LINUX */ + + +/** + * Gets the pre-init data for the hand-over to the other version + * of this code. + * + * The reason why we pass this information on is that it contains + * open directories and files. Later it may include even more info + * (int the verified arrays mostly). + * + * The receiver is supR3HardenedRecvPreInitData. + * + * @param pPreInitData Where to store it. + */ +DECLHIDDEN(void) supR3HardenedGetPreInitData(PSUPPREINITDATA pPreInitData) +{ + pPreInitData->cInstallFiles = RT_ELEMENTS(g_aSupInstallFiles); + pPreInitData->paInstallFiles = &g_aSupInstallFiles[0]; + pPreInitData->paVerifiedFiles = &g_aSupVerifiedFiles[0]; + + pPreInitData->cVerifiedDirs = RT_ELEMENTS(g_aSupVerifiedDirs); + pPreInitData->paVerifiedDirs = &g_aSupVerifiedDirs[0]; +} + + +/** + * Receives the pre-init data from the static executable stub. + * + * @returns VBox status code. Will not bitch on failure since the + * runtime isn't ready for it, so that is left to the exe stub. + * + * @param pPreInitData The hand-over data. + */ +DECLHIDDEN(int) supR3HardenedRecvPreInitData(PCSUPPREINITDATA pPreInitData) +{ + /* + * Compare the array lengths and the contents of g_aSupInstallFiles. + */ + if ( pPreInitData->cInstallFiles != RT_ELEMENTS(g_aSupInstallFiles) + || pPreInitData->cVerifiedDirs != RT_ELEMENTS(g_aSupVerifiedDirs)) + return VERR_VERSION_MISMATCH; + SUPINSTFILE const *paInstallFiles = pPreInitData->paInstallFiles; + for (unsigned iFile = 0; iFile < RT_ELEMENTS(g_aSupInstallFiles); iFile++) + if ( g_aSupInstallFiles[iFile].enmDir != paInstallFiles[iFile].enmDir + || g_aSupInstallFiles[iFile].enmType != paInstallFiles[iFile].enmType + || g_aSupInstallFiles[iFile].fOptional != paInstallFiles[iFile].fOptional + || suplibHardenedStrCmp(g_aSupInstallFiles[iFile].pszFile, paInstallFiles[iFile].pszFile)) + return VERR_VERSION_MISMATCH; + + /* + * Check that we're not called out of order. + * If dynamic linking it screwed up, we may end up here. + */ + if ( !ASMMemIsZero(&g_aSupVerifiedFiles[0], sizeof(g_aSupVerifiedFiles)) + || !ASMMemIsZero(&g_aSupVerifiedDirs[0], sizeof(g_aSupVerifiedDirs))) + return VERR_WRONG_ORDER; + + /* + * Copy the verification data over. + */ + suplibHardenedMemCopy(&g_aSupVerifiedFiles[0], pPreInitData->paVerifiedFiles, sizeof(g_aSupVerifiedFiles)); + suplibHardenedMemCopy(&g_aSupVerifiedDirs[0], pPreInitData->paVerifiedDirs, sizeof(g_aSupVerifiedDirs)); + return VINF_SUCCESS; +} diff --git a/src/VBox/HostDrivers/Support/SUPSvc.cpp b/src/VBox/HostDrivers/Support/SUPSvc.cpp new file mode 100644 index 00000000..778d8863 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPSvc.cpp @@ -0,0 +1,456 @@ +/* $Id: SUPSvc.cpp $ */ +/** @file + * VirtualBox Support Service - Common Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <VBox/log.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> + +#include "SUPSvcInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Service state. + */ +typedef enum SUPSVCSERVICESTATE +{ + kSupSvcServiceState_Invalid = 0, + kSupSvcServiceState_NotCreated, + kSupSvcServiceState_Paused, + kSupSvcServiceState_Running, + kSupSvcServiceState_End +} SUPSVCSERVICESTATE; + + +/** + * Service descriptor. + */ +typedef struct SUPSVCSERVICE +{ + /** The service name. */ + const char *pszName; + /** The service state. */ + SUPSVCSERVICESTATE enmState; + /** The instance handle returned by pfnCreate. */ + void *pvInstance; + + /** + * Create the service (don't start it). + * + * @returns VBox status code, log entry is written on failure. + * @param ppvInstance Where to store the instance handle. + */ + DECLCALLBACKMEMBER(int, pfnCreate,(void **ppvInstance)); + + /** + * Start the service. + * + * @param pvInstance The instance handle. + */ + DECLCALLBACKMEMBER(void, pfnStart,(void *pvInstance)); + + /** + * Attempt to stop a running service. + * + * This should fail if there are active clients. A stopped service + * can be restarted by calling pfnStart. + * + * @returns VBox status code, log entry is written on failure. + * @param pvInstance The instance handle. + */ + DECLCALLBACKMEMBER(int, pfnTryStop,(void *pvInstance)); + + /** + * Destroy the service, stopping first it if necessary. + * + * @param pvInstance The instance handle. + * @param fRunning Whether the service is running or not. + */ + DECLCALLBACKMEMBER(void, pfnStopAndDestroy,(void *pvInstance, bool fRunning)); +} SUPSVCSERVICE; +/** Pointer to a service descriptor. */ +typedef SUPSVCSERVICE *PSUPSVCSERVICE; +/** Pointer to a const service descriptor. */ +typedef SUPSVCSERVICE const *PCSUPSVCSERVICE; + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static SUPSVCSERVICE g_aServices[] = +{ + { + "Global", + kSupSvcServiceState_NotCreated, + NULL, + supSvcGlobalCreate, + supSvcGlobalStart, + supSvcGlobalTryStop, + supSvcGlobalStopAndDestroy, + } +#ifdef RT_OS_WINDOWS + , + { + "Grant", + kSupSvcServiceState_NotCreated, + NULL, + supSvcGrantCreate, + supSvcGrantStart, + supSvcGrantTryStop, + supSvcGrantStopAndDestroy, + } +#endif +}; + + + +/** + * Instantiates and starts the services. + * + * @returns VBox status code. Done bitching on failure. + */ +int supSvcCreateAndStartServices(void) +{ + LogFlowFuncEnter(); + + /* + * Validate that all services are in the NotCreated state. + */ + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aServices); i++) + if (g_aServices[i].enmState != kSupSvcServiceState_NotCreated) + { + supSvcLogError("service %s in state %d, expected state %d (NotCreated)", + g_aServices[i].pszName, g_aServices[i].enmState, kSupSvcServiceState_NotCreated); + return VERR_WRONG_ORDER; + } + + /* + * Create all the services, then start them. + */ + int rc = VINF_SUCCESS; + for (i = 0; i < RT_ELEMENTS(g_aServices); i++) + { + void *pvInstance = NULL; + int rc = g_aServices[i].pfnCreate(&pvInstance); + if (RT_FAILURE(rc)) + { + Log(("supSvcCreateAndStartServices: %s -> %Rrc\n", g_aServices[i].pszName, rc)); + break; + } + g_aServices[i].pvInstance = pvInstance; + g_aServices[i].enmState = kSupSvcServiceState_Paused; + } + if (RT_SUCCESS(rc)) + { + for (i = 0; i < RT_ELEMENTS(g_aServices); i++) + { + g_aServices[i].pfnStart(g_aServices[i].pvInstance); + g_aServices[i].enmState = kSupSvcServiceState_Running; + } + } + else + { + /* + * Destroy any services we managed to instantiate. + */ + while (i-- > 0) + { + g_aServices[i].pfnStopAndDestroy(g_aServices[i].pvInstance, false /* fRunning */); + g_aServices[i].pvInstance = NULL; + g_aServices[i].enmState = kSupSvcServiceState_NotCreated; + } + } + + LogFlow(("supSvcCreateAndStartServices: returns %Rrc\n", rc)); + return rc; +} + + +/** + * Checks if it's possible to stop the services. + * + * @returns VBox status code, done bitching on failure. + */ +int supSvcTryStopServices(void) +{ + LogFlowFuncEnter(); + + /* + * Check that the services are all created and count the running ones. + */ + unsigned i; + unsigned cRunning = 0; + for (i = 0; i < RT_ELEMENTS(g_aServices); i++) + if (g_aServices[i].enmState == kSupSvcServiceState_Running) + cRunning++; + else if (g_aServices[i].enmState == kSupSvcServiceState_NotCreated) + { + supSvcLogError("service %s in state %d (NotCreated), expected pause or running", + g_aServices[i].pszName, g_aServices[i].enmState, kSupSvcServiceState_NotCreated); + return VERR_WRONG_ORDER; + } + if (!cRunning) + return VINF_SUCCESS; /* all stopped, nothing to do. */ + Assert(cRunning == RT_ELEMENTS(g_aServices)); /* all or nothing */ + + /* + * Try stop them in reverse of start order. + */ + int rc = VINF_SUCCESS; + i = RT_ELEMENTS(g_aServices); + while (i-- > 0) + { + rc = g_aServices[i].pfnTryStop(g_aServices[i].pvInstance); + if (RT_FAILURE(rc)) + { + Log(("supSvcTryStopServices: %s -> %Rrc\n", g_aServices[i].pszName, rc)); + break; + } + g_aServices[i].enmState = kSupSvcServiceState_Paused; + } + if (RT_FAILURE(rc)) + { + /* Failed, restart the ones we succeeded in stopping. */ + while (++i < RT_ELEMENTS(g_aServices)) + { + g_aServices[i].pfnStart(g_aServices[i].pvInstance); + g_aServices[i].enmState = kSupSvcServiceState_Running; + } + } + LogFlow(("supSvcTryStopServices: returns %Rrc\n", rc)); + return rc; +} + + +/** + * Stops and destroys the services. + */ +void supSvcStopAndDestroyServices(void) +{ + LogFlowFuncEnter(); + + /* + * Stop and destroy the service in reverse of start order. + */ + unsigned i = RT_ELEMENTS(g_aServices); + while (i-- > 0) + if (g_aServices[i].enmState != kSupSvcServiceState_NotCreated) + { + g_aServices[i].pfnStopAndDestroy(g_aServices[i].pvInstance, + g_aServices[i].enmState == kSupSvcServiceState_Running); + g_aServices[i].pvInstance = NULL; + g_aServices[i].enmState = kSupSvcServiceState_NotCreated; + } + + LogFlowFuncLeave(); +} + + + +/** + * Logs the message to the appropriate system log. + * + * In debug builds this will also put it in the debug log. + * + * @param pszMsg The log string. + * + * @remarks This may later be replaced by the release logger and callback destination(s). + */ +void supSvcLogErrorStr(const char *pszMsg) +{ + supSvcOsLogErrorStr(pszMsg); + LogRel(("%s\n", pszMsg)); +} + + +/** + * Logs the message to the appropriate system log. + * + * In debug builds this will also put it in the debug log. + * + * @param pszFormat The log string. No trailing newline. + * @param va Format arguments. + * + * @todo This should later be replaced by the release logger and callback destination(s). + */ +void supSvcLogErrorV(const char *pszFormat, va_list va) +{ + if (*pszFormat) + { + char *pszMsg = NULL; + if (RTStrAPrintfV(&pszMsg, pszFormat, va) != -1) + { + supSvcLogErrorStr(pszMsg); + RTStrFree(pszMsg); + } + else + supSvcLogErrorStr(pszFormat); + } +} + + +/** + * Logs the error message to the appropriate system log. + * + * In debug builds this will also put it in the debug log. + * + * @param pszFormat The log string. No trailing newline. + * @param ... Format arguments. + * + * @todo This should later be replaced by the release logger and callback destination(s). + */ +void supSvcLogError(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supSvcLogErrorV(pszFormat, va); + va_end(va); +} + + +/** + * Deals with RTGetOpt failure, bitching in the system log. + * + * @returns 1 + * @param pszAction The action name. + * @param rc The RTGetOpt return value. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + * @param pValue The value returned by RTGetOpt. + */ +int supSvcLogGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTOPTIONUNION pValue) +{ + supSvcLogError("%s - RTGetOpt failure, %Rrc (%d): %s", + pszAction, rc, rc, iArg < argc ? argv[iArg] : "<null>"); + return 1; +} + + +/** + * Bitch about too many arguments (after RTGetOpt stops) in the system log. + * + * @returns 1 + * @param pszAction The action name. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + */ +int supSvcLogTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg) +{ + Assert(iArg < argc); + supSvcLogError("%s - Too many arguments: %s", pszAction, argv[iArg]); + for ( ; iArg < argc; iArg++) + LogRel(("arg#%i: %s\n", iArg, argv[iArg])); + return 1; +} + + +/** + * Prints an error message to the screen. + * + * @param pszFormat The message format string. + * @param va Format arguments. + */ +void supSvcDisplayErrorV(const char *pszFormat, va_list va) +{ + RTStrmPrintf(g_pStdErr, "VBoxSupSvc error: "); + RTStrmPrintfV(g_pStdErr, pszFormat, va); + Log(("supSvcDisplayErrorV: %s", pszFormat)); /** @todo format it! */ +} + + +/** + * Prints an error message to the screen. + * + * @param pszFormat The message format string. + * @param ... Format arguments. + */ +void supSvcDisplayError(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + supSvcDisplayErrorV(pszFormat, va); + va_end(va); +} + + +/** + * Deals with RTGetOpt failure. + * + * @returns 1 + * @param pszAction The action name. + * @param rc The RTGetOpt return value. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + * @param pValue The value returned by RTGetOpt. + */ +int supSvcDisplayGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTOPTIONUNION pValue) +{ + supSvcDisplayError("%s - RTGetOpt failure, %Rrc (%d): %s\n", + pszAction, rc, rc, iArg < argc ? argv[iArg] : "<null>"); + return 1; +} + + +/** + * Bitch about too many arguments (after RTGetOpt stops). + * + * @returns 1 + * @param pszAction The action name. + * @param argc The argument count. + * @param argv The argument vector. + * @param iArg The argument index. + */ +int supSvcDisplayTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg) +{ + Assert(iArg < argc); + supSvcDisplayError("%s - Too many arguments: %s\n", pszAction, argv[iArg]); + return 1; +} + diff --git a/src/VBox/HostDrivers/Support/SUPSvcGlobal.cpp b/src/VBox/HostDrivers/Support/SUPSvcGlobal.cpp new file mode 100644 index 00000000..62b526d4 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPSvcGlobal.cpp @@ -0,0 +1,79 @@ +/* $Id: SUPSvcGlobal.cpp $ */ +/** @file + * VirtualBox Support Service - The Global Service. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "SUPSvcInternal.h" + +#include <iprt/errcore.h> +#include <iprt/assert.h> + + +/** @copydoc SUPSVCSERVICE::pfnCreate */ +DECLCALLBACK(int) supSvcGlobalCreate(void **ppvInstance) +{ + *ppvInstance = (void *)1; + return VINF_SUCCESS; +} + + +/** @copydoc SUPSVCSERVICE::pfnStart */ +DECLCALLBACK(void) supSvcGlobalStart(void *pvInstance) +{ + Assert(pvInstance == (void *)1); + NOREF(pvInstance); +} + + +/** @copydoc SUPSVCSERVICE::pfnTryStop */ +DECLCALLBACK(int) supSvcGlobalTryStop(void *pvInstance) +{ + Assert(pvInstance == (void *)1); + NOREF(pvInstance); + return VINF_SUCCESS; +} + + +/** @copydoc SUPSVCSERVICE::pfnStopAndDestroy */ +DECLCALLBACK(void) supSvcGlobalStopAndDestroy(void *pvInstance, bool fRunning) +{ + Assert(pvInstance == (void *)1); + NOREF(pvInstance); + NOREF(fRunning); +} + diff --git a/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp b/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp new file mode 100644 index 00000000..80fa05ed --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPSvcGrant.cpp @@ -0,0 +1,1012 @@ +/* $Id: SUPSvcGrant.cpp $ */ +/** @file + * VirtualBox Support Service - The Grant Service. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include "SUPSvcInternal.h" + +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/localipc.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to a client instance. */ +typedef struct SUPSVCGRANTSESSION *PSUPSVCGRANTSESSION; +/** Pointer to a Grant service instance. */ +typedef struct SUPSVCGRANT *PSUPSVCGRANT; + + +/** + * Grant service session data. + */ +typedef struct SUPSVCGRANTSESSION +{ + /** Pointer to the next client in the list. */ + PSUPSVCGRANTSESSION pNext; + /** Pointer to the previous client in the list. */ + PSUPSVCGRANTSESSION pPrev; + /** Pointer to the parent (the service instance). */ + PSUPSVCGRANT volatile pParent; + /** The local ipc client handle. */ + RTLOCALIPCSESSION volatile hSession; + /** Indicate that the thread should terminate ASAP. */ + bool volatile fTerminate; + /** The thread handle. */ + RTTHREAD hThread; + +} SUPSVCGRANTSESSION; + + +/** + * State grant service machine. + */ +typedef enum SUPSVCGRANTSTATE +{ + /** The invalid zero entry. */ + kSupSvcGrantState_Invalid = 0, + /** Creating - the thread is being started. + * Next: Paused or Butchered. */ + kSupSvcGrantState_Creating, + /** Paused - the thread is blocked on it's user event semaphore. + * Next: Resuming, Terminating or Butchered. + * Prev: Creating, Pausing */ + kSupSvcGrantState_Paused, + /** Resuming - the thread is being unblocked and ushered into RTLocalIpcServiceListen. + * Next: Listen or Butchered. + * Prev: Paused */ + kSupSvcGrantState_Resuming, + /** Listen - the thread is in RTLocalIpcServerListen or setting up an incoming session. + * Next: Pausing or Butchered. + * Prev: Resuming */ + kSupSvcGrantState_Listen, + /** Pausing - Cancelling the listen and dropping any incoming sessions. + * Next: Paused or Butchered. + * Prev: Listen */ + kSupSvcGrantState_Pausing, + /** Butchered - The thread has quit because something when terribly wrong. + * Next: Destroyed + * Prev: Any. */ + kSupSvcGrantState_Butchered, + /** Pausing - Cancelling the listen and dropping any incoming sessions. + * Next: Destroyed + * Prev: Paused */ + kSupSvcGrantState_Terminating, + /** Destroyed - the instance is invalid. + * Prev: Butchered or Terminating */ + kSupSvcGrantState_Destroyed, + /** The end of valid state values. */ + kSupSvcGrantState_End, + /** The usual 32-bit blowup hack. */ + kSupSvcGrantState_32BitHack = 0x7fffffff +} SUPSVCGRANTSTATE; + + +/** + * Grant service instance data. + */ +typedef struct SUPSVCGRANT +{ + /** The local ipc server handle. */ + RTLOCALIPCSERVER hServer; + + /** Critical section serializing access to the session list, the state, + * the response event, the session event, and the thread event. */ + RTCRITSECT CritSect; + /** The service thread will signal this event when it has changed to + * the 'paused' or 'running' state. */ + RTSEMEVENT hResponseEvent; + /** Event that's signaled on session termination. */ + RTSEMEVENT hSessionEvent; + /** The handle to the service thread. */ + RTTHREAD hThread; + /** Head of the session list. */ + PSUPSVCGRANTSESSION volatile pSessionHead; + /** The service state. */ + SUPSVCGRANTSTATE volatile enmState; + + /** Critical section serializing access to the SUPR3HardenedVerify APIs. */ + RTCRITSECT VerifyCritSect; +} SUPSVCGRANT; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static const char *supSvcGrantStateName(SUPSVCGRANTSTATE enmState); + + + + +/** + * Services a client session. + * + * @returns VINF_SUCCESS. + * + * @param hThread The thread handle. + * @param pvSession Pointer to the session instance data. + */ +static DECLCALLBACK(int) supSvcGrantSessionThread(RTTHREAD hThread, void *pvSession) +{ + PSUPSVCGRANTSESSION pThis = (PSUPSVCGRANTSESSION)pvSession; + RTLOCALIPCSESSION hSession = pThis->hSession; + Log(("supSvcGrantSessionThread(%p):\n", pThis)); + + /* + * Process client requests until it quits or we're cancelled on termination. + */ + while (!ASMAtomicUoReadBool(&pThis->fTerminate)) + { + RTThreadSleep(1000); + /** @todo */ + } + + /* + * Clean up the session. + */ + PSUPSVCGRANT pParent = ASMAtomicReadPtrT(&pThis->pParent, PSUPSVCGRANT); + if (pParent) + RTCritSectEnter(&pParent->CritSect); + else + Log(("supSvcGrantSessionThread(%p): No parent\n", pThis)); + + ASMAtomicXchgHandle(&pThis->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionClose(hSession); + else + Log(("supSvcGrantSessionThread(%p): No session handle\n", pThis)); + + if (pParent) + { + RTSemEventSignal(pParent->hSessionEvent); + RTCritSectLeave(&pParent->CritSect); + } + Log(("supSvcGrantSessionThread(%p): exits\n")); + return VINF_SUCCESS; +} + + +/** + * Cleans up a session. + * + * This is called while inside the grant service critical section. + * + * @param pThis The session to destroy. + * @param pParent The parent. + */ +static void supSvcGrantSessionDestroy(PSUPSVCGRANTSESSION pThis, PSUPSVCGRANT pParent) +{ + /* + * Unlink it. + */ + if (pThis->pNext) + { + Assert(pThis->pNext->pPrev == pThis); + pThis->pNext->pPrev = pThis->pPrev; + } + + if (pThis->pPrev) + { + Assert(pThis->pPrev->pNext == pThis); + pThis->pPrev->pNext = pThis->pNext; + } + else if (pParent->pSessionHead == pThis) + pParent->pSessionHead = pThis->pNext; + + /* + * Free the resources associated with it. + */ + pThis->hThread = NIL_RTTHREAD; + pThis->pNext = NULL; + pThis->pPrev = NULL; + + RTLOCALIPCSESSION hSession; + ASMAtomicXchgHandle(&pThis->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionClose(hSession); + + RTMemFree(pThis); +} + + +/** + * Cleans up zombie sessions, locked. + * + * @param pThis Pointer to the grant service instance data. + */ +static void supSvcGrantCleanUpSessionsLocked(PSUPSVCGRANT pThis) +{ + /* + * Iterate until be make it all the way thru the list. + * + * Only use the thread state as and indicator on whether we can destroy + * the session or not. + */ + PSUPSVCGRANTSESSION pCur; + do + { + for (pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + int rc = RTThreadWait(pCur->hThread, 0, NULL); + if (RT_SUCCESS(rc)) + { + supSvcGrantSessionDestroy(pCur, pThis); + break; + } + + Assert(rc == VERR_TIMEOUT); + Assert(pCur->hThread != NIL_RTTHREAD); + Assert(pCur->pNext != pThis->pSessionHead); + } + } while (pCur); +} + + +/** + * Cleans up zombie sessions. + * + * @returns VINF_SUCCESS, VBox error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param fOwnCritSect Whether we own the crit sect already. The state is preserved. + */ +static int supSvcGrantCleanUpSessions(PSUPSVCGRANT pThis, bool fOwnCritSect) +{ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantCleanUpSessions: RTCritSectEnter returns %Rrc", rc); + return rc; + } + + supSvcGrantCleanUpSessionsLocked(pThis); + + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; +} + + +/** + * Gets the state name. + * + * @returns The state name string (read only). + * @param enmState The state. + */ +static const char *supSvcGrantStateName(SUPSVCGRANTSTATE enmState) +{ + switch (enmState) + { + case kSupSvcGrantState_Invalid: return "Invalid"; + case kSupSvcGrantState_Creating: return "Creating"; + case kSupSvcGrantState_Paused: return "Paused"; + case kSupSvcGrantState_Resuming: return "Resuming"; + case kSupSvcGrantState_Listen: return "Listen"; + case kSupSvcGrantState_Pausing: return "Pausing"; + case kSupSvcGrantState_Butchered: return "Butchered"; + case kSupSvcGrantState_Terminating: return "Terminating"; + case kSupSvcGrantState_Destroyed: return "Destroyed"; + default: return "?Unknown?"; + } +} + + +/** + * Attempts to flip into the butchered state. + * + * @returns rc. + * @param pThis The instance data. + * @param fOwnCritSect Whether we own the crit sect already. + * @param pszFailed What failed. + * @param rc What to return (lazy bird). + */ +static int supSvcGrantThreadButchered(PSUPSVCGRANT pThis, bool fOwnCritSect, const char *pszFailed, int rc) +{ + int rc2 = VINF_SUCCESS; + if (!fOwnCritSect) + rc2 = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc2)) + { + supSvcLogError("supSvcGrantThread(%s): Butchered; %Rrc: %s", + supSvcGrantStateName(pThis->enmState), rc, pszFailed); + pThis->enmState = kSupSvcGrantState_Butchered; + + RTCritSectLeave(&pThis->CritSect); + } + return rc; +} + + +/** + * Creates a new session. + * + * @returns VINF_SUCCESS on success, VBox error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param hSession The client session handle. + */ +static int supSvcGrantThreadCreateSession(PSUPSVCGRANT pThis, RTLOCALIPCSESSION hSession) +{ + /* + * Allocate and initialize a new session instance before entering the critsect. + */ + PSUPSVCGRANTSESSION pSession = (PSUPSVCGRANTSESSION)RTMemAlloc(sizeof(*pSession)); + if (!pSession) + { + supSvcLogError("supSvcGrantThreadListen: failed to allocate session"); + return VINF_SUCCESS; /* not fatal? */ + } + pSession->pPrev = NULL; + pSession->pNext = NULL; + pSession->pParent = pThis; + pSession->hSession = hSession; + pSession->fTerminate = false; + pSession->hThread = NIL_RTTHREAD; + + /* + * Enter the critsect, check the state, link it and fire off the session thread. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* check the state */ + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == kSupSvcGrantState_Listen) + { + /* link it */ + pSession->pNext = pThis->pSessionHead; + if (pThis->pSessionHead) + pThis->pSessionHead->pPrev = pSession; + pThis->pSessionHead = pSession; + + /* fire up the thread */ + Log(("supSvcGrantThreadListen: starting session %p\n", pSession)); + rc = RTThreadCreate(&pSession->hThread, supSvcGrantSessionThread, pSession, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "SESSION"); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTCritSectLeave", rc); + + /* + * Successfully handled the client. + */ + return VINF_SUCCESS; + } + + /* bail out */ + supSvcLogError("supSvcGrantThreadListen: RTThreadCreate returns %Rrc", rc); + } + else + Log(("supSvcGrantThreadListen: dropping connection, state %s\n", supSvcGrantStateName(enmState))); + + RTCritSectLeave(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else + supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTCritSectEnter", rc); + RTLocalIpcSessionClose(hSession); + RTMemFree(pSession); + return rc; +} + + +/** + * Listen for a client session and kicks off the service thread for it. + * + * @returns VINF_SUCCESS on normal state change, failure if something gets screwed up. + * + * @param pThis Pointer to the grant service instance data. + */ +static int supSvcGrantThreadListen(PSUPSVCGRANT pThis) +{ + /* + * Wait for a client to connect and create a new session. + */ + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pThis->hServer, &hClientSession); + if (RT_FAILURE(rc)) + { + if (rc == VERR_CANCELLED) + LogFlow(("supSvcGrantThreadListen: cancelled\n")); + else if (rc == VERR_TRY_AGAIN) + /* for testing */; + else + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "RTLocalIpcServerListen", rc); + return VINF_SUCCESS; + } + + return supSvcGrantThreadCreateSession(pThis, hClientSession); +} + + +/** + * Grant service thread. + * + * This thread is the one listening for clients and kicks off + * the session threads and stuff. + * + * @returns VINF_SUCCESS on normal exit, VBox error status on failure. + * @param hThread The thread handle. + * @param pvThis Pointer to the grant service instance data. + */ +static DECLCALLBACK(int) supSvcGrantThread(RTTHREAD hThread, void *pvThis) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvThis; + + /* + * The state loop. + */ + for (;;) + { + /* + * Switch on the current state (requires critsect). + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantThread - RTCritSectEnter returns %Rrc", rc); + return rc; + } + + SUPSVCGRANTSTATE enmState = pThis->enmState; + LogFlow(("supSvcGrantThread: switching %s\n", supSvcGrantStateName(enmState))); + switch (enmState) + { + case kSupSvcGrantState_Creating: + case kSupSvcGrantState_Pausing: + pThis->enmState = kSupSvcGrantState_Paused; + rc = RTSemEventSignal(pThis->hResponseEvent); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "RTSemEventSignal", rc); + RT_FALL_THRU(); + + case kSupSvcGrantState_Paused: + RTCritSectLeave(&pThis->CritSect); + + rc = RTThreadUserWait(hThread, 60*1000); /* wake up once in a while (paranoia) */ + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect*/, "RTThreadUserWait", rc); + break; + + case kSupSvcGrantState_Resuming: + pThis->enmState = kSupSvcGrantState_Listen; + rc = RTSemEventSignal(pThis->hResponseEvent); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "RTSemEventSignal", rc); + RT_FALL_THRU(); + + case kSupSvcGrantState_Listen: + RTCritSectLeave(&pThis->CritSect); + rc = supSvcGrantThreadListen(pThis); + if (RT_FAILURE(rc)) + { + Log(("supSvcGrantThread: supSvcGrantDoListening returns %Rrc, exiting\n", rc)); + return rc; + } + break; + + case kSupSvcGrantState_Terminating: + RTCritSectLeave(&pThis->CritSect); + Log(("supSvcGrantThread: Done\n")); + return VINF_SUCCESS; + + case kSupSvcGrantState_Butchered: + default: + return supSvcGrantThreadButchered(pThis, true /* fOwnCritSect*/, "Bad state", VERR_INTERNAL_ERROR); + } + + /* + * Massage the session list between clients and states. + */ + rc = supSvcGrantCleanUpSessions(pThis, false /* fOwnCritSect */); + if (RT_FAILURE(rc)) + return supSvcGrantThreadButchered(pThis, false /* fOwnCritSect */, "supSvcGrantCleanUpSessions", rc); + } +} + + +/** + * Waits for the service thread to respond to a state change. + * + * @returns VINF_SUCCESS on success, VERR_TIMEOUT if it doesn't respond in time, other error code on internal error. + * + * @param pThis Pointer to the grant service instance data. + * @param enmCurState The current state. + * @param enmNewState The new state we're waiting for it to enter. + */ +static int supSvcGrantWait(PSUPSVCGRANT pThis, SUPSVCGRANTSTATE enmCurState, SUPSVCGRANTSTATE enmNewState) +{ + LogFlow(("supSvcGrantWait(,%s,%s): enter\n", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState))); + + /* + * Wait a short while for the response event to be set. + */ + RTSemEventWait(pThis->hResponseEvent, 1000); + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + if (pThis->enmState == enmNewState) + { + RTCritSectLeave(&pThis->CritSect); + rc = VINF_SUCCESS; + } + else if (pThis->enmState == enmCurState) + { + /* + * Wait good while longer. + */ + RTCritSectLeave(&pThis->CritSect); + rc = RTSemEventWait(pThis->hResponseEvent, 59*1000); /* 59 sec */ + if (RT_SUCCESS(rc) || rc == VERR_TIMEOUT) + { + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Check the state whether we've succeeded. + */ + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == enmNewState) + rc = VINF_SUCCESS; + else if (enmState == enmCurState) + { + supSvcLogError("supSvcGrantWait(,%s,%s) - the thread doesn't respond in a timely manner, failing.", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + rc = VERR_TIMEOUT; + } + else + { + supSvcLogError("supSvcGrantWait(,%s,%s) - wrong state %s!", supSvcGrantStateName(enmCurState), + supSvcGrantStateName(enmNewState), supSvcGrantStateName(enmState)); + AssertMsgFailed(("%s\n", supSvcGrantStateName(enmState))); + rc = VERR_INTERNAL_ERROR; + } + + RTCritSectLeave(&pThis->CritSect); + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTCritSectEnter returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTSemEventWait returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + } + else + { + supSvcLogError("supSvcGrantWait(,%s,%s) - wrong state %s!", supSvcGrantStateName(enmCurState), + supSvcGrantStateName(enmNewState), supSvcGrantStateName(pThis->enmState)); + AssertMsgFailed(("%s\n", supSvcGrantStateName(pThis->enmState))); + RTCritSectLeave(&pThis->CritSect); + rc = VERR_INTERNAL_ERROR; + } + } + else + supSvcLogError("supSvcGrantWait(,%s,%s) - RTCritSectEnter returns %Rrc", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState)); + + Log(("supSvcGrantWait(,%s,%s): returns %Rrc\n", + supSvcGrantStateName(enmCurState), supSvcGrantStateName(enmNewState), rc)); + return rc; +} + + +/** @copydoc SUPSVCSERVICE::pfnCreate */ +DECLCALLBACK(int) supSvcGrantCreate(void **ppvInstance) +{ + LogFlowFuncEnter(); + + /* + * Allocate and initialize the session data. + */ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)RTMemAlloc(sizeof(*pThis)); + if (!pThis) + { + supSvcLogError("supSvcGrantCreate - no memory"); + return VERR_NO_MEMORY; + } + bool fFreeIt = true; + pThis->pSessionHead = NULL; + pThis->enmState = kSupSvcGrantState_Creating; + int rc = RTCritSectInit(&pThis->VerifyCritSect); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pThis->hResponseEvent); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pThis->hSessionEvent); + if (RT_SUCCESS(rc)) + { + /* + * Create the local IPC instance and then finally fire up the thread. + */ + rc = RTLocalIpcServerCreate(&pThis->hServer, SUPSVC_GRANT_SERVICE_NAME, RTLOCALIPC_FLAGS_MULTI_SESSION); + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&pThis->hThread, supSvcGrantThread, pThis, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "GRANT"); + if (RT_SUCCESS(rc)) + { + rc = supSvcGrantWait(pThis, kSupSvcGrantState_Creating, kSupSvcGrantState_Paused); + if (RT_SUCCESS(rc)) + { + /* + * Successfully created the grant service! + */ + Log(("supSvcGrantCreate: returns VINF_SUCCESS (pThis=%p)\n", pThis)); + *ppvInstance = pThis; + return VINF_SUCCESS; + } + + /* + * The thread FAILED to start in a timely manner! + */ + RTCritSectEnter(&pThis->CritSect); + pThis->enmState = kSupSvcGrantState_Terminating; + RTCritSectLeave(&pThis->CritSect); + + RTThreadUserSignal(pThis->hThread); + + int cTries = 10; + int rc2 = RTThreadWait(pThis->hThread, 20000, NULL); + if (RT_FAILURE(rc2)) + { + /* poke it a few more times before giving up. */ + while (--cTries > 0) + { + RTThreadUserSignal(pThis->hThread); + RTLocalIpcServerCancel(pThis->hServer); + if (RTThreadWait(pThis->hThread, 1000, NULL) != VERR_TIMEOUT) + break; + } + } + fFreeIt = cTries <= 0; + } + else + supSvcLogError("supSvcGrantCreate - RTThreadCreate returns %Rrc", rc); + RTLocalIpcServerDestroy(pThis->hServer); + pThis->hServer = NIL_RTLOCALIPCSERVER; + } + else + supSvcLogError("supSvcGrantCreate - RTLocalIpcServiceCreate returns %Rrc", rc); + RTSemEventDestroy(pThis->hSessionEvent); + pThis->hSessionEvent = NIL_RTSEMEVENT; + } + else + supSvcLogError("supSvcGrantCreate - RTSemEventCreate returns %Rrc", rc); + RTSemEventDestroy(pThis->hResponseEvent); + pThis->hResponseEvent = NIL_RTSEMEVENT; + } + else + supSvcLogError("supSvcGrantCreate - RTSemEventCreate returns %Rrc", rc); + RTCritSectDelete(&pThis->CritSect); + } + else + supSvcLogError("supSvcGrantCreate - RTCritSectInit returns %Rrc", rc); + RTCritSectDelete(&pThis->VerifyCritSect); + } + else + supSvcLogError("supSvcGrantCreate - RTCritSectInit returns %Rrc", rc); + if (fFreeIt) + RTMemFree(pThis); + Log(("supSvcGrantCreate: returns %Rrc\n", rc)); + return rc; +} + + +/** @copydoc SUPSVCSERVICE::pfnStart */ +DECLCALLBACK(void) supSvcGrantStart(void *pvInstance) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + + /* + * Change the state and signal the thread. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + bool fInCritSect = true; + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState == kSupSvcGrantState_Paused) + { + pThis->enmState = kSupSvcGrantState_Resuming; + rc = RTThreadUserSignal(pThis->hThread); + if (RT_SUCCESS(rc)) + { + /* + * Wait for the bugger to respond (no need to bitch here). + */ + RTCritSectLeave(&pThis->CritSect); + supSvcGrantWait(pThis, kSupSvcGrantState_Resuming, kSupSvcGrantState_Listen); + fInCritSect = false; + } + } + else + supSvcLogError("supSvcGrantStart - Incorrect state %s!", supSvcGrantStateName(enmState)); + if (fInCritSect) + RTCritSectLeave(&pThis->CritSect); + } + else + { + supSvcLogError("supSvcGrantStart - RTCritSectEnter returns %Rrc!", rc); + AssertRCReturnVoid(rc); + } +} + + +/** @copydoc SUPSVCSERVICE::pfnTryStop */ +DECLCALLBACK(int) supSvcGrantTryStop(void *pvInstance) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + + /* + * Don't give up immediately. + */ + uint64_t u64StartTS = RTTimeMilliTS(); + int rc; + for (;;) + { + /* + * First check the state to make sure the thing is actually running. + * If the critsect is butchered, just pretend success. + */ + rc = RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantTryStop - RTCritSectEnter returns %Rrc", rc); + AssertRC(rc); + return VINF_SUCCESS; + } + SUPSVCGRANTSTATE enmState = pThis->enmState; + if (enmState != kSupSvcGrantState_Listen) + { + supSvcLogError("supSvcGrantTryStop - Not running, state: %s", supSvcGrantStateName(enmState)); + RTCritSectLeave(&pThis->CritSect); + return VINF_SUCCESS; + } + + /* + * If there are no clients, usher the thread into the paused state. + */ + supSvcGrantCleanUpSessionsLocked(pThis); + if (!pThis->pSessionHead) + { + rc = RTThreadUserReset(pThis->hThread); + pThis->enmState = kSupSvcGrantState_Pausing; + int rc2 = RTLocalIpcServerCancel(pThis->hServer); + int rc3 = RTCritSectLeave(&pThis->CritSect); + if (RT_SUCCESS(rc) && RT_SUCCESS(rc2) && RT_SUCCESS(rc3)) + supSvcGrantWait(pThis, kSupSvcGrantState_Pausing, kSupSvcGrantState_Paused); + else + { + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantTryStop - RTThreadUserReset returns %Rrc", rc); + if (RT_FAILURE(rc2)) + supSvcLogError("supSvcGrantTryStop - RTLocalIpcServerCancel returns %Rrc", rc); + if (RT_FAILURE(rc3)) + supSvcLogError("supSvcGrantTryStop - RTCritSectLeave returns %Rrc", rc); + } + return VINF_SUCCESS; + } + + /* + * Check the time limit, otherwise wait for a client event. + */ + uint64_t u64Elapsed = RTTimeMilliTS() - u64StartTS; + if (u64Elapsed >= 60*1000) /* 1 min */ + { + unsigned cSessions = 0; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + cSessions++; + RTCritSectLeave(&pThis->CritSect); + + supSvcLogError("supSvcGrantTryStop - %u active sessions after waiting %u ms", cSessions, (unsigned)u64Elapsed); + return VERR_TRY_AGAIN; + } + + rc = RTCritSectLeave(&pThis->CritSect); + if (RT_FAILURE(rc)) + { + supSvcLogError("supSvcGrantTryStop - RTCritSectLeave returns %Rrc", rc); + return VINF_SUCCESS; + } + + rc = RTSemEventWait(pThis->hSessionEvent, 60*1000 - u64Elapsed); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + { + supSvcLogError("supSvcGrantTryStop - RTSemEventWait returns %Rrc", rc); + return VINF_SUCCESS; + } + } +} + + +/** @copydoc SUPSVCSERVICE::pfnStopAndDestroy */ +DECLCALLBACK(void) supSvcGrantStopAndDestroy(void *pvInstance, bool fRunning) +{ + PSUPSVCGRANT pThis = (PSUPSVCGRANT)pvInstance; + int rc; + + /* + * Attempt to stop the service, cancelling blocked server and client calls. + */ + RTCritSectEnter(&pThis->CritSect); + + SUPSVCGRANTSTATE enmState = pThis->enmState; + AssertMsg(fRunning == (pThis->enmState == kSupSvcGrantState_Listen), + ("%RTbool %s\n", fRunning, supSvcGrantStateName(enmState))); + + if (enmState == kSupSvcGrantState_Listen) + { + RTThreadUserReset(pThis->hThread); + pThis->enmState = kSupSvcGrantState_Paused; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + ASMAtomicWriteBool(&pCur->fTerminate, true); + + /* try cancel local ipc operations that might be pending */ + RTLocalIpcServerCancel(pThis->hServer); + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + RTLOCALIPCSESSION hSession; + ASMAtomicReadHandle(&pCur->hSession, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionCancel(hSession); + } + + /* + * Wait for the thread to respond (outside the crit sect). + */ + RTCritSectLeave(&pThis->CritSect); + supSvcGrantWait(pThis, kSupSvcGrantState_Pausing, kSupSvcGrantState_Paused); + RTCritSectEnter(&pThis->CritSect); + + /* + * Wait for any lingering sessions to exit. + */ + supSvcGrantCleanUpSessionsLocked(pThis); + if (pThis->pSessionHead) + { + uint64_t u64StartTS = RTTimeMilliTS(); + do + { + /* Destroy the sessions since cancelling didn't do the trick. */ + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + { + RTLOCALIPCSESSION hSession; + ASMAtomicXchgHandle(&pCur->hSession, NIL_RTLOCALIPCSESSION, &hSession); + if (hSession != NIL_RTLOCALIPCSESSION) + { + rc = RTLocalIpcSessionClose(hSession); + AssertRC(rc); + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy: RTLocalIpcSessionClose(%p) returns %Rrc", + (uintptr_t)hSession, rc); + } + } + + /* Check the time. */ + uint64_t u64Elapsed = RTTimeMilliTS() - u64StartTS; + if (u64Elapsed >= 60*1000) /* 1 min */ + break; + + /* wait */ + RTCritSectLeave(&pThis->CritSect); + rc = RTSemEventWait(pThis->hSessionEvent, 60*1000 - u64Elapsed); + RTCritSectEnter(&pThis->CritSect); + if (RT_FAILURE(rc) && rc != VERR_TIMEOUT) + break; + + /* cleanup and check again */ + supSvcGrantCleanUpSessionsLocked(pThis); + } while (pThis->pSessionHead); + } + } + + /* + * Tell the service thread to terminate and wait for it to do so. + */ + pThis->enmState = kSupSvcGrantState_Terminating; + RTLOCALIPCSERVER hServer; + ASMAtomicXchgHandle(&pThis->hServer, NIL_RTLOCALIPCSERVER, &hServer); + RTThreadUserSignal(pThis->hThread); + + RTCritSectLeave(&pThis->CritSect); + + rc = RTThreadWait(pThis->hThread, 20*1000, NULL); + if (RT_FAILURE(rc) && rc == VERR_TIMEOUT) + { + RTThreadUserSignal(pThis->hThread); + RTLocalIpcServerDestroy(hServer); + hServer = NIL_RTLOCALIPCSERVER; + + rc = RTThreadWait(pThis->hThread, 40*1000, NULL); + if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy - RTThreadWait(40 sec) returns %Rrc", rc); + } + else if (RT_FAILURE(rc)) + supSvcLogError("supSvcGrantStopAndDestroy - RTThreadWait(20 sec) returns %Rrc", rc); + pThis->hThread = NIL_RTTHREAD; + + /* + * Kill the parent pointers of any lingering sessions. + */ + RTCritSectEnter(&pThis->CritSect); + pThis->enmState = kSupSvcGrantState_Destroyed; + + supSvcGrantCleanUpSessionsLocked(pThis); + unsigned cSessions = 0; + for (PSUPSVCGRANTSESSION pCur = pThis->pSessionHead; pCur; pCur = pCur->pNext) + ASMAtomicWriteNullPtr(&pCur->pParent); + + RTCritSectLeave(&pThis->CritSect); + if (cSessions) + supSvcLogError("supSvcGrantStopAndDestroy: %d session failed to terminate!", cSessions); + + /* + * Free the resource. + */ + RTLocalIpcServerDestroy(hServer); + + RTSemEventDestroy(pThis->hResponseEvent); + pThis->hResponseEvent = NIL_RTSEMEVENT; + + RTSemEventDestroy(pThis->hSessionEvent); + pThis->hSessionEvent = NIL_RTSEMEVENT; + + RTCritSectDelete(&pThis->VerifyCritSect); + RTCritSectDelete(&pThis->CritSect); + + RTMemFree(pThis); + + Log(("supSvcGrantStopAndDestroy: done (rc=%Rrc)\n", rc)); +} + diff --git a/src/VBox/HostDrivers/Support/SUPSvcInternal.h b/src/VBox/HostDrivers/Support/SUPSvcInternal.h new file mode 100644 index 00000000..88782b86 --- /dev/null +++ b/src/VBox/HostDrivers/Support/SUPSvcInternal.h @@ -0,0 +1,104 @@ +/* $Id: SUPSvcInternal.h $ */ +/** @file + * VirtualBox Support Service - Internal header. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_SUPSvcInternal_h +#define VBOX_INCLUDED_SRC_Support_SUPSvcInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <iprt/stdarg.h> +#include <iprt/getopt.h> + +RT_C_DECLS_BEGIN + +/** @name Common Helpers + * @{ */ +void supSvcLogErrorStr(const char *pszMsg); +void supSvcLogErrorV(const char *pszFormat, va_list va); +void supSvcLogError(const char *pszFormat, ...); +int supSvcLogGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTOPTIONUNION pValue); +int supSvcLogTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg); +void supSvcDisplayErrorV(const char *pszFormat, va_list va); +void supSvcDisplayError(const char *pszFormat, ...); +int supSvcDisplayGetOptError(const char *pszAction, int rc, int argc, char **argv, int iArg, PCRTOPTIONUNION pValue); +int supSvcDisplayTooManyArgsError(const char *pszAction, int argc, char **argv, int iArg); +/** @} */ + + +/** @name OS Backend + * @{ */ +/** + * Logs the message to the appropriate system log. + * + * @param pszMsg The log string. + */ +void supSvcOsLogErrorStr(const char *pszMsg); +/** @} */ + + +/** @name The Service Manager + * @{ */ +void supSvcStopAndDestroyServices(void); +int supSvcTryStopServices(void); +int supSvcCreateAndStartServices(void); +/** @} */ + + +/** @name The Grant Service + * @{ */ +#define SUPSVC_GRANT_SERVICE_NAME "VirtualBoxGrantSvc" +DECLCALLBACK(int) supSvcGrantCreate(void **ppvInstance); +DECLCALLBACK(void) supSvcGrantStart(void *pvInstance); +DECLCALLBACK(int) supSvcGrantTryStop(void *pvInstance); +DECLCALLBACK(void) supSvcGrantStopAndDestroy(void *pvInstance, bool fRunning); +/** @} */ + + +/** @name The Global Service + * @{ */ +DECLCALLBACK(int) supSvcGlobalCreate(void **ppvInstance); +DECLCALLBACK(void) supSvcGlobalStart(void *pvInstance); +DECLCALLBACK(int) supSvcGlobalTryStop(void *pvInstance); +DECLCALLBACK(void) supSvcGlobalStopAndDestroy(void *pvInstance, bool fRunning); +/** @} */ + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Support_SUPSvcInternal_h */ + diff --git a/src/VBox/HostDrivers/Support/bldSUPSignedDummy.cpp b/src/VBox/HostDrivers/Support/bldSUPSignedDummy.cpp new file mode 100644 index 00000000..8f780a1c --- /dev/null +++ b/src/VBox/HostDrivers/Support/bldSUPSignedDummy.cpp @@ -0,0 +1,42 @@ +/* $Id: bldSUPSignedDummy.cpp $ */ +/** @file + * VirtualBox Support Library - Dummy program for extracting signing certificate. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +int main() +{ + return 0; +} + diff --git a/src/VBox/HostDrivers/Support/darwin/Info.plist b/src/VBox/HostDrivers/Support/darwin/Info.plist new file mode 100644 index 00000000..e010a700 --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/Info.plist @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleExecutable</key> <string>VBoxDrv</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxDrv</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundleName</key> <string>VBoxDrv</string> + <key>CFBundlePackageType</key> <string>KEXT</string> + <key>CFBundleSignature</key> <string>????</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>OSBundleCompatibleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>IOKitPersonalities</key> + <dict> + <key>VBoxDrv</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxDrv</string> + <key>IOClass</key> <string>org_virtualbox_SupDrv</string> + <key>IOMatchCategory</key> <string>org_virtualbox_SupDrv</string> + <key>IOProviderClass</key> <string>IOResources</string> + <key>IOResourceMatch</key> <string>IOKit</string> + <key>IOUserClientClass</key> <string>org_virtualbox_SupDrvClient</string> + </dict> + </dict> + <key>OSBundleLibraries</key> + <dict> + <key>com.apple.kpi.bsd</key> <string>9.0.0</string> + <key>com.apple.kpi.mach</key> <string>9.0.0</string> + <key>com.apple.kpi.libkern</key> <string>9.0.0</string> + <key>com.apple.kpi.unsupported</key> <string>9.0.0</string> + <key>com.apple.kpi.iokit</key> <string>9.0.0</string> + </dict> + <key>OSBundleLibraries_x86_64</key> + <dict> + <key>com.apple.kpi.bsd</key> <string>10.0.0d4</string> + <key>com.apple.kpi.mach</key> <string>10.0.0d3</string> + <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string> + <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string> + <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string> + </dict> +</dict> +</plist> + diff --git a/src/VBox/HostDrivers/Support/darwin/Makefile.kup b/src/VBox/HostDrivers/Support/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp b/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp new file mode 100644 index 00000000..35f6e8ba --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp @@ -0,0 +1,2410 @@ +/* $Id: SUPDrv-darwin.cpp $ */ +/** @file + * VirtualBox Support Driver - Darwin Specific Code. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../../../Runtime/r0drv/darwin/the-darwin-kernel.h" + +#include "../SUPDrvInternal.h" +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/ctype.h> +#include <iprt/dbg.h> +#include <iprt/initterm.h> +#include <iprt/file.h> +#include <iprt/ldr.h> +#include <iprt/mem.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/x86.h> +#include <iprt/crypto/applecodesign.h> +#include <iprt/crypto/store.h> +#include <iprt/crypto/pkcs7.h> +#include <iprt/crypto/x509.h> +#include <VBox/err.h> +#include <VBox/log.h> + +#include <mach/kmod.h> +#include <miscfs/devfs/devfs.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/ioccom.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/kauth.h> +#include <IOKit/IOService.h> +#include <IOKit/IOUserClient.h> +#include <IOKit/pwr_mgt/RootDomain.h> +#include <IOKit/IODeviceTreeSupport.h> +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 +# include <IOKit/usb/IOUSBHIDDriver.h> +#endif +#include <IOKit/bluetooth/IOBluetoothHIDDriver.h> +#include <IOKit/bluetooth/IOBluetoothHIDDriverTypes.h> + +#ifdef VBOX_WITH_HOST_VMX +# include <libkern/version.h> +RT_C_DECLS_BEGIN +# include <i386/vmx.h> +RT_C_DECLS_END +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The system device node name. */ +#define DEVICE_NAME_SYS "vboxdrv" +/** The user device node name. */ +#define DEVICE_NAME_USR "vboxdrvu" + + +/** @name For debugging/whatever, now permanent. + * @{ */ +#define VBOX_PROC_SELFNAME_LEN 31 +#define VBOX_RETRIEVE_CUR_PROC_NAME(a_Name) char a_Name[VBOX_PROC_SELFNAME_LEN + 1]; \ + proc_selfname(a_Name, VBOX_PROC_SELFNAME_LEN) +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxDrvDarwinStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxDrvDarwinStop(struct kmod_info *pKModInfo, void *pvData); +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION +static int supdrvDarwinInitCertStores(PSUPDRVDEVEXT pDevExt); +static void supdrvDarwinDestroyCertStores(PSUPDRVDEVEXT pDevExt); +#endif + +static int VBoxDrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int VBoxDrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int VBoxDrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess); +#ifndef VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV +static int VBoxDrvDarwinIOCtlSMAP(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess); +#endif +static int VBoxDrvDarwinIOCtlSlow(PSUPDRVSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess); + +static int VBoxDrvDarwinErr2DarwinErr(int rc); + +static IOReturn VBoxDrvDarwinSleepHandler(void *pvTarget, void *pvRefCon, UInt32 uMessageType, IOService *pProvider, void *pvMessageArgument, vm_size_t argSize); +RT_C_DECLS_END + +static int vboxdrvDarwinResolveSymbols(void); +static bool vboxdrvDarwinCpuHasSMAP(void); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The service class. + * This is just a formality really. + */ +class org_virtualbox_SupDrv : public IOService +{ + OSDeclareDefaultStructors(org_virtualbox_SupDrv); + +public: + virtual bool init(OSDictionary *pDictionary = 0); + virtual void free(void); + virtual bool start(IOService *pProvider); + virtual void stop(IOService *pProvider); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool terminate(IOOptionBits fOptions); + + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; +}; + +OSDefineMetaClassAndStructors(org_virtualbox_SupDrv, IOService); + + +/** + * An attempt at getting that clientDied() notification. + * I don't think it'll work as I cannot figure out where/what creates the correct + * port right. + */ +class org_virtualbox_SupDrvClient : public IOUserClient +{ + OSDeclareDefaultStructors(org_virtualbox_SupDrvClient); + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; + + PSUPDRVSESSION m_pSession; /**< The session. */ + task_t m_Task; /**< The client task. */ + org_virtualbox_SupDrv *m_pProvider; /**< The service provider. */ + +public: + virtual bool initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type); + virtual bool start(IOService *pProvider); + static void sessionClose(RTPROCESS Process); + virtual IOReturn clientClose(void); + virtual IOReturn clientDied(void); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual bool finalize(IOOptionBits fOptions); + virtual void stop(IOService *pProvider); + + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); +}; + +OSDefineMetaClassAndStructors(org_virtualbox_SupDrvClient, IOUserClient); + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Declare the module stuff. + */ +RT_C_DECLS_BEGIN +extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData); +extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData); + +KMOD_EXPLICIT_DECL(VBoxDrv, VBOX_VERSION_STRING, _start, _stop) +DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = VBoxDrvDarwinStart; +DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = VBoxDrvDarwinStop; +DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + + +/** + * Device extention & session data association structure. + */ +static SUPDRVDEVEXT g_DevExt; + +/** + * The character device switch table for the driver. + */ +static struct cdevsw g_DevCW = +{ + /** @todo g++ doesn't like this syntax - it worked with gcc before renaming to .cpp. */ + /*.d_open = */VBoxDrvDarwinOpen, + /*.d_close = */VBoxDrvDarwinClose, + /*.d_read = */eno_rdwrt, + /*.d_write = */eno_rdwrt, + /*.d_ioctl = */VBoxDrvDarwinIOCtl, + /*.d_stop = */eno_stop, + /*.d_reset = */eno_reset, + /*.d_ttys = */NULL, + /*.d_select= */eno_select, + /*.d_mmap = */eno_mmap, + /*.d_strategy = */eno_strat, + /*.d_getc = */(void *)(uintptr_t)&enodev, //eno_getc, + /*.d_putc = */(void *)(uintptr_t)&enodev, //eno_putc, + /*.d_type = */0 +}; + +/** Major device number. */ +static int g_iMajorDeviceNo = -1; +/** Registered devfs device handle for the system device. */ +static void *g_hDevFsDeviceSys = NULL; +/** Registered devfs device handle for the user device. */ +static void *g_hDevFsDeviceUsr = NULL; + +/** Spinlock protecting g_apSessionHashTab. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +/** Hash table */ +static PSUPDRVSESSION g_apSessionHashTab[19]; +/** Calculates the index into g_apSessionHashTab.*/ +#define SESSION_HASH(pid) ((pid) % RT_ELEMENTS(g_apSessionHashTab)) +/** The number of open sessions. */ +static int32_t volatile g_cSessions = 0; +/** The notifier handle for the sleep callback handler. */ +static IONotifier *g_pSleepNotifier = NULL; + +/** Pointer to vmx_suspend(). */ +static PFNRT g_pfnVmxSuspend = NULL; +/** Pointer to vmx_resume(). */ +static PFNRT g_pfnVmxResume = NULL; +/** Pointer to vmx_use_count. */ +static int volatile *g_pVmxUseCount = NULL; + +#ifdef SUPDRV_WITH_MSR_PROBER +/** Pointer to rdmsr_carefully if found. Returns 0 on success. */ +static int (*g_pfnRdMsrCarefully)(uint32_t uMsr, uint32_t *puLow, uint32_t *puHigh) = NULL; +/** Pointer to rdmsr64_carefully if found. Returns 0 on success. */ +static int (*g_pfnRdMsr64Carefully)(uint32_t uMsr, uint64_t *uValue) = NULL; +/** Pointer to wrmsr[64]_carefully if found. Returns 0 on success. */ +static int (*g_pfnWrMsr64Carefully)(uint32_t uMsr, uint64_t uValue) = NULL; +#endif + +/** SUPKERNELFEATURES_XXX */ +static uint32_t g_fKernelFeatures = 0; + +/** + * Start the kernel module. + */ +static kern_return_t VBoxDrvDarwinStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); +#ifdef DEBUG + printf("VBoxDrvDarwinStart\n"); +#endif + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the device extension. + */ + rc = supdrvInitDevExt(&g_DevExt, sizeof(SUPDRVSESSION)); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + supdrvDarwinInitCertStores(&g_DevExt); +#endif + + /* + * Initialize the session hash table. + */ + memset(g_apSessionHashTab, 0, sizeof(g_apSessionHashTab)); /* paranoia */ + rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxDrvDarwin"); + if (RT_SUCCESS(rc)) + { + if (vboxdrvDarwinCpuHasSMAP()) + { + g_fKernelFeatures |= SUPKERNELFEATURES_SMAP; +#ifndef VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV + LogRel(("disabling SMAP for VBoxDrvDarwinIOCtl\n")); + g_DevCW.d_ioctl = VBoxDrvDarwinIOCtlSMAP; +#endif + } + + /* + * Resolve some extra kernel symbols. + */ + rc = vboxdrvDarwinResolveSymbols(); + if (RT_SUCCESS(rc)) + { + + /* + * Registering ourselves as a character device. + */ + g_iMajorDeviceNo = cdevsw_add(-1, &g_DevCW); + if (g_iMajorDeviceNo >= 0) + { +#ifdef VBOX_WITH_HARDENING + g_hDevFsDeviceSys = devfs_make_node(makedev(g_iMajorDeviceNo, 0), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0600, DEVICE_NAME_SYS); +#else + g_hDevFsDeviceSys = devfs_make_node(makedev(g_iMajorDeviceNo, 0), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_SYS); +#endif + if (g_hDevFsDeviceSys) + { + g_hDevFsDeviceUsr = devfs_make_node(makedev(g_iMajorDeviceNo, 1), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0666, DEVICE_NAME_USR); + if (g_hDevFsDeviceUsr) + { + LogRel(("VBoxDrv: version " VBOX_VERSION_STRING " r%d; IOCtl version %#x; IDC version %#x; dev major=%d\n", + VBOX_SVN_REV, SUPDRV_IOC_VERSION, SUPDRV_IDC_VERSION, g_iMajorDeviceNo)); + + /* Register a sleep/wakeup notification callback */ + g_pSleepNotifier = registerPrioritySleepWakeInterest(&VBoxDrvDarwinSleepHandler, &g_DevExt, NULL); + if (g_pSleepNotifier == NULL) + LogRel(("VBoxDrv: register for sleep/wakeup events failed\n")); + + return KMOD_RETURN_SUCCESS; + } + + LogRel(("VBoxDrv: devfs_make_node(makedev(%d,1),,,,%s) failed\n", g_iMajorDeviceNo, DEVICE_NAME_USR)); + devfs_remove(g_hDevFsDeviceSys); + g_hDevFsDeviceSys = NULL; + } + else + LogRel(("VBoxDrv: devfs_make_node(makedev(%d,0),,,,%s) failed\n", g_iMajorDeviceNo, DEVICE_NAME_SYS)); + + cdevsw_remove(g_iMajorDeviceNo, &g_DevCW); + g_iMajorDeviceNo = -1; + } + else + LogRel(("VBoxDrv: cdevsw_add failed (%d)\n", g_iMajorDeviceNo)); + } +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + supdrvDarwinDestroyCertStores(&g_DevExt); +#endif + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; + } + else + LogRel(("VBoxDrv: RTSpinlockCreate failed (rc=%d)\n", rc)); + supdrvDeleteDevExt(&g_DevExt); + } + else + printf("VBoxDrv: failed to initialize device extension (rc=%d)\n", rc); + RTR0TermForced(); + } + else + printf("VBoxDrv: failed to initialize IPRT (rc=%d)\n", rc); + + memset(&g_DevExt, 0, sizeof(g_DevExt)); + return KMOD_RETURN_FAILURE; +} + + +/** + * Resolves kernel symbols we need and some we just would like to have. + */ +static int vboxdrvDarwinResolveSymbols(void) +{ + RTDBGKRNLINFO hKrnlInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlInfo, 0); + if (RT_SUCCESS(rc)) + { + /* + * The VMX stuff - required with raw-mode (in theory for 64-bit on + * 32-bit too, but we never did that on darwin). + */ + int rc1 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "vmx_resume", (void **)&g_pfnVmxResume); + int rc2 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "vmx_suspend", (void **)&g_pfnVmxSuspend); + int rc3 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "vmx_use_count", (void **)&g_pVmxUseCount); + if (RT_SUCCESS(rc1) && RT_SUCCESS(rc2) && RT_SUCCESS(rc3)) + { + LogRel(("VBoxDrv: vmx_resume=%p vmx_suspend=%p vmx_use_count=%p (%d) cr4=%#x\n", + g_pfnVmxResume, g_pfnVmxSuspend, g_pVmxUseCount, *g_pVmxUseCount, ASMGetCR4() )); + } + else + { + LogRel(("VBoxDrv: failed to resolve vmx stuff: vmx_resume=%Rrc vmx_suspend=%Rrc vmx_use_count=%Rrc", rc1, rc2, rc3)); + g_pfnVmxResume = NULL; + g_pfnVmxSuspend = NULL; + g_pVmxUseCount = NULL; +#ifdef VBOX_WITH_RAW_MODE + rc = VERR_SYMBOL_NOT_FOUND; +#endif + } + + if (RT_SUCCESS(rc)) + { +#ifdef SUPDRV_WITH_MSR_PROBER + /* + * MSR prober stuff - optional! + */ + rc2 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "rdmsr_carefully", (void **)&g_pfnRdMsrCarefully); + if (RT_FAILURE(rc2)) + g_pfnRdMsrCarefully = NULL; + rc2 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "rdmsr64_carefully", (void **)&g_pfnRdMsr64Carefully); + if (RT_FAILURE(rc2)) + g_pfnRdMsr64Carefully = NULL; +# ifdef RT_ARCH_AMD64 /* Missing 64 in name, so if implemented on 32-bit it could have different signature. */ + rc2 = RTR0DbgKrnlInfoQuerySymbol(hKrnlInfo, NULL, "wrmsr_carefully", (void **)&g_pfnWrMsr64Carefully); + if (RT_FAILURE(rc2)) +# endif + g_pfnWrMsr64Carefully = NULL; + + LogRel(("VBoxDrv: g_pfnRdMsrCarefully=%p g_pfnRdMsr64Carefully=%p g_pfnWrMsr64Carefully=%p\n", + g_pfnRdMsrCarefully, g_pfnRdMsr64Carefully, g_pfnWrMsr64Carefully)); + +#endif /* SUPDRV_WITH_MSR_PROBER */ + } + + RTR0DbgKrnlInfoRelease(hKrnlInfo); + } + else + LogRel(("VBoxDrv: Failed to open kernel symbols, rc=%Rrc\n", rc)); + return rc; +} + + +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + +/** + * Initalizes the certificate stores (code signing) in the device extension. + */ +static int supdrvDarwinInitCertStores(PSUPDRVDEVEXT pDevExt) +{ + pDevExt->hAdditionalStore = NIL_RTCRSTORE; + + pDevExt->hRootStore = NIL_RTCRSTORE; + int rc = RTCrStoreCreateInMem(&pDevExt->hRootStore, g_cSUPTrustedTAs + 1); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < g_cSUPTrustedTAs; i++) + { + int rc2 = RTCrStoreCertAddEncoded(pDevExt->hRootStore, RTCRCERTCTX_F_ENC_TAF_DER, + g_aSUPTrustedTAs[i].pch, g_aSUPTrustedTAs[i].cb, NULL); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + { + printf("VBoxDrv: Error loading g_aSUPTrustedTAs[%u]: %d\n", i, rc); + rc = rc2; + } + } + + /* We implicitly trust the build certificate. */ + int rc2 = RTCrStoreCertAddEncoded(pDevExt->hRootStore, RTCRCERTCTX_F_ENC_X509_DER, + g_abSUPBuildCert, g_cbSUPBuildCert, NULL); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + { + printf("VBoxDrv: Error loading g_cbSUPBuildCert: %d\n", rc); + rc = rc2; + } + } + return rc; +} + + +/** + * Releases the certificate stores in the device extension. + */ +static void supdrvDarwinDestroyCertStores(PSUPDRVDEVEXT pDevExt) +{ + if (pDevExt->hRootStore != NIL_RTCRSTORE) + { + uint32_t cRefs = RTCrStoreRelease(pDevExt->hRootStore); + Assert(cRefs == 0); RT_NOREF(cRefs); + pDevExt->hRootStore = NIL_RTCRSTORE; + } + if (pDevExt->hAdditionalStore != NIL_RTCRSTORE) + { + uint32_t cRefs = RTCrStoreRelease(pDevExt->hAdditionalStore); + Assert(cRefs == 0); RT_NOREF(cRefs); + pDevExt->hAdditionalStore = NIL_RTCRSTORE; + } +} + +#endif /* VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION */ + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxDrvDarwinStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + int rc; + LogFlow(("VBoxDrvDarwinStop\n")); + + /** @todo I've got a nagging feeling that we'll have to keep track of users and refuse + * unloading if we're busy. Investigate and implement this! */ + + /* + * Undo the work done during start (in reverse order). + */ + if (g_pSleepNotifier) + { + g_pSleepNotifier->remove(); + g_pSleepNotifier = NULL; + } + + devfs_remove(g_hDevFsDeviceUsr); + g_hDevFsDeviceUsr = NULL; + + devfs_remove(g_hDevFsDeviceSys); + g_hDevFsDeviceSys = NULL; + + rc = cdevsw_remove(g_iMajorDeviceNo, &g_DevCW); + Assert(rc == g_iMajorDeviceNo); + g_iMajorDeviceNo = -1; + + supdrvDeleteDevExt(&g_DevExt); + + rc = RTSpinlockDestroy(g_Spinlock); + AssertRC(rc); + g_Spinlock = NIL_RTSPINLOCK; + +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + supdrvDarwinDestroyCertStores(&g_DevExt); +#endif + + RTR0TermForced(); + + memset(&g_DevExt, 0, sizeof(g_DevExt)); +#ifdef DEBUG + printf("VBoxDrvDarwinStop - done\n"); +#endif + return KMOD_RETURN_SUCCESS; +} + + +/** + * Device open. Called on open /dev/vboxdrv + * + * @param Dev The device number. + * @param fFlags ???. + * @param fDevType ???. + * @param pProcess The process issuing this request. + */ +static int VBoxDrvDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(fFlags, fDevType); +#ifdef DEBUG_DARWIN_GIP + char szName[128]; + szName[0] = '\0'; + proc_name(proc_pid(pProcess), szName, sizeof(szName)); + Log(("VBoxDrvDarwinOpen: pid=%d '%s'\n", proc_pid(pProcess), szName)); +#endif + + /* + * Only two minor devices numbers are allowed. + */ + if (minor(Dev) != 0 && minor(Dev) != 1) + return EACCES; + + /* + * The process issuing the request must be the current process. + */ + RTPROCESS Process = RTProcSelf(); + if ((int)Process != proc_pid(pProcess)) + return EIO; + + /* + * Find the session created by org_virtualbox_SupDrvClient, fail + * if no such session, and mark it as opened. We set the uid & gid + * here too, since that is more straight forward at this point. + */ + const bool fUnrestricted = minor(Dev) == 0; + int rc = VINF_SUCCESS; + PSUPDRVSESSION pSession = NULL; + kauth_cred_t pCred = kauth_cred_proc_ref(pProcess); + if (pCred) + { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 + RTUID Uid = kauth_cred_getruid(pCred); + RTGID Gid = kauth_cred_getrgid(pCred); +#else + RTUID Uid = pCred->cr_ruid; + RTGID Gid = pCred->cr_rgid; +#endif + unsigned iHash = SESSION_HASH(Process); + RTSpinlockAcquire(g_Spinlock); + + pSession = g_apSessionHashTab[iHash]; + while (pSession && pSession->Process != Process) + pSession = pSession->pNextHash; + if (pSession) + { + if (!pSession->fOpened) + { + pSession->fOpened = true; + pSession->fUnrestricted = fUnrestricted; + pSession->Uid = Uid; + pSession->Gid = Gid; + } + else + rc = VERR_ALREADY_LOADED; + } + else + rc = VERR_GENERAL_FAILURE; + + RTSpinlockRelease(g_Spinlock); +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 + kauth_cred_unref(&pCred); +#else /* 10.4 */ + /* The 10.4u SDK headers and 10.4.11 kernel source have inconsistent definitions + of kauth_cred_unref(), so use the other (now deprecated) API for releasing it. */ + kauth_cred_rele(pCred); +#endif /* 10.4 */ + } + else + rc = VERR_INVALID_PARAMETER; + +#ifdef DEBUG_DARWIN_GIP + OSDBGPRINT(("VBoxDrvDarwinOpen: pid=%d '%s' pSession=%p rc=%d\n", proc_pid(pProcess), szName, pSession, rc)); +#else + Log(("VBoxDrvDarwinOpen: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, proc_pid(pProcess))); +#endif + return VBoxDrvDarwinErr2DarwinErr(rc); +} + + +/** + * Close device. + */ +static int VBoxDrvDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags, fDevType, pProcess); + Log(("VBoxDrvDarwinClose: pid=%d\n", (int)RTProcSelf())); + Assert(proc_pid(pProcess) == (int)RTProcSelf()); + + /* + * Hand the session closing to org_virtualbox_SupDrvClient. + */ + org_virtualbox_SupDrvClient::sessionClose(RTProcSelf()); + return 0; +} + + +/** + * Device I/O Control entry point. + * + * @returns Darwin for slow IOCtls and VBox status code for the fast ones. + * @param Dev The device number (major+minor). + * @param iCmd The IOCtl command. + * @param pData Pointer to the data (if any it's a SUPDRVIOCTLDATA (kernel copy)). + * @param fFlags Flag saying we're a character device (like we didn't know already). + * @param pProcess The process issuing this request. + */ +static int VBoxDrvDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess) +{ + RT_NOREF(fFlags); + const bool fUnrestricted = minor(Dev) == 0; + const RTPROCESS Process = proc_pid(pProcess); + const unsigned iHash = SESSION_HASH(Process); + PSUPDRVSESSION pSession; + +#ifdef VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV + /* + * Refuse all I/O control calls if we've ever detected EFLAGS.AC being cleared. + * + * This isn't a problem, as there is absolutely nothing in the kernel context that + * depend on user context triggering cleanups. That would be pretty wild, right? + */ + if (RT_UNLIKELY(g_DevExt.cBadContextCalls > 0)) + { + SUPR0Printf("VBoxDrvDarwinIOCtl: EFLAGS.AC=0 detected %u times, refusing all I/O controls!\n", g_DevExt.cBadContextCalls); + return EDEVERR; + } +#endif + + /* + * Find the session. + */ + RTSpinlockAcquire(g_Spinlock); + + pSession = g_apSessionHashTab[iHash]; + while (pSession && (pSession->Process != Process || pSession->fUnrestricted != fUnrestricted || !pSession->fOpened)) + pSession = pSession->pNextHash; + + if (RT_LIKELY(pSession)) + supdrvSessionRetain(pSession); + + RTSpinlockRelease(g_Spinlock); + if (RT_UNLIKELY(!pSession)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#lx\n", + (int)Process, iCmd)); + return EINVAL; + } + + /* + * Deal with the two high-speed IOCtl that takes it's arguments from + * the session and iCmd, and only returns a VBox status code. + */ + int rc; + AssertCompile((SUP_IOCTL_FAST_DO_FIRST & 0xff) == (SUP_IOCTL_FLAG | 64)); + if ( (uintptr_t)(iCmd - SUP_IOCTL_FAST_DO_FIRST) < (uintptr_t)32 + && fUnrestricted) + rc = supdrvIOCtlFast(iCmd - SUP_IOCTL_FAST_DO_FIRST, *(uint32_t *)pData, &g_DevExt, pSession); + else + rc = VBoxDrvDarwinIOCtlSlow(pSession, iCmd, pData, pProcess); + + supdrvSessionRelease(pSession); + return rc; +} + + +#ifndef VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV +/** + * Alternative Device I/O Control entry point on hosts with SMAP support. + * + * @returns Darwin for slow IOCtls and VBox status code for the fast ones. + * @param Dev The device number (major+minor). + * @param iCmd The IOCtl command. + * @param pData Pointer to the data (if any it's a SUPDRVIOCTLDATA (kernel copy)). + * @param fFlags Flag saying we're a character device (like we didn't know already). + * @param pProcess The process issuing this request. + */ +static int VBoxDrvDarwinIOCtlSMAP(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess) +{ + /* + * Allow VBox R0 code to touch R3 memory. Setting the AC bit disables the + * SMAP check. + */ + RTCCUINTREG fSavedEfl = ASMAddFlags(X86_EFL_AC); + + int rc = VBoxDrvDarwinIOCtl(Dev, iCmd, pData, fFlags, pProcess); + +# if defined(VBOX_STRICT) || defined(VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV) + /* + * Before we restore AC and the rest of EFLAGS, check if the IOCtl handler code + * accidentially modified it or some other important flag. + */ + if (RT_UNLIKELY( (ASMGetFlags() & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF | X86_EFL_IOPL)) + != ((fSavedEfl & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF | X86_EFL_IOPL)) | X86_EFL_AC) )) + { + char szTmp[48]; + RTStrPrintf(szTmp, sizeof(szTmp), "iCmd=%#x: %#x->%#x!", iCmd, (uint32_t)fSavedEfl, (uint32_t)ASMGetFlags()); + supdrvBadContext(&g_DevExt, "SUPDrv-darwin.cpp", __LINE__, szTmp); + } +# endif + + ASMSetFlags(fSavedEfl); + return rc; +} +#endif /* VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV */ + + +/** + * Worker for VBoxDrvDarwinIOCtl that takes the slow IOCtl functions. + * + * @returns Darwin errno. + * + * @param pSession The session. + * @param iCmd The IOCtl command. + * @param pData Pointer to the kernel copy of the SUPDRVIOCTLDATA buffer. + * @param pProcess The calling process. + */ +static int VBoxDrvDarwinIOCtlSlow(PSUPDRVSESSION pSession, u_long iCmd, caddr_t pData, struct proc *pProcess) +{ + RT_NOREF(pProcess); + LogFlow(("VBoxDrvDarwinIOCtlSlow: pSession=%p iCmd=%p pData=%p pProcess=%p\n", pSession, iCmd, pData, pProcess)); + + + /* + * Buffered or unbuffered? + */ + PSUPREQHDR pHdr; + user_addr_t pUser = 0; + void *pvPageBuf = NULL; + uint32_t cbReq = IOCPARM_LEN(iCmd); + if ((IOC_DIRMASK & iCmd) == IOC_INOUT) + { + pHdr = (PSUPREQHDR)pData; + if (RT_UNLIKELY(cbReq < sizeof(*pHdr))) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: cbReq=%#x < %#x; iCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), iCmd)); + return EINVAL; + } + if (RT_UNLIKELY((pHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: bad magic fFlags=%#x; iCmd=%#lx\n", pHdr->fFlags, iCmd)); + return EINVAL; + } + if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq + || pHdr->cbIn < sizeof(*pHdr) + || pHdr->cbOut < sizeof(*pHdr))) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: max(%#x,%#x) != %#x; iCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, iCmd)); + return EINVAL; + } + } + else if ((IOC_DIRMASK & iCmd) == IOC_VOID && !cbReq) + { + /* + * Get the header and figure out how much we're gonna have to read. + */ + IPRT_DARWIN_SAVE_EFL_AC(); + SUPREQHDR Hdr; + pUser = (user_addr_t)*(void **)pData; + int rc = copyin(pUser, &Hdr, sizeof(Hdr)); + if (RT_UNLIKELY(rc)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyin(%llx,Hdr,) -> %#x; iCmd=%#lx\n", (unsigned long long)pUser, rc, iCmd)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: bad magic fFlags=%#x; iCmd=%#lx\n", Hdr.fFlags, iCmd)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return EINVAL; + } + cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) + || Hdr.cbOut < sizeof(Hdr) + || cbReq > _1M*16)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: max(%#x,%#x); iCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, iCmd)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return EINVAL; + } + + /* + * Allocate buffer and copy in the data. + */ + pHdr = (PSUPREQHDR)RTMemTmpAlloc(cbReq); + if (!pHdr) + pvPageBuf = pHdr = (PSUPREQHDR)IOMallocAligned(RT_ALIGN_Z(cbReq, PAGE_SIZE), 8); + if (RT_UNLIKELY(!pHdr)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: failed to allocate buffer of %d bytes; iCmd=%#lx\n", cbReq, iCmd)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return ENOMEM; + } + rc = copyin(pUser, pHdr, Hdr.cbIn); + if (RT_UNLIKELY(rc)) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyin(%llx,%p,%#x) -> %#x; iCmd=%#lx\n", + (unsigned long long)pUser, pHdr, Hdr.cbIn, rc, iCmd)); + if (pvPageBuf) + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + else + RTMemTmpFree(pHdr); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + if (Hdr.cbIn < cbReq) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + { + Log(("VBoxDrvDarwinIOCtlSlow: huh? cbReq=%#x iCmd=%#lx\n", cbReq, iCmd)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + int rc = supdrvIOCtl(iCmd, &g_DevExt, pSession, pHdr, cbReq); + if (RT_LIKELY(!rc)) + { + /* + * If not buffered, copy back the buffer before returning. + */ + if (pUser) + { + IPRT_DARWIN_SAVE_EFL_AC(); + uint32_t cbOut = pHdr->cbOut; + if (cbOut > cbReq) + { + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, iCmd)); + cbOut = cbReq; + } + rc = copyout(pHdr, pUser, cbOut); + if (RT_UNLIKELY(rc)) + OSDBGPRINT(("VBoxDrvDarwinIOCtlSlow: copyout(%p,%llx,%#x) -> %d; uCmd=%#lx!\n", + pHdr, (unsigned long long)pUser, cbOut, rc, iCmd)); + + /* cleanup */ + if (pvPageBuf) + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + else + RTMemTmpFree(pHdr); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + } + else + { + /* + * The request failed, just clean up. + */ + if (pUser) + { + if (pvPageBuf) + { + IPRT_DARWIN_SAVE_EFL_AC(); + IOFreeAligned(pvPageBuf, RT_ALIGN_Z(cbReq, PAGE_SIZE)); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + RTMemTmpFree(pHdr); + } + + Log(("VBoxDrvDarwinIOCtlSlow: pid=%d iCmd=%lx pData=%p failed, rc=%d\n", proc_pid(pProcess), iCmd, (void *)pData, rc)); + rc = EINVAL; + } + + Log2(("VBoxDrvDarwinIOCtlSlow: returns %d\n", rc)); + return rc; +} + + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code, see supdrvIDC. + * @param uReq The request code. + * @param pReq The request. + */ +DECLEXPORT(int) VBOXCALL SUPDrvDarwinIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + PSUPDRVSESSION pSession; + + /* + * Some quick validations. + */ + if (RT_UNLIKELY(!RT_VALID_PTR(pReq))) + return VERR_INVALID_POINTER; + + pSession = pReq->pSession; + if (pSession) + { + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) + return VERR_INVALID_PARAMETER; + if (RT_UNLIKELY(pSession->pDevExt != &g_DevExt)) + return VERR_INVALID_PARAMETER; + } + else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT)) + return VERR_INVALID_PARAMETER; + + /* + * Do the job. + */ + return supdrvIDC(uReq, &g_DevExt, pSession, 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. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + +/** + * Callback for blah blah blah. + */ +IOReturn VBoxDrvDarwinSleepHandler(void * /* pvTarget */, void *pvRefCon, UInt32 uMessageType, + IOService *pProvider, void *pvMsgArg, vm_size_t cbMsgArg) +{ + RT_NOREF(pProvider, pvMsgArg, cbMsgArg); + LogFlow(("VBoxDrv: Got sleep/wake notice. Message type was %x\n", uMessageType)); + + if (uMessageType == kIOMessageSystemWillSleep) + RTPowerSignalEvent(RTPOWEREVENT_SUSPEND); + else if (uMessageType == kIOMessageSystemHasPoweredOn) + RTPowerSignalEvent(RTPOWEREVENT_RESUME); + + acknowledgeSleepWakeNotification(pvRefCon); + + return 0; +} + + +#ifdef VBOX_WITH_HOST_VMX +/** + * For cleaning up the mess we left behind on Yosemite with 4.3.28 and earlier. + * + * We ASSUME VT-x is supported by the CPU. + * + * @param idCpu Unused. + * @param pvUser1 Unused. + * @param pvUser2 Unused. + */ +static DECLCALLBACK(void) vboxdrvDarwinVmxEnableFix(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + RT_NOREF(idCpu, pvUser1, pvUser2); + RTCCUINTREG uCr4 = ASMGetCR4(); + if (!(uCr4 & X86_CR4_VMXE)) + { + uCr4 |= X86_CR4_VMXE; + ASMSetCR4(uCr4); + } +} +#endif + + +/** + * @copydoc SUPR0EnableVTx + */ +int VBOXCALL supdrvOSEnableVTx(bool fEnable) +{ +#ifdef VBOX_WITH_HOST_VMX + int rc; + if ( version_major >= 10 /* 10 = 10.6.x = Snow Leopard */ +# ifdef VBOX_WITH_RAW_MODE + && g_pfnVmxSuspend + && g_pfnVmxResume + && g_pVmxUseCount +# endif + ) + { + IPRT_DARWIN_SAVE_EFL_AC(); + if (fEnable) + { + /* + * We screwed up on Yosemite and didn't notice that we weren't + * calling host_vmxon. CR4.VMXE may therefore have been disabled + * by us. So, first time around we make sure it's set so we won't + * crash in the pre-4.3.28/5.0RC1 upgrade scenario. + * See @bugref{7907}. + */ + static bool volatile g_fDoneCleanup = false; + if (!g_fDoneCleanup) + { + if (version_major == 14 /* 14 = 10.10 = yosemite */) + { + uint32_t fCaps; + rc = supdrvQueryVTCapsInternal(&fCaps); + if (RT_SUCCESS(rc)) + { + if (fCaps & SUPVTCAPS_VT_X) + rc = RTMpOnAll(vboxdrvDarwinVmxEnableFix, NULL, NULL); + else + rc = VERR_VMX_NO_VMX; + } + if (RT_FAILURE(rc)) + { + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + } + g_fDoneCleanup = true; + } + + /* + * Call the kernel. + */ + AssertLogRelMsg(!g_pVmxUseCount || *g_pVmxUseCount >= 0, + ("vmx_use_count=%d (@ %p, expected it to be a positive number\n", + *g_pVmxUseCount, g_pVmxUseCount)); + + rc = host_vmxon(false /* exclusive */); + if (rc == VMX_OK) + rc = VINF_SUCCESS; + else if (rc == VMX_UNSUPPORTED) + rc = VERR_VMX_NO_VMX; + else if (rc == VMX_INUSE) + rc = VERR_VMX_IN_VMX_ROOT_MODE; + else /* shouldn't happen, but just in case. */ + { + LogRel(("host_vmxon returned %d\n", rc)); + rc = VERR_UNRESOLVED_ERROR; + } + LogRel(("VBoxDrv: host_vmxon -> vmx_use_count=%d rc=%Rrc\n", *g_pVmxUseCount, rc)); + } + else + { + AssertLogRelMsgReturn(!g_pVmxUseCount || *g_pVmxUseCount >= 1, + ("vmx_use_count=%d (@ %p, expected it to be a non-zero positive number\n", + *g_pVmxUseCount, g_pVmxUseCount), + VERR_WRONG_ORDER); + host_vmxoff(); + rc = VINF_SUCCESS; + LogRel(("VBoxDrv: host_vmxoff -> vmx_use_count=%d\n", *g_pVmxUseCount)); + } + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + { + /* In 10.5.x the host_vmxon is severely broken! Don't use it, it will + frequnetly panic the host. */ + rc = VERR_NOT_SUPPORTED; + } + return rc; +#else + return VERR_NOT_SUPPORTED; +#endif +} + + +/** + * @copydoc SUPR0SuspendVTxOnCpu + */ +bool VBOXCALL supdrvOSSuspendVTxOnCpu(void) +{ +#ifdef VBOX_WITH_HOST_VMX + /* + * Consult the VMX usage counter, don't try suspend if not enabled. + * + * Note! The host_vmxon/off code is still race prone since, but this is + * currently the best we can do without always enable VMX when + * loading the driver. + */ + if ( g_pVmxUseCount + && *g_pVmxUseCount > 0) + { + IPRT_DARWIN_SAVE_EFL_AC(); + g_pfnVmxSuspend(); + IPRT_DARWIN_RESTORE_EFL_AC(); + return true; + } + return false; +#else + return false; +#endif +} + + +/** + * @copydoc SUPR0ResumeVTxOnCpu + */ +void VBOXCALL supdrvOSResumeVTxOnCpu(bool fSuspended) +{ +#ifdef VBOX_WITH_HOST_VMX + /* + * Don't consult the counter here, the state knows better. + * We're executing with interrupts disabled and anyone racing us with + * disabling VT-x will be waiting in the rendezvous code. + */ + if ( fSuspended + && g_pfnVmxResume) + { + IPRT_DARWIN_SAVE_EFL_AC(); + g_pfnVmxResume(); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + Assert(!fSuspended); +#else + Assert(!fSuspended); +#endif +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + NOREF(pDevExt); + return false; +} + + +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + /** @todo verify this. */ + return false; +} + + +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + return false; +} + + +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + +/** + * @callback_method_impl{FNRTLDRIMPORT} + */ +static DECLCALLBACK(int) supdrvDarwinLdrOpenImportCallback(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, + unsigned uSymbol, PRTLDRADDR pValue, void *pvUser) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + + /* + * First consult the VMMR0 module if there is one fully loaded. + * This is necessary as VMMR0 may overload assertion and logger symbols. + */ + if (pDevExt->pvVMMR0) + for (PSUPDRVLDRIMAGE pImage = pDevExt->pLdrImages; pImage; pImage = pImage->pNext) + if (pImage->pvImage == pDevExt->pvVMMR0) + { + if ( pImage->uState == SUP_IOCTL_LDR_LOAD + && pImage->hLdrMod != NIL_RTLDRMOD) + { + int rc = RTLdrGetSymbolEx(pImage->hLdrMod, pImage->pvImage, (uintptr_t)pImage->pvImage, + UINT32_MAX, pszSymbol, pValue); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + } + break; + } + + /* + * Then we consult the SUPDrv export table. + */ + uintptr_t uValue = 0; + int rc = supdrvLdrGetExportedSymbol(pszSymbol, &uValue); + if (RT_SUCCESS(rc)) + { + *pValue = uValue; + return VINF_SUCCESS; + } + + /* + * Failed. + */ + printf("VBoxDrv: Unable to resolve symbol '%s'.\n", pszSymbol); + RT_NOREF(hLdrMod, pszModule, uSymbol); + return VERR_SYMBOL_NOT_FOUND; +} + + +/** + * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK, + * Verify that the signing certificate is sane.} + */ +static DECLCALLBACK(int) supdrvDarwinLdrOpenVerifyCertificatCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, + uint32_t fFlags, void *pvUser, PRTERRINFO pErrInfo) +{ + RT_NOREF(pvUser); //PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; +# ifdef DEBUG_bird + printf("supdrvDarwinLdrOpenVerifyCertificatCallback: pCert=%p hCertPaths=%p\n", (void *)pCert, (void *)hCertPaths); +# endif + +# if 0 + /* + * Test signing certificates normally doesn't have all the necessary + * features required below. So, treat them as special cases. + */ + if ( hCertPaths == NIL_RTCRX509CERTPATHS + && RTCrX509Name_Compare(&pCert->TbsCertificate.Issuer, &pCert->TbsCertificate.Subject) == 0) + { + RTMsgInfo("Test signed.\n"); + return VINF_SUCCESS; + } +# endif + + /* + * Standard code signing capabilites required. + */ + int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo); + if ( RT_SUCCESS(rc) + && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)) + { + uint32_t cDevIdApp = 0; + uint32_t cDevIdKext = 0; + uint32_t cDevIdMacDev = 0; + for (uint32_t i = 0; i < pCert->TbsCertificate.T3.Extensions.cItems; i++) + { + PCRTCRX509EXTENSION pExt = pCert->TbsCertificate.T3.Extensions.papItems[i]; + if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) == 0) + { + cDevIdApp++; + if (!pExt->Critical.fValue) + rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "Dev ID Application certificate extension is not flagged critical"); + } + else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) == 0) + { + cDevIdKext++; + if (!pExt->Critical.fValue) + rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "Dev ID kext certificate extension is not flagged critical"); + } + else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_MAC_SW_DEV_OID) == 0) + { + cDevIdMacDev++; + if (!pExt->Critical.fValue) + rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "Dev ID MAC SW dev certificate extension is not flagged critical"); + } + } +# ifdef VBOX_WITH_DARWIN_R0_TEST_SIGN + /* + * Mac application software development certs do not have the usually required extensions. + */ + if (cDevIdMacDev) + { + cDevIdApp++; + cDevIdKext++; + } +# endif + if (cDevIdApp == 0) + rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "Certificate is missing the 'Dev ID Application' extension"); + if (cDevIdKext == 0) + rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "Certificate is missing the 'Dev ID kext' extension"); + } + + return rc; +} + + +/** + * @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} + */ +static DECLCALLBACK(int) supdrvDarwinLdrOpenVerifyCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, + PRTERRINFO pErrInfo, void *pvUser) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pvUser; + RT_NOREF_PV(hLdrMod); + + switch (pInfo->enmType) + { + case RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA: + if (pInfo->pvExternalData) + { + PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->pvSignature; + RTTIMESPEC ValidationTime; + RTTimeNow(&ValidationTime); + + return RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo, + RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY + | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT + | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT, + pDevExt->hAdditionalStore, pDevExt->hRootStore, &ValidationTime, + supdrvDarwinLdrOpenVerifyCertificatCallback, pDevExt, + pInfo->pvExternalData, pInfo->cbExternalData, pErrInfo); + } + return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Expected external data with signature!"); + + default: + return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported signature type: %d", pInfo->enmType); + } +} + +#endif /* VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION */ + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + /* + * Initialize our members. + */ + pImage->hLdrMod = NIL_RTLDRMOD; + pImage->hMemAlloc = NIL_RTR0MEMOBJ; + + /* + * We have to double buffer the file to be avoid a potential race between + * validation and actual image loading. This could be eliminated later by + * baking the image validation into the RTLdrGetBits operation. + * + * Note! After calling RTLdrOpenInMemory, pvFile is owned by the loader and will be + * freed via the RTFileReadAllFree callback when the loader module is closed. + */ + void *pvFile = NULL; + size_t cbFile = 0; + int rc = RTFileReadAllEx(pszFilename, 0, _32M, RTFILE_RDALL_O_DENY_WRITE, &pvFile, &cbFile); + if (RT_SUCCESS(rc)) + { + PRTERRINFOSTATIC pErrInfo = (PRTERRINFOSTATIC)RTMemTmpAlloc(sizeof(RTERRINFOSTATIC)); + RTLDRMOD hLdrMod = NIL_RTLDRMOD; + rc = RTLdrOpenInMemory(pszFilename, 0 /*fFlags*/, RTLDRARCH_HOST, cbFile, + NULL /*pfnRead*/, RTFileReadAllFree, pvFile, + &hLdrMod, pErrInfo ? RTErrInfoInitStatic(pErrInfo) : NULL); + if (RT_SUCCESS(rc)) + { + /* + * Validate the image. + */ + rc = RTLdrVerifySignature(hLdrMod, supdrvDarwinLdrOpenVerifyCallback, pDevExt, + pErrInfo ? RTErrInfoInitStatic(pErrInfo) : NULL); + if (RT_SUCCESS(rc)) + { + /* + * Allocate memory for the object and load it into it. + */ + size_t cbImage = RTLdrSize(hLdrMod); + if (cbImage == pImage->cbImageBits) + { + RTR0MEMOBJ hMemAlloc; + rc = RTR0MemObjAllocPage(&hMemAlloc, cbImage, true /*fExecutable*/); + if (RT_SUCCESS(rc)) + { + void *pvImageBits = RTR0MemObjAddress(hMemAlloc); + rc = RTLdrGetBits(hLdrMod, pvImageBits, (uintptr_t)pvImageBits, + supdrvDarwinLdrOpenImportCallback, pDevExt); + if (RT_SUCCESS(rc)) + { + /* + * Commit. + */ + pImage->hMemAlloc = hMemAlloc; + pImage->hLdrMod = hLdrMod; + pImage->pvImage = pvImageBits; + RTMemTmpFree(pErrInfo); + /** @todo Call RTLdrDone. */ + kprintf("VBoxDrv: Loaded %s at %p\n", pImage->szName, pvImageBits); + return VINF_SUCCESS; + } + + RTR0MemObjFree(hMemAlloc, true /*fFreeMappings*/); + } + else + printf("VBoxDrv: Failed to allocate %u bytes for %s: %d\n", (unsigned)cbImage, pszFilename, rc); + } + else + { + printf("VBoxDrv: Image size mismatch for %s: %#x, ring-3 says %#x\n", + pszFilename, (unsigned)cbImage, (unsigned)pImage->cbImageBits); + rc = VERR_LDR_MISMATCH_NATIVE; + } + } + else if (pErrInfo && RTErrInfoIsSet(&pErrInfo->Core)) + printf("VBoxDrv: RTLdrVerifySignature(%s) failed: %d - %s\n", pszFilename, rc, pErrInfo->Core.pszMsg); + else + printf("VBoxDrv: RTLdrVerifySignature(%s) failed: %d\n", pszFilename, rc); + RTLdrClose(hLdrMod); + } + else if (pErrInfo && RTErrInfoIsSet(&pErrInfo->Core)) + printf("VBoxDrv: RTLdrOpenInMemory(%s) failed: %d - %s\n", pszFilename, rc, pErrInfo->Core.pszMsg); + else + printf("VBoxDrv: RTLdrOpenInMemory(%s) failed: %d\n", pszFilename, rc); + RTMemTmpFree(pErrInfo); + } + return rc; +#else /* !VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION */ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); + return VERR_NOT_SUPPORTED; +#endif /* !VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION */ +} + + +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION +/** + * @callback_method_impl{FNRTLDRENUMSYMS, + * Worker for supdrvOSLdrValidatePointer. + */ +static DECLCALLBACK(int) supdrvDarwinLdrValidatePointerCallback(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol, + RTLDRADDR Value, void *pvUser) +{ + RT_NOREF(hLdrMod, pszSymbol, uSymbol); + if (Value == (uintptr_t)pvUser) + return VINF_CALLBACK_RETURN; + return VINF_SUCCESS; +} +#endif + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + AssertReturn(pImage->hLdrMod != NIL_RTLDRMOD, VERR_INVALID_STATE); + + /* + * If we've got a symbol name, just to a lookup and compare addresses. + */ + int rc; + if (RT_C_IS_UPPER(*pszSymbol)) + { + RTLDRADDR uValueFound; + rc = RTLdrGetSymbolEx(pImage->hLdrMod, pImage->pvImage, (uintptr_t)pImage->pvImage, UINT32_MAX, pszSymbol, &uValueFound); + if (RT_SUCCESS(rc)) + { + if (uValueFound == (uintptr_t)pv) + rc = VINF_SUCCESS; + else + { + SUPR0Printf("SUPDrv: Different exports found for %s in %s: %RTptr, expected %p\n", + pszSymbol, pImage->szName, (RTUINTPTR)uValueFound, pv); + rc = VERR_LDR_BAD_FIXUP; + } + } + else + SUPR0Printf("SUPDrv: No export named %s (%p) in %s!\n", pszSymbol, pv, pImage->szName); + } + /* + * Otherwise do a symbol enumeration and look for the entrypoint. + */ + else + { + rc = RTLdrEnumSymbols(pImage->hLdrMod, 0 /*fFlags*/, pImage->pvImage, (uintptr_t)pImage->pvImage, + supdrvDarwinLdrValidatePointerCallback, pv); + if (rc == VINF_CALLBACK_RETURN) + rc = VINF_SUCCESS; + else if (RT_SUCCESS(rc)) + { + SUPR0Printf("SUPDrv: No export with address %p (%s) in %s!\n", pv, pszSymbol, pImage->szName); + rc = VERR_NOT_FOUND; + } + else + SUPR0Printf("SUPDrv: RTLdrEnumSymbols failed on %s: %Rrc\n", pImage->szName, rc); + } + RT_NOREF(pDevExt, pbImageBits); + return rc; +#else + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + return VERR_NOT_SUPPORTED; +#endif +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + /* + * Just hand the problem to RTLdrGetSymbolEx. + */ + RTLDRADDR uValueFound; + int rc = RTLdrGetSymbolEx(pImage->hLdrMod, pImage->pvImage, (uintptr_t)pImage->pvImage, UINT32_MAX, pszSymbol, &uValueFound); + if (RT_SUCCESS(rc)) + { + *ppvSymbol = (void *)(uintptr_t)uValueFound; + return VINF_SUCCESS; + } + RT_NOREF(pDevExt, cchSymbol); + return rc; + +#else + RT_NOREF(pDevExt, pImage, pszSymbol, cchSymbol, ppvSymbol); + return VERR_WRONG_ORDER; +#endif +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + /* State paranoia. */ + AssertReturn(pImage->hLdrMod != NIL_RTLDRMOD, VERR_INVALID_STATE); + AssertReturn(pImage->hMemAlloc != NIL_RTR0MEMOBJ, VERR_INVALID_STATE); + AssertReturn(pImage->pvImage, VERR_INVALID_STATE); + + /* + * We should get an identical match with ring-3 here, so the code here is + * trivial in comparision to SUPDrv-win.cpp. + */ + if (!memcmp(pImage->pvImage, pbImageBits, pImage->cbImageBits)) + return VINF_SUCCESS; + + /* + * Try show what when wrong (code is copied from supdrvNtCompare). + */ + uint32_t cbLeft = pImage->cbImageBits; + const uint8_t *pbNativeBits = (const uint8_t *)pImage->pvImage; + for (size_t off = 0; cbLeft > 0; off++, cbLeft--) + if (pbNativeBits[off] != pbImageBits[off]) + { + /* Note! We need to copy image bits into a temporary stack buffer here as we'd + otherwise risk overwriting them while formatting the error message. */ + uint8_t abBytes[64]; + memcpy(abBytes, &pbImageBits[off], RT_MIN(64, cbLeft)); + supdrvLdrLoadError(VERR_LDR_MISMATCH_NATIVE, pReq, + "Mismatch at %#x (%p) of %s loaded at %p:\n" + "ring-0: %.*Rhxs\n" + "ring-3: %.*Rhxs", + off, &pbNativeBits[off], pImage->szName, pImage->pvImage, + RT_MIN(64, cbLeft), &pbNativeBits[off], + RT_MIN(64, cbLeft), &abBytes[0]); + printf("SUPDrv: %s\n", pReq->u.Out.szError); + break; + } + + RT_NOREF(pDevExt); + return VERR_LDR_MISMATCH_NATIVE; + +#else + NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq); + return VERR_NOT_SUPPORTED; +#endif +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ +#ifdef VBOX_WITH_DARWIN_R0_DARWIN_IMAGE_VERIFICATION + if (pImage->hLdrMod != NIL_RTLDRMOD) + { + int rc = RTLdrClose(pImage->hLdrMod); + AssertRC(rc); + pImage->hLdrMod = NIL_RTLDRMOD; + } + if (pImage->hMemAlloc != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pImage->hMemAlloc, true /*fFreeMappings*/); + pImage->hMemAlloc = NIL_RTR0MEMOBJ; + } + NOREF(pDevExt); +#else + NOREF(pDevExt); NOREF(pImage); +#endif +} + + +void VBOXCALL supdrvOSLdrNotifyLoaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ +#if 1 + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +#else + /* + * Try store the image load address in NVRAM so we can retrived it on panic. + * Note! This only works if you're root! - Acutally, it doesn't work at all at the moment. FIXME! + */ + IORegistryEntry *pEntry = IORegistryEntry::fromPath("/options", gIODTPlane); + if (pEntry) + { + char szVar[80]; + RTStrPrintf(szVar, sizeof(szVar), "vboximage"/*-%s*/, pImage->szName); + char szValue[48]; + RTStrPrintf(szValue, sizeof(szValue), "%#llx,%#llx", (uint64_t)(uintptr_t)pImage->pvImage, + (uint64_t)(uintptr_t)pImage->pvImage + pImage->cbImageBits - 1); + bool fRc = pEntry->setProperty(szVar, szValue); NOREF(fRc); + pEntry->release(); + SUPR0Printf("fRc=%d '%s'='%s'\n", fRc, szVar, szValue); + } + /*else + SUPR0Printf("failed to find /options in gIODTPlane\n");*/ +#endif +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +#ifdef SUPDRV_WITH_MSR_PROBER + +typedef struct SUPDRVDARWINMSRARGS +{ + RTUINT64U uValue; + uint32_t uMsr; + int rc; +} SUPDRVDARWINMSRARGS, *PSUPDRVDARWINMSRARGS; + +/** + * On CPU worker for supdrvOSMsrProberRead. + * + * @param idCpu Ignored. + * @param pvUser1 Pointer to a SUPDRVDARWINMSRARGS. + * @param pvUser2 Ignored. + */ +static DECLCALLBACK(void) supdrvDarwinMsrProberReadOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPDRVDARWINMSRARGS pArgs = (PSUPDRVDARWINMSRARGS)pvUser1; + if (g_pfnRdMsr64Carefully) + pArgs->rc = g_pfnRdMsr64Carefully(pArgs->uMsr, &pArgs->uValue.u); + else if (g_pfnRdMsrCarefully) + pArgs->rc = g_pfnRdMsrCarefully(pArgs->uMsr, &pArgs->uValue.s.Lo, &pArgs->uValue.s.Hi); + else + pArgs->rc = 2; + NOREF(idCpu); NOREF(pvUser2); +} + + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + if (!g_pfnRdMsr64Carefully && !g_pfnRdMsrCarefully) + return VERR_NOT_SUPPORTED; + + SUPDRVDARWINMSRARGS Args; + Args.uMsr = uMsr; + Args.uValue.u = 0; + Args.rc = -1; + + if (idCpu == NIL_RTCPUID) + { + IPRT_DARWIN_SAVE_EFL_AC(); + supdrvDarwinMsrProberReadOnCpu(idCpu, &Args, NULL); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + { + int rc = RTMpOnSpecific(idCpu, supdrvDarwinMsrProberReadOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.rc) + return VERR_ACCESS_DENIED; + *puValue = Args.uValue.u; + return VINF_SUCCESS; +} + + +/** + * On CPU worker for supdrvOSMsrProberWrite. + * + * @param idCpu Ignored. + * @param pvUser1 Pointer to a SUPDRVDARWINMSRARGS. + * @param pvUser2 Ignored. + */ +static DECLCALLBACK(void) supdrvDarwinMsrProberWriteOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPDRVDARWINMSRARGS pArgs = (PSUPDRVDARWINMSRARGS)pvUser1; + if (g_pfnWrMsr64Carefully) + pArgs->rc = g_pfnWrMsr64Carefully(pArgs->uMsr, pArgs->uValue.u); + else + pArgs->rc = 2; + NOREF(idCpu); NOREF(pvUser2); +} + + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + if (!g_pfnWrMsr64Carefully) + return VERR_NOT_SUPPORTED; + + SUPDRVDARWINMSRARGS Args; + Args.uMsr = uMsr; + Args.uValue.u = uValue; + Args.rc = -1; + + if (idCpu == NIL_RTCPUID) + { + IPRT_DARWIN_SAVE_EFL_AC(); + supdrvDarwinMsrProberWriteOnCpu(idCpu, &Args, NULL); + IPRT_DARWIN_RESTORE_EFL_AC(); + } + else + { + int rc = RTMpOnSpecific(idCpu, supdrvDarwinMsrProberWriteOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.rc) + return VERR_ACCESS_DENIED; + return VINF_SUCCESS; +} + + +/** + * Worker for supdrvOSMsrProberModify. + */ +static DECLCALLBACK(void) supdrvDarwinMsrProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + RT_NOREF(idCpu, pvUser2); + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; + register uint32_t uMsr = pReq->u.In.uMsr; + bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; + uint64_t uBefore; + uint64_t uWritten; + uint64_t uAfter; + int rcBefore, rcWrite, rcAfter, rcRestore; + RTCCUINTREG fOldFlags; + + /* Initialize result variables. */ + uBefore = uWritten = uAfter = 0; + rcWrite = rcAfter = rcRestore = -1; + + /* + * Do the job. + */ + fOldFlags = ASMIntDisableFlags(); + ASMCompilerBarrier(); /* paranoia */ + if (!fFaster) + ASMWriteBackAndInvalidateCaches(); + + rcBefore = g_pfnRdMsr64Carefully(uMsr, &uBefore); + if (rcBefore >= 0) + { + register uint64_t uRestore = uBefore; + uWritten = uRestore; + uWritten &= pReq->u.In.uArgs.Modify.fAndMask; + uWritten |= pReq->u.In.uArgs.Modify.fOrMask; + + rcWrite = g_pfnWrMsr64Carefully(uMsr, uWritten); + rcAfter = g_pfnRdMsr64Carefully(uMsr, &uAfter); + rcRestore = g_pfnWrMsr64Carefully(uMsr, uRestore); + + if (!fFaster) + { + ASMWriteBackAndInvalidateCaches(); + ASMReloadCR3(); + ASMNopPause(); + } + } + + ASMCompilerBarrier(); /* paranoia */ + ASMSetFlags(fOldFlags); + + /* + * Write out the results. + */ + pReq->u.Out.uResults.Modify.uBefore = uBefore; + pReq->u.Out.uResults.Modify.uWritten = uWritten; + pReq->u.Out.uResults.Modify.uAfter = uAfter; + pReq->u.Out.uResults.Modify.fBeforeGp = rcBefore != 0; + pReq->u.Out.uResults.Modify.fModifyGp = rcWrite != 0; + pReq->u.Out.uResults.Modify.fAfterGp = rcAfter != 0; + pReq->u.Out.uResults.Modify.fRestoreGp = rcRestore != 0; + RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + if (!g_pfnWrMsr64Carefully || !g_pfnRdMsr64Carefully) + return VERR_NOT_SUPPORTED; + if (idCpu == NIL_RTCPUID) + { + IPRT_DARWIN_SAVE_EFL_AC(); + supdrvDarwinMsrProberModifyOnCpu(idCpu, pReq, NULL); + IPRT_DARWIN_RESTORE_EFL_AC(); + return VINF_SUCCESS; + } + return RTMpOnSpecific(idCpu, supdrvDarwinMsrProberModifyOnCpu, pReq, NULL); +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + +/** + * Resume Bluetooth keyboard. + * If there is no Bluetooth keyboard device connected to the system we just ignore this. + */ +static void supdrvDarwinResumeBluetoothKbd(void) +{ + OSDictionary *pDictionary = IOService::serviceMatching("AppleBluetoothHIDKeyboard"); + if (pDictionary) + { + OSIterator *pIter; + IOBluetoothHIDDriver *pDriver; + + pIter = IOService::getMatchingServices(pDictionary); + if (pIter) + { + while ((pDriver = (IOBluetoothHIDDriver *)pIter->getNextObject())) + if (pDriver->isKeyboard()) + (void)pDriver->hidControl(IOBTHID_CONTROL_EXIT_SUSPEND); + + pIter->release(); + } + pDictionary->release(); + } +} + +/** + * Resume built-in keyboard on MacBook Air and Pro hosts. + * If there is no built-in keyboard device attached to the system we just ignore this. + */ +static void supdrvDarwinResumeBuiltinKbd(void) +{ + /** @todo macbook pro 16 w/ 10.15.5 as the "Apple Internal Keyboard / + * Trackpad" hooked up to "HID Relay" / "AppleUserUSBHostHIDDevice" + * and "AppleUserUSBHostHIDDevice" among other things, but not + * "AppleUSBTCKeyboard". This change is probably older than 10.15, + * given that IOUSBHIDDriver not is present in the 10.11 SDK. */ +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 + /* + * AppleUSBTCKeyboard KEXT is responsible for built-in keyboard management. + * We resume keyboard by accessing to its IOService. + */ + OSDictionary *pDictionary = IOService::serviceMatching("AppleUSBTCKeyboard"); + if (pDictionary) + { + OSIterator *pIter; + IOUSBHIDDriver *pDriver; + + pIter = IOService::getMatchingServices(pDictionary); + if (pIter) + { + while ((pDriver = (IOUSBHIDDriver *)pIter->getNextObject())) + if (pDriver->IsPortSuspended()) + pDriver->SuspendPort(false, 0); + + pIter->release(); + } + pDictionary->release(); + } +#endif +} + + +/** + * Resume suspended keyboard devices (if any). + */ +int VBOXCALL supdrvDarwinResumeSuspendedKbds(void) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + supdrvDarwinResumeBuiltinKbd(); + supdrvDarwinResumeBluetoothKbd(); + IPRT_DARWIN_RESTORE_EFL_AC(); + return 0; +} + + +/** + * Converts an IPRT error code to a darwin error code. + * + * @returns corresponding darwin error code. + * @param rc IPRT status code. + */ +static int VBoxDrvDarwinErr2DarwinErr(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return 0; + case VERR_GENERAL_FAILURE: return EACCES; + case VERR_INVALID_PARAMETER: return EINVAL; + case VERR_INVALID_MAGIC: return EILSEQ; + case VERR_INVALID_HANDLE: return ENXIO; + case VERR_INVALID_POINTER: return EFAULT; + case VERR_LOCK_FAILED: return ENOLCK; + case VERR_ALREADY_LOADED: return EEXIST; + case VERR_PERMISSION_DENIED: return EPERM; + case VERR_VERSION_MISMATCH: return ENOSYS; + } + + return EPERM; +} + + +/** + * Check if the CPU has SMAP support. + */ +static bool vboxdrvDarwinCpuHasSMAP(void) +{ + uint32_t uMaxId, uEAX, uEBX, uECX, uEDX; + ASMCpuId(0, &uMaxId, &uEBX, &uECX, &uEDX); + if ( RTX86IsValidStdRange(uMaxId) + && uMaxId >= 0x00000007) + { + ASMCpuId_Idx_ECX(0x00000007, 0, &uEAX, &uEBX, &uECX, &uEDX); + if (uEBX & X86_CPUID_STEXT_FEATURE_EBX_SMAP) + return true; + } +#ifdef VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV + return true; +#else + return false; +#endif +} + + +RTDECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + + char szMsg[512]; + RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + printf("%s", szMsg); + kprintf("%s", szMsg); + + IPRT_DARWIN_RESTORE_EFL_AC(); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return g_fKernelFeatures; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + +/* + * + * org_virtualbox_SupDrv + * + * - IOService diff resync - + * - IOService diff resync - + * - IOService diff resync - + * + */ + + +/** + * Initialize the object. + */ +bool org_virtualbox_SupDrv::init(OSDictionary *pDictionary) +{ + LogFlow(("IOService::init([%p], %p)\n", this, pDictionary)); + if (IOService::init(pDictionary)) + { + /* init members. */ + return true; + } + return false; +} + + +/** + * Free the object. + */ +void org_virtualbox_SupDrv::free(void) +{ + LogFlow(("IOService::free([%p])\n", this)); + IOService::free(); +} + + +/** + * Check if it's ok to start this service. + * It's always ok by us, so it's up to IOService to decide really. + */ +IOService *org_virtualbox_SupDrv::probe(IOService *pProvider, SInt32 *pi32Score) +{ + LogFlow(("IOService::probe([%p])\n", this)); + return IOService::probe(pProvider, pi32Score); +} + + +/** + * Start this service. + */ +bool org_virtualbox_SupDrv::start(IOService *pProvider) +{ + LogFlow(("org_virtualbox_SupDrv::start([%p])\n", this)); + + if (IOService::start(pProvider)) + { + /* register the service. */ + registerService(); + return true; + } + return false; +} + + +/** + * Stop this service. + */ +void org_virtualbox_SupDrv::stop(IOService *pProvider) +{ + LogFlow(("org_virtualbox_SupDrv::stop([%p], %p)\n", this, pProvider)); + IOService::stop(pProvider); +} + + +/** + * Termination request. + * + * @return true if we're ok with shutting down now, false if we're not. + * @param fOptions Flags. + */ +bool org_virtualbox_SupDrv::terminate(IOOptionBits fOptions) +{ + bool fRc; + LogFlow(("org_virtualbox_SupDrv::terminate: reference_count=%d g_cSessions=%d (fOptions=%#x)\n", + KMOD_INFO_NAME.reference_count, ASMAtomicUoReadS32(&g_cSessions), fOptions)); + if ( KMOD_INFO_NAME.reference_count != 0 + || ASMAtomicUoReadS32(&g_cSessions)) + fRc = false; + else + fRc = IOService::terminate(fOptions); + LogFlow(("org_virtualbox_SupDrv::terminate: returns %d\n", fRc)); + return fRc; +} + + +/* + * + * org_virtualbox_SupDrvClient + * + */ + + +/** + * Initializer called when the client opens the service. + */ +bool org_virtualbox_SupDrvClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type) +{ + LogFlow(("org_virtualbox_SupDrvClient::initWithTask([%p], %#x, %p, %#x) (cur pid=%d proc=%p)\n", + this, OwningTask, pvSecurityId, u32Type, RTProcSelf(), RTR0ProcHandleSelf())); + AssertMsg((RTR0PROCESS)OwningTask == RTR0ProcHandleSelf(), ("%p %p\n", OwningTask, RTR0ProcHandleSelf())); + + if (!OwningTask) + return false; + + if (u32Type != SUP_DARWIN_IOSERVICE_COOKIE) + { + VBOX_RETRIEVE_CUR_PROC_NAME(szProcName); + LogRelMax(10,("org_virtualbox_SupDrvClient::initWithTask: Bad cookie %#x (%s)\n", u32Type, szProcName)); + return false; + } + + if (IOUserClient::initWithTask(OwningTask, pvSecurityId , u32Type)) + { + /* + * In theory we have to call task_reference() to make sure that the task is + * valid during the lifetime of this object. The pointer is only used to check + * for the context this object is called in though and never dereferenced + * or passed to anything which might, so we just skip this step. + */ + m_Task = OwningTask; + m_pSession = NULL; + m_pProvider = NULL; + return true; + } + return false; +} + + +/** + * Start the client service. + */ +bool org_virtualbox_SupDrvClient::start(IOService *pProvider) +{ + LogFlow(("org_virtualbox_SupDrvClient::start([%p], %p) (cur pid=%d proc=%p)\n", + this, pProvider, RTProcSelf(), RTR0ProcHandleSelf() )); + AssertMsgReturn((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(), + ("%p %p\n", m_Task, RTR0ProcHandleSelf()), + false); + + if (IOUserClient::start(pProvider)) + { + m_pProvider = OSDynamicCast(org_virtualbox_SupDrv, pProvider); + if (m_pProvider) + { + Assert(!m_pSession); + + /* + * Create a new session. + */ + int rc = supdrvCreateSession(&g_DevExt, true /* fUser */, false /*fUnrestricted*/, &m_pSession); + if (RT_SUCCESS(rc)) + { + m_pSession->fOpened = false; + /* The Uid, Gid and fUnrestricted fields are set on open. */ + + /* + * Insert it into the hash table, checking that there isn't + * already one for this process first. (One session per proc!) + */ + unsigned iHash = SESSION_HASH(m_pSession->Process); + RTSpinlockAcquire(g_Spinlock); + + PSUPDRVSESSION pCur = g_apSessionHashTab[iHash]; + while (pCur && pCur->Process != m_pSession->Process) + pCur = pCur->pNextHash; + if (!pCur) + { + m_pSession->pNextHash = g_apSessionHashTab[iHash]; + g_apSessionHashTab[iHash] = m_pSession; + m_pSession->pvSupDrvClient = this; + ASMAtomicIncS32(&g_cSessions); + rc = VINF_SUCCESS; + } + else + rc = VERR_ALREADY_LOADED; + + RTSpinlockRelease(g_Spinlock); + if (RT_SUCCESS(rc)) + { + Log(("org_virtualbox_SupDrvClient::start: created session %p for pid %d\n", m_pSession, (int)RTProcSelf())); + return true; + } + + LogFlow(("org_virtualbox_SupDrvClient::start: already got a session for this process (%p)\n", pCur)); + supdrvSessionRelease(m_pSession); + } + + m_pSession = NULL; + LogFlow(("org_virtualbox_SupDrvClient::start: rc=%Rrc from supdrvCreateSession\n", rc)); + } + else + LogFlow(("org_virtualbox_SupDrvClient::start: %p isn't org_virtualbox_SupDrv\n", pProvider)); + } + return false; +} + + +/** + * Common worker for clientClose and VBoxDrvDarwinClose. + */ +/* static */ void org_virtualbox_SupDrvClient::sessionClose(RTPROCESS Process) +{ + /* + * Find the session and remove it from the hash table. + * + * Note! Only one session per process. (Both start() and + * VBoxDrvDarwinOpen makes sure this is so.) + */ + const unsigned iHash = SESSION_HASH(Process); + RTSpinlockAcquire(g_Spinlock); + PSUPDRVSESSION pSession = g_apSessionHashTab[iHash]; + if (pSession) + { + if (pSession->Process == Process) + { + g_apSessionHashTab[iHash] = pSession->pNextHash; + pSession->pNextHash = NULL; + ASMAtomicDecS32(&g_cSessions); + } + else + { + PSUPDRVSESSION pPrev = pSession; + pSession = pSession->pNextHash; + while (pSession) + { + if (pSession->Process == Process) + { + pPrev->pNextHash = pSession->pNextHash; + pSession->pNextHash = NULL; + ASMAtomicDecS32(&g_cSessions); + break; + } + + /* next */ + pPrev = pSession; + pSession = pSession->pNextHash; + } + } + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + Log(("SupDrvClient::sessionClose: pSession == NULL, pid=%d; freed already?\n", (int)Process)); + return; + } + + /* + * Remove it from the client object. + */ + org_virtualbox_SupDrvClient *pThis = (org_virtualbox_SupDrvClient *)pSession->pvSupDrvClient; + pSession->pvSupDrvClient = NULL; + if (pThis) + { + Assert(pThis->m_pSession == pSession); + pThis->m_pSession = NULL; + } + + /* + * Close the session. + */ + supdrvSessionRelease(pSession); +} + + +/** + * Client exits normally. + */ +IOReturn org_virtualbox_SupDrvClient::clientClose(void) +{ + LogFlow(("org_virtualbox_SupDrvClient::clientClose([%p]) (cur pid=%d proc=%p)\n", this, RTProcSelf(), RTR0ProcHandleSelf())); + AssertMsg((RTR0PROCESS)m_Task == RTR0ProcHandleSelf(), ("%p %p\n", m_Task, RTR0ProcHandleSelf())); + + /* + * Clean up the session if it's still around. + * + * We cannot rely 100% on close, and in the case of a dead client + * we'll end up hanging inside vm_map_remove() if we postpone it. + */ + if (m_pSession) + { + sessionClose(RTProcSelf()); + Assert(!m_pSession); + } + + m_pProvider = NULL; + terminate(); + + return kIOReturnSuccess; +} + + +/** + * The client exits abnormally / forgets to do cleanups. (logging) + */ +IOReturn org_virtualbox_SupDrvClient::clientDied(void) +{ + LogFlow(("IOService::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n", this, m_Task, RTR0ProcHandleSelf(), RTProcSelf())); + + /* IOUserClient::clientDied() calls clientClose, so we'll just do the work there. */ + return IOUserClient::clientDied(); +} + + +/** + * Terminate the service (initiate the destruction). (logging) + */ +bool org_virtualbox_SupDrvClient::terminate(IOOptionBits fOptions) +{ + LogFlow(("IOService::terminate([%p], %#x)\n", this, fOptions)); + return IOUserClient::terminate(fOptions); +} + + +/** + * The final stage of the client service destruction. (logging) + */ +bool org_virtualbox_SupDrvClient::finalize(IOOptionBits fOptions) +{ + LogFlow(("IOService::finalize([%p], %#x)\n", this, fOptions)); + return IOUserClient::finalize(fOptions); +} + + +/** + * Stop the client service. (logging) + */ +void org_virtualbox_SupDrvClient::stop(IOService *pProvider) +{ + LogFlow(("IOService::stop([%p])\n", this)); + IOUserClient::stop(pProvider); +} + diff --git a/src/VBox/HostDrivers/Support/darwin/SUPLib-darwin.cpp b/src/VBox/HostDrivers/Support/darwin/SUPLib-darwin.cpp new file mode 100644 index 00000000..77f1db79 --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPLib-darwin.cpp @@ -0,0 +1,333 @@ +/* $Id: SUPLib-darwin.cpp $ */ +/** @file + * VirtualBox Support Library - Darwin specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# ifndef LOG_DISABLED +# define LOG_DISABLED +# endif +# define RTLOG_REL_DISABLED +# include <iprt/log.h> +#endif + +#include <VBox/types.h> +#include <VBox/sup.h> +#include <VBox/param.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <mach/mach_port.h> +#include <IOKit/IOKitLib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** System device name. */ +#define DEVICE_NAME_SYS "/dev/vboxdrv" +/** User device name. */ +#define DEVICE_NAME_USR "/dev/vboxdrvu" +/** The IOClass key of the service (see SUPDrv-darwin.cpp / Info.plist). */ +#define IOCLASS_NAME "org_virtualbox_SupDrv" + + + +/** + * Opens the BSD device node. + * + * @returns VBox status code. + */ +static int suplibDarwinOpenDevice(PSUPLIBDATA pThis, bool fUnrestricted) +{ + /* + * Open the BSD device. + * This will connect to the session created when the SupDrvClient was + * started, so it has to be done after opening the service (IOC v9.1+). + */ + int hDevice = open(fUnrestricted ? DEVICE_NAME_SYS : DEVICE_NAME_USR, O_RDWR, 0); + if (hDevice < 0) + { + int rc; + switch (errno) + { + case ENODEV: rc = VERR_VM_DRIVER_LOAD_ERROR; break; + case EPERM: + case EACCES: rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; break; + case ENOENT: rc = VERR_VM_DRIVER_NOT_INSTALLED; break; + default: rc = VERR_VM_DRIVER_OPEN_ERROR; break; + } + LogRel(("SUP: Failed to open \"%s\", errno=%d, rc=%Rrc\n", fUnrestricted ? DEVICE_NAME_SYS : DEVICE_NAME_USR, errno, rc)); + return rc; + } + + /* + * Mark the file handle close on exec. + */ + if (fcntl(hDevice, F_SETFD, FD_CLOEXEC) != 0) + { +#ifdef IN_SUP_HARDENED_R3 + int rc = VERR_INTERNAL_ERROR; +#else + int err = errno; + int rc = RTErrConvertFromErrno(err); + LogRel(("suplibOSInit: setting FD_CLOEXEC failed, errno=%d (%Rrc)\n", err, rc)); +#endif + close(hDevice); + return rc; + } + + pThis->hDevice = hDevice; + pThis->fUnrestricted = fUnrestricted; + return VINF_SUCCESS; +} + + +/** + * Opens the IOKit service, instantiating org_virtualbox_SupDrvClient. + * + * @returns VBox status code. + */ +static int suplibDarwinOpenService(PSUPLIBDATA pThis) +{ + /* + * Open the IOKit client first - The first step is finding the service. + */ + mach_port_t MasterPort; + kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (kr != kIOReturnSuccess) + { + LogRel(("IOMasterPort -> %d\n", kr)); + return VERR_GENERAL_FAILURE; + } + + CFDictionaryRef ClassToMatch = IOServiceMatching(IOCLASS_NAME); + if (!ClassToMatch) + { + LogRel(("IOServiceMatching(\"%s\") failed.\n", IOCLASS_NAME)); + return VERR_GENERAL_FAILURE; + } + + /* Create an io_iterator_t for all instances of our drivers class that exist in the IORegistry. */ + io_iterator_t Iterator; + kr = IOServiceGetMatchingServices(MasterPort, ClassToMatch, &Iterator); + if (kr != kIOReturnSuccess) + { + LogRel(("IOServiceGetMatchingServices returned %d\n", kr)); + return VERR_GENERAL_FAILURE; + } + + /* Get the first item in the iterator and release it. */ + io_service_t ServiceObject = IOIteratorNext(Iterator); + IOObjectRelease(Iterator); + if (!ServiceObject) + { + LogRel(("SUP: Couldn't find any matches. The kernel module is probably not loaded.\n")); + return VERR_VM_DRIVER_NOT_INSTALLED; + } + + /* + * Open the service. + * + * This will cause the user client class in SUPDrv-darwin.cpp to be + * instantiated and create a session for this process. + */ + io_connect_t Connection = 0; + kr = IOServiceOpen(ServiceObject, mach_task_self(), SUP_DARWIN_IOSERVICE_COOKIE, &Connection); + IOObjectRelease(ServiceObject); + if (kr != kIOReturnSuccess) + { + LogRel(("SUP: IOServiceOpen returned %d. Driver open failed.\n", kr)); + pThis->uConnection = 0; + return VERR_VM_DRIVER_OPEN_ERROR; + } + + AssertCompile(sizeof(pThis->uConnection) >= sizeof(Connection)); + pThis->uConnection = Connection; + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + RT_NOREF(penmWhat, pErrInfo); + + /* + * Nothing to do if pre-inited. + */ + if (fPreInited) + return VINF_SUCCESS; + + /* + * Driverless? + */ + if (fFlags & SUPR3INIT_F_DRIVERLESS) + { + pThis->fDriverless = true; + return VINF_SUCCESS; + } + + /* + * Do the job. + */ + Assert(pThis->hDevice == (intptr_t)NIL_RTFILE); + int rc = suplibDarwinOpenService(pThis); + if (RT_SUCCESS(rc)) + { + rc = suplibDarwinOpenDevice(pThis, RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED)); + if (RT_FAILURE(rc)) + { + kern_return_t kr = IOServiceClose((io_connect_t)pThis->uConnection); + if (kr != kIOReturnSuccess) + { + LogRel(("Warning: IOServiceClose(%RCv) returned %d\n", pThis->uConnection, kr)); + AssertFailed(); + } + pThis->uConnection = 0; + } + } + if ( RT_FAILURE(rc) + && fFlags & SUPR3INIT_F_DRIVERLESS_MASK) + { + LogRel(("Failed to open \"%s\", rc=%Rrc - Switching to driverless mode.\n", IOCLASS_NAME, rc)); + pThis->fDriverless = true; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Close the connection to the IOService. + * This will cause the SUPDRVSESSION to be closed (starting IOC 9.1). + */ + if (pThis->uConnection) + { + kern_return_t kr = IOServiceClose((io_connect_t)pThis->uConnection); + if (kr != kIOReturnSuccess) + { + LogRel(("Warning: IOServiceClose(%RCv) returned %d\n", pThis->uConnection, kr)); + AssertFailed(); + } + pThis->uConnection = 0; + } + + /* + * Check if we're inited at all. + */ + if (pThis->hDevice != (intptr_t)NIL_RTFILE) + { + if (close(pThis->hDevice)) + AssertFailed(); + pThis->hDevice = (intptr_t)NIL_RTFILE; + } + + return VINF_SUCCESS; +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + RT_NOREF(cbReq); + if (RT_LIKELY(ioctl(pThis->hDevice, uFunction, pvReq) >= 0)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + int rc = ioctl(pThis->hDevice, uFunction, idCpu); + if (rc == -1) + rc = errno; + return rc; +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + *ppvPages = valloc(cPages << PAGE_SHIFT); + if (*ppvPages) + { + memset(*ppvPages, 0, cPages << PAGE_SHIFT); + return VINF_SUCCESS; + } + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t /* cPages */) +{ + NOREF(pThis); + free(pvPages); + return VINF_SUCCESS; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/darwin/SUPR0IdcClient-darwin.c b/src/VBox/HostDrivers/Support/darwin/SUPR0IdcClient-darwin.c new file mode 100644 index 00000000..4899d896 --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPR0IdcClient-darwin.c @@ -0,0 +1,66 @@ +/* $Id: SUPR0IdcClient-darwin.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Darwin Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_DISCONNECT, pReq); +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq) +{ + int rc = SUPDrvDarwinIDC(iReq, pReq); + if (RT_SUCCESS(rc)) + rc = pReq->rc; + + NOREF(pHandle); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlements.plist b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlements.plist new file mode 100644 index 00000000..3587afb0 --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlements.plist @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <!-- <key>com.apple.security.cs.allow-jit</key> <true/> --> + <!-- <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> --> + <!-- + The following two are required here even though they apply only to the VM + process. The issue is that TCC is looking up the primary bundle for these entitlements + and crashes the VM process if the entitlements are not here even though they are used + in the VM process only. This is not documented anywhere by Apple. + From Console.app when these are missing: + + tccd: [com.apple.TCC:access] Prompting policy for hardened runtime; service: + kTCCServiceMicrophone requires entitlement com.apple.security.device.audio-input but it is missing for + RESP:{ + ID: org.virtualbox.app.VirtualBox, + PID[17253], + auid: 501, + euid: 501, + responsible path: '/Applications/VirtualBox.app/Contents/MacOS/VirtualBox', + binary path: '/Applications/VirtualBox.app/Contents/MacOS/VirtualBox' + }, + REQ:{ + ID: org.virtualbox.app.VirtualBoxVM, + PID[17331], + auid: 501, + euid: 501, + binary path: '/Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM' + } + --> + <!-- For audio input --> + <key>com.apple.security.device.audio-input</key> <true/> + <!-- For emulated webcam --> + <key>com.apple.security.device.camera</key> <true/> +</dict> +</plist> diff --git a/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlementsVM.plist b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlementsVM.plist new file mode 100644 index 00000000..b1ce780b --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlementsVM.plist @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <!-- <key>com.apple.security.cs.allow-jit</key> <true/> --> + <!-- + The following two entitlements are required for using AppleHV on Catalina. + The first entitlement allows us to have unsigned executable memory in the guests + address space like the BIOS code (and essentially all the guests address space which + is mapped as RWX). + The second entitlement is required in order to map guest memory as RWX into the + guests address space. + These entitlements are not required starting with BigSur+ where Apple has clearly + changed something in their entitlement scheme without properly documenting it. + --> + <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> + <key>com.apple.security.cs.disable-executable-page-protection</key> <true/> + <!-- For audio input --> + <key>com.apple.security.device.audio-input</key> <true/> + <!-- For emulated webcam --> + <key>com.apple.security.device.camera</key> <true/> + <!-- For HID input monitoring --> + <key>com.apple.security.device.usb</key> <true/> + <!-- For vmnet based bridging and host-only networking --> + <key>com.apple.vm.networking</key> <true/> + <!-- For USB capturing --> + <key>com.apple.vm.device-access</key> <true/> + <!-- + The following are required for using AppleHV (need the second one for running Catalina) + but are actually stored in the provisioning profile because these are special entitlements. + --> + <!--<key>com.apple.security.hypervisor</key> <true/>--> + <!--<key>com.apple.vm.hypervisor</key> <true/>--> +</dict> +</plist> diff --git a/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedMain-darwin.cpp b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedMain-darwin.cpp new file mode 100644 index 00000000..58e7e619 --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/SUPR3HardenedMain-darwin.cpp @@ -0,0 +1,287 @@ +/* $Id: SUPR3HardenedMain-darwin.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), posix bits. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/err.h> +#include <VBox/sup.h> + +#include <iprt/path.h> +#include <iprt/string.h> + +#include <dlfcn.h> +#include <sys/mman.h> +#include <errno.h> +#include <sys/sysctl.h> /* sysctlbyname() */ +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> /* issetugid() */ +#include <mach-o/dyld.h> + +#include "SUPLibInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Interpose table entry. + */ +typedef struct DYLDINTERPOSE +{ + /** The symbol address to replace with. */ + const void *pvReplacement; + /** The replaced symbol address. */ + const void *pvReplacee; +} DYLDINTERPOSE; +/** Pointer to an interposer table entry. */ +typedef DYLDINTERPOSE *PDYLDINTERPOSE; +/** Pointer to a const interposer table entry. */ +typedef const DYLDINTERPOSE *PCDYLDINTERPOSE; + +/** @sa dyld_dynamic_interpose(). */ +typedef const mach_header *FNDYLDDYNAMICINTERPOSE(const struct mach_header *mh, PCDYLDINTERPOSE paSym, size_t cSyms); +/** Pointer to dyld_dynamic_interpose. */ +typedef FNDYLDDYNAMICINTERPOSE *PFNDYLDDYNAMICINTERPOSE; + +/** @sa dlopen(). */ +typedef void *FNDLOPEN(const char *path, int mode); +/** Pointer to dlopen. */ +typedef FNDLOPEN *PFNDLOPEN; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +extern "C" void _dyld_register_func_for_add_image(void (*func)(const struct mach_header *mh, intptr_t vmaddr_slide)); + +static void *supR3HardenedDarwinDlopenInterpose(const char *path, int mode); +static int supR3HardenedDarwinIssetugidInterpose(void); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Flag whether macOS 11.x (BigSur) or later was detected. + * See comments in supR3HardenedDarwinDlopenInterpose for details. */ +static bool g_fMacOs11Plus = false; +/** Resolved dyld_dynamic_interpose() value. */ +static PFNDYLDDYNAMICINTERPOSE g_pfnDyldDynamicInterpose = NULL; +/** Pointer to the real dlopen() function used from the interposer when verification succeeded. */ +static PFNDLOPEN g_pfnDlopenReal = NULL; +/** + * The interposer table. + */ +static const DYLDINTERPOSE g_aInterposers[] = +{ + { (const void *)(uintptr_t)&supR3HardenedDarwinDlopenInterpose, (const void *)(uintptr_t)&dlopen }, + { (const void *)(uintptr_t)&supR3HardenedDarwinIssetugidInterpose, (const void *)(uintptr_t)&issetugid } +}; + + +/** + * dlopen() interposer which verifies that the path to be loaded meets the criteria for hardened builds. + * + * @sa dlopen() man page. + */ +static void *supR3HardenedDarwinDlopenInterpose(const char *path, int mode) +{ + /* + * Giving NULL as the filename indicates opening the main program which is fine + * We are already loaded and executing after all. + * + * Filenames without any path component (whether absolute or relative) are allowed + * unconditionally too as the loader will only search the default paths configured by root. + */ + if ( path + && strchr(path, '/') != NULL) + { + int rc = VINF_SUCCESS; + + /* + * Starting with macOS 11.0 (BigSur) system provided libraries + * under /System/Libraries are not stored on the filesystem anymore + * but in a dynamic linker cache. The integrity of the linker cache + * is maintained by the system and dyld. Our verification code fails because + * it can't find the file. + * The obvious solution is to exclude paths starting with /System/Libraries + * when we run on BigSur. Other paths are still subject to verification. + */ + if ( !g_fMacOs11Plus + || strncmp(path, RT_STR_TUPLE("/System/Library"))) + rc = supR3HardenedVerifyFileFollowSymlinks(path, RTHCUINTPTR_MAX, true /* fMaybe3rdParty */, + NULL /* pErrInfo */); + if (RT_FAILURE(rc)) + return NULL; + } + + return g_pfnDlopenReal(path, mode); +} + + +/** + * Override this one to try hide the fact that we're setuid to root orginially. + * + * @sa issetugid() man page. + * + * Mac OS X: Really ugly hack to bypass a set-uid check in AppKit. + * + * This will modify the issetugid() function to always return zero. This must + * be done _before_ AppKit is initialized, otherwise it will refuse to play ball + * with us as it distrusts set-uid processes since Snow Leopard. We, however, + * have carefully dropped all root privileges at this point and there should be + * no reason for any security concern here. + */ +static int supR3HardenedDarwinIssetugidInterpose(void) +{ +#ifdef DEBUG + Dl_info Info = {0}; + char szMsg[512]; + size_t cchMsg; + const void * uCaller = __builtin_return_address(0); + if (dladdr(uCaller, &Info)) + cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p %s::%s+%p (via %p)\n", + uCaller, Info.dli_fname, Info.dli_sname, (void *)((uintptr_t)uCaller - (uintptr_t)Info.dli_saddr), __builtin_return_address(1)); + else + cchMsg = snprintf(szMsg, sizeof(szMsg), "DEBUG: issetugid_for_AppKit was called by %p (via %p)\n", uCaller, __builtin_return_address(1)); + write(2, szMsg, cchMsg); +#endif + return 0; +} + + +/** + * Callback to get notified of new images being loaded to be able to apply our dlopn() interposer. + * + * @param mh Pointer to the mach header of the loaded image. + * @param vmaddr_slide The slide value for ASLR. + */ +static DECLCALLBACK(void) supR3HardenedDarwinAddImage(const struct mach_header *mh, intptr_t vmaddr_slide) +{ + RT_NOREF(vmaddr_slide); + + g_pfnDyldDynamicInterpose(mh, &g_aInterposers[0], RT_ELEMENTS(g_aInterposers)); +} + + +/** + * Hardening initialization for macOS hosts. + * + * @note Doesn't return on error. + */ +DECLHIDDEN(void) supR3HardenedDarwinInit(void) +{ + /* + * Check whether we are running on macOS BigSur by checking kern.osproductversion + * available since some point in 2018. + */ + char szVers[256]; RT_ZERO(szVers); + size_t cbVers = sizeof(szVers); + int rc = sysctlbyname("kern.osproductversion", &szVers[0], &cbVers, NULL, 0); + if ( !rc + && memcmp(&szVers[0], RT_STR_TUPLE("10.16")) >= 0) + g_fMacOs11Plus = true; + + /* Saved to call real dlopen() later on, as we will interpose dlopen() from the main binary in the next step as well. */ + g_pfnDlopenReal = (PFNDLOPEN)dlsym(RTLD_DEFAULT, "dlopen"); + g_pfnDyldDynamicInterpose = (PFNDYLDDYNAMICINTERPOSE)dlsym(RTLD_DEFAULT, "dyld_dynamic_interpose"); + if (!g_pfnDyldDynamicInterpose) + supR3HardenedFatalMsg("supR3HardenedDarwinInit", kSupInitOp_Integrity, VERR_SYMBOL_NOT_FOUND, + "Failed to find dyld_dynamic_interpose()"); + + /* + * The following will causes our add image notification to be called for all images loaded so far. + * The callback will set up the interposer. + */ + _dyld_register_func_for_add_image(supR3HardenedDarwinAddImage); +} + + + +/* + * assert.cpp + * + * ASSUMES working DECLHIDDEN or there will be symbol confusion! + */ + +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; + snprintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%u) %s\n", + pszExpr, pszFile, uLine, pszFunction); +} + + +RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) +{ + vsnprintf(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); +} + diff --git a/src/VBox/HostDrivers/Support/darwin/load.sh b/src/VBox/HostDrivers/Support/darwin/load.sh new file mode 100755 index 00000000..b30c154e --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/load.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# $Id: load.sh $ +## @file +# For development. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` +DRVNAME="VBoxDrv.kext" +BUNDLE="org.virtualbox.kext.VBoxDrv" + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +# Make sure VBoxUSB is unloaded as it might be using symbols from us. +LOADED=`kextstat -b org.virtualbox.kext.VBoxUSB -l` +if test -n "$LOADED"; then + echo "load.sh: Unloading org.virtualbox.kext.VBoxUSB..." + sudo kextunload -v 6 -b org.virtualbox.kext.VBoxUSB + LOADED=`kextstat -b org.virtualbox.kext.VBoxUSB -l` + if test -n "$LOADED"; then + echo "load.sh: failed to unload org.virtualbox.kext.VBoxUSB, see above..." + exit 1; + fi + echo "load.sh: Successfully unloaded org.virtualbox.kext.VBoxUSB" +fi + +# Make sure VBoxNetFlt is unloaded as it might be using symbols from us. +LOADED=`kextstat -b org.virtualbox.kext.VBoxNetFlt -l` +if test -n "$LOADED"; then + echo "load.sh: Unloading org.virtualbox.kext.VBoxNetFlt..." + sudo kextunload -v 6 -b org.virtualbox.kext.VBoxNetFlt + LOADED=`kextstat -b org.virtualbox.kext.VBoxNetFlt -l` + if test -n "$LOADED"; then + echo "load.sh: failed to unload org.virtualbox.kext.VBoxNetFlt, see above..." + exit 1; + fi + echo "load.sh: Successfully unloaded org.virtualbox.kext.VBoxNetFlt" +fi + +# Make sure VBoxNetAdp is unloaded as it might be using symbols from us. +LOADED=`kextstat -b org.virtualbox.kext.VBoxNetAdp -l` +if test -n "$LOADED"; then + echo "load.sh: Unloading org.virtualbox.kext.VBoxNetAdp..." + sudo kextunload -v 6 -b org.virtualbox.kext.VBoxNetAdp + LOADED=`kextstat -b org.virtualbox.kext.VBoxNetAdp -l` + if test -n "$LOADED"; then + echo "load.sh: failed to unload org.virtualbox.kext.VBoxNetAdp, see above..." + exit 1; + fi + echo "load.sh: Successfully unloaded org.virtualbox.kext.VBoxNetAdp" +fi + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "load.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "load.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "load.sh: Successfully unloaded $BUNDLE" +fi + +set -e + +# Copy the .kext to the symbols directory and tweak the kextload options. +if test -n "$VBOX_DARWIN_SYMS"; then + echo "load.sh: copying the extension the symbol area..." + rm -Rf "$VBOX_DARWIN_SYMS/$DRVNAME" + mkdir -p "$VBOX_DARWIN_SYMS" + cp -R "$DIR" "$VBOX_DARWIN_SYMS/" + OPTS="$OPTS -s $VBOX_DARWIN_SYMS/ " + sync +fi + +trap "sudo chown -R `whoami` $DIR; exit 1" INT +trap "sudo chown -R `whoami` $DIR; exit 1" ERR +# On smbfs, this might succeed just fine but make no actual changes, +# so we might have to temporarily copy the driver to a local directory. +if sudo chown -R root:wheel "$DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/loaddrv.tmp + echo "load.sh: chown didn't work on $DIR, using temp location $TMP_DIR/$DRVNAME" + + # clean up first (no sudo rm) + if test -e "$TMP_DIR"; then + sudo chown -R `whoami` "$TMP_DIR" + rm -Rf "$TMP_DIR" + fi + + # make a copy and switch over DIR + mkdir -p "$TMP_DIR/" + sudo cp -Rp "$DIR" "$TMP_DIR/" + DIR="$TMP_DIR/$DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" +fi +sudo chmod -R o-rwx "$DIR" +sync +echo "load.sh: loading $DIR..." + +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS \"$DIR\")" + sudo kextutil $OPTS "$DIR" +else + sudo kextload $OPTS "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" +#sudo chmod 666 /dev/vboxdrv +kextstat | grep org.virtualbox.kext +if [ -n "${VBOX_DARWIN_SYMS}" -a "$XNU_VERSION" -ge "10" ]; then + dsymutil -o "${VBOX_DARWIN_SYMS}/${DRVNAME}.dSYM" "${DIR}/Contents/MacOS/`basename -s .kext ${DRVNAME}`" + sync +fi + diff --git a/src/VBox/HostDrivers/Support/darwin/sys/dtrace_glue.h b/src/VBox/HostDrivers/Support/darwin/sys/dtrace_glue.h new file mode 100644 index 00000000..cacb890b --- /dev/null +++ b/src/VBox/HostDrivers/Support/darwin/sys/dtrace_glue.h @@ -0,0 +1,49 @@ +/* $Id: dtrace_glue.h $ */ +/** @file + * VirtualBox Support Driver - Darwin, mock-up of missing sys/dtrace-glue.h. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_darwin_sys_dtrace_glue_h +#define VBOX_INCLUDED_SRC_Support_darwin_sys_dtrace_glue_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define _KERNEL +typedef struct solaris_cred cred_t; +typedef struct solaris_kthread kthread_t; +typedef struct solaris_x86_saved_state x86_saved_state_t; +typedef unsigned int model_t; + +#endif /* !VBOX_INCLUDED_SRC_Support_darwin_sys_dtrace_glue_h */ diff --git a/src/VBox/HostDrivers/Support/freebsd/Makefile b/src/VBox/HostDrivers/Support/freebsd/Makefile new file mode 100644 index 00000000..a174d8ec --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/Makefile @@ -0,0 +1,207 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox FreeBSD Host Driver. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +KMOD = vboxdrv + +CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DSUPDRV_WITH_RELEASE_LOGGER -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBOX_WITH_HARDENING -DVBOX_WITH_64_BITS_GUESTS + +.if (${MACHINE_ARCH} == "i386") + CFLAGS += -DRT_ARCH_X86 +.elif (${MACHINE_ARCH} == "amd64") + CFLAGS += -DRT_ARCH_AMD64 +.endif + +SRCS = \ + SUPDrv.c \ + SUPDrvGip.c \ + SUPDrvSem.c \ + SUPDrvTracer.c \ + SUPLibAll.c \ + +# Include needed interface headers so they are created during build +SRCS += \ + device_if.h \ + bus_if.h + +.PATH: ${.CURDIR}/freebsd +SRCS += \ + SUPDrv-freebsd.c + +.PATH: ${.CURDIR}/alloc +SRCS += \ + heapsimple.c \ + alloc.c + +.PATH: ${.CURDIR}/common/err +SRCS += \ + RTErrConvertFromErrno.c \ + RTErrConvertToErrno.c \ + errinfo.c + +.PATH: ${.CURDIR}/common/log +SRCS += \ + log.c \ + logellipsis.c \ + logrel.c \ + logrelellipsis.c \ + logcom.c \ + logformat.c \ + RTLogCreateEx.c + +.PATH: ${.CURDIR}/common/misc +SRCS += \ + RTAssertMsg1Weak.c \ + RTAssertMsg2.c \ + RTAssertMsg2Add.c \ + RTAssertMsg2AddWeak.c \ + RTAssertMsg2AddWeakV.c \ + RTAssertMsg2Weak.c \ + RTAssertMsg2WeakV.c \ + assert.c \ + handletable.c \ + handletablectx.c \ + once.c \ + term.c \ + thread.c + +.PATH: ${.CURDIR}/common/string +SRCS += \ + RTStrCat.c \ + RTStrNCmp.c \ + RTStrNLen.c \ + RTStrCopy.c \ + RTStrCopyEx.c \ + RTStrCopyP.c \ + RTStrEnd.c \ + strformat.c \ + RTStrFormat.c \ + strformatnum.c \ + strformatrt.c \ + strformattype.c \ + strprintf.c \ + strprintf-ellipsis.c \ + strprintf2.c \ + strprintf2-ellipsis.c \ + strtonum.c \ + memchr.c \ + stringalloc.c + +.PATH: ${.CURDIR}/common/rand +SRCS += \ + rand.c \ + randadv.c \ + randparkmiller.c + +.PATH: ${.CURDIR}/common/path +SRCS += \ + RTPathStripFilename.c + +.PATH: ${.CURDIR}/common/checksum +SRCS += \ + crc32.c \ + ipv4.c \ + ipv6.c + +.PATH: ${.CURDIR}/common/table +SRCS += \ + avlpv.c + +.PATH: ${.CURDIR}/common/time +SRCS += \ + time.c + +.PATH: ${.CURDIR}/generic +SRCS += \ + uuid-generic.c \ + RTAssertShouldPanic-generic.c \ + RTLogWriteDebugger-generic.c \ + RTLogWriteStdOut-stub-generic.c \ + RTLogWriteStdErr-stub-generic.c \ + RTLogWriteUser-generic.c \ + RTMpGetArraySize-generic.c \ + RTMpOnPair-generic.c \ + RTRandAdvCreateSystemFaster-generic.c \ + RTRandAdvCreateSystemTruer-generic.c \ + RTSemEventWait-2-ex-generic.c \ + RTSemEventWaitNoResume-2-ex-generic.c \ + RTSemEventMultiWait-2-ex-generic.c \ + RTSemEventMultiWaitNoResume-2-ex-generic.c \ + RTTimerCreate-generic.c \ + rtStrFormatKernelAddress-generic.c \ + errvars-generic.c \ + mppresent-generic.c \ + timer-generic.c + +.PATH: ${.CURDIR}/r0drv +SRCS += \ + alloc-r0drv.c \ + alloc-ef-r0drv.c \ + initterm-r0drv.c \ + memobj-r0drv.c \ + powernotification-r0drv.c + +.PATH: ${.CURDIR}/r0drv/freebsd +SRCS += \ + assert-r0drv-freebsd.c \ + alloc-r0drv-freebsd.c \ + initterm-r0drv-freebsd.c \ + memobj-r0drv-freebsd.c \ + memuserkernel-r0drv-freebsd.c \ + mp-r0drv-freebsd.c \ + process-r0drv-freebsd.c \ + semevent-r0drv-freebsd.c \ + semeventmulti-r0drv-freebsd.c \ + semfastmutex-r0drv-freebsd.c \ + semmutex-r0drv-freebsd.c \ + spinlock-r0drv-freebsd.c \ + thread-r0drv-freebsd.c \ + thread2-r0drv-freebsd.c \ + time-r0drv-freebsd.c + +.PATH: ${.CURDIR}/r0drv/generic +SRCS += \ + semspinmutex-r0drv-generic.c \ + mpnotification-r0drv-generic.c \ + threadctxhooks-r0drv-generic.c \ + RTMpIsCpuWorkPending-r0drv-generic.c + +.PATH: ${.CURDIR}/VBox +SRCS += \ + log-vbox.c \ + RTLogWriteVmm-amd64-x86.c + +.include <bsd.kmod.mk> + diff --git a/src/VBox/HostDrivers/Support/freebsd/Makefile.kup b/src/VBox/HostDrivers/Support/freebsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c new file mode 100644 index 00000000..0a20031d --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c @@ -0,0 +1,679 @@ +/* $Id: SUPDrv-freebsd.c $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - FreeBSD specifics. + */ + +/* + * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +/* Deal with conflicts first. */ +#include <sys/param.h> +#undef PVM +#include <sys/types.h> +#include <sys/module.h> +#include <sys/systm.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <vm/pmap.h> /* for pmap_map() */ + +#include "../SUPDrvInternal.h" +#include <VBox/version.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/spinlock.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/uuid.h> +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/err.h> +#include <iprt/asm.h> + +#ifdef VBOX_WITH_HARDENING +# define VBOXDRV_PERM 0600 +#else +# define VBOXDRV_PERM 0666 +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int VBoxDrvFreeBSDModuleEvent(struct module *pMod, int enmEventType, void *pvArg); +static int VBoxDrvFreeBSDLoad(void); +static int VBoxDrvFreeBSDUnload(void); + +static d_open_t VBoxDrvFreeBSDOpenUsr; +static d_open_t VBoxDrvFreeBSDOpenSys; +static void vboxdrvFreeBSDDtr(void *pvData); +static d_ioctl_t VBoxDrvFreeBSDIOCtl; +static int VBoxDrvFreeBSDIOCtlSlow(PSUPDRVSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Module info structure used by the kernel. + */ +static moduledata_t g_VBoxDrvFreeBSDModule = +{ + "vboxdrv", + VBoxDrvFreeBSDModuleEvent, + NULL +}; + +/** Declare the module as a pseudo device. */ +DECLARE_MODULE(vboxdrv, g_VBoxDrvFreeBSDModule, SI_SUB_PSEUDO, SI_ORDER_ANY); +MODULE_VERSION(vboxdrv, 1); + +/** + * The /dev/vboxdrv character device entry points. + */ +static struct cdevsw g_VBoxDrvFreeBSDChrDevSwSys = +{ + .d_version = D_VERSION, + .d_open = VBoxDrvFreeBSDOpenSys, + .d_ioctl = VBoxDrvFreeBSDIOCtl, + .d_name = "vboxdrv" +}; +/** The /dev/vboxdrv character device. */ +static struct cdev *g_pVBoxDrvFreeBSDChrDevSys; + +/** + * The /dev/vboxdrvu character device entry points. + */ +static struct cdevsw g_VBoxDrvFreeBSDChrDevSwUsr = +{ + .d_version = D_VERSION, + .d_open = VBoxDrvFreeBSDOpenUsr, + .d_ioctl = VBoxDrvFreeBSDIOCtl, + .d_name = "vboxdrvu" +}; +/** The /dev/vboxdrvu character device. */ +static struct cdev *g_pVBoxDrvFreeBSDChrDevUsr; + +/** Reference counter. */ +static volatile uint32_t g_cUsers; + +/** The device extention. */ +static SUPDRVDEVEXT g_VBoxDrvFreeBSDDevExt; + +/** + * Module event handler. + * + * @param pMod The module structure. + * @param enmEventType The event type (modeventtype_t). + * @param pvArg Module argument. NULL. + * + * @return 0 on success, errno.h status code on failure. + */ +static int VBoxDrvFreeBSDModuleEvent(struct module *pMod, int enmEventType, void *pvArg) +{ + int rc; + switch (enmEventType) + { + case MOD_LOAD: + rc = VBoxDrvFreeBSDLoad(); + break; + + case MOD_UNLOAD: + mtx_unlock(&Giant); + rc = VBoxDrvFreeBSDUnload(); + mtx_lock(&Giant); + break; + + case MOD_SHUTDOWN: + case MOD_QUIESCE: + default: + return EOPNOTSUPP; + } + + if (RT_SUCCESS(rc)) + return 0; + return RTErrConvertToErrno(rc); +} + + +static int VBoxDrvFreeBSDLoad(void) +{ + g_cUsers = 0; + + /* + * Initialize the runtime. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxDrvFreeBSDLoad:\n")); + + /* + * Initialize the device extension. + */ + rc = supdrvInitDevExt(&g_VBoxDrvFreeBSDDevExt, sizeof(SUPDRVSESSION)); + if (RT_SUCCESS(rc)) + { + /* + * Configure character devices. Add symbolic links for compatibility. + */ + g_pVBoxDrvFreeBSDChrDevSys = make_dev(&g_VBoxDrvFreeBSDChrDevSwSys, 0, UID_ROOT, GID_WHEEL, VBOXDRV_PERM, "vboxdrv"); + g_pVBoxDrvFreeBSDChrDevUsr = make_dev(&g_VBoxDrvFreeBSDChrDevSwUsr, 1, UID_ROOT, GID_WHEEL, 0666, "vboxdrvu"); + return VINF_SUCCESS; + } + + printf("vboxdrv: supdrvInitDevExt failed, rc=%d\n", rc); + RTR0Term(); + } + else + printf("vboxdrv: RTR0Init failed, rc=%d\n", rc); + return rc; +} + +static int VBoxDrvFreeBSDUnload(void) +{ + Log(("VBoxDrvFreeBSDUnload:\n")); + + if (g_cUsers > 0) + return VERR_RESOURCE_BUSY; + + /* + * Reserve what we did in VBoxDrvFreeBSDInit. + */ + destroy_dev(g_pVBoxDrvFreeBSDChrDevUsr); + destroy_dev(g_pVBoxDrvFreeBSDChrDevSys); + + supdrvDeleteDevExt(&g_VBoxDrvFreeBSDDevExt); + + RTR0TermForced(); + + memset(&g_VBoxDrvFreeBSDDevExt, 0, sizeof(g_VBoxDrvFreeBSDDevExt)); + return VINF_SUCCESS; +} + + +/** + * + * @returns 0 on success, errno on failure. + * EBUSY if the device is used by someone else. + * @param pDev The device node. + * @param fOpen The open flags. + * @param iDevType Some device type thing we don't use. + * @param pTd The thread. + * @param fUnrestricted Set if opening /dev/vboxdrv, clear if /dev/vboxdrvu. + */ +static int vboxdrvFreeBSDOpenCommon(struct cdev *pDev, int fOpen, int iDevType, struct thread *pTd, bool fUnrestricted) +{ + PSUPDRVSESSION pSession; + int rc; + + /* + * Let's be a bit picky about the flags... + */ + if (fOpen != (FREAD | FWRITE /*=O_RDWR*/)) + { + Log(("VBoxDrvFreeBSDOpen: fOpen=%#x expected %#x\n", fOpen, O_RDWR)); + return EINVAL; + } + + /* + * Create a new session. + */ + rc = supdrvCreateSession(&g_VBoxDrvFreeBSDDevExt, true /* fUser */, fUnrestricted, &pSession); + if (RT_SUCCESS(rc)) + { + /** @todo get (r)uid and (r)gid. + pSession->Uid = stuff; + pSession->Gid = stuff; */ + rc = devfs_set_cdevpriv(pSession, vboxdrvFreeBSDDtr); Assert(rc == 0); + Log(("VBoxDrvFreeBSDOpen: pSession=%p\n", pSession)); + ASMAtomicIncU32(&g_cUsers); + return 0; + } + + return RTErrConvertToErrno(rc); +} + + +/** For vboxdrv. */ +static int VBoxDrvFreeBSDOpenSys(struct cdev *pDev, int fOpen, int iDevType, struct thread *pTd) +{ + return vboxdrvFreeBSDOpenCommon(pDev, fOpen, iDevType, pTd, true); +} + + +/** For vboxdrvu. */ +static int VBoxDrvFreeBSDOpenUsr(struct cdev *pDev, int fOpen, int iDevType, struct thread *pTd) +{ + return vboxdrvFreeBSDOpenCommon(pDev, fOpen, iDevType, pTd, false); +} + + +/** + * Close a file device previously opened by VBoxDrvFreeBSDOpen. + * + * @param pvData The session being closed. + */ +static void vboxdrvFreeBSDDtr(void *pvData) +{ + PSUPDRVSESSION pSession = pvData; + Log(("vboxdrvFreeBSDDtr: pSession=%p\n", pSession)); + + /* + * Close the session. + */ + supdrvSessionRelease(pSession); + ASMAtomicDecU32(&g_cUsers); +} + + +/** + * I/O control request. + * + * @returns depends... + * @param pDev The device. + * @param ulCmd The command. + * @param pvData Pointer to the data. + * @param fFile The file descriptor flags. + * @param pTd The calling thread. + */ +static int VBoxDrvFreeBSDIOCtl(struct cdev *pDev, u_long ulCmd, caddr_t pvData, int fFile, struct thread *pTd) +{ + PSUPDRVSESSION pSession; + devfs_get_cdevpriv((void **)&pSession); + + /* + * Deal with the fast ioctl path first. + */ + AssertCompile((SUP_IOCTL_FAST_DO_FIRST & 0xff) == (SUP_IOCTL_FLAG | 64)); + if ( (uintptr_t)(ulCmd - SUP_IOCTL_FAST_DO_FIRST) < (uintptr_t)32 + && pSession->fUnrestricted) + return supdrvIOCtlFast(ulCmd - SUP_IOCTL_FAST_DO_FIRST, *(uint32_t *)pvData, &g_VBoxDrvFreeBSDDevExt, pSession); + + return VBoxDrvFreeBSDIOCtlSlow(pSession, ulCmd, pvData, pTd); +} + + +/** + * Deal with the 'slow' I/O control requests. + * + * @returns 0 on success, appropriate errno on failure. + * @param pSession The session. + * @param ulCmd The command. + * @param pvData The request data. + * @param pTd The calling thread. + */ +static int VBoxDrvFreeBSDIOCtlSlow(PSUPDRVSESSION pSession, u_long ulCmd, caddr_t pvData, struct thread *pTd) +{ + PSUPREQHDR pHdr; + uint32_t cbReq = IOCPARM_LEN(ulCmd); + void *pvUser = NULL; + + /* + * Buffered request? + */ + if ((IOC_DIRMASK & ulCmd) == IOC_INOUT) + { + pHdr = (PSUPREQHDR)pvData; + if (RT_UNLIKELY(cbReq < sizeof(*pHdr))) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: cbReq=%#x < %#x; ulCmd=%#lx\n", cbReq, (int)sizeof(*pHdr), ulCmd)); + return EINVAL; + } + if (RT_UNLIKELY((pHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: bad magic fFlags=%#x; ulCmd=%#lx\n", pHdr->fFlags, ulCmd)); + return EINVAL; + } + if (RT_UNLIKELY( RT_MAX(pHdr->cbIn, pHdr->cbOut) != cbReq + || pHdr->cbIn < sizeof(*pHdr) + || pHdr->cbOut < sizeof(*pHdr))) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: max(%#x,%#x) != %#x; ulCmd=%#lx\n", pHdr->cbIn, pHdr->cbOut, cbReq, ulCmd)); + return EINVAL; + } + } + /* + * Big unbuffered request? + */ + else if ((IOC_DIRMASK & ulCmd) == IOC_VOID && !cbReq) + { + /* + * Read the header, validate it and figure out how much that needs to be buffered. + */ + SUPREQHDR Hdr; + pvUser = *(void **)pvData; + int rc = copyin(pvUser, &Hdr, sizeof(Hdr)); + if (RT_UNLIKELY(rc)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyin(%p,Hdr,) -> %#x; ulCmd=%#lx\n", pvUser, rc, ulCmd)); + return rc; + } + if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: bad magic fFlags=%#x; ulCmd=%#lx\n", Hdr.fFlags, ulCmd)); + return EINVAL; + } + cbReq = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr) + || Hdr.cbOut < sizeof(Hdr) + || cbReq > _1M*16)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: max(%#x,%#x); ulCmd=%#lx\n", Hdr.cbIn, Hdr.cbOut, ulCmd)); + return EINVAL; + } + + /* + * Allocate buffer and copy in the data. + */ + pHdr = (PSUPREQHDR)RTMemTmpAlloc(cbReq); + if (RT_UNLIKELY(!pHdr)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: failed to allocate buffer of %d bytes; ulCmd=%#lx\n", cbReq, ulCmd)); + return ENOMEM; + } + rc = copyin(pvUser, pHdr, Hdr.cbIn); + if (RT_UNLIKELY(rc)) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyin(%p,%p,%#x) -> %#x; ulCmd=%#lx\n", + pvUser, pHdr, Hdr.cbIn, rc, ulCmd)); + RTMemTmpFree(pHdr); + return rc; + } + if (Hdr.cbIn < cbReq) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbReq - Hdr.cbIn); + } + else + { + Log(("VBoxDrvFreeBSDIOCtlSlow: huh? cbReq=%#x ulCmd=%#lx\n", cbReq, ulCmd)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + int rc = supdrvIOCtl(ulCmd, &g_VBoxDrvFreeBSDDevExt, pSession, pHdr, cbReq); + if (RT_LIKELY(!rc)) + { + /* + * If unbuffered, copy back the result before returning. + */ + if (pvUser) + { + uint32_t cbOut = pHdr->cbOut; + if (cbOut > cbReq) + { + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: too much output! %#x > %#x; uCmd=%#lx!\n", cbOut, cbReq, ulCmd)); + cbOut = cbReq; + } + rc = copyout(pHdr, pvUser, cbOut); + if (RT_UNLIKELY(rc)) + OSDBGPRINT(("VBoxDrvFreeBSDIOCtlSlow: copyout(%p,%p,%#x) -> %d; uCmd=%#lx!\n", pHdr, pvUser, cbOut, rc, ulCmd)); + + Log(("VBoxDrvFreeBSDIOCtlSlow: returns %d / %d ulCmd=%lx\n", 0, pHdr->rc, ulCmd)); + + /* cleanup */ + RTMemTmpFree(pHdr); + } + } + else + { + /* + * The request failed, just clean up. + */ + if (pvUser) + RTMemTmpFree(pHdr); + + Log(("VBoxDrvFreeBSDIOCtlSlow: ulCmd=%lx pData=%p failed, rc=%d\n", ulCmd, pvData, rc)); + rc = EINVAL; + } + + return rc; +} + + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code, see supdrvIDC. + * @param uReq The request code. + * @param pReq The request. + */ +int VBOXCALL SUPDrvFreeBSDIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + PSUPDRVSESSION pSession; + + /* + * Some quick validations. + */ + if (RT_UNLIKELY(!RT_VALID_PTR(pReq))) + return VERR_INVALID_POINTER; + + pSession = pReq->pSession; + if (pSession) + { + if (RT_UNLIKELY(!RT_VALID_PTR(pReq->pSession))) + return VERR_INVALID_PARAMETER; + if (RT_UNLIKELY(pSession->pDevExt != &g_VBoxDrvFreeBSDDevExt)) + return VERR_INVALID_PARAMETER; + } + else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT)) + return VERR_INVALID_PARAMETER; + + /* + * Do the job. + */ + return supdrvIDC(uReq, &g_VBoxDrvFreeBSDDevExt, pSession, 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); + NOREF(pSession); +} + + +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + return false; +} + + +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + /** @todo verify this. */ + return false; +} + + +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + return false; +} + + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt, pImage, pszSymbol, cchSymbol, ppvSymbol); + return VERR_WRONG_ORDER; +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + +#ifdef SUPDRV_WITH_MSR_PROBER + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + NOREF(uMsr); NOREF(idCpu); NOREF(puValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + NOREF(uMsr); NOREF(idCpu); NOREF(uValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + NOREF(idCpu); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_ARM64) +SUPR0DECL(int) SUPR0HCPhysToVirt(RTHCPHYS HCPhys, void **ppv) +{ + AssertReturn(!(HCPhys & PAGE_OFFSET_MASK), VERR_INVALID_POINTER); + AssertReturn(HCPhys != NIL_RTHCPHYS, VERR_INVALID_POINTER); + *ppv = (void *)(uintptr_t)pmap_map(NULL, HCPhys, (HCPhys | PAGE_OFFSET_MASK) + 1, VM_PROT_WRITE | VM_PROT_READ); + return VINF_SUCCESS; +} +#endif + + +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + char szMsg[256]; + RTStrPrintfV(szMsg, sizeof(szMsg), pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + printf("%s", szMsg); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + diff --git a/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.def b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.def new file mode 100644 index 00000000..f6e776e5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.def @@ -0,0 +1,2 @@ +SUPDrvLinuxIDC + diff --git a/src/VBox/HostDrivers/Support/freebsd/SUPLib-freebsd.cpp b/src/VBox/HostDrivers/Support/freebsd/SUPLib-freebsd.cpp new file mode 100644 index 00000000..cfd5244f --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/SUPLib-freebsd.cpp @@ -0,0 +1,197 @@ +/* $Id: SUPLib-freebsd.cpp $ */ +/** @file + * VirtualBox Support Library - FreeBSD specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# define LOG_DISABLED +# define RTLOG_REL_DISABLED +# include <iprt/log.h> +#endif + +#include <VBox/types.h> +#include <VBox/sup.h> +#include <VBox/param.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** System device name. */ +#define DEVICE_NAME_SYS "/dev/vboxdrv" +/** User device name. */ +#define DEVICE_NAME_USR "/dev/vboxdrvu" + + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + /* + * Nothing to do if pre-inited. + */ + if (fPreInited) + return VINF_SUCCESS; + + /* + * Try open the BSD device. + */ + const char * const *pszDeviceNm = fFlags & SUPR3INIT_F_UNRESTRICTED ? DEVICE_NAME_SYS : DEVICE_NAME_USR; + int hDevice = open(pszDeviceNm, O_RDWR, 0); + if (hDevice < 0) + { + int rc; + switch (errno) + { + case ENODEV: rc = VERR_VM_DRIVER_LOAD_ERROR; break; + case EPERM: + case EACCES: rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; break; + case ENOENT: rc = VERR_VM_DRIVER_NOT_INSTALLED; break; + default: rc = VERR_VM_DRIVER_OPEN_ERROR; break; + } + LogRel(("Failed to open \"%s\", errno=%d, rc=%Rrc\n", pszDeviceNm, errno, rc)); + return rc; + } + + /* + * Mark the file handle close on exec. + */ + if (fcntl(hDevice, F_SETFD, FD_CLOEXEC) != 0) + { +#ifdef IN_SUP_HARDENED_R3 + int rc = VERR_INTERNAL_ERROR; +#else + int err = errno; + int rc = RTErrConvertFromErrno(err); + LogRel(("suplibOSInit: setting FD_CLOEXEC failed, errno=%d (%Rrc)\n", err, rc)); +#endif + close(hDevice); + return rc; + } + + /* + * We're done. + */ + pThis->hDevice = hDevice; + pThis->fUnrestricted = RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Check if we're inited at all. + */ + if (pThis->hDevice != (intptr_t)NIL_RTFILE) + { + if (close(pThis->hDevice)) + AssertFailed(); + pThis->hDevice = (intptr_t)NIL_RTFILE; + } + return VINF_SUCCESS; +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + if (RT_LIKELY(ioctl(pThis->hDevice, uFunction, pvReq) >= 0)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + int rc = ioctl(pThis->hDevice, uFunction, idCpu); + if (rc == -1) + rc = errno; + return rc; +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + *ppvPages = RTMemPageAllocZ(cPages << PAGE_SHIFT); + if (*ppvPages) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t cPages) +{ + NOREF(pThis); + RTMemPageFree(pvPages, cPages * PAGE_SIZE); + return VINF_SUCCESS; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c b/src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c new file mode 100644 index 00000000..c251c2e2 --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c @@ -0,0 +1,66 @@ +/* $Id: SUPR0IdcClient-freebsd.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, FreeBSD Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_DISCONNECT, pReq); +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq) +{ + int rc = SUPDrvFreeBSDIDC(iReq, pReq); + if (RT_SUCCESS(rc)) + rc = pReq->rc; + + NOREF(pHandle); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/freebsd/files_vboxdrv b/src/VBox/HostDrivers/Support/freebsd/files_vboxdrv new file mode 100755 index 00000000..0da2f2e6 --- /dev/null +++ b/src/VBox/HostDrivers/Support/freebsd/files_vboxdrv @@ -0,0 +1,240 @@ +#!/bin/sh +# $Id: files_vboxdrv $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +FILES_VBOXDRV_NOBIN=" \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/alloca.h=>include/iprt/alloca.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/errno.h=>include/iprt/errno.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/handletable.h=>include/iprt/handletable.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/uint128.h=>include/iprt/uint128.h \ + ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \ + ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/rand.h=>include/iprt/rand.h \ + ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \ + ${PATH_ROOT}/include/iprt/once.h=>include/iprt/once.h \ + ${PATH_ROOT}/include/iprt/critsect.h=>include/iprt/critsect.h \ + ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \ + ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/sup.h=>include/VBox/sup.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/version.h=>include/VBox/version.h \ + ${PATH_ROOT}/include/VBox/SUPDrvMangling.h=>include/VBox/SUPDrvMangling.h \ + ${PATH_ROOT}/include/VBox/VBoxTpG.h=>include/VBox/VBoxTpG.h \ + ${PATH_ROOT}/include/VBox/vmm/hm_vmx.h=>include/VBox/vmm/hm_vmx.h \ + ${PATH_ROOT}/include/VBox/vmm/hm_svm.h=>include/VBox/vmm/hm_svm.h \ + ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c=>freebsd/SUPDrv-freebsd.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrv.cpp=>SUPDrv.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvGip.cpp=>SUPDrvGip.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvSem.cpp=>SUPDrvSem.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvTracer.cpp=>SUPDrvTracer.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIOC.h=>SUPDrvIOC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvInternal.h=>SUPDrvInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPLibAll.cpp=>SUPLibAll.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>alloc/heapsimple.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/alloc.cpp=>alloc/alloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.cpp=>common/misc/handletable.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.h=>common/misc/handletable.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletablectx.cpp=>common/misc/handletablectx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/once.cpp=>common/misc/once.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/term.cpp=>common/misc/term.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/memchr.cpp=>common/string/memchr.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/rand.cpp=>common/rand/rand.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/randadv.cpp=>common/rand/randadv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/rand/randparkmiller.cpp=>common/rand/randparkmiller.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/path/RTPathStripFilename.cpp=>common/path/RTPathStripFilename.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv4.cpp=>common/checksum/ipv4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv6.cpp=>common/checksum/ipv6.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/rand.h=>include/internal/rand.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \ + ${PATH_ROOT}/src/VBox/Runtime/generic/rtStrFormatKernelAddress-generic.cpp=>generic/rtStrFormatKernelAddress-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteUser-generic.cpp=>generic/RTLogWriteUser-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteDebugger-generic.cpp=>generic/RTLogWriteDebugger-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemFaster-generic.cpp=>generic/RTRandAdvCreateSystemFaster-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTRandAdvCreateSystemTruer-generic.cpp=>generic/RTRandAdvCreateSystemTruer-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTTimerCreate-generic.cpp=>generic/RTTimerCreate-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpGetArraySize-generic.cpp=>generic/RTMpGetArraySize-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpOnPair-generic.cpp=>generic/RTMpOnPair-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/timer-generic.cpp=>generic/timer-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/uuid-generic.cpp=>generic/uuid-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-ef-r0drv.cpp=>r0drv/alloc-ef-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/assert-r0drv-freebsd.c=>r0drv/freebsd/assert-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/alloc-r0drv-freebsd.c=>r0drv/freebsd/alloc-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/initterm-r0drv-freebsd.c=>r0drv/freebsd/initterm-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memobj-r0drv-freebsd.c=>r0drv/freebsd/memobj-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/memuserkernel-r0drv-freebsd.c=>r0drv/freebsd/memuserkernel-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/mp-r0drv-freebsd.c=>r0drv/freebsd/mp-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/process-r0drv-freebsd.c=>r0drv/freebsd/process-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semevent-r0drv-freebsd.c=>r0drv/freebsd/semevent-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semeventmulti-r0drv-freebsd.c=>r0drv/freebsd/semeventmulti-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semfastmutex-r0drv-freebsd.c=>r0drv/freebsd/semfastmutex-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/semmutex-r0drv-freebsd.c=>r0drv/freebsd/semmutex-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/spinlock-r0drv-freebsd.c=>r0drv/freebsd/spinlock-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread-r0drv-freebsd.c=>r0drv/freebsd/thread-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/thread2-r0drv-freebsd.c=>r0drv/freebsd/thread2-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/time-r0drv-freebsd.c=>r0drv/freebsd/time-r0drv-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/sleepqueue-r0drv-freebsd.h=>r0drv/freebsd/sleepqueue-r0drv-freebsd.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/mpnotification-r0drv-generic.cpp=>r0drv/generic/mpnotification-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/threadctxhooks-r0drv-generic.cpp=>r0drv/generic/threadctxhooks-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.cpp=>r0drv/generic/RTMpIsCpuWorkPending-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + +FILES_VBOXDRV_BIN=" \ +" diff --git a/src/VBox/HostDrivers/Support/linux/LnxPerfHack.cpp b/src/VBox/HostDrivers/Support/linux/LnxPerfHack.cpp new file mode 100644 index 00000000..056a2da8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/LnxPerfHack.cpp @@ -0,0 +1,446 @@ +/* $Id: LnxPerfHack.cpp $ */ +/** @file + * LnxPerfHack - Dirty hack to make perf find our .r0 modules. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/ldr.h> +#include <iprt/sort.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/mem.h> +#include <iprt/message.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define LNXPERFILEHDR_MAGIC RT_MAKE_U64_FROM_U8('P','E','R','F','I','L','E','2') + +#define LNXPERF_RECORD_MMAP 1 +#define LNXPERF_RECORD_MMAP2 10 + +#define LNXPERF_RECORD_MISC_CPUMODE_MASK UINT16_C(0x0007) +#define LNXPERF_RECORD_MISC_CPUMODE_UNKNOWN UINT16_C(0x0000) +#define LNXPERF_RECORD_MISC_KERNEL UINT16_C(0x0001) +#define LNXPERF_RECORD_MISC_USER UINT16_C(0x0002) +#define LNXPERF_RECORD_MISC_HYPERVISOR UINT16_C(0x0003) +#define LNXPERF_RECORD_MISC_GUEST_KERNEL UINT16_C(0x0004) +#define LNXPERF_RECORD_MISC_GUEST_USER UINT16_C(0x0005) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The file header. */ +typedef struct LNXPERFFILEHDR +{ + uint64_t uMagic; /**< LNXPERFILEHDR_MAGIC */ + uint64_t cbHdr; + uint64_t cbAttr; + struct LNXPERFFILESECTION + { + uint64_t off, cb; + } Attrs, Data, EventTypes; + uint64_t bmAddsFeatures[256/64]; +} LNXPERFFILEHDR; +typedef LNXPERFFILEHDR *PLNXPERFFILEHDR; + + +typedef struct LNXPERFRECORDHEADER +{ + uint32_t uType; + uint16_t fMisc; + uint16_t cb; +} LNXPERFRECORDHEADER; +AssertCompileSize(LNXPERFRECORDHEADER, 8); +typedef LNXPERFRECORDHEADER *PLNXPERFRECORDHEADER; + +typedef struct LNXPERFRECORDMMAP +{ + LNXPERFRECORDHEADER Hdr; + uint32_t pid; + uint32_t tid; + uint64_t uAddress; + uint64_t cbMapping; + uint64_t offFile; + RT_FLEXIBLE_ARRAY_EXTENSION + char szFilename[RT_FLEXIBLE_ARRAY]; +} LNXPERFRECORDMMAP; +typedef LNXPERFRECORDMMAP *PLNXPERFRECORDMMAP; + +typedef struct MYMODULE +{ + uint64_t uAddress; + uint64_t cbMapping; + uint64_t offFile; + const char *pszName; + uint32_t cchName; + uint16_t cbRecord; + uint64_t offRecord; +} MYMODULE; +typedef MYMODULE *PMYMODULE; + + +DECLCALLBACK(int) CompModuleRecordOffset(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PMYMODULE pLeft = (PMYMODULE)pvElement1; + PMYMODULE pRight = (PMYMODULE)pvElement2; + RT_NOREF(pvUser); + return pLeft->offRecord < pRight->offRecord ? -1 + : pLeft->offRecord > pRight->offRecord ? 1 : 0; + +} + + +DECLCALLBACK(int) CompModuleNameLengthDesc(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PMYMODULE pLeft = (PMYMODULE)pvElement1; + PMYMODULE pRight = (PMYMODULE)pvElement2; + RT_NOREF(pvUser); + return pLeft->cchName < pRight->cchName ? 1 + : pLeft->cchName > pRight->cchName ? -1 : 0; +} + + +/** @callback_method_impl{FNRTLDRENUMSEGS} */ +static DECLCALLBACK(int) SegmentEnumCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser) +{ + RT_NOREF(hLdrMod); + if (pSeg->pszName && RTStrStartsWith(pSeg->pszName, ".text")) + { + PMYMODULE pModEntry = (PMYMODULE)pvUser; + //pModEntry->offFile = pModEntry->uAddress; + pModEntry->uAddress += pSeg->RVA; + pModEntry->cbMapping = pSeg->cbMapped; + //pModEntry->offFile = pModEntry->uAddress - pSeg->LinkAddress; + pModEntry->offFile = RT_MAX(pSeg->offFile, 0); + return VINF_CALLBACK_RETURN; + } + return VINF_SUCCESS; +} + + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + static const RTGETOPTDEF s_aOptions[] = + { + { "--input", 'i', RTGETOPT_REQ_STRING }, + { "--output", 'o', RTGETOPT_REQ_STRING }, + { "--module", 'm', RTGETOPT_REQ_STRING }, + { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + }; + const char *pszInput = NULL; + const char *pszOutput = NULL; + unsigned cVerbosity = 0; + unsigned cModules = 0; + MYMODULE aModules[10]; + unsigned cSkipPatterns = 1; + const char *apszSkipPatterns[10] = { "*kallsyms*", }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + AssertRCReturn(rc, RTMsgErrorExitFailure("RTGetOptInit failed: %Rrc", rc)); + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'i': + pszInput = ValueUnion.psz; + break; + + case 'o': + pszOutput = ValueUnion.psz; + break; + + case 'm': + { + if (cModules >= RT_ELEMENTS(aModules)) + return RTMsgErrorExitFailure("Too many modules (max %u)", RT_ELEMENTS(aModules)); + aModules[cModules].pszName = ValueUnion.psz; + aModules[cModules].cchName = strlen(ValueUnion.psz); + + rc = RTGetOptFetchValue(&GetState, &ValueUnion, RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX); + if (RT_FAILURE(rc)) + return RTGetOptPrintError(rc, &ValueUnion); /*??*/ + aModules[cModules].uAddress = ValueUnion.u64; + + /* We need to find the .text section as that's what we'll be creating an mmap record for. */ + RTERRINFOSTATIC ErrInfo; + RTLDRMOD hLdrMod; + rc = RTLdrOpenEx(aModules[cModules].pszName, RTLDR_O_FOR_DEBUG, RTLDRARCH_WHATEVER, + &hLdrMod, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + { + return RTMsgErrorExitFailure("RTLdrOpenEx failed on '%s': %Rrc%#RTeim", + aModules[cModules].pszName, rc, &ErrInfo.Core); + } + rc = RTLdrEnumSegments(hLdrMod, SegmentEnumCallback, &aModules[cModules]); + if (rc != VINF_CALLBACK_RETURN) + return RTMsgErrorExitFailure("Failed to locate the .text section in '%s'!", aModules[cModules].pszName); + + aModules[cModules].cbRecord = 0; + aModules[cModules].offRecord = UINT64_MAX; + + cModules++; + break; + } + + case 'q': + cVerbosity = 0; + break; + + case 'v': + cVerbosity++; + break; + + case 'h': + RTPrintf("usage: %s -i <perf.in> -o <perf.out> -m vmmr0.r0 <loadaddress> [-m ..] [-v]\n" + "\n" + "It is recommended to use eu-unstrip to combine the VMMR0.r0 and\n" + "VMMR0.debug files into a single file again.\n" + "\n" + "For the 'annotation' feature of perf to work, it is necessary to patch\n" + "machine__process_kernel_mmap_event() in tools/perf/utils/machine.c, adding" + "the following after 'map->end = map->start + ...:\n" + "\n" + "/* bird: Transfer pgoff to reloc as dso__process_kernel_symbol overwrites\n" + " map->pgoff with sh_offset later. Kind of ASSUMES sh_offset == sh_addr. */\n" + "if (event->mmap.pgoff && map->dso && !map->dso->rel)\n" + " map->reloc = map->start - event->mmap.pgoff;\n" + , argv[0]); + return RTEXITCODE_SUCCESS; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (!pszInput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No input file specified"); + if (!pszOutput) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No output file specified"); + if (RTFileExists(pszOutput)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Output file exists: %s", pszOutput); + + + /* + * Open the input file and check the header. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszInput, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to open '%s': %Rrc", pszInput, rc); + + union + { + LNXPERFFILEHDR FileHdr; + uint8_t ab[_64K]; /* max record size */ + } u; + rc = RTFileRead(hFile, &u.FileHdr, sizeof(u.FileHdr), NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error reading file header: %Rrc", rc); + if (u.FileHdr.uMagic != LNXPERFILEHDR_MAGIC) + return RTMsgErrorExitFailure("Invalid file header magic: %.8Rhxs", &u.FileHdr.uMagic); + if (u.FileHdr.cbHdr != sizeof(u.FileHdr)) + return RTMsgErrorExitFailure("Invalid file header size: %RU64, expected %zu", u.FileHdr.cbHdr, sizeof(u.FileHdr)); + uint64_t const offData = u.FileHdr.Data.off; + uint64_t const cbData = u.FileHdr.Data.cb; + + /* + * Jump to the data portion and look for suitable kmod mmap + * records to replace. + * + * We sort the modules in descreasing name length first to make sure + * not to waste voluminous records on short replacement names. + */ + RTSortShell(aModules, cModules, sizeof(aModules[0]), CompModuleNameLengthDesc, NULL); + + unsigned cModulesLeft = cModules ? cModules : cVerbosity > 0; + uint64_t offRecord = 0; + while (offRecord + 32 < cbData && cModulesLeft > 0) + { + size_t cbToRead = (size_t)RT_MIN(cbData - offRecord, sizeof(u)); + rc = RTFileReadAt(hFile, offData + offRecord, &u, cbToRead, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("RTFileReadAt(,%llu,,%zu,) failed: %Rrc", offData + offRecord, cbToRead, rc); + + uint64_t const offEnd = offRecord + cbToRead; + uint8_t *pb = u.ab; + while (offRecord + 32 < offEnd) + { + PLNXPERFRECORDHEADER pRecHdr = (PLNXPERFRECORDHEADER)pb; + uint64_t const offNext = offRecord + pRecHdr->cb; + if (offNext > offEnd) + break; + if ( pRecHdr->uType == LNXPERF_RECORD_MMAP + && (pRecHdr->fMisc & LNXPERF_RECORD_MISC_CPUMODE_MASK) == LNXPERF_RECORD_MISC_KERNEL) + { + PLNXPERFRECORDMMAP pMmapRec = (PLNXPERFRECORDMMAP)pRecHdr; + if (cVerbosity > 0) + RTMsgInfo("MMAP: %016RX64 (%016RX64) LB %012RX64 %s\n", + pMmapRec->uAddress, pMmapRec->offFile, pMmapRec->cbMapping, pMmapRec->szFilename); + + bool fSkip = false; + for (unsigned i = 0; i < cSkipPatterns && !fSkip; i++) + fSkip = RTStrSimplePatternMatch(apszSkipPatterns[i], pMmapRec->szFilename); + + if (!fSkip) + { + /* Figure the max filename length we dare to put here. */ + size_t cchFilename = strlen(pMmapRec->szFilename); + cchFilename = RT_ALIGN_Z(cchFilename + 1, 8) - 1; + + for (unsigned i = 0; i < cModules; i++) + if ( aModules[i].offRecord == UINT64_MAX + && aModules[i].cchName <= cchFilename) + { + aModules[i].cbRecord = pRecHdr->cb; + aModules[i].offRecord = offData + offRecord; + cModulesLeft--; + if (cVerbosity > 0) + RTMsgInfo("Will replace module %s at offset %RU64 with %s\n", + pMmapRec->szFilename, offRecord, aModules[i].pszName); + break; + } + } + } + + /* Advance */ + pb += pRecHdr->cb; + offRecord = offNext; + } + } + + /* + * Only proceed if we found insertion points for all specified modules. + */ + if (cModulesLeft) + { + if (cModules) + { + RTMsgError("Unable to find suitable targets for:\n"); + for (unsigned i = 0; i < cModules; i++) + if (aModules[i].offRecord == UINT64_MAX) + RTMsgError(" %s\n", aModules[i].pszName); + } + else + RTMsgError("No modules given, so nothing to do.\n"); + return RTEXITCODE_FAILURE; + } + + /* + * Sort the modules by record offset to simplify the copying. + */ + RTSortShell(aModules, cModules, sizeof(aModules[0]), CompModuleRecordOffset, NULL); + + RTFILE hOutFile; + rc = RTFileOpen(&hOutFile, pszOutput, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to creating '%s' for the output: %Rrc", pszOutput, rc); + + unsigned iModule = 0; + uint64_t offNext = aModules[0].offRecord; + uint64_t off = 0; + for (;;) + { + Assert(off <= offNext); + + /* Read a chunk of data. Records we modify are read separately. */ + size_t cbToRead = RT_MIN(offNext - off, sizeof(u)); + if (cbToRead == 0) + cbToRead = aModules[iModule].cbRecord; + size_t cbActual = 0; + rc = RTFileReadAt(hFile, off, &u, cbToRead, &cbActual); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error reading %zu bytes at %RU64 in '%s': %Rrc", cbToRead, off, pszInput, rc); + + /* EOF? */ + if (cbActual == 0) + break; + + /* A record we wish to modify? */ + if (off == offNext) + { + if (cbActual != aModules[iModule].cbRecord) + return RTMsgErrorExitFailure("Internal error: cbActual=%zu cbRecord=%u off=%RU64\n", + cbActual, aModules[iModule].cbRecord, off); + + PLNXPERFRECORDMMAP pMmapRec = (PLNXPERFRECORDMMAP)&u.ab[0]; + strcpy(pMmapRec->szFilename, aModules[iModule].pszName); + pMmapRec->uAddress = aModules[iModule].uAddress; + pMmapRec->cbMapping = aModules[iModule].cbMapping; + pMmapRec->offFile = aModules[iModule].offFile; + RTMsgInfo("Done: %s\n", pMmapRec->szFilename); + + iModule++; + if (iModule < cModules) + offNext = aModules[iModule].offRecord; + else + offNext = UINT64_MAX; + } + + /* Write out the data. */ + rc = RTFileWrite(hOutFile, &u, cbActual, NULL); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error writing %zu bytes at %RU64 to '%s': %Rrc", cbActual, off, pszOutput, rc); + + /* Advance.*/ + off += cbActual; + } + + if (iModule != cModules) + return RTMsgErrorExitFailure("Internal error: iModule=%u cModules=%u\n", iModule, cModules); + + rc = RTFileClose(hOutFile); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Error closing output file '%s': %Rrc", pszOutput, rc); + + return RTEXITCODE_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/Support/linux/Makefile b/src/VBox/HostDrivers/Support/linux/Makefile new file mode 100644 index 00000000..900bcbd7 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/Makefile @@ -0,0 +1,208 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox Linux Host Driver. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile-header.gmk +VBOXDRV_DIR := $(VBOX_MODULE_SRC_DIR) + +VBOXMOD_NAME = vboxdrv +VBOXMOD_OBJS = \ + linux/SUPDrv-linux.o \ + SUPDrv.o \ + SUPDrvGip.o \ + SUPDrvSem.o \ + SUPDrvTracer.o \ + SUPLibAll.o \ + common/string/strformatrt.o + +ifndef VBOX_WITHOUT_COMBINED_SOURCES +VBOXMOD_OBJS += \ + combined-agnostic1.o \ + combined-agnostic2.o \ + combined-os-specific.o +else # VBOX_WITHOUT_COMBINED_SOURCES +VBOXMOD_OBJS += \ + r0drv/alloc-r0drv.o \ + r0drv/initterm-r0drv.o \ + r0drv/memobj-r0drv.o \ + r0drv/mpnotification-r0drv.o \ + r0drv/powernotification-r0drv.o \ + r0drv/linux/assert-r0drv-linux.o \ + r0drv/linux/alloc-r0drv-linux.o \ + r0drv/linux/initterm-r0drv-linux.o \ + r0drv/linux/memobj-r0drv-linux.o \ + r0drv/linux/memuserkernel-r0drv-linux.o \ + r0drv/linux/mp-r0drv-linux.o \ + r0drv/linux/mpnotification-r0drv-linux.o \ + r0drv/linux/process-r0drv-linux.o \ + r0drv/linux/rtStrFormatKernelAddress-r0drv-linux.o \ + r0drv/linux/semevent-r0drv-linux.o \ + r0drv/linux/semeventmulti-r0drv-linux.o \ + r0drv/linux/semfastmutex-r0drv-linux.o \ + r0drv/linux/semmutex-r0drv-linux.o \ + r0drv/linux/spinlock-r0drv-linux.o \ + r0drv/linux/thread-r0drv-linux.o \ + r0drv/linux/thread2-r0drv-linux.o \ + r0drv/linux/threadctxhooks-r0drv-linux.o \ + r0drv/linux/time-r0drv-linux.o \ + r0drv/linux/timer-r0drv-linux.o \ + r0drv/generic/semspinmutex-r0drv-generic.o \ + common/alloc/alloc.o \ + common/checksum/crc32.o \ + common/checksum/ipv4.o \ + common/checksum/ipv6.o \ + common/err/RTErrConvertFromErrno.o \ + common/err/RTErrConvertToErrno.o \ + common/err/errinfo.o \ + common/log/log.o \ + common/log/logellipsis.o \ + common/log/logrel.o \ + common/log/logrelellipsis.o \ + common/log/logcom.o \ + common/log/logformat.o \ + common/log/RTLogCreateEx.o \ + common/misc/RTAssertMsg1Weak.o \ + common/misc/RTAssertMsg2.o \ + common/misc/RTAssertMsg2Add.o \ + common/misc/RTAssertMsg2AddWeak.o \ + common/misc/RTAssertMsg2AddWeakV.o \ + common/misc/RTAssertMsg2Weak.o \ + common/misc/RTAssertMsg2WeakV.o \ + common/misc/assert.o \ + common/misc/handletable.o \ + common/misc/handletablectx.o \ + common/misc/thread.o \ + common/string/RTStrCat.o \ + common/string/RTStrCopy.o \ + common/string/RTStrCopyEx.o \ + common/string/RTStrCopyP.o \ + common/string/RTStrEnd.o \ + common/string/RTStrNCmp.o \ + common/string/RTStrNLen.o \ + common/string/stringalloc.o \ + common/string/strformat.o \ + common/string/RTStrFormat.o \ + common/string/strformatnum.o \ + common/string/strformattype.o \ + common/string/strprintf.o \ + common/string/strprintf-ellipsis.o \ + common/string/strprintf2.o \ + common/string/strprintf2-ellipsis.o \ + common/string/strtonum.o \ + common/table/avlpv.o \ + common/time/time.o \ + r0drv/linux/RTLogWriteDebugger-r0drv-linux.o \ + generic/RTAssertShouldPanic-generic.o \ + generic/RTLogWriteStdErr-stub-generic.o \ + generic/RTLogWriteStdOut-stub-generic.o \ + generic/RTLogWriteUser-generic.o \ + generic/RTMpGetArraySize-generic.o \ + generic/RTMpGetCoreCount-generic.o \ + generic/RTSemEventWait-2-ex-generic.o \ + generic/RTSemEventWaitNoResume-2-ex-generic.o \ + generic/RTSemEventMultiWait-2-ex-generic.o \ + generic/RTSemEventMultiWaitNoResume-2-ex-generic.o \ + generic/RTTimerCreate-generic.o \ + generic/errvars-generic.o \ + generic/mppresent-generic.o \ + generic/uuid-generic.o \ + VBox/log-vbox.o \ + VBox/RTLogWriteVmm-amd64-x86.o + ifeq ($(VBOX_KBUILD_TARGET_ARCH),amd64) +VBOXMOD_OBJS += common/alloc/heapsimple.o + endif +endif # VBOX_WITHOUT_COMBINED_SOURCES +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + math/gcc/divdi3.o \ + math/gcc/divmoddi4.o \ + math/gcc/moddi3.o \ + math/gcc/qdivrem.o \ + math/gcc/udivdi3.o \ + math/gcc/udivmoddi4.o \ + math/gcc/divdi3.o \ + math/gcc/umoddi3.o +endif +ifdef VBOX_WITH_NATIVE_DTRACE +VBOXMOD_OBJS += SUPDrvDTrace.o +endif + +VBOXMOD_INCL = \ + $(VBOXDRV_DIR) \ + $(VBOXDRV_DIR)include \ + $(VBOXDRV_DIR)r0drv/linux +ifdef VBOX_WITH_NATIVE_DTRACE +VBOXMOD_INCL += \ + /usr/include/linux \ + /usr/include +endif + +VBOXMOD_DEFS = \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + RT_WITH_VBOX \ + VBOX_WITH_HARDENING \ + SUPDRV_WITH_RELEASE_LOGGER \ + VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV \ + IPRT_WITHOUT_EFLAGS_AC_PRESERVING \ + VBOX_WITH_64_BITS_GUESTS # <-- must be consistent with Config.kmk! +ifndef CONFIG_VBOXDRV_FIXEDMAJOR +VBOXMOD_DEFS += CONFIG_VBOXDRV_AS_MISC +endif +ifdef VBOX_WITH_NATIVE_DTRACE +VBOXMOD_DEFS += VBOX_WITH_NATIVE_DTRACE +endif +ifdef VBOX_WITH_TEXT_MODMEM_HACK +VBOXMOD_DEFS += RTMEMALLOC_EXEC_HEAP VBOX_WITH_TEXT_MODMEM_HACK +endif +VBOXMOD_CFLAGS = -include $(VBOXDRV_DIR)include/VBox/SUPDrvMangling.h \ + -fno-omit-frame-pointer -fno-pie -Wno-declaration-after-statement + + +include $(obj)/Makefile-footer.gmk + +check: $(VBOXMOD_0_TARGET) + @if ! readelf -p __ksymtab_strings vboxdrv.ko | grep -E "\[.*\] *(RT|g_..*RT.*)"; then \ + echo "All exported IPRT symbols are properly renamed!"; \ + else \ + echo "error: Some exported IPRT symbols was not properly renamed! See above." >&2; \ + false; \ + fi + diff --git a/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vboxddr0.gmk b/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vboxddr0.gmk new file mode 100644 index 00000000..08c7f604 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vboxddr0.gmk @@ -0,0 +1,43 @@ +# $Id: Makefile-vbox_vboxddr0.gmk $ +## @file +# Makefile for the VBoxDDR0.r0 wrapper module. +# + +# +# Copyright (C) 2021-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +VBOXMOD_NAME := vbox_vboxddr0 +WRAPPED_MODULE_NAME := VBoxDDR0 +WRAPPED_MODULE_FLAGS := 0 +WRAPPER_NEED_VMMR0 := yes + +include $(dir $(lastword $(MAKEFILE_LIST)))Makefile-wrapper.gmk + diff --git a/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vmmr0.gmk b/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vmmr0.gmk new file mode 100644 index 00000000..c338d025 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/Makefile-vbox_vmmr0.gmk @@ -0,0 +1,44 @@ +# $Id: Makefile-vbox_vmmr0.gmk $ +## @file +# Makefile for the VMMR0.r0 wrapper module. +# + +# +# Copyright (C) 2021-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +VBOXMOD_NAME := vbox_vmmr0 +WRAPPED_MODULE_NAME := VMMR0 +WRAPPED_MODULE_FLAGS := SUPLDRWRAPPEDMODULE_F_VMMR0 +WRAPPED_MODULE_IS_VMMR0 := yes +WRAPPED_MODULE_LINUX_EXPORTS := yes + +include $(dir $(lastword $(MAKEFILE_LIST)))Makefile-wrapper.gmk + diff --git a/src/VBox/HostDrivers/Support/linux/Makefile-wrapper.gmk b/src/VBox/HostDrivers/Support/linux/Makefile-wrapper.gmk new file mode 100644 index 00000000..a47d55be --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/Makefile-wrapper.gmk @@ -0,0 +1,166 @@ +# $Id: Makefile-wrapper.gmk $ +## @file +# Makefile template for a wrapper module. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# Check template parameters. +ifndef WRAPPED_MODULE_NAME +$(error WRAPPED_MODULE_NAME not defined) +endif +ifndef VBOXMOD_NAME +$(error VBOXMOD_NAME not defined) +endif + + +# Linux kbuild sets this to our source directory if we are called from there +ifndef VBOX_MODULE_SRC_DIR + VBOX_MODULE_SRC_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))/ +endif +VBOXDRV_DIR := $(abspath $(VBOX_MODULE_SRC_DIR)../vboxdrv/)/ +VBOX_VMMR0_DIR := $(abspath $(VBOX_MODULE_SRC_DIR)../vbox_vmmr0/)/ +include $(VBOXDRV_DIR)Makefile-header.gmk + +# All of these wrappers depend on +ifndef KBUILD_EXTRA_SYMBOLS +KBUILD_EXTRA_SYMBOLS := $(VBOXDRV_DIR)Module.symvers + ifdef WRAPPER_NEED_VMMR0 +KBUILD_EXTRA_SYMBOLS += $(VBOX_VMMR0_DIR)Module.symvers + endif +endif + +VBOXMOD_OBJS = \ + SUPWrapperMod-linux.o \ + $(WRAPPED_MODULE_NAME).o + +VBOXMOD_INCL = \ + $(VBOXDRV_DIR) \ + $(VBOXDRV_DIR)include \ + $(VBOXDRV_DIR)r0drv/linux + +VBOXMOD_DEFS += \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + \ + WRAPPED_MODULE_NAME=\"$(WRAPPED_MODULE_NAME).r0\" \ + WRAPPED_MODULE_SYMBOL_INCLUDE=\"$(WRAPPED_MODULE_NAME)-symbols.h\" +ifdef WRAPPED_MODULE_FLAGS +VBOXMOD_DEFS += WRAPPED_MODULE_FLAGS="$(WRAPPED_MODULE_FLAGS)" +endif +ifdef WRAPPED_MODULE_LINUX_EXPORTS +VBOXMOD_DEFS += WRAPPED_MODULE_LINUX_EXPORTS +endif +ifdef WRAPPED_MODULE_SRV_REQ_HANDLER +VBOXMOD_DEFS += WRAPPED_MODULE_SRV_REQ_HANDLER="$(WRAPPED_MODULE_SRV_REQ_HANDLER)" +endif +ifdef WRAPPED_MODULE_IS_VMMR0 +VBOXMOD_DEFS += WRAPPED_MODULE_VMMR0_ENTRY_FAST=VMMR0EntryFast +VBOXMOD_DEFS += WRAPPED_MODULE_VMMR0_ENTRY_EX=VMMR0EntryEx +endif +ifdef WRAPPED_MODULE_NO_INIT +VBOXMOD_DEFS += WRAPPED_MODULE_INIT=NULL +endif +ifdef WRAPPED_MODULE_NO_TERM +VBOXMOD_DEFS += WRAPPED_MODULE_TERM=NULL +endif +ifdef WRAPPED_MODULE_LICENSE_PROPRIETARY +VBOXMOD_DEFS += WRAPPED_MODULE_LICENSE_PROPRIETARY +endif + +VBOXMOD_CFLAGS = -include $(VBOXDRV_DIR)include/VBox/SUPDrvMangling.h \ + -fno-omit-frame-pointer -fno-pie -Wno-declaration-after-statement + +## @todo cleanup + +include $(VBOXDRV_DIR)Makefile-footer.gmk + + +# +# Custom rules (some of this could later be done before install). +# +SUPWrapperMod-linux.c \ +$(VBOX_MODULE_SRC_DIR)SUPWrapperMod-linux.c \ +$(VBOX_MODULE_SRC_DIR)SUPWrapperMod-linux.o \ +: $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME)-symbols.h + +$(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME)-symbols.h: \ + $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).r0 \ + $(VBOX_MODULE_SRC_DIR)Makefile-wrapper.gmk \ + $(VBOX_MODULE_SRC_DIR)Makefile + rm -f -- "$@" "$@-tmp1" "$@-tmp2" + objdump --syms "$<" > "$@-tmp1" + sed -e '/[[:space:]].hidden[[:space:]]/d' \ + -e '/[[:space:]]\*UND\*[[:space:]]/d' \ + -e '/[[:space:]]vboxr0mod_/d' \ + -e '/^[[:xdigit:]][[:xdigit:]]*[[:space:]][gu]/!d' \ + -e 's/^\([[:xdigit:]]*\)[[:space:]].*000[[:xdigit:]]*[[:space:]]\([_a-zA-Z].*\)$$/SYMBOL_ENTRY(\2)/' \ + -e '/SYMBOL_ENTRY(ModuleInit)/d' \ + -e '/SYMBOL_ENTRY(ModuleTerm)/d' \ + "$@-tmp1" > "$@-tmp2" + sort "$@-tmp2" > "$@" + rm -f -- "$@-tmp1" "$@-tmp2" + + +$(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).mangle-symbols: \ + $(VBOXDRV_DIR)Module.symvers \ + $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME)-symbols.h \ + $(VBOX_MODULE_SRC_DIR)Makefile-wrapper.gmk \ + $(VBOX_MODULE_SRC_DIR)Makefile + sed -e 's/SYMBOL_ENTRY(\([^)]*\))/\/\1 VBoxHost_\1\/d/' \ + "$(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME)-symbols.h" \ + > "$@-ignore.sed" + sed -e '/[[:space:]]VBoxHost_/!d' \ + -e 's/^0x[[:xdigit:]]*[[:space:]]VBoxHost_\([^[:space:]]*\)[[:space:]].*$$/\1 VBoxHost_\1/' \ + -f "$@-ignore.sed" \ + "$<" \ + > "$@" + +$(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).o: \ + $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).r0 \ + $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).debug \ + $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).mangle-symbols \ + $(VBOX_MODULE_SRC_DIR)Makefile-wrapper.gmk \ + $(VBOX_MODULE_SRC_DIR)Makefile \ + $(if $(CONFIG_UNWINDER_ORC),$(objtool_dep),) + rm -f -- "$@" "$@-tmp" + eu-unstrip -o "$@-tmp" $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).r0 $(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).debug + objcopy --redefine-syms=$(VBOX_MODULE_SRC_DIR)$(WRAPPED_MODULE_NAME).mangle-symbols "$@-tmp" "$@" + rm -f -- "$@-tmp" +ifdef CONFIG_UNWINDER_ORC # Must generate the ORC sections. + $(__objtool_obj) $(objtool_args) $@ +endif + touch $(VBOX_MODULE_SRC_DIR).$(WRAPPED_MODULE_NAME).o.cmd + diff --git a/src/VBox/HostDrivers/Support/linux/Makefile.kup b/src/VBox/HostDrivers/Support/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c new file mode 100644 index 00000000..2a77412c --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c @@ -0,0 +1,1796 @@ +/* $Id: SUPDrv-linux.c $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Linux specifics. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../SUPDrvInternal.h" +#include "the-linux-kernel.h" +#include "version-generated.h" +#include "product-generated.h" +#include "revision-generated.h" + +#include <iprt/assert.h> +#include <iprt/spinlock.h> +#include <iprt/semaphore.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/thread.h> +#include <VBox/err.h> +#include <iprt/mem.h> +#include <VBox/log.h> +#include <iprt/mp.h> + +/** @todo figure out the exact version number */ +#if RTLNX_VER_MIN(2,6,16) +# include <iprt/power.h> +# define VBOX_WITH_SUSPEND_NOTIFICATION +#endif + +#include <linux/sched.h> +#include <linux/miscdevice.h> +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +# include <linux/platform_device.h> +#endif +#if (RTLNX_VER_MIN(2,6,28)) && defined(SUPDRV_WITH_MSR_PROBER) +# define SUPDRV_LINUX_HAS_SAFE_MSR_API +# include <asm/msr.h> +#endif + +#include <asm/desc.h> + +#include <iprt/asm-amd64-x86.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* check kernel version */ +# ifndef SUPDRV_AGNOSTIC +# if RTLNX_VER_MAX(2,6,0) +# error Unsupported kernel version! +# endif +# endif + +#ifdef CONFIG_X86_HIGH_ENTRY +# error "CONFIG_X86_HIGH_ENTRY is not supported by VBoxDrv at this time." +#endif + +/* We cannot include x86.h, so we copy the defines we need here: */ +#define X86_EFL_IF RT_BIT(9) +#define X86_EFL_AC RT_BIT(18) +#define X86_EFL_DF RT_BIT(10) +#define X86_EFL_IOPL (RT_BIT(12) | RT_BIT(13)) + +/* To include the version number of VirtualBox into kernel backtraces: */ +#define VBoxDrvLinuxVersion RT_CONCAT3(RT_CONCAT(VBOX_VERSION_MAJOR, _), \ + RT_CONCAT(VBOX_VERSION_MINOR, _), \ + VBOX_VERSION_BUILD) +#define VBoxDrvLinuxIOCtl RT_CONCAT(VBoxDrvLinuxIOCtl_,VBoxDrvLinuxVersion) + +/* Once externally provided, this string will be printed into kernel log on + * module start together with the rest of versioning information. */ +#ifndef VBOX_EXTRA_VERSION_STRING +# define VBOX_EXTRA_VERSION_STRING "" +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#if RTLNX_VER_MIN(5,0,0) +/** Wrapper module list entry. */ +typedef struct SUPDRVLNXMODULE +{ + RTLISTNODE ListEntry; + struct module *pModule; +} SUPDRVLNXMODULE; +/** Pointer to a wrapper module list entry. */ +typedef SUPDRVLNXMODULE *PSUPDRVLNXMODULE; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxDrvLinuxInit(void); +static void __exit VBoxDrvLinuxUnload(void); +static int VBoxDrvLinuxCreateSys(struct inode *pInode, struct file *pFilp); +static int VBoxDrvLinuxCreateUsr(struct inode *pInode, struct file *pFilp); +static int VBoxDrvLinuxClose(struct inode *pInode, struct file *pFilp); +#ifdef HAVE_UNLOCKED_IOCTL +static long VBoxDrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#else +static int VBoxDrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg); +#endif +static int VBoxDrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PSUPDRVSESSION pSession); +static int VBoxDrvLinuxErr2LinuxErr(int); +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +static int VBoxDrvProbe(struct platform_device *pDev); +# if RTLNX_VER_MIN(2,6,30) +static int VBoxDrvSuspend(struct device *pDev); +static int VBoxDrvResume(struct device *pDev); +# else +static int VBoxDrvSuspend(struct platform_device *pDev, pm_message_t State); +static int VBoxDrvResume(struct platform_device *pDev); +# endif +static void VBoxDevRelease(struct device *pDev); +#endif +#if RTLNX_VER_MIN(5,0,0) +static int supdrvLinuxLdrModuleNotifyCallback(struct notifier_block *pBlock, + unsigned long uModuleState, void *pvModule); +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Device extention & session data association structure. + */ +static SUPDRVDEVEXT g_DevExt; + +/** Module parameter. + * Not prefixed because the name is used by macros and the end of this file. */ +static int force_async_tsc = 0; + +/** The system device name. */ +#define DEVICE_NAME_SYS "vboxdrv" +/** The user device name. */ +#define DEVICE_NAME_USR "vboxdrvu" + +/** The file_operations structure. */ +static struct file_operations gFileOpsVBoxDrvSys = +{ + owner: THIS_MODULE, + open: VBoxDrvLinuxCreateSys, + release: VBoxDrvLinuxClose, +#ifdef HAVE_UNLOCKED_IOCTL + unlocked_ioctl: VBoxDrvLinuxIOCtl, +#else + ioctl: VBoxDrvLinuxIOCtl, +#endif +}; + +/** The file_operations structure. */ +static struct file_operations gFileOpsVBoxDrvUsr = +{ + owner: THIS_MODULE, + open: VBoxDrvLinuxCreateUsr, + release: VBoxDrvLinuxClose, +#ifdef HAVE_UNLOCKED_IOCTL + unlocked_ioctl: VBoxDrvLinuxIOCtl, +#else + ioctl: VBoxDrvLinuxIOCtl, +#endif +}; + +/** The miscdevice structure for vboxdrv. */ +static struct miscdevice gMiscDeviceSys = +{ + minor: MISC_DYNAMIC_MINOR, + name: DEVICE_NAME_SYS, + fops: &gFileOpsVBoxDrvSys, +# if RTLNX_VER_MAX(2,6,18) + devfs_name: DEVICE_NAME_SYS, +# endif +}; +/** The miscdevice structure for vboxdrvu. */ +static struct miscdevice gMiscDeviceUsr = +{ + minor: MISC_DYNAMIC_MINOR, + name: DEVICE_NAME_USR, + fops: &gFileOpsVBoxDrvUsr, +# if RTLNX_VER_MAX(2,6,18) + devfs_name: DEVICE_NAME_USR, +# endif +}; + + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + +# if RTLNX_VER_MIN(2,6,30) +static struct dev_pm_ops gPlatformPMOps = +{ + .suspend = VBoxDrvSuspend, /* before entering deep sleep */ + .resume = VBoxDrvResume, /* after wakeup from deep sleep */ + .freeze = VBoxDrvSuspend, /* before creating hibernation image */ + .restore = VBoxDrvResume, /* after waking up from hibernation */ +}; +# endif + +static struct platform_driver gPlatformDriver = +{ + .probe = VBoxDrvProbe, +# if RTLNX_VER_MAX(2,6,30) + .suspend = VBoxDrvSuspend, + .resume = VBoxDrvResume, +# endif + /** @todo .shutdown? */ + .driver = + { + .name = "vboxdrv", +# if RTLNX_VER_MIN(2,6,30) + .pm = &gPlatformPMOps, +# endif + } +}; + +static struct platform_device gPlatformDevice = +{ + .name = "vboxdrv", + .dev = + { + .release = VBoxDevRelease + } +}; + +#endif /* VBOX_WITH_SUSPEND_NOTIFICATION */ + +#if RTLNX_VER_MIN(5,0,0) +/** Module load/unload notification registration record. */ +static struct notifier_block g_supdrvLinuxModuleNotifierBlock = +{ + .notifier_call = supdrvLinuxLdrModuleNotifyCallback, + .priority = 0 +}; +/** Spinlock protecting g_supdrvLinuxWrapperModuleList. */ +static spinlock_t g_supdrvLinuxWrapperModuleSpinlock; +/** List of potential wrapper modules (PSUPDRVLNXMODULE). */ +static RTLISTANCHOR g_supdrvLinuxWrapperModuleList; +#endif + + +/** Get the kernel UID for the current process. */ +DECLINLINE(RTUID) vboxdrvLinuxKernUid(void) +{ +#if RTLNX_VER_MIN(2,6,29) +# if RTLNX_VER_MIN(3,5,0) + return __kuid_val(current->cred->uid); +# else + return current->cred->uid; +# endif +#else + return current->uid; +#endif +} + + +/** Get the kernel GID for the current process. */ +DECLINLINE(RTGID) vboxdrvLinuxKernGid(void) +{ +#if RTLNX_VER_MIN(2,6,29) +# if RTLNX_VER_MIN(3,5,0) + return __kgid_val(current->cred->gid); +# else + return current->cred->gid; +# endif +#else + return current->gid; +#endif +} + + +#ifdef VBOX_WITH_HARDENING +/** Get the effective UID within the current user namespace. */ +DECLINLINE(RTUID) vboxdrvLinuxEuidInNs(void) +{ +# if RTLNX_VER_MIN(2,6,29) +# if RTLNX_VER_MIN(3,5,0) + return from_kuid(current_user_ns(), current->cred->euid); +# else + return current->cred->euid; +# endif +# else + return current->euid; +# endif +} +#endif + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxDrvLinuxInit(void) +{ + int rc; + +#if RTLNX_VER_MIN(5,0,0) + spin_lock_init(&g_supdrvLinuxWrapperModuleSpinlock); + RTListInit(&g_supdrvLinuxWrapperModuleList); +#endif + + /* + * Check for synchronous/asynchronous TSC mode. + */ + printk(KERN_DEBUG "vboxdrv: Found %u processor cores/threads\n", (unsigned)RTMpGetOnlineCount()); + rc = misc_register(&gMiscDeviceSys); + if (rc) + { + printk(KERN_ERR "vboxdrv: Can't register system misc device! rc=%d\n", rc); + return rc; + } + rc = misc_register(&gMiscDeviceUsr); + if (rc) + { + printk(KERN_ERR "vboxdrv: Can't register user misc device! rc=%d\n", rc); + misc_deregister(&gMiscDeviceSys); + return rc; + } + if (!rc) + { + /* + * Initialize the runtime. + * On AMD64 we'll have to donate the high rwx memory block to the exec allocator. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxDrv::ModuleInit\n")); + + /* + * Initialize the device extension. + */ + rc = supdrvInitDevExt(&g_DevExt, sizeof(SUPDRVSESSION)); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + rc = platform_driver_register(&gPlatformDriver); + if (rc == 0) + { + rc = platform_device_register(&gPlatformDevice); + if (rc == 0) +#endif + { +#if RTLNX_VER_MIN(5,0,0) + /* + * Register the module notifier. + */ + int rc2 = register_module_notifier(&g_supdrvLinuxModuleNotifierBlock); + if (rc2) + printk(KERN_WARNING "vboxdrv: failed to register module notifier! rc2=%d\n", rc2); +#endif + + + printk(KERN_INFO "vboxdrv: TSC mode is %s, tentative frequency %llu Hz\n", + SUPGetGIPModeName(g_DevExt.pGip), g_DevExt.pGip->u64CpuHz); + LogFlow(("VBoxDrv::ModuleInit returning %#x\n", rc)); + printk(KERN_DEBUG "vboxdrv: Successfully loaded version " + VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV) + VBOX_EXTRA_VERSION_STRING + " (interface " RT_XSTR(SUPDRV_IOC_VERSION) ")\n"); + return rc; + } +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + else + platform_driver_unregister(&gPlatformDriver); + } +#endif + } + + rc = -EINVAL; + RTR0TermForced(); + } + else + rc = -EINVAL; + + /* + * Failed, cleanup and return the error code. + */ + } + misc_deregister(&gMiscDeviceSys); + misc_deregister(&gMiscDeviceUsr); + Log(("VBoxDrv::ModuleInit returning %#x (minor:%d & %d)\n", rc, gMiscDeviceSys.minor, gMiscDeviceUsr.minor)); + return rc; +} + + +/** + * Unload the module. + */ +static void __exit VBoxDrvLinuxUnload(void) +{ + Log(("VBoxDrvLinuxUnload\n")); + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION + platform_device_unregister(&gPlatformDevice); + platform_driver_unregister(&gPlatformDriver); +#endif + +#if RTLNX_VER_MIN(5,0,0) + /* + * Kick the list of potential wrapper modules. + */ + unregister_module_notifier(&g_supdrvLinuxModuleNotifierBlock); + + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); + while (!RTListIsEmpty(&g_supdrvLinuxWrapperModuleList)) + { + PSUPDRVLNXMODULE pCur = RTListRemoveFirst(&g_supdrvLinuxWrapperModuleList, SUPDRVLNXMODULE, ListEntry); + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + + pCur->pModule = NULL; + RTMemFree(pCur); + + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); + } + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); +#endif + + /* + * I Don't think it's possible to unload a driver which processes have + * opened, at least we'll blindly assume that here. + */ + misc_deregister(&gMiscDeviceUsr); + misc_deregister(&gMiscDeviceSys); + + /* + * Destroy GIP, delete the device extension and terminate IPRT. + */ + supdrvDeleteDevExt(&g_DevExt); + RTR0TermForced(); +} + + +/** + * Common open code. + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + * @param fUnrestricted Indicates which device node which was opened. + */ +static int vboxdrvLinuxCreateCommon(struct inode *pInode, struct file *pFilp, bool fUnrestricted) +{ + int rc; + PSUPDRVSESSION pSession; + Log(("VBoxDrvLinuxCreate: pFilp=%p pid=%d/%d %s\n", pFilp, RTProcSelf(), current->pid, current->comm)); + +#ifdef VBOX_WITH_HARDENING + /* + * Only root is allowed to access the unrestricted device, enforce it! + */ + if ( fUnrestricted + && vboxdrvLinuxEuidInNs() != 0 /* root */ ) + { + Log(("VBoxDrvLinuxCreate: euid=%d, expected 0 (root)\n", vboxdrvLinuxEuidInNs())); + return -EPERM; + } +#endif /* VBOX_WITH_HARDENING */ + + /* + * Call common code for the rest. + */ + rc = supdrvCreateSession(&g_DevExt, true /* fUser */, fUnrestricted, &pSession); + if (!rc) + { + pSession->Uid = vboxdrvLinuxKernUid(); + pSession->Gid = vboxdrvLinuxKernGid(); + } + + pFilp->private_data = pSession; + + Log(("VBoxDrvLinuxCreate: g_DevExt=%p pSession=%p rc=%d/%d (pid=%d/%d %s)\n", + &g_DevExt, pSession, rc, VBoxDrvLinuxErr2LinuxErr(rc), + RTProcSelf(), current->pid, current->comm)); + return VBoxDrvLinuxErr2LinuxErr(rc); +} + + +/** /dev/vboxdrv. */ +static int VBoxDrvLinuxCreateSys(struct inode *pInode, struct file *pFilp) +{ + return vboxdrvLinuxCreateCommon(pInode, pFilp, true); +} + + +/** /dev/vboxdrvu. */ +static int VBoxDrvLinuxCreateUsr(struct inode *pInode, struct file *pFilp) +{ + return vboxdrvLinuxCreateCommon(pInode, pFilp, false); +} + + +/** + * Close device. + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int VBoxDrvLinuxClose(struct inode *pInode, struct file *pFilp) +{ + Log(("VBoxDrvLinuxClose: pFilp=%p pSession=%p pid=%d/%d %s\n", + pFilp, pFilp->private_data, RTProcSelf(), current->pid, current->comm)); + supdrvSessionRelease((PSUPDRVSESSION)pFilp->private_data); + pFilp->private_data = NULL; + return 0; +} + + +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +/** + * Dummy device release function. We have to provide this function, + * otherwise the kernel will complain. + * + * @param pDev Pointer to the platform device. + */ +static void VBoxDevRelease(struct device *pDev) +{ +} + +/** + * Dummy probe function. + * + * @param pDev Pointer to the platform device. + */ +static int VBoxDrvProbe(struct platform_device *pDev) +{ + return 0; +} + +/** + * Suspend callback. + * @param pDev Pointer to the platform device. + * @param State Message type, see Documentation/power/devices.txt. + * Ignored. + */ +# if RTLNX_VER_MIN(2,6,30) && !defined(DOXYGEN_RUNNING) +static int VBoxDrvSuspend(struct device *pDev) +# else +static int VBoxDrvSuspend(struct platform_device *pDev, pm_message_t State) +# endif +{ + RTPowerSignalEvent(RTPOWEREVENT_SUSPEND); + return 0; +} + +/** + * Resume callback. + * + * @param pDev Pointer to the platform device. + */ +# if RTLNX_VER_MIN(2,6,30) +static int VBoxDrvResume(struct device *pDev) +# else +static int VBoxDrvResume(struct platform_device *pDev) +# endif +{ + RTPowerSignalEvent(RTPOWEREVENT_RESUME); + return 0; +} +#endif /* VBOX_WITH_SUSPEND_NOTIFICATION */ + + +/** + * Device I/O Control entry point. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + */ +#if defined(HAVE_UNLOCKED_IOCTL) || defined(DOXYGEN_RUNNING) +static long VBoxDrvLinuxIOCtl(struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#else +static int VBoxDrvLinuxIOCtl(struct inode *pInode, struct file *pFilp, unsigned int uCmd, unsigned long ulArg) +#endif +{ + PSUPDRVSESSION pSession = (PSUPDRVSESSION)pFilp->private_data; + int rc; +#ifndef VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV +# if defined(VBOX_STRICT) || defined(VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV) + RTCCUINTREG fSavedEfl; + + /* + * Refuse all I/O control calls if we've ever detected EFLAGS.AC being cleared. + * + * This isn't a problem, as there is absolutely nothing in the kernel context that + * depend on user context triggering cleanups. That would be pretty wild, right? + */ + if (RT_UNLIKELY(g_DevExt.cBadContextCalls > 0)) + { + SUPR0Printf("VBoxDrvLinuxIOCtl: EFLAGS.AC=0 detected %u times, refusing all I/O controls!\n", g_DevExt.cBadContextCalls); + return ESPIPE; + } + + fSavedEfl = ASMAddFlags(X86_EFL_AC); +# else + stac(); +# endif +#endif + + /* + * Deal with the two high-speed IOCtl that takes it's arguments from + * the session and iCmd, and only returns a VBox status code. + */ + AssertCompile(_IOC_NRSHIFT == 0 && _IOC_NRBITS == 8); +#ifdef HAVE_UNLOCKED_IOCTL + if (RT_LIKELY( (unsigned int)(uCmd - SUP_IOCTL_FAST_DO_FIRST) < (unsigned int)32 + && pSession->fUnrestricted)) + rc = supdrvIOCtlFast(uCmd - SUP_IOCTL_FAST_DO_FIRST, ulArg, &g_DevExt, pSession); + else + rc = VBoxDrvLinuxIOCtlSlow(pFilp, uCmd, ulArg, pSession); +#else /* !HAVE_UNLOCKED_IOCTL */ + unlock_kernel(); + if (RT_LIKELY( (unsigned int)(uCmd - SUP_IOCTL_FAST_DO_FIRST) < (unsigned int)32 + && pSession->fUnrestricted)) + rc = supdrvIOCtlFast(uCmd - SUP_IOCTL_FAST_DO_FIRST, ulArg, &g_DevExt, pSession); + else + rc = VBoxDrvLinuxIOCtlSlow(pFilp, uCmd, ulArg, pSession); + lock_kernel(); +#endif /* !HAVE_UNLOCKED_IOCTL */ + +#ifndef VBOX_WITHOUT_EFLAGS_AC_SET_IN_VBOXDRV +# if defined(VBOX_STRICT) || defined(VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV) + /* + * Before we restore AC and the rest of EFLAGS, check if the IOCtl handler code + * accidentially modified it or some other important flag. + */ + if (RT_UNLIKELY( (ASMGetFlags() & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF)) + != ((fSavedEfl & (X86_EFL_AC | X86_EFL_IF | X86_EFL_DF)) | X86_EFL_AC) )) + { + char szTmp[48]; + RTStrPrintf(szTmp, sizeof(szTmp), "uCmd=%#x: %#x->%#x!", _IOC_NR(uCmd), (uint32_t)fSavedEfl, (uint32_t)ASMGetFlags()); + supdrvBadContext(&g_DevExt, "SUPDrv-linux.c", __LINE__, szTmp); + } + ASMSetFlags(fSavedEfl); +# else + clac(); +# endif +#endif + return rc; +} + + +/** + * Device I/O Control entry point. + * + * @param pFilp Associated file pointer. + * @param uCmd The function specified to ioctl(). + * @param ulArg The argument specified to ioctl(). + * @param pSession The session instance. + */ +static int VBoxDrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PSUPDRVSESSION pSession) +{ + int rc; + SUPREQHDR Hdr; + PSUPREQHDR pHdr; + uint32_t cbBuf; + + Log6(("VBoxDrvLinuxIOCtl: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid)); + + /* + * Read the header. + */ + if (RT_FAILURE(RTR0MemUserCopyFrom(&Hdr, ulArg, sizeof(Hdr)))) + { + Log(("VBoxDrvLinuxIOCtl: copy_from_user(,%#lx,) failed; uCmd=%#x\n", ulArg, uCmd)); + return -EFAULT; + } + if (RT_UNLIKELY((Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + Log(("VBoxDrvLinuxIOCtl: bad header magic %#x; uCmd=%#x\n", Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK, uCmd)); + return -EINVAL; + } + + /* + * Buffer the request. + */ + cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut); + if (RT_UNLIKELY(cbBuf > _1M*16)) + { + Log(("VBoxDrvLinuxIOCtl: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd)); + return -E2BIG; + } + if (RT_UNLIKELY(_IOC_SIZE(uCmd) ? cbBuf != _IOC_SIZE(uCmd) : Hdr.cbIn < sizeof(Hdr))) + { + Log(("VBoxDrvLinuxIOCtl: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x\n", cbBuf, _IOC_SIZE(uCmd), uCmd)); + return -EINVAL; + } + pHdr = RTMemAlloc(cbBuf); + if (RT_UNLIKELY(!pHdr)) + { + OSDBGPRINT(("VBoxDrvLinuxIOCtl: failed to allocate buffer of %d bytes for uCmd=%#x\n", cbBuf, uCmd)); + return -ENOMEM; + } + if (RT_FAILURE(RTR0MemUserCopyFrom(pHdr, ulArg, Hdr.cbIn))) + { + Log(("VBoxDrvLinuxIOCtl: copy_from_user(,%#lx, %#x) failed; uCmd=%#x\n", ulArg, Hdr.cbIn, uCmd)); + RTMemFree(pHdr); + return -EFAULT; + } + if (Hdr.cbIn < cbBuf) + RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbBuf - Hdr.cbIn); + + /* + * Process the IOCtl. + */ + rc = supdrvIOCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf); + + /* + * Copy ioctl data and output buffer back to user space. + */ + if (RT_LIKELY(!rc)) + { + uint32_t cbOut = pHdr->cbOut; + if (RT_UNLIKELY(cbOut > cbBuf)) + { + OSDBGPRINT(("VBoxDrvLinuxIOCtl: too much output! %#x > %#x; uCmd=%#x!\n", cbOut, cbBuf, uCmd)); + cbOut = cbBuf; + } + if (RT_FAILURE(RTR0MemUserCopyTo(ulArg, pHdr, cbOut))) + { + /* this is really bad! */ + OSDBGPRINT(("VBoxDrvLinuxIOCtl: copy_to_user(%#lx,,%#x); uCmd=%#x!\n", ulArg, cbOut, uCmd)); + rc = -EFAULT; + } + } + else + { + Log(("VBoxDrvLinuxIOCtl: pFilp=%p uCmd=%#x ulArg=%p failed, rc=%d\n", pFilp, uCmd, (void *)ulArg, rc)); + rc = -EINVAL; + } + RTMemFree(pHdr); + + Log6(("VBoxDrvLinuxIOCtl: returns %d (pid=%d/%d)\n", rc, RTProcSelf(), current->pid)); + return rc; +} + + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code, see supdrvIDC. + * @param uReq The request code. + * @param pReq The request. + */ +int VBOXCALL SUPDrvLinuxIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + PSUPDRVSESSION pSession; + + /* + * Some quick validations. + */ + if (RT_UNLIKELY(!RT_VALID_PTR(pReq))) + return VERR_INVALID_POINTER; + + pSession = pReq->pSession; + if (pSession) + { + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) + return VERR_INVALID_PARAMETER; + if (RT_UNLIKELY(pSession->pDevExt != &g_DevExt)) + return VERR_INVALID_PARAMETER; + } + else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT)) + return VERR_INVALID_PARAMETER; + + /* + * Do the job. + */ + return supdrvIDC(uReq, &g_DevExt, pSession, pReq); +} +EXPORT_SYMBOL(SUPDrvLinuxIDC); + + +#if RTLNX_VER_MIN(5,0,0) + +/** + * Checks if the given module is one of our potential wrapper modules or not. + */ +static bool supdrvLinuxLdrIsPotentialWrapperModule(struct module const *pModule) +{ + if ( pModule + && strncmp(pModule->name, RT_STR_TUPLE("vbox_")) == 0) + return true; + return false; +} + +/** + * Called when a kernel module changes state. + * + * We use this to listen for wrapper modules being loaded, since some evil + * bugger removed the find_module() export in 5.13. + */ +static int supdrvLinuxLdrModuleNotifyCallback(struct notifier_block *pBlock, unsigned long uModuleState, void *pvModule) +{ + struct module *pModule = (struct module *)pvModule; + switch (uModuleState) + { + case MODULE_STATE_UNFORMED: /* Setting up the module... */ + break; + + /* + * The module is about to have its ctors & init functions called. + * + * Add anything that looks like a wrapper module to our tracker list. + */ + case MODULE_STATE_COMING: + if (supdrvLinuxLdrIsPotentialWrapperModule(pModule)) + { + PSUPDRVLNXMODULE pTracker = (PSUPDRVLNXMODULE)RTMemAlloc(sizeof(*pTracker)); + if (pTracker) + { + pTracker->pModule = pModule; + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); + RTListPrepend(&g_supdrvLinuxWrapperModuleList, &pTracker->ListEntry); + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + } + } + break; + + case MODULE_STATE_LIVE: + break; + + /* + * The module has been uninited and is going away. + * + * Remove the tracker entry for the module, if we have one. + */ + case MODULE_STATE_GOING: + { + PSUPDRVLNXMODULE pCur; + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); + RTListForEach(&g_supdrvLinuxWrapperModuleList, pCur, SUPDRVLNXMODULE, ListEntry) + { + if (pCur->pModule == pModule) + { + RTListNodeRemove(&pCur->ListEntry); + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + + pCur->pModule = NULL; + RTMemFree(pCur); + + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); /* silly */ + break; + } + } + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + break; + } + } + RT_NOREF(pBlock); + return NOTIFY_OK; +} + +/** + * Replacement for find_module() that's no longer exported with 5.13. + */ +static struct module *supdrvLinuxLdrFindModule(const char *pszLnxModName) +{ + PSUPDRVLNXMODULE pCur; + + spin_lock(&g_supdrvLinuxWrapperModuleSpinlock); + RTListForEach(&g_supdrvLinuxWrapperModuleList, pCur, SUPDRVLNXMODULE, ListEntry) + { + struct module * const pModule = pCur->pModule; + if ( pModule + && strcmp(pszLnxModName, pModule->name) == 0) + { + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + return pModule; + } + } + spin_unlock(&g_supdrvLinuxWrapperModuleSpinlock); + return NULL; +} + +#endif /* >= 5.0.0 */ + + +/** + * Used by native wrapper modules, forwarding to supdrvLdrRegisterWrappedModule + * with device extension prepended to the argument list. + */ +SUPR0DECL(int) SUPDrvLinuxLdrRegisterWrappedModule(PCSUPLDRWRAPPEDMODULE pWrappedModInfo, + const char *pszLnxModName, void **phMod) +{ + AssertPtrReturn(pszLnxModName, VERR_INVALID_POINTER); + AssertReturn(*pszLnxModName, VERR_INVALID_NAME); + + /* Locate the module structure for the caller so can later reference + and dereference it to prevent unloading while it is being used. + + Before Linux v5.9 this could be done by address (__module_address() + or __module_text_address()), but someone (guess who) apparently on + a mission to make life miserable for out-of-tree modules or something, + decided it was only used by build-in code and unexported both of them. + + I could find no init callouts getting a struct module pointer either, + nor any module name hint anywhere I could see. So, we're left with + hardcoding the module name via the compiler and pass it along to + SUPDrv so we can call find_module() here. + + Sigh^2. + + Update 5.13: + The find_module() and module_mutex symbols are no longer exported, + probably the doing of the same evil bugger mentioned above. So, we now + register a module notification callback and track the modules we're + interested in that way. */ + +#if RTLNX_VER_MIN(5,0,0) + struct module *pLnxModule = supdrvLinuxLdrFindModule(pszLnxModName); + if (pLnxModule) + return supdrvLdrRegisterWrappedModule(&g_DevExt, pWrappedModInfo, pLnxModule, phMod); + printk("vboxdrv: supdrvLinuxLdrFindModule(%s) failed in SUPDrvLinuxLdrRegisterWrappedModule!\n", pszLnxModName); + return VERR_MODULE_NOT_FOUND; + +#elif RTLNX_VER_MIN(2,6,30) + if (mutex_lock_interruptible(&module_mutex) == 0) + { + struct module *pLnxModule = find_module(pszLnxModName); + mutex_unlock(&module_mutex); + if (pLnxModule) + return supdrvLdrRegisterWrappedModule(&g_DevExt, pWrappedModInfo, pLnxModule, phMod); + printk("vboxdrv: find_module(%s) failed in SUPDrvLinuxLdrRegisterWrappedModule!\n", pszLnxModName); + return VERR_MODULE_NOT_FOUND; + } + return VERR_INTERRUPTED; + +#else + printk("vboxdrv: wrapper modules are not supported on 2.6.29 and earlier. sorry.\n"); + return VERR_NOT_SUPPORTED; +#endif +} +EXPORT_SYMBOL(SUPDrvLinuxLdrRegisterWrappedModule); + + +/** + * Used by native wrapper modules, forwarding to supdrvLdrDeregisterWrappedModule + * with device extension prepended to the argument list. + */ +SUPR0DECL(int) SUPDrvLinuxLdrDeregisterWrappedModule(PCSUPLDRWRAPPEDMODULE pWrappedModInfo, void **phMod) +{ + return supdrvLdrDeregisterWrappedModule(&g_DevExt, pWrappedModInfo, phMod); +} +EXPORT_SYMBOL(SUPDrvLinuxLdrDeregisterWrappedModule); + + +RTCCUINTREG VBOXCALL supdrvOSChangeCR4(RTCCUINTREG fOrMask, RTCCUINTREG fAndMask) +{ +#if RTLNX_VER_MIN(5,8,0) + unsigned long fSavedFlags; + local_irq_save(fSavedFlags); + RTCCUINTREG const uOld = cr4_read_shadow(); + cr4_update_irqsoff(fOrMask, ~fAndMask); /* Same as this function, only it is not returning the old value. */ + AssertMsg(cr4_read_shadow() == ((uOld & fAndMask) | fOrMask), + ("fOrMask=%#RTreg fAndMask=%#RTreg uOld=%#RTreg; new cr4=%#llx\n", fOrMask, fAndMask, uOld, cr4_read_shadow())); + local_irq_restore(fSavedFlags); +#else +# if RTLNX_VER_MIN(3,20,0) + RTCCUINTREG const uOld = this_cpu_read(cpu_tlbstate.cr4); +# else + RTCCUINTREG const uOld = ASMGetCR4(); +# endif + RTCCUINTREG const uNew = (uOld & fAndMask) | fOrMask; + if (uNew != uOld) + { +# if RTLNX_VER_MIN(3,20,0) + this_cpu_write(cpu_tlbstate.cr4, uNew); + __write_cr4(uNew); +# else + ASMSetCR4(uNew); +# endif + } +#endif + return uOld; +} + + +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. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + return force_async_tsc != 0; +} + + +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + return true; +} + + +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + return false; +} + + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +/** @def VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS + * A very crude hack for debugging using perf and dtrace. + * + * DO ABSOLUTELY NOT ENABLE IN PRODUCTION BUILDS! DEVELOPMENT ONLY!! + * DO ABSOLUTELY NOT ENABLE IN PRODUCTION BUILDS! DEVELOPMENT ONLY!! + * DO ABSOLUTELY NOT ENABLE IN PRODUCTION BUILDS! DEVELOPMENT ONLY!! + * + */ +#if 0 || defined(DOXYGEN_RUNNING) +# define VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS +#endif + +#if defined(VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS) && defined(CONFIG_MODULES_TREE_LOOKUP) +/** Whether g_pfnModTreeInsert and g_pfnModTreeRemove have been initialized. + * @remarks can still be NULL after init. */ +static volatile bool g_fLookedForModTreeFunctions = false; +static void (*g_pfnModTreeInsert)(struct mod_tree_node *) = NULL; /**< __mod_tree_insert */ +static void (*g_pfnModTreeRemove)(struct mod_tree_node *) = NULL; /**< __mod_tree_remove */ +#endif + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ +#ifdef VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS /* Not for production use!! Debugging only! */ + /* + * This trick stops working with 4.2 when CONFIG_MODULES_TREE_LOOKUP is + * defined. The module lookups are done via a tree structure and we + * cannot get at the root of it. :-( + */ +# ifdef CONFIG_KALLSYMS + size_t const cchName = strlen(pImage->szName); +# endif + struct module *pMyMod, *pSelfMod, *pTestMod, *pTestModByName; + IPRT_LINUX_SAVE_EFL_AC(); + + pImage->pLnxModHack = NULL; + +# ifdef CONFIG_MODULES_TREE_LOOKUP + /* + * This is pretty naive, but works for 4.2 on arch linux. I don't think we + * can count on finding __mod_tree_remove in all kernel builds as it's not + * marked noinline like __mod_tree_insert. + */ + if (!g_fLookedForModTreeFunctions) + { + unsigned long ulInsert = kallsyms_lookup_name("__mod_tree_insert"); + unsigned long ulRemove = kallsyms_lookup_name("__mod_tree_remove"); + if (!ulInsert || !ulRemove) + { + g_fLookedForModTreeFunctions = true; + printk(KERN_ERR "vboxdrv: failed to locate __mod_tree_insert and __mod_tree_remove.\n"); + IPRT_LINUX_RESTORE_EFL_AC(); + return; + } + *(unsigned long *)&g_pfnModTreeInsert = ulInsert; + *(unsigned long *)&g_pfnModTreeRemove = ulRemove; + ASMCompilerBarrier(); + g_fLookedForModTreeFunctions = true; + } + else if (!g_pfnModTreeInsert || !g_pfnModTreeRemove) + return; +#endif + + /* + * Make sure we've found our own module, otherwise we cannot access the linked list. + */ + mutex_lock(&module_mutex); + pSelfMod = find_module("vboxdrv"); + mutex_unlock(&module_mutex); + if (!pSelfMod) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return; + } + + /* + * Cook up a module structure for the image. + * We allocate symbol and string tables in the allocation and the module to keep things simple. + */ +# ifdef CONFIG_KALLSYMS + pMyMod = (struct module *)RTMemAllocZ(sizeof(*pMyMod) + + sizeof(Elf_Sym) * 3 + + 1 + cchName * 2 + sizeof("_start") + sizeof("_end") + 4 ); +# else + pMyMod = (struct module *)RTMemAllocZ(sizeof(*pMyMod)); +# endif + if (pMyMod) + { + int rc = VINF_SUCCESS; +# ifdef CONFIG_KALLSYMS + Elf_Sym *paSymbols = (Elf_Sym *)(pMyMod + 1); + char *pchStrTab = (char *)(paSymbols + 3); +# endif + + pMyMod->state = MODULE_STATE_LIVE; + INIT_LIST_HEAD(&pMyMod->list); /* just in case */ + + /* Perf only matches up files with a .ko extension (maybe .ko.gz), + so in order for this crap to work smoothly, we append .ko to the + module name and require the user to create symbolic links in + /lib/modules/`uname -r`: + for i in VMMR0.r0 VBoxDDR0.r0 VBoxDD2R0.r0; do + sudo ln -s /mnt/scratch/vbox/svn/trunk/out/linux.amd64/debug/bin/$i /lib/modules/`uname -r`/$i.ko; + done */ + RTStrPrintf(pMyMod->name, sizeof(pMyMod->name), "%s", pImage->szName); + + /* sysfs bits. */ + INIT_LIST_HEAD(&pMyMod->mkobj.kobj.entry); /* rest of kobj is already zeroed, hopefully never accessed... */ + pMyMod->mkobj.mod = pMyMod; + pMyMod->mkobj.drivers_dir = NULL; + pMyMod->mkobj.mp = NULL; + pMyMod->mkobj.kobj_completion = NULL; + + pMyMod->modinfo_attrs = NULL; /* hopefully not accessed after setup. */ + pMyMod->holders_dir = NULL; /* hopefully not accessed. */ + pMyMod->version = "N/A"; + pMyMod->srcversion = "N/A"; + + /* We export no symbols. */ + pMyMod->num_syms = 0; + pMyMod->syms = NULL; + pMyMod->crcs = NULL; + + pMyMod->num_gpl_syms = 0; + pMyMod->gpl_syms = NULL; + pMyMod->gpl_crcs = NULL; + + pMyMod->num_gpl_future_syms = 0; + pMyMod->gpl_future_syms = NULL; + pMyMod->gpl_future_crcs = NULL; + +# if CONFIG_UNUSED_SYMBOLS + pMyMod->num_unused_syms = 0; + pMyMod->unused_syms = NULL; + pMyMod->unused_crcs = NULL; + + pMyMod->num_unused_gpl_syms = 0; + pMyMod->unused_gpl_syms = NULL; + pMyMod->unused_gpl_crcs = NULL; +# endif + /* No kernel parameters either. */ + pMyMod->kp = NULL; + pMyMod->num_kp = 0; + +# ifdef CONFIG_MODULE_SIG + /* Pretend ok signature. */ + pMyMod->sig_ok = true; +# endif + /* No exception table. */ + pMyMod->num_exentries = 0; + pMyMod->extable = NULL; + + /* No init function */ + pMyMod->init = NULL; + pMyMod->module_init = NULL; + pMyMod->init_size = 0; + pMyMod->init_ro_size = 0; + pMyMod->init_text_size = 0; + + /* The module address and size. It's all text. */ + pMyMod->module_core = pImage->pvImage; + pMyMod->core_size = pImage->cbImageBits; + pMyMod->core_text_size = pImage->cbImageBits; + pMyMod->core_ro_size = pImage->cbImageBits; + +#ifdef CONFIG_MODULES_TREE_LOOKUP + /* Fill in the self pointers for the tree nodes. */ + pMyMod->mtn_core.mod = pMyMod; + pMyMod->mtn_init.mod = pMyMod; +#endif + /* They invented the tained bit for us, didn't they? */ + pMyMod->taints = 1; + +# ifdef CONFIG_GENERIC_BUGS + /* No BUGs in our modules. */ + pMyMod->num_bugs = 0; + INIT_LIST_HEAD(&pMyMod->bug_list); + pMyMod->bug_table = NULL; +# endif + +# ifdef CONFIG_KALLSYMS + /* The core stuff is documented as only used when loading. So just zero them. */ + pMyMod->core_num_syms = 0; + pMyMod->core_symtab = NULL; + pMyMod->core_strtab = NULL; + + /* Construct a symbol table with start and end symbols. + Note! We don't have our own symbol table at this point, image bit + are not uploaded yet! */ + pMyMod->num_symtab = 3; + pMyMod->symtab = paSymbols; + pMyMod->strtab = pchStrTab; + RT_ZERO(paSymbols[0]); + pchStrTab[0] = '\0'; + paSymbols[1].st_name = 1; + paSymbols[2].st_name = 2 + RTStrPrintf(&pchStrTab[paSymbols[1].st_name], cchName + sizeof("_start"), + "%s_start", pImage->szName); + RTStrPrintf(&pchStrTab[paSymbols[2].st_name], cchName + sizeof("_end"), "%s_end", pImage->szName); + paSymbols[1].st_info = 't'; + paSymbols[2].st_info = 'b'; + paSymbols[1].st_other = 0; + paSymbols[2].st_other = 0; + paSymbols[1].st_shndx = 0; + paSymbols[2].st_shndx = 0; + paSymbols[1].st_value = (uintptr_t)pImage->pvImage; + paSymbols[2].st_value = (uintptr_t)pImage->pvImage + pImage->cbImageBits - 1; + paSymbols[1].st_size = pImage->cbImageBits - 1; + paSymbols[2].st_size = 1; +# endif + /* No arguments, but seems its always non-NULL so put empty string there. */ + pMyMod->args = ""; + +# ifdef CONFIG_SMP + /* No per CPU data. */ + pMyMod->percpu = NULL; + pMyMod->percpu_size = 0; +# endif +# ifdef CONFIG_TRACEPOINTS + /* No tracepoints we like to share. */ + pMyMod->num_tracepoints = 0; + pMyMod->tracepoints_ptrs = NULL; +#endif +# ifdef HAVE_JUMP_LABEL + /* No jump lable stuff either. */ + pMyMod->jump_entries = NULL; + pMyMod->num_jump_entries = 0; +# endif +# ifdef CONFIG_TRACING + pMyMod->num_trace_bprintk_fmt = 0; + pMyMod->trace_bprintk_fmt_start = NULL; +# endif +# ifdef CONFIG_EVENT_TRACING + pMyMod->trace_events = NULL; + pMyMod->num_trace_events = 0; +# endif +# ifdef CONFIG_FTRACE_MCOUNT_RECORD + pMyMod->num_ftrace_callsites = 0; + pMyMod->ftrace_callsites = NULL; +# endif +# ifdef CONFIG_MODULE_UNLOAD + /* Dependency lists, not worth sharing */ + INIT_LIST_HEAD(&pMyMod->source_list); + INIT_LIST_HEAD(&pMyMod->target_list); + + /* Nobody waiting and no exit function. */ +# if RTLNX_VER_MAX(3,13,0) + pMyMod->waiter = NULL; +# endif + pMyMod->exit = NULL; + + /* References, very important as we must not allow the module + to be unloaded using rmmod. */ +# if RTLNX_VER_MIN(3,19,0) + atomic_set(&pMyMod->refcnt, 42); +# else + pMyMod->refptr = alloc_percpu(struct module_ref); + if (pMyMod->refptr) + { + int iCpu; + for_each_possible_cpu(iCpu) + { + per_cpu_ptr(pMyMod->refptr, iCpu)->decs = 0; + per_cpu_ptr(pMyMod->refptr, iCpu)->incs = 1; + } + } + else + rc = VERR_NO_MEMORY; +# endif +# endif +# ifdef CONFIG_CONSTRUCTORS + /* No constructors. */ + pMyMod->ctors = NULL; + pMyMod->num_ctors = 0; +# endif + if (RT_SUCCESS(rc)) + { + bool fIsModText; + + /* + * Add the module to the list. + */ + mutex_lock(&module_mutex); + list_add_rcu(&pMyMod->list, &pSelfMod->list); + pImage->pLnxModHack = pMyMod; +# ifdef CONFIG_MODULES_TREE_LOOKUP + g_pfnModTreeInsert(&pMyMod->mtn_core); /* __mod_tree_insert */ +# endif + mutex_unlock(&module_mutex); + + /* + * Test it. + */ + mutex_lock(&module_mutex); + pTestModByName = find_module(pMyMod->name); + pTestMod = __module_address((uintptr_t)pImage->pvImage + pImage->cbImageBits / 4); + fIsModText = __module_text_address((uintptr_t)pImage->pvImage + pImage->cbImageBits / 2); + mutex_unlock(&module_mutex); + if ( pTestMod == pMyMod + && pTestModByName == pMyMod + && fIsModText) + printk(KERN_ERR "vboxdrv: fake module works for '%s' (%#lx to %#lx)\n", + pMyMod->name, (unsigned long)paSymbols[1].st_value, (unsigned long)paSymbols[2].st_value); + else + printk(KERN_ERR "vboxdrv: failed to find fake module (pTestMod=%p, pTestModByName=%p, pMyMod=%p, fIsModText=%d)\n", + pTestMod, pTestModByName, pMyMod, fIsModText); + } + else + RTMemFree(pMyMod); + } + + IPRT_LINUX_RESTORE_EFL_AC(); +#else + pImage->pLnxModHack = NULL; +#endif + NOREF(pDevExt); NOREF(pImage); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ +#ifdef VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS /* Not for production use!! Debugging only! */ + struct module *pMyMod = pImage->pLnxModHack; + pImage->pLnxModHack = NULL; + if (pMyMod) + { + /* + * Remove the fake module list entry and free it. + */ + IPRT_LINUX_SAVE_EFL_AC(); + mutex_lock(&module_mutex); + list_del_rcu(&pMyMod->list); +# ifdef CONFIG_MODULES_TREE_LOOKUP + g_pfnModTreeRemove(&pMyMod->mtn_core); +# endif + synchronize_sched(); + mutex_unlock(&module_mutex); + +# if RTLNX_VER_MAX(3,19,0) + free_percpu(pMyMod->refptr); +# endif + RTMemFree(pMyMod); + IPRT_LINUX_RESTORE_EFL_AC(); + } + +#else + Assert(pImage->pLnxModHack == NULL); +#endif + NOREF(pDevExt); NOREF(pImage); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ +#ifdef VBOX_WITH_NON_PROD_HACK_FOR_PERF_STACKS +# error "implement me!" +#endif + RT_NOREF(pDevExt, pImage, pszSymbol, cchSymbol, ppvSymbol); + return VERR_WRONG_ORDER; +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + struct module *pLnxMod = (struct module *)pImage->pvWrappedNative; + Assert(!pImage->fLnxWrapperRef); + AssertReturnVoid(pLnxMod); + pImage->fLnxWrapperRef = try_module_get(pLnxMod); + RT_NOREF(pDevExt); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + if (pImage->fLnxWrapperRef) + { + struct module *pLnxMod = (struct module *)pImage->pvWrappedNative; + pImage->fLnxWrapperRef = false; + module_put(pLnxMod); + } + RT_NOREF(pDevExt); +} + + +#ifdef SUPDRV_WITH_MSR_PROBER + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ +# ifdef SUPDRV_LINUX_HAS_SAFE_MSR_API + uint32_t u32Low, u32High; + int rc; + + IPRT_LINUX_SAVE_EFL_AC(); + if (idCpu == NIL_RTCPUID) + rc = rdmsr_safe(uMsr, &u32Low, &u32High); + else if (RTMpIsCpuOnline(idCpu)) + rc = rdmsr_safe_on_cpu(idCpu, uMsr, &u32Low, &u32High); + else + return VERR_CPU_OFFLINE; + IPRT_LINUX_RESTORE_EFL_AC(); + if (rc == 0) + { + *puValue = RT_MAKE_U64(u32Low, u32High); + return VINF_SUCCESS; + } + return VERR_ACCESS_DENIED; +# else + return VERR_NOT_SUPPORTED; +# endif +} + + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ +# ifdef SUPDRV_LINUX_HAS_SAFE_MSR_API + int rc; + + IPRT_LINUX_SAVE_EFL_AC(); + if (idCpu == NIL_RTCPUID) + rc = wrmsr_safe(uMsr, RT_LODWORD(uValue), RT_HIDWORD(uValue)); + else if (RTMpIsCpuOnline(idCpu)) + rc = wrmsr_safe_on_cpu(idCpu, uMsr, RT_LODWORD(uValue), RT_HIDWORD(uValue)); + else + return VERR_CPU_OFFLINE; + IPRT_LINUX_RESTORE_EFL_AC(); + + if (rc == 0) + return VINF_SUCCESS; + return VERR_ACCESS_DENIED; +# else + return VERR_NOT_SUPPORTED; +# endif +} + +# ifdef SUPDRV_LINUX_HAS_SAFE_MSR_API +/** + * Worker for supdrvOSMsrProberModify. + */ +static DECLCALLBACK(void) supdrvLnxMsrProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; + register uint32_t uMsr = pReq->u.In.uMsr; + bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; + uint64_t uBefore; + uint64_t uWritten; + uint64_t uAfter; + int rcBefore, rcWrite, rcAfter, rcRestore; + RTCCUINTREG fOldFlags; + + /* Initialize result variables. */ + uBefore = uWritten = uAfter = 0; + rcWrite = rcAfter = rcRestore = -EIO; + + /* + * Do the job. + */ + fOldFlags = ASMIntDisableFlags(); + ASMCompilerBarrier(); /* paranoia */ + if (!fFaster) + ASMWriteBackAndInvalidateCaches(); + + rcBefore = rdmsrl_safe(uMsr, &uBefore); + if (rcBefore >= 0) + { + register uint64_t uRestore = uBefore; + uWritten = uRestore; + uWritten &= pReq->u.In.uArgs.Modify.fAndMask; + uWritten |= pReq->u.In.uArgs.Modify.fOrMask; + + rcWrite = wrmsr_safe(uMsr, RT_LODWORD(uWritten), RT_HIDWORD(uWritten)); + rcAfter = rdmsrl_safe(uMsr, &uAfter); + rcRestore = wrmsr_safe(uMsr, RT_LODWORD(uRestore), RT_HIDWORD(uRestore)); + + if (!fFaster) + { + ASMWriteBackAndInvalidateCaches(); + ASMReloadCR3(); + ASMNopPause(); + } + } + + ASMCompilerBarrier(); /* paranoia */ + ASMSetFlags(fOldFlags); + + /* + * Write out the results. + */ + pReq->u.Out.uResults.Modify.uBefore = uBefore; + pReq->u.Out.uResults.Modify.uWritten = uWritten; + pReq->u.Out.uResults.Modify.uAfter = uAfter; + pReq->u.Out.uResults.Modify.fBeforeGp = rcBefore != 0; + pReq->u.Out.uResults.Modify.fModifyGp = rcWrite != 0; + pReq->u.Out.uResults.Modify.fAfterGp = rcAfter != 0; + pReq->u.Out.uResults.Modify.fRestoreGp = rcRestore != 0; + RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); +} +# endif + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ +# ifdef SUPDRV_LINUX_HAS_SAFE_MSR_API + if (idCpu == NIL_RTCPUID) + { + supdrvLnxMsrProberModifyOnCpu(idCpu, pReq, NULL); + return VINF_SUCCESS; + } + return RTMpOnSpecific(idCpu, supdrvLnxMsrProberModifyOnCpu, pReq, NULL); +# else + return VERR_NOT_SUPPORTED; +# endif +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +/** + * Converts a supdrv error code to an linux error code. + * + * @returns corresponding linux error code. + * @param rc IPRT status code. + */ +static int VBoxDrvLinuxErr2LinuxErr(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return 0; + case VERR_GENERAL_FAILURE: return -EACCES; + case VERR_INVALID_PARAMETER: return -EINVAL; + case VERR_INVALID_MAGIC: return -EILSEQ; + case VERR_INVALID_HANDLE: return -ENXIO; + case VERR_INVALID_POINTER: return -EFAULT; + case VERR_LOCK_FAILED: return -ENOLCK; + case VERR_ALREADY_LOADED: return -EEXIST; + case VERR_PERMISSION_DENIED: return -EPERM; + case VERR_VERSION_MISMATCH: return -ENOSYS; + case VERR_IDT_FAILED: return -1000; + } + + return -EPERM; +} + + +SUPR0DECL(int) SUPR0HCPhysToVirt(RTHCPHYS HCPhys, void **ppv) +{ + AssertReturn(!(HCPhys & PAGE_OFFSET_MASK), VERR_INVALID_POINTER); + AssertReturn(HCPhys != NIL_RTHCPHYS, VERR_INVALID_POINTER); + /* Would've like to use valid_phys_addr_range for this test, but it isn't exported. */ + AssertReturn((HCPhys | PAGE_OFFSET_MASK) < __pa(high_memory), VERR_INVALID_POINTER); + *ppv = phys_to_virt(HCPhys); + return VINF_SUCCESS; +} +SUPR0_EXPORT_SYMBOL(SUPR0HCPhysToVirt); + + +RTDECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + char szMsg[512]; + IPRT_LINUX_SAVE_EFL_AC(); + + RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + printk("%s", szMsg); + + IPRT_LINUX_RESTORE_EFL_AC(); + return 0; +} +SUPR0_EXPORT_SYMBOL(SUPR0PrintfV); + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + uint32_t fFlags = 0; + /* Note! bird 2023-10-20: Apparently, with CONFIG_PAX_KERNEXEC these days, + not only is the regular GDT read-only, but the one returned by + get_current_gdt_rw() is also read-only despite the name. + + We don't know exactly when this started, or if it was always like + this, but getting hold of the relevant patches isn't all that + straight forward any longer it seems (which is weird for linux + patches), so, we've just enabled slow-mode for all PAX_KERNEXEC + kernels regardless of kernel version. + + Looking at grsecurity patch for 4.9.9, it looks like the writable + GDT stuff never worked with PaX/grsec. + */ +#ifdef CONFIG_PAX_KERNEXEC + fFlags |= SUPKERNELFEATURES_GDT_READ_ONLY; +#elif RTLNX_VER_MIN(4,12,0) + fFlags |= SUPKERNELFEATURES_GDT_NEED_WRITABLE; +#endif + +#if defined(VBOX_STRICT) || defined(VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV) + fFlags |= SUPKERNELFEATURES_SMAP; +#elif defined(CONFIG_X86_SMAP) + if (ASMGetCR4() & X86_CR4_SMAP) + fFlags |= SUPKERNELFEATURES_SMAP; +#endif + return fFlags; +} +SUPR0_EXPORT_SYMBOL(SUPR0GetKernelFeatures); + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +#if RTLNX_VER_MIN(4,19,0) /* Going back to 4.19.0 for better coverage, we + probably only need 5.17.7+ in the end. */ + /* + * HACK ALERT! + * + * We'd like to use the old __kernel_fpu_begin() API which was removed in + * early 2019, because we typically run with preemption enabled and have an + * preemption hook installed which will call kernel_fpu_end() in case we're + * scheduled out after getting in here. The preemption hook is almost + * useless if we run with preemption disabled. + * + * For the case where the kernel does not have preemption hooks, we get here + * with preemption already disabled and one more count doesn't make any + * difference. + * + * So, after the kernel_fpu_begin() call we undo the implicit preempt_disable() + * call it does, so the preemption hook can do its work and the VBox user has + * a more responsive system. + * + * See @bugref{10209#c12} and onwards for more details. + */ + Assert(fCtxHook || !RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + kernel_fpu_begin(); +# if 0 /* Always do it for now for better test coverage. */ + if (fCtxHook) +# endif + preempt_enable(); + return false; /** @todo Not sure if we have license to use any extended state, or + * if we're limited to the SSE & x87 FPU. If it's the former, + * we should return @a true and the caller can skip + * saving+restoring the host state and save some time. */ +#else + return false; +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0FpuBegin); + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +#if RTLNX_VER_MIN(4,19,0) + /* HACK ALERT! See SUPR0FpuBegin for an explanation of this. */ + Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); +# if 0 /* Always do it for now for better test coverage. */ + if (fCtxHook) +# endif + preempt_disable(); + kernel_fpu_end(); +#endif +} +SUPR0_EXPORT_SYMBOL(SUPR0FpuEnd); + + +int VBOXCALL supdrvOSGetCurrentGdtRw(RTHCUINTPTR *pGdtRw) +{ +#if RTLNX_VER_MIN(4,12,0) && !defined(CONFIG_PAX_KERNEXEC) + *pGdtRw = (RTHCUINTPTR)get_current_gdt_rw(); + return VINF_SUCCESS; +#else + return VERR_NOT_IMPLEMENTED; +#endif +} + + +module_init(VBoxDrvLinuxInit); +module_exit(VBoxDrvLinuxUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " Support Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV) " (" RT_XSTR(SUPDRV_IOC_VERSION) ")"); +#endif + +module_param(force_async_tsc, int, 0444); +MODULE_PARM_DESC(force_async_tsc, "force the asynchronous TSC mode"); + diff --git a/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.mod.c b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.mod.c new file mode 100644 index 00000000..4bb02712 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.mod.c @@ -0,0 +1,97 @@ +/* $Id: SUPDrv-linux.mod.c $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Autogenerated Linux code. + * + * This is checked in to assist syntax checking the module. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "SUPDrvInternal.h" /* for KBUILD_STR */ +#include "the-linux-kernel.h" +#include <linux/vermagic.h> + +MODULE_INFO(vermagic, VERMAGIC_STRING); + +#undef unix +struct module __this_module +__attribute__((section(".gnu.linkonce.this_module"))) = { + .name = __stringify(KBUILD_MODNAME), + .init = init_module, +#ifdef CONFIG_MODULE_UNLOAD + .exit = cleanup_module, +#endif +}; + +static const struct modversion_info ____versions[] +__attribute_used__ +__attribute__((section("__versions"))) = { + { 0, "cleanup_module" }, + { 0, "init_module" }, + { 0, "struct_module" }, + { 0, "strpbrk" }, + { 0, "__kmalloc" }, + { 0, "mem_map" }, + { 0, "vmalloc" }, + { 0, "malloc_sizes" }, + { 0, "vfree" }, + { 0, "change_page_attr" }, + { 0, "__might_sleep" }, + { 0, "remap_page_range" }, + { 0, "__alloc_pages" }, + { 0, "printk" }, + { 0, "__PAGE_KERNEL" }, + { 0, "rwsem_wake" }, + { 0, "copy_to_user" }, + { 0, "preempt_schedule" }, + { 0, "contig_page_data" }, + { 0, "do_mmap_pgoff" }, + { 0, "find_vma" }, + { 0, "kmem_cache_alloc" }, + { 0, "__free_pages" }, + { 0, "do_munmap" }, + { 0, "get_user_pages" }, + { 0, "vsnprintf" }, + { 0, "kfree" }, + { 0, "memcpy" }, + { 0, "put_page" }, + { 0, "__up_wakeup" }, + { 0, "__down_failed" }, + { 0, "copy_from_user" }, + { 0, "rwsem_down_read_failed" }, +}; + +static const char __module_depends[] +__attribute_used__ +__attribute__((section(".modinfo"))) = +"depends="; + diff --git a/src/VBox/HostDrivers/Support/linux/SUPLib-linux.cpp b/src/VBox/HostDrivers/Support/linux/SUPLib-linux.cpp new file mode 100644 index 00000000..5bdcda63 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPLib-linux.cpp @@ -0,0 +1,388 @@ +/* $Id: SUPLib-linux.cpp $ */ +/** @file + * VirtualBox Support Library - GNU/Linux specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# undef RT_STRICT +# ifndef LOG_DISABLED +# define LOG_DISABLED +# endif +# define RTLOG_REL_DISABLED +# include <iprt/log.h> +#endif + +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <malloc.h> + +#include <VBox/log.h> +#include <VBox/sup.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <VBox/types.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** System device name. */ +#define DEVICE_NAME_SYS "/dev/vboxdrv" +/** User device name. */ +#define DEVICE_NAME_USR "/dev/vboxdrvu" + +/* define MADV_DONTFORK if it's missing from the system headers. */ +#ifndef MADV_DONTFORK +# define MADV_DONTFORK 10 +#endif + + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + RT_NOREF2(penmWhat, pErrInfo); + + /* + * Nothing to do if pre-inited. + */ + if (fPreInited) + return VINF_SUCCESS; + Assert(pThis->hDevice == (intptr_t)NIL_RTFILE); + + /* + * Check if madvise works. + */ + void *pv = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pv == MAP_FAILED) + return VERR_NO_MEMORY; + pThis->fSysMadviseWorks = (0 == madvise(pv, PAGE_SIZE, MADV_DONTFORK)); + munmap(pv, PAGE_SIZE); + + /* + * Driverless? + */ + if (fFlags & SUPR3INIT_F_DRIVERLESS) + { + pThis->fDriverless = true; + return VINF_SUCCESS; + } + + /* + * Try open the device. + */ + const char *pszDeviceNm = fFlags & SUPR3INIT_F_UNRESTRICTED ? DEVICE_NAME_SYS : DEVICE_NAME_USR; + int hDevice = open(pszDeviceNm, O_RDWR, 0); + if (hDevice < 0) + { + /* + * Try load the device. + */ + hDevice = open(pszDeviceNm, O_RDWR, 0); + if (hDevice < 0) + { + int rc; + switch (errno) + { + case ENXIO: /* see man 2 open, ENODEV is actually a kernel bug */ + case ENODEV: rc = VERR_VM_DRIVER_LOAD_ERROR; break; + case EPERM: + case EACCES: rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; break; + case ENOENT: rc = VERR_VM_DRIVER_NOT_INSTALLED; break; + default: rc = VERR_VM_DRIVER_OPEN_ERROR; break; + } + if (fFlags & SUPR3INIT_F_DRIVERLESS_MASK) + { + LogRel(("Failed to open \"%s\", errno=%d, rc=%Rrc - Switching to driverless mode.\n", pszDeviceNm, errno, rc)); + pThis->fDriverless = true; + return VINF_SUCCESS; + } + LogRel(("Failed to open \"%s\", errno=%d, rc=%Rrc\n", pszDeviceNm, errno, rc)); + return rc; + } + } + + /* + * Mark the file handle close on exec. + */ + if (fcntl(hDevice, F_SETFD, FD_CLOEXEC) == -1) + { + close(hDevice); +#ifdef IN_SUP_HARDENED_R3 + return VERR_INTERNAL_ERROR; +#else + return RTErrConvertFromErrno(errno); +#endif + } + + /* + * We're done. + */ + pThis->hDevice = hDevice; + pThis->fUnrestricted = RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Close the device if it's actually open. + */ + if (pThis->hDevice != (intptr_t)NIL_RTFILE) + { + if (close(pThis->hDevice)) + AssertFailed(); + pThis->hDevice = (intptr_t)NIL_RTFILE; + } + + return 0; +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + // nothing to do on Linux + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + // nothing to do on Linux + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + AssertMsg(pThis->hDevice != (intptr_t)NIL_RTFILE, ("SUPLIB not initiated successfully!\n")); + NOREF(cbReq); + + /* + * Issue device iocontrol. + */ + if (RT_LIKELY(ioctl(pThis->hDevice, uFunction, pvReq) >= 0)) + return VINF_SUCCESS; + + /* This is the reverse operation of the one found in SUPDrv-linux.c */ + switch (errno) + { + case EACCES: return VERR_GENERAL_FAILURE; + case EINVAL: return VERR_INVALID_PARAMETER; + case EILSEQ: return VERR_INVALID_MAGIC; + case ENXIO: return VERR_INVALID_HANDLE; + case EFAULT: return VERR_INVALID_POINTER; + case ENOLCK: return VERR_LOCK_FAILED; + case EEXIST: return VERR_ALREADY_LOADED; + case EPERM: return VERR_PERMISSION_DENIED; + case ENOSYS: return VERR_VERSION_MISMATCH; + case 1000: return VERR_IDT_FAILED; + } + + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + int rc = ioctl(pThis->hDevice, uFunction, idCpu); + if (rc == -1) + rc = -errno; + return rc; +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + /* + * If large pages are requested, try use the MAP_HUGETBL flags. This takes + * pages from the reserved huge page pool (see sysctl vm.nr_hugepages) and + * is typically not configured. Also, when the pool is exhausted we get + * ENOMEM back at us. So, when it fails try again w/o MAP_HUGETLB. + */ + int fMmap = MAP_PRIVATE | MAP_ANONYMOUS; +#ifdef MAP_HUGETLB + if ((fFlags & SUP_PAGE_ALLOC_F_LARGE_PAGES) && !(cPages & 511)) + fMmap |= MAP_HUGETLB; +#endif + + size_t cbMmap = cPages << PAGE_SHIFT; + if ( !pThis->fSysMadviseWorks + && (fFlags & (SUP_PAGE_ALLOC_F_FOR_LOCKING | SUP_PAGE_ALLOC_F_LARGE_PAGES)) == SUP_PAGE_ALLOC_F_FOR_LOCKING) + cbMmap += PAGE_SIZE * 2; + + uint8_t *pbPages = (uint8_t *)mmap(NULL, cbMmap, PROT_READ | PROT_WRITE, fMmap, -1, 0); +#ifdef MAP_HUGETLB + if (pbPages == MAP_FAILED && (fMmap & MAP_HUGETLB)) + { + /* Try again without MAP_HUGETLB if mmap fails: */ + fMmap &= ~MAP_HUGETLB; + if (!pThis->fSysMadviseWorks && (fFlags & SUP_PAGE_ALLOC_F_FOR_LOCKING)) + cbMmap = (cPages + 2) << PAGE_SHIFT; + pbPages = (uint8_t *)mmap(NULL, cbMmap, PROT_READ | PROT_WRITE, fMmap, -1, 0); + } +#endif + if (pbPages != MAP_FAILED) + { + if ( !(fFlags & SUP_PAGE_ALLOC_F_FOR_LOCKING) + || pThis->fSysMadviseWorks +#ifdef MAP_HUGETLB + || (fMmap & MAP_HUGETLB) +#endif + ) + { + /* + * It is not fatal if we fail here but a forked child (e.g. the ALSA sound server) + * could crash. Linux < 2.6.16 does not implement madvise(MADV_DONTFORK) but the + * kernel seems to split bigger VMAs and that is all that we want -- later we set the + * VM_DONTCOPY attribute in supdrvOSLockMemOne(). + */ + if ( madvise(pbPages, cbMmap, MADV_DONTFORK) +#ifdef MAP_HUGETLB + && !(fMmap & MAP_HUGETLB) +#endif + ) + LogRel(("SUPLib: madvise %p-%p failed\n", pbPages, cbMmap)); + +#ifdef MADV_HUGEPAGE + /* + * Try enable transparent huge pages for the allocation if desired + * and we weren't able to use MAP_HUGETBL above. + * Note! KVM doesn't seem to benefit much from this. + */ + if ( !(fMmap & MAP_HUGETLB) + && (fFlags & SUP_PAGE_ALLOC_F_LARGE_PAGES) + && !(cPages & 511)) /** @todo PORTME: x86 assumption */ + madvise(pbPages, cbMmap, MADV_HUGEPAGE); +#endif + } + else + { + /* + * madvise(MADV_DONTFORK) is not available (most probably Linux 2.4). Enclose any + * mmapped region by two unmapped pages to guarantee that there is exactly one VM + * area struct of the very same size as the mmap area. + */ + mprotect(pbPages, PAGE_SIZE, PROT_NONE); + mprotect(pbPages + cbMmap - PAGE_SIZE, PAGE_SIZE, PROT_NONE); + pbPages += PAGE_SHIFT; + } + + /** @todo Dunno why we do this, really. It's a waste of time. Maybe it was + * to try make sure the pages were allocated or something before we locked them, + * so I qualified it with SUP_PAGE_ALLOC_F_FOR_LOCKING (unused) for now... */ + if (fFlags & SUP_PAGE_ALLOC_F_FOR_LOCKING) + memset(pbPages, 0, cPages << PAGE_SHIFT); + + *ppvPages = pbPages; + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t cPages) +{ + NOREF(pThis); + munmap(pvPages, cPages << PAGE_SHIFT); + return VINF_SUCCESS; +} + + +/** + * Check if the host kernel supports VT-x or not. + * + * Older Linux kernels clear the VMXE bit in the CR4 register (function + * tlb_flush_all()) leading to a host kernel panic. + * + * @returns VBox status code (no info). + * @param ppszWhy Where to return explanatory message. + */ +DECLHIDDEN(int) suplibOsQueryVTxSupported(const char **ppszWhy) +{ + char szBuf[256]; + int rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szBuf, sizeof(szBuf)); + if (RT_SUCCESS(rc)) + { + char *pszNext; + uint32_t uA, uB, uC; + + rc = RTStrToUInt32Ex(szBuf, &pszNext, 10, &uA); + if ( RT_SUCCESS(rc) + && *pszNext == '.') + { + /* + * new version number scheme starting with Linux 3.0 + */ + if (uA >= 3) + return VINF_SUCCESS; + rc = RTStrToUInt32Ex(pszNext+1, &pszNext, 10, &uB); + if ( RT_SUCCESS(rc) + && *pszNext == '.') + { + rc = RTStrToUInt32Ex(pszNext+1, &pszNext, 10, &uC); + if (RT_SUCCESS(rc)) + { + uint32_t uLinuxVersion = (uA << 16) + (uB << 8) + uC; + if (uLinuxVersion >= (2 << 16) + (6 << 8) + 13) + return VINF_SUCCESS; + } + } + } + } + + *ppszWhy = "Linux 2.6.13 or newer required!"; + return VERR_SUPDRV_KERNEL_TOO_OLD_FOR_VTX; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c b/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c new file mode 100644 index 00000000..3a13ebc9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c @@ -0,0 +1,66 @@ +/* $Id: SUPR0IdcClient-linux.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Linux Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_DISCONNECT, pReq); +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq) +{ + int rc = SUPDrvLinuxIDC(iReq, pReq); + if (RT_SUCCESS(rc)) + rc = pReq->rc; + + NOREF(pHandle); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/linux/SUPWrapperMod-linux.c b/src/VBox/HostDrivers/Support/linux/SUPWrapperMod-linux.c new file mode 100644 index 00000000..75ac7606 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/SUPWrapperMod-linux.c @@ -0,0 +1,211 @@ +/* $Id: SUPWrapperMod-linux.c $ */ +/** @file + * Linux .r0 wrapper module template. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define IPRT_WITHOUT_EFLAGS_AC_PRESERVING +#include "the-linux-kernel.h" + +#include "version-generated.h" +#include "product-generated.h" +#include "revision-generated.h" + +#include <VBox/sup.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def WRAPPED_MODULE_FLAGS + * SUPLDRWRAPPEDMODULE_F_XXX or 0. Default: 0 */ +#ifndef WRAPPED_MODULE_FLAGS +# define WRAPPED_MODULE_FLAGS 0 +#endif +/** @def WRAPPED_MODULE_INIT + * The module init function or NULL. Default: ModuleInit */ +#ifndef WRAPPED_MODULE_INIT +# define WRAPPED_MODULE_INIT ModuleInit +#endif +/** @def WRAPPED_MODULE_TERM + * The module termination function or NULL. Default: ModuleTerm */ +#ifndef WRAPPED_MODULE_TERM +# define WRAPPED_MODULE_TERM ModuleTerm +#endif +/** @def WRAPPED_MODULE_SRV_REQ_HANDLER + * The service request handler function. Default: NULL */ +#ifndef WRAPPED_MODULE_SRV_REQ_HANDLER +# define WRAPPED_MODULE_SRV_REQ_HANDLER NULL +#endif +/** @def WRAPPED_MODULE_VMMR0_ENTRY_FAST + * The VMMR0 fast entry point. Default: NULL */ +#ifndef WRAPPED_MODULE_VMMR0_ENTRY_FAST +# define WRAPPED_MODULE_VMMR0_ENTRY_FAST NULL +#endif +/** @def WRAPPED_MODULE_VMMR0_ENTRY_EX + * The VMMR0 extended entry point. Default: NULL */ +#ifndef WRAPPED_MODULE_VMMR0_ENTRY_EX +# define WRAPPED_MODULE_VMMR0_ENTRY_EX NULL +#endif +/** @def WRAPPED_MODULE_SRV_REQ_HANDLER + * The service request handler function. Default: NULL */ +#ifndef WRAPPED_MODULE_SRV_REQ_HANDLER +# define WRAPPED_MODULE_SRV_REQ_HANDLER NULL +#endif + +#ifdef DOXYGEN_RUNNING +/** @def WRAPPED_MODULE_LINUX_EXPORTS + * Define to enabled linux exports. (Needed for VMMR0.r0 only at present.) */ +# define WRAPPED_MODULE_LINUX_EXPORTS +#endif +#ifdef DOXYGEN_RUNNING +/** @def WRAPPED_MODULE_LICENSE_PROPRIETARY + * Define to select proprietary license instead of GPL. */ +# define WRAPPED_MODULE_LICENSE_PROPRIETARY +#endif +#ifdef DOXYGEN_RUNNING +/** @def WRAPPED_MODULE_SYMBOL_INCLUDE + * The include with SYMBOL_ENTRY() invocations for all exported symbols. */ +# define WRAPPED_MODULE_SYMBOL_INCLUDE "iprt/cdefs.h" +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxWrapperModInit(void); +static void __exit VBoxWrapperModUnload(void); + + +/* + * Prototype the symbols: + */ +#undef RT_MANGLER +#define RT_MANGLER(a_Name) a_Name /* No mangling */ +#define SYMBOL_ENTRY(a_Name) extern FNRT a_Name; +#include WRAPPED_MODULE_SYMBOL_INCLUDE +#undef SYMBOL_ENTRY + +/* + * Export the symbols linux style: + */ +#ifdef WRAPPED_MODULE_LINUX_EXPORTS +# define SYMBOL_ENTRY(a_Name) EXPORT_SYMBOL(a_Name); +# include WRAPPED_MODULE_SYMBOL_INCLUDE +# undef SYMBOL_ENTRY +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +extern char vboxr0mod_start[]; /**< start of text in the .r0 module. */ +extern char vboxr0mod_end[]; /**< end of bss in the .r0 module. */ + +/** The symbol table. */ +static SUPLDRWRAPMODSYMBOL const g_aSymbols[] = +{ +#define SYMBOL_ENTRY(a_Name) { #a_Name, &a_Name }, +#include WRAPPED_MODULE_SYMBOL_INCLUDE +#undef SYMBOL_ENTRY +}; + +/** Wrapped module registration info. */ +static SUPLDRWRAPPEDMODULE const g_WrappedMod = +{ + /* .uMagic = */ SUPLDRWRAPPEDMODULE_MAGIC, + /* .uVersion = */ SUPLDRWRAPPEDMODULE_VERSION, + /* .fFlags = */ WRAPPED_MODULE_FLAGS, + /* .pvImageStart = */ &vboxr0mod_start[0], + /* .pvImageEnd = */ &vboxr0mod_end[0], + + /* .pfnModuleInit = */ WRAPPED_MODULE_INIT, + /* .pfnModuleTerm = */ WRAPPED_MODULE_TERM, + /* .pfnVMMR0EntryFast = */ WRAPPED_MODULE_VMMR0_ENTRY_FAST, + /* .pfnVMMR0EntryEx = */ WRAPPED_MODULE_VMMR0_ENTRY_EX, + /* .pfnSrvReqHandler = */ (PFNSUPR0SERVICEREQHANDLER)WRAPPED_MODULE_SRV_REQ_HANDLER, + + /* .apSymbols = */ g_aSymbols, + /* .cSymbols = */ RT_ELEMENTS(g_aSymbols), + + /* .szName = */ WRAPPED_MODULE_NAME, + /* .uEndMagic = */ SUPLDRWRAPPEDMODULE_MAGIC, +}; + +/** The wrapped module handle. */ +static void *g_hWrappedRegistration = NULL; + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxWrapperModInit(void) +{ + /*printk("vboxwrap/" WRAPPED_MODULE_NAME ": VBoxWrapperModInit\n");*/ + int rc = SUPDrvLinuxLdrRegisterWrappedModule(&g_WrappedMod, KBUILD_MODNAME, &g_hWrappedRegistration); + if (rc == 0) + return 0; + printk("vboxwrap/" WRAPPED_MODULE_NAME ": SUPDrvLinuxRegisterWrappedModule failed: %d\n", rc); + return -EINVAL; +} + + +/** + * Unload the module. + */ +static void __exit VBoxWrapperModUnload(void) +{ + /*printk("vboxwrap/" WRAPPED_MODULE_NAME ": VBoxWrapperModUnload\n");*/ + SUPDrvLinuxLdrDeregisterWrappedModule(&g_WrappedMod, &g_hWrappedRegistration); +} + +module_init(VBoxWrapperModInit); +module_exit(VBoxWrapperModUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " - " WRAPPED_MODULE_NAME); +#ifndef WRAPPED_MODULE_LICENSE_PROPRIETARY +MODULE_LICENSE("GPL"); +#else +MODULE_LICENSE("Proprietary"); +#endif +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV)); +#endif + diff --git a/src/VBox/HostDrivers/Support/linux/VBoxR0-wrapped.lds b/src/VBox/HostDrivers/Support/linux/VBoxR0-wrapped.lds new file mode 100644 index 00000000..f414cfac --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/VBoxR0-wrapped.lds @@ -0,0 +1,49 @@ +/* $Id: VBoxR0-wrapped.lds $ */ +/** @file + * Linker script augmentations for wrapped .r0. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* Define a symbol up close to the start of the module: */ +SECTIONS +{ + vboxr0mod_start = .; +} INSERT BEFORE .text; + +/* Define a symbol up close to the end of the module: */ +SECTIONS +{ + vboxr0mod_end = .; +} INSERT AFTER .bss; + diff --git a/src/VBox/HostDrivers/Support/linux/combined-agnostic1.c b/src/VBox/HostDrivers/Support/linux/combined-agnostic1.c new file mode 100644 index 00000000..de557f80 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/combined-agnostic1.c @@ -0,0 +1,99 @@ +/* $Id: combined-agnostic1.c $ */ +/** @file + * SUPDrv - Combine a bunch of OS agnostic sources into one compile unit. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "internal/iprt.h" +#include <VBox/log.h> + +#undef LOG_GROUP +#include "r0drv/alloc-r0drv.c" +#undef LOG_GROUP +#include "r0drv/initterm-r0drv.c" +#undef LOG_GROUP +#include "r0drv/memobj-r0drv.c" +#undef LOG_GROUP +#include "r0drv/mpnotification-r0drv.c" +#undef LOG_GROUP +#include "r0drv/powernotification-r0drv.c" +#undef LOG_GROUP +#include "r0drv/generic/semspinmutex-r0drv-generic.c" +#undef LOG_GROUP +#include "common/alloc/alloc.c" +#undef LOG_GROUP +#include "common/checksum/crc32.c" +#undef LOG_GROUP +#include "common/checksum/ipv4.c" +#undef LOG_GROUP +#include "common/checksum/ipv6.c" +#undef LOG_GROUP +#include "common/err/errinfo.c" +#undef LOG_GROUP +#include "common/log/log.c" +#undef LOG_GROUP +#include "common/log/logellipsis.c" +#undef LOG_GROUP +#include "common/log/logrel.c" +#undef LOG_GROUP +#include "common/log/logrelellipsis.c" +#undef LOG_GROUP +#include "common/log/logcom.c" +#undef LOG_GROUP +#include "common/log/logformat.c" +#undef LOG_GROUP +#include "common/log/RTLogCreateEx.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg1Weak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2Add.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2AddWeak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2AddWeakV.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2Weak.c" +#undef LOG_GROUP +#include "common/misc/RTAssertMsg2WeakV.c" +#undef LOG_GROUP +#include "common/misc/assert.c" +#undef LOG_GROUP +#include "common/misc/handletable.c" +#undef LOG_GROUP +#include "common/misc/handletablectx.c" +#undef LOG_GROUP +#include "common/misc/thread.c" + diff --git a/src/VBox/HostDrivers/Support/linux/combined-agnostic2.c b/src/VBox/HostDrivers/Support/linux/combined-agnostic2.c new file mode 100644 index 00000000..63da1a7e --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/combined-agnostic2.c @@ -0,0 +1,116 @@ +/* $Id: combined-agnostic2.c $ */ +/** @file + * SUPDrv - Combine a bunch of OS agnostic sources into one compile unit. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define LOG_GROUP LOG_GROUP_DEFAULT +#include "internal/iprt.h" +#include <VBox/log.h> + +#undef LOG_GROUP +#include "common/string/RTStrCat.c" +#undef LOG_GROUP +#include "common/string/RTStrCopy.c" +#undef LOG_GROUP +#include "common/string/RTStrCopyEx.c" +#undef LOG_GROUP +#include "common/string/RTStrCopyP.c" +#undef LOG_GROUP +#include "common/string/RTStrEnd.c" +#undef LOG_GROUP +#include "common/string/RTStrNCmp.c" +#undef LOG_GROUP +#include "common/string/RTStrNLen.c" +#undef LOG_GROUP +#include "common/string/stringalloc.c" +#undef LOG_GROUP +#include "common/string/strformat.c" +#undef LOG_GROUP +#include "common/string/RTStrFormat.c" +#undef LOG_GROUP +#include "common/string/strformatnum.c" +#undef LOG_GROUP +#include "common/string/strformattype.c" +#undef LOG_GROUP +#include "common/string/strprintf.c" +#undef LOG_GROUP +#include "common/string/strprintf-ellipsis.c" +#undef LOG_GROUP +#include "common/string/strprintf2.c" +#undef LOG_GROUP +#include "common/string/strprintf2-ellipsis.c" +#undef LOG_GROUP +#include "common/string/strtonum.c" +#undef LOG_GROUP +#include "common/table/avlpv.c" +#undef LOG_GROUP +#include "common/time/time.c" +#undef LOG_GROUP +#include "generic/RTAssertShouldPanic-generic.c" +#undef LOG_GROUP +#include "generic/RTLogWriteStdErr-stub-generic.c" +#undef LOG_GROUP +#include "generic/RTLogWriteStdOut-stub-generic.c" +#undef LOG_GROUP +#include "generic/RTLogWriteUser-generic.c" +#undef LOG_GROUP +#include "generic/RTMpGetArraySize-generic.c" +#undef LOG_GROUP +#include "generic/RTMpGetCoreCount-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventWait-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventWaitNoResume-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventMultiWait-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTSemEventMultiWaitNoResume-2-ex-generic.c" +#undef LOG_GROUP +#include "generic/RTTimerCreate-generic.c" +#undef LOG_GROUP +#include "generic/errvars-generic.c" +#undef LOG_GROUP +#include "generic/mppresent-generic.c" +#undef LOG_GROUP +#include "generic/uuid-generic.c" +#undef LOG_GROUP +#include "VBox/log-vbox.c" +#undef LOG_GROUP +#include "VBox/RTLogWriteVmm-amd64-x86.c" + +#ifdef RT_ARCH_AMD64 +# undef LOG_GROUP +# include "common/alloc/heapsimple.c" +#endif + diff --git a/src/VBox/HostDrivers/Support/linux/combined-os-specific.c b/src/VBox/HostDrivers/Support/linux/combined-os-specific.c new file mode 100644 index 00000000..e08d2800 --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/combined-os-specific.c @@ -0,0 +1,68 @@ +/* $Id: combined-os-specific.c $ */ +/** @file + * SUPDrv - Combine a bunch of OS specific sources into one compile unit. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +#include "the-linux-kernel.h" + +#include "r0drv/linux/alloc-r0drv-linux.c" +#include "r0drv/linux/assert-r0drv-linux.c" +#include "r0drv/linux/initterm-r0drv-linux.c" +#include "r0drv/linux/memobj-r0drv-linux.c" +#include "r0drv/linux/memuserkernel-r0drv-linux.c" +#include "r0drv/linux/mp-r0drv-linux.c" +#include "r0drv/linux/mpnotification-r0drv-linux.c" +#include "r0drv/linux/process-r0drv-linux.c" +#undef LOG_GROUP +#include "r0drv/linux/rtStrFormatKernelAddress-r0drv-linux.c" +#undef LOG_GROUP +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include "r0drv/linux/semevent-r0drv-linux.c" +#include "r0drv/linux/semeventmulti-r0drv-linux.c" +#include "r0drv/linux/semfastmutex-r0drv-linux.c" +#include "r0drv/linux/semmutex-r0drv-linux.c" +#include "r0drv/linux/spinlock-r0drv-linux.c" +#include "r0drv/linux/thread-r0drv-linux.c" +#include "r0drv/linux/thread2-r0drv-linux.c" +#include "r0drv/linux/threadctxhooks-r0drv-linux.c" +#undef LOG_GROUP +#include "r0drv/linux/time-r0drv-linux.c" +#undef LOG_GROUP +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include "r0drv/linux/timer-r0drv-linux.c" +#include "r0drv/linux/RTLogWriteDebugger-r0drv-linux.c" +#include "common/err/RTErrConvertFromErrno.c" +#include "common/err/RTErrConvertToErrno.c" + diff --git a/src/VBox/HostDrivers/Support/linux/files_vboxdrv b/src/VBox/HostDrivers/Support/linux/files_vboxdrv new file mode 100755 index 00000000..cd8b214a --- /dev/null +++ b/src/VBox/HostDrivers/Support/linux/files_vboxdrv @@ -0,0 +1,242 @@ +#!/bin/sh +# $Id: files_vboxdrv $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +FILES_VBOXDRV_NOBIN=" \ + ${PATH_ROOT}/include/iprt/alloc.h=>include/iprt/alloc.h \ + ${PATH_ROOT}/include/iprt/asm.h=>include/iprt/asm.h \ + ${PATH_ROOT}/include/iprt/asm-amd64-x86.h=>include/iprt/asm-amd64-x86.h \ + ${PATH_ROOT}/include/iprt/asm-math.h=>include/iprt/asm-math.h \ + ${PATH_ROOT}/include/iprt/assert.h=>include/iprt/assert.h \ + ${PATH_ROOT}/include/iprt/assertcompile.h=>include/iprt/assertcompile.h \ + ${PATH_ROOT}/include/iprt/avl.h=>include/iprt/avl.h \ + ${PATH_ROOT}/include/iprt/cdefs.h=>include/iprt/cdefs.h \ + ${PATH_ROOT}/include/iprt/cpuset.h=>include/iprt/cpuset.h \ + ${PATH_ROOT}/include/iprt/crc.h=>include/iprt/crc.h \ + ${PATH_ROOT}/include/iprt/ctype.h=>include/iprt/ctype.h \ + ${PATH_ROOT}/include/iprt/err.h=>include/iprt/err.h \ + ${PATH_ROOT}/include/iprt/errcore.h=>include/iprt/errcore.h \ + ${PATH_ROOT}/include/iprt/errno.h=>include/iprt/errno.h \ + ${PATH_ROOT}/include/iprt/heap.h=>include/iprt/heap.h \ + ${PATH_ROOT}/include/iprt/handletable.h=>include/iprt/handletable.h \ + ${PATH_ROOT}/include/iprt/initterm.h=>include/iprt/initterm.h \ + ${PATH_ROOT}/include/iprt/latin1.h=>include/iprt/latin1.h \ + ${PATH_ROOT}/include/iprt/list.h=>include/iprt/list.h \ + ${PATH_ROOT}/include/iprt/lockvalidator.h=>include/iprt/lockvalidator.h \ + ${PATH_ROOT}/include/iprt/log.h=>include/iprt/log.h \ + ${PATH_ROOT}/include/iprt/mangling.h=>include/iprt/mangling.h \ + ${PATH_ROOT}/include/iprt/mem.h=>include/iprt/mem.h \ + ${PATH_ROOT}/include/iprt/memobj.h=>include/iprt/memobj.h \ + ${PATH_ROOT}/include/iprt/mp.h=>include/iprt/mp.h \ + ${PATH_ROOT}/include/iprt/net.h=>include/iprt/net.h \ + ${PATH_ROOT}/include/iprt/param.h=>include/iprt/param.h \ + ${PATH_ROOT}/include/iprt/path.h=>include/iprt/path.h \ + ${PATH_ROOT}/include/iprt/power.h=>include/iprt/power.h \ + ${PATH_ROOT}/include/iprt/process.h=>include/iprt/process.h \ + ${PATH_ROOT}/include/iprt/rand.h=>include/iprt/rand.h \ + ${PATH_ROOT}/include/iprt/semaphore.h=>include/iprt/semaphore.h \ + ${PATH_ROOT}/include/iprt/spinlock.h=>include/iprt/spinlock.h \ + ${PATH_ROOT}/include/iprt/stdarg.h=>include/iprt/stdarg.h \ + ${PATH_ROOT}/include/iprt/stdint.h=>include/iprt/stdint.h \ + ${PATH_ROOT}/include/iprt/string.h=>include/iprt/string.h \ + ${PATH_ROOT}/include/iprt/thread.h=>include/iprt/thread.h \ + ${PATH_ROOT}/include/iprt/time.h=>include/iprt/time.h \ + ${PATH_ROOT}/include/iprt/timer.h=>include/iprt/timer.h \ + ${PATH_ROOT}/include/iprt/types.h=>include/iprt/types.h \ + ${PATH_ROOT}/include/iprt/uint128.h=>include/iprt/uint128.h \ + ${PATH_ROOT}/include/iprt/uint64.h=>include/iprt/uint64.h \ + ${PATH_ROOT}/include/iprt/uni.h=>include/iprt/uni.h \ + ${PATH_ROOT}/include/iprt/utf16.h=>include/iprt/utf16.h \ + ${PATH_ROOT}/include/iprt/uuid.h=>include/iprt/uuid.h \ + ${PATH_ROOT}/include/iprt/x86.h=>include/iprt/x86.h \ + ${PATH_ROOT}/include/iprt/x86-helpers.h=>include/iprt/x86-helpers.h \ + ${PATH_ROOT}/include/iprt/linux/version.h=>include/iprt/linux/version.h \ + ${PATH_ROOT}/include/iprt/nocrt/limits.h=>include/iprt/nocrt/limits.h \ + ${PATH_ROOT}/include/VBox/cdefs.h=>include/VBox/cdefs.h \ + ${PATH_ROOT}/include/VBox/err.h=>include/VBox/err.h \ + ${PATH_ROOT}/include/VBox/log.h=>include/VBox/log.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/sup.h=>include/VBox/sup.h \ + ${PATH_ROOT}/include/VBox/types.h=>include/VBox/types.h \ + ${PATH_ROOT}/include/VBox/SUPDrvMangling.h=>include/VBox/SUPDrvMangling.h \ + ${PATH_ROOT}/include/VBox/VBoxTpG.h=>include/VBox/VBoxTpG.h \ + ${PATH_ROOT}/include/VBox/vmm/hm_vmx.h=>include/VBox/vmm/hm_vmx.h \ + ${PATH_ROOT}/include/VBox/vmm/hm_svm.h=>include/VBox/vmm/hm_svm.h \ + ${PATH_ROOT}/include/VBox/vmm/cpuidcall.h=>include/VBox/vmm/cpuidcall.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c=>linux/SUPDrv-linux.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/combined-agnostic1.c=>combined-agnostic1.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/combined-agnostic2.c=>combined-agnostic2.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/combined-os-specific.c=>combined-os-specific.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrv.cpp=>SUPDrv.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvGip.cpp=>SUPDrvGip.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvSem.cpp=>SUPDrvSem.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvTracer.cpp=>SUPDrvTracer.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp=>SUPDrvDTrace.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIOC.h=>SUPDrvIOC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvInternal.h=>SUPDrvInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPLibAll.cpp=>SUPLibAll.c \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-footer.gmk=>Makefile-footer.gmk \ + ${PATH_ROOT}/src/VBox/Installer/linux/Makefile-header.gmk=>Makefile-header.gmk \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/alloc.cpp=>common/alloc/alloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/alloc/heapsimple.cpp=>common/alloc/heapsimple.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/crc32.cpp=>common/checksum/crc32.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv4.cpp=>common/checksum/ipv4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/checksum/ipv6.cpp=>common/checksum/ipv6.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp=>common/err/RTErrConvertFromErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp=>common/err/RTErrConvertToErrno.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/err/errinfo.cpp=>common/err/errinfo.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/log.cpp=>common/log/log.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logellipsis.cpp=>common/log/logellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrel.cpp=>common/log/logrel.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logrelellipsis.cpp=>common/log/logrelellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logcom.cpp=>common/log/logcom.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/logformat.cpp=>common/log/logformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/log/RTLogCreateEx.cpp=>common/log/RTLogCreateEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divdi3.c=>math/gcc/divdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/divmoddi4.c=>math/gcc/divmoddi4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/moddi3.c=>math/gcc/moddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/qdivrem.c=>math/gcc/qdivrem.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/quad.h=>math/gcc/quad.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivdi3.c=>math/gcc/udivdi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/udivmoddi4.c=>math/gcc/udivmoddi4.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/math/gcc/umoddi3.c=>math/gcc/umoddi3.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp=>common/misc/RTAssertMsg1Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp=>common/misc/RTAssertMsg2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp=>common/misc/RTAssertMsg2Add.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp=>common/misc/RTAssertMsg2AddWeak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp=>common/misc/RTAssertMsg2AddWeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp=>common/misc/RTAssertMsg2Weak.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp=>common/misc/RTAssertMsg2WeakV.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/assert.cpp=>common/misc/assert.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.cpp=>common/misc/handletable.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletable.h=>common/misc/handletable.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/handletablectx.cpp=>common/misc/handletablectx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/misc/thread.cpp=>common/misc/thread.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCat.cpp=>common/string/RTStrCat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopy.cpp=>common/string/RTStrCopy.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyEx.cpp=>common/string/RTStrCopyEx.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrCopyP.cpp=>common/string/RTStrCopyP.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrEnd.cpp=>common/string/RTStrEnd.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNCmp.cpp=>common/string/RTStrNCmp.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrNLen.cpp=>common/string/RTStrNLen.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/stringalloc.cpp=>common/string/stringalloc.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformat.cpp=>common/string/strformat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/RTStrFormat.cpp=>common/string/RTStrFormat.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatnum.cpp=>common/string/strformatnum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformatrt.cpp=>common/string/strformatrt.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strformattype.cpp=>common/string/strformattype.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf.cpp=>common/string/strprintf.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp=>common/string/strprintf-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2.cpp=>common/string/strprintf2.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp=>common/string/strprintf2-ellipsis.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/string/strtonum.cpp=>common/string/strtonum.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avlpv.cpp=>common/table/avlpv.c \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Base.cpp.h=>common/table/avl_Base.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Get.cpp.h=>common/table/avl_Get.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h=>common/table/avl_GetBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h=>common/table/avl_RemoveBestFit.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h=>common/table/avl_DoWithAll.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/table/avl_Destroy.cpp.h=>common/table/avl_Destroy.cpp.h \ + ${PATH_ROOT}/src/VBox/Runtime/common/time/time.cpp=>common/time/time.c \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/assert.h=>include/internal/assert.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/initterm.h=>include/internal/initterm.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/lockvalidator.h=>include/internal/lockvalidator.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/magics.h=>include/internal/magics.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/mem.h=>include/internal/mem.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/memobj.h=>include/internal/memobj.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/string.h=>include/internal/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/sched.h=>include/internal/sched.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/process.h=>include/internal/process.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/thread.h=>include/internal/thread.h \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/time.h=>include/internal/time.h \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTAssertShouldPanic-generic.cpp=>generic/RTAssertShouldPanic-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdErr-stub-generic.cpp=>generic/RTLogWriteStdErr-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteStdOut-stub-generic.cpp=>generic/RTLogWriteStdOut-stub-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTLogWriteUser-generic.cpp=>generic/RTLogWriteUser-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpGetArraySize-generic.cpp=>generic/RTMpGetArraySize-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTMpGetCoreCount-generic.cpp=>generic/RTMpGetCoreCount-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWait-2-ex-generic.cpp=>generic/RTSemEventWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWait-2-ex-generic.cpp=>generic/RTSemEventMultiWait-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTSemEventMultiWaitNoResume-2-ex-generic.cpp=>generic/RTSemEventMultiWaitNoResume-2-ex-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/RTTimerCreate-generic.cpp=>generic/RTTimerCreate-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/errvars-generic.cpp=>generic/errvars-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/mppresent-generic.cpp=>generic/mppresent-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/generic/uuid-generic.cpp=>generic/uuid-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.cpp=>r0drv/alloc-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/alloc-r0drv.h=>r0drv/alloc-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/initterm-r0drv.cpp=>r0drv/initterm-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mp-r0drv.h=>r0drv/mp-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/mpnotification-r0drv.c=>r0drv/mpnotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/power-r0drv.h=>r0drv/power-r0drv.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/powernotification-r0drv.c=>r0drv/powernotification-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/rtStrFormatKernelAddress-r0drv-linux.c=>r0drv/linux/rtStrFormatKernelAddress-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/RTLogWriteDebugger-r0drv-linux.c=>r0drv/linux/RTLogWriteDebugger-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/assert-r0drv-linux.c=>r0drv/linux/assert-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/alloc-r0drv-linux.c=>r0drv/linux/alloc-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/initterm-r0drv-linux.c=>r0drv/linux/initterm-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memobj-r0drv-linux.c=>r0drv/linux/memobj-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/memuserkernel-r0drv-linux.c=>r0drv/linux/memuserkernel-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mp-r0drv-linux.c=>r0drv/linux/mp-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/mpnotification-r0drv-linux.c=>r0drv/linux/mpnotification-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/process-r0drv-linux.c=>r0drv/linux/process-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semevent-r0drv-linux.c=>r0drv/linux/semevent-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semeventmulti-r0drv-linux.c=>r0drv/linux/semeventmulti-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semfastmutex-r0drv-linux.c=>r0drv/linux/semfastmutex-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/semmutex-r0drv-linux.c=>r0drv/linux/semmutex-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/spinlock-r0drv-linux.c=>r0drv/linux/spinlock-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/string.h=>r0drv/linux/string.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread-r0drv-linux.c=>r0drv/linux/thread-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/thread2-r0drv-linux.c=>r0drv/linux/thread2-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c=>r0drv/linux/threadctxhooks-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/time-r0drv-linux.c=>r0drv/linux/time-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/timer-r0drv-linux.c=>r0drv/linux/timer-r0drv-linux.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/waitqueue-r0drv-linux.h=>r0drv/linux/waitqueue-r0drv-linux.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/semspinmutex-r0drv-generic.c=>r0drv/generic/semspinmutex-r0drv-generic.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/generic/threadctxhooks-r0drv-generic.cpp=>r0drv/generic/threadctxhooks-r0drv-generic.cpp \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/memobj-r0drv.cpp=>r0drv/memobj-r0drv.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/log-vbox.cpp=>VBox/log-vbox.c \ + ${PATH_ROOT}/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.cpp=>VBox/RTLogWriteVmm-amd64-x86.c \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + +FILES_VBOXDRV_BIN=" \ +" diff --git a/src/VBox/HostDrivers/Support/os2/Makefile.kup b/src/VBox/HostDrivers/Support/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp new file mode 100644 index 00000000..9379f4ac --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp @@ -0,0 +1,570 @@ +/* $Id: SUPDrv-os2.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - OS/2 specifics. + */ + +/* + * Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include <os2ddk/bsekee.h> +#undef RT_MAX + +#include "SUPDrvInternal.h" +#include <VBox/version.h> +#include <iprt/initterm.h> +#include <iprt/string.h> +#include <iprt/spinlock.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <VBox/log.h> +#include <iprt/param.h> +#include <VBox/version.h> + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Device extention & session data association structure. + */ +static SUPDRVDEVEXT g_DevExt; +/** Spinlock protecting g_apSessionHashTab. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +/** Hash table */ +static PSUPDRVSESSION g_apSessionHashTab[19]; +/** Calculates the index into g_apSessionHashTab.*/ +#define SESSION_HASH(sfn) ((sfn) % RT_ELEMENTS(g_apSessionHashTab)) + +RT_C_DECLS_BEGIN +/* Defined in SUPDrvA-os2.asm */ +extern uint16_t g_offLogHead; +extern uint16_t volatile g_offLogTail; +extern uint16_t const g_cchLogMax; +extern char g_szLog[]; +/* (init only:) */ +extern char g_szInitText[]; +extern uint16_t g_cchInitText; +extern uint16_t g_cchInitTextMax; +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + + +/** + * 32-bit Ring-0 initialization. + * + * @returns 0 on success, non-zero on failure. + * @param pszArgs Pointer to the device arguments. + */ +DECLASM(int) VBoxDrvInit(const char *pszArgs) +{ + /* + * Initialize the runtime. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxDrvInit: pszArgs=%s\n", pszArgs)); + + /* + * Initialize the device extension. + */ + rc = supdrvInitDevExt(&g_DevExt, sizeof(SUPDRVSESSION)); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the session hash table. + */ + rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxDrvOS2"); + if (RT_SUCCESS(rc)) + { + /* + * Process the commandline. Later. + */ + bool fVerbose = true; + + /* + * Success + */ + if (fVerbose) + { + strcpy(&g_szInitText[0], + "\r\n" + "VirtualBox.org Support Driver for OS/2 version " VBOX_VERSION_STRING "\r\n" + "Copyright (C) 2007 Knut St. Osmundsen\r\n" + "Copyright (C) 2007-" VBOX_C_YEAR " Oracle Corporation\r\n"); + g_cchInitText = strlen(&g_szInitText[0]); + } + return VINF_SUCCESS; + } + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxDrv.sys: RTSpinlockCreate failed, rc=%Rrc\n", rc); + supdrvDeleteDevExt(&g_DevExt); + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxDrv.sys: supdrvInitDevExt failed, rc=%Rrc\n", rc); + RTR0Term(); + } + else + g_cchInitText = RTStrPrintf(&g_szInitText[0], g_cchInitTextMax, "VBoxDrv.sys: RTR0Init failed, rc=%Rrc\n", rc); + return rc; +} + + +DECLASM(int) VBoxDrvOpen(uint16_t sfn) +{ + int rc; + PSUPDRVSESSION pSession; + + /* + * Create a new session. + */ + rc = supdrvCreateSession(&g_DevExt, true /* fUser */, true /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { + pSession->sfn = sfn; + + /* + * Insert it into the hash table. + */ + unsigned iHash = SESSION_HASH(sfn); + RTSpinlockAcquire(g_Spinlock); + pSession->pNextHash = g_apSessionHashTab[iHash]; + g_apSessionHashTab[iHash] = pSession; + RTSpinlockRelease(g_Spinlock); + } + + Log(("VBoxDrvOpen: g_DevExt=%p pSession=%p rc=%d pid=%d\n", &g_DevExt, pSession, rc, (int)RTProcSelf())); + return rc; +} + + +DECLASM(int) VBoxDrvClose(uint16_t sfn) +{ + Log(("VBoxDrvClose: pid=%d sfn=%d\n", (int)RTProcSelf(), sfn)); + + /* + * Remove from the hash table. + */ + PSUPDRVSESSION pSession; + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + RTSpinlockAcquire(g_Spinlock); + + pSession = g_apSessionHashTab[iHash]; + if (pSession) + { + if ( pSession->sfn == sfn + && pSession->Process == Process) + { + g_apSessionHashTab[iHash] = pSession->pNextHash; + pSession->pNextHash = NULL; + } + else + { + PSUPDRVSESSION pPrev = pSession; + pSession = pSession->pNextHash; + while (pSession) + { + if ( pSession->sfn == sfn + && pSession->Process == Process) + { + pPrev->pNextHash = pSession->pNextHash; + pSession->pNextHash = NULL; + break; + } + + /* next */ + pPrev = pSession; + pSession = pSession->pNextHash; + } + } + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + OSDBGPRINT(("VBoxDrvIoctl: WHUT?!? pSession == NULL! This must be a mistake... pid=%d sfn=%d\n", (int)Process, sfn)); + return VERR_INVALID_PARAMETER; + } + + /* + * Close the session. + */ + supdrvSessionRelease(pSession); + return 0; +} + + +DECLASM(int) VBoxDrvIOCtlFast(uint16_t sfn, uint8_t iFunction) +{ + /* + * Find the session. + */ + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + PSUPDRVSESSION pSession; + + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + if (pSession && pSession->Process != Process) + { + do pSession = pSession->pNextHash; + while ( pSession + && ( pSession->sfn != sfn + || pSession->Process != Process)); + + if (RT_LIKELY(pSession)) + supdrvSessionRetain(pSession); + } + RTSpinlockRelease(g_Spinlock); + if (RT_UNLIKELY(!pSession)) + { + OSDBGPRINT(("VBoxDrvIoctl: WHUT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process)); + return VERR_INVALID_PARAMETER; + } + + /* + * Dispatch the fast IOCtl. + */ + int rc; + if ((unsigned)(iFunction - SUP_IOCTL_FAST_DO_FIRST) < (unsigned)32) + rc = supdrvIOCtlFast(iFunction, 0, &g_DevExt, pSession); + else + rc = VERR_INVALID_FUNCTION; + supdrvSessionRelease(pSession); + return rc; +} + + +DECLASM(int) VBoxDrvIOCtl(uint16_t sfn, uint8_t iCat, uint8_t iFunction, void *pvParm, void *pvData, uint16_t *pcbParm, uint16_t *pcbData) +{ + /* + * Find the session. + */ + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(sfn); + PSUPDRVSESSION pSession; + + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + if (pSession && pSession->Process != Process) + { + do pSession = pSession->pNextHash; + while ( pSession + && ( pSession->sfn != sfn + || pSession->Process != Process)); + + if (RT_LIKELY(pSession)) + supdrvSessionRetain(pSession); + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + OSDBGPRINT(("VBoxDrvIoctl: WHUT?!? pSession == NULL! This must be a mistake... pid=%d\n", (int)Process)); + return VERR_INVALID_PARAMETER; + } + + /* + * Verify the category and dispatch the IOCtl. + */ + int rc; + if (RT_LIKELY(iCat == SUP_CTL_CATEGORY)) + { + Log(("VBoxDrvIOCtl: pSession=%p iFunction=%#x pvParm=%p pvData=%p *pcbParm=%d *pcbData=%d\n", pSession, iFunction, pvParm, pvData, *pcbParm, *pcbData)); + Assert(pvParm); + Assert(!pvData); + + /* + * Lock the header. + */ + PSUPREQHDR pHdr = (PSUPREQHDR)pvParm; + AssertReturn(*pcbParm == sizeof(*pHdr), VERR_INVALID_PARAMETER); + KernVMLock_t Lock; + rc = KernVMLock(VMDHL_WRITE, pHdr, *pcbParm, &Lock, (KernPageList_t *)-1, NULL); + AssertMsgReturn(!rc, ("KernVMLock(VMDHL_WRITE, %p, %#x, &p, NULL, NULL) -> %d\n", pHdr, *pcbParm, &Lock, rc), VERR_LOCK_FAILED); + + /* + * Validate the header. + */ + if (RT_LIKELY((pHdr->fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) == SUPREQHDR_FLAGS_MAGIC)) + { + uint32_t cbReq = RT_MAX(pHdr->cbIn, pHdr->cbOut); + if (RT_LIKELY( pHdr->cbIn >= sizeof(*pHdr) + && pHdr->cbOut >= sizeof(*pHdr) + && cbReq <= _1M*16)) + { + /* + * Lock the rest of the buffer if necessary. + */ + if (((uintptr_t)pHdr & PAGE_OFFSET_MASK) + cbReq > PAGE_SIZE) + { + rc = KernVMUnlock(&Lock); + AssertMsgReturn(!rc, ("KernVMUnlock(Lock) -> %#x\n", rc), VERR_LOCK_FAILED); + + rc = KernVMLock(VMDHL_WRITE, pHdr, cbReq, &Lock, (KernPageList_t *)-1, NULL); + AssertMsgReturn(!rc, ("KernVMLock(VMDHL_WRITE, %p, %#x, &p, NULL, NULL) -> %d\n", pHdr, cbReq, &Lock, rc), VERR_LOCK_FAILED); + } + + /* + * Process the IOCtl. + */ + rc = supdrvIOCtl(iFunction, &g_DevExt, pSession, pHdr, cbReq); + } + else + { + OSDBGPRINT(("VBoxDrvIOCtl: max(%#x,%#x); iCmd=%#x\n", pHdr->cbIn, pHdr->cbOut, iFunction)); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + OSDBGPRINT(("VBoxDrvIOCtl: bad magic fFlags=%#x; iCmd=%#x\n", pHdr->fFlags, iFunction)); + rc = VERR_INVALID_PARAMETER; + } + + /* + * Unlock and return. + */ + int rc2 = KernVMUnlock(&Lock); + AssertMsg(!rc2, ("rc2=%d\n", rc2)); NOREF(rc2); + } + else + rc = VERR_NOT_SUPPORTED; + + supdrvSessionRelease(pSession); + Log2(("VBoxDrvIOCtl: returns %d\n", rc)); + return 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); +} + + +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + NOREF(pDevExt); + return false; +} + + +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + return false; +} + + +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + return false; +} + + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt, pImage, pszSymbol, cchSymbol, ppvSymbol); + return VERR_WRONG_ORDER; +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + +#ifdef SUPDRV_WITH_MSR_PROBER + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + NOREF(uMsr); NOREF(idCpu); NOREF(puValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + NOREF(uMsr); NOREF(idCpu); NOREF(uValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + NOREF(idCpu); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +/** + * Callback for writing to the log buffer. + * + * @returns number of bytes written. + * @param pvArg Unused. + * @param pachChars Pointer to an array of utf-8 characters. + * @param cbChars Number of bytes in the character array pointed to by pachChars. + */ +static DECLCALLBACK(size_t) VBoxDrvLogOutput(void *pvArg, const char *pachChars, size_t cbChars) +{ + size_t cchWritten = 0; + while (cbChars-- > 0) + { + const uint16_t offLogHead = g_offLogHead; + const uint16_t offLogHeadNext = (offLogHead + 1) & (g_cchLogMax - 1); + if (offLogHeadNext == g_offLogTail) + break; /* no */ + g_szLog[offLogHead] = *pachChars++; + g_offLogHead = offLogHeadNext; + cchWritten++; + } + return cchWritten; +} + + +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ +#if 0 //def DEBUG_bird + va_list va2; + va_copy(va2, va); + RTLogComPrintfV(pszFormat, va2); + va_end(va2); +#endif + + RTLogFormatV(VBoxDrvLogOutput, NULL, pszFormat, va); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + diff --git a/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.def b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.def new file mode 100644 index 00000000..ae815d5e --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/SUPDrv-os2.def @@ -0,0 +1,55 @@ +; $Id: SUPDrv-os2.def $ +;; @file +; VBoxDrv - OS/2 definition file. +; + +; +; Copyright (C) 2007-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +PHYSICAL DEVICE VBoxDrv +DESCRIPTION 'VirtualBox.org Support Driver for OS/2.' +CODE PRELOAD EXECUTEREAD +DATA PRELOAD +; We're using wlink.exe, so this doesn't work. +;SEGMENTS +; DATA16 class 'FAR_DATA' +; DATA16_INIT class 'FAR_DATA' +; +; CODE16 class 'CODE' +; CODE16_INIT class 'CODE' +; +; CODE32 class 'CODE' +; TEXT32 class 'CODE' +; +; DATA32 class 'DATA' +; BSS32 class 'BSS' + diff --git a/src/VBox/HostDrivers/Support/os2/SUPDrvA-os2.asm b/src/VBox/HostDrivers/Support/os2/SUPDrvA-os2.asm new file mode 100644 index 00000000..b27fcd0f --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/SUPDrvA-os2.asm @@ -0,0 +1,973 @@ +; $Id: SUPDrvA-os2.asm $ +;; @file +; VBoxDrv - OS/2 assembly file, the first file in the link. +; + +; +; Copyright (c) 2007 knut st. osmundsen <bird-src-spam@anduin.net> +; +; Permission is hereby granted, free of charge, to any person +; obtaining a copy of this software and associated documentation +; files (the "Software"), to deal in the Software without +; restriction, including without limitation the rights to use, +; copy, modify, merge, publish, distribute, sublicense, and/or sell +; copies of the Software, and to permit persons to whom the +; Software is furnished to do so, subject to the following +; conditions: +; +; The above copyright notice and this permission notice shall be +; included in all copies or substantial portions of the Software. +; +; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +; OTHER DEALINGS IN THE SOFTWARE. +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%define RT_INCL_16BIT_SEGMENTS +%include "iprt/asmdefs.mac" + + +;******************************************************************************* +;* Structures and Typedefs * +;******************************************************************************* +;; +; Request packet header. +struc PKTHDR + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 +endstruc + + +;; +; Init request packet - input. +struc PKTINITIN + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .data_1 resb 1 + .fpfnDevHlp resd 1 + .fpszArgs resd 1 + .data_2 resb 1 +endstruc + +;; +; Init request packet - output. +struc PKTINITOUT + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .cUnits resb 1 ; block devs only. + .cbCode16 resw 1 + .cbData16 resw 1 + .fpaBPBs resd 1 ; block devs only. + .data_2 resb 1 +endstruc + +;; +; Open request packet. +struc PKTOPEN + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + .sfn resw 1 +endstruc + +;; +; Close request packet. +struc PKTCLOSE + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + .sfn resw 1 +endstruc + +;; +; IOCtl request packet. +struc PKTIOCTL + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .cat resb 1 + .fun resb 1 + .pParm resd 1 + .pData resd 1 + .sfn resw 1 + .cbParm resw 1 + .cbData resw 1 +endstruc + +;; +; Read/Write request packet +struc PKTRW + .cb resb 1 + .unit resb 1 + .cmd resb 1 + .status resw 1 + .res1 resd 1 + .link resd 1 + + .media resb 1 + .PhysTrans resd 1 + .cbTrans resw 1 + .start resd 1 + .sfn resw 1 +endstruc + + + +;; +; The two device headers. +segment DATA16 + +; Some devhdr.inc stuff. +%define DEVLEV_3 0180h +%define DEV_30 0800h +%define DEV_CHAR_DEV 8000h +%define DEV_16MB 0002h +%define DEV_IOCTL2 0001h + +; Some dhcalls.h stuff. +%define DevHlp_VirtToLin 05bh +%define DevHlp_SAVE_MESSAGE 03dh +%define DevHlp_PhysToVirt 015h + +; Fast IOCtl category, also defined in SUPDrvIOC.h +%define SUP_CTL_CATEGORY_FAST 0c1h + + +;******************************************************************************* +;* External Symbols * +;******************************************************************************* +extern KernThunkStackTo32 +extern KernThunkStackTo16 +extern DOS16OPEN +extern DOS16CLOSE +extern DOS16WRITE +extern NAME(VBoxDrvInit) +extern NAME(VBoxDrvOpen) +extern NAME(VBoxDrvClose) +extern NAME(VBoxDrvIOCtl) +extern NAME(VBoxDrvIOCtlFast) + + +;; +; Device headers. The first one is the one we'll be opening and the +; latter is only used for 32-bit initialization. +GLOBALNAME g_VBoxDrvHdr1 + dw NAME(g_VBoxDrvHdr2) wrt DATA16 ; NextHeader.off + dw DATA16 ; NextHeader.sel + dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV; SDevAtt + dw NAME(VBoxDrvEP) wrt CODE16 ; StrategyEP + dw 0 ; InterruptEP + db 'vboxdrv$' ; DevName + dw 0 ; SDevProtCS + dw 0 ; SDevProtDS + dw 0 ; SDevRealCS + dw 0 ; SDevRealDS + dd DEV_16MB | DEV_IOCTL2 ; SDevCaps + +align 4 +GLOBALNAME g_VBoxDrvHdr2 + dd 0ffffffffh ; NextHeader (NIL) + dw DEVLEV_3 | DEV_30 | DEV_CHAR_DEV; SDevAtt + dw NAME(VBoxDrvInitEP) wrt CODE16 ; StrategyEP + dw 0 ; InterruptEP + db 'vboxdr1$' ; DevName + dw 0 ; SDevProtCS + dw 0 ; SDevProtDS + dw 0 ; SDevRealCS + dw 0 ; SDevRealDS + dd DEV_16MB | DEV_IOCTL2 ; SDevCaps + + +;; Tristate 32-bit initialization indicator [0 = need init, -1 = init failed, 1 init succeeded]. +; Check in the open path of the primary driver. The secondary driver will +; open the primary one during it's init and thereby trigger the 32-bit init. +GLOBALNAME g_fInitialized + db 0 + +align 4 +;; Pointer to the device helper service routine +; This is set during the initialization of the 2nd device driver. +GLOBALNAME g_fpfnDevHlp + dd 0 + + +;; Where we write to the log. +GLOBALNAME g_offLogHead + dw 0 +;; Where we read from the log. +GLOBALNAME g_offLogTail + dw 0 +;; The size of the log. (power of two!) +%define LOG_SIZE 16384 +GLOBALNAME g_cchLogMax + dw LOG_SIZE +;; The log buffer. +GLOBALNAME g_szLog + times LOG_SIZE db 0 + + +; +; The init data. +; +segment DATA16_INIT +GLOBALNAME g_InitDataStart + +;; Far pointer to the device argument. +g_fpszArgs: + dd 0 + +%if 0 +;; Message table for the Save_Message device helper. +GLOBALNAME g_MsgTab + dw 1178 ; MsgId - 'MSG_REPLACEMENT_STRING'. + dw 1 ; cMsgStrings + dw NAME(g_szInitText) ; MsgStrings[0] + dw seg NAME(g_szInitText) +%else +;; Far pointer to DOS16WRITE (corrected set before called). +; Just a temporary hack to work around a wlink issue. +GLOBALNAME g_fpfnDos16Write + dw DOS16WRITE + dw seg DOS16WRITE +%endif + +;; Size of the text currently in the g_szInitText buffer. +GLOBALNAME g_cchInitText + dw 0 +;; The max size of text that can fit into the g_szInitText buffer. +GLOBALNAME g_cchInitTextMax + dw 512 +;; The init text buffer. +GLOBALNAME g_szInitText + times 512 db 0 + +; +; The 16-bit code segment. +; +segment CODE16 + + +;; +; The strategy entry point (vboxdrv$). +; +; ss:bx -> request packet +; ds:si -> device header +; +; Can clobber any registers it likes except SP. +; +BEGINPROC VBoxDrvEP + push ebp + mov ebp, esp + push es ; bp - 2 + push bx ; bp - 4 + and sp, 0fffch + + ; + ; Check for the most frequent first. + ; + cmp byte [es:bx + PKTHDR.cmd], 10h ; Generic IOCtl + jne near VBoxDrvEP_NotGenIOCtl + + + ; + ; Generic I/O Control Request. + ; +VBoxDrvEP_GenIOCtl: + + ; Fast IOCtl? + cmp byte [es:bx + PKTIOCTL.cat], SUP_CTL_CATEGORY_FAST + jne VBoxDrvEP_GenIOCtl_Other + + ; + ; Fast IOCtl. + ; DECLASM(int) VBoxDrvIOCtlFast(uint16_t sfn, uint8_t iFunction) + ; +VBoxDrvEP_GenIOCtl_Fast: + ; function. + movzx edx, byte [es:bx + PKTIOCTL.fun] + push edx ; 04h + + ; system file number. + movzx eax, word [es:bx + PKTIOCTL.sfn] + push eax ; 00h + + ; go to the 32-bit code + ;jmp far dword NAME(VBoxDrvEP_GenIOCtl_Fast_32) wrt FLAT + db 066h + db 0eah + dd NAME(VBoxDrvEP_GenIOCtl_Fast_32) ;wrt FLAT + dw TEXT32 wrt FLAT +segment TEXT32 +GLOBALNAME VBoxDrvEP_GenIOCtl_Fast_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code (don't cleanup the stack). + call NAME(VBoxDrvIOCtlFast) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + ; jump back to the 16-bit code. + ;jmp far dword NAME(VBoxDrvEP_GenIOCtl_Fast_16) wrt CODE16 + db 066h + db 0eah + dw NAME(VBoxDrvEP_GenIOCtl_Fast_16) wrt CODE16 + dw CODE16 +segment CODE16 +GLOBALNAME VBoxDrvEP_GenIOCtl_Fast_16 + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near VBoxDrvEP_GeneralFailure + + ; setup output stuff. + xor eax, eax + mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData. + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + + mov sp, bp + pop ebp + retf + + ; + ; Other IOCtl (slow) + ; +VBoxDrvEP_GenIOCtl_Other: + mov eax, [es:bx + PKTIOCTL.cbParm] ; Load cbParm and cbData + push eax ; 1eh - in/out data size. + ; 1ch - in/out parameter size. + push edx ; 18h - pointer to data size (filled in later). + push ecx ; 14h - pointer to param size (filled in later). + + ; pData (convert to flat 32-bit) + mov ax, word [es:bx + PKTIOCTL.pData + 2] ; selector + cmp ax, 3 ; <= 3 -> nil selector... + jbe .no_data + movzx esi, word [es:bx + PKTIOCTL.pData] ; offset + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near VBoxDrvEP_GeneralFailure + jmp .finish_data +.no_data: + xor eax, eax +.finish_data: + push eax ; 10h + + ; pParm (convert to flat 32-bit) + mov ax, word [es:bx + PKTIOCTL.pParm + 2] ; selector + cmp ax, 3 ; <= 3 -> nil selector... + jbe .no_parm + movzx esi, word [es:bx + PKTIOCTL.pParm] ; offset + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near VBoxDrvEP_GeneralFailure + jmp .finish_parm +.no_parm: + xor eax, eax +.finish_parm: + push eax ; 0ch + + ; function. + movzx edx, byte [es:bx + PKTIOCTL.fun] + push edx ; 08h + + ; category. + movzx ecx, byte [es:bx + PKTIOCTL.cat] + push ecx ; 04h + + ; system file number. + movzx eax, word [es:bx + PKTIOCTL.sfn] + push eax ; 00h + + ; go to the 32-bit code + ;jmp far dword NAME(VBoxDrvEP_GenIOCtl_Other_32) wrt FLAT + db 066h + db 0eah + dd NAME(VBoxDrvEP_GenIOCtl_Other_32) ;wrt FLAT + dw TEXT32 wrt FLAT +segment TEXT32 +GLOBALNAME VBoxDrvEP_GenIOCtl_Other_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; update in/out parameter pointers + lea eax, [esp + 1ch] + mov [esp + 14h], eax + lea edx, [esp + 1eh] + mov [esp + 18h], edx + + ; call the C code (don't cleanup the stack). + call NAME(VBoxDrvIOCtl) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + ; jump back to the 16-bit code. + ;jmp far dword NAME(VBoxDrvEP_GenIOCtl_Other_16) wrt CODE16 + db 066h + db 0eah + dw NAME(VBoxDrvEP_GenIOCtl_Other_16) wrt CODE16 + dw CODE16 +segment CODE16 +GLOBALNAME VBoxDrvEP_GenIOCtl_Other_16 + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near VBoxDrvEP_GeneralFailure + + ; setup output stuff. + mov edx, esp + mov eax, [ss:edx + 1ch] ; output sizes. + mov [es:bx + PKTIOCTL.cbParm], eax ; update cbParm and cbData. + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + + mov sp, bp + pop ebp + retf + + + ; + ; Less Performance Critical Requests. + ; +VBoxDrvEP_NotGenIOCtl: + cmp byte [es:bx + PKTHDR.cmd], 0dh ; Open + je VBoxDrvEP_Open + cmp byte [es:bx + PKTHDR.cmd], 0eh ; Close + je VBoxDrvEP_Close + cmp byte [es:bx + PKTHDR.cmd], 00h ; Init + je VBoxDrvEP_Init + cmp byte [es:bx + PKTHDR.cmd], 04h ; Read + je near VBoxDrvEP_Read + jmp near VBoxDrvEP_NotSupported + + + ; + ; Open Request. w/ ring-0 init. + ; +VBoxDrvEP_Open: + cmp byte [NAME(g_fInitialized)], 1 + jne VBoxDrvEP_OpenOther + + ; First argument, the system file number. + movzx eax, word [es:bx + PKTOPEN.sfn] + push eax + + ; go to the 32-bit code + ;jmp far dword NAME(VBoxDrvEP_Open_32) wrt FLAT + db 066h + db 0eah + dd NAME(VBoxDrvEP_Open_32) ;wrt FLAT + dw TEXT32 wrt FLAT +segment TEXT32 +GLOBALNAME VBoxDrvEP_Open_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(VBoxDrvOpen) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + ; jump back to the 16-bit code. + ;jmp far dword NAME(VBoxDrvEP_Open_32) wrt CODE16 + db 066h + db 0eah + dw NAME(VBoxDrvEP_Open_16) wrt CODE16 + dw CODE16 +segment CODE16 +GLOBALNAME VBoxDrvEP_Open_16 + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near VBoxDrvEP_GeneralFailure + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near VBoxDrvEP_Done + + ; Initializing or failed init? +VBoxDrvEP_OpenOther: + cmp byte [NAME(g_fInitialized)], 0 + jne VBoxDrvEP_OpenFailed + + mov byte [NAME(g_fInitialized)], -1 + call NAME(VBoxDrvRing0Init) + cmp byte [NAME(g_fInitialized)], 1 + je VBoxDrvEP_Open + +VBoxDrvEP_OpenFailed: + mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed. + jmp near VBoxDrvEP_Done + + + ; + ; Close Request. + ; +VBoxDrvEP_Close: + ; First argument, the system file number. + movzx eax, word [es:bx + PKTOPEN.sfn] + push eax + + ; go to the 32-bit code + ;jmp far dword NAME(VBoxDrvEP_Close_32) wrt FLAT + db 066h + db 0eah + dd NAME(VBoxDrvEP_Close_32) ;wrt FLAT + dw TEXT32 wrt FLAT +segment TEXT32 +GLOBALNAME VBoxDrvEP_Close_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(VBoxDrvClose) + + ; switch back the stack. + push eax + call KernThunkStackTo16 + pop eax + + ; jump back to the 16-bit code. + ;jmp far dword NAME(VBoxDrvEP_Close_32) wrt CODE16 + db 066h + db 0eah + dw NAME(VBoxDrvEP_Close_16) wrt CODE16 + dw CODE16 +segment CODE16 +GLOBALNAME VBoxDrvEP_Close_16 + les bx, [bp - 4] ; Reload the packet pointer. + or eax, eax + jnz near VBoxDrvEP_GeneralFailure + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near VBoxDrvEP_Done + + + ; + ; Init Request. + ; The other driver header will do this. + ; +VBoxDrvEP_Init: + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16 + mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp near VBoxDrvEP_Done + + + ; + ; Read Request. + ; Return log data. + ; +VBoxDrvEP_Read: + ; Any log data available? + xor dx, dx + mov ax, [NAME(g_offLogTail)] + cmp ax, [NAME(g_offLogHead)] + jz near .log_done + + ; create a temporary mapping of the physical buffer. Docs claims it trashes nearly everything... + push ebp + mov cx, [es:bx + PKTRW.cbTrans] + push cx + mov ax, [es:bx + PKTRW.PhysTrans + 2] + mov bx, [es:bx + PKTRW.PhysTrans] + mov dh, 1 + mov dl, DevHlp_PhysToVirt + call far [NAME(g_fpfnDevHlp)] + pop bx ; bx = cbTrans + pop ebp + jc near .log_phystovirt_failed + ; es:di -> the output buffer. + + ; setup the copy operation. + mov ax, [NAME(g_offLogTail)] + xor dx, dx ; dx tracks the number of bytes copied. +.log_loop: + mov cx, [NAME(g_offLogHead)] + cmp ax, cx + je .log_done + jb .log_loop_before + mov cx, LOG_SIZE +.log_loop_before: ; cx = end offset + sub cx, ax ; cx = sequential bytes to copy. + cmp cx, bx + jbe .log_loop_min + mov cx, bx ; output buffer is smaller than available data. +.log_loop_min: + mov si, NAME(g_szLog) + add si, ax ; ds:si -> the log buffer. + add dx, cx ; update output counter + add ax, cx ; calc new offLogTail + and ax, LOG_SIZE - 1 + rep movsb ; do the copy + mov [NAME(g_offLogTail)], ax ; commit the read. + jmp .log_loop + +.log_done: + les bx, [bp - 4] ; Reload the packet pointer. + mov word [es:bx + PKTRW.cbTrans], dx + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + jmp near VBoxDrvEP_Done + +.log_phystovirt_failed: + les bx, [bp - 4] ; Reload the packet pointer. + jmp VBoxDrvEP_GeneralFailure + + + ; + ; Return 'unknown command' error. + ; +VBoxDrvEP_NotSupported: + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + jmp VBoxDrvEP_Done + + ; + ; Return 'general failure' error. + ; +VBoxDrvEP_GeneralFailure: + mov word [es:bx + PKTHDR.status], 0810ch ; error, done, general failure. + jmp VBoxDrvEP_Done + + ; + ; Non-optimized return path. + ; +VBoxDrvEP_Done: + mov sp, bp + pop ebp + retf +ENDPROC VBoxDrvEP + + +;; +; The helper device entry point. +; +; This is only used to do the DosOpen on the main driver so we can +; do ring-3 init and report failures. +; +GLOBALNAME VBoxDrvInitEP + ; The only request we're servicing is the 'init' one. + cmp word [es:bx + PKTHDR.cmd], 0 + je near NAME(VBoxDrvInitEPServiceInitReq) + + ; Ok, it's not the init request, just fail it. + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + retf + + +; +; The 16-bit init code. +; +segment CODE16_INIT +GLOBALNAME g_InitCodeStart + +;; The device name for DosOpen. +g_szDeviceName: + db '\DEV\vboxdrv$', 0 + +; icsdebug can't see where stuff starts otherwise. (kDevTest) +int3 +int3 +int3 +int3 +int3 +int3 + +;; +; The Ring-3 init code. +; +BEGINPROC VBoxDrvInitEPServiceInitReq + push ebp + mov ebp, esp + push es ; bp - 2 + push sp ; bp - 4 + push -1 ; bp - 6: hfOpen + push 0 ; bp - 8: usAction + and sp, 0fffch + + ; check for the init package. + cmp word [es:bx + PKTHDR.cmd], 0 + jne near .not_init + + ; + ; Copy the data out of the init packet. + ; + mov eax, [es:bx + PKTINITIN.fpfnDevHlp] + mov [NAME(g_fpfnDevHlp)], eax + mov edx, [es:bx + PKTINITIN.fpszArgs] + mov [g_fpszArgs], edx + + ; + ; Open the first driver, close it, and check status. + ; + + ; APIRET _Pascal DosOpen(PSZ pszFname, PHFILE phfOpen, PUSHORT pusAction, + ; ULONG ulFSize, USHORT usAttr, USHORT fsOpenFlags, + ; USHORT fsOpenMode, ULONG ulReserved); + push seg g_szDeviceName ; pszFname + push g_szDeviceName + push ss ; phfOpen + lea dx, [bp - 6] + push dx + push ss ; pusAction + lea dx, [bp - 8] + push dx + push dword 0 ; ulFSize + push 0 ; usAttr = FILE_NORMAL + push 1 ; fsOpenFlags = FILE_OPEN + push 00040h ; fsOpenMode = OPEN_SHARE_DENYNONE | OPEN_ACCESS_READONLY + push dword 0 ; ulReserved + call far DOS16OPEN + + push ax ; Quickly flush any text. + call NAME(VBoxDrvInitFlushText) + pop ax + + or ax, ax + jnz .done_err + + ; APIRET APIENTRY DosClose(HFILE hf); + mov cx, [bp - 6] + push cx + call far DOS16CLOSE + or ax, ax + jnz .done_err ; This can't happen (I hope). + + ; + ; Ok, we're good. + ; + mov word [es:bx + PKTHDR.status], 00100h ; done, ok. + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], NAME(g_InitCodeStart) wrt CODE16 + mov word [es:bx + PKTINITOUT.cbData16], NAME(g_InitDataStart) wrt DATA16 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp .done + + ; + ; Init failure. + ; +.done_err: + mov word [es:bx + PKTHDR.status], 0810fh ; error, done, init failed. + mov byte [es:bx + PKTINITOUT.cUnits], 0 + mov word [es:bx + PKTINITOUT.cbCode16], 0 + mov word [es:bx + PKTINITOUT.cbData16], 0 + mov dword [es:bx + PKTINITOUT.fpaBPBs], 0 + jmp .done + + ; + ; Not init, return 'unknown command'. + ; +.not_init: + mov word [es:bx + PKTHDR.status], 08103h ; error, done, unknown command. + jmp .done + + ; + ; Request done. + ; +.done: + mov sp, bp + pop ebp + retf +ENDPROC VBoxDrvInitEPServiceInitReq + + +;; +; The Ring-0 init code. +; +BEGINPROC VBoxDrvRing0Init + push es + push esi + push ebp + mov ebp, esp + and sp, 0fffch + + ; + ; Thunk the argument string pointer first. + ; + movzx esi, word [g_fpszArgs] ; offset + mov ax, [g_fpszArgs + 2] ; selector + mov dl, DevHlp_VirtToLin + call far [NAME(g_fpfnDevHlp)] + jc near VBoxDrvRing0Init_done ; eax is non-zero on failure (can't happen) + push eax ; 00h - pszArgs (for VBoxDrvInit). + + ; + ; Do 16-bit init? + ; + + + ; + ; Do 32-bit init + ; + ;jmp far dword NAME(VBoxDrvRing0Init_32) wrt FLAT + db 066h + db 0eah + dd NAME(VBoxDrvRing0Init_32) ;wrt FLAT + dw TEXT32 wrt FLAT +segment TEXT32 +GLOBALNAME VBoxDrvRing0Init_32 + + ; switch stack to 32-bit. + mov ax, DATA32 wrt FLAT + mov ds, ax + mov es, ax + call KernThunkStackTo32 + + ; call the C code. + call NAME(VBoxDrvInit) + + ; switch back the stack and reload ds. + push eax + call KernThunkStackTo16 + pop eax + + mov dx, seg NAME(g_fInitialized) + mov ds, dx + + ; jump back to the 16-bit code. + ;jmp far dword NAME(VBoxDrvRing0Init_16) wrt CODE16 + db 066h + db 0eah + dw NAME(VBoxDrvRing0Init_16) wrt CODE16 + dw CODE16_INIT +segment CODE16_INIT +GLOBALNAME VBoxDrvRing0Init_16 + + ; check the result and set g_fInitialized on success. + or eax, eax + jnz VBoxDrvRing0Init_done + mov byte [NAME(g_fInitialized)], 1 + +VBoxDrvRing0Init_done: + mov sp, bp + pop ebp + pop esi + pop es + ret +ENDPROC VBoxDrvRing0Init + + +;; +; Flush any text in the text buffer. +; +BEGINPROC VBoxDrvInitFlushText + push bp + mov bp, sp + + ; Anything in the buffer? + mov ax, [NAME(g_cchInitText)] + or ax, ax + jz .done + +%if 1 + ; Write it to STDOUT. + ; APIRET _Pascal DosWrite(HFILE hf, PVOID pvBuf, USHORT cbBuf, PUSHORT pcbBytesWritten); + push ax ; bp - 2 : cbBytesWritten + mov cx, sp + push 1 ; STDOUT + push seg NAME(g_szInitText) ; pvBuf + push NAME(g_szInitText) + push ax ; cbBuf + push ss ; pcbBytesWritten + push cx +%if 0 ; wlink generates a non-aliased fixup here which results in 16-bit offset with the flat 32-bit selector. + call far DOS16WRITE +%else + ; convert flat pointer to a far pointer using the tiled algorithm. + push ds + mov ax, DATA32 wrt FLAT + mov ds, ax + mov eax, g_pfnDos16Write wrt FLAT + movzx eax, word [eax + 2] ; High word of the flat address (in DATA32). + shl ax, 3 + or ax, 0007h + pop ds + mov [NAME(g_fpfnDos16Write) + 2], ax ; Update the selector (in DATA16_INIT). + ; do the call + call far [NAME(g_fpfnDos16Write)] +%endif + +%else ; alternative workaround for the wlink issue. + ; Use the save message devhlp. + push esi + push ebx + xor bx, bx + mov si, NAME(g_MsgTab) + mov dx, seg NAME(g_MsgTab) + mov ds, dx + mov dl, DevHlp_SAVE_MESSAGE + call far [NAME(g_fpfnDevHlp)] + pop ebx + pop esi +%endif + + ; Empty the buffer. + mov word [NAME(g_cchInitText)], 0 + mov byte [NAME(g_szInitText)], 0 + +.done: + mov sp, bp + pop bp + ret +ENDPROC VBoxDrvInitFlushText + + + +;; +; This must be present +segment DATA32 +g_pfnDos16Write: + dd DOS16WRITE ; flat + diff --git a/src/VBox/HostDrivers/Support/os2/SUPLib-os2.cpp b/src/VBox/HostDrivers/Support/os2/SUPLib-os2.cpp new file mode 100644 index 00000000..f391f2ad --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/SUPLib-os2.cpp @@ -0,0 +1,204 @@ +/* $Id: SUPLib-os2.cpp $ */ +/** @file + * VirtualBox Support Library - OS/2 specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define INCL_BASE +#define INCL_ERRORS +#include <os2.h> +#undef RT_MAX + +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# define LOG_DISABLED +# define RTLOG_REL_DISABLED +# include <iprt/log.h> +#endif + +#include <VBox/types.h> +#include <VBox/sup.h> +#include <VBox/param.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** OS/2 Device name. */ +#define DEVICE_NAME "/dev/vboxdrv$" + + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + /* + * Nothing to do if pre-inited. + */ + if (fPreInited) + return VINF_SUCCESS; + + /* + * Try open the device. + */ + ULONG ulAction = 0; + HFILE hDevice = (HFILE)-1; + APIRET rc = DosOpen((PCSZ)DEVICE_NAME, + &hDevice, + &ulAction, + 0, + FILE_NORMAL, + OPEN_ACTION_FAIL_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_FLAGS_NOINHERIT | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE, + NULL); + if (rc) + { + int vrc; + switch (rc) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: vrc = VERR_VM_DRIVER_NOT_INSTALLED; break; + default: vrc = VERR_VM_DRIVER_OPEN_ERROR; break; + } + LogRel(("Failed to open \"%s\", rc=%d, vrc=%Rrc\n", DEVICE_NAME, rc, vrc)); + return vrc; + } + + pThis->hDevice = hDevice; + pThis->fUnrestricted = true; + RT_NOREF(fFlags); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Check if we're inited at all. + */ + if (pThis->hDevice != (intptr_t)NIL_RTFILE) + { + APIRET rc = DosClose((HFILE)pThis->hDevice); + AssertMsg(rc == NO_ERROR, ("%d\n", rc)); NOREF(rc); + pThis->hDevice = (intptr_t)NIL_RTFILE; + } + + return 0; +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + /** @remark OS/2: Not supported */ + return VERR_NOT_SUPPORTED; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + /** @remark OS/2: Not supported */ + return VERR_NOT_SUPPORTED; +} + + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + ULONG cbReturned = sizeof(SUPREQHDR); + int rc = DosDevIOCtl((HFILE)pThis->hDevice, SUP_CTL_CATEGORY, uFunction, + pvReq, cbReturned, &cbReturned, + NULL, 0, NULL); + if (RT_LIKELY(rc == NO_ERROR)) + return VINF_SUCCESS; + return RTErrConvertFromOS2(rc); +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + NOREF(idCpu); + int32_t rcRet = VERR_INTERNAL_ERROR; + int rc = DosDevIOCtl((HFILE)pThis->hDevice, SUP_CTL_CATEGORY_FAST, uFunction, + NULL, 0, NULL, + NULL, 0, NULL); + if (RT_LIKELY(rc == NO_ERROR)) + rc = rcRet; + else + rc = RTErrConvertFromOS2(rc); + return rc; +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + *ppvPages = NULL; + int rc = DosAllocMem(ppvPages, cPages << PAGE_SHIFT, PAG_READ | PAG_WRITE | PAG_EXECUTE | PAG_COMMIT | OBJ_ANY); + if (rc == ERROR_INVALID_PARAMETER) + rc = DosAllocMem(ppvPages, cPages << PAGE_SHIFT, PAG_READ | PAG_WRITE | PAG_EXECUTE | PAG_COMMIT | OBJ_ANY); + if (!rc) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromOS2(rc); + return rc; +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t /* cPages */) +{ + NOREF(pThis); + if (pvPages) + { + int rc = DosFreeMem(pvPages); + Assert(!rc); NOREF(rc); + } + return VINF_SUCCESS; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/os2/SUPR0IdcClient-os2.c b/src/VBox/HostDrivers/Support/os2/SUPR0IdcClient-os2.c new file mode 100644 index 00000000..034986e4 --- /dev/null +++ b/src/VBox/HostDrivers/Support/os2/SUPR0IdcClient-os2.c @@ -0,0 +1,61 @@ +/* $Id: SUPR0IdcClient-os2.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, OS/2 Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq) +{ + return VERR_NOT_SUPPORTED; +} + diff --git a/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMain-posix.cpp b/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMain-posix.cpp new file mode 100644 index 00000000..fe55c4da --- /dev/null +++ b/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMain-posix.cpp @@ -0,0 +1,708 @@ +/* $Id: SUPR3HardenedMain-posix.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), posix bits. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/err.h> +#include <VBox/dis.h> +#include <VBox/sup.h> + +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/x86.h> + +#include <dlfcn.h> +#include <sys/mman.h> +#if defined(RT_OS_SOLARIS) +# include <link.h> +#endif +#include <stdio.h> +#include <stdint.h> + +#include "SUPLibInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** + * Memory for code patching. + */ +#define DLOPEN_PATCH_MEMORY_SIZE _4K + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifndef SUP_HARDENED_WITHOUT_DLOPEN_PATCHING +/** + * Callback (SUPHARDENEDPOSIXHOOK::pfnResolv) for triggering lazy GOT resolver. + * + * This generally just calls the API in a harmless manner and triggers the lazy + * resolving of the symbol, ensuring a proper address in the GOT/PLT entry. + * + * On Solaris dlsym() will return the value in the GOT/PLT entry. We don't wish + * to patch the lazy loader trampoline function, but rather the real function! + */ +typedef DECLCALLBACKTYPE(void, FNSUPHARDENEDSYMRESOLVE,(void)); +/** Pointer to FNSUPHARDENEDSYMRESOLVE. */ +typedef FNSUPHARDENEDSYMRESOLVE *PFNSUPHARDENEDSYMRESOLVE; + +/** + * A hook descriptor. + */ +typedef struct SUPHARDENEDPOSIXHOOK +{ + /** The symbol to hook. */ + const char *pszSymbol; + /** The intercepting wrapper doing additional checks. */ + PFNRT pfnHook; + /** Where to store the pointer to the code into patch memory + * which resumes the original call. + * @note uintptr_t instead of PFNRT is for Clang 11. */ + uintptr_t *ppfnRealResume; + /** Pointer to the resolver method used on Solaris. */ + PFNSUPHARDENEDSYMRESOLVE pfnResolve; +} SUPHARDENEDPOSIXHOOK; +/** Pointer to a hook descriptor. */ +typedef SUPHARDENEDPOSIXHOOK *PSUPHARDENEDPOSIXHOOK; +/** Pointer to a const hook descriptor. */ +typedef const SUPHARDENEDPOSIXHOOK *PCSUPHARDENEDPOSIXHOOK; + +/** dlopen() declaration. */ +typedef void *FNDLOPEN(const char *pszFilename, int fFlags); +/** Pointer to dlopen. */ +typedef FNDLOPEN *PFNDLOPEN; + +#ifdef SUP_HARDENED_WITH_DLMOPEN +/** dlmopen() declaration */ +typedef void *FNDLMOPEN(Lmid_t idLm, const char *pszFilename, int fFlags); +/** Pointer to dlmopen. */ +typedef FNDLMOPEN *PFNDLMOPEN; +#endif + +#endif /* SUP_HARDENED_WITHOUT_DLOPEN_PATCHING */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifndef SUP_HARDENED_WITHOUT_DLOPEN_PATCHING +static FNSUPHARDENEDSYMRESOLVE supR3HardenedPosixMonitorDlopenResolve; +#ifdef SUP_HARDENED_WITH_DLMOPEN +static FNSUPHARDENEDSYMRESOLVE supR3HardenedPosixMonitorDlmopenResolve; +#endif + +/* SUPR3HardenedMainA-posix.asm: */ +DECLASM(void) supR3HardenedPosixMonitor_Dlopen(const char *pszFilename, int fFlags); +#ifdef SUP_HARDENED_WITH_DLMOPEN +DECLASM(void) supR3HardenedPosixMonitor_Dlmopen(Lmid_t idLm, const char *pszFilename, int fFlags); +#endif +#endif /* SUP_HARDENED_WITHOUT_DLOPEN_PATCHING */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifndef SUP_HARDENED_WITHOUT_DLOPEN_PATCHING + +RT_C_DECLS_BEGIN +/** Resume patch for dlopen(), jumped to form assembly stub. */ +DECL_HIDDEN_DATA(PFNDLOPEN) g_pfnDlopenReal = NULL; +#ifdef SUP_HARDENED_WITH_DLMOPEN +/** Resume patch for dlmopen(), jumped to form assembly stub. */ +DECL_HIDDEN_DATA(PFNDLMOPEN) g_pfnDlmopenReal = NULL; +#endif +RT_C_DECLS_END + +/** Memory allocated for the patches. */ +static uint8_t *g_pbExecMemory = NULL; +/** Offset into the patch memory which is not used. */ +static uint32_t g_offExecMemory = 0; + +/** + * Array of hooks to install. + */ +static SUPHARDENEDPOSIXHOOK const g_aHooks[] = +{ + /* pszSymbol, pfnHook, ppfnRealResume, pfnResolve */ + { "dlopen", (PFNRT)supR3HardenedPosixMonitor_Dlopen, (uintptr_t *)&g_pfnDlopenReal, supR3HardenedPosixMonitorDlopenResolve }, +#ifdef SUP_HARDENED_WITH_DLMOPEN + { "dlmopen", (PFNRT)supR3HardenedPosixMonitor_Dlmopen, (uintptr_t *)&g_pfnDlmopenReal, supR3HardenedPosixMonitorDlmopenResolve } +#endif +}; + + + +/** + * Verifies the given library for proper access rights for further loading + * into the process. + * + * @returns Flag whether the access rights of the library look sane and loading + * it is not considered a security risk. Returns true if the library + * looks sane, false otherwise. + * @param pszFilename The library to load, this can be an absolute or relative path + * or just the filename of the library when the default paths should + * be searched. NULL is allowed too to indicate opening the main + * binary. + */ +DECLASM(bool) supR3HardenedPosixMonitor_VerifyLibrary(const char *pszFilename) +{ + /* + * Giving NULL as the filename indicates opening the main program which is fine + * We are already loaded and executing after all. + * + * Filenames without any path component (whether absolute or relative) are allowed + * unconditionally too as the loader will only search the default paths configured by root. + */ + bool fAllow = true; + + if ( pszFilename + && strchr(pszFilename, '/') != NULL) + { +#if defined(RT_OS_LINUX) + int rc = supR3HardenedVerifyFileFollowSymlinks(pszFilename, RTHCUINTPTR_MAX, true /* fMaybe3rdParty */, + NULL /* pErrInfo */); +#else + int rc = supR3HardenedVerifyFile(pszFilename, RTHCUINTPTR_MAX, true /* fMaybe3rdParty */, + NULL /* pErrInfo */); +#endif + + if (RT_FAILURE(rc)) + fAllow = false; + } + + return fAllow; +} + + +/** + * Returns the start address of the given symbol if found or NULL otherwise. + * + * @returns Start address of the symbol or NULL if not found. + * @param pszSymbol The symbol name. + * @param pfnResolve The resolver to call before trying to query the start address. + */ +static void *supR3HardenedMainPosixGetStartBySymbol(const char *pszSymbol, PFNSUPHARDENEDSYMRESOLVE pfnResolve) +{ +#ifndef RT_OS_SOLARIS + RT_NOREF(pfnResolve); + return dlsym(RTLD_DEFAULT, pszSymbol); + +#else /* RT_OS_SOLARIS */ + /* + * Solaris is tricky as dlsym doesn't return the actual start address of + * the symbol but the start of the trampoline in the PLT of the caller. + * + * Disassemble the first jmp instruction to get at the entry in the global + * offset table where the actual address is stored. + * + * To counter lazy symbol resolving, we first have to call the API before + * trying to resolve and disassemble it. + */ + pfnResolve(); + + uint8_t *pbSym = (uint8_t *)dlsym(RTLD_DEFAULT, pszSymbol); + +# ifdef RT_ARCH_AMD64 + DISSTATE Dis; + uint32_t cbInstr = 1; + int rc = DISInstr(pbSym, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || Dis.pCurInstr->uOpcode != OP_JMP + || !(Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */)) + return NULL; + + /* Extract start address. */ + pbSym = (pbSym + cbInstr + Dis.Param1.uDisp.i32); + pbSym = (uint8_t *)*((uintptr_t *)pbSym); +# else +# error "Unsupported architecture" +# endif + + return pbSym; +#endif /* RT_OS_SOLARIS */ +} + + +/** + * Allocates executable patch memory with the given constraints. + * + * @returns VBox status code. + * @param cb Size of the patch memory in bytes. + * @param pvHint Where to try allocating nearby. + * @param fRipRelAddr Flag whether the executable memory must be within + * 2GB before or after the hint as it will contain + * instructions using RIP relative addressing + */ +static uint8_t *supR3HardenedMainPosixExecMemAlloc(size_t cb, void *pvHint, bool fRipRelAddr) +{ + AssertReturn(cb < _1K, NULL); + + /* Lazy allocation of exectuable memory. */ + if (!g_pbExecMemory) + { + g_pbExecMemory = (uint8_t *)mmap(pvHint, DLOPEN_PATCH_MEMORY_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + g_offExecMemory = 0; + if (g_pbExecMemory == MAP_FAILED) + return NULL; + + memset(g_pbExecMemory, 0xcc, DLOPEN_PATCH_MEMORY_SIZE); + } + + if (g_offExecMemory + cb >= DLOPEN_PATCH_MEMORY_SIZE) + return NULL; + + uint8_t *pb = &g_pbExecMemory[g_offExecMemory]; + + if (fRipRelAddr) + { + /* Check that we allocated within 2GB of the hint. */ + uintptr_t uPtrHint = (uintptr_t)pvHint; + uintptr_t uPtrPatchMem = (uintptr_t)pb; + uintptr_t cbDistance = uPtrHint < uPtrPatchMem + ? uPtrPatchMem - uPtrHint + : uPtrHint - uPtrPatchMem; + + if (cbDistance >= _2G - _4K) + return NULL; + } + + g_offExecMemory = RT_ALIGN_32(g_offExecMemory + cb, 16); + return pb; +} + + +/** + * Hooks the given method to execute the given one first. + * + * @returns VBox status code. + * @param pszSymbol The symbol to hook. + * @param pfnHook The hook to install. + * @param ppfnReal Where to store the pointer to entry point of the real method + * (somewhere in patch memory). + * @param pfnResolve The resolver to call before trying to query the start address. + */ +static int supR3HardenedMainPosixHookOne(const char *pszSymbol, PFNRT pfnHook, uintptr_t /*PFNRT*/ *ppfnReal, + PFNSUPHARDENEDSYMRESOLVE pfnResolve) +{ + void *pfnTarget = supR3HardenedMainPosixGetStartBySymbol(pszSymbol, pfnResolve); + if (!pfnTarget) + return VERR_NOT_FOUND; + + /* + * Make the target memory writeable to be able to insert the patch. + * Unprotect two pages in case the code crosses a page boundary. + */ + void *pvTargetBase = (void *)(((uintptr_t)pfnTarget) & ~(uintptr_t)(_4K - 1)); + int rcPsx = mprotect(pvTargetBase, 2 * _4K, PROT_WRITE | PROT_READ | PROT_EXEC); + if (rcPsx == -1) + return VERR_SUPLIB_TEXT_NOT_WRITEABLE; + + uint8_t * const pbTarget = (uint8_t *)(uintptr_t)pfnTarget; + + DISSTATE Dis; + uint32_t cbInstr; + uint32_t offJmpBack = 0; + uint32_t cbPatchMem = 0; + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + uint32_t cRipRelMovs = 0; + uint32_t cRelCalls = 0; + + /* Just use the disassembler to skip 12 bytes or more, we might need to + rewrite mov instructions using RIP relative addressing. */ + while (offJmpBack < 12) + { + cbInstr = 1; + int rc = DISInstr(pbTarget + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || ( Dis.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW + && Dis.pCurInstr->uOpcode != OP_CALL) + || ( Dis.ModRM.Bits.Mod == 0 + && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */ + && Dis.pCurInstr->uOpcode != OP_MOV)) + return VERR_SUPLIB_UNEXPECTED_INSTRUCTION; + + if (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) + cRipRelMovs++; + if ( Dis.pCurInstr->uOpcode == OP_CALL + && (Dis.pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW)) + cRelCalls++; + + offJmpBack += cbInstr; + cbPatchMem += cbInstr; + } + + /* + * Each relative call requires extra bytes as it is converted to a pushq imm32 + * + mov [RSP+4], imm32 + a jmp qword [$+8 wrt RIP] to avoid clobbering registers. + */ + cbPatchMem += cRelCalls * RT_ALIGN_32(13 + 6 + 8, 8); + cbPatchMem += 14; /* jmp qword [$+8 wrt RIP] + 8 byte address to jump to. */ + cbPatchMem = RT_ALIGN_32(cbPatchMem, 8); + + /* Allocate suitable executable memory available. */ + bool fConvRipRelMovs = false; + uint8_t *pbPatchMem = supR3HardenedMainPosixExecMemAlloc(cbPatchMem, pbTarget, cRipRelMovs > 0); + if (!pbPatchMem) + { + /* + * Try to allocate memory again without the RIP relative mov addressing constraint + * Makes it a bit more difficult for us later on but there is no way around it. + * We need to increase the patch memory because we create two instructions for one + * (7 bytes for the RIP relative mov vs. 13 bytes for the two instructions replacing it -> + * need to allocate 6 bytes more per RIP relative mov). + */ + fConvRipRelMovs = true; + if (cRipRelMovs > 0) + pbPatchMem = supR3HardenedMainPosixExecMemAlloc(cbPatchMem + cRipRelMovs * 6, + pbTarget, false /*fRipRelAddr*/); + + if (!pbPatchMem) + return VERR_NO_MEMORY; + } + + /* Assemble the code for resuming the call.*/ + *ppfnReal = (uintptr_t)pbPatchMem; + + /* Go through the instructions to patch and fixup any rip relative mov instructions. */ + uint32_t offInsn = 0; + while (offInsn < offJmpBack) + { + cbInstr = 1; + int rc = DISInstr(pbTarget + offInsn, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || ( Dis.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW + && Dis.pCurInstr->uOpcode != OP_CALL)) + return VERR_SUPLIB_UNEXPECTED_INSTRUCTION; + + if ( Dis.ModRM.Bits.Mod == 0 + && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */ + && Dis.pCurInstr->uOpcode == OP_MOV) + { + /* Deduce destination register and write out new instruction. */ + if (RT_UNLIKELY(!( (Dis.Param1.fUse & (DISUSE_BASE | DISUSE_REG_GEN64)) + && (Dis.Param2.fUse & DISUSE_RIPDISPLACEMENT32)))) + return VERR_SUPLIB_UNEXPECTED_INSTRUCTION; + + uintptr_t uAddr = (uintptr_t)&pbTarget[offInsn + cbInstr] + (intptr_t)Dis.Param2.uDisp.i32; + + if (fConvRipRelMovs) + { + /* + * Create two instructions, first one moves the address as a constant to the destination register + * and the second one loads the data from the memory into the destination register. + */ + + *pbPatchMem++ = 0x48; + *pbPatchMem++ = 0xb8 + Dis.Param1.Base.idxGenReg; + *(uintptr_t *)pbPatchMem = uAddr; + pbPatchMem += sizeof(uintptr_t); + + *pbPatchMem++ = 0x48; + *pbPatchMem++ = 0x8b; + *pbPatchMem++ = (Dis.Param1.Base.idxGenReg << X86_MODRM_REG_SHIFT) | Dis.Param1.Base.idxGenReg; + } + else + { + intptr_t iDispNew = uAddr - (uintptr_t)&pbPatchMem[3 + sizeof(int32_t)]; + Assert(iDispNew == (int32_t)iDispNew); + + /* Assemble the mov to register instruction with the updated rip relative displacement. */ + *pbPatchMem++ = 0x48; + *pbPatchMem++ = 0x8b; + *pbPatchMem++ = (Dis.Param1.Base.idxGenReg << X86_MODRM_REG_SHIFT) | 5; + *(int32_t *)pbPatchMem = (int32_t)iDispNew; + pbPatchMem += sizeof(int32_t); + } + } + else if ( Dis.pCurInstr->uOpcode == OP_CALL + && (Dis.pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW)) + { + /* Convert to absolute jump. */ + uintptr_t uAddr = (uintptr_t)&pbTarget[offInsn + cbInstr] + (intptr_t)Dis.Param1.uValue; + + /* Skip the push instructions till the return address is known. */ + uint8_t *pbPatchMemPush = pbPatchMem; + pbPatchMem += 13; + + *pbPatchMem++ = 0xff; /* jmp qword [$+8 wrt RIP] */ + *pbPatchMem++ = 0x25; + *(uint32_t *)pbPatchMem = (uint32_t)(RT_ALIGN_PT(pbPatchMem + 4, 8, uint8_t *) - (pbPatchMem + 4)); + pbPatchMem = RT_ALIGN_PT(pbPatchMem + 4, 8, uint8_t *); + *(uint64_t *)pbPatchMem = uAddr; + pbPatchMem += sizeof(uint64_t); + + /* Push the return address onto stack. Difficult on amd64 without clobbering registers... */ + uintptr_t uAddrReturn = (uintptr_t)pbPatchMem; + *pbPatchMemPush++ = 0x68; /* push imm32 sign-extended as 64-bit*/ + *(uint32_t *)pbPatchMemPush = RT_LO_U32(uAddrReturn); + pbPatchMemPush += sizeof(uint32_t); + *pbPatchMemPush++ = 0xc7; + *pbPatchMemPush++ = 0x44; + *pbPatchMemPush++ = 0x24; + *pbPatchMemPush++ = 0x04; /* movl [RSP+4], imm32 */ + *(uint32_t *)pbPatchMemPush = RT_HI_U32(uAddrReturn); + } + else + { + memcpy(pbPatchMem, pbTarget + offInsn, cbInstr); + pbPatchMem += cbInstr; + } + + offInsn += cbInstr; + } + + *pbPatchMem++ = 0xff; /* jmp qword [$+8 wrt RIP] */ + *pbPatchMem++ = 0x25; + *(uint32_t *)pbPatchMem = (uint32_t)(RT_ALIGN_PT(pbPatchMem + 4, 8, uint8_t *) - (pbPatchMem + 4)); + pbPatchMem = RT_ALIGN_PT(pbPatchMem + 4, 8, uint8_t *); + *(uint64_t *)pbPatchMem = (uintptr_t)&pbTarget[offJmpBack]; + + /* Assemble the patch. */ + Assert(offJmpBack >= 12); + pbTarget[0] = 0x48; /* mov rax, qword */ + pbTarget[1] = 0xb8; + *(uintptr_t *)&pbTarget[2] = (uintptr_t)pfnHook; + pbTarget[10] = 0xff; /* jmp rax */ + pbTarget[11] = 0xe0; + +#else /* !RT_ARCH_AMD64 */ + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbTarget + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || ( (Dis.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) + && Dis.pCurInstr->uOpcode != OP_CALL)) + return VERR_SUPLIB_UNEXPECTED_INSTRUCTION; + + if ( Dis.pCurInstr->uOpcode == OP_CALL + && (Dis.pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW)) + cbPatchMem += 10; /* push imm32 + jmp rel32 */ + else + cbPatchMem += cbInstr; + + offJmpBack += cbInstr; + } + + /* Allocate suitable exectuable memory available. */ + uint8_t *pbPatchMem = supR3HardenedMainPosixExecMemAlloc(cbPatchMem, pbTarget, false /* fRipRelAddr */); + if (!pbPatchMem) + return VERR_NO_MEMORY; + + /* Assemble the code for resuming the call.*/ + *ppfnReal = (uintptr_t)pbPatchMem; + + /* Go through the instructions to patch and fixup any relative call instructions. */ + uint32_t offInsn = 0; + while (offInsn < offJmpBack) + { + cbInstr = 1; + int rc = DISInstr(pbTarget + offInsn, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || ( (Dis.pCurInstr->fOpType & DISOPTYPE_CONTROLFLOW) + && Dis.pCurInstr->uOpcode != OP_CALL)) + return VERR_SUPLIB_UNEXPECTED_INSTRUCTION; + + if ( Dis.pCurInstr->uOpcode == OP_CALL + && (Dis.pCurInstr->fOpType & DISOPTYPE_RELATIVE_CONTROLFLOW)) + { + /* + * Don't use a call instruction directly but push the original return address + * onto the stack and use a relative jump to the call target. + * The reason here is that on Linux the called method saves the return + * address from the stack which will be different from the original because + * the code is executed from our patch memory. + * + * Luckily the call instruction is 5 bytes long which means it is always the + * last instruction to patch and we don't need to return from the call + * to patch memory anyway but can use this method to resume the original call. + */ + AssertReturn(offInsn + cbInstr >= offJmpBack, VERR_SUPLIB_UNEXPECTED_INSTRUCTION); /* Must be last instruction! */ + + /* push return address */ + uint32_t const uAddrReturn = (uintptr_t)&pbTarget[offInsn + cbInstr]; /* The return address to push to the stack. */ + + *pbPatchMem++ = 0x68; /* push dword */ + *(uint32_t *)pbPatchMem = uAddrReturn; + pbPatchMem += sizeof(uint32_t); + + /* jmp rel32 to the call target */ + uintptr_t const uAddr = uAddrReturn + (int32_t)Dis.Param1.uValue; + int32_t const i32DispNew = uAddr - (uintptr_t)&pbPatchMem[5]; + + *pbPatchMem++ = 0xe9; /* jmp rel32 */ + *(int32_t *)pbPatchMem = i32DispNew; + pbPatchMem += sizeof(int32_t); + } + else + { + memcpy(pbPatchMem, pbTarget + offInsn, cbInstr); + pbPatchMem += cbInstr; + } + + offInsn += cbInstr; + } + + *pbPatchMem++ = 0xe9; /* jmp rel32 */ + *(uint32_t *)pbPatchMem = (uintptr_t)&pbTarget[offJmpBack] - ((uintptr_t)pbPatchMem + 4); + + /* Assemble the patch. */ + Assert(offJmpBack >= 5); + pbTarget[0] = 0xe9; + *(uint32_t *)&pbTarget[1] = (uintptr_t)pfnHook - (uintptr_t)&pbTarget[1+4]; +#endif /* !RT_ARCH_AMD64 */ + + /* + * Re-seal target (ASSUMING that the shared object either has page aligned + * section or that the patch target is far enough from the writable parts). + */ + rcPsx = mprotect(pvTargetBase, 2 * _4K, PROT_READ | PROT_EXEC); + if (rcPsx == -1) + return VERR_SUPLIB_TEXT_NOT_SEALED; + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSUPHARDENEDSYMRESOLVE, dlopen} + */ +static DECLCALLBACK(void) supR3HardenedPosixMonitorDlopenResolve(void) +{ + /* Make harmless dlopen call. */ + void *pv = dlopen(NULL, RTLD_LAZY); + if (pv) + dlclose(pv); +} + + +#ifdef SUP_HARDENED_WITH_DLMOPEN +/** + * @callback_method_impl{FNSUPHARDENEDSYMRESOLVE, dlmopen} + */ +static DECLCALLBACK(void) supR3HardenedPosixMonitorDlmopenResolve(void) +{ + /* Make harmless dlmopen call. */ + void *pv = dlmopen(LM_ID_BASE, NULL, RTLD_LAZY); + if (pv) + dlclose(pv); +} +#endif + +#endif /* SUP_HARDENED_WITHOUT_DLOPEN_PATCHING */ + + +/** + * Hardening initialization for POSIX compatible hosts. + * + * @note Doesn't return on error. + */ +DECLHIDDEN(void) supR3HardenedPosixInit(void) +{ +#ifndef SUP_HARDENED_WITHOUT_DLOPEN_PATCHING + for (unsigned i = 0; i < RT_ELEMENTS(g_aHooks); i++) + { + PCSUPHARDENEDPOSIXHOOK pHook = &g_aHooks[i]; + int rc = supR3HardenedMainPosixHookOne(pHook->pszSymbol, pHook->pfnHook, pHook->ppfnRealResume, pHook->pfnResolve); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedPosixInit", kSupInitOp_Integrity, rc, + "Failed to hook the %s interface", pHook->pszSymbol); + } +#endif +} + + + +/* + * assert.cpp + * + * ASSUMES working DECLHIDDEN or there will be symbol confusion! + */ + +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; + snprintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%u) %s\n", + pszExpr, pszFile, uLine, pszFunction); +} + + +RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) +{ + vsnprintf(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); +} + diff --git a/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMainA-posix.asm b/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMainA-posix.asm new file mode 100644 index 00000000..e64b0efe --- /dev/null +++ b/src/VBox/HostDrivers/Support/posix/SUPR3HardenedMainA-posix.asm @@ -0,0 +1,170 @@ +; $Id: SUPR3HardenedMainA-posix.asm $ +;; @file +; VirtualBox Support Library - Hardened main(), Posix assembly bits. +; + +; +; Copyright (C) 2017-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + + +;********************************************************************************************************************************* +;* External Symbols * +;********************************************************************************************************************************* +; External code. +BEGINCODE +extern NAME(supR3HardenedPosixMonitor_VerifyLibrary) + +; External data +BEGINDATA +extern NAME(g_pfnDlopenReal) +%ifdef SUP_HARDENED_WITH_DLMOPEN +extern NAME(g_pfnDlmopenReal) +%endif + + + +BEGINCODE + +;; +; Wrapper for dlopen() handing the call over to the file verification code +; and resuming the call if we get a green light to load the library. +; +align 16 +BEGINPROC supR3HardenedPosixMonitor_Dlopen + push xBP + mov xBP, xSP + +%ifdef RT_ARCH_AMD64 + ; Save parameters on the stack + push rdi + push rsi +%else + sub esp, 4 ; 16-byte stack alignment before call. + push dword [xBP + 08h] ; first parameter. +%endif + + ; + ; Call the verification method. + ; + call NAME(supR3HardenedPosixMonitor_VerifyLibrary) + + ; + ; Restore parameters for the next call and get the stack back to the + ; original state. + ; +%ifdef RT_ARCH_AMD64 + pop rsi + pop rdi +%endif + leave + + ; Check the result and resume the call if the result is positive, + ; otherwise clean up and return NULL + test al, al + je short .failed + + ; Resume the original dlopen call by jumping into the saved code. + jmp [NAME(g_pfnDlopenReal) xWrtRIP] + +.failed: + ; + ; Don't use leave here as we didn't use the enter instruction. Just clear + ; xAX and return + ; + xor xAX, xAX + ret +ENDPROC supR3HardenedPosixMonitor_Dlopen + + +%ifdef SUP_HARDENED_WITH_DLMOPEN +;; +; Wrapper for dlmopen() handing the call over to the file verification code +; and resuming the call if we get a green light to load the library. +; +align 16 +BEGINPROC supR3HardenedPosixMonitor_Dlmopen + push xBP + mov xBP, xSP + +%ifdef RT_ARCH_AMD64 + sub rsp, 8 ; 16-byte stack alignment before call. + + ; Save parameters on the stack + push rdi + push rsi + push rdx + + mov rdi, rsi ; Move the second parameter to the front +%else + sub esp, 4 ; 16-byte stack alignment before call. + push dword [xBP + 0ch] ; Move the second parameter to the front +%endif + + ; + ; Call the verification method. + ; + call NAME(supR3HardenedPosixMonitor_VerifyLibrary) + + ; + ; Restore parameters for the next call and get the stack back to the + ; original state. + ; +%ifdef RT_ARCH_AMD64 + pop rdx + pop rsi + pop rdi +%endif + leave + + ; Check the result and resume the call if the result is positive, + ; otherwise clean up and return NULL + test al, al + je short .failed + + ; Resume the original dlopen call by jumping into the saved code. + jmp [NAME(g_pfnDlmopenReal) xWrtRIP] + +.failed: + ; + ; Don't use leave here as we didn't use the enter instruction. Just clear + ; xAX and return + ; + xor xAX, xAX + ret +ENDPROC supR3HardenedPosixMonitor_Dlmopen +%endif + diff --git a/src/VBox/HostDrivers/Support/solaris/Makefile.kup b/src/VBox/HostDrivers/Support/solaris/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c b/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c new file mode 100644 index 00000000..5e29b0a8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c @@ -0,0 +1,1347 @@ +/* $Id: SUPDrv-solaris.c $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Solaris specifics. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include <sys/types.h> +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/uio.h> +#include <sys/buf.h> +#include <sys/modctl.h> +#include <sys/kobj.h> +#include <sys/kobj_impl.h> +#include <sys/open.h> +#include <sys/conf.h> +#include <sys/cmn_err.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/file.h> +#include <sys/priv_names.h> +#include <vm/hat.h> +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "../SUPDrvInternal.h" +#include <VBox/log.h> +#include <VBox/param.h> +#include <VBox/version.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/mp.h> +#include <iprt/path.h> +#include <iprt/power.h> +#include <iprt/process.h> +#include <iprt/thread.h> +#include <iprt/initterm.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/err.h> + +#include "dtrace/SUPDrv.h" + +extern caddr_t hat_kpm_pfn2va(pfn_t); /* Found in vm/hat.h on solaris 11.3, but not on older like 10u7. */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The system device name. */ +#define DEVICE_NAME_SYS "vboxdrv" +/** The user device name. */ +#define DEVICE_NAME_USR "vboxdrvu" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC "VirtualBox HostDrv" +/** Maximum number of driver instances. */ +#define DEVICE_MAXINSTANCES 16 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int VBoxDrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +static int VBoxDrvSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +static int VBoxDrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int VBoxDrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int VBoxDrvSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArgs, int mode, cred_t *pCred, int *pVal); + +static int VBoxDrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t Cmd); +static int VBoxDrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t Cmd); +static int VBoxDrvSolarisQuiesceNotNeeded(dev_info_t *pDip); + +static int VBoxSupDrvErr2SolarisErr(int rc); +static int VBoxDrvSolarisIOCtlSlow(PSUPDRVSESSION pSession, int Cmd, int Mode, intptr_t pArgs); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxDrvSolarisCbOps = +{ + VBoxDrvSolarisOpen, + VBoxDrvSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + VBoxDrvSolarisRead, + VBoxDrvSolarisWrite, + VBoxDrvSolarisIOCtl, + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_VBoxDrvSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + nulldev, /* get info */ + nulldev, /* identify */ + nulldev, /* probe */ + VBoxDrvSolarisAttach, + VBoxDrvSolarisDetach, + nodev, /* reset */ + &g_VBoxDrvSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + VBoxDrvSolarisQuiesceNotNeeded +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxDrvSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxDrvSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxDrvSolarisModLinkage = +{ + MODREV_1, /* loadable module system revision */ + { + &g_VBoxDrvSolarisModule, + NULL /* terminate array of linkage structures */ + } +}; + +#ifndef USE_SESSION_HASH +/** + * State info for each open file handle. + */ +typedef struct +{ + /**< Pointer to the session data. */ + PSUPDRVSESSION pSession; +} vbox_devstate_t; +#else +/** State info. for each driver instance. */ +typedef struct +{ + dev_info_t *pDip; /* Device handle */ +} vbox_devstate_t; +#endif + +/** Opaque pointer to list of state */ +static void *g_pVBoxDrvSolarisState; + +/** Device extention & session data association structure */ +static SUPDRVDEVEXT g_DevExt; + +/** Hash table */ +static PSUPDRVSESSION g_apSessionHashTab[19]; +/** Spinlock protecting g_apSessionHashTab. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +/** Calculates bucket index into g_apSessionHashTab.*/ +#define SESSION_HASH(sfn) ((sfn) % RT_ELEMENTS(g_apSessionHashTab)) + +/** + * Kernel entry points + */ +int _init(void) +{ +#if 0 /* No IPRT logging before RTR0Init() is done! */ + LogFlowFunc(("vboxdrv:_init\n")); +#endif + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxDrvSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + cmn_err(CE_NOTE, "vboxdrv: failed to disable autounloading!\n"); + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the device extension + */ + rc = supdrvInitDevExt(&g_DevExt, sizeof(SUPDRVSESSION)); + if (RT_SUCCESS(rc)) + { + cmn_err(CE_CONT, "!tsc::mode %s @ tentative %lu Hz\n", SUPGetGIPModeName(g_DevExt.pGip), g_DevExt.pGip->u64CpuHz); + + /* + * Initialize the session hash table. + */ + memset(g_apSessionHashTab, 0, sizeof(g_apSessionHashTab)); + rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxDrvSol"); + if (RT_SUCCESS(rc)) + { + rc = ddi_soft_state_init(&g_pVBoxDrvSolarisState, sizeof(vbox_devstate_t), 8); + if (!rc) + { + rc = mod_install(&g_VBoxDrvSolarisModLinkage); + if (!rc) + return rc; /* success */ + + ddi_soft_state_fini(&g_pVBoxDrvSolarisState); + LogRel(("vboxdrv: mod_install failed! rc=%d\n", rc)); + } + else + LogRel(("vboxdrv: failed to initialize soft state.\n")); + + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; + } + else + { + LogRel(("VBoxDrvSolarisAttach: RTSpinlockCreate failed\n")); + rc = RTErrConvertToErrno(rc); + } + supdrvDeleteDevExt(&g_DevExt); + } + else + { + LogRel(("VBoxDrvSolarisAttach: supdrvInitDevExt failed\n")); + rc = EINVAL; + } + RTR0TermForced(); + } + else + { + LogRel(("VBoxDrvSolarisAttach: failed to init R0Drv\n")); + rc = RTErrConvertToErrno(rc); + } + memset(&g_DevExt, 0, sizeof(g_DevExt)); + + return rc; +} + + +int _fini(void) +{ + LogFlowFunc(("vboxdrv:_fini\n")); + + /* + * Undo the work we did at start (in the reverse order). + */ + int rc = mod_remove(&g_VBoxDrvSolarisModLinkage); + if (rc != 0) + return rc; + + supdrvDeleteDevExt(&g_DevExt); + + rc = RTSpinlockDestroy(g_Spinlock); + AssertRC(rc); + g_Spinlock = NIL_RTSPINLOCK; + + RTR0TermForced(); + + memset(&g_DevExt, 0, sizeof(g_DevExt)); + + ddi_soft_state_fini(&g_pVBoxDrvSolarisState); + return 0; +} + + +int _info(struct modinfo *pModInfo) +{ +#if 0 /* No IPRT logging before RTR0Init() is done! And yes this is called before _init()!*/ + LogFlowFunc(("vboxdrv:_init\n")); +#endif + int e = mod_info(&g_VBoxDrvSolarisModLinkage, pModInfo); + return e; +} + + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param enmCmd Operation type (attach/resume). + * + * @return corresponding solaris error code. + */ +static int VBoxDrvSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFlowFunc(("VBoxDrvSolarisAttach\n")); + + switch (enmCmd) + { + case DDI_ATTACH: + { + int rc; +#ifdef USE_SESSION_HASH + int instance = ddi_get_instance(pDip); + vbox_devstate_t *pState; + + if (ddi_soft_state_zalloc(g_pVBoxDrvSolarisState, instance) != DDI_SUCCESS) + { + LogRel(("VBoxDrvSolarisAttach: state alloc failed\n")); + return DDI_FAILURE; + } + + pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, instance); +#endif + + /* + * Register for suspend/resume notifications + */ + rc = ddi_prop_create(DDI_DEV_T_NONE, pDip, DDI_PROP_CANSLEEP /* kmem alloc can sleep */, + "pm-hardware-state", "needs-suspend-resume", sizeof("needs-suspend-resume")); + if (rc != DDI_PROP_SUCCESS) + LogRel(("vboxdrv: Suspend/Resume notification registration failed.\n")); + + /* + * Register ourselves as a character device, pseudo-driver + */ +#ifdef VBOX_WITH_HARDENING + rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME_SYS, S_IFCHR, 0 /*minor*/, DDI_PSEUDO, + 0, NULL, NULL, 0600); +#else + rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME_SYS, S_IFCHR, 0 /*minor*/, DDI_PSEUDO, + 0, "none", "none", 0666); +#endif + if (rc == DDI_SUCCESS) + { + rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME_USR, S_IFCHR, 1 /*minor*/, DDI_PSEUDO, + 0, "none", "none", 0666); + if (rc == DDI_SUCCESS) + { +#ifdef USE_SESSION_HASH + pState->pDip = pDip; +#endif + ddi_report_dev(pDip); + return DDI_SUCCESS; + } + ddi_remove_minor_node(pDip, NULL); + } + + return DDI_FAILURE; + } + + case DDI_RESUME: + { +#if 0 + RTSemFastMutexRequest(g_DevExt.mtxGip); + if (g_DevExt.pGipTimer) + RTTimerStart(g_DevExt.pGipTimer, 0); + + RTSemFastMutexRelease(g_DevExt.mtxGip); +#endif + RTPowerSignalEvent(RTPOWEREVENT_RESUME); + LogFlow(("vboxdrv: Awakened from suspend.\n")); + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } + + return DDI_FAILURE; +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param enmCmd Operation type (detach/suspend). + * + * @return corresponding solaris error code. + */ +static int VBoxDrvSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFlowFunc(("VBoxDrvSolarisDetach\n")); + switch (enmCmd) + { + case DDI_DETACH: + { +#ifndef USE_SESSION_HASH + ddi_remove_minor_node(pDip, NULL); +#else + int instance = ddi_get_instance(pDip); + vbox_devstate_t *pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, instance); + ddi_remove_minor_node(pDip, NULL); + ddi_soft_state_free(g_pVBoxDrvSolarisState, instance); +#endif + ddi_prop_remove_all(pDip); + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { +#if 0 + RTSemFastMutexRequest(g_DevExt.mtxGip); + if (g_DevExt.pGipTimer && g_DevExt.cGipUsers > 0) + RTTimerStop(g_DevExt.pGipTimer); + + RTSemFastMutexRelease(g_DevExt.mtxGip); +#endif + RTPowerSignalEvent(RTPOWEREVENT_SUSPEND); + LogFlow(("vboxdrv: Falling to suspend mode.\n")); + return DDI_SUCCESS; + + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Quiesce not-needed entry point, as Solaris 10 doesn't have any + * ddi_quiesce_not_needed() function. + * + * @param pDip The module structure instance. + * + * @return corresponding solaris error code. + */ +static int VBoxDrvSolarisQuiesceNotNeeded(dev_info_t *pDip) +{ + return DDI_SUCCESS; +} + + +/** + * open() worker. + */ +static int VBoxDrvSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred) +{ + const bool fUnrestricted = getminor(*pDev) == 0; + PSUPDRVSESSION pSession; + int rc; + + LogFlowFunc(("VBoxDrvSolarisOpen: pDev=%p:%#x\n", pDev, *pDev)); + + /* + * Validate input + */ + if ( (getminor(*pDev) != 0 && getminor(*pDev) != 1) + || fType != OTYP_CHR) + return EINVAL; /* See mmopen for precedent. */ + +#ifndef USE_SESSION_HASH + /* + * Locate a new device open instance. + * + * For each open call we'll allocate an item in the soft state of the device. + * The item index is stored in the dev_t. I hope this is ok... + */ + vbox_devstate_t *pState = NULL; + unsigned iOpenInstance; + for (iOpenInstance = 0; iOpenInstance < 4096; iOpenInstance++) + { + if ( !ddi_get_soft_state(g_pVBoxDrvSolarisState, iOpenInstance) /* faster */ + && ddi_soft_state_zalloc(g_pVBoxDrvSolarisState, iOpenInstance) == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, iOpenInstance); + break; + } + } + if (!pState) + { + LogRel(("VBoxDrvSolarisOpen: too many open instances.\n")); + return ENXIO; + } + + /* + * Create a new session. + */ + rc = supdrvCreateSession(&g_DevExt, true /* fUser */, fUnrestricted, &pSession); + if (RT_SUCCESS(rc)) + { + pSession->Uid = crgetruid(pCred); + pSession->Gid = crgetrgid(pCred); + + pState->pSession = pSession; + *pDev = makedevice(getmajor(*pDev), iOpenInstance); + LogFlow(("VBoxDrvSolarisOpen: Dev=%#x pSession=%p pid=%d r0proc=%p thread=%p\n", + *pDev, pSession, RTProcSelf(), RTR0ProcHandleSelf(), RTThreadNativeSelf() )); + return 0; + } + + /* failed - clean up */ + ddi_soft_state_free(g_pVBoxDrvSolarisState, iOpenInstance); + +#else + /* + * Create a new session. + * Sessions in Solaris driver are mostly useless. It's however needed + * in VBoxDrvSolarisIOCtlSlow() while calling supdrvIOCtl() + */ + rc = supdrvCreateSession(&g_DevExt, true /* fUser */, fUnrestricted, &pSession); + if (RT_SUCCESS(rc)) + { + unsigned iHash; + + pSession->Uid = crgetruid(pCred); + pSession->Gid = crgetrgid(pCred); + + /* + * Insert it into the hash table. + */ +# error "Only one entry per process!" + iHash = SESSION_HASH(pSession->Process); + RTSpinlockAcquire(g_Spinlock); + pSession->pNextHash = g_apSessionHashTab[iHash]; + g_apSessionHashTab[iHash] = pSession; + RTSpinlockRelease(g_Spinlock); + LogFlow(("VBoxDrvSolarisOpen success\n")); + } + + int instance; + for (instance = 0; instance < DEVICE_MAXINSTANCES; instance++) + { + vbox_devstate_t *pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, instance); + if (pState) + break; + } + + if (instance >= DEVICE_MAXINSTANCES) + { + LogRel(("VBoxDrvSolarisOpen: All instances exhausted\n")); + return ENXIO; + } + + *pDev = makedevice(getmajor(*pDev), instance); +#endif + + return VBoxSupDrvErr2SolarisErr(rc); +} + + +static int VBoxDrvSolarisClose(dev_t Dev, int flag, int otyp, cred_t *cred) +{ + LogFlowFunc(("VBoxDrvSolarisClose: Dev=%#x\n", Dev)); + +#ifndef USE_SESSION_HASH + /* + * Get the session and free the soft state item. + */ + vbox_devstate_t *pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, getminor(Dev)); + if (!pState) + { + LogRel(("VBoxDrvSolarisClose: no state data for %#x (%d)\n", Dev, getminor(Dev))); + return EFAULT; + } + + PSUPDRVSESSION pSession = pState->pSession; + pState->pSession = NULL; + ddi_soft_state_free(g_pVBoxDrvSolarisState, getminor(Dev)); + + if (!pSession) + { + LogRel(("VBoxDrvSolarisClose: no session in state data for %#x (%d)\n", Dev, getminor(Dev))); + return EFAULT; + } + LogFlow(("VBoxDrvSolarisClose: Dev=%#x pSession=%p pid=%d r0proc=%p thread=%p\n", + Dev, pSession, RTProcSelf(), RTR0ProcHandleSelf(), RTThreadNativeSelf() )); + +#else + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(Process); + PSUPDRVSESSION pSession; + + /* + * Remove from the hash table. + */ + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + if (pSession) + { + if (pSession->Process == Process) + { + g_apSessionHashTab[iHash] = pSession->pNextHash; + pSession->pNextHash = NULL; + } + else + { + PSUPDRVSESSION pPrev = pSession; + pSession = pSession->pNextHash; + while (pSession) + { + if (pSession->Process == Process) + { + pPrev->pNextHash = pSession->pNextHash; + pSession->pNextHash = NULL; + break; + } + + /* next */ + pPrev = pSession; + pSession = pSession->pNextHash; + } + } + } + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + LogRel(("VBoxDrvSolarisClose: WHAT?!? pSession == NULL! This must be a mistake... pid=%d (close)\n", (int)Process)); + return EFAULT; + } +#endif + + /* + * Close the session. + */ + supdrvSessionRelease(pSession); + return 0; +} + + +static int VBoxDrvSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFlowFunc(("VBoxDrvSolarisRead")); + return 0; +} + + +static int VBoxDrvSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFlowFunc(("VBoxDrvSolarisWrite")); + return 0; +} + + +/** + * Driver ioctl, an alternate entry point for this character driver. + * + * @param Dev Device number + * @param iCmd Operation identifier + * @param pArgs Arguments from user to driver + * @param Mode Information bitfield (read/write, address space etc.) + * @param pCred User credentials + * @param pVal Return value for calling process. + * + * @return corresponding solaris error code. + */ +static int VBoxDrvSolarisIOCtl(dev_t Dev, int iCmd, intptr_t pArgs, int Mode, cred_t *pCred, int *pVal) +{ +#ifndef USE_SESSION_HASH + /* + * Get the session from the soft state item. + */ + vbox_devstate_t *pState = ddi_get_soft_state(g_pVBoxDrvSolarisState, getminor(Dev)); + if (!pState) + { + LogRel(("VBoxDrvSolarisIOCtl: no state data for %#x (%d)\n", Dev, getminor(Dev))); + return EINVAL; + } + + PSUPDRVSESSION pSession = pState->pSession; + if (!pSession) + { + LogRel(("VBoxDrvSolarisIOCtl: no session in state data for %#x (%d)\n", Dev, getminor(Dev))); + return DDI_SUCCESS; + } +#else + const RTPROCESS Process = RTProcSelf(); + const unsigned iHash = SESSION_HASH(Process); + PSUPDRVSESSION pSession; + const bool fUnrestricted = getminor(Dev) == 0; + + /* + * Find the session. + */ + RTSpinlockAcquire(g_Spinlock); + pSession = g_apSessionHashTab[iHash]; + while (pSession && pSession->Process != Process && pSession->fUnrestricted == fUnrestricted); + pSession = pSession->pNextHash; + RTSpinlockRelease(g_Spinlock); + if (!pSession) + { + LogRel(("VBoxSupDrvIOCtl: WHAT?!? pSession == NULL! This must be a mistake... pid=%d iCmd=%#x Dev=%#x\n", + (int)Process, iCmd, (int)Dev)); + return EINVAL; + } +#endif + + /* + * Deal with the two high-speed IOCtl that takes it's arguments from + * the session and iCmd, and only returns a VBox status code. + */ + AssertCompile((SUP_IOCTL_FAST_DO_FIRST & 0xff) == (SUP_IOCTL_FLAG | 64)); + if ( (unsigned)(iCmd - SUP_IOCTL_FAST_DO_FIRST) < (unsigned)32 + && pSession->fUnrestricted) + { + *pVal = supdrvIOCtlFast(iCmd - SUP_IOCTL_FAST_DO_FIRST, pArgs, &g_DevExt, pSession); + return 0; + } + + return VBoxDrvSolarisIOCtlSlow(pSession, iCmd, Mode, pArgs); +} + + +/** @def IOCPARM_LEN + * Gets the length from the ioctl number. + * This is normally defined by sys/ioccom.h on BSD systems... + */ +#ifndef IOCPARM_LEN +# define IOCPARM_LEN(x) ( ((x) >> 16) & IOCPARM_MASK ) +#endif + + +/** + * Worker for VBoxSupDrvIOCtl that takes the slow IOCtl functions. + * + * @returns Solaris errno. + * + * @param pSession The session. + * @param iCmd The IOCtl command. + * @param Mode Information bitfield (for specifying ownership of data) + * @param iArg User space address of the request buffer. + */ +static int VBoxDrvSolarisIOCtlSlow(PSUPDRVSESSION pSession, int iCmd, int Mode, intptr_t iArg) +{ + int rc; + uint32_t cbBuf = 0; + union + { + SUPREQHDR Hdr; + uint8_t abBuf[64]; + } StackBuf; + PSUPREQHDR pHdr; + + + /* + * Read the header. + */ + if (RT_UNLIKELY(IOCPARM_LEN(iCmd) != sizeof(StackBuf.Hdr))) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: iCmd=%#x len %d expected %d\n", iCmd, IOCPARM_LEN(iCmd), sizeof(StackBuf.Hdr))); + return EINVAL; + } + rc = ddi_copyin((void *)iArg, &StackBuf.Hdr, sizeof(StackBuf.Hdr), Mode); + if (RT_UNLIKELY(rc)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: ddi_copyin(,%#lx,) failed; iCmd=%#x. rc=%d\n", iArg, iCmd, rc)); + return EFAULT; + } + if (RT_UNLIKELY((StackBuf.Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK) != SUPREQHDR_FLAGS_MAGIC)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: bad header magic %#x; iCmd=%#x\n", StackBuf.Hdr.fFlags & SUPREQHDR_FLAGS_MAGIC_MASK, iCmd)); + return EINVAL; + } + cbBuf = RT_MAX(StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut); + if (RT_UNLIKELY( StackBuf.Hdr.cbIn < sizeof(StackBuf.Hdr) + || StackBuf.Hdr.cbOut < sizeof(StackBuf.Hdr) + || cbBuf > _1M*16)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: max(%#x,%#x); iCmd=%#x\n", StackBuf.Hdr.cbIn, StackBuf.Hdr.cbOut, iCmd)); + return EINVAL; + } + + /* + * Buffer the request. + */ + if (cbBuf <= sizeof(StackBuf)) + pHdr = &StackBuf.Hdr; + else + { + pHdr = RTMemTmpAlloc(cbBuf); + if (RT_UNLIKELY(!pHdr)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: failed to allocate buffer of %d bytes for iCmd=%#x.\n", cbBuf, iCmd)); + return ENOMEM; + } + } + rc = ddi_copyin((void *)iArg, pHdr, cbBuf, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: copy_from_user(,%#lx, %#x) failed; iCmd=%#x. rc=%d\n", iArg, cbBuf, iCmd, rc)); + if (pHdr != &StackBuf.Hdr) + RTMemFree(pHdr); + return EFAULT; + } + + /* + * Process the IOCtl. + */ + rc = supdrvIOCtl(iCmd, &g_DevExt, pSession, pHdr, cbBuf); + + /* + * Copy ioctl data and output buffer back to user space. + */ + if (RT_LIKELY(!rc)) + { + uint32_t cbOut = pHdr->cbOut; + if (RT_UNLIKELY(cbOut > cbBuf)) + { + LogRel(("VBoxDrvSolarisIOCtlSlow: too much output! %#x > %#x; iCmd=%#x!\n", cbOut, cbBuf, iCmd)); + cbOut = cbBuf; + } + rc = ddi_copyout(pHdr, (void *)iArg, cbOut, Mode); + if (RT_UNLIKELY(rc != 0)) + { + /* this is really bad */ + LogRel(("VBoxDrvSolarisIOCtlSlow: ddi_copyout(,%p,%d) failed. rc=%d\n", (void *)iArg, cbBuf, rc)); + rc = EFAULT; + } + } + else + rc = EINVAL; + + if (pHdr != &StackBuf.Hdr) + RTMemTmpFree(pHdr); + return rc; +} + + +/** + * The SUPDRV IDC entry point. + * + * @returns VBox status code, see supdrvIDC. + * @param uReq The request code. + * @param pReq The request. + */ +int VBOXCALL SUPDrvSolarisIDC(uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + PSUPDRVSESSION pSession; + + /* + * Some quick validations. + */ + if (RT_UNLIKELY(!RT_VALID_PTR(pReq))) + return VERR_INVALID_POINTER; + + pSession = pReq->pSession; + if (pSession) + { + if (RT_UNLIKELY(!RT_VALID_PTR(pSession))) + return VERR_INVALID_PARAMETER; + if (RT_UNLIKELY(pSession->pDevExt != &g_DevExt)) + return VERR_INVALID_PARAMETER; + } + else if (RT_UNLIKELY(uReq != SUPDRV_IDC_REQ_CONNECT)) + return VERR_INVALID_PARAMETER; + + /* + * Do the job. + */ + return supdrvIDC(uReq, &g_DevExt, pSession, pReq); +} + + +/** + * Converts an supdrv error code to a solaris error code. + * + * @returns corresponding solaris error code. + * @param rc IPRT status code. + */ +static int VBoxSupDrvErr2SolarisErr(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return 0; + case VERR_GENERAL_FAILURE: return EACCES; + case VERR_INVALID_PARAMETER: return EINVAL; + case VERR_INVALID_MAGIC: return EILSEQ; + case VERR_INVALID_HANDLE: return ENXIO; + case VERR_INVALID_POINTER: return EFAULT; + case VERR_LOCK_FAILED: return ENOLCK; + case VERR_ALREADY_LOADED: return EEXIST; + case VERR_PERMISSION_DENIED: return EPERM; + case VERR_VERSION_MISMATCH: return ENOSYS; + } + + return EPERM; +} + + +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. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + return false; +} + + +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + /** @todo verify this. */ + return false; +} + + +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + return false; +} + + +#if defined(VBOX_WITH_NATIVE_SOLARIS_LOADING) \ + && !defined(VBOX_WITHOUT_NATIVE_R0_LOADER) + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + pImage->idSolMod = -1; + pImage->pSolModCtl = NULL; + +# if 1 /* This approach requires _init/_fini/_info stubs. */ + /* + * Construct a filename that escapes the module search path and let us + * specify a root path. + */ + /** @todo change this to use modctl and use_path=0. */ + const char *pszName = RTPathFilename(pszFilename); + AssertReturn(pszName, VERR_INVALID_PARAMETER); + char *pszSubDir = RTStrAPrintf2("../../../../../../../../../../..%.*s", pszName - pszFilename - 1, pszFilename); + if (!pszSubDir) + return VERR_NO_STR_MEMORY; + int idMod = modload(pszSubDir, pszName); + if (idMod == -1) + { + /* This is an horrible hack for avoiding the mod-present check in + modrload on S10. Fortunately, nobody else seems to be using that + variable... */ + extern int swaploaded; + int saved_swaploaded = swaploaded; + swaploaded = 0; + idMod = modload(pszSubDir, pszName); + swaploaded = saved_swaploaded; + } + RTStrFree(pszSubDir); + if (idMod == -1) + { + LogRel(("modload(,%s): failed, could be anything...\n", pszFilename)); + return VERR_LDR_GENERAL_FAILURE; + } + + modctl_t *pModCtl = mod_hold_by_id(idMod); + if (!pModCtl) + { + LogRel(("mod_hold_by_id(,%s): failed, weird.\n", pszFilename)); + /* No point in calling modunload. */ + return VERR_LDR_GENERAL_FAILURE; + } + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD | MOD_NOUNLOAD; /* paranoia */ + +# else + + const int idMod = -1; + modctl_t *pModCtl = mod_hold_by_name(pszFilename); + if (!pModCtl) + { + LogRel(("mod_hold_by_name failed for '%s'\n", pszFilename)); + return VERR_LDR_GENERAL_FAILURE; + } + + int rc = kobj_load_module(pModCtl, 0 /*use_path*/); + if (rc != 0) + { + LogRel(("kobj_load_module failed with rc=%d for '%s'\n", rc, pszFilename)); + mod_release_mod(pModCtl); + return RTErrConvertFromErrno(rc); + } +# endif + + /* + * Get the module info. + * + * Note! The text section is actually not at mi_base, but and the next + * alignment boundrary and there seems to be no easy way of + * getting at this address. This sabotages supdrvOSLdrLoad. + * Bastards! + */ + struct modinfo ModInfo; + kobj_getmodinfo(pModCtl->mod_mp, &ModInfo); + pImage->pvImage = ModInfo.mi_base; + pImage->idSolMod = idMod; + pImage->pSolModCtl = pModCtl; + + mod_release_mod(pImage->pSolModCtl); + LogRel(("supdrvOSLdrOpen: succeeded for '%s' (mi_base=%p mi_size=%#x), id=%d ctl=%p\n", + pszFilename, ModInfo.mi_base, ModInfo.mi_size, idMod, pModCtl)); + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + if (kobj_addrcheck(pImage->pSolModCtl->mod_mp, pv)) + return VERR_INVALID_PARAMETER; + return VINF_SUCCESS; +} + + +/** + * Resolves a module entry point address. + * + * @returns VBox status code. + * @param pImage The image. + * @param pszSymbol The symbol name. + * @param ppvValue Where to store the value. On input this holds + * the symbol value SUPLib calculated. + */ +static int supdrvSolLdrResolvEp(PSUPDRVLDRIMAGE pImage, const char *pszSymbol, void **ppvValue) +{ + /* Don't try resolve symbols which, according to SUPLib, aren't there. */ + if (!*ppvValue) + return VINF_SUCCESS; + + uintptr_t uValue = modlookup_by_modctl(pImage->pSolModCtl, pszSymbol); + if (!uValue) + { + LogRel(("supdrvOSLdrLoad on %s failed to resolve %s\n", pImage->szName, pszSymbol)); + return VERR_SYMBOL_NOT_FOUND; + } + *ppvValue = (void *)uValue; + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ +#if 0 /* This doesn't work because of text alignment. */ + /* + * Comparing is very very difficult since text and data may be allocated + * separately. + */ + size_t cbCompare = RT_MIN(pImage->cbImageBits, 64); + if (memcmp(pImage->pvImage, pbImageBits, cbCompare)) + { + LogRel(("Image mismatch: %s (%p)\n", pImage->szName, pImage->pvImage)); + LogRel(("Native: %.*Rhxs\n", cbCompare, pImage->pvImage)); + LogRel(("SUPLib: %.*Rhxs\n", cbCompare, pbImageBits)); + return VERR_LDR_MISMATCH_NATIVE; + } +#endif + + /* + * Get the exported symbol addresses. + */ + int rc; + modctl_t *pModCtl = mod_hold_by_id(pImage->idSolMod); + if (pModCtl && pModCtl == pImage->pSolModCtl) + { + uint32_t iSym = pImage->cSymbols; + while (iSym-- > 0) + { + const char *pszSymbol = &pImage->pachStrTab[pImage->paSymbols[iSym].offName]; + uintptr_t uValue = modlookup_by_modctl(pImage->pSolModCtl, pszSymbol); + if (!uValue) + { + LogRel(("supdrvOSLdrLoad on %s failed to resolve the exported symbol: '%s'\n", pImage->szName, pszSymbol)); + break; + } + uintptr_t offSymbol = uValue - (uintptr_t)pImage->pvImage; + pImage->paSymbols[iSym].offSymbol = offSymbol; + if (pImage->paSymbols[iSym].offSymbol != (int32_t)offSymbol) + { + LogRel(("supdrvOSLdrLoad on %s symbol out of range: %p (%s) \n", pImage->szName, offSymbol, pszSymbol)); + break; + } + } + + rc = iSym == UINT32_MAX ? VINF_SUCCESS : VERR_LDR_GENERAL_FAILURE; + + /* + * Get the standard module entry points. + */ + if (RT_SUCCESS(rc)) + { + rc = supdrvSolLdrResolvEp(pImage, "ModuleInit", (void **)&pImage->pfnModuleInit); + if (RT_SUCCESS(rc)) + rc = supdrvSolLdrResolvEp(pImage, "ModuleTerm", (void **)&pImage->pfnModuleTerm); + + switch (pReq->u.In.eEPType) + { + case SUPLDRLOADEP_VMMR0: + { + if (RT_SUCCESS(rc)) + rc = supdrvSolLdrResolvEp(pImage, "VMMR0EntryFast", (void **)&pReq->u.In.EP.VMMR0.pvVMMR0EntryFast); + if (RT_SUCCESS(rc)) + rc = supdrvSolLdrResolvEp(pImage, "VMMR0EntryEx", (void **)&pReq->u.In.EP.VMMR0.pvVMMR0EntryEx); + break; + } + + case SUPLDRLOADEP_SERVICE: + { + /** @todo we need the name of the entry point. */ + return VERR_NOT_SUPPORTED; + } + } + } + + mod_release_mod(pImage->pSolModCtl); + } + else + { + LogRel(("mod_hold_by_id failed in supdrvOSLdrLoad on %s: %p\n", pImage->szName, pModCtl)); + rc = VERR_LDR_MISMATCH_NATIVE; + } + return rc; +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ +# if 1 + pImage->pSolModCtl->mod_loadflags &= ~MOD_NOUNLOAD; + int rc = modunload(pImage->idSolMod); + if (rc) + LogRel(("modunload(%u (%s)) failed: %d\n", pImage->idSolMod, pImage->szName, rc)); +# else + kobj_unload_module(pImage->pSolModCtl); +# endif + pImage->pSolModCtl = NULL; + pImage->idSolMod = NULL; +} + +#else /* !VBOX_WITH_NATIVE_SOLARIS_LOADING */ + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pv); NOREF(pbImageBits); NOREF(pszSymbol); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pbImageBits); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + +#endif /* !VBOX_WITH_NATIVE_SOLARIS_LOADING */ + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt, pImage, pszSymbol, cchSymbol, ppvSymbol); + return VERR_WRONG_ORDER; +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + +#ifdef SUPDRV_WITH_MSR_PROBER + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ +/** @todo cmi_hdl_rdmsr can safely do this. there is also the on_trap() fun + * for catching traps that could possibly be used directly. */ + NOREF(uMsr); NOREF(idCpu); NOREF(puValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ +/** @todo cmi_hdl_wrmsr can safely do this. */ + NOREF(uMsr); NOREF(idCpu); NOREF(uValue); + return VERR_NOT_SUPPORTED; +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + NOREF(idCpu); NOREF(pReq); + return VERR_NOT_SUPPORTED; +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +SUPR0DECL(int) SUPR0HCPhysToVirt(RTHCPHYS HCPhys, void **ppv) +{ + AssertReturn(!(HCPhys & PAGE_OFFSET_MASK), VERR_INVALID_POINTER); + AssertReturn(HCPhys != NIL_RTHCPHYS, VERR_INVALID_POINTER); + HCPhys >>= PAGE_SHIFT; + AssertReturn(HCPhys <= physmax, VERR_INVALID_POINTER); + *ppv = hat_kpm_pfn2va(HCPhys); + return VINF_SUCCESS; +} + + +RTDECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + /* cmn_err() acquires adaptive mutexes. Not preemption safe, see @bugref{6657}. */ + if (RTThreadPreemptIsEnabled(NIL_RTTHREAD)) + { + char szMsg[512]; + RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + cmn_err(CE_CONT, "%s", szMsg); + } + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + diff --git a/src/VBox/HostDrivers/Support/solaris/SUPLib-solaris.cpp b/src/VBox/HostDrivers/Support/solaris/SUPLib-solaris.cpp new file mode 100644 index 00000000..8e12b929 --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/SUPLib-solaris.cpp @@ -0,0 +1,245 @@ +/* $Id: SUPLib-solaris.cpp $ */ +/** @file + * VirtualBox Support Library - Solaris specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# ifndef LOG_DISABLED +# define LOG_DISABLED +# endif +# define RTLOG_REL_DISABLED +# include <iprt/log.h> +#endif + +#include <VBox/types.h> +#include <VBox/sup.h> +#include <VBox/param.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/zone.h> + +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <sys/mman.h> +#include <stdlib.h> +#include <stdio.h> +#include <zone.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Solaris device link - system. */ +#define DEVICE_NAME_SYS "/devices/pseudo/vboxdrv@0:vboxdrv" +/** Solaris device link - user. */ +#define DEVICE_NAME_USR "/devices/pseudo/vboxdrv@0:vboxdrvu" +/** Solaris device link - system (non-global zone). */ +#define DEVICE_NAME_SYS_ZONE "/dev/vboxdrv" +/** Solaris device link - user (non-global zone). */ +#define DEVICE_NAME_USR_ZONE "/dev/vboxdrvu" + + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + /* + * Nothing to do if pre-inited. + */ + if (fPreInited) + return VINF_SUCCESS; + + /* + * Open dummy files to preallocate file descriptors, see @bugref{4650}. + */ + for (int i = 0; i < SUPLIB_FLT_DUMMYFILES; i++) + { + pThis->ahDummy[i] = -1; + int hDummy = open("/dev/null", O_RDWR, 0); + if (hDummy >= 0) + { + if (fcntl(hDummy, F_SETFD, FD_CLOEXEC) == 0) + pThis->ahDummy[i] = hDummy; + else + { + close(hDummy); + LogRel(("Failed to set close on exec [%d] /dev/null! errno=%d\n", i, errno)); + } + } + else + LogRel(("Failed to open[%d] /dev/null! errno=%d\n", i, errno)); + } + + /* + * Try to open the device. + */ + const char *pszDeviceNm; + if (getzoneid() == GLOBAL_ZONEID) + pszDeviceNm = fFlags & SUPR3INIT_F_UNRESTRICTED ? DEVICE_NAME_SYS : DEVICE_NAME_USR; + else + pszDeviceNm = fFlags & SUPR3INIT_F_UNRESTRICTED ? DEVICE_NAME_SYS_ZONE : DEVICE_NAME_USR_ZONE; + int hDevice = open(pszDeviceNm, O_RDWR, 0); + if (hDevice < 0) + { + int rc; + switch (errno) + { + case ENODEV: rc = VERR_VM_DRIVER_LOAD_ERROR; break; + case EPERM: + case EACCES: rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; break; + case ENOENT: rc = VERR_VM_DRIVER_NOT_INSTALLED; break; + default: rc = VERR_VM_DRIVER_OPEN_ERROR; break; + } + LogRel(("Failed to open \"%s\", errno=%d, rc=%Rrc\n", pszDeviceNm, errno, rc)); + return rc; + } + + /* + * Mark the file handle close on exec. + */ + if (fcntl(hDevice, F_SETFD, FD_CLOEXEC) != 0) + { +#ifdef IN_SUP_HARDENED_R3 + int rc = VERR_INTERNAL_ERROR; +#else + int err = errno; + int rc = RTErrConvertFromErrno(err); + LogRel(("suplibOSInit: setting FD_CLOEXEC failed, errno=%d (%Rrc)\n", err, rc)); +#endif + close(hDevice); + return rc; + } + + pThis->hDevice = hDevice; + pThis->fUnrestricted = RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Close the dummy files first. + */ + for (int i = 0; i < SUPLIB_FLT_DUMMYFILES; i++) + { + if (pThis->ahDummy[i] != -1) + { + close(pThis->ahDummy[i]); + pThis->ahDummy[i] = -1; + } + } + + /* + * Check if we're initialized + */ + if (pThis->hDevice != (intptr_t)NIL_RTFILE) + { + if (close(pThis->hDevice)) + AssertFailed(); + pThis->hDevice = (intptr_t)NIL_RTFILE; + } + + return VINF_SUCCESS; +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + return VERR_NOT_IMPLEMENTED; +} + + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + if (RT_LIKELY(ioctl(pThis->hDevice, uFunction, pvReq) >= 0)) + return VINF_SUCCESS; + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + int rc = ioctl(pThis->hDevice, uFunction, idCpu); + if (rc == -1) + rc = errno; + return rc; +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + *ppvPages = mmap(NULL, cPages * PAGE_SIZE, PROT_EXEC | PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (*ppvPages != (void *)-1) + return VINF_SUCCESS; + if (errno == EAGAIN) + return VERR_NO_MEMORY; + return RTErrConvertFromErrno(errno); +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t cPages) +{ + NOREF(pThis); + munmap(pvPages, cPages * PAGE_SIZE); + return VINF_SUCCESS; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/solaris/SUPR0IdcClient-solaris.c b/src/VBox/HostDrivers/Support/solaris/SUPR0IdcClient-solaris.c new file mode 100644 index 00000000..ba0b5e3e --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/SUPR0IdcClient-solaris.c @@ -0,0 +1,66 @@ +/* $Id: SUPR0IdcClient-solaris.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Solaris Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNativeCall(pHandle, SUPDRV_IDC_REQ_DISCONNECT, pReq); +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t iReq, PSUPDRVIDCREQHDR pReq) +{ + int rc = SUPDrvSolarisIDC(iReq, pReq); + if (RT_SUCCESS(rc)) + rc = pReq->rc; + + NOREF(pHandle); + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/solaris/load.sh b/src/VBox/HostDrivers/Support/solaris/load.sh new file mode 100755 index 00000000..b2f86bab --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/load.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# $Id: load.sh $ +## @file +# For development. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +DRVNAME="vboxdrv" +DRIVERS_USING_IT="vboxusb vboxusbmon vboxnet vboxflt vboxbow" + +DRVFILE=`dirname "$0"` +DRVFILE=`cd "$DRVFILE" && pwd` +DRVFILE="$DRVFILE/$DRVNAME" +if [ ! -f "$DRVFILE" ]; then + echo "load.sh: Cannot find $DRVFILE or it's not a file..." + exit 1; +fi +if [ ! -f "$DRVFILE.conf" ]; then + echo "load.sh: Cannot find $DRVFILE.conf or it's not a file..." + exit 1; +fi + +SUDO=sudo +hash "${SUDO}" 2> /dev/null || SUDO=pfexec +#set -x + +# Disable the zone access service. +servicefound=`svcs -H "virtualbox/zoneaccess" 2>/dev/null | grep '^online'` +if test ! -z "$servicefound"; then + $SUDO svcadm disable svc:/application/virtualbox/zoneaccess:default +fi + +# Unload driver that may depend on the driver we're going to (re-)load +# as well as the driver itself. +for drv in $DRIVERS_USING_IT $DRVNAME; +do + LOADED=`modinfo | grep -w "$drv"` + if test -n "$LOADED"; then + MODID=`echo "$LOADED" | cut -d ' ' -f 1` + $SUDO modunload -i $MODID; + LOADED=`modinfo | grep -w "$drv"`; + if test -n "$LOADED"; then + echo "load.sh: failed to unload $drv"; + dmesg | tail + exit 1; + fi + fi +done + +# +# Reconfigure the driver so it get a major number. +# +# Note! We have to copy the driver and config files to somewhere the kernel can +# find them. It is searched for as drv/${DRVNAME}.conf in +# kobj_module_path, which is usually: +# /platform/i86pc/kernel /kernel /usr/kernel +# To try prevent bad drivers from being loaded on the next boot, we remove +# always the files. +# +MY_RC=1 +set -e +$SUDO rm -f \ + "/platform/i86pc/kernel/drv/${DRVNAME}.conf" \ + "/platform/i86pc/kernel/drv/${DRVNAME}" \ + "/platform/i86pc/kernel/drv/amd64/${DRVNAME}" +sync +$SUDO cp "${DRVFILE}" /platform/i86pc/kernel/drv/amd64/ +$SUDO cp "${DRVFILE}.conf" /platform/i86pc/kernel/drv/ +set +e + +$SUDO rem_drv $DRVNAME +if $SUDO add_drv -v $DRVNAME; then + sync + if $SUDO modload "/platform/i86pc/kernel/drv/amd64/${DRVNAME}"; then + echo "load.sh: successfully loaded the driver" + modinfo | grep -w "$DRVNAME" + MY_RC=0 + if test ! -h "/dev/vboxdrv"; then + $SUDO ln -sf "/devices/pseudo/vboxdrv@0:vboxdrv" /dev/vboxdrv + $SUDO chmod 0666 /dev/vboxdrv + fi + else + dmesg | tail + echo "load.sh: modload failed" + fi +else + dmesg | tail + echo "load.sh: add_drv failed." +fi + +$SUDO rm -f \ + "/platform/i86pc/kernel/drv/${DRVNAME}.conf" \ + "/platform/i86pc/kernel/drv/${DRVNAME}" \ + "/platform/i86pc/kernel/drv/amd64/${DRVNAME}" +sync + +exit $MY_RC; + diff --git a/src/VBox/HostDrivers/Support/solaris/vboxdrv.conf b/src/VBox/HostDrivers/Support/solaris/vboxdrv.conf new file mode 100644 index 00000000..fcc02230 --- /dev/null +++ b/src/VBox/HostDrivers/Support/solaris/vboxdrv.conf @@ -0,0 +1,43 @@ +# $Id: vboxdrv.conf $ +## @file +# VirtualBox Solaris Host Driver Configuration. +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +# This needs to go into /platform/i86pc/kernel/drv, +# while the 64-bit driver object goes into the amd64 +# subdirectory (32-bit drivers goes into the same +# directory). After copying execute add_drv vboxdrv. +# +name="vboxdrv" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/Support/testcase/Makefile.kmk b/src/VBox/HostDrivers/Support/testcase/Makefile.kmk new file mode 100644 index 00000000..039557ac --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/Makefile.kmk @@ -0,0 +1,158 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the SUPLib testcases. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# We need the VMM/Config.kmk one for the VMM_COMMON_DEFS variable. +ifndef VBOX_VMM_CONFIG_KMK_INCLUDED + include $(PATH_ROOT)/src/VBox/VMM/Config.kmk +endif + +PROGRAMS += \ + SUPInstall \ + SUPUninstall \ + SUPLoggerCtl +ifdef VBOX_WITH_TESTCASES + if defined(VBOX_WITH_HARDENING) + PROGRAMS += \ + tstSupVerify + endif + if !defined(VBOX_WITH_HARDENING) || "$(KBUILD_TARGET)" != "win" + PROGRAMS += \ + tstPage \ + tstContiguous \ + tstInit \ + tstInt \ + tstLow \ + tstPin \ + tstGetPagingMode \ + tstSupLoadModule \ + tstSupSem \ + tstSupSem-Zombie \ + tstSupTscDelta + endif + PROGRAMS.win += \ + tstNtQueryStuff +endif # VBOX_WITH_TESTCASES + +SUPInstall_TEMPLATE = VBoxR3Exe +SUPInstall_SOURCES = SUPInstall.cpp +SUPInstall_LIBS = $(LIB_RUNTIME) + +SUPUninstall_TEMPLATE = VBoxR3Exe +SUPUninstall_SOURCES = SUPUninstall.cpp +SUPUninstall_LIBS = $(LIB_RUNTIME) + +SUPLoggerCtl_TEMPLATE = VBoxR3Exe +SUPLoggerCtl_SOURCES = SUPLoggerCtl.cpp +SUPLoggerCtl_LIBS = $(LIB_RUNTIME) + +tstInt_TEMPLATE = VBoxR3Exe +tstInt_DEFS = $(VMM_COMMON_DEFS) +tstInt_SOURCES = tstInt.cpp +tstInt_LIBS = $(LIB_RUNTIME) + +tstContiguous_TEMPLATE = VBoxR3TstExe +tstContiguous_SOURCES = tstContiguous.cpp + +tstInit_TEMPLATE = VBoxR3TstExe +tstInit_SOURCES = tstInit.cpp + +tstLow_TEMPLATE = VBoxR3TstExe +tstLow_SOURCES = tstLow.cpp + +tstNtQueryStuff_TEMPLATE = VBoxR3TstExe +tstNtQueryStuff_SDKS = VBoxNtDll +tstNtQueryStuff_SOURCES = tstNtQueryStuff.cpp + +tstPin_TEMPLATE = VBoxR3TstExe +tstPin_SOURCES = tstPin.cpp + +tstPage_TEMPLATE = VBoxR3TstExe +tstPage_SOURCES = tstPage.cpp + +# +# tstGIP-2 +# +ifdef VBOX_WITH_TESTCASES + if defined(VBOX_WITH_HARDENING) && "$(KBUILD_TARGET)" == "win" + PROGRAMS += tstGIP-2Hardened + DLLS += tstGIP-2 + else + PROGRAMS += tstGIP-2 + endif +endif + +tstGIP-2Hardened_TEMPLATE = VBoxR3HardenedTstExe +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + tstGIP-2Hardened_DEFS = PROGRAM_NAME_STR="tstGIP-2" +else + tstGIP-2Hardened_DEFS = PROGRAM_NAME_STR=\"tstGIP-2\" +endif +tstGIP-2Hardened_SOURCES = ../SUPR3HardenedMainTemplateTestcase.cpp +tstGIP-2Hardened_NAME = tstGIP-2 + +if defined(VBOX_WITH_HARDENING) && "$(KBUILD_TARGET)" == "win" + tstGIP-2_TEMPLATE := VBoxR3HardenedTstDll +else + tstGIP-2_TEMPLATE := VBoxR3TstExe +endif +tstGIP-2_SOURCES = tstGIP-2.cpp + +tstGetPagingMode_TEMPLATE = VBoxR3TstExe +tstGetPagingMode_SOURCES = tstGetPagingMode.cpp + +tstSupLoadModule_TEMPLATE = VBoxR3TstExe +tstSupLoadModule_SOURCES = tstSupLoadModule.cpp + +tstSupSem_TEMPLATE = VBoxR3TstExe +tstSupSem_SOURCES = tstSupSem.cpp + +tstSupSem-Zombie_TEMPLATE = VBoxR3TstExe +tstSupSem-Zombie_SOURCES = tstSupSem-Zombie.cpp + +tstSupTscDelta_TEMPLATE = VBoxR3TstExe +tstSupTscDelta_SOURCES = tstSupTscDelta.cpp + +# For testing supR3HardenedVerifyFile on windows. +tstSupVerify_TEMPLATE = VBoxR3TstExe +tstSupVerify_SOURCES = tstSupVerify.cpp + + + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/Support/testcase/SUPInstall.cpp b/src/VBox/HostDrivers/Support/testcase/SUPInstall.cpp new file mode 100644 index 00000000..246ff7a1 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/SUPInstall.cpp @@ -0,0 +1,69 @@ +/* $Id: SUPInstall.cpp $ */ +/** @file + * SUPInstall - Driver Install + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/initterm.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments"); + NOREF(argv); + + int rc = SUPR3Install(); + if (RT_SUCCESS(rc)) + { + if (rc == VINF_SUCCESS) + RTMsgInfo("Installed successfully!"); + else if (rc == VINF_ALREADY_INITIALIZED) + RTMsgInfo("Already loaded."); + else if (rc == VWRN_ALREADY_EXISTS) + RTMsgInfo("Service already existed; started successfully."); + else + RTMsgInfo("Unexpected status: %Rrc", rc); + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExit(RTEXITCODE_FAILURE, "installation failed. rc=%Rrc", rc); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/SUPLoggerCtl.cpp b/src/VBox/HostDrivers/Support/testcase/SUPLoggerCtl.cpp new file mode 100644 index 00000000..61a6e718 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/SUPLoggerCtl.cpp @@ -0,0 +1,196 @@ +/* $Id: SUPLoggerCtl.cpp $ */ +/** @file + * SUPLoggerCtl - Support Driver Logger Control. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/buildconfig.h> +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> + + +/** + * Prints the usage. + * @returns 1. + */ +static int usage(void) +{ + RTPrintf("usage: SUPLoggerCtl [-f|--flags <flags-settings>] \\\n" + " [-g|--groups <groups-settings>] \\\n" + " [-d|--dest <destination-specifiers>] \\\n" + " [-l|--which <release|debug>] \\\n" + " [-o|--what <set|create|destroy>]\n" + " or: SUPLoggerCtl <-h|--help>\n" + "\n" + ); + return 1; +} + + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + + /* + * Options are mandatory. + */ + if (argc <= 1) + return usage(); + + /* + * Parse the options. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--flags", 'f', RTGETOPT_REQ_STRING }, + { "--groups", 'g', RTGETOPT_REQ_STRING }, + { "--dest", 'd', RTGETOPT_REQ_STRING }, + { "--what", 'o', RTGETOPT_REQ_STRING }, + { "--which", 'l', RTGETOPT_REQ_STRING }, + }; + + const char *pszFlags = ""; + const char *pszGroups = ""; + const char *pszDest = ""; + SUPLOGGER enmWhich = SUPLOGGER_DEBUG; + enum + { + kSupLoggerCtl_Set, kSupLoggerCtl_Create, kSupLoggerCtl_Destroy + } enmWhat = kSupLoggerCtl_Set; + + int ch; + RTGETOPTUNION Val; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'f': + pszFlags = Val.psz; + break; + + case 'g': + pszGroups = Val.psz; + break; + + case 'd': + pszDest = Val.psz; + break; + + case 'o': + if (!strcmp(Val.psz, "set")) + enmWhat = kSupLoggerCtl_Set; + else if (!strcmp(Val.psz, "create")) + enmWhat = kSupLoggerCtl_Create; + else if (!strcmp(Val.psz, "destroy")) + enmWhat = kSupLoggerCtl_Destroy; + else + { + RTStrmPrintf(g_pStdErr, "SUPLoggerCtl: error: Unknown operation '%s'.\n", Val.psz); + return 1; + } + break; + + case 'l': + if (!strcmp(Val.psz, "debug")) + enmWhich = SUPLOGGER_DEBUG; + else if (!strcmp(Val.psz, "release")) + enmWhich = SUPLOGGER_RELEASE; + else + { + RTStrmPrintf(g_pStdErr, "SUPLoggerCtl: error: Unknown logger '%s'.\n", Val.psz); + return 1; + } + break; + + case 'h': + return usage(); + + case 'V': + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return 0; + + case VINF_GETOPT_NOT_OPTION: + RTStrmPrintf(g_pStdErr, "SUPLoggerCtl: error: Unexpected argument '%s'.\n", Val.psz); + return 1; + + default: + return RTGetOptPrintError(ch, &Val); + } + } + + /* + * Make sure the support library is initialized. + */ + int rc = SUPR3Init(NULL /*ppSession*/); + if (RT_SUCCESS(rc)) + { + /* + * Do the requested job. + */ + switch (enmWhat) + { + case kSupLoggerCtl_Set: + rc = SUPR3LoggerSettings(enmWhich, pszFlags, pszGroups, pszDest); + break; + case kSupLoggerCtl_Create: + rc = SUPR3LoggerCreate(enmWhich, pszFlags, pszGroups, pszDest); + break; + case kSupLoggerCtl_Destroy: + rc = SUPR3LoggerDestroy(enmWhich); + break; + default: + rc = VERR_INTERNAL_ERROR; + break; + } + if (RT_SUCCESS(rc)) + RTPrintf("SUPLoggerCtl: Success\n"); + else + RTStrmPrintf(g_pStdErr, "SUPLoggerCtl: error: rc=%Rrc\n", rc); + } + else + RTStrmPrintf(g_pStdErr, "SUPR3Init: error: rc=%Rrc\n", rc); + + return RT_SUCCESS(rc) ? 0 : 1; +} + diff --git a/src/VBox/HostDrivers/Support/testcase/SUPUninstall.cpp b/src/VBox/HostDrivers/Support/testcase/SUPUninstall.cpp new file mode 100644 index 00000000..64b47795 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/SUPUninstall.cpp @@ -0,0 +1,63 @@ +/* $Id: SUPUninstall.cpp $ */ +/** @file + * SUPUninstall - Driver Uninstall. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/message.h> + + +int main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + int rc = SUPR3Uninstall(); + if (RT_SUCCESS(rc)) + { + RTMsgInfo("uninstalled successfully"); + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExit(RTEXITCODE_FAILURE, "uninstallation failed. rc=%Rrc", rc); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstContiguous.cpp b/src/VBox/HostDrivers/Support/testcase/tstContiguous.cpp new file mode 100644 index 00000000..6f1e8290 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstContiguous.cpp @@ -0,0 +1,118 @@ +/* $Id: tstContiguous.cpp $ */ +/** @file + * SUP Testcase - Contiguous Memory Interface (ring-3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/param.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <stdlib.h> +#include <string.h> + + +int main(int argc, char **argv) +{ + int rc; + int rcRet = 0; + + RTR3InitExe(argc, &argv, 0); + rc = SUPR3Init(NULL); + RTPrintf("tstContiguous: SUPR3Init -> rc=%Rrc\n", rc); + rcRet += rc != 0; + if (!rc) + { + /* + * Allocate a bit of contiguous memory. + */ + RTHCPHYS HCPhys; + void *pv = SUPR3ContAlloc(8, NULL, &HCPhys); + rcRet += pv == NULL || HCPhys == 0; + if (pv && HCPhys) + { + memset(pv, 0xff, PAGE_SIZE * 8); + pv = SUPR3ContAlloc(5, NULL, &HCPhys); + rcRet += pv == NULL || HCPhys == 0; + if (pv && HCPhys) + { + memset(pv, 0x7f, PAGE_SIZE * 5); + rc = SUPR3ContFree(pv, 5); + rcRet += rc != 0; + if (rc) + RTPrintf("tstContiguous: SUPR3ContFree failed! rc=%Rrc\n", rc); + + void *apv[128]; + for (unsigned i = 0; i < RT_ELEMENTS(apv); i++) + { + apv[i] = SUPR3ContAlloc(1 + (i % 11), NULL, &HCPhys); + if (!apv[i]) + { + RTPrintf("tstContiguous: i=%d: failed to allocate %d pages", i, 1 + (i % 11)); +#if defined(RT_ARCH_X86) && defined(RT_OS_LINUX) + /* With 32-bit address spaces it's sometimes difficult + * to find bigger chunks of contiguous memory */ + if (i % 11 > 7) + RTPrintf(" => ignoring (32-bit host)"); + else +#endif + rcRet++; + RTPrintf("\n"); + } + } + for (unsigned i = 0; i < RT_ELEMENTS(apv); i++) + if (apv[i]) + { + rc = SUPR3ContFree(apv[i], 1 + (i % 11)); + rcRet += rc != 0; + if (rc) + RTPrintf("tstContiguous: i=%d SUPR3ContFree failed! rc=%Rrc\n", i, rc); + } + } + else + RTPrintf("tstContiguous: SUPR3ContAlloc (2nd) failed!\n"); + } + else + RTPrintf("tstContiguous: SUPR3ContAlloc failed!\n"); + + rc = SUPR3Term(false /*fForced*/); + RTPrintf("tstContiguous: SUPR3Term -> rc=%Rrc\n", rc); + rcRet += rc != 0; + } + + return rcRet ? 1 : 0; +} diff --git a/src/VBox/HostDrivers/Support/testcase/tstGIP-2.cpp b/src/VBox/HostDrivers/Support/testcase/tstGIP-2.cpp new file mode 100644 index 00000000..a183ca27 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstGIP-2.cpp @@ -0,0 +1,356 @@ +/* $Id: tstGIP-2.cpp $ */ +/** @file + * SUP Testcase - Global Info Page interface (ring 3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <VBox/param.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/getopt.h> +#include <iprt/x86.h> + + +/** + * Entry point. + */ +extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + /* + * Parse args + */ + static const RTGETOPTDEF g_aOptions[] = + { + { "--iterations", 'i', RTGETOPT_REQ_INT32 }, + { "--hex", 'h', RTGETOPT_REQ_NOTHING }, + { "--decimal", 'd', RTGETOPT_REQ_NOTHING }, + { "--spin", 's', RTGETOPT_REQ_NOTHING }, + { "--reference", 'r', RTGETOPT_REQ_UINT64 }, /* reference value of CpuHz, display the + * CpuHz deviation in a separate column. */ + { "--notestmode", 't', RTGETOPT_REQ_NOTHING } /* don't run GIP in test-mode (atm, test-mode + * implies updating GIP CpuHz even when invariant) */ + }; + + bool fHex = true; + bool fSpin = false; + bool fCompat = true; + bool fTestMode = true; + int ch; + uint32_t cIterations = 40; + uint64_t uCpuHzRef = UINT64_MAX; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'i': + cIterations = ValueUnion.u32; + break; + + case 'd': + fHex = false; + break; + + case 'h': + fHex = true; + break; + + case 's': + fSpin = true; + break; + + case 'r': + uCpuHzRef = ValueUnion.u64; + break; + + case 't': + fTestMode = false; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Init + */ + PSUPDRVSESSION pSession = NIL_RTR0PTR; + int rc = SUPR3Init(&pSession); + if (RT_SUCCESS(rc)) + { + if (g_pSUPGlobalInfoPage) + { + uint64_t uCpuHzOverallDeviation = 0; + uint32_t cCpuHzNotCompat = 0; + int64_t iCpuHzMaxDeviation = 0; + int32_t cCpuHzOverallDevCnt = 0; + uint32_t cCpuHzChecked = 0; + + /* Pick current CpuHz as the reference if none was specified. */ + if (uCpuHzRef == UINT64_MAX) + uCpuHzRef = SUPGetCpuHzFromGip(g_pSUPGlobalInfoPage); + + if ( fTestMode + && g_pSUPGlobalInfoPage->u32Mode == SUPGIPMODE_INVARIANT_TSC) + SUPR3GipSetFlags(SUPGIP_FLAGS_TESTING_ENABLE, UINT32_MAX); + + RTPrintf("tstGIP-2: u32Mode=%d (%s) fTestMode=%RTbool u32Version=%#x fGetGipCpu=%#RX32 cPages=%#RX32\n", + g_pSUPGlobalInfoPage->u32Mode, + SUPGetGIPModeName(g_pSUPGlobalInfoPage), + fTestMode, + g_pSUPGlobalInfoPage->u32Version, + g_pSUPGlobalInfoPage->fGetGipCpu, + g_pSUPGlobalInfoPage->cPages); + RTPrintf("tstGIP-2: cCpus=%d cPossibleCpus=%d cPossibleCpuGroups=%d cPresentCpus=%d cOnlineCpus=%d idCpuMax=%#x\n", + g_pSUPGlobalInfoPage->cCpus, + g_pSUPGlobalInfoPage->cPossibleCpus, + g_pSUPGlobalInfoPage->cPossibleCpuGroups, + g_pSUPGlobalInfoPage->cPresentCpus, + g_pSUPGlobalInfoPage->cOnlineCpus, + g_pSUPGlobalInfoPage->idCpuMax); + RTPrintf("tstGIP-2: u32UpdateHz=%RU32 u32UpdateIntervalNS=%RU32 u64NanoTSLastUpdateHz=%RX64 u64CpuHz=%RU64 uCpuHzRef=%RU64\n", + g_pSUPGlobalInfoPage->u32UpdateHz, + g_pSUPGlobalInfoPage->u32UpdateIntervalNS, + g_pSUPGlobalInfoPage->u64NanoTSLastUpdateHz, + g_pSUPGlobalInfoPage->u64CpuHz, + uCpuHzRef); + for (uint32_t iCpu = 0; iCpu < g_pSUPGlobalInfoPage->cCpus; iCpu++) + if (g_pSUPGlobalInfoPage->aCPUs[iCpu].enmState != SUPGIPCPUSTATE_INVALID) + { + SUPGIPCPU const *pGipCpu = &g_pSUPGlobalInfoPage->aCPUs[iCpu]; + RTPrintf("tstGIP-2: aCPU[%3u]: enmState=%d iCpuSet=%-3u idCpu=%#010x iCpuGroup=%-2u iCpuGroupMember=%-3u idApic=%#06x\n", + iCpu, pGipCpu->enmState, pGipCpu->iCpuSet, pGipCpu->idCpu, pGipCpu->iCpuGroup, + pGipCpu->iCpuGroupMember, pGipCpu->idApic); + } + + RTPrintf(fHex + ? "tstGIP-2: it: u64NanoTS delta u64TSC UpIntTSC H TransId CpuHz %sTSC Interval History...\n" + : "tstGIP-2: it: u64NanoTS delta u64TSC UpIntTSC H TransId CpuHz %sTSC Interval History...\n", + uCpuHzRef ? " CpuHz deviation Compat " : ""); + static SUPGIPCPU s_aaCPUs[2][RTCPUSET_MAX_CPUS]; + for (uint32_t i = 0; i < cIterations; i++) + { + /* Copy the data. */ + memcpy(&s_aaCPUs[i & 1][0], &g_pSUPGlobalInfoPage->aCPUs[0], g_pSUPGlobalInfoPage->cCpus * sizeof(g_pSUPGlobalInfoPage->aCPUs[0])); + + /* Display it & find something to spin on. */ + uint32_t u32TransactionId = 0; + uint32_t volatile *pu32TransactionId = NULL; + for (unsigned iCpu = 0; iCpu < g_pSUPGlobalInfoPage->cCpus; iCpu++) + if (g_pSUPGlobalInfoPage->aCPUs[iCpu].enmState == SUPGIPCPUSTATE_ONLINE) + { + char szCpuHzDeviation[32]; + PSUPGIPCPU pPrevCpu = &s_aaCPUs[!(i & 1)][iCpu]; + PSUPGIPCPU pCpu = &s_aaCPUs[i & 1][iCpu]; + if (uCpuHzRef) + { + /* Only CPU 0 is updated for invariant & sync modes, see supdrvGipUpdate(). */ + if ( iCpu == 0 + || g_pSUPGlobalInfoPage->u32Mode == SUPGIPMODE_ASYNC_TSC) + { + /* Wait until the history validation code takes effect. */ + if (pCpu->u32TransactionId > 23 + (8 * 2) + 1) + { + int64_t iCpuHzDeviation = pCpu->u64CpuHz - uCpuHzRef; + uint64_t uCpuHzDeviation = RT_ABS(iCpuHzDeviation); + bool fCurHzCompat = SUPIsTscFreqCompatibleEx(uCpuHzRef, pCpu->u64CpuHz, false /*fRelax*/); + if (uCpuHzDeviation <= 999999999) + { + if (RT_ABS(iCpuHzDeviation) > RT_ABS(iCpuHzMaxDeviation)) + iCpuHzMaxDeviation = iCpuHzDeviation; + uCpuHzOverallDeviation += uCpuHzDeviation; + cCpuHzOverallDevCnt++; + uint32_t uPct = (uint32_t)(uCpuHzDeviation * 100000 / uCpuHzRef + 5); + RTStrPrintf(szCpuHzDeviation, sizeof(szCpuHzDeviation), "%10RI64%3d.%02d%% %RTbool ", + iCpuHzDeviation, uPct / 1000, (uPct % 1000) / 10, fCurHzCompat); + } + else + { + RTStrPrintf(szCpuHzDeviation, sizeof(szCpuHzDeviation), "%17s %RTbool ", "?", + fCurHzCompat); + } + + if (!fCurHzCompat) + ++cCpuHzNotCompat; + fCompat &= fCurHzCompat; + ++cCpuHzChecked; + } + else + RTStrPrintf(szCpuHzDeviation, sizeof(szCpuHzDeviation), "%25s ", "priming"); + } + else + RTStrPrintf(szCpuHzDeviation, sizeof(szCpuHzDeviation), "%25s ", ""); + } + else + szCpuHzDeviation[0] = '\0'; + RTPrintf(fHex + ? "tstGIP-2: %4d/%d: %016llx %09llx %016llx %08x %d %08x %15llu %s%08x %08x %08x %08x %08x %08x %08x %08x (%d)\n" + : "tstGIP-2: %4d/%d: %016llu %09llu %016llu %010u %d %010u %15llu %s%08x %08x %08x %08x %08x %08x %08x %08x (%d)\n", + i, iCpu, + pCpu->u64NanoTS, + i ? pCpu->u64NanoTS - pPrevCpu->u64NanoTS : 0, + pCpu->u64TSC, + pCpu->u32UpdateIntervalTSC, + pCpu->iTSCHistoryHead, + pCpu->u32TransactionId, + pCpu->u64CpuHz, + szCpuHzDeviation, + pCpu->au32TSCHistory[0], + pCpu->au32TSCHistory[1], + pCpu->au32TSCHistory[2], + pCpu->au32TSCHistory[3], + pCpu->au32TSCHistory[4], + pCpu->au32TSCHistory[5], + pCpu->au32TSCHistory[6], + pCpu->au32TSCHistory[7], + pCpu->cErrors); + if (!pu32TransactionId) + { + pu32TransactionId = &g_pSUPGlobalInfoPage->aCPUs[iCpu].u32TransactionId; + u32TransactionId = pCpu->u32TransactionId; + } + } + + /* Wait a bit / spin. */ + if (!fSpin) + RTThreadSleep(9); + else + { + if (pu32TransactionId) + { + uint32_t uTmp; + while ( u32TransactionId == (uTmp = *pu32TransactionId) + || (uTmp & 1)) + ASMNopPause(); + } + else + RTThreadSleep(1); + } + } + + /* + * Display TSC deltas. + * + * First iterative over the APIC ID array to get mostly consistent CPUID to APIC ID mapping. + * Then iterate over the offline CPUs. It is possible that there's a race between the online/offline + * states between the two iterations, but that cannot be helped from ring-3 anyway and not a biggie. + */ + RTPrintf("tstGIP-2: TSC deltas:\n"); + RTPrintf("tstGIP-2: idApic: i64TSCDelta\n"); + for (uint32_t i = 0; i < RT_ELEMENTS(g_pSUPGlobalInfoPage->aiCpuFromApicId); i++) + { + uint16_t iCpu = g_pSUPGlobalInfoPage->aiCpuFromApicId[i]; + if (iCpu != UINT16_MAX) + RTPrintf("tstGIP-2: %#7x: %6lld (grp=%#04x mbr=%#05x set=%d cpu=%#05x)\n", + g_pSUPGlobalInfoPage->aCPUs[iCpu].idApic, g_pSUPGlobalInfoPage->aCPUs[iCpu].i64TSCDelta, + g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuGroup, g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuGroupMember, + g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuSet, iCpu); + } + + for (uint32_t iCpu = 0; iCpu < g_pSUPGlobalInfoPage->cCpus; iCpu++) + if (g_pSUPGlobalInfoPage->aCPUs[iCpu].idApic == UINT16_MAX) + RTPrintf("tstGIP-2: offline: %6lld (grp=%#04x mbr=%#05x set=%d cpu=%#05x)\n", + g_pSUPGlobalInfoPage->aCPUs[iCpu].i64TSCDelta, g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuGroup, + g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuGroupMember, g_pSUPGlobalInfoPage->aCPUs[iCpu].iCpuSet, iCpu); + + RTPrintf("tstGIP-2: enmUseTscDelta=%d fGetGipCpu=%#x\n", + g_pSUPGlobalInfoPage->enmUseTscDelta, g_pSUPGlobalInfoPage->fGetGipCpu); + if (uCpuHzRef) + { + if (cCpuHzOverallDevCnt) + { + uint32_t uPct = (uint32_t)(uCpuHzOverallDeviation * 100000 / cCpuHzOverallDevCnt / uCpuHzRef + 5); + RTPrintf("tstGIP-2: Average CpuHz deviation: %d.%02d%%\n", + uPct / 1000, (uPct % 1000) / 10); + + uint32_t uMaxPct = (uint32_t)(RT_ABS(iCpuHzMaxDeviation) * 100000 / uCpuHzRef + 5); + RTPrintf("tstGIP-2: Maximum CpuHz deviation: %d.%02d%% (%RI64 ticks)\n", + uMaxPct / 1000, (uMaxPct % 1000) / 10, iCpuHzMaxDeviation); + } + else + { + RTPrintf("tstGIP-2: Average CpuHz deviation: ??.??\n"); + RTPrintf("tstGIP-2: Average CpuHz deviation: ??.??\n"); + } + + RTPrintf("tstGIP-2: CpuHz compatibility: %RTbool (incompatible %u of %u times w/ %RU64 Hz - %s GIP)\n", fCompat, + cCpuHzNotCompat, cCpuHzChecked, uCpuHzRef, SUPGetGIPModeName(g_pSUPGlobalInfoPage)); + + if ( !fCompat + && g_pSUPGlobalInfoPage->u32Mode == SUPGIPMODE_INVARIANT_TSC) + rc = -1; + } + + /* Disable GIP test mode. */ + if (fTestMode) + SUPR3GipSetFlags(0, ~SUPGIP_FLAGS_TESTING_ENABLE); + } + else + { + RTPrintf("tstGIP-2: g_pSUPGlobalInfoPage is NULL\n"); + rc = -1; + } + + SUPR3Term(false /*fForced*/); + } + else + RTPrintf("tstGIP-2: SUPR3Init failed: %Rrc\n", rc); + return !!rc; +} + +#if !defined(VBOX_WITH_HARDENING) || !defined(RT_OS_WINDOWS) +/** + * Main entry point. + */ +int main(int argc, char **argv) +{ + return TrustedMain(argc, argv); +} +#endif + diff --git a/src/VBox/HostDrivers/Support/testcase/tstGetPagingMode.cpp b/src/VBox/HostDrivers/Support/testcase/tstGetPagingMode.cpp new file mode 100644 index 00000000..c3d0a5ef --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstGetPagingMode.cpp @@ -0,0 +1,104 @@ +/* $Id: tstGetPagingMode.cpp $ */ +/** @file + * SUP Testcase - Host paging mode interface (ring 3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> + + +int main(int argc, char **argv) +{ + int rc; + RTR3InitExe(argc, &argv, 0); + rc = SUPR3Init(NULL); + if (RT_SUCCESS(rc)) + { + SUPPAGINGMODE enmMode = SUPR3GetPagingMode(); + switch (enmMode) + { + case SUPPAGINGMODE_INVALID: + RTPrintf("SUPPAGINGMODE_INVALID\n"); + break; + case SUPPAGINGMODE_32_BIT: + RTPrintf("SUPPAGINGMODE_32_BIT\n"); + break; + case SUPPAGINGMODE_32_BIT_GLOBAL: + RTPrintf("SUPPAGINGMODE_32_BIT_GLOBAL\n"); + break; + case SUPPAGINGMODE_PAE: + RTPrintf("SUPPAGINGMODE_PAE\n"); + break; + case SUPPAGINGMODE_PAE_GLOBAL: + RTPrintf("SUPPAGINGMODE_PAE_GLOBAL\n"); + break; + case SUPPAGINGMODE_PAE_NX: + RTPrintf("SUPPAGINGMODE_PAE_NX\n"); + break; + case SUPPAGINGMODE_PAE_GLOBAL_NX: + RTPrintf("SUPPAGINGMODE_PAE_GLOBAL_NX\n"); + break; + case SUPPAGINGMODE_AMD64: + RTPrintf("SUPPAGINGMODE_AMD64\n"); + break; + case SUPPAGINGMODE_AMD64_GLOBAL: + RTPrintf("SUPPAGINGMODE_AMD64_GLOBAL\n"); + break; + case SUPPAGINGMODE_AMD64_NX: + RTPrintf("SUPPAGINGMODE_AMD64_NX\n"); + break; + case SUPPAGINGMODE_AMD64_GLOBAL_NX: + RTPrintf("SUPPAGINGMODE_AMD64_GLOBAL_NX\n"); + break; + default: + RTPrintf("Unknown mode %d\n", enmMode); + rc = VERR_INTERNAL_ERROR; + break; + } + + int rc2 = SUPR3Term(false /*fForced*/); + RTPrintf("SUPR3Term -> rc=%Rrc\n", rc2); + } + else + RTPrintf("SUPR3Init -> rc=%Rrc\n", rc); + + return !RT_SUCCESS(rc); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstInit.cpp b/src/VBox/HostDrivers/Support/testcase/tstInit.cpp new file mode 100644 index 00000000..1a2a78f5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstInit.cpp @@ -0,0 +1,61 @@ +/* $Id: tstInit.cpp $ */ +/** @file + * SUP Testcase - Support Library initialization and termination. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> + + +int main(int argc, char **argv) +{ + int rc; + RTR3InitExe(argc, &argv, 0); + rc = SUPR3Init(NULL); + RTPrintf("tstInit: SUPR3Init -> rc=%Rrc\n", rc); + if (!rc) + { + rc = SUPR3Term(false /*fForced*/); + RTPrintf("tstInit: SUPR3Term -> rc=%Rrc\n", rc); + } + + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstInt.cpp b/src/VBox/HostDrivers/Support/testcase/tstInt.cpp new file mode 100644 index 00000000..094d5a3d --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstInt.cpp @@ -0,0 +1,240 @@ +/* $Id: tstInt.cpp $ */ +/** @file + * SUP Testcase - Test the interrupt gate feature of the support library. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/vmm/vmm.h> +#include <VBox/vmm/gvmm.h> +#include <VBox/vmm/vm.h> +#include <iprt/errcore.h> +#include <VBox/param.h> +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/asm-amd64-x86.h> +#else +# define ASMReadTSC RTTimeSystemNanoTS +#endif +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/alloc.h> +#include <iprt/time.h> +#include <iprt/path.h> + + +int main(int argc, char **argv) +{ + int rcRet = 0; + int i; + int rc; + int cIterations = argc > 1 ? RTStrToUInt32(argv[1]) : 32; + if (cIterations == 0) + cIterations = 64; + + /* + * Init. + */ + RTR3InitExe(argc, &argv, 0); + PSUPDRVSESSION pSession; + rc = SUPR3Init(&pSession); + rcRet += rc != 0; + RTPrintf("tstInt: SUPR3Init -> rc=%Rrc\n", rc); + char szFile[RTPATH_MAX]; + if (!rc) + { + rc = RTPathExecDir(szFile, sizeof(szFile) - sizeof("/VMMR0.r0")); + } + char szAbsFile[RTPATH_MAX]; + if (RT_SUCCESS(rc)) + { + strcat(szFile, "/VMMR0.r0"); + rc = RTPathAbs(szFile, szAbsFile, sizeof(szAbsFile)); + } + if (RT_SUCCESS(rc)) + { + /* + * Load VMM code. + */ + RTERRINFOSTATIC ErrInfo; + rc = SUPR3LoadVMM(szAbsFile, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + /* + * Create a tiny dummy VM so we can do NOP calls into it using the fast I/O control path. + */ + GVMMCREATEVMREQ CreateVMReq; + CreateVMReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + CreateVMReq.Hdr.cbReq = sizeof(CreateVMReq); + CreateVMReq.pSession = pSession; + CreateVMReq.pVMR0 = NIL_RTR0PTR; + CreateVMReq.pVMR3 = NULL; + CreateVMReq.cCpus = 1; + rc = SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, VMMR0_DO_GVMM_CREATE_VM, 0, &CreateVMReq.Hdr); + if (RT_SUCCESS(rc)) + { + PVM pVM = CreateVMReq.pVMR3; + AssertRelease(RT_VALID_PTR(pVM)); + AssertRelease(pVM->pVMR0ForCall == CreateVMReq.pVMR0); + AssertRelease(pVM->pSession == pSession); + AssertRelease(pVM->cCpus == 1); + pVM->enmVMState = VMSTATE_CREATED; + PVMR0 const pVMR0 = CreateVMReq.pVMR0; + + rc = SUPR3SetVMForFastIOCtl(pVMR0); + if (!rc) + { + /* + * Call VMM code with invalid function. + */ + for (i = cIterations; i > 0; i--) + { + rc = SUPR3CallVMMR0(pVMR0, NIL_VMCPUID, VMMR0_DO_SLOW_NOP, NULL); + if (rc != VINF_SUCCESS) + { + RTPrintf("tstInt: SUPR3CallVMMR0 -> rc=%Rrc i=%d Expected VINF_SUCCESS!\n", rc, i); + rcRet++; + break; + } + } + RTPrintf("tstInt: Performed SUPR3CallVMMR0 %d times (rc=%Rrc)\n", cIterations, rc); + + /* + * The fast path. + */ + if (rc == VINF_SUCCESS) + { + RTTimeNanoTS(); + uint64_t StartTS = RTTimeNanoTS(); + uint64_t StartTick = ASMReadTSC(); + uint64_t MinTicks = UINT64_MAX; + for (i = 0; i < 1000000; i++) + { + uint64_t OneStartTick = ASMReadTSC(); + rc = SUPR3CallVMMR0Fast(pVMR0, VMMR0_DO_NOP, 0); + uint64_t Ticks = ASMReadTSC() - OneStartTick; + if (Ticks < MinTicks) + MinTicks = Ticks; + + if (RT_UNLIKELY(rc != VINF_SUCCESS)) + { + RTPrintf("tstInt: SUPR3CallVMMR0Fast -> rc=%Rrc i=%d Expected VINF_SUCCESS!\n", rc, i); + rcRet++; + break; + } + } + uint64_t Ticks = ASMReadTSC() - StartTick; + uint64_t NanoSecs = RTTimeNanoTS() - StartTS; + + RTPrintf("tstInt: SUPR3CallVMMR0Fast - %d iterations in %llu ns / %llu ticks. %llu ns / %#llu ticks per iteration. Min %llu ticks.\n", + i, NanoSecs, Ticks, NanoSecs / i, Ticks / i, MinTicks); + + /* + * The ordinary path. + */ + RTTimeNanoTS(); + StartTS = RTTimeNanoTS(); + StartTick = ASMReadTSC(); + MinTicks = UINT64_MAX; + for (i = 0; i < 1000000; i++) + { + uint64_t OneStartTick = ASMReadTSC(); + rc = SUPR3CallVMMR0Ex(pVMR0, NIL_VMCPUID, VMMR0_DO_SLOW_NOP, 0, NULL); + uint64_t OneTicks = ASMReadTSC() - OneStartTick; + if (OneTicks < MinTicks) + MinTicks = OneTicks; + + if (RT_UNLIKELY(rc != VINF_SUCCESS)) + { + RTPrintf("tstInt: SUPR3CallVMMR0Ex -> rc=%Rrc i=%d Expected VINF_SUCCESS!\n", rc, i); + rcRet++; + break; + } + } + Ticks = ASMReadTSC() - StartTick; + NanoSecs = RTTimeNanoTS() - StartTS; + + RTPrintf("tstInt: SUPR3CallVMMR0Ex - %d iterations in %llu ns / %llu ticks. %llu ns / %#llu ticks per iteration. Min %llu ticks.\n", + i, NanoSecs, Ticks, NanoSecs / i, Ticks / i, MinTicks); + } + } + else + { + RTPrintf("tstInt: SUPR3SetVMForFastIOCtl failed: %Rrc\n", rc); + rcRet++; + } + + rc = SUPR3CallVMMR0Ex(pVMR0, 0 /*idCpu*/, VMMR0_DO_GVMM_DESTROY_VM, 0, NULL); + if (RT_FAILURE(rc)) + { + RTPrintf("tstInt: VMMR0_DO_GVMM_DESTROY_VM failed: %Rrc\n", rc); + rcRet++; + } + } + else + { + RTPrintf("tstInt: VMMR0_DO_GVMM_CREATE_VM failed\n"); + rcRet++; + } + + /* + * Unload VMM. + */ + rc = SUPR3UnloadVMM(); + if (rc) + { + RTPrintf("tstInt: SUPR3UnloadVMM failed with rc=%Rrc\n", rc); + rcRet++; + } + } + else + { + RTPrintf("tstInt: SUPR3LoadVMM failed with rc=%Rrc%#RTeim\n", rc, &ErrInfo.Core); + rcRet++; + } + + /* + * Terminate. + */ + rc = SUPR3Term(false /*fForced*/); + rcRet += rc != 0; + RTPrintf("tstInt: SUPR3Term -> rc=%Rrc\n", rc); + } + + return !!rc; +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstLow.cpp b/src/VBox/HostDrivers/Support/testcase/tstLow.cpp new file mode 100644 index 00000000..032accdb --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstLow.cpp @@ -0,0 +1,164 @@ +/* $Id: tstLow.cpp $ */ +/** @file + * SUP Testcase - Low (<4GB) Memory Allocate interface (ring 3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/param.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/string.h> + + +int main(int argc, char **argv) +{ + int rc; + int rcRet = 0; + + RTR3InitExe(argc, &argv, 0); + RTPrintf("tstLow: TESTING...\n"); + + rc = SUPR3Init(NULL); + if (RT_SUCCESS(rc)) + { + /* + * Allocate a bit of contiguous memory. + */ + SUPPAGE aPages0[128]; + void *pvPages0 = (void *)0x77777777; + memset(&aPages0[0], 0x8f, sizeof(aPages0)); + rc = SUPR3LowAlloc(RT_ELEMENTS(aPages0), &pvPages0, NULL, aPages0); + if (RT_SUCCESS(rc)) + { + /* check that the pages are below 4GB and valid. */ + for (unsigned iPage = 0; iPage < RT_ELEMENTS(aPages0); iPage++) + { + RTPrintf("%-4d: Phys=%RHp Reserved=%p\n", iPage, aPages0[iPage].Phys, aPages0[iPage].uReserved); + if (aPages0[iPage].uReserved != 0) + { + rcRet++; + RTPrintf("tstLow: error: aPages0[%d].uReserved=%#x expected 0!\n", iPage, aPages0[iPage].uReserved); + } + if ( aPages0[iPage].Phys >= _4G + || (aPages0[iPage].Phys & PAGE_OFFSET_MASK)) + { + rcRet++; + RTPrintf("tstLow: error: aPages0[%d].Phys=%RHp!\n", iPage, aPages0[iPage].Phys); + } + } + if (!rcRet) + { + for (unsigned iPage = 0; iPage < RT_ELEMENTS(aPages0); iPage++) + memset((char *)pvPages0 + iPage * PAGE_SIZE, iPage, PAGE_SIZE); + for (unsigned iPage = 0; iPage < RT_ELEMENTS(aPages0); iPage++) + for (uint8_t *pu8 = (uint8_t *)pvPages0 + iPage * PAGE_SIZE, *pu8End = pu8 + PAGE_SIZE; pu8 < pu8End; pu8++) + if (*pu8 != (uint8_t)iPage) + { + RTPrintf("tstLow: error: invalid page content %02x != %02x. iPage=%u off=%#x\n", + *pu8, (uint8_t)iPage, iPage, (uintptr_t)pu8 & PAGE_OFFSET_MASK); + rcRet++; + } + } + SUPR3LowFree(pvPages0, RT_ELEMENTS(aPages0)); + } + else + { + RTPrintf("SUPR3LowAlloc(%d,,) failed -> rc=%Rrc\n", RT_ELEMENTS(aPages0), rc); + rcRet++; + } + + /* + * Allocate odd amounts in from 1 to 127. + */ + for (unsigned cPages = 1; cPages <= 127; cPages++) + { + SUPPAGE aPages1[128]; + void *pvPages1 = (void *)0x77777777; + memset(&aPages1[0], 0x8f, sizeof(aPages1)); + rc = SUPR3LowAlloc(cPages, &pvPages1, NULL, aPages1); + if (RT_SUCCESS(rc)) + { + /* check that the pages are below 4GB and valid. */ + for (unsigned iPage = 0; iPage < cPages; iPage++) + { + RTPrintf("%-4d::%-4d: Phys=%RHp Reserved=%p\n", cPages, iPage, aPages1[iPage].Phys, aPages1[iPage].uReserved); + if (aPages1[iPage].uReserved != 0) + { + rcRet++; + RTPrintf("tstLow: error: aPages1[%d].uReserved=%#x expected 0!\n", iPage, aPages1[iPage].uReserved); + } + if ( aPages1[iPage].Phys >= _4G + || (aPages1[iPage].Phys & PAGE_OFFSET_MASK)) + { + rcRet++; + RTPrintf("tstLow: error: aPages1[%d].Phys=%RHp!\n", iPage, aPages1[iPage].Phys); + } + } + if (!rcRet) + { + for (unsigned iPage = 0; iPage < cPages; iPage++) + memset((char *)pvPages1 + iPage * PAGE_SIZE, iPage, PAGE_SIZE); + for (unsigned iPage = 0; iPage < cPages; iPage++) + for (uint8_t *pu8 = (uint8_t *)pvPages1 + iPage * PAGE_SIZE, *pu8End = pu8 + PAGE_SIZE; pu8 < pu8End; pu8++) + if (*pu8 != (uint8_t)iPage) + { + RTPrintf("tstLow: error: invalid page content %02x != %02x. iPage=%p off=%#x\n", + *pu8, (uint8_t)iPage, iPage, (uintptr_t)pu8 & PAGE_OFFSET_MASK); + rcRet++; + } + } + SUPR3LowFree(pvPages1, cPages); + } + else + { + RTPrintf("SUPR3LowAlloc(%d,,) failed -> rc=%Rrc\n", cPages, rc); + rcRet++; + } + } + + } + else + { + RTPrintf("SUPR3Init -> rc=%Rrc\n", rc); + rcRet++; + } + + + return rcRet; +} diff --git a/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp b/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp new file mode 100644 index 00000000..9f181afd --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp @@ -0,0 +1,440 @@ +/* $Id: tstNtQueryStuff.cpp $ */ +/** @file + * SUP Testcase - Exploring some NT Query APIs. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> +#include <iprt/test.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct FLAGDESC +{ + ULONG f; + const char *psz; +} FLAGDESC; +typedef const FLAGDESC *PCFLAGDESC; + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; +static HANDLE g_hProcess = NULL; + + +static char *stringifyAppend(char *pszBuf, size_t *pcbBuf, const char *pszAppend, bool fWithSpace) +{ + size_t cchAppend = strlen(pszAppend); + if (cchAppend + 1 + fWithSpace <= *pcbBuf) + { + if (fWithSpace) + { + *pszBuf++ = ' '; + *pcbBuf += 1; + } + memcpy(pszBuf, pszAppend, cchAppend + 1); + *pcbBuf -= cchAppend; + pszBuf += cchAppend; + } + + return pszBuf; +} + + +static char *stringifyAppendUnknownFlags(uint32_t fFlags, char *pszBuf, size_t *pcbBuf, bool fWithSpace) +{ + for (unsigned iBit = 0; iBit < 32; iBit++) + if (fFlags & RT_BIT_32(iBit)) + { + char szTmp[32]; /* lazy bird */ + RTStrPrintf(szTmp, sizeof(szTmp), "BIT(%d)", iBit); + pszBuf = stringifyAppend(pszBuf, pcbBuf, szTmp, fWithSpace); + fWithSpace = true; + } + + return pszBuf; +} + + +static char *stringifyFlags(uint32_t fFlags, char *pszBuf, size_t cbBuf, PCFLAGDESC paFlagDesc, size_t cFlagDesc) +{ + char *pszBufStart = pszBuf; + if (fFlags) + { + for (size_t i = 0; i < cFlagDesc; i++) + { + if (fFlags & paFlagDesc[i].f) + { + fFlags &= ~paFlagDesc[i].f; + pszBuf = stringifyAppend(pszBuf, &cbBuf, paFlagDesc[i].psz, pszBuf != pszBufStart); + } + } + + if (fFlags) + stringifyAppendUnknownFlags(fFlags, pszBuf, &cbBuf, pszBuf != pszBufStart); + } + else + { + pszBuf[0] = '0'; + pszBuf[1] = '\0'; + } + return pszBufStart; +} + + +static char *stringifyMemType(uint32_t fType, char *pszBuf, size_t cbBuf) +{ + static const FLAGDESC s_aMemTypes[] = + { + { MEM_PRIVATE, "PRIVATE" }, + { MEM_MAPPED, "MAPPED" }, + { MEM_IMAGE, "IMAGE" }, + }; + return stringifyFlags(fType, pszBuf, cbBuf, s_aMemTypes, RT_ELEMENTS(s_aMemTypes)); +} + + +static char *stringifyMemState(uint32_t fState, char *pszBuf, size_t cbBuf) +{ + static const FLAGDESC s_aMemStates[] = + { + { MEM_FREE, "FREE" }, + { MEM_COMMIT, "COMMIT" }, + { MEM_RESERVE, "RESERVE" }, + { MEM_DECOMMIT, "DECOMMMIT" }, + }; + return stringifyFlags(fState, pszBuf, cbBuf, s_aMemStates, RT_ELEMENTS(s_aMemStates)); +} + + +static char *stringifyMemProt(uint32_t fProt, char *pszBuf, size_t cbBuf) +{ + static const FLAGDESC s_aProtections[] = + { + { PAGE_NOACCESS, "NOACCESS" }, + { PAGE_READONLY, "READONLY" }, + { PAGE_READWRITE, "READWRITE" }, + { PAGE_WRITECOPY, "WRITECOPY" }, + { PAGE_EXECUTE, "EXECUTE" }, + { PAGE_EXECUTE_READ, "EXECUTE_READ" }, + { PAGE_EXECUTE_READWRITE, "EXECUTE_READWRITE" }, + { PAGE_EXECUTE_WRITECOPY, "EXECUTE_WRITECOPY" }, + { PAGE_GUARD, "GUARD" }, + { PAGE_NOCACHE, "NOCACHE" }, + { PAGE_WRITECOMBINE, "WRITECOMBINE" }, + + }; + return stringifyFlags(fProt, pszBuf, cbBuf, s_aProtections, RT_ELEMENTS(s_aProtections)); +} + + + +static void tstQueryVirtualMemory(void) +{ + RTTestISub("NtQueryVirtualMemory"); + + uintptr_t cbAdvance = 0; + uintptr_t uPtrWhere = 0; + for (;;) + { + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 }; + NTSTATUS rcNt = NtQueryVirtualMemory(g_hProcess, + (void const *)uPtrWhere, + MemoryBasicInformation, + &MemInfo, + sizeof(MemInfo), + &cbActual); + if (!NT_SUCCESS(rcNt)) + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "%p: rcNt=%#x\n", uPtrWhere, rcNt); + break; + } + + /* stringify the memory state. */ + char szMemType[1024]; + char szMemState[1024]; + char szMemProt[1024]; + char szAllocProt[1024]; + + if ( MemInfo.AllocationBase != NULL + && MemInfo.AllocationBase == MemInfo.BaseAddress + && MemInfo.Protect != MemInfo.AllocationProtect) + RTTestIPrintf(RTTESTLVL_ALWAYS, "\n"); + + RTTestIPrintf(RTTESTLVL_ALWAYS, "%p-%p %-8s %-8s %-12s", + MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, + stringifyMemType(MemInfo.Type, szMemType, sizeof(szMemType)), + stringifyMemState(MemInfo.State, szMemState, sizeof(szMemState)), + stringifyMemProt(MemInfo.Protect, szMemProt, sizeof(szMemProt)) + ); + if ((uintptr_t)MemInfo.AllocationBase != 0) + { + if (MemInfo.AllocationBase != MemInfo.BaseAddress) + RTTestIPrintf(RTTESTLVL_ALWAYS, " %p", MemInfo.AllocationBase); + else + RTTestIPrintf(RTTESTLVL_ALWAYS, " %s", stringifyMemProt(MemInfo.AllocationProtect, szAllocProt, sizeof(szAllocProt))); + } + RTTestIPrintf(RTTESTLVL_ALWAYS, "\n"); + + if ((uintptr_t)MemInfo.BaseAddress != uPtrWhere) + RTTestIPrintf(RTTESTLVL_ALWAYS, " !Warning! Queried %p got BaseAddress=%p!\n", + uPtrWhere, MemInfo.BaseAddress); + + /* Image or mapped, then try get a file name. */ + if (MemInfo.Type == MEM_IMAGE || MemInfo.Type == MEM_MAPPED) + { + union + { + MEMORY_SECTION_NAME Core; + WCHAR awcPadding[UNICODE_STRING_MAX_CHARS + (sizeof(UNICODE_STRING_MAX_CHARS) + 1) / sizeof(WCHAR)]; + } uBuf; + RT_ZERO(uBuf); + uBuf.Core.SectionFileName.Length = UNICODE_STRING_MAX_CHARS * 2; + uBuf.Core.SectionFileName.MaximumLength = UNICODE_STRING_MAX_CHARS * 2; + uBuf.Core.SectionFileName.Buffer = &uBuf.Core.NameBuffer[0]; + + cbActual = 0; + rcNt = NtQueryVirtualMemory(g_hProcess, + (void const *)uPtrWhere, + MemorySectionName, + &uBuf, + sizeof(uBuf), + &cbActual); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, " %.*ls\n", + uBuf.Core.SectionFileName.Length / 2, uBuf.Core.SectionFileName.Buffer); + else + { + RTTestIPrintf(RTTESTLVL_ALWAYS, "%p: MemorySectionName - rcNt=%#x\n", uPtrWhere, rcNt); + RTTESTI_CHECK(rcNt == STATUS_FILE_INVALID && MemInfo.Type == MEM_MAPPED); + } + } + + /* Advance. */ + cbAdvance = MemInfo.RegionSize; + //cbAdvance = 0; + if (uPtrWhere + cbAdvance <= uPtrWhere) + break; + uPtrWhere += MemInfo.RegionSize; + } +} + + +static void tstQueryInformationProcess(void) +{ + RTTestISub("NtQueryInformationProcess"); + + NTSTATUS rcNt; + + /* Basic info */ + PROCESS_BASIC_INFORMATION BasicInfo; + RT_ZERO(BasicInfo); + DWORD cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessBasicInformation, + &BasicInfo, sizeof(BasicInfo), &cbActual); + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "BasicInfo:\n" + " UniqueProcessId = %#x (%6d)\n" + " InheritedFromUniqueProcessId = %#x (%6d)\n" + " ExitStatus = %#x\n" + " PebBaseAddress = %p\n" + " AffinityMask = %#zx\n" + " BasePriority = %#zx\n" + , + BasicInfo.UniqueProcessId, BasicInfo.UniqueProcessId, + BasicInfo.InheritedFromUniqueProcessId, BasicInfo.InheritedFromUniqueProcessId, + BasicInfo.ExitStatus, + BasicInfo.PebBaseAddress, + BasicInfo.AffinityMask, + BasicInfo.BasePriority + ); + + /* Debugger present? */ + DWORD_PTR uPtr = ~(DWORD_PTR)0; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessDebugPort, + &uPtr, sizeof(uPtr), &cbActual); + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessDebugPort: %p\n", uPtr); + + /* Debug object handle, whatever that is... */ + uPtr = ~(DWORD_PTR)0; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessDebugObjectHandle, + &uPtr, sizeof(uPtr), &cbActual); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessDebugObjectHandle: %p\n", uPtr); + else if (rcNt == STATUS_PORT_NOT_SET) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessDebugObjectHandle: rcNt=%#x (STATUS_PORT_NOT_SET)\n", uPtr); + else + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + + /* 32-bit app on 64-bit host? */ + uPtr = ~(DWORD_PTR)0; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessWow64Information, + &uPtr, sizeof(uPtr), &cbActual); + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessWow64Information: %p\n", uPtr); + + /* Process image name (NT). */ + struct + { + UNICODE_STRING UniStr; + WCHAR awBuffer[UNICODE_STRING_MAX_CHARS]; + } StrBuf; + RT_ZERO(StrBuf); + StrBuf.UniStr.Length = UNICODE_STRING_MAX_CHARS * 2; + StrBuf.UniStr.MaximumLength = UNICODE_STRING_MAX_CHARS * 2; + StrBuf.UniStr.Buffer = &StrBuf.awBuffer[0]; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessImageFileName, + &StrBuf, sizeof(StrBuf), &cbActual); + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileName: len=%u\n %.*ls\n", + StrBuf.UniStr.Length, StrBuf.UniStr.Length, StrBuf.UniStr.Buffer); + + /* Process image name (Win32) - Not available on Windows 2003. */ + RT_ZERO(StrBuf); + StrBuf.UniStr.Length = UNICODE_STRING_MAX_CHARS * 2; + StrBuf.UniStr.MaximumLength = UNICODE_STRING_MAX_CHARS * 2; + StrBuf.UniStr.Buffer = &StrBuf.awBuffer[0]; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessImageFileNameWin32, + &StrBuf, sizeof(StrBuf), &cbActual); + if (rcNt != STATUS_INVALID_INFO_CLASS) + { + RTTESTI_CHECK_MSG(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileNameWin32: len=%u\n %.*ls\n", + StrBuf.UniStr.Length, StrBuf.UniStr.Length, StrBuf.UniStr.Buffer); + } + else + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileNameWin32: Not supported (STATUS_INVALID_INFO_CLASS).\n"); + + /* Process image mapping - Not available on Windows 2003. */ + uPtr = ~(DWORD_PTR)0; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessImageFileMapping, + &uPtr, sizeof(uPtr), &cbActual); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileMapping: %p\n", uPtr); + else if (rcNt == STATUS_OBJECT_TYPE_MISMATCH) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileMapping: rcNt=%#x (STATUS_OBJECT_TYPE_MISMATCH)\n", rcNt); + else if (rcNt == STATUS_INVALID_INFO_CLASS) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessImageFileMapping: Not supported (STATUS_INVALID_INFO_CLASS).\n"); + else + RTTestIFailed("ProcessImageFileMapping: rcNt=%#x\n", rcNt); + + + /* Handles. Broken for 64-bit input. */ + uint32_t u32 = UINT32_MAX; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessHandleCount, + &u32, sizeof(u32), &cbActual); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessHandleCount: %#x (%d)\n", u32, u32); + else + RTTestIFailed("ProcessHandleCount: rcNt=%#x\n", rcNt); + + /* Execute flags. */ +#if 0 /* fails... wrong process handle? */ + u32 = ~(DWORD_PTR)0; + cbActual = 0; + rcNt = NtQueryInformationProcess(g_hProcess, + ProcessExecuteFlags, + &u32, sizeof(u32), &cbActual); + if (NT_SUCCESS(rcNt)) + RTTestIPrintf(RTTESTLVL_ALWAYS, "ProcessExecuteFlags: %#p\n", u32); + else + RTTestIFailed("ProcessExecuteFlags: rcNt=%#x\n", rcNt); +#endif + + /** @todo ProcessImageInformation */ +} + + +int main(int argc, char **argv) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstNtQueryStuff", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + g_hProcess = GetCurrentProcess(); + if (argc >= 2 && argv[1][0] != '-') + { + const char *pszPid = argv[1]; + uint32_t idPid = RTStrToInt32(pszPid); + + 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/testcase/tstPage.cpp b/src/VBox/HostDrivers/Support/testcase/tstPage.cpp new file mode 100644 index 00000000..2104d56f --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstPage.cpp @@ -0,0 +1,101 @@ +/* $Id: tstPage.cpp $ */ +/** @file + * SUP Testcase - Page allocation interface (ring 3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/param.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <string.h> + + +int main(int argc, char **argv) +{ + int cErrors = 0; + + RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + int rc = SUPR3Init(NULL); + cErrors += rc != 0; + if (!rc) + { + void *pv; + rc = SUPR3PageAlloc(1, 0, &pv); + cErrors += rc != 0; + if (!rc) + { + memset(pv, 0xff, PAGE_SIZE); + rc = SUPR3PageFree(pv, 1); + cErrors += rc != 0; + if (rc) + RTPrintf("tstPage: SUPR3PageFree() failed rc=%Rrc\n", rc); + } + else + RTPrintf("tstPage: SUPR3PageAlloc(1,) failed rc=%Rrc\n", rc); + + /* + * Big chunk. + */ + rc = SUPR3PageAlloc(1023, 0, &pv); + cErrors += rc != 0; + if (!rc) + { + memset(pv, 0xfe, 1023 << PAGE_SHIFT); + rc = SUPR3PageFree(pv, 1023); + cErrors += rc != 0; + if (rc) + RTPrintf("tstPage: SUPR3PageFree() failed rc=%Rrc\n", rc); + } + else + RTPrintf("tstPage: SUPR3PageAlloc(1,) failed rc=%Rrc\n", rc); + + + //rc = SUPR3Term(); + cErrors += rc != 0; + if (rc) + RTPrintf("tstPage: SUPR3Term failed rc=%Rrc\n", rc); + } + else + RTPrintf("tstPage: SUPR3Init failed rc=%Rrc\n", rc); + + if (!cErrors) + RTPrintf("tstPage: SUCCESS\n"); + else + RTPrintf("tstPage: FAILURE - %d errors\n", cErrors); + return !!cErrors; +} diff --git a/src/VBox/HostDrivers/Support/testcase/tstPin.cpp b/src/VBox/HostDrivers/Support/testcase/tstPin.cpp new file mode 100644 index 00000000..33d7c131 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstPin.cpp @@ -0,0 +1,223 @@ +/* $Id: tstPin.cpp $ */ +/** @file + * SUP Testcase - Memory locking interface (ring 3). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <VBox/param.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/thread.h> +#include <iprt/string.h> + +#include "../SUPLibInternal.h" + + +int main(int argc, char **argv) +{ + int rc; + int rcRet = 0; + RTHCPHYS HCPhys; + + RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + rc = SUPR3Init(NULL); + RTPrintf("SUPR3Init -> rc=%d\n", rc); + rcRet += rc != 0; + if (!rc) + { + /* + * Simple test. + */ + void *pv; + rc = SUPR3PageAlloc(1, 0, &pv); + AssertRC(rc); + RTPrintf("pv=%p\n", pv); + SUPPAGE aPages[1]; + rc = supR3PageLock(pv, 1, &aPages[0]); + RTPrintf("rc=%d pv=%p aPages[0]=%RHp\n", rc, pv, aPages[0]); + RTThreadSleep(1500); +#if 0 + RTPrintf("Unlocking...\n"); + RTThreadSleep(250); + rc = SUPPageUnlock(pv); + RTPrintf("rc=%d\n", rc); + RTThreadSleep(1500); +#endif + + /* + * More extensive. + */ + static struct + { + void *pv; + void *pvAligned; + SUPPAGE aPages[16]; + } aPinnings[500]; + for (unsigned i = 0; i < sizeof(aPinnings) / sizeof(aPinnings[0]); i++) + { + aPinnings[i].pv = NULL; + SUPR3PageAlloc(0x10000 >> PAGE_SHIFT, 0, &aPinnings[i].pv); + aPinnings[i].pvAligned = RT_ALIGN_P(aPinnings[i].pv, PAGE_SIZE); + rc = supR3PageLock(aPinnings[i].pvAligned, 0xf000 >> PAGE_SHIFT, &aPinnings[i].aPages[0]); + if (!rc) + { + RTPrintf("i=%d: pvAligned=%p pv=%p:\n", i, aPinnings[i].pvAligned, aPinnings[i].pv); + memset(aPinnings[i].pv, 0xfa, 0x10000); + unsigned c4GPluss = 0; + for (unsigned j = 0; j < (0xf000 >> PAGE_SHIFT); j++) + if (aPinnings[i].aPages[j].Phys >= _4G) + { + RTPrintf("%2d: vrt=%p phys=%RHp\n", j, (char *)aPinnings[i].pvAligned + (j << PAGE_SHIFT), aPinnings[i].aPages[j].Phys); + c4GPluss++; + } + RTPrintf("i=%d: c4GPluss=%d\n", i, c4GPluss); + } + else + { + RTPrintf("SUPPageLock -> rc=%d\n", rc); + rcRet++; + SUPR3PageFree(aPinnings[i].pv, 0x10000 >> PAGE_SHIFT); + aPinnings[i].pv = aPinnings[i].pvAligned = NULL; + break; + } + } + + for (unsigned i = 0; i < sizeof(aPinnings) / sizeof(aPinnings[0]); i += 2) + { + if (aPinnings[i].pvAligned) + { + rc = supR3PageUnlock(aPinnings[i].pvAligned); + if (rc) + { + RTPrintf("SUPPageUnlock(%p) -> rc=%d\n", aPinnings[i].pvAligned, rc); + rcRet++; + } + memset(aPinnings[i].pv, 0xaf, 0x10000); + } + } + + for (unsigned i = 0; i < sizeof(aPinnings) / sizeof(aPinnings[0]); i += 2) + { + if (aPinnings[i].pv) + { + memset(aPinnings[i].pv, 0xcc, 0x10000); + SUPR3PageFree(aPinnings[i].pv, 0x10000 >> PAGE_SHIFT); + aPinnings[i].pv = NULL; + } + } + + +/* Support for allocating Ring-0 executable memory with contiguous physical backing isn't implemented on Solaris. */ +#if !defined(RT_OS_SOLARIS) + /* + * Allocate a bit of contiguous memory. + */ + pv = SUPR3ContAlloc(RT_ALIGN_Z(15003, PAGE_SIZE) >> PAGE_SHIFT, NULL, &HCPhys); + rcRet += pv == NULL || HCPhys == 0; + if (pv && HCPhys) + { + RTPrintf("SUPR3ContAlloc(15003) -> HCPhys=%llx pv=%p\n", HCPhys, pv); + void *pv0 = pv; + memset(pv0, 0xaf, 15003); + pv = SUPR3ContAlloc(RT_ALIGN_Z(12999, PAGE_SIZE) >> PAGE_SHIFT, NULL, &HCPhys); + rcRet += pv == NULL || HCPhys == 0; + if (pv && HCPhys) + { + RTPrintf("SUPR3ContAlloc(12999) -> HCPhys=%llx pv=%p\n", HCPhys, pv); + memset(pv, 0xbf, 12999); + rc = SUPR3ContFree(pv, RT_ALIGN_Z(12999, PAGE_SIZE) >> PAGE_SHIFT); + rcRet += rc != 0; + if (rc) + RTPrintf("SUPR3ContFree failed! rc=%d\n", rc); + } + else + RTPrintf("SUPR3ContAlloc (2nd) failed!\n"); + memset(pv0, 0xaf, 15003); + /* pv0 is intentionally not freed! */ + } + else + RTPrintf("SUPR3ContAlloc failed!\n"); +#endif + + /* + * Allocate a big chunk of virtual memory and then lock it. + */ + #define BIG_SIZE 72*1024*1024 + #define BIG_SIZEPP (BIG_SIZE + PAGE_SIZE) + pv = NULL; + SUPR3PageAlloc(BIG_SIZEPP >> PAGE_SHIFT, 0, &pv); + if (pv) + { + static SUPPAGE s_aPages[BIG_SIZE >> PAGE_SHIFT]; + void *pvAligned = RT_ALIGN_P(pv, PAGE_SIZE); + rc = supR3PageLock(pvAligned, BIG_SIZE >> PAGE_SHIFT, &s_aPages[0]); + if (!rc) + { + /* dump */ + RTPrintf("SUPPageLock(%p,%d,) succeeded!\n", pvAligned, BIG_SIZE); + memset(pv, 0x42, BIG_SIZEPP); + #if 0 + for (unsigned j = 0; j < (BIG_SIZE >> PAGE_SHIFT); j++) + RTPrintf("%2d: vrt=%p phys=%08x\n", j, (char *)pvAligned + (j << PAGE_SHIFT), (uintptr_t)s_aPages[j].pvPhys); + #endif + + /* unlock */ + rc = supR3PageUnlock(pvAligned); + if (rc) + { + RTPrintf("SUPPageUnlock(%p) -> rc=%d\n", pvAligned, rc); + rcRet++; + } + memset(pv, 0xcc, BIG_SIZEPP); + } + else + { + RTPrintf("SUPPageLock(%p) -> rc=%d\n", pvAligned, rc); + rcRet++; + } + SUPR3PageFree(pv, BIG_SIZEPP >> PAGE_SHIFT); + } + + rc = SUPR3Term(false /*fForced*/); + RTPrintf("SUPR3Term -> rc=%d\n", rc); + rcRet += rc != 0; + } + + return rcRet; +} diff --git a/src/VBox/HostDrivers/Support/testcase/tstSupLoadModule.cpp b/src/VBox/HostDrivers/Support/testcase/tstSupLoadModule.cpp new file mode 100644 index 00000000..844cc552 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstSupLoadModule.cpp @@ -0,0 +1,128 @@ +/* $Id: tstSupLoadModule.cpp $ */ +/** @file + * SUP Testcase - Test SUPR3LoadModule. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> + +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> + + +int main(int argc, char **argv) +{ + /* + * Init. + */ + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Process arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--keep", 'k', RTGETOPT_REQ_NOTHING }, + { "--no-keep", 'n', RTGETOPT_REQ_NOTHING }, + }; + + bool fKeepLoaded = false; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case VINF_GETOPT_NOT_OPTION: + { + void *pvImageBase; + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + rc = SUPR3LoadModule(ValueUnion.psz, RTPathFilename(ValueUnion.psz), &pvImageBase, &ErrInfo.Core); + if (RT_FAILURE(rc)) + { + RTMsgError("%Rrc when attempting to load '%s': %s\n", rc, ValueUnion.psz, ErrInfo.Core.pszMsg); + return 1; + } + RTPrintf("Loaded '%s' at %p\n", ValueUnion.psz, pvImageBase); + + if (!fKeepLoaded) + { + rc = SUPR3FreeModule(pvImageBase); + if (RT_FAILURE(rc)) + { + RTMsgError("%Rrc when attempting to load '%s'\n", rc, ValueUnion.psz); + return 1; + } + } + break; + } + + case 'k': + fKeepLoaded = true; + break; + + case 'n': + fKeepLoaded = false; + break; + + case 'h': + RTPrintf("%s [mod1 [mod2...]]\n", argv[0]); + return 1; + + case 'V': + RTPrintf("$Revision: 155244 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + return 0; +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstSupSem-Zombie.cpp b/src/VBox/HostDrivers/Support/testcase/tstSupSem-Zombie.cpp new file mode 100644 index 00000000..fff1795c --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstSupSem-Zombie.cpp @@ -0,0 +1,232 @@ +/* $Id: tstSupSem-Zombie.cpp $ */ +/** @file + * Support Library Testcase - Ring-3 Semaphore interface - Zombie bugs. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> + +#include <VBox/param.h> +#include <iprt/env.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> +#include <iprt/time.h> +#include <iprt/thread.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +static PSUPDRVSESSION g_pSession; +static RTTEST g_hTest; + + + +static DECLCALLBACK(int) tstSupSemSRETimed(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENT hEvent = (SUPSEMEVENT)pvUser; + RTThreadUserSignal(hSelf); + int rc = SUPSemEventWaitNoResume(g_pSession, hEvent, 120*1000); + AssertReleaseMsg(rc == VERR_INTERRUPTED, ("%Rrc\n", rc)); + return rc; +} + + +static DECLCALLBACK(int) tstSupSemMRETimed(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENTMULTI hEventMulti = (SUPSEMEVENTMULTI)pvUser; + RTThreadUserSignal(hSelf); + int rc = SUPSemEventMultiWaitNoResume(g_pSession, hEventMulti, 120*1000); + AssertReleaseMsg(rc == VERR_INTERRUPTED, ("%Rrc\n", rc)); + return rc; +} + + +static DECLCALLBACK(int) tstSupSemSREInf(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENT hEvent = (SUPSEMEVENT)pvUser; + RTThreadUserSignal(hSelf); + int rc = SUPSemEventWaitNoResume(g_pSession, hEvent, RT_INDEFINITE_WAIT); + AssertReleaseMsg(rc == VERR_INTERRUPTED, ("%Rrc\n", rc)); + return rc; +} + + +static DECLCALLBACK(int) tstSupSemMREInf(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENTMULTI hEventMulti = (SUPSEMEVENTMULTI)pvUser; + RTThreadUserSignal(hSelf); + int rc = SUPSemEventMultiWaitNoResume(g_pSession, hEventMulti, RT_INDEFINITE_WAIT); + AssertReleaseMsg(rc == VERR_INTERRUPTED, ("%Rrc\n", rc)); + return rc; +} + +static int mainChild(void) +{ + /* + * Init. + */ + int rc = RTR3InitExeNoArguments(RTR3INIT_FLAGS_TRY_SUPLIB); + if (RT_FAILURE(rc)) + { + RTPrintf("tstSupSem-Zombie-Child: fatal error: RTR3InitExeNoArguments failed with rc=%Rrc\n", rc); + return 1; + } + + RTTEST hTest; + rc = RTTestCreate("tstSupSem-Zombie-Child", &hTest); + if (RT_FAILURE(rc)) + { + RTPrintf("tstSupSem-Zombie-Child: fatal error: RTTestCreate failed with rc=%Rrc\n", rc); + return 1; + } + g_hTest = hTest; + + PSUPDRVSESSION pSession; + rc = SUPR3Init(&pSession); + if (RT_FAILURE(rc)) + { + RTTestFailed(hTest, "SUPR3Init failed with rc=%Rrc\n", rc); + return RTTestSummaryAndDestroy(hTest); + } + g_pSession = pSession; + + /* + * A semaphore of each kind and throw a bunch of threads on them. + */ + SUPSEMEVENT hEvent = NIL_SUPSEMEVENT; + RTTESTI_CHECK_RC(rc = SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + SUPSEMEVENTMULTI hEventMulti = NIL_SUPSEMEVENT; + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEventMulti), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + for (uint32_t cThreads = 0; cThreads < 5; cThreads++) + { + RTTHREAD hThread; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemSRETimed, (void *)hEvent, 0, RTTHREADTYPE_TIMER, 0 /*fFlags*/, "IntSRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemMRETimed, (void *)hEventMulti, 0, RTTHREADTYPE_TIMER, 0 /*fFlags*/, "IntMRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemSREInf, (void *)hEvent, 0, RTTHREADTYPE_TIMER, 0 /*fFlags*/, "IntSRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemMREInf, (void *)hEventMulti, 0, RTTHREADTYPE_TIMER, 0 /*fFlags*/, "IntMRE"), VINF_SUCCESS); + RTThreadSleep(2); + } + RTThreadSleep(50); + + /* + * This is where the test really starts... + */ + return 0; + } + } + + return RTTestSummaryAndDestroy(hTest); +} + + +/** + * The parent main routine. + * @param argv0 The executable name (or whatever). + */ +static int mainParent(const char *argv0) +{ + /* + * Init. + */ + RTTEST hTest; + int rc = RTTestInitAndCreate("tstSupSem-Zombie", &hTest); + if (rc) + return rc; + RTTestBanner(hTest); + + /* + * Spin of the child process which may or may not turn into a zombie + */ + for (uint32_t iPass = 0; iPass < 32; iPass++) + { + RTTestSubF(hTest, "Pass %u", iPass); + + RTPROCESS hProcess; + const char *apszArgs[3] = { argv0, "--child", NULL }; + RTTESTI_CHECK_RC_OK(rc = RTProcCreate(argv0, apszArgs, RTENV_DEFAULT, 0 /*fFlags*/, &hProcess)); + if (RT_SUCCESS(rc)) + { + /* + * Wait for 60 seconds then give up. + */ + RTPROCSTATUS Status; + uint64_t StartTS = RTTimeMilliTS(); + for (;;) + { + rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (RT_SUCCESS(rc)) + break; + uint64_t cElapsed = RTTimeMilliTS() - StartTS; + if (cElapsed > 60*1000) + break; + RTThreadSleep(cElapsed < 60 ? 30 : cElapsed < 200 ? 10 : 100); + } + RTTESTI_CHECK_RC_OK(rc); + if ( RT_SUCCESS(rc) + && ( Status.enmReason != RTPROCEXITREASON_NORMAL + || Status.iStatus != 0)) + { + RTTestIFailed("child %d (%#x) reason %d\n", Status.iStatus, Status.iStatus, Status.enmReason); + rc = VERR_PERMISSION_DENIED; + } + } + /* one zombie process is enough. */ + if (RT_FAILURE(rc)) + break; + } + + return RTTestSummaryAndDestroy(hTest); +} + + +int main(int argc, char **argv) +{ + if ( argc == 2 + && !strcmp(argv[1], "--child")) + return mainChild(); + return mainParent(argv[0]); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstSupSem.cpp b/src/VBox/HostDrivers/Support/testcase/tstSupSem.cpp new file mode 100644 index 00000000..3091d67a --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstSupSem.cpp @@ -0,0 +1,649 @@ +/* $Id: tstSupSem.cpp $ */ +/** @file + * Support Library Testcase - Ring-3 Semaphore interface. + */ + +/* + * Copyright (C) 2009-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> + +#include <VBox/param.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/stream.h> +#include <iprt/test.h> +#include <iprt/thread.h> +#include <iprt/process.h> +#include <iprt/env.h> +#include <iprt/string.h> +#include <iprt/time.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +static PSUPDRVSESSION g_pSession; +static RTTEST g_hTest; +static uint32_t g_cMillies; /* Used by the interruptible tests. */ + + + +static DECLCALLBACK(int) tstSupSemInterruptibleSRE(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENT hEvent = (SUPSEMEVENT)pvUser; + RTThreadUserSignal(hSelf); + return SUPSemEventWaitNoResume(g_pSession, hEvent, g_cMillies); +} + + +static DECLCALLBACK(int) tstSupSemInterruptibleMRE(RTTHREAD hSelf, void *pvUser) +{ + SUPSEMEVENTMULTI hEventMulti = (SUPSEMEVENTMULTI)pvUser; + RTThreadUserSignal(hSelf); + return SUPSemEventMultiWaitNoResume(g_pSession, hEventMulti, g_cMillies); +} + + +int main(int argc, char **argv) +{ + bool fSys = true; + bool fGip = false; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fGip = true; +#endif + + /* + * Init. + */ + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + if (argc == 2 && !strcmp(argv[1], "child")) + { + RTThreadSleep(300); + return 0; + } + + RTTEST hTest; + rc = RTTestCreate("tstSupSem", &hTest); + if (RT_FAILURE(rc)) + { + RTPrintf("tstSupSem: fatal error: RTTestCreate failed with rc=%Rrc\n", rc); + return 1; + } + g_hTest = hTest; + + PSUPDRVSESSION pSession; + rc = SUPR3Init(&pSession); + if (RT_FAILURE(rc)) + { + RTTestFailed(hTest, "SUPR3Init failed with rc=%Rrc\n", rc); + return RTTestSummaryAndDestroy(hTest); + } + g_pSession = pSession; + RTTestBanner(hTest); + + /* + * Basic API checks. + */ + RTTestSub(hTest, "Single Release Event (SRE) API"); + SUPSEMEVENT hEvent = NIL_SUPSEMEVENT; + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 0), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 2), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 8), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent,20), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 1), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 2), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 8), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 20), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent,1000),VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 0), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 2), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent, 8), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventWaitNoResume(pSession, hEvent,20), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, NIL_SUPSEMEVENT), VINF_SUCCESS); + + RTTestSub(hTest, "Multiple Release Event (MRE) API"); + SUPSEMEVENTMULTI hEventMulti = NIL_SUPSEMEVENT; + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 2), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 8), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti,20), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiSignal(pSession, hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 1), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 2), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 8), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti,20), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti,1000), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiSignal(pSession, hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiSignal(pSession, hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiReset(pSession, hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 1), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 2), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 8), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti,20), VERR_TIMEOUT); + RTTESTI_CHECK_RC(SUPSemEventMultiSignal(pSession, hEventMulti), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 0), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 1), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 2), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 8), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti, 20), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiWaitNoResume(pSession, hEventMulti,1000), VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEventMulti), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEventMulti), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, NIL_SUPSEMEVENTMULTI), VINF_SUCCESS); + +#if !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS) + RTTestSub(hTest, "SRE Interruptibility"); + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + g_cMillies = RT_INDEFINITE_WAIT; + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleSRE, (void *)hEvent, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntSRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadUserWait(hThread, 60*1000), VINF_SUCCESS); + RTThreadSleep(120); + RTThreadPoke(hThread); + int rcThread = VINF_SUCCESS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 60*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VERR_INTERRUPTED); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + g_cMillies = 120*1000; + hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleSRE, (void *)hEvent, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntSRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadUserWait(hThread, 60*1000), VINF_SUCCESS); + RTThreadSleep(120); + RTThreadPoke(hThread); + rcThread = VINF_SUCCESS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 60*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VERR_INTERRUPTED); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + + + RTTestSub(hTest, "MRE Interruptibility"); + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEventMulti), VINF_SUCCESS); + g_cMillies = RT_INDEFINITE_WAIT; + hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleMRE, (void *)hEventMulti, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntMRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadUserWait(hThread, 60*1000), VINF_SUCCESS); + RTThreadSleep(120); + RTThreadPoke(hThread); + rcThread = VINF_SUCCESS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 60*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VERR_INTERRUPTED); + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEventMulti), VINF_OBJECT_DESTROYED); + + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEventMulti), VINF_SUCCESS); + g_cMillies = 120*1000; + hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleMRE, (void *)hEventMulti, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntMRE"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadUserWait(hThread, 60*1000), VINF_SUCCESS); + RTThreadSleep(120); + RTThreadPoke(hThread); + rcThread = VINF_SUCCESS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 60*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VERR_INTERRUPTED); + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEventMulti), VINF_OBJECT_DESTROYED); + + /* + * Fork test. + * Spawn a thread waiting for an event, then spawn a new child process (of + * ourselves) and make sure that this does not alter the intended behaviour + * of our event semaphore implementation (see @bugref{5090}). + */ + RTTestSub(hTest, "SRE Process Spawn"); + hThread = NIL_RTTHREAD; + g_cMillies = 120*1000; + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleSRE, (void *)hEvent, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntSRE"), VINF_SUCCESS); + + const char *apszArgs[3] = { argv[0], "child", NULL }; + RTPROCESS Process = NIL_RTPROCESS; + RTThreadSleep(250); + RTTESTI_CHECK_RC(RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, 0, &Process), VINF_SUCCESS); + + RTThreadSleep(250); + RTTESTI_CHECK_RC(SUPSemEventSignal(pSession, hEvent), VINF_SUCCESS); + + rcThread = VERR_GENERAL_FAILURE; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 120*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS); + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + + + RTTestSub(hTest, "MRE Process Spawn"); + hThread = NIL_RTTHREAD; + g_cMillies = 120*1000; + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEvent), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstSupSemInterruptibleMRE, (void *)hEvent, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntSRE"), VINF_SUCCESS); + + RTTHREAD hThread2 = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread2, tstSupSemInterruptibleMRE, (void *)hEvent, 0, RTTHREADTYPE_TIMER, RTTHREADFLAGS_WAITABLE, "IntSRE"), VINF_SUCCESS); + + Process = NIL_RTPROCESS; + RTThreadSleep(250); + RTTESTI_CHECK_RC(RTProcCreate(apszArgs[0], apszArgs, RTENV_DEFAULT, 0, &Process), VINF_SUCCESS); + + RTThreadSleep(250); + RTTESTI_CHECK_RC(SUPSemEventMultiSignal(pSession, hEvent), VINF_SUCCESS); + + rcThread = VERR_GENERAL_FAILURE; + RTTESTI_CHECK_RC(RTThreadWait(hThread, 120*1000, &rcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS); + + int rcThread2 = VERR_GENERAL_FAILURE; + RTTESTI_CHECK_RC(RTThreadWait(hThread2, 120*1000, &rcThread2), VINF_SUCCESS); + RTTESTI_CHECK_RC(rcThread2, VINF_SUCCESS); + + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + +#endif /* !OS2 && !WINDOWS */ + + { + +#define LOOP_COUNT 20 + static unsigned const s_acMsIntervals[] = { 0, 1, 2, 3, 4, 8, 10, 16, 32 }; + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "SRE Timeout Accuracy (ms)"); + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acMsIntervals); i++) + { + uint64_t cMs = s_acMsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + int rcX = SUPSemEventWaitNoResume(pSession, hEvent, cMs); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cMs=%u", rcX, cLoops, cMs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%u ms min (clock=sys)", cMs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%u ms avg (clock=sys)", cMs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%u ms min (clock=gip)", cMs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%u ms avg (clock=gip)", cMs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "MRE Timeout Accuracy (ms)"); + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acMsIntervals); i++) + { + uint64_t cMs = s_acMsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + int rcX = SUPSemEventMultiWaitNoResume(pSession, hEvent, cMs); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cMs=%u", rcX, cLoops, cMs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%u ms min (clock=sys)", cMs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%u ms avg (clock=sys)", cMs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%u ms min (clock=gip)", cMs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%u ms avg (clock=gip)", cMs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + } + + { + static uint32_t const s_acNsIntervals[] = + { + 0, 1000, 5000, 15000, 30000, 50000, 100000, 250000, 500000, 750000, 900000, 1500000, 2200000 + }; + + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "SUPSemEventWaitNsRelIntr Accuracy"); + RTTestValueF(hTest, SUPSemEventGetResolution(pSession), RTTESTUNIT_NS, "SRE resolution"); + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acNsIntervals); i++) + { + uint64_t cNs = s_acNsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + int rcX = SUPSemEventWaitNsRelIntr(pSession, hEvent, cNs); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cNs=%u", rcX, cLoops, cNs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%'u ns min (clock=sys)", cNs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=sys)", cNs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%'u ns min (clock=gip)", cNs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=gip)", cNs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "SUPSemEventMultiWaitNsRelIntr Accuracy"); + RTTestValueF(hTest, SUPSemEventMultiGetResolution(pSession), RTTESTUNIT_NS, "MRE resolution"); + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acNsIntervals); i++) + { + uint64_t cNs = s_acNsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + int rcX = SUPSemEventMultiWaitNsRelIntr(pSession, hEvent, cNs); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cNs=%u", rcX, cLoops, cNs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%'u ns min (clock=sys)", cNs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=sys)", cNs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%'u ns min (clock=gip)", cNs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=gip)", cNs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "SUPSemEventWaitNsAbsIntr Accuracy"); + RTTestValueF(hTest, SUPSemEventGetResolution(pSession), RTTESTUNIT_NS, "MRE resolution"); + RTTESTI_CHECK_RC(SUPSemEventCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acNsIntervals); i++) + { + uint64_t cNs = s_acNsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + uint64_t uAbsDeadline = (fGip ? u64Start : u64StartSys) + cNs; + int rcX = SUPSemEventWaitNsAbsIntr(pSession, hEvent, uAbsDeadline); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cNs=%u", rcX, cLoops, cNs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%'u ns min (clock=sys)", cNs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=sys)", cNs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%'u ns min (clock=gip)", cNs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=gip)", cNs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + + + if (RTTestErrorCount(hTest) == 0) + { + RTTestSub(hTest, "SUPSemEventMultiWaitNsAbsIntr Accuracy"); + RTTestValueF(hTest, SUPSemEventMultiGetResolution(pSession), RTTESTUNIT_NS, "MRE resolution"); + RTTESTI_CHECK_RC(SUPSemEventMultiCreate(pSession, &hEvent), VINF_SUCCESS); + + uint32_t cInterrupted = 0; + for (unsigned i = 0; i < RT_ELEMENTS(s_acNsIntervals); i++) + { + uint64_t cNs = s_acNsIntervals[i]; + uint64_t cNsMinSys = UINT64_MAX; + uint64_t cNsMin = UINT64_MAX; + uint64_t cNsTotalSys= 0; + uint64_t cNsTotal = 0; + unsigned cLoops = 0; + while (cLoops < LOOP_COUNT) + { + uint64_t u64StartSys = RTTimeSystemNanoTS(); + uint64_t u64Start = RTTimeNanoTS(); + uint64_t uAbsDeadline = (fGip ? u64Start : u64StartSys) + cNs; + int rcX = SUPSemEventMultiWaitNsAbsIntr(pSession, hEvent, uAbsDeadline); + uint64_t cNsElapsedSys = RTTimeSystemNanoTS() - u64StartSys; + uint64_t cNsElapsed = RTTimeNanoTS() - u64Start; + + if (rcX == VERR_INTERRUPTED) + { + cInterrupted++; + continue; /* retry */ + } + if (rcX != VERR_TIMEOUT) + RTTestFailed(hTest, "%Rrc cLoops=%u cNs=%u", rcX, cLoops, cNs); + + if (cNsElapsedSys < cNsMinSys) + cNsMinSys = cNsElapsedSys; + if (cNsElapsed < cNsMin) + cNsMin = cNsElapsed; + cNsTotalSys += cNsElapsedSys; + cNsTotal += cNsElapsed; + cLoops++; + } + if (fSys) + { + RTTestValueF(hTest, cNsMinSys, RTTESTUNIT_NS, "%'u ns min (clock=sys)", cNs); + RTTestValueF(hTest, cNsTotalSys / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=sys)", cNs); + } + if (fGip) + { + RTTestValueF(hTest, cNsMin, RTTESTUNIT_NS, "%'u ns min (clock=gip)", cNs); + RTTestValueF(hTest, cNsTotal / cLoops, RTTESTUNIT_NS, "%'u ns avg (clock=gip)", cNs); + } + } + + RTTESTI_CHECK_RC(SUPSemEventMultiClose(pSession, hEvent), VINF_OBJECT_DESTROYED); + RTTestValueF(hTest, cInterrupted, RTTESTUNIT_OCCURRENCES, "VERR_INTERRUPTED returned"); + } + + } + + + /* + * Done. + */ + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstSupTscDelta.cpp b/src/VBox/HostDrivers/Support/testcase/tstSupTscDelta.cpp new file mode 100644 index 00000000..4fe0d8d4 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstSupTscDelta.cpp @@ -0,0 +1,235 @@ +/* $Id: tstSupTscDelta.cpp $ */ +/** @file + * SUP Testcase - Global Info Page TSC Delta Measurement Utility. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/getopt.h> +#include <iprt/test.h> +#include <iprt/thread.h> + + + +int main(int argc, char **argv) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitExAndCreate(argc, &argv, 0 /*fRtInit*/, "tstSupTscDelta", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + + /* + * Parse args + */ + static const RTGETOPTDEF g_aOptions[] = + { + { "--iterations", 'i', RTGETOPT_REQ_INT32 }, + { "--delay", 'd', RTGETOPT_REQ_INT32 }, + }; + + uint32_t cIterations = 0; /* Currently 0 so that it doesn't upset testing. */ + uint32_t cMsSleepBetweenIterations = 10; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'd': + cMsSleepBetweenIterations = ValueUnion.u32; + break; + case 'i': + cIterations = ValueUnion.u32; + break; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + if (!cIterations) + return RTTestSkipAndDestroy(hTest, "Nothing to do. The --iterations argument is 0 or not given."); + + /* + * Init + */ + PSUPDRVSESSION pSession = NIL_RTR0PTR; + int rc = SUPR3Init(&pSession); + if (RT_SUCCESS(rc)) + { + PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage; + if (pGip) + { + if (pGip->enmUseTscDelta < SUPGIPUSETSCDELTA_PRACTICALLY_ZERO) + return RTTestSkipAndDestroy(hTest, "No deltas to play with: enmUseTscDelta=%d\n", pGip->enmUseTscDelta); + + /* + * Init stats. + */ + struct + { + int64_t iLowest; + int64_t iHighest; + int64_t iTotal; + uint64_t uAbsMin; + uint64_t uAbsMax; + uint64_t uAbsTotal; + } aCpuStats[RTCPUSET_MAX_CPUS]; + RT_ZERO(aCpuStats); + for (uint32_t i = 0; i < pGip->cCpus; i++) + { + aCpuStats[i].iLowest = INT64_MAX; + aCpuStats[i].iHighest = INT64_MIN; + aCpuStats[i].uAbsMin = UINT64_MAX; + } + + /* + * Do the work. + */ + for (uint32_t iIteration = 0; ; iIteration++) + { + /* + * Display the current deltas and gather statistics. + */ + RTPrintf("tstSupTscDelta: Iteration #%u results:", iIteration); + for (uint32_t iCpu = 0; iCpu < pGip->cCpus; iCpu++) + { + int64_t iTscDelta = pGip->aCPUs[iCpu].i64TSCDelta; + + /* print */ + if ((iCpu % 4) == 0) + RTPrintf("\ntstSupTscDelta:"); + if (pGip->aCPUs[iCpu].enmState != SUPGIPCPUSTATE_ONLINE) + RTPrintf(" %02x: offline ", iCpu); + else if (iTscDelta != INT64_MAX) + RTPrintf(" %02x: %-12lld", iCpu, iTscDelta); + else + RTPrintf(" %02x: INT64_MAX ", iCpu); + + /* stats */ + if ( iTscDelta != INT64_MAX + && pGip->aCPUs[iCpu].enmState == SUPGIPCPUSTATE_ONLINE) + { + if (aCpuStats[iCpu].iLowest > iTscDelta) + aCpuStats[iCpu].iLowest = iTscDelta; + if (aCpuStats[iCpu].iHighest < iTscDelta) + aCpuStats[iCpu].iHighest = iTscDelta; + aCpuStats[iCpu].iTotal += iTscDelta; + + uint64_t uAbsTscDelta = iTscDelta >= 0 ? (uint64_t)iTscDelta : (uint64_t)-iTscDelta; + if (aCpuStats[iCpu].uAbsMin > uAbsTscDelta) + aCpuStats[iCpu].uAbsMin = uAbsTscDelta; + if (aCpuStats[iCpu].uAbsMax < uAbsTscDelta) + aCpuStats[iCpu].uAbsMax = uAbsTscDelta; + aCpuStats[iCpu].uAbsTotal += uAbsTscDelta; + } + } + if (((pGip->cCpus - 1) % 4) != 0) + RTPrintf("\n"); + + /* + * Done? + */ + if (iIteration + 1 >= cIterations) + break; + + /* + * Force a new measurement. + */ + RTThreadSleep(cMsSleepBetweenIterations); + for (uint32_t iCpu = 0; iCpu < pGip->cCpus; iCpu++) + if (pGip->aCPUs[iCpu].enmState == SUPGIPCPUSTATE_ONLINE) + { + rc = SUPR3TscDeltaMeasure(pGip->aCPUs[iCpu].idCpu, false /*fAsync*/, true /*fForce*/, 64, 16 /*ms*/); + if (RT_FAILURE(rc)) + RTTestFailed(hTest, "SUPR3TscDeltaMeasure failed on %#x: %Rrc", pGip->aCPUs[iCpu].idCpu, rc); + } + } + + /* + * Display statistics that we've gathered. + */ + RTPrintf("tstSupTscDelta: Results:\n"); + int64_t iLowest = INT64_MAX; + int64_t iHighest = INT64_MIN; + int64_t iTotal = 0; + uint32_t cTotal = 0; + for (uint32_t iCpu = 0; iCpu < pGip->cCpus; iCpu++) + { + if (pGip->aCPUs[iCpu].enmState != SUPGIPCPUSTATE_ONLINE) + RTPrintf("tstSupTscDelta: %02x: offline\n", iCpu); + else + { + RTPrintf("tstSupTscDelta: %02x: lowest=%-12lld highest=%-12lld average=%-12lld spread=%-12lld\n", + iCpu, + aCpuStats[iCpu].iLowest, + aCpuStats[iCpu].iHighest, + aCpuStats[iCpu].iTotal / cIterations, + aCpuStats[iCpu].iHighest - aCpuStats[iCpu].iLowest); + RTPrintf( "tstSupTscDelta: absmin=%-12llu absmax=%-12llu absavg=%-12llu idCpu=%#4x idApic=%#4x\n", + aCpuStats[iCpu].uAbsMin, + aCpuStats[iCpu].uAbsMax, + aCpuStats[iCpu].uAbsTotal / cIterations, + pGip->aCPUs[iCpu].idCpu, + pGip->aCPUs[iCpu].idApic); + if (iLowest > aCpuStats[iCpu].iLowest) + iLowest = aCpuStats[iCpu].iLowest; + if (iHighest < aCpuStats[iCpu].iHighest) + iHighest = aCpuStats[iCpu].iHighest; + iTotal += aCpuStats[iCpu].iHighest; + cTotal += cIterations; + } + } + RTPrintf("tstSupTscDelta: all: lowest=%-12lld highest=%-12lld average=%-12lld spread=%-12lld\n", + iLowest, iHighest, iTotal / cTotal, iHighest - iLowest); + } + else + RTTestFailed(hTest, "g_pSUPGlobalInfoPage is NULL"); + + SUPR3Term(false /*fForced*/); + } + else + RTTestFailed(hTest, "SUPR3Init failed: %Rrc", rc); + return RTTestSummaryAndDestroy(hTest); +} + diff --git a/src/VBox/HostDrivers/Support/testcase/tstSupVerify.cpp b/src/VBox/HostDrivers/Support/testcase/tstSupVerify.cpp new file mode 100644 index 00000000..b8f157d8 --- /dev/null +++ b/src/VBox/HostDrivers/Support/testcase/tstSupVerify.cpp @@ -0,0 +1,162 @@ +/* $Id: tstSupVerify.cpp $ */ +/** @file + * SUP Testcase - Test SUPR3HardenedVerifyPlugIn. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/sup.h> +#include <iprt/errcore.h> + +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> + + +//#define DYNAMIC +#ifdef DYNAMIC +# include <iprt/win/windows.h> + +# define DYNAMIC_IMPORTS() \ + ONE_IMPORT(RTR3InitExe); \ + ONE_IMPORT(RTMsgInitFailure); \ + ONE_IMPORT(RTGetOpt); \ + ONE_IMPORT(RTGetOptInit); \ + ONE_IMPORT(RTGetOptPrintError); \ + ONE_IMPORT(RTMsgError); \ + ONE_IMPORT(RTMsgErrorExit); \ + ONE_IMPORT(RTMsgInfo); \ + ONE_IMPORT(RTPrintf); \ + ONE_IMPORT(SUPR3HardenedVerifyInit); \ + ONE_IMPORT(SUPR3HardenedVerifyPlugIn) + +# define ONE_IMPORT(a_fnName) static decltype(a_fnName) *g_pfn##a_fnName +DYNAMIC_IMPORTS(); +# undef ONE_IMPORT + +static void resolve(void) +{ + HMODULE hmod = LoadLibrary("VBoxRT.dll"); + DWORD cbWritten = 0; + +# define ONE_IMPORT(a_fnName) do { \ + g_pfn##a_fnName = (decltype(a_fnName) *)GetProcAddress(hmod, #a_fnName); \ + if (!g_pfn##a_fnName) \ + WriteFile(GetStdHandle(STD_ERROR_HANDLE), RT_STR_TUPLE("Failed to resolve: " #a_fnName "\r\n"), &cbWritten, NULL); \ + } while (0) + DYNAMIC_IMPORTS(); +# undef ONE_IMPORT +} + +#define RTR3InitExe g_pfnRTR3InitExe +#define RTMsgInitFailure g_pfnRTMsgInitFailure +#define RTGetOpt g_pfnRTGetOpt +#define RTGetOptInit g_pfnRTGetOptInit +#define RTGetOptPrintError g_pfnRTGetOptPrintError +#define RTMsgError g_pfnRTMsgError +#define RTMsgErrorExit g_pfnRTMsgErrorExit +#define RTMsgInfo g_pfnRTMsgInfo +#define RTPrintf g_pfnRTPrintf +#define SUPR3HardenedVerifyInit g_pfnSUPR3HardenedVerifyInit +#define SUPR3HardenedVerifyPlugIn g_pfnSUPR3HardenedVerifyPlugIn + +#endif /* DYNAMIC */ + +int main(int argc, char **argv) +{ + /* + * Init. + */ +#ifdef DYNAMIC + resolve(); +#endif + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + rc = SUPR3HardenedVerifyInit(); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "SUPR3HardenedVerifyInit failed: %Rrc", rc); + + /* + * Process arguments. + */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--dummy", 'd', RTGETOPT_REQ_NOTHING }, + }; + + //bool fKeepLoaded = false; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case VINF_GETOPT_NOT_OPTION: + { + RTERRINFOSTATIC ErrInfo; + RTErrInfoInitStatic(&ErrInfo); + rc = SUPR3HardenedVerifyPlugIn(ValueUnion.psz, &ErrInfo.Core); + if (RT_SUCCESS(rc)) + RTMsgInfo("SUPR3HardenedVerifyPlugIn: %Rrc for '%s'\n", rc, ValueUnion.psz); + else + RTMsgError("SUPR3HardenedVerifyPlugIn: %Rrc for '%s' ErrInfo: %s\n", + rc, ValueUnion.psz, ErrInfo.Core.pszMsg); + break; + } + + case 'h': + RTPrintf("%s [dll1 [dll2...]]\n", argv[0]); + return 1; + + case 'V': + RTPrintf("$Revision: 155244 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + return 0; +} + diff --git a/src/VBox/HostDrivers/Support/win/Makefile.kup b/src/VBox/HostDrivers/Support/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/Makefile.kup diff --git a/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp new file mode 100644 index 00000000..369e68c0 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp @@ -0,0 +1,5719 @@ +/* $Id: SUPDrv-win.cpp $ */ +/** @file + * VBoxDrv - The VirtualBox Support Driver - Windows NT specifics. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef IPRT_NT_MAP_TO_ZW +# define IPRT_NT_MAP_TO_ZW +#endif +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../SUPDrvInternal.h" +#include <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/rand.h> +#include <iprt/semaphore.h> +#include <iprt/spinlock.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/x86.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 * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxDrv" +/** The Pool tag (VBox). */ +#define SUPDRV_NT_POOL_TAG 'xoBV' + +/** NT device name for user access. */ +#define DEVICE_NAME_NT_USR L"\\Device\\VBoxDrvU" +#ifdef VBOX_WITH_HARDENING +/** Macro for checking for deflecting calls to the stub device. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +/** Macro for checking for deflecting calls to the stub and error info + * devices. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub || (a_pDevObj) == g_pDevObjErrorInfo) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +#else +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) do {} while (0) +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) do {} while (0) +#endif + +/** Enables the fast I/O control code path. */ +#define VBOXDRV_WITH_FAST_IO + +/** Enables generating UID from NT SIDs so the GMM can share free memory + * among VMs running as the same user. */ +#define VBOXDRV_WITH_SID_TO_UID_MAPPING + +/* Missing if we're compiling against older WDKs. */ +#ifndef NonPagedPoolNx +# define NonPagedPoolNx ((POOL_TYPE)512) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** + * SID to User ID mapping. + * + * This is used to generate a RTUID value for a NT security identifier. + * Normally, the UID is the hash of the SID string, but due to collisions it may + * differ. See g_NtUserIdHashTree and g_NtUserIdUidTree. + */ +typedef struct SUPDRVNTUSERID +{ + /** Hash tree node, key: RTStrHash1 of szSid. */ + AVLLU32NODECORE HashCore; + /** UID three node, key: The UID. */ + AVLU32NODECORE UidCore; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The length of the SID string. */ + uint16_t cchSid; + /** The SID string for the user. */ + char szSid[RT_FLEXIBLE_ARRAY]; +} SUPDRVNTUSERID; +/** Pointer to a SID to UID mapping. */ +typedef SUPDRVNTUSERID *PSUPDRVNTUSERID; +#endif + +/** + * Device extension used by VBoxDrvU. + */ +typedef struct SUPDRVDEVEXTUSR +{ + /** Global cookie (same location as in SUPDRVDEVEXT, different value). */ + uint32_t u32Cookie; + /** Pointer to the main driver extension. */ + PSUPDRVDEVEXT pMainDrvExt; +} SUPDRVDEVEXTUSR; +AssertCompileMembersAtSameOffset(SUPDRVDEVEXT, u32Cookie, SUPDRVDEVEXTUSR, u32Cookie); +/** Pointer to the VBoxDrvU device extension. */ +typedef SUPDRVDEVEXTUSR *PSUPDRVDEVEXTUSR; +/** Value of SUPDRVDEVEXTUSR::u32Cookie. */ +#define SUPDRVDEVEXTUSR_COOKIE UINT32_C(0x12345678) + +/** Get the main device extension. */ +#define SUPDRVNT_GET_DEVEXT(pDevObj) \ + ( pDevObj != g_pDevObjUsr \ + ? (PSUPDRVDEVEXT)pDevObj->DeviceExtension \ + : ((PSUPDRVDEVEXTUSR)pDevObj->DeviceExtension)->pMainDrvExt ) + +#ifdef VBOX_WITH_HARDENING + +/** + * Device extension used by VBoxDrvStub. + */ +typedef struct SUPDRVDEVEXTSTUB +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTSTUB; +/** Pointer to the VBoxDrvStub device extension. */ +typedef SUPDRVDEVEXTSTUB *PSUPDRVDEVEXTSTUB; +/** Value of SUPDRVDEVEXTSTUB::Common.u32Cookie. */ +#define SUPDRVDEVEXTSTUB_COOKIE UINT32_C(0x90abcdef) + + +/** + * Device extension used by VBoxDrvErrorInfo. + */ +typedef struct SUPDRVDEVEXTERRORINFO +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTERRORINFO; +/** Pointer to the VBoxDrvErrorInfo device extension. */ +typedef SUPDRVDEVEXTERRORINFO *PSUPDRVDEVEXTERRORINFO; +/** Value of SUPDRVDEVEXTERRORINFO::Common.u32Cookie. */ +#define SUPDRVDEVEXTERRORINFO_COOKIE UINT32_C(0xBadC0ca0) + +/** + * Error info for a failed VBoxDrv or VBoxDrvStub open attempt. + */ +typedef struct SUPDRVNTERRORINFO +{ + /** The list entry (in g_ErrorInfoHead). */ + RTLISTNODE ListEntry; + /** The ID of the process this error info belongs to. */ + HANDLE hProcessId; + /** The ID of the thread owning this info. */ + HANDLE hThreadId; + /** Milliseconds createion timestamp (for cleaning up). */ + uint64_t uCreatedMsTs; + /** Number of bytes of valid info. */ + uint32_t cchErrorInfo; + /** The error info. */ + char szErrorInfo[16384 - sizeof(RTLISTNODE) - sizeof(HANDLE)*2 - sizeof(uint64_t) - sizeof(uint32_t) - 0x20]; +} SUPDRVNTERRORINFO; +/** Pointer to error info. */ +typedef SUPDRVNTERRORINFO *PSUPDRVNTERRORINFO; + + +/** + * The kind of process we're protecting. + */ +typedef enum SUPDRVNTPROTECTKIND +{ + kSupDrvNtProtectKind_Invalid = 0, + + /** Stub process protection while performing process verification. + * Next: StubSpawning (or free) */ + kSupDrvNtProtectKind_StubUnverified, + /** Stub process protection before it creates the VM process. + * Next: StubParent, StubDead. */ + kSupDrvNtProtectKind_StubSpawning, + /** Stub process protection while having a VM process as child. + * Next: StubDead */ + kSupDrvNtProtectKind_StubParent, + /** Dead stub process. */ + kSupDrvNtProtectKind_StubDead, + + /** Potential VM process. + * Next: VmProcessConfirmed, VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessUnconfirmed, + /** Confirmed VM process. + * Next: VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessConfirmed, + /** Dead VM process. */ + kSupDrvNtProtectKind_VmProcessDead, + + /** End of valid protection kinds. */ + kSupDrvNtProtectKind_End +} SUPDRVNTPROTECTKIND; + +/** + * A NT process protection structure. + */ +typedef struct SUPDRVNTPROTECT +{ + /** The AVL node core structure. The process ID is the pid. */ + AVLPVNODECORE AvlCore; + /** Magic value (SUPDRVNTPROTECT_MAGIC). */ + uint32_t volatile u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The kind of process we're protecting. */ + SUPDRVNTPROTECTKIND volatile enmProcessKind; + /** Whether this structure is in the tree. */ + bool fInTree : 1; + /** 7,: Hack to allow the supid themes service duplicate handle privileges to + * our process. */ + bool fThemesFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstThreadCreateHandle : 1; + /** 8.1: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fCsrssFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle duplicated by CSRSS + * during process creation. Only applicable to VmProcessUnconfirmed. On + * 32-bit systems we allow two as ZoneAlarm's system call hooks has been + * observed to do some seemingly unnecessary duplication work. */ + int32_t volatile cCsrssFirstProcessDuplicateHandle; + + /** The parent PID for VM processes, otherwise NULL. */ + HANDLE hParentPid; + /** The TID of the thread opening VBoxDrv or VBoxDrvStub, NULL if not opened. */ + HANDLE hOpenTid; + /** The PID of the CSRSS process associated with this process. */ + HANDLE hCsrssPid; + /** Pointer to the CSRSS process structure (referenced). */ + PEPROCESS pCsrssProcess; + /** State dependent data. */ + union + { + /** A stub process in the StubParent state will keep a reference to a child + * while it's in the VmProcessUnconfirmed state so that it can be cleaned up + * correctly if things doesn't work out. */ + struct SUPDRVNTPROTECT *pChild; + /** A process in the VmProcessUnconfirmed state will keep a weak + * reference to the parent's protection structure so it can clean up the pChild + * reference the parent has to it. */ + struct SUPDRVNTPROTECT *pParent; + } u; +} SUPDRVNTPROTECT; +/** Pointer to a NT process protection record. */ +typedef SUPDRVNTPROTECT *PSUPDRVNTPROTECT; +/** The SUPDRVNTPROTECT::u32Magic value (Robert A. Heinlein). */ +# define SUPDRVNTPROTECT_MAGIC UINT32_C(0x19070707) +/** The SUPDRVNTPROTECT::u32Magic value of a dead structure. */ +# define SUPDRVNTPROTECT_MAGIC_DEAD UINT32_C(0x19880508) + +/** Pointer to ObGetObjectType. */ +typedef POBJECT_TYPE (NTAPI *PFNOBGETOBJECTTYPE)(PVOID); +/** Pointer to ObRegisterCallbacks. */ +typedef NTSTATUS (NTAPI *PFNOBREGISTERCALLBACKS)(POB_CALLBACK_REGISTRATION, PVOID *); +/** Pointer to ObUnregisterCallbacks. */ +typedef VOID (NTAPI *PFNOBUNREGISTERCALLBACKS)(PVOID); +/** Pointer to PsSetCreateProcessNotifyRoutineEx. */ +typedef NTSTATUS (NTAPI *PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)(PCREATE_PROCESS_NOTIFY_ROUTINE_EX, BOOLEAN); +/** Pointer to PsReferenceProcessFilePointer. */ +typedef NTSTATUS (NTAPI *PFNPSREFERENCEPROCESSFILEPOINTER)(PEPROCESS, PFILE_OBJECT *); +/** Pointer to PsIsProtectedProcessLight. */ +typedef BOOLEAN (NTAPI *PFNPSISPROTECTEDPROCESSLIGHT)(PEPROCESS); +/** Pointer to ZwAlpcCreatePort. */ +typedef NTSTATUS (NTAPI *PFNZWALPCCREATEPORT)(PHANDLE, POBJECT_ATTRIBUTES, struct _ALPC_PORT_ATTRIBUTES *); + +#endif /* VBOX_WITH_HARDENINIG */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj); +static NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp); +#ifdef VBOXDRV_WITH_FAST_IO +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj); +#endif +static NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack); +static NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1, PVOID pArgument2); +static NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc); +#ifdef VBOX_WITH_HARDENING +static NTSTATUS supdrvNtProtectInit(void); +static void supdrvNtProtectTerm(void); +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, + SUPDRVNTPROTECTKIND enmProcessKind, bool fLink); +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect); +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid); +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect); +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect); + +static bool supdrvNtIsDebuggerAttached(void); +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId); + +#endif + + +/********************************************************************************************************************************* +* Exported Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The non-paged pool type to use, NonPagedPool or NonPagedPoolNx. */ +static POOL_TYPE g_enmNonPagedPoolType = NonPagedPool; +/** Pointer to the system device instance. */ +static PDEVICE_OBJECT g_pDevObjSys = NULL; +/** Pointer to the user device instance. */ +static PDEVICE_OBJECT g_pDevObjUsr = NULL; +#ifdef VBOXDRV_WITH_FAST_IO +/** Fast I/O dispatch table. */ +static FAST_IO_DISPATCH const g_VBoxDrvFastIoDispatch = +{ + /* .SizeOfFastIoDispatch = */ sizeof(g_VBoxDrvFastIoDispatch), + /* .FastIoCheckIfPossible = */ NULL, + /* .FastIoRead = */ NULL, + /* .FastIoWrite = */ NULL, + /* .FastIoQueryBasicInfo = */ NULL, + /* .FastIoQueryStandardInfo = */ NULL, + /* .FastIoLock = */ NULL, + /* .FastIoUnlockSingle = */ NULL, + /* .FastIoUnlockAll = */ NULL, + /* .FastIoUnlockAllByKey = */ NULL, + /* .FastIoDeviceControl = */ VBoxDrvNtFastIoDeviceControl, + /* .AcquireFileForNtCreateSection = */ NULL, + /* .ReleaseFileForNtCreateSection = */ NULL, + /* .FastIoDetachDevice = */ NULL, + /* .FastIoQueryNetworkOpenInfo = */ NULL, + /* .AcquireForModWrite = */ NULL, + /* .MdlRead = */ NULL, + /* .MdlReadComplete = */ NULL, + /* .PrepareMdlWrite = */ NULL, + /* .MdlWriteComplete = */ NULL, + /* .FastIoReadCompressed = */ NULL, + /* .FastIoWriteCompressed = */ NULL, + /* .MdlReadCompleteCompressed = */ NULL, + /* .MdlWriteCompleteCompressed = */ NULL, + /* .FastIoQueryOpen = */ NULL, + /* .ReleaseForModWrite = */ NULL, + /* .AcquireForCcFlush = */ NULL, + /* .ReleaseForCcFlush = */ NULL, +}; +#endif /* VBOXDRV_WITH_FAST_IO */ + +/** Default ZERO value. */ +static ULONG g_fOptDefaultZero = 0; +/** Registry values. + * We wrap these in a struct to ensure they are followed by a little zero + * padding in order to limit the chance of trouble on unpatched systems. */ +struct +{ + /** The ForceAsync registry value. */ + ULONG fOptForceAsyncTsc; + /** Padding. */ + uint64_t au64Padding[2]; +} g_Options = { FALSE, 0, 0 }; +/** Registry query table for RtlQueryRegistryValues. */ +static RTL_QUERY_REGISTRY_TABLE g_aRegValues[] = +{ + { + /* .QueryRoutine = */ NULL, + /* .Flags = */ RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, + /* .Name = */ L"ForceAsyncTsc", + /* .EntryContext = */ &g_Options.fOptForceAsyncTsc, + /* .DefaultType = */ (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, + /* .DefaultData = */ &g_fOptDefaultZero, + /* .DefaultLength = */ sizeof(g_fOptDefaultZero), + }, + { NULL, 0, NULL, NULL, 0, NULL, 0 } /* terminator entry. */ +}; + +/** Pointer to KeQueryMaximumGroupCount. */ +static PFNKEQUERYMAXIMUMGROUPCOUNT g_pfnKeQueryMaximumGroupCount = NULL; +/** Pointer to KeGetProcessorIndexFromNumber. */ +static PFNKEGETPROCESSORINDEXFROMNUMBER g_pfnKeGetProcessorIndexFromNumber = NULL; +/** Pointer to KeGetProcessorNumberFromIndex. */ +static PFNKEGETPROCESSORNUMBERFROMINDEX g_pfnKeGetProcessorNumberFromIndex = NULL; + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** Spinlock protecting g_NtUserIdHashTree and g_NtUserIdUidTree. */ +static RTSPINLOCK g_hNtUserIdLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTUSERID structures by hash value. */ +static PAVLLU32NODECORE g_NtUserIdHashTree = NULL; +/** AVL tree of SUPDRVNTUSERID structures by UID. */ +static PAVLU32NODECORE g_NtUserIdUidTree = NULL; +#endif + +#ifdef VBOX_WITH_HARDENING +/** Pointer to the stub device instance. */ +static PDEVICE_OBJECT g_pDevObjStub = NULL; +/** Spinlock protecting g_NtProtectTree as well as the releasing of protection + * structures. */ +static RTSPINLOCK g_hNtProtectLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTPROTECT structures. */ +static AVLPVTREE g_NtProtectTree = NULL; +/** Cookie returned by ObRegisterCallbacks for the callbacks. */ +static PVOID g_pvObCallbacksCookie = NULL; +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ +uint32_t g_uNtVerCombined = 0; +/** Pointer to ObGetObjectType if available.. */ +static PFNOBGETOBJECTTYPE g_pfnObGetObjectType = NULL; +/** Pointer to ObRegisterCallbacks if available.. */ +static PFNOBREGISTERCALLBACKS g_pfnObRegisterCallbacks = NULL; +/** Pointer to ObUnregisterCallbacks if available.. */ +static PFNOBUNREGISTERCALLBACKS g_pfnObUnRegisterCallbacks = NULL; +/** Pointer to PsSetCreateProcessNotifyRoutineEx if available.. */ +static PFNPSSETCREATEPROCESSNOTIFYROUTINEEX g_pfnPsSetCreateProcessNotifyRoutineEx = NULL; +/** Pointer to PsReferenceProcessFilePointer if available. */ +static PFNPSREFERENCEPROCESSFILEPOINTER g_pfnPsReferenceProcessFilePointer = NULL; +/** Pointer to PsIsProtectedProcessLight. */ +static PFNPSISPROTECTEDPROCESSLIGHT g_pfnPsIsProtectedProcessLight = NULL; +/** Pointer to ZwAlpcCreatePort. */ +static PFNZWALPCCREATEPORT g_pfnZwAlpcCreatePort = NULL; + +# ifdef RT_ARCH_AMD64 +extern "C" { +/** Pointer to KiServiceLinkage (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64). */ +PFNRT g_pfnKiServiceLinkage = NULL; +/** Pointer to KiServiceInternal (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64) */ +PFNRT g_pfnKiServiceInternal = NULL; +} +# endif +/** The primary ALPC port object type. (LpcPortObjectType at init time.) */ +static POBJECT_TYPE g_pAlpcPortObjectType1 = NULL; +/** The secondary ALPC port object type. (Sampled at runtime.) */ +static POBJECT_TYPE volatile g_pAlpcPortObjectType2 = NULL; + +/** Pointer to the error information device instance. */ +static PDEVICE_OBJECT g_pDevObjErrorInfo = NULL; +/** Fast mutex semaphore protecting the error info list. */ +static RTSEMMUTEX g_hErrorInfoLock = NIL_RTSEMMUTEX; +/** Head of the error info (SUPDRVNTERRORINFO). */ +static RTLISTANCHOR g_ErrorInfoHead; + +#endif + + +/** + * Takes care of creating the devices and their symbolic links. + * + * @returns NT status code. + * @param pDrvObj Pointer to driver object. + */ +static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) +{ + /* + * System device. + */ + UNICODE_STRING DevName; + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_SYS); + NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXT), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjSys); + if (NT_SUCCESS(rcNt)) + { + /* + * User device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_USR); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTUSR), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjUsr); + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtUsr->u32Cookie = SUPDRVDEVEXTUSR_COOKIE; + +#ifdef VBOX_WITH_HARDENING + /* + * Hardened stub device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_STUB); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTSTUB), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjStub); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtStub->Common.u32Cookie = SUPDRVDEVEXTSTUB_COOKIE; + + /* + * Hardened error information device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_ERROR_INFO); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTERRORINFO), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, + &g_pDevObjErrorInfo); + if (NT_SUCCESS(rcNt)) + { + g_pDevObjErrorInfo->Flags |= DO_BUFFERED_IO; + + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrInf = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrInf->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtErrInf->Common.u32Cookie = SUPDRVDEVEXTERRORINFO_COOKIE; + +#endif + /* Done. */ + return rcNt; +#ifdef VBOX_WITH_HARDENING + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + } + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjStub); + g_pDevObjUsr = NULL; + } + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; +#endif + } + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; + } + return rcNt; +} + +/** + * Destroys the devices and links created by vboxdrvNtCreateDevices. + */ +static void vboxdrvNtDestroyDevices(void) +{ + if (g_pDevObjUsr) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = NULL; + } +#ifdef VBOX_WITH_HARDENING + if (g_pDevObjStub) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = NULL; + } + if (g_pDevObjErrorInfo) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrorInfo = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrorInfo->Common.pMainDrvExt = NULL; + } +#endif + +#ifdef VBOX_WITH_HARDENING + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + IoDeleteDevice(g_pDevObjStub); + g_pDevObjStub = NULL; +#endif + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; +} + + +/** + * Driver entry point. + * + * @returns appropriate status code. + * @param pDrvObj Pointer to driver object. + * @param pRegPath Registry base path. + */ +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + RT_NOREF1(pRegPath); + + /* + * Sanity checks. + */ +#ifdef VBOXDRV_WITH_FAST_IO + if (g_VBoxDrvFastIoDispatch.FastIoDeviceControl != VBoxDrvNtFastIoDeviceControl) + { + DbgPrint("VBoxDrv: FastIoDeviceControl=%p instead of %p\n", + g_VBoxDrvFastIoDispatch.FastIoDeviceControl, VBoxDrvNtFastIoDeviceControl); + return STATUS_INTERNAL_ERROR; + } +#endif + + /* + * Figure out if we can use NonPagedPoolNx or not. + */ + ULONG ulMajorVersion, ulMinorVersion, ulBuildNumber; + PsGetVersion(&ulMajorVersion, &ulMinorVersion, &ulBuildNumber, NULL); + if (ulMajorVersion > 6 || (ulMajorVersion == 6 && ulMinorVersion >= 2)) /* >= 6.2 (W8)*/ + g_enmNonPagedPoolType = NonPagedPoolNx; + + /* + * Query options first so any overflows on unpatched machines will do less + * harm (see MS11-011 / 2393802 / 2011-03-18). + * + * Unfortunately, pRegPath isn't documented as zero terminated, even if it + * quite likely always is, so we have to make a copy here. + */ + NTSTATUS rcNt; + PWSTR pwszCopy = (PWSTR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, pRegPath->Length + sizeof(WCHAR), 'VBox'); + if (pwszCopy) + { + memcpy(pwszCopy, pRegPath->Buffer, pRegPath->Length); + pwszCopy[pRegPath->Length / sizeof(WCHAR)] = '\0'; + rcNt = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, pwszCopy, + g_aRegValues, NULL /*pvContext*/, NULL /*pvEnv*/); + ExFreePoolWithTag(pwszCopy, 'VBox'); + /* Probably safe to ignore rcNt here. */ + } + + /* + * Resolve methods we want but isn't available everywhere. + */ + UNICODE_STRING RoutineName; + RtlInitUnicodeString(&RoutineName, L"KeQueryMaximumGroupCount"); + g_pfnKeQueryMaximumGroupCount = (PFNKEQUERYMAXIMUMGROUPCOUNT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorIndexFromNumber"); + g_pfnKeGetProcessorIndexFromNumber = (PFNKEGETPROCESSORINDEXFROMNUMBER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorNumberFromIndex"); + g_pfnKeGetProcessorNumberFromIndex = (PFNKEGETPROCESSORNUMBERFROMINDEX)MmGetSystemRoutineAddress(&RoutineName); + + Assert( (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeGetProcessorIndexFromNumber != NULL) + && (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeQueryMaximumGroupCount != NULL)); /* all or nothing. */ + + /* + * Initialize the runtime (IPRT). + */ + int vrc = RTR0Init(0); + if (RT_SUCCESS(vrc)) + { + Log(("VBoxDrv::DriverEntry\n")); + +#ifdef VBOX_WITH_HARDENING + /* + * Initialize process protection. + */ + rcNt = supdrvNtProtectInit(); + if (NT_SUCCESS(rcNt)) +#endif + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + /* + * Create the spinlock for the SID -> UID mappings. + */ + vrc = RTSpinlockCreate(&g_hNtUserIdLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtUserId"); + if (RT_SUCCESS(vrc)) +#endif + { + /* + * Create device. + * (That means creating a device object and a symbolic link so the DOS + * subsystems (OS/2, win32, ++) can access the device.) + */ + rcNt = vboxdrvNtCreateDevices(pDrvObj); + if (NT_SUCCESS(rcNt)) + { + /* + * Initialize the device extension. + */ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); + + vrc = supdrvInitDevExt(pDevExt, sizeof(SUPDRVSESSION)); + if (!vrc) + { + /* + * Setup the driver entry points in pDrvObj. + */ + pDrvObj->DriverUnload = VBoxDrvNtUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxDrvNtCreate; + pDrvObj->MajorFunction[IRP_MJ_CLEANUP] = VBoxDrvNtCleanup; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxDrvNtClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxDrvNtDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxDrvNtInternalDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_READ] = VBoxDrvNtRead; + pDrvObj->MajorFunction[IRP_MJ_WRITE] = VBoxDrvNtNotSupportedStub; + +#ifdef VBOXDRV_WITH_FAST_IO + /* Fast I/O to speed up guest execution roundtrips. */ + pDrvObj->FastIoDispatch = (PFAST_IO_DISPATCH)&g_VBoxDrvFastIoDispatch; +#endif + + /* + * Register ourselves for power state changes. We don't + * currently care if this fails. + */ + UNICODE_STRING CallbackName; + RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); + + OBJECT_ATTRIBUTES Attr; + InitializeObjectAttributes(&Attr, &CallbackName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + rcNt = ExCreateCallback(&pDevExt->pObjPowerCallback, &Attr, TRUE, TRUE); + if (rcNt == STATUS_SUCCESS) + pDevExt->hPowerCallback = ExRegisterCallback(pDevExt->pObjPowerCallback, + VBoxPowerDispatchCallback, + g_pDevObjSys); + + /* + * Done! Returning success! + */ + Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); + return STATUS_SUCCESS; + } + + /* + * Failed. Clean up. + */ + Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + + vboxdrvNtDestroyDevices(); + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + else + rcNt = VBoxDrvNtErr2NtStatus(vrc); +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + } + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + } + else + { + Log(("RTR0Init failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + } + if (NT_SUCCESS(rcNt)) + rcNt = STATUS_INVALID_PARAMETER; + return rcNt; +} + + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + + Log(("VBoxDrvNtUnload at irql %d\n", KeGetCurrentIrql())); + + /* Clean up the power callback registration. */ + if (pDevExt->hPowerCallback) + ExUnregisterCallback(pDevExt->hPowerCallback); + if (pDevExt->pObjPowerCallback) + ObDereferenceObject(pDevExt->pObjPowerCallback); + + /* + * We ASSUME that it's not possible to unload a driver with open handles. + */ + supdrvDeleteDevExt(pDevExt); +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + vboxdrvNtDestroyDevices(); + + NOREF(pDrvObj); +} + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + +/** + * Worker for supdrvNtUserIdMakeForSession. + */ +static bool supdrvNtUserIdMakeUid(PSUPDRVNTUSERID pNtUserId) +{ + pNtUserId->UidCore.Key = pNtUserId->HashCore.Key; + for (uint32_t cTries = 0; cTries < _4K; cTries++) + { + bool fRc = RTAvlU32Insert(&g_NtUserIdUidTree, &pNtUserId->UidCore); + if (fRc) + return true; + pNtUserId->UidCore.Key += pNtUserId->cchSid | 1; + } + return false; +} + + +/** + * Try create a RTUID value for the session. + * + * @returns VBox status code. + * @param pSession The session to try set SUPDRVSESSION::Uid for. + */ +static int supdrvNtUserIdMakeForSession(PSUPDRVSESSION pSession) +{ + /* + * Get the current security context and query the User SID for it. + */ + SECURITY_SUBJECT_CONTEXT Ctx = { NULL, SecurityIdentification, NULL, NULL }; + SeCaptureSubjectContext(&Ctx); + + int rc; + TOKEN_USER *pTokenUser = NULL; + NTSTATUS rcNt = SeQueryInformationToken(SeQuerySubjectContextToken(&Ctx) /* or always PrimaryToken?*/, + TokenUser, (PVOID *)&pTokenUser); + if (NT_SUCCESS(rcNt)) + { + /* + * Convert the user SID to a string to make it easier to handle, then prepare + * a user ID entry for it as that way we can combine lookup and insertion and + * avoid needing to deal with races. + */ + UNICODE_STRING UniStr = RTNT_NULL_UNISTR(); + rcNt = RtlConvertSidToUnicodeString(&UniStr, pTokenUser->User.Sid, TRUE /*AllocateDesitnationString*/); + if (NT_SUCCESS(rcNt)) + { + size_t cchSid = 0; + rc = RTUtf16CalcUtf8LenEx(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &cchSid); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTUSERID const pNtUserIdNew = (PSUPDRVNTUSERID)RTMemAlloc(RT_UOFFSETOF_DYN(SUPDRVNTUSERID, szSid[cchSid + 1])); + if (pNtUserIdNew) + { + char *pszSid = pNtUserIdNew->szSid; + rc = RTUtf16ToUtf8Ex(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &pszSid, cchSid + 1, NULL); + if (RT_SUCCESS(rc)) + { + pNtUserIdNew->HashCore.Key = RTStrHash1(pNtUserIdNew->szSid); + pNtUserIdNew->cchSid = (uint16_t)cchSid; + pNtUserIdNew->cRefs = 1; + Log5Func(("pNtUserId=%p cchSid=%u hash=%#x '%s'\n", pNtUserIdNew, cchSid, pNtUserIdNew->HashCore.Key, pszSid)); + + /* + * Do the lookup / insert. + */ + RTSpinlockAcquire(g_hNtUserIdLock); + AssertCompileMemberOffset(SUPDRVNTUSERID, HashCore, 0); + PSUPDRVNTUSERID pNtUserId = (PSUPDRVNTUSERID)RTAvllU32Get(&g_NtUserIdHashTree, pNtUserIdNew->HashCore.Key); + if (pNtUserId) + { + /* Match the strings till we reach the end of the collision list. */ + PSUPDRVNTUSERID const pNtUserIdHead = pNtUserId; + while ( pNtUserId + && ( pNtUserId->cchSid != cchSid + || memcmp(pNtUserId->szSid, pNtUserId->szSid, cchSid) != 0)) + pNtUserId = (PSUPDRVNTUSERID)pNtUserId->HashCore.pList; + if (pNtUserId) + { + /* Found matching: Retain reference and free the new entry we prepared. */ + uint32_t const cRefs = ASMAtomicIncU32(&pNtUserId->cRefs); + Assert(cRefs < _16K); RT_NOREF(cRefs); + RTSpinlockRelease(g_hNtUserIdLock); + Log5Func(("Using %p / %#x instead\n", pNtUserId, pNtUserId->UidCore.Key)); + } + else + { + /* No match: Try insert prepared entry after the head node. */ + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + pNtUserIdNew->HashCore.pList = pNtUserIdHead->HashCore.pList; + pNtUserIdHead->HashCore.pList = &pNtUserIdNew->HashCore; + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("supdrvNtUserIdMakeForSession: failed to insert new\n")); + } + } + else + { + /* No matching hash: Try insert the prepared entry. */ + pNtUserIdNew->UidCore.Key = pNtUserIdNew->HashCore.Key; + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + RTAvllU32Insert(&g_NtUserIdHashTree, &pNtUserIdNew->HashCore); + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one, no conflict)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("failed to insert!! WTF!?!\n")); + } + + if (pNtUserId != pNtUserIdNew) + RTMemFree(pNtUserIdNew); + + /* + * Update the session info. + */ + pSession->pNtUserId = pNtUserId; + pSession->Uid = pNtUserId ? (RTUID)pNtUserId->UidCore.Key : NIL_RTUID; + } + else + RTMemFree(pNtUserIdNew); + } + else + rc = VERR_NO_MEMORY; + } + RtlFreeUnicodeString(&UniStr); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("RtlConvertSidToUnicodeString failed: %#x / %Rrc\n", rcNt, rc)); + } + ExFreePool(pTokenUser); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("SeQueryInformationToken failed: %#x / %Rrc\n", rcNt, rc)); + } + + SeReleaseSubjectContext(&Ctx); + return rc; +} + + +/** + * Releases a reference to @a pNtUserId. + * + * @param pNtUserId The NT user ID entry to release. + */ +static void supdrvNtUserIdRelease(PSUPDRVNTUSERID pNtUserId) +{ + if (pNtUserId) + { + uint32_t const cRefs = ASMAtomicDecU32(&pNtUserId->cRefs); + Log5Func(("%p / %#x: cRefs=%d\n", pNtUserId, pNtUserId->cRefs)); + Assert(cRefs < _8K); + if (cRefs == 0) + { + RTSpinlockAcquire(g_hNtUserIdLock); + if (pNtUserId->cRefs == 0) + { + PAVLLU32NODECORE pAssert1 = RTAvllU32RemoveNode(&g_NtUserIdHashTree, &pNtUserId->HashCore); + PAVLU32NODECORE pAssert2 = RTAvlU32Remove(&g_NtUserIdUidTree, pNtUserId->UidCore.Key); + + RTSpinlockRelease(g_hNtUserIdLock); + + Assert(pAssert1 == &pNtUserId->HashCore); + Assert(pAssert2 == &pNtUserId->UidCore); + RT_NOREF(pAssert1, pAssert2); + + RTMemFree(pNtUserId); + } + else + RTSpinlockRelease(g_hNtUserIdLock); + } + } +} + +#endif /* VBOXDRV_WITH_SID_TO_UID_MAPPING */ + +/** + * For simplifying request completion into a simple return statement, extended + * version. + * + * @returns rcNt + * @param rcNt The status code. + * @param uInfo Extra info value. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp) +{ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = uInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * For simplifying request completion into a simple return statement. + * + * @returns rcNt + * @param rcNt The status code. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp) +{ + return supdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp); +} + + +/** + * Create (i.e. Open) file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + + /* + * We are not remotely similar to a directory... + * (But this is possible.) + */ + if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) + return supdrvNtCompleteRequest(STATUS_NOT_A_DIRECTORY, pIrp); + + /* + * Don't create a session for kernel clients, they'll close the handle + * immediately and work with the file object via + * VBoxDrvNtInternalDeviceControl. The first request will be one to + * create a session. + */ + NTSTATUS rcNt; + if (pIrp->RequestorMode == KernelMode) + { + if (pDevObj == g_pDevObjSys) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + rcNt = STATUS_ACCESS_DENIED; + } +#ifdef VBOX_WITH_HARDENING + /* + * Anyone can open the error device. + */ + else if (pDevObj == g_pDevObjErrorInfo) + { + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } +#endif + else + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + /* + * Make sure no debuggers are attached to non-user processes. + */ + if ( pDevObj != g_pDevObjUsr + && supdrvNtIsDebuggerAttached()) + { + LogRel(("vboxdrv: Process %p is being debugged, access to vboxdrv / vboxdrvu declined.\n", + PsGetProcessId(PsGetCurrentProcess()))); + rcNt = STATUS_TRUST_FAILURE; + } + else +#endif + { + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_HARDENING + /* + * Access to the stub device is only granted to processes which + * passes verification. + * + * Note! The stub device has no need for a SUPDRVSESSION structure, + * so the it uses the SUPDRVNTPROTECT directly instead. + */ + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = NULL; + rc = supdrvNtProtectCreate(&pNtProtect, PsGetProcessId(PsGetCurrentProcess()), + kSupDrvNtProtectKind_StubUnverified, true /*fLink*/); + if (RT_SUCCESS(rc)) + { + rc = supdrvNtProtectFindAssociatedCsrss(pNtProtect); + if (RT_SUCCESS(rc)) + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + + supdrvNtProtectRelease(pNtProtect); + } + LogRel(("vboxdrv: Declined %p access to VBoxDrvStub: rc=%d\n", PsGetProcessId(PsGetCurrentProcess()), rc)); + } + /* + * Unrestricted access is only granted to a process in the + * VmProcessUnconfirmed state that checks out correctly and is + * allowed to transition to VmProcessConfirmed. Again, only one + * session per process. + */ + else if (pDevObj != g_pDevObjUsr) + { + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(PsGetCurrentProcess())); + if (pNtProtect) + { + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + /* Create a session. */ + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, + &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pSession->pNtProtect = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + + /* No second attempt. */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + LogRel(("vboxdrv: supdrvCreateSession failed for process %p: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + LogRel(("vboxdrv: Process %p failed process verification: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process (enmProcessKind=%d).\n", + PsGetProcessId(PsGetCurrentProcess()), pNtProtect->enmProcessKind)); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_2; + } + supdrvNtProtectRelease(pNtProtect); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process.\n", PsGetProcessId(PsGetCurrentProcess()))); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_1; + } + } + /* + * Call common code to create an unprivileged session. + */ + else + { + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, false /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pSession; /* Keeps reference. No race. */ + pSession->pNtProtect = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + } + +#else /* !VBOX_WITH_HARDENING */ + /* + * Call common code to create a session. + */ + pFileObj->FsContext = NULL; + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +# ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +# endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + } +#endif /* !VBOX_WITH_HARDENING */ + + /* bail out */ + rcNt = VBoxDrvNtErr2NtStatus(rc); + } + } + + Assert(!NT_SUCCESS(rcNt)); + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequest(rcNt, pIrp); /* Note. the IoStatus is completely ignored on error. */ +} + + +/** + * Clean up file handle entry point. + * + * This is called when the last handle reference is released, or something like + * that. In the case of IoGetDeviceObjectPointer, this is called as it closes + * the handle, however it will go on using the file object afterwards... + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +/** + * Close file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtClose: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +#ifdef VBOXDRV_WITH_FAST_IO +/** + * Fast I/O device control callback. + * + * This performs no buffering, neither on the way in or out. + * + * @returns TRUE if handled, FALSE if the normal I/O control routine should be + * called. + * @param pFileObj The file object. + * @param fWait Whether it's a blocking call + * @param pvInput The input buffer as specified by the user. + * @param cbInput The size of the input buffer. + * @param pvOutput The output buffer as specfied by the user. + * @param cbOutput The size of the output buffer. + * @param uCmd The I/O command/function being invoked. + * @param pIoStatus Where to return the status of the operation. + * @param pDevObj The device object.. + */ +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj) +{ + RT_NOREF1(fWait); + + /* + * Only the normal devices, not the stub or error info ones. + */ + if (pDevObj != g_pDevObjSys && pDevObj != g_pDevObjUsr) + { + pIoStatus->Status = STATUS_NOT_SUPPORTED; + pIoStatus->Information = 0; + return TRUE; + } + + /* + * Check the input a little bit and get a the session references. + */ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + if (!pSession) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + return TRUE; + } + + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + supdrvSessionRelease(pSession); + return TRUE; + } +#endif + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pvOutput/* VMCPU id */, + pDevExt, pSession); + pIoStatus->Status = RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; + pIoStatus->Information = 0; /* Could be used to pass rc if we liked. */ + supdrvSessionRelease(pSession); + return TRUE; + } + } + + /* + * The normal path. + */ + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtFastIoDeviceControl(%p): ioctl=%#x pvIn=%p cbIn=%#x pvOut=%p cbOut=%#x pSession=%p\n", + pDevExt, uCmd, pvInput, cbInput, pvOutput, cbOutput, pSession)); + +# ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(NULL)) +# endif + { + /* + * In this fast I/O device control path we have to do our own buffering. + */ + /* Verify that the I/O control function matches our pattern. */ + if ((uCmd & 0x3) == METHOD_BUFFERED) + { + /* Get the header so we can validate it a little bit against the + parameters before allocating any memory kernel for the reqest. */ + SUPREQHDR Hdr; + if (cbInput >= sizeof(Hdr) && cbOutput >= sizeof(Hdr)) + { + __try + { + RtlCopyMemory(&Hdr, pvInput, sizeof(Hdr)); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + } + } + else + { + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + rcNt = STATUS_INVALID_PARAMETER; + } + if (NT_SUCCESS(rcNt)) + { + /* Verify that the sizes in the request header are correct. */ + ULONG cbBuf = RT_MAX(cbInput, cbOutput); + if ( cbInput == Hdr.cbIn + && cbOutput == Hdr.cbOut + && cbBuf < _1M*16) + { + /* Allocate a buffer and copy all the input into it. */ + PSUPREQHDR pHdr = (PSUPREQHDR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, cbBuf, 'VBox'); + if (pHdr) + { + __try + { + RtlCopyMemory(pHdr, pvInput, cbInput); + if (cbInput < cbBuf) + RtlZeroMemory((uint8_t *)pHdr + cbInput, cbBuf - cbInput); + if (!memcmp(pHdr, &Hdr, sizeof(Hdr))) + rcNt = STATUS_SUCCESS; + else + rcNt = STATUS_INVALID_PARAMETER; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + if (NT_SUCCESS(rcNt)) + { + /* + * Now call the common code to do the real work. + */ + rc = supdrvIOCtl(uCmd, pDevExt, pSession, pHdr, cbBuf); + if (RT_SUCCESS(rc)) + { + /* + * Copy back the result. + */ + cbOut = pHdr->cbOut; + if (cbOut > cbOutput) + { + cbOut = cbOutput; + OSDBGPRINT(("VBoxDrvNtFastIoDeviceControl: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, uCmd)); + } + if (cbOut) + { + __try + { + RtlCopyMemory(pvOutput, pHdr, cbOut); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + } + else + rcNt = STATUS_SUCCESS; + } + else if (rc == VERR_INVALID_PARAMETER) + rcNt = STATUS_INVALID_PARAMETER; + else + rcNt = STATUS_NOT_SUPPORTED; + Log2(("VBoxDrvNtFastIoDeviceControl: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + Log(("VBoxDrvNtFastIoDeviceControl: Error reading %u bytes of user memory at %p (uCmd=%#x)\n", + cbInput, pvInput, uCmd)); + ExFreePoolWithTag(pHdr, 'VBox'); + } + else + rcNt = STATUS_NO_MEMORY; + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + uCmd, Hdr.cbIn, Hdr.cbOut, cbInput, cbOutput)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: not buffered request (%#x) - not supported\n", uCmd)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +# ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtFastIoDeviceControl: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +# endif + + /* complete the request. */ + pIoStatus->Status = rcNt; + pIoStatus->Information = cbOut; + supdrvSessionRelease(pSession); + return TRUE; /* handled. */ +} +#endif /* VBOXDRV_WITH_FAST_IO */ + + +/** + * Device I/O Control entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pStack->FileObject->FsContext); + + if (!RT_VALID_PTR(pSession)) + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + } +#endif + + ULONG uCmd = pStack->Parameters.DeviceIoControl.IoControlCode; + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, + pDevExt, pSession); + + /* Complete the I/O request. */ + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); + } + } + + return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack); +} + + +/** + * Worker for VBoxDrvNtDeviceControl that takes the slow IOCtl functions. + * + * @returns NT status code. + * + * @param pDevExt Device extension. + * @param pSession The session. + * @param pIrp Request packet. + * @param pStack The stack location containing the DeviceControl parameters. + */ +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack) +{ + NTSTATUS rcNt; + uint32_t cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + +#ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(pIrp)) +#endif + { + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify that the sizes in the request header are correct. */ + PSUPREQHDR pHdr = (PSUPREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut) + { + /* Zero extra output bytes to make sure we don't leak anything. */ + if (pHdr->cbIn < pHdr->cbOut) + RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn); + + /* + * Do the job. + */ + rc = supdrvIOCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr, + RT_MAX(pHdr->cbIn, pHdr->cbOut)); + if (!rc) + { + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cbOut; + if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength) + { + cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength; + OSDBGPRINT(("VBoxDrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode)); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + Log2(("VBoxDrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +#ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +#endif + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + supdrvSessionRelease(pSession); + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Internal Device I/O Control entry point, used for IDC. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack ? pStack->FileObject : NULL; + PSUPDRVSESSION pSession = pFileObj ? (PSUPDRVSESSION)pFileObj->FsContext : NULL; + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtInternalDeviceControl(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify the pDevExt in the session. */ + if ( pStack->Parameters.DeviceIoControl.IoControlCode != SUPDRV_IDC_REQ_CONNECT + ? RT_VALID_PTR(pSession) && pSession->pDevExt == pDevExt + : !pSession + ) + { + /* Verify that the size in the request header is correct. */ + PSUPDRVIDCREQHDR pHdr = (PSUPDRVIDCREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cb + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cb) + { + /* + * Call the generic code. + * + * Note! Connect and disconnect requires some extra attention + * in order to get the session handling right. + */ + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = NULL; + + rc = supdrvIDC(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr); + if (!rc) + { + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_CONNECT) + pFileObj->FsContext = ((PSUPDRVIDCREQCONNECT)pHdr)->u.Out.pSession; + + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cb; + } + else + { + rcNt = STATUS_INVALID_PARAMETER; + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = pSession; + } + Log2(("VBoxDrvNtInternalDeviceControl: returns %#x/rc=%#x\n", rcNt, rc)); + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cb : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + rcNt = STATUS_NOT_SUPPORTED; + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Implementation of the read major function for VBoxDrvErrorInfo. + * + * This is a stub function for the other devices. + * + * @returns NT status code. + * @param pDevObj The device object. + * @param pIrp The I/O request packet. + */ +NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtRead\n")); + RT_NOREF1(pDevObj); + + NTSTATUS rcNt; + pIrp->IoStatus.Information = 0; + +#ifdef VBOX_WITH_HARDENING + /* + * VBoxDrvErrorInfo? + */ + if (pDevObj == g_pDevObjErrorInfo) + { + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + if ( pStack + && (pIrp->Flags & IRP_BUFFERED_IO)) + { + /* + * Look up the process error information. + */ + HANDLE hCurThreadId = PsGetCurrentThreadId(); + HANDLE hCurProcessId = PsGetCurrentProcessId(); + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pMatch = NULL; + PSUPDRVNTERRORINFO pCur; + RTListForEach(&g_ErrorInfoHead, pCur, SUPDRVNTERRORINFO, ListEntry) + { + if ( pCur->hProcessId == hCurProcessId + && pCur->hThreadId == hCurThreadId) + { + pMatch = pCur; + break; + } + } + + /* + * Did we find error info and is the caller requesting data within it? + * If so, check the destination buffer and copy the data into it. + */ + if ( pMatch + && pStack->Parameters.Read.ByteOffset.QuadPart < pMatch->cchErrorInfo + && pStack->Parameters.Read.ByteOffset.QuadPart >= 0) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if (pvDstBuf) + { + uint32_t offRead = (uint32_t)pStack->Parameters.Read.ByteOffset.QuadPart; + uint32_t cbToRead = pMatch->cchErrorInfo - offRead; + if (cbToRead < pStack->Parameters.Read.Length) + RT_BZERO((uint8_t *)pvDstBuf + cbToRead, pStack->Parameters.Read.Length - cbToRead); + else + cbToRead = pStack->Parameters.Read.Length; + memcpy(pvDstBuf, &pMatch->szErrorInfo[offRead], cbToRead); + pIrp->IoStatus.Information = cbToRead; + + rcNt = STATUS_SUCCESS; + } + else + rcNt = STATUS_INVALID_ADDRESS; + } + /* + * End of file. Free the info. + */ + else if (pMatch) + { + RTListNodeRemove(&pMatch->ListEntry); + RTMemFree(pMatch); + rcNt = STATUS_END_OF_FILE; + } + /* + * We found no error info. Return EOF. + */ + else + rcNt = STATUS_END_OF_FILE; + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + rcNt = STATUS_UNSUCCESSFUL; + + /* Paranoia: Clear the buffer on failure. */ + if (!NT_SUCCESS(rcNt)) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if ( pvDstBuf + && pStack->Parameters.Read.Length) + RT_BZERO(pvDstBuf, pStack->Parameters.Read.Length); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + } + else +#endif /* VBOX_WITH_HARDENING */ + { + /* + * Stub. + */ + rcNt = STATUS_NOT_SUPPORTED; + } + + /* + * Complete the request. + */ + pIrp->IoStatus.Status = rcNt; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Stub function for functions we don't implemented. + * + * @returns STATUS_NOT_SUPPORTED + * @param pDevObj Device object. + * @param pIrp IRP. + */ +NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtNotSupportedStub\n")); + NOREF(pDevObj); + + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return STATUS_NOT_SUPPORTED; +} + + +/** + * ExRegisterCallback handler for power events + * + * @param pCallbackContext User supplied parameter (pDevObj) + * @param pvArgument1 First argument + * @param pvArgument2 Second argument + */ +VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pvArgument1, PVOID pvArgument2) +{ + /*PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pCallbackContext;*/ RT_NOREF1(pCallbackContext); + Log(("VBoxPowerDispatchCallback: %x %x\n", pvArgument1, pvArgument2)); + + /* Power change imminent? */ + if ((uintptr_t)pvArgument1 == PO_CB_SYSTEM_STATE_LOCK) + { + if (pvArgument2 == NULL) + Log(("VBoxPowerDispatchCallback: about to go into suspend mode!\n")); + else + Log(("VBoxPowerDispatchCallback: resumed!\n")); + + /* Inform any clients that have registered themselves with IPRT. */ + RTPowerSignalEvent(pvArgument2 == NULL ? RTPOWEREVENT_SUSPEND : RTPOWEREVENT_RESUME); + } +} + + +/** + * Called to clean up the session structure before it's freed. + * + * @param pDevExt The device globals. + * @param pSession The session that's being cleaned up. + */ +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ +#ifdef VBOX_WITH_HARDENING + if (pSession->pNtProtect) + { + supdrvNtProtectRelease(pSession->pNtProtect); + pSession->pNtProtect = NULL; + } + RT_NOREF1(pDevExt); +#else + RT_NOREF2(pDevExt, pSession); +#endif +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + if (pSession->pNtUserId) + { + supdrvNtUserIdRelease(pSession->pNtUserId); + pSession->pNtUserId = NULL; + } +#endif +} + + +void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +size_t VBOXCALL supdrvOSGipGetGroupTableSize(PSUPDRVDEVEXT pDevExt) +{ + NOREF(pDevExt); + uint32_t cMaxCpus = RTMpGetCount(); + uint32_t cGroups = RTMpGetMaxCpuGroupCount(); + + return cGroups * RT_UOFFSETOF(SUPGIPCPUGROUP, aiCpuSetIdxs) + + RT_SIZEOFMEMB(SUPGIPCPUGROUP, aiCpuSetIdxs[0]) * cMaxCpus; +} + + +int VBOXCALL supdrvOSInitGipGroupTable(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, size_t cbGipCpuGroups) +{ + Assert(cbGipCpuGroups > 0); NOREF(cbGipCpuGroups); NOREF(pDevExt); + + unsigned const cGroups = RTMpGetMaxCpuGroupCount(); + AssertReturn(cGroups > 0 && cGroups < RT_ELEMENTS(pGip->aoffCpuGroup), VERR_INTERNAL_ERROR_2); + pGip->cPossibleCpuGroups = cGroups; + + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)&pGip->aCPUs[pGip->cCpus]; + for (uint32_t idxGroup = 0; idxGroup < cGroups; idxGroup++) + { + uint32_t cActive = 0; + uint32_t const cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + uint32_t const cbNeeded = RT_UOFFSETOF_DYN(SUPGIPCPUGROUP, aiCpuSetIdxs[cMax]); + uintptr_t const offGroup = (uintptr_t)pGroup - (uintptr_t)pGip; + AssertReturn(cbNeeded <= cbGipCpuGroups, VERR_INTERNAL_ERROR_3); + AssertReturn(cActive <= cMax, VERR_INTERNAL_ERROR_4); + AssertReturn(offGroup == (uint32_t)offGroup, VERR_INTERNAL_ERROR_5); + + pGip->aoffCpuGroup[idxGroup] = offGroup; + pGroup->cMembers = cActive; + pGroup->cMaxMembers = cMax; + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + pGroup->aiCpuSetIdxs[idxMember] = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + Assert((unsigned)pGroup->aiCpuSetIdxs[idxMember] < pGip->cPossibleCpus); + } + + /* advance. */ + cbGipCpuGroups -= cbNeeded; + pGroup = (PSUPGIPCPUGROUP)&pGroup->aiCpuSetIdxs[cMax]; + } + + return VINF_SUCCESS; +} + + +void VBOXCALL supdrvOSGipInitGroupBitsForCpu(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pGipCpu) +{ + NOREF(pDevExt); + + /* + * Translate the CPU index into a group and member. + */ + PROCESSOR_NUMBER ProcNum = { 0, (UCHAR)pGipCpu->iCpuSet, 0 }; + if (g_pfnKeGetProcessorNumberFromIndex) + { + NTSTATUS rcNt = g_pfnKeGetProcessorNumberFromIndex(pGipCpu->iCpuSet, &ProcNum); + if (NT_SUCCESS(rcNt)) + Assert(ProcNum.Group < g_pfnKeQueryMaximumGroupCount()); + else + { + AssertFailed(); + ProcNum.Group = 0; + ProcNum.Number = pGipCpu->iCpuSet; + } + } + pGipCpu->iCpuGroup = ProcNum.Group; + pGipCpu->iCpuGroupMember = ProcNum.Number; + + /* + * Update the group info. Just do this wholesale for now (doesn't scale well). + */ + for (uint32_t idxGroup = 0; idxGroup < pGip->cPossibleCpuGroups; idxGroup++) + { + uint32_t offGroup = pGip->aoffCpuGroup[idxGroup]; + if (offGroup != UINT32_MAX) + { + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offGroup); + uint32_t cActive = 0; + uint32_t cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + + AssertStmt(cMax == pGroup->cMaxMembers, cMax = pGroup->cMaxMembers); + AssertStmt(cActive <= cMax, cActive = cMax); + if (pGroup->cMembers != cActive) + ASMAtomicWriteU16(&pGroup->cMembers, cActive); + + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + int idxCpuSet = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + AssertMsg((unsigned)idxCpuSet < pGip->cPossibleCpus, + ("%d vs %d for %u.%u\n", idxCpuSet, pGip->cPossibleCpus, idxGroup, idxMember)); + + if (pGroup->aiCpuSetIdxs[idxMember] != idxCpuSet) + ASMAtomicWriteS16(&pGroup->aiCpuSetIdxs[idxMember], idxCpuSet); + } + } + } +} + + +/** + * Initializes any OS specific object creator fields. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +/** + * Force async tsc mode (stub). + */ +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + RT_NOREF1(pDevExt); + return g_Options.fOptForceAsyncTsc != 0; +} + + +/** + * Whether the host takes CPUs offline during a suspend/resume operation. + */ +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + return false; +} + + +/** + * Whether the hardware TSC has been synchronized by the OS. + */ +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + /* If IPRT didn't find KeIpiGenericCall we pretend windows(, the firmware, + or whoever) always configures TSCs perfectly. */ + return !RTMpOnPairIsConcurrentExecSupported(); +} + + +#define MY_SystemLoadGdiDriverInSystemSpaceInformation 54 +#define MY_SystemUnloadGdiDriverInformation 27 + +typedef struct MYSYSTEMGDIDRIVERINFO +{ + UNICODE_STRING Name; /**< In: image file name. */ + PVOID ImageAddress; /**< Out: the load address. */ + PVOID SectionPointer; /**< Out: section object. */ + PVOID EntryPointer; /**< Out: entry point address. */ + PVOID ExportSectionPointer; /**< Out: export directory/section. */ + ULONG ImageLength; /**< Out: SizeOfImage. */ +} MYSYSTEMGDIDRIVERINFO; + +extern "C" __declspec(dllimport) NTSTATUS NTAPI ZwSetSystemInformation(ULONG, PVOID, ULONG); + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + pImage->pvNtSectionObj = NULL; + pImage->hMemLock = NIL_RTR0MEMOBJ; + +#ifdef VBOX_WITHOUT_NATIVE_R0_LOADER +# ifndef RT_ARCH_X86 +# error "VBOX_WITHOUT_NATIVE_R0_LOADER is only safe on x86." +# endif + NOREF(pDevExt); NOREF(pszFilename); NOREF(pImage); + return VERR_NOT_SUPPORTED; + +#else + /* + * Convert the filename from DOS UTF-8 to NT UTF-16. + */ + size_t cwcFilename; + int rc = RTStrCalcUtf16LenEx(pszFilename, RTSTR_MAX, &cwcFilename); + if (RT_FAILURE(rc)) + return rc; + + PRTUTF16 pwcsFilename = (PRTUTF16)RTMemTmpAlloc((4 + cwcFilename + 1) * sizeof(RTUTF16)); + if (!pwcsFilename) + return VERR_NO_TMP_MEMORY; + + pwcsFilename[0] = '\\'; + pwcsFilename[1] = '?'; + pwcsFilename[2] = '?'; + pwcsFilename[3] = '\\'; + PRTUTF16 pwcsTmp = &pwcsFilename[4]; + rc = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwcsTmp, cwcFilename + 1, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Try load it. + */ + MYSYSTEMGDIDRIVERINFO Info; + RtlInitUnicodeString(&Info.Name, pwcsFilename); + Info.ImageAddress = NULL; + Info.SectionPointer = NULL; + Info.EntryPointer = NULL; + Info.ExportSectionPointer = NULL; + Info.ImageLength = 0; + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemLoadGdiDriverInSystemSpaceInformation, &Info, sizeof(Info)); + if (NT_SUCCESS(rcNt)) + { + pImage->pvImage = Info.ImageAddress; + pImage->pvNtSectionObj = Info.SectionPointer; + Log(("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer)); +# ifdef DEBUG_bird + SUPR0Printf("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer); +# endif + if (pImage->cbImageBits == Info.ImageLength) + { + /* + * Lock down the entire image, just to be on the safe side. + */ + rc = RTR0MemObjLockKernel(&pImage->hMemLock, pImage->pvImage, pImage->cbImageBits, RTMEM_PROT_READ); + if (RT_FAILURE(rc)) + { + pImage->hMemLock = NIL_RTR0MEMOBJ; + supdrvOSLdrUnload(pDevExt, pImage); + } + } + else + { + supdrvOSLdrUnload(pDevExt, pImage); + rc = VERR_LDR_MISMATCH_NATIVE; + } + } + else + { + Log(("rcNt=%#x '%ls'\n", rcNt, pwcsFilename)); + SUPR0Printf("VBoxDrv: rcNt=%x '%ws'\n", rcNt, pwcsFilename); + switch (rcNt) + { + case /* 0xc0000003 */ STATUS_INVALID_INFO_CLASS: +# ifdef RT_ARCH_AMD64 + /* Unwind will crash and BSOD, so no fallback here! */ + rc = VERR_NOT_IMPLEMENTED; +# else + /* + * Use the old way of loading the modules. + * + * Note! We do *NOT* try class 26 because it will probably + * not work correctly on terminal servers and such. + */ + rc = VERR_NOT_SUPPORTED; +# endif + break; + case /* 0xc0000034 */ STATUS_OBJECT_NAME_NOT_FOUND: + rc = VERR_MODULE_NOT_FOUND; + break; + case /* 0xC0000263 */ STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: + rc = VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND; + break; + case /* 0xC0000428 */ STATUS_INVALID_IMAGE_HASH: + rc = VERR_LDR_IMAGE_HASH; + break; + case /* 0xC000010E */ STATUS_IMAGE_ALREADY_LOADED: + Log(("WARNING: see @bugref{4853} for cause of this failure on Windows 7 x64\n")); + rc = VERR_ALREADY_LOADED; + break; + default: + rc = VERR_LDR_GENERAL_FAILURE; + break; + } + + pImage->pvNtSectionObj = NULL; + } + } + + RTMemTmpFree(pwcsFilename); + NOREF(pDevExt); + return rc; +#endif +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +/** + * Common worker for supdrvOSLdrQuerySymbol and supdrvOSLdrValidatePointer. + * + * @note Similar code in rtR0DbgKrnlNtParseModule. + */ +static int supdrvOSLdrValidatePointerOrQuerySymbol(PSUPDRVLDRIMAGE pImage, void *pv, const char *pszSymbol, + size_t cchSymbol, void **ppvSymbol) +{ + AssertReturn(pImage->pvNtSectionObj, VERR_INVALID_STATE); + Assert(pszSymbol || !ppvSymbol); + + /* + * Locate the export directory in the loaded image. + */ + uint8_t const *pbMapping = (uint8_t const *)pImage->pvImage; + uint32_t const cbMapping = pImage->cbImageBits; + uint32_t const uRvaToValidate = (uint32_t)((uintptr_t)pv - (uintptr_t)pbMapping); + AssertReturn(uRvaToValidate < cbMapping || ppvSymbol, VERR_INTERNAL_ERROR_3); + + uint32_t const offNtHdrs = *(uint16_t *)pbMapping == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbMapping)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < cbMapping, VERR_INTERNAL_ERROR_5); + + IMAGE_NT_HEADERS const *pNtHdrs = (IMAGE_NT_HEADERS const *)((uintptr_t)pbMapping + offNtHdrs); + AssertLogRelReturn(pNtHdrs->Signature == IMAGE_NT_SIGNATURE, VERR_INVALID_EXE_SIGNATURE); + AssertLogRelReturn(pNtHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, VERR_BAD_EXE_FORMAT); + AssertLogRelReturn(pNtHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES, VERR_BAD_EXE_FORMAT); + + uint32_t const offEndSectHdrs = offNtHdrs + + sizeof(*pNtHdrs) + + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + AssertReturn(offEndSectHdrs < cbMapping, VERR_BAD_EXE_FORMAT); + + /* + * Find the export directory. + */ + IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!ExpDir.Size) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + AssertReturn( ExpDir.Size >= sizeof(IMAGE_EXPORT_DIRECTORY) + && ExpDir.VirtualAddress >= offEndSectHdrs + && ExpDir.VirtualAddress < cbMapping + && ExpDir.VirtualAddress + ExpDir.Size <= cbMapping, VERR_BAD_EXE_FORMAT); + + IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pbMapping[ExpDir.VirtualAddress]; + + uint32_t const cNamedExports = pExpDir->NumberOfNames; + AssertReturn(cNamedExports < _1M, VERR_BAD_EXE_FORMAT); + AssertReturn(pExpDir->NumberOfFunctions < _1M, VERR_BAD_EXE_FORMAT); + if (pExpDir->NumberOfFunctions == 0 || cNamedExports == 0) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + + uint32_t const cExports = RT_MAX(cNamedExports, pExpDir->NumberOfFunctions); + + AssertReturn( pExpDir->AddressOfFunctions >= offEndSectHdrs + && pExpDir->AddressOfFunctions < cbMapping + && pExpDir->AddressOfFunctions + cExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfFunctions]; + + AssertReturn( pExpDir->AddressOfNames >= offEndSectHdrs + && pExpDir->AddressOfNames < cbMapping + && pExpDir->AddressOfNames + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffNamedExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfNames]; + + AssertReturn( pExpDir->AddressOfNameOrdinals >= offEndSectHdrs + && pExpDir->AddressOfNameOrdinals < cbMapping + && pExpDir->AddressOfNameOrdinals + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint16_t const * const pau16NameOrdinals = (uint16_t const *)&pbMapping[pExpDir->AddressOfNameOrdinals]; + + /* + * Validate the entrypoint RVA by scanning the export table. + */ + uint32_t iExportOrdinal = UINT32_MAX; + if (!ppvSymbol) + { + for (uint32_t i = 0; i < cExports; i++) + if (paoffExports[i] == uRvaToValidate) + { + iExportOrdinal = i; + break; + } + if (iExportOrdinal == UINT32_MAX) + { + SUPR0Printf("SUPDrv: No export with rva %#x (%s) in %s!\n", uRvaToValidate, pszSymbol, pImage->szName); + return VERR_NOT_FOUND; + } + } + + /* + * Can we validate the symbol name too or should we find a name? + * If so, just do a linear search. + */ + if (pszSymbol && (RT_C_IS_UPPER(*pszSymbol) || ppvSymbol)) + { + for (uint32_t i = 0; i < cNamedExports; i++) + { + uint32_t const offName = paoffNamedExports[i]; + AssertReturn(offName < cbMapping, VERR_BAD_EXE_FORMAT); + uint32_t const cchMaxName = cbMapping - offName; + const char * const pszName = (const char *)&pbMapping[offName]; + const char * const pszEnd = (const char *)memchr(pszName, '\0', cchMaxName); + AssertReturn(pszEnd, VERR_BAD_EXE_FORMAT); + + if ( cchSymbol == (size_t)(pszEnd - pszName) + && memcmp(pszName, pszSymbol, cchSymbol) == 0) + { + if (ppvSymbol) + { + iExportOrdinal = pau16NameOrdinals[i]; + if ( iExportOrdinal < cExports + && paoffExports[iExportOrdinal] < cbMapping) + { + *ppvSymbol = (void *)(paoffExports[iExportOrdinal] + pbMapping); + return VINF_SUCCESS; + } + } + else if (pau16NameOrdinals[i] == iExportOrdinal) + return VINF_SUCCESS; + else + SUPR0Printf("SUPDrv: Different exports found for %s and rva %#x in %s: %#x vs %#x\n", + pszSymbol, uRvaToValidate, pImage->szName, pau16NameOrdinals[i], iExportOrdinal); + return VERR_LDR_BAD_FIXUP; + } + } + if (!ppvSymbol) + SUPR0Printf("SUPDrv: No export named %s (%#x) in %s!\n", pszSymbol, uRvaToValidate, pImage->szName); + return VERR_SYMBOL_NOT_FOUND; + } + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + RT_NOREF(pDevExt, pbImageBits); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, pv, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0, NULL); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt); + AssertReturn(ppvSymbol, VERR_INVALID_PARAMETER); + AssertReturn(pszSymbol, VERR_INVALID_PARAMETER); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, NULL, pszSymbol, cchSymbol, ppvSymbol); +} + + +/** + * memcmp + errormsg + log. + * + * @returns Same as memcmp. + * @param pImage The image. + * @param pbImageBits The image bits ring-3 uploads. + * @param uRva The RVA to start comparing at. + * @param cb The number of bytes to compare. + * @param pReq The load request. + */ +static int supdrvNtCompare(PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, uint32_t uRva, uint32_t cb, PSUPLDRLOAD pReq) +{ + int iDiff = memcmp((uint8_t const *)pImage->pvImage + uRva, pbImageBits + uRva, cb); + if (iDiff) + { + uint32_t cbLeft = cb; + const uint8_t *pbNativeBits = (const uint8_t *)pImage->pvImage; + for (size_t off = uRva; cbLeft > 0; off++, cbLeft--) + if (pbNativeBits[off] != pbImageBits[off]) + { + /* Note! We need to copy image bits into a temporary stack buffer here as we'd + otherwise risk overwriting them while formatting the error message. */ + uint8_t abBytes[64]; + memcpy(abBytes, &pbImageBits[off], RT_MIN(64, cbLeft)); + supdrvLdrLoadError(VERR_LDR_MISMATCH_NATIVE, pReq, + "Mismatch at %#x (%p) of %s loaded at %p:\n" + "ntld: %.*Rhxs\n" + "iprt: %.*Rhxs", + off, &pbNativeBits[off], pImage->szName, pImage->pvImage, + RT_MIN(64, cbLeft), &pbNativeBits[off], + RT_MIN(64, cbLeft), &abBytes[0]); + SUPR0Printf("VBoxDrv: %s", pReq->u.Out.szError); + break; + } + } + return iDiff; +} + +/** Image compare exclusion regions. */ +typedef struct SUPDRVNTEXCLREGIONS +{ + /** Number of regions. */ + uint32_t cRegions; + /** The regions. */ + struct SUPDRVNTEXCLREGION + { + uint32_t uRva; + uint32_t cb; + } aRegions[20]; +} SUPDRVNTEXCLREGIONS; + +/** + * Adds an exclusion region to the collection. + */ +static bool supdrvNtAddExclRegion(SUPDRVNTEXCLREGIONS *pRegions, uint32_t uRvaRegion, uint32_t cbRegion) +{ + uint32_t const cRegions = pRegions->cRegions; + AssertReturn(cRegions + 1 <= RT_ELEMENTS(pRegions->aRegions), false); + uint32_t i = 0; + for (; i < cRegions; i++) + if (uRvaRegion < pRegions->aRegions[i].uRva) + break; + if (i != cRegions) + memmove(&pRegions->aRegions[i + 1], &pRegions->aRegions[i], (cRegions - i) * sizeof(pRegions->aRegions[0])); + pRegions->aRegions[i].uRva = uRvaRegion; + pRegions->aRegions[i].cb = cbRegion; + pRegions->cRegions++; + return true; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); + if (pImage->pvNtSectionObj) + { + /* + * Usually, the entire image matches exactly. + */ + if (!memcmp(pImage->pvImage, pbImageBits, pImage->cbImageBits)) + return VINF_SUCCESS; + + /* + * On Windows 10 the ImageBase member of the optional header is sometimes + * updated with the actual load address and sometimes not. + * On older windows versions (builds <= 9200?), a user mode address is + * sometimes found in the image base field after upgrading to VC++ 14.2. + */ + uint32_t const offNtHdrs = *(uint16_t *)pbImageBits == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbImageBits)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < pImage->cbImageBits, VERR_INTERNAL_ERROR_5); + IMAGE_NT_HEADERS const *pNtHdrsIprt = (IMAGE_NT_HEADERS const *)(pbImageBits + offNtHdrs); + IMAGE_NT_HEADERS const *pNtHdrsNtLd = (IMAGE_NT_HEADERS const *)((uintptr_t)pImage->pvImage + offNtHdrs); + + uint32_t const offImageBase = offNtHdrs + RT_UOFFSETOF(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + uint32_t const cbImageBase = RT_SIZEOFMEMB(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + if ( pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase + && pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && !memcmp(pImage->pvImage, pbImageBits, offImageBase) + && !memcmp((uint8_t const *)pImage->pvImage + offImageBase + cbImageBase, + pbImageBits + offImageBase + cbImageBase, + pImage->cbImageBits - offImageBase - cbImageBase)) + return VINF_SUCCESS; + + /* + * On Windows Server 2003 (sp2 x86) both import thunk tables are fixed + * up and we typically get a mismatch in the INIT section. + * + * So, lets see if everything matches when excluding the + * OriginalFirstThunk tables and (maybe) the ImageBase member. + * For simplicity the max number of exclusion regions is set to 16. + */ + if ( pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && pNtHdrsIprt->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size >= sizeof(IMAGE_IMPORT_DESCRIPTOR) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < pImage->cbImageBits + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size >= sizeof(IMAGE_LOAD_CONFIG_DIRECTORY) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress < pImage->cbImageBits) + { + SUPDRVNTEXCLREGIONS ExcludeRegions; + ExcludeRegions.cRegions = 0; + + /* ImageBase: */ + if (pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase) + supdrvNtAddExclRegion(&ExcludeRegions, offImageBase, cbImageBase); + + /* Imports: */ + uint32_t cImpsLeft = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + / sizeof(IMAGE_IMPORT_DESCRIPTOR); + uint32_t offImps = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + AssertLogRelReturn(offImps + cImpsLeft * sizeof(IMAGE_IMPORT_DESCRIPTOR) <= pImage->cbImageBits, VERR_INTERNAL_ERROR_3); + IMAGE_IMPORT_DESCRIPTOR const *pImp = (IMAGE_IMPORT_DESCRIPTOR const *)(pbImageBits + offImps); + while ( cImpsLeft-- > 0 + && ExcludeRegions.cRegions < RT_ELEMENTS(ExcludeRegions.aRegions)) + { + uint32_t uRvaThunk = pImp->OriginalFirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA) + && uRvaThunk != pImp->FirstThunk) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + +#if 0 /* Useful for VMMR0 hacking, not for production use. See also SUPDrvLdr.cpp. */ + /* Exclude the other thunk table if ntoskrnl.exe. */ + uint32_t uRvaName = pImp->Name; + if ( uRvaName > sizeof(IMAGE_NT_HEADERS) + && uRvaName < pImage->cbImageBits - sizeof("ntoskrnl.exe") + && memcmp(&pbImageBits[uRvaName], RT_STR_TUPLE("ntoskrnl.exe")) == 0) + { + uRvaThunk = pImp->FirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA)) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + } +#endif + + /* advance */ + pImp++; + } + + /* Exclude the security cookie if present. */ + uint32_t const cbCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size; + uint32_t const offCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress; + IMAGE_LOAD_CONFIG_DIRECTORY const * const pCfg = (IMAGE_LOAD_CONFIG_DIRECTORY const *)&pbImageBits[offCfg]; + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) + && pCfg->SecurityCookie != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->SecurityCookie - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Also exclude the GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer pointer variables. */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFCheckFunctionPointer) + && pCfg->GuardCFCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFDispatchFunctionPointer) + && pCfg->GuardCFDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Ditto for the XFG variants: */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGCheckFunctionPointer) + && pCfg->GuardXFGCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGDispatchFunctionPointer) + && pCfg->GuardXFGDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /** @todo What about GuardRFVerifyStackPointerFunctionPointer and + * GuardRFFailureRoutineFunctionPointer? Ignore for now as the compiler we're + * using (19.26.28805) sets them to zero from what I can tell. */ + + /* + * Ok, do the comparison. + */ + int iDiff = 0; + uint32_t uRvaNext = 0; + for (unsigned i = 0; !iDiff && i < ExcludeRegions.cRegions; i++) + { + if (uRvaNext < ExcludeRegions.aRegions[i].uRva) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, ExcludeRegions.aRegions[i].uRva - uRvaNext, pReq); + uRvaNext = ExcludeRegions.aRegions[i].uRva + ExcludeRegions.aRegions[i].cb; + } + if (!iDiff && uRvaNext < pImage->cbImageBits) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, pImage->cbImageBits - uRvaNext, pReq); + if (!iDiff) + { + /* + * If there is a cookie init export, call it. + * + * This typically just does: + * __security_cookie = (rdtsc ^ &__security_cookie) & 0xffffffffffff; + * __security_cookie_complement = ~__security_cookie; + */ + PFNRT pfnModuleInitSecurityCookie = NULL; + int rcSym = supdrvOSLdrQuerySymbol(pDevExt, pImage, RT_STR_TUPLE("ModuleInitSecurityCookie"), + (void **)&pfnModuleInitSecurityCookie); + if (RT_SUCCESS(rcSym) && pfnModuleInitSecurityCookie) + pfnModuleInitSecurityCookie(); + + return VINF_SUCCESS; + } + } + else + supdrvNtCompare(pImage, pbImageBits, 0, pImage->cbImageBits, pReq); + return VERR_LDR_MISMATCH_NATIVE; + } + return supdrvLdrLoadError(VERR_INTERNAL_ERROR_4, pReq, "No NT section object! Impossible!"); +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + if (pImage->pvNtSectionObj) + { + if (pImage->hMemLock != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pImage->hMemLock, false /*fFreeMappings*/); + pImage->hMemLock = NIL_RTR0MEMOBJ; + } + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemUnloadGdiDriverInformation, + &pImage->pvNtSectionObj, sizeof(pImage->pvNtSectionObj)); + if (rcNt != STATUS_SUCCESS) + SUPR0Printf("VBoxDrv: failed to unload '%s', rcNt=%#x\n", pImage->szName, rcNt); + pImage->pvNtSectionObj = NULL; + } + NOREF(pDevExt); +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +#ifdef SUPDRV_WITH_MSR_PROBER + +#if 1 +/** @todo make this selectable. */ +# define AMD_MSR_PASSCODE 0x9c5a203a +#else +# define ASMRdMsrEx(a, b, c) ASMRdMsr(a) +# define ASMWrMsrEx(a, b, c) ASMWrMsr(a,c) +#endif + + +/** + * Argument package used by supdrvOSMsrProberRead and supdrvOSMsrProberWrite. + */ +typedef struct SUPDRVNTMSPROBERARGS +{ + uint32_t uMsr; + uint64_t uValue; + bool fGp; +} SUPDRVNTMSPROBERARGS; + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberRead.} */ +static DECLCALLBACK(void) supdrvNtMsProberReadOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + pArgs->uValue = ASMRdMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + pArgs->uValue = 0; + } + ASMSetFlags(fOldFlags); +} + + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = 0; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberReadOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberReadOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + *puValue = Args.uValue; + return VINF_SUCCESS; +} + + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberWrite.} */ +static DECLCALLBACK(void) supdrvNtMsProberWriteOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + ASMWrMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE, pArgs->uValue); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + } + ASMSetFlags(fOldFlags); +} + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = uValue; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberWriteOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberWriteOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + return VINF_SUCCESS; +} + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberModify.} */ +static DECLCALLBACK(void) supdrvNtMsProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; + register uint32_t uMsr = pReq->u.In.uMsr; + bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; + uint64_t uBefore = 0; + uint64_t uWritten = 0; + uint64_t uAfter = 0; + bool fBeforeGp = true; + bool fModifyGp = true; + bool fAfterGp = true; + bool fRestoreGp = true; + RTCCUINTREG fOldFlags; + RT_NOREF2(idCpu, pvUser2); + + /* + * Do the job. + */ + fOldFlags = ASMIntDisableFlags(); + ASMCompilerBarrier(); /* paranoia */ + if (!fFaster) + ASMWriteBackAndInvalidateCaches(); + + __try + { + uBefore = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fBeforeGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fBeforeGp = true; + } + if (!fBeforeGp) + { + register uint64_t uRestore = uBefore; + + /* Modify. */ + uWritten = uRestore; + uWritten &= pReq->u.In.uArgs.Modify.fAndMask; + uWritten |= pReq->u.In.uArgs.Modify.fOrMask; + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uWritten); + fModifyGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fModifyGp = true; + } + + /* Read modified value. */ + __try + { + uAfter = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fAfterGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fAfterGp = true; + } + + /* Restore original value. */ + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uRestore); + fRestoreGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fRestoreGp = true; + } + + /* Invalid everything we can. */ + if (!fFaster) + { + ASMWriteBackAndInvalidateCaches(); + ASMReloadCR3(); + ASMNopPause(); + } + } + + ASMCompilerBarrier(); /* paranoia */ + ASMSetFlags(fOldFlags); + + /* + * Write out the results. + */ + pReq->u.Out.uResults.Modify.uBefore = uBefore; + pReq->u.Out.uResults.Modify.uWritten = uWritten; + pReq->u.Out.uResults.Modify.uAfter = uAfter; + pReq->u.Out.uResults.Modify.fBeforeGp = fBeforeGp; + pReq->u.Out.uResults.Modify.fModifyGp = fModifyGp; + pReq->u.Out.uResults.Modify.fAfterGp = fAfterGp; + pReq->u.Out.uResults.Modify.fRestoreGp = fRestoreGp; + RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + if (idCpu == NIL_RTCPUID) + { + supdrvNtMsProberModifyOnCpu(idCpu, pReq, NULL); + return VINF_SUCCESS; + } + return RTMpOnSpecific(idCpu, supdrvNtMsProberModifyOnCpu, pReq, NULL); +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +/** + * Converts an IPRT error code to an nt status code. + * + * @returns corresponding nt status code. + * @param rc IPRT error status code. + */ +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return STATUS_SUCCESS; + case VERR_GENERAL_FAILURE: return STATUS_NOT_SUPPORTED; + case VERR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; + case VERR_INVALID_MAGIC: return STATUS_UNKNOWN_REVISION; + case VERR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; + case VERR_INVALID_POINTER: return STATUS_INVALID_ADDRESS; + case VERR_LOCK_FAILED: return STATUS_NOT_LOCKED; + case VERR_ALREADY_LOADED: return STATUS_IMAGE_ALREADY_LOADED; + case VERR_PERMISSION_DENIED: return STATUS_ACCESS_DENIED; + case VERR_VERSION_MISMATCH: return STATUS_REVISION_MISMATCH; + } + + if (rc < 0) + { + if (((uint32_t)rc & UINT32_C(0xffff0000)) == UINT32_C(0xffff0000)) + return (NTSTATUS)( ((uint32_t)rc & UINT32_C(0xffff)) | SUP_NT_STATUS_BASE ); + } + return STATUS_UNSUCCESSFUL; +} + + +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + char szMsg[384]; + size_t cch = RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + RTLogWriteDebugger(szMsg, cch); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + + +SUPR0DECL(int) SUPR0IoCtlSetupForHandle(PSUPDRVSESSION pSession, intptr_t hHandle, uint32_t fFlags, PSUPR0IOCTLCTX *ppCtx) +{ + /* + * Validate input. + */ + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Turn the partition handle into a file object and related device object + * so that we can issue direct I/O control calls to the pair later. + */ + PFILE_OBJECT pFileObject = NULL; + OBJECT_HANDLE_INFORMATION HandleInfo = { 0, 0 }; + NTSTATUS rcNt = ObReferenceObjectByHandle((HANDLE)hHandle, /*FILE_WRITE_DATA*/0, *IoFileObjectType, + UserMode, (void **)&pFileObject, &HandleInfo); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + AssertPtrReturn(pFileObject, VERR_INTERNAL_ERROR_3); + + PDEVICE_OBJECT pDevObject = IoGetRelatedDeviceObject(pFileObject); + AssertMsgReturnStmt(RT_VALID_PTR(pDevObject), ("pDevObject=%p\n", pDevObject), + ObDereferenceObject(pFileObject), VERR_INTERNAL_ERROR_2); + + /* + * Allocate a context structure and fill it in. + */ + PSUPR0IOCTLCTX pCtx = (PSUPR0IOCTLCTX)RTMemAllocZ(sizeof(*pCtx)); + if (pCtx) + { + pCtx->u32Magic = SUPR0IOCTLCTX_MAGIC; + pCtx->cRefs = 1; + pCtx->pFileObject = pFileObject; + pCtx->pDeviceObject = pDevObject; + + PDRIVER_OBJECT pDrvObject = pDevObject->DriverObject; + if ( RT_VALID_PTR(pDrvObject->FastIoDispatch) + && RT_VALID_PTR(pDrvObject->FastIoDispatch->FastIoDeviceControl)) + pCtx->pfnFastIoDeviceControl = pDrvObject->FastIoDispatch->FastIoDeviceControl; + else + pCtx->pfnFastIoDeviceControl = NULL; + *ppCtx = pCtx; + return VINF_SUCCESS; + } + + ObDereferenceObject(pFileObject); + return VERR_NO_MEMORY; +} + + +/** + * I/O control destructor for NT. + * + * @param pCtx The context to destroy. + */ +static void supdrvNtIoCtlContextDestroy(PSUPR0IOCTLCTX pCtx) +{ + PFILE_OBJECT pFileObject = pCtx->pFileObject; + pCtx->pfnFastIoDeviceControl = NULL; + pCtx->pFileObject = NULL; + pCtx->pDeviceObject = NULL; + ASMAtomicWriteU32(&pCtx->u32Magic, ~SUPR0IOCTLCTX_MAGIC); + + if (RT_VALID_PTR(pFileObject)) + ObDereferenceObject(pFileObject); + RTMemFree(pCtx); +} + + +SUPR0DECL(int) SUPR0IoCtlCleanup(PSUPR0IOCTLCTX pCtx) +{ + if (pCtx != NULL) + { + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + uint32_t cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + } + return VINF_SUCCESS; +} + + +SUPR0DECL(int) SUPR0IoCtlPerform(PSUPR0IOCTLCTX pCtx, uintptr_t uFunction, + void *pvInput, RTR3PTR pvInputUser, size_t cbInput, + void *pvOutput, RTR3PTR pvOutputUser, size_t cbOutput, + int32_t *piNativeRc) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + /* Reference the context. */ + uint32_t cRefs = ASMAtomicIncU32(&pCtx->cRefs); + Assert(cRefs > 1 && cRefs < _4K); + + /* + * Try fast I/O control path first. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + if (pCtx->pfnFastIoDeviceControl) + { + /* Must pass user addresses here as that's what's being expected. */ + BOOLEAN fHandled = pCtx->pfnFastIoDeviceControl(pCtx->pFileObject, + TRUE /*Wait*/, + (void *)pvInputUser, (ULONG)cbInput, + (void *)pvOutputUser, (ULONG)cbOutput, + uFunction, + &Ios, + pCtx->pDeviceObject); + if (fHandled) + { + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + /* Set/convert status and return. */ + if (piNativeRc) + { + *piNativeRc = Ios.Status; + return VINF_SUCCESS; + } + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(Ios.Status); + } + + /* + * Fall back on IRP if not handled. + * + * Note! Perhaps we should rather fail, because VID.SYS will crash getting + * the partition ID with the code below. It tries to zero the output + * buffer as if it were as system buffer... + */ + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * For directly accessed buffers we must supply user mode addresses or + * we'll fail ProbeForWrite validation. + */ + switch (uFunction & 3) + { + case METHOD_BUFFERED: + /* For buffered accesses, we can supply kernel buffers. */ + break; + + case METHOD_IN_DIRECT: + pvInput = (void *)pvInputUser; + break; + + case METHOD_NEITHER: + pvInput = (void *)pvInputUser; + RT_FALL_THRU(); + + case METHOD_OUT_DIRECT: + pvOutput = (void *)pvOutputUser; + break; + } + + /* + * Build the request. + */ + int rc; + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + PIRP pIrp = IoBuildDeviceIoControlRequest(uFunction, pCtx->pDeviceObject, + pvInput, (ULONG)cbInput, pvOutput, (ULONG)cbOutput, + FALSE /* InternalDeviceControl */, &Event, &Ios); + if (pIrp) + { + IoGetNextIrpStackLocation(pIrp)->FileObject = pCtx->pFileObject; + + /* + * Make the call. + */ + NTSTATUS rcNt = IoCallDriver(pCtx->pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + { + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + AssertMsg(rcNt == STATUS_SUCCESS, ("rcNt=%#x\n", rcNt)); + rcNt = Ios.Status; + } + else if (NT_SUCCESS(rcNt) && Ios.Status != STATUS_SUCCESS) + rcNt = Ios.Status; + + /* Set/convert return code. */ + if (piNativeRc) + { + *piNativeRc = rcNt; + rc = VINF_SUCCESS; + } + else if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + { + if (piNativeRc) + *piNativeRc = STATUS_NO_MEMORY; + rc = VERR_NO_MEMORY; + } + + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + return rc; +} + + +#ifdef VBOX_WITH_HARDENING + +/** @name Identifying Special Processes: CSRSS.EXE + * @{ */ + + +/** + * Checks if the process is a system32 process by the given name. + * + * @returns true / false. + * @param pProcess The process to check. + * @param pszName The lower case process name (no path!). + */ +static bool supdrvNtProtectIsSystem32ProcessMatch(PEPROCESS pProcess, const char *pszName) +{ + Assert(strlen(pszName) < 16); /* see buffer below */ + + /* + * This test works on XP+. + */ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (RTStrICmp(pszImageFile, pszName) != 0) + return false; + + /* + * This test requires a Vista+ API. + */ + if (g_pfnPsReferenceProcessFilePointer) + { + PFILE_OBJECT pFile = NULL; + NTSTATUS rcNt = g_pfnPsReferenceProcessFilePointer(pProcess, &pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + union + { + OBJECT_NAME_INFORMATION Info; + uint8_t abBuffer[sizeof(g_System32NtPath) + 16 * sizeof(WCHAR)]; + } Buf; + ULONG cbIgn; + rcNt = ObQueryNameString(pFile, &Buf.Info, sizeof(Buf) - sizeof(WCHAR), &cbIgn); + ObDereferenceObject(pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + /* Terminate the name. */ + PRTUTF16 pwszName = Buf.Info.Name.Buffer; + pwszName[Buf.Info.Name.Length / sizeof(RTUTF16)] = '\0'; + + /* Match the name against the system32 directory path. */ + uint32_t cbSystem32 = g_System32NtPath.UniStr.Length; + if (Buf.Info.Name.Length < cbSystem32) + return false; + if (memcmp(pwszName, g_System32NtPath.UniStr.Buffer, cbSystem32)) + return false; + pwszName += cbSystem32 / sizeof(RTUTF16); + if (*pwszName++ != '\\') + return false; + + /* Compare the name. */ + const char *pszRight = pszName; + for (;;) + { + WCHAR wchLeft = *pwszName++; + char chRight = *pszRight++; + Assert(chRight == RT_C_TO_LOWER(chRight)); + + if ( wchLeft != chRight + && RT_C_TO_LOWER(wchLeft) != chRight) + return false; + if (!chRight) + break; + } + } + + return true; +} + + +/** + * Checks if the current process is likely to be CSRSS. + * + * @returns true/false. + * @param pProcess The process. + */ +static bool supdrvNtProtectIsCsrssByProcess(PEPROCESS pProcess) +{ + /* + * On Windows 8.1 CSRSS.EXE is a protected process. + */ + if (g_pfnPsIsProtectedProcessLight) + { + if (!g_pfnPsIsProtectedProcessLight(pProcess)) + return false; + } + + /* + * The name tests. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pProcess, "csrss.exe")) + return false; + + /** @todo Could extend the CSRSS.EXE check with that the TokenUser of the + * current process must be "NT AUTHORITY\SYSTEM" (S-1-5-18). */ + + return true; +} + + +/** + * Helper for supdrvNtProtectGetAlpcPortObjectType that tries out a name. + * + * @returns true if done, false if not. + * @param pwszPortNm The port path. + * @param ppObjType The object type return variable, updated when + * returning true. + */ +static bool supdrvNtProtectGetAlpcPortObjectType2(PCRTUTF16 pwszPortNm, POBJECT_TYPE *ppObjType) +{ + bool fDone = false; + + UNICODE_STRING UniStrPortNm; + UniStrPortNm.Buffer = (WCHAR *)pwszPortNm; + UniStrPortNm.Length = (USHORT)(RTUtf16Len(pwszPortNm) * sizeof(WCHAR)); + UniStrPortNm.MaximumLength = UniStrPortNm.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStrPortNm, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + HANDLE hPort; + NTSTATUS rcNt = g_pfnZwAlpcCreatePort(&hPort, &ObjAttr, NULL /*pPortAttribs*/); + if (NT_SUCCESS(rcNt)) + { + PVOID pvObject; + rcNt = ObReferenceObjectByHandle(hPort, 0 /*DesiredAccess*/, NULL /*pObjectType*/, + KernelMode, &pvObject, NULL /*pHandleInfo*/); + if (NT_SUCCESS(rcNt)) + { + POBJECT_TYPE pObjType = g_pfnObGetObjectType(pvObject); + if (pObjType) + { + SUPR0Printf("vboxdrv: ALPC Port Object Type %p (vs %p)\n", pObjType, *ppObjType); + *ppObjType = pObjType; + fDone = true; + } + ObDereferenceObject(pvObject); + } + NtClose(hPort); + } + return fDone; +} + + +/** + * Attempts to retrieve the ALPC Port object type. + * + * We've had at least three reports that using LpcPortObjectType when trying to + * get at the ApiPort object results in STATUS_OBJECT_TYPE_MISMATCH errors. + * It's not known who has modified LpcPortObjectType or AlpcPortObjectType (not + * exported) so that it differs from the actual ApiPort type, or maybe this + * unknown entity is intercepting our attempt to reference the port and + * tries to mislead us. The paranoid explanataion is of course that some evil + * root kit like software is messing with the OS, however, it's possible that + * this is valid kernel behavior that 99.8% of our users and 100% of the + * developers are not triggering for some reason. + * + * The code here creates an ALPC port object and gets it's type. It will cache + * the result in g_pAlpcPortObjectType2 on success. + * + * @returns Object type. + * @param uSessionId The session id. + * @param pszSessionId The session id formatted as a string. + */ +static POBJECT_TYPE supdrvNtProtectGetAlpcPortObjectType(uint32_t uSessionId, const char *pszSessionId) +{ + POBJECT_TYPE pObjType = *LpcPortObjectType; + + if ( g_pfnZwAlpcCreatePort + && g_pfnObGetObjectType) + { + int rc; + ssize_t cchTmp; NOREF(cchTmp); + char szTmp[16]; + RTUTF16 wszPortNm[128]; + size_t offRand; + + /* + * First attempt is in the session directory. + */ + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\VBoxDrv-"); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + bool fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + if (!fDone) + { + /* + * Try base names. + */ + if (uSessionId == 0) + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + else + { + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + } + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + } + + /* Cache the result in g_pAlpcPortObjectType2. */ + if ( g_pAlpcPortObjectType2 == NULL + && pObjType != g_pAlpcPortObjectType1 + && fDone) + g_pAlpcPortObjectType2 = pObjType; + + } + + return pObjType; +} + + +/** + * Called in the context of VBoxDrvNtCreate to determin the CSRSS for the + * current process. + * + * The Client/Server Runtime Subsystem (CSRSS) process needs to be allowed some + * additional access right so we need to make 101% sure we correctly identify + * the CSRSS process a process is associated with. + * + * @returns IPRT status code. + * @param pNtProtect The NT protected process structure. The + * hCsrssPid member will be updated on success. + */ +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect) +{ + Assert(pNtProtect->AvlCore.Key == PsGetCurrentProcessId()); + Assert(pNtProtect->pCsrssProcess == NULL); + Assert(pNtProtect->hCsrssPid == NULL); + + /* + * We'll try use the ApiPort LPC object for the session we're in to track + * down the CSRSS process. So, we start by constructing a path to it. + */ + int rc; + uint32_t uSessionId = PsGetProcessSessionId(PsGetCurrentProcess()); + char szSessionId[16]; + WCHAR wszApiPort[48]; + if (uSessionId == 0) + { + szSessionId[0] = '0'; + szSessionId[1] = '\0'; + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + else + { + ssize_t cchTmp = RTStrFormatU32(szSessionId, sizeof(szSessionId), uSessionId, 10, 0, 0, 0); + AssertReturn(cchTmp > 0, (int)cchTmp); + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Sessions\\"); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), szSessionId); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + AssertRCReturn(rc, rc); + + UNICODE_STRING ApiPortStr; + ApiPortStr.Buffer = wszApiPort; + ApiPortStr.Length = (USHORT)(RTUtf16Len(wszApiPort) * sizeof(RTUTF16)); + ApiPortStr.MaximumLength = ApiPortStr.Length + sizeof(RTUTF16); + + /* + * The object cannot be opened, but we can reference it by name. + */ + void *pvApiPortObj = NULL; + NTSTATUS rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType1, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pAlpcPortObjectType2 != NULL) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType2, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pfnObGetObjectType + && g_pfnZwAlpcCreatePort) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + supdrvNtProtectGetAlpcPortObjectType(uSessionId, szSessionId), + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if (!NT_SUCCESS(rcNt)) + { + SUPR0Printf("vboxdrv: Error opening '%ls': %#x\n", wszApiPort, rcNt); + return rcNt == STATUS_OBJECT_TYPE_MISMATCH ? VERR_SUPDRV_APIPORT_OPEN_ERROR_TYPE : VERR_SUPDRV_APIPORT_OPEN_ERROR; + } + + /* + * Query the processes in the system so we can locate CSRSS.EXE candidates. + * Note! Attempts at using SystemSessionProcessInformation failed with + * STATUS_ACCESS_VIOLATION. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + ULONG cbNeeded = _64K - 32; + uint32_t cbBuf; + uint8_t *pbBuf = NULL; + do + { + cbBuf = RT_ALIGN(cbNeeded + _4K, _64K) - 32; + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + break; + + cbNeeded = 0; +#if 0 /* doesn't work. */ + SYSTEM_SESSION_PROCESS_INFORMATION Req; + Req.SessionId = uSessionId; + Req.BufferLength = cbBuf; + Req.Buffer = pbBuf; + rcNt = NtQuerySystemInformation(SystemSessionProcessInformation, &Req, sizeof(Req), &cbNeeded); +#else + rcNt = NtQuerySystemInformation(SystemProcessInformation, pbBuf, cbBuf, &cbNeeded); +#endif + if (NT_SUCCESS(rcNt)) + break; + + RTMemFree(pbBuf); + pbBuf = NULL; + } while ( rcNt == STATUS_INFO_LENGTH_MISMATCH + && cbNeeded > cbBuf + && cbNeeded < 32U*_1M); + + if ( pbBuf + && NT_SUCCESS(rcNt) + && cbNeeded >= sizeof(SYSTEM_PROCESS_INFORMATION)) + { + /* + * Walk the returned data and look for the process associated with the + * ApiPort object. The ApiPort object keeps the EPROCESS address of + * the owner process (i.e. CSRSS) relatively early in the structure. On + * 64-bit windows 8.1 it's at offset 0x18. So, obtain the EPROCESS + * pointer to likely CSRSS processes and check for a match in the first + * 0x40 bytes of the ApiPort object. + */ + rc = VERR_SUPDRV_CSRSS_NOT_FOUND; + for (uint32_t offBuf = 0; offBuf <= cbNeeded - sizeof(SYSTEM_PROCESS_INFORMATION);) + { + PRTNT_SYSTEM_PROCESS_INFORMATION pProcInfo = (PRTNT_SYSTEM_PROCESS_INFORMATION)&pbBuf[offBuf]; + if ( pProcInfo->ProcessName.Length == 9 * sizeof(WCHAR) + && pProcInfo->NumberOfThreads > 2 /* Very low guess. */ + && pProcInfo->HandleCount > 32 /* Very low guess, I hope. */ + && (uintptr_t)pProcInfo->ProcessName.Buffer - (uintptr_t)pbBuf < cbNeeded + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[0]) == 'c' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[1]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[2]) == 'r' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[3]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[4]) == 's' + && pProcInfo->ProcessName.Buffer[5] == '.' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[6]) == 'e' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[7]) == 'x' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[8]) == 'e' ) + { + + /* Get the process structure and perform some more thorough + process checks. */ + PEPROCESS pProcess; + rcNt = PsLookupProcessByProcessId(pProcInfo->UniqueProcessId, &pProcess); + if (NT_SUCCESS(rcNt)) + { + if (supdrvNtProtectIsCsrssByProcess(pProcess)) + { + if (PsGetProcessSessionId(pProcess) == uSessionId) + { + /* Final test, check the ApiPort. + Note! The old LPC (pre Vista) objects has the PID + much earlier in the structure. Might be + worth looking for it instead. */ + bool fThatsIt = false; + __try + { + PEPROCESS *ppPortProc = (PEPROCESS *)pvApiPortObj; + uint32_t cTests = g_uNtVerCombined >= SUP_NT_VER_VISTA ? 16 : 38; /* ALPC since Vista. */ + do + { + fThatsIt = *ppPortProc == pProcess; + ppPortProc++; + } while (!fThatsIt && --cTests > 0); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fThatsIt = false; + } + if (fThatsIt) + { + /* Ok, we found it! Keep the process structure + reference as well as the PID so we can + safely identify it later on. */ + pNtProtect->hCsrssPid = pProcInfo->UniqueProcessId; + pNtProtect->pCsrssProcess = pProcess; + rc = VINF_SUCCESS; + break; + } + } + } + + ObDereferenceObject(pProcess); + } + } + + /* Advance. */ + if (!pProcInfo->NextEntryOffset) + break; + offBuf += pProcInfo->NextEntryOffset; + } + } + else + rc = VERR_SUPDRV_SESSION_PROCESS_ENUM_ERROR; + RTMemFree(pbBuf); + ObDereferenceObject(pvApiPortObj); + return rc; +} + + +/** + * Checks that the given process is the CSRSS process associated with protected + * process. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pCsrss The process structure of the alleged CSRSS.EXE + * process. + */ +static bool supdrvNtProtectIsAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pCsrss) +{ + if (pNtProtect->pCsrssProcess == pCsrss) + { + if (pNtProtect->hCsrssPid == PsGetProcessId(pCsrss)) + { + return true; + } + } + return false; +} + + +/** + * Checks if the given process is the stupid themes service. + * + * The caller does some screening of access masks and what not. We do the rest. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pAnnoyingProcess The process structure of an process that might + * happen to be the annoying themes process. + */ +static bool supdrvNtProtectIsFrigginThemesService(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pAnnoyingProcess) +{ + RT_NOREF1(pNtProtect); + + /* + * Check the process name. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pAnnoyingProcess, "svchost.exe")) + return false; + + /** @todo Come up with more checks. */ + + return true; +} + + +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS +/** + * Checks if the given process is one of the whitelisted debuggers. + * + * @returns true / false. + * @param pProcess The process to check. + */ +static bool supdrvNtProtectIsWhitelistedDebugger(PEPROCESS pProcess) +{ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (pszImageFile[0] == 'w' || pszImageFile[0] == 'W') + { + if (RTStrICmp(pszImageFile, "windbg.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfault.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfaultsecure.exe") == 0) + return true; + } + else if (pszImageFile[0] == 'd' || pszImageFile[0] == 'D') + { + if (RTStrICmp(pszImageFile, "drwtsn32.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "dwwin.exe") == 0) + return true; + } + + return false; +} +#endif /* VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** @} */ + + +/** @name Process Creation Callbacks. + * @{ */ + + +/** + * Cleans up VBoxDrv or VBoxDrvStub error info not collected by the dead process. + * + * @param hProcessId The ID of the dead process. + */ +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId) +{ + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pCur, pNext; + RTListForEachSafe(&g_ErrorInfoHead, pCur, pNext, SUPDRVNTERRORINFO, ListEntry) + { + if (pCur->hProcessId == hProcessId) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + } + RTSemMutexRelease(g_hErrorInfoLock); + } +} + + +/** + * Common worker used by the process creation hooks as well as the process + * handle creation hooks to check if a VM process is being created. + * + * @returns true if likely to be a VM process, false if not. + * @param pNtStub The NT protection structure for the possible + * stub process. + * @param hParentPid The parent pid. + * @param hChildPid The child pid. + */ +static bool supdrvNtProtectIsSpawningStubProcess(PSUPDRVNTPROTECT pNtStub, HANDLE hParentPid, HANDLE hChildPid) +{ + bool fRc = false; + if (pNtStub->AvlCore.Key == hParentPid) /* paranoia */ + { + if (pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + /* Compare short names. */ + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(hParentPid, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + PEPROCESS pChildProcess; + rcNt = PsLookupProcessByProcessId(hChildPid, &pChildProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszStub = (const char *)PsGetProcessImageFileName(pStubProcess); + const char *pszChild = (const char *)PsGetProcessImageFileName(pChildProcess); + fRc = pszStub != NULL + && pszChild != NULL + && strcmp(pszStub, pszChild) == 0; + + /** @todo check that the full image names matches. */ + + ObDereferenceObject(pChildProcess); + } + ObDereferenceObject(pStubProcess); + } + } + } + return fRc; +} + + +/** + * Common code used by the notifies to protect a child process. + * + * @returns VBox status code. + * @param pNtStub The NT protect structure for the parent. + * @param hChildPid The child pid. + */ +static int supdrvNtProtectProtectNewStubChild(PSUPDRVNTPROTECT pNtParent, HANDLE hChildPid) +{ + /* + * Create a child protection struction. + */ + PSUPDRVNTPROTECT pNtChild; + int rc = supdrvNtProtectCreate(&pNtChild, hChildPid, kSupDrvNtProtectKind_VmProcessUnconfirmed, false /*fLink*/); + if (RT_SUCCESS(rc)) + { + pNtChild->fFirstProcessCreateHandle = true; + pNtChild->fFirstThreadCreateHandle = true; + pNtChild->fCsrssFirstProcessCreateHandle = true; + pNtChild->cCsrssFirstProcessDuplicateHandle = ARCH_BITS == 32 ? 2 : 1; + pNtChild->fThemesFirstProcessCreateHandle = true; + pNtChild->hParentPid = pNtParent->AvlCore.Key; + pNtChild->hCsrssPid = pNtParent->hCsrssPid; + pNtChild->pCsrssProcess = pNtParent->pCsrssProcess; + if (pNtChild->pCsrssProcess) + ObReferenceObject(pNtChild->pCsrssProcess); + + /* + * Take the spinlock, recheck parent conditions and link things. + */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtParent->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtChild->AvlCore); + if (fSuccess) + { + pNtChild->fInTree = true; + pNtParent->u.pChild = pNtChild; /* Parent keeps the initial reference. */ + pNtParent->enmProcessKind = kSupDrvNtProtectKind_StubParent; + pNtChild->u.pParent = pNtParent; + + RTSpinlockRelease(g_hNtProtectLock); + return VINF_SUCCESS; + } + + rc = VERR_INTERNAL_ERROR_2; + } + else + rc = VERR_WRONG_ORDER; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtChild); + } + return rc; +} + + +/** + * Common process termination code. + * + * Transitions protected process to the dead states, protecting against handle + * PID reuse (esp. with unconfirmed VM processes) and handle cleanup issues. + * + * @param hDeadPid The PID of the dead process. + */ +static void supdrvNtProtectUnprotectDeadProcess(HANDLE hDeadPid) +{ + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hDeadPid); + if (pNtProtect) + { + PSUPDRVNTPROTECT pNtChild = NULL; + + RTSpinlockAcquire(g_hNtProtectLock); + + /* + * If this is an unconfirmed VM process, we must release the reference + * the parent structure holds. + */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + PSUPDRVNTPROTECT pNtParent = pNtProtect->u.pParent; + AssertRelease(pNtParent); AssertRelease(pNtParent->u.pChild == pNtProtect); + pNtParent->u.pChild = NULL; + pNtProtect->u.pParent = NULL; + pNtChild = pNtProtect; + } + /* + * If this is a stub exitting before the VM process gets confirmed, + * release the protection of the potential VM process as this is not + * the prescribed behavior. + */ + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtProtect->u.pChild) + { + pNtChild = pNtProtect->u.pChild; + pNtProtect->u.pChild = NULL; + pNtChild->u.pParent = NULL; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* + * Transition it to the dead state to prevent it from opening the + * support driver again or be posthumously abused as a vm process parent. + */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubSpawning + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_StubDead; + + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtProtect); + supdrvNtProtectRelease(pNtChild); + + /* + * Do session cleanups. + */ + AssertReturnVoid((HANDLE)(uintptr_t)RTProcSelf() == hDeadPid); + if (g_pDevObjSys) + { + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, (RTPROCESS)(uintptr_t)hDeadPid, + RTR0ProcHandleSelf(), NULL); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + } +} + + +/** + * Common worker for the process creation callback that verifies a new child + * being created by the handle creation callback code. + * + * @param pNtStub The parent. + * @param pNtVm The child. + * @param fCallerChecks The result of any additional tests the caller made. + * This is in order to avoid duplicating the failure + * path code. + */ +static void supdrvNtProtectVerifyNewChildProtection(PSUPDRVNTPROTECT pNtStub, PSUPDRVNTPROTECT pNtVm, bool fCallerChecks) +{ + if ( fCallerChecks + && pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtVm->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtVm->u.pParent == pNtStub + && pNtStub->u.pChild == pNtVm) + { + /* Fine, reset the CSRSS hack (fixes ViRobot APT Shield 2.0 issue). */ + pNtVm->fFirstProcessCreateHandle = true; + return; + } + + LogRel(("vboxdrv: Misdetected vm stub; hParentPid=%p hChildPid=%p\n", pNtStub->AvlCore.Key, pNtVm->AvlCore.Key)); + if (pNtStub->enmProcessKind != kSupDrvNtProtectKind_VmProcessConfirmed) + supdrvNtProtectUnprotectDeadProcess(pNtVm->AvlCore.Key); +} + + +/** + * Old style callback (since forever). + * + * @param hParentPid The parent PID. + * @param hNewPid The PID of the new child. + * @param fCreated TRUE if it's a creation notification, + * FALSE if termination. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotify(HANDLE hParentPid, HANDLE hNewPid, BOOLEAN fCreated) +{ + /* + * Is it a new process that needs protection? + */ + if (fCreated) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + else + { + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, true); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + + +/** + * New style callback (Vista SP1+ / w2k8). + * + * @param pNewProcess The new process. + * @param hNewPid The PID of the new process. + * @param pInfo Process creation details. NULL if process + * termination notification. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotifyEx(PEPROCESS pNewProcess, HANDLE hNewPid, PPS_CREATE_NOTIFY_INFO pInfo) +{ + RT_NOREF1(pNewProcess); + + /* + * Is it a new process that needs protection? + */ + if (pInfo) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(pInfo->CreatingThreadId.UniqueProcess); + + Log(("vboxdrv/NewProcessEx: ctx=%04zx/%p pid=%04zx ppid=%04zx ctor=%04zx/%04zx rcNt=%#x %.*ls\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + hNewPid, pInfo->ParentProcessId, + pInfo->CreatingThreadId.UniqueProcess, pInfo->CreatingThreadId.UniqueThread, pInfo->CreationStatus, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? (size_t)pInfo->ImageFileName->Length / 2 : 0, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? pInfo->ImageFileName->Buffer : NULL)); + + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + /* Parent must be creator. */ + if (pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, pInfo->ParentProcessId, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + } + else + { + /* Parent must be creator (as above). */ + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, + pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + +/** @} */ + + +/** @name Process Handle Callbacks. + * @{ */ + +/** Process rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOW_PROCESS_RIGHTS \ + ( PROCESS_TERMINATE \ + | PROCESS_VM_READ \ + | PROCESS_QUERY_INFORMATION \ + | PROCESS_QUERY_LIMITED_INFORMATION \ + | PROCESS_SUSPEND_RESUME \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) + +/** Evil process rights. */ +# define SUPDRV_NT_EVIL_PROCESS_RIGHTS \ + ( PROCESS_CREATE_THREAD \ + | PROCESS_SET_SESSIONID /*?*/ \ + | PROCESS_VM_OPERATION \ + | PROCESS_VM_WRITE \ + | PROCESS_DUP_HANDLE \ + | PROCESS_CREATE_PROCESS /*?*/ \ + | PROCESS_SET_QUOTA /*?*/ \ + | PROCESS_SET_INFORMATION \ + | PROCESS_SET_LIMITED_INFORMATION /*?*/ \ + | 0) +AssertCompile((SUPDRV_NT_ALLOW_PROCESS_RIGHTS & SUPDRV_NT_EVIL_PROCESS_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ProcessHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + /* + * Protected? Kludge required for NtOpenProcess calls comming in before + * the create process hook triggers on Windows 8.1 (possibly others too). + */ + HANDLE hObjPid = PsGetProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hObjPid); + if (!pNtProtect) + { + HANDLE hParentPid = PsGetProcessInheritedFromUniqueProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hObjPid)) + { + supdrvNtProtectProtectNewStubChild(pNtStub, hObjPid); + pNtProtect = supdrvNtProtectLookup(hObjPid); + } + supdrvNtProtectRelease(pNtStub); + } + } + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + /* + * Ok, it's a protected process. Strip rights as required or possible. + */ + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOW_PROCESS_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing itself. */ + if ((PEPROCESS)pOpInfo->Object == PsGetCurrentProcess()) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#endif + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->CreateHandleInformation.DesiredAccess; + + /* Special case 1 on Vista, 7 & 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. + + Special case 2 on 8.1: + The CreateProcess code requires additional rights for + something, we'll drop these in the stub code. */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) + && ExGetPreviousMode() != KernelMode) + { + if ( !pOpInfo->KernelHandle + && fDesiredAccess == s_fCsrssStupidDesires) + { + if (g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3)) + fAllowedRights |= s_fCsrssStupidDesires; + else + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_SET_INFORMATION + | PROCESS_SET_LIMITED_INFORMATION + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstProcessCreateHandle = false; + } + + /* Special case 3 on 8.1: + The interaction between the CreateProcess code and CSRSS.EXE + has changed to the better with Windows 8.1. CSRSS.EXE no + longer duplicates the process (thread too) handle, but opens + it, thus allowing us to do our job. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fCsrssFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fCsrssFirstProcessCreateHandle = false; + if (fDesiredAccess == s_fCsrssStupidDesires) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for CreateProcess/VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 4, Windows 7, Vista, possibly 8, but not 8.1: + The Themes service requires PROCESS_DUP_HANDLE access to our + process or we won't get any menus and dialogs will be half + unreadable. This is _very_ unfortunate and more work will + go into making this more secure. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0) + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && fDesiredAccess == 0x1478 /* 6.1.7600.16385 (win7_rtm.090713-1255) */ + && pNtProtect->fThemesFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsFrigginThemesService(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fThemesFirstProcessCreateHandle = true; /* Only once! */ + fAllowedRights |= PROCESS_DUP_HANDLE; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + /* Special case 6a, Windows 10+: AudioDG.exe opens the process with the + PROCESS_SET_LIMITED_INFORMATION right. It seems like it need it for + some myserious and weirdly placed cpu set management of our process. + I'd love to understand what that's all about... + Currently playing safe and only grand this right, however limited, to + audiodg.exe. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p/pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + fDesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + fAllowedRights, fDesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode() )); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing itself. */ + if ( (PEPROCESS)pOpInfo->Object == PsGetCurrentProcess() + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pOpInfo->Object) + { + Log(("vboxdrv/ProcessHandlePre: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess; + + /* Special case 5 on Vista, 7 & 8: + This is the CSRSS.EXE end of special case #1. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->cCsrssFirstProcessDuplicateHandle > 0 + && pOpInfo->KernelHandle == 0 + && fDesiredAccess == s_fCsrssStupidDesires + && pNtProtect->hParentPid + == PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess())) + { + if (ASMAtomicDecS32(&pNtProtect->cCsrssFirstProcessDuplicateHandle) >= 0) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS, PROCESS_DUP_HANDLE */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for launching VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 6b, Windows 10+: AudioDG.exe duplicates the handle it opened above. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + fDesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ProcessHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE + ? pOpInfo->Parameters->CreateHandleInformation.GrantedAccess + : pOpInfo->Parameters->DuplicateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOW_PROCESS_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | PROCESS_UNKNOWN_4000 /* Seen set on win 8.1 */ + /*| PROCESS_UNKNOWN_8000 */ ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOW_PROCESS_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOW_PROCESS_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOW_PROCESS_RIGHTS + +/** @} */ + + +/** @name Thread Handle Callbacks + * @{ */ + +/* From ntifs.h */ +extern "C" NTKERNELAPI PEPROCESS __stdcall IoThreadToProcess(PETHREAD); + +/** Thread rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOWED_THREAD_RIGHTS \ + ( THREAD_TERMINATE \ + | THREAD_GET_CONTEXT \ + | THREAD_QUERY_INFORMATION \ + | THREAD_QUERY_LIMITED_INFORMATION \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) +/** @todo consider THREAD_SET_LIMITED_INFORMATION & THREAD_RESUME */ + +/** Evil thread rights. + * @remarks THREAD_RESUME is not included as it seems to be forced upon us by + * Windows 8.1, at least for some processes. We dont' actively + * allow it though, just tollerate it when forced to. */ +# define SUPDRV_NT_EVIL_THREAD_RIGHTS \ + ( THREAD_SUSPEND_RESUME \ + | THREAD_SET_CONTEXT \ + | THREAD_SET_INFORMATION \ + | THREAD_SET_LIMITED_INFORMATION /*?*/ \ + | THREAD_SET_THREAD_TOKEN /*?*/ \ + | THREAD_IMPERSONATE /*?*/ \ + | THREAD_DIRECT_IMPERSONATION /*?*/ \ + /*| THREAD_RESUME - see remarks. */ \ + | 0) +AssertCompile((SUPDRV_NT_EVIL_THREAD_RIGHTS & SUPDRV_NT_ALLOWED_THREAD_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ThreadHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + PEPROCESS pProcess = IoThreadToProcess((PETHREAD)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(pProcess)); + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOWED_THREAD_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing its own threads. */ + if (pProcess == PsGetCurrentProcess()) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind)); + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstThreadCreateHandle = false; + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } +#endif + else + { + /* Special case 1 on Vista, 7, 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstThreadCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) ) + { + if ( !pOpInfo->KernelHandle + && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == s_fCsrssStupidDesires) + { + fAllowedRights |= s_fCsrssStupidDesires; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstThreadCreateHandle = false; + } + + /* Special case 2 on 8.1, possibly also Vista, 7, 8: + When creating a process like VBoxTestOGL from the VM process, + CSRSS.EXE will try talk to the calling thread and, it + appears, impersonate it. We unfortunately need to allow + this or there will be no 3D support. Typical DbgPrint: + "SXS: BasepCreateActCtx() Calling csrss server failed. Status = 0xc00000a5" */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode())); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing its own threads. */ + if ( pProcess == PsGetCurrentProcess() + && (PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pProcess) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + /* Special case 3 on Vista, 7, 8: + This is the follow up to special case 1. */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ThreadHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Parameters->CreateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOWED_THREAD_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | THREAD_RESUME /* This seems to be force upon us too with 8.1. */ + ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOWED_THREAD_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOWED_THREAD_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOWED_THREAD_RIGHTS + +/** @} */ + + +/** + * Creates a new process protection structure. + * + * @returns VBox status code. + * @param ppNtProtect Where to return the pointer to the structure + * on success. + * @param hPid The process ID of the process to protect. + * @param enmProcessKind The kind of process we're protecting. + * @param fLink Whether to link the structure into the tree. + */ +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, SUPDRVNTPROTECTKIND enmProcessKind, bool fLink) +{ + AssertReturn(g_hNtProtectLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); + + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)RTMemAllocZ(sizeof(*pNtProtect)); + if (!pNtProtect) + return VERR_NO_MEMORY; + + pNtProtect->AvlCore.Key = hPid; + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC; + pNtProtect->cRefs = 1; + pNtProtect->enmProcessKind = enmProcessKind; + pNtProtect->hParentPid = NULL; + pNtProtect->hOpenTid = NULL; + pNtProtect->hCsrssPid = NULL; + pNtProtect->pCsrssProcess = NULL; + + if (fLink) + { + RTSpinlockAcquire(g_hNtProtectLock); + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtProtect->AvlCore); + pNtProtect->fInTree = fSuccess; + RTSpinlockRelease(g_hNtProtectLock); + + if (!fSuccess) + { + /* Duplicate entry, fail. */ + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC_DEAD; + LogRel(("supdrvNtProtectCreate: Duplicate (%#x).\n", pNtProtect->AvlCore.Key)); + RTMemFree(pNtProtect); + return VERR_DUPLICATE; + } + } + + *ppNtProtect = pNtProtect; + return VINF_SUCCESS; +} + + +/** + * Releases a reference to a NT protection structure. + * + * @param pNtProtect The NT protection structure. + */ +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect) +{ + if (!pNtProtect) + return; + AssertReturnVoid(pNtProtect->u32Magic == SUPDRVNTPROTECT_MAGIC); + + RTSpinlockAcquire(g_hNtProtectLock); + uint32_t cRefs = ASMAtomicDecU32(&pNtProtect->cRefs); + if (cRefs != 0) + RTSpinlockRelease(g_hNtProtectLock); + else + { + /* + * That was the last reference. Remove it from the tree, invalidate it + * and free the resources associated with it. Also, release any + * child/parent references related to this protection structure. + */ + ASMAtomicWriteU32(&pNtProtect->u32Magic, SUPDRVNTPROTECT_MAGIC_DEAD); + if (pNtProtect->fInTree) + { + PSUPDRVNTPROTECT pRemoved = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pNtProtect->AvlCore.Key); + Assert(pRemoved == pNtProtect); RT_NOREF_PV(pRemoved); + pNtProtect->fInTree = false; + } + + PSUPDRVNTPROTECT pChild = NULL; + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent) + { + pChild = pNtProtect->u.pChild; + if (pChild) + { + pNtProtect->u.pChild = NULL; + pChild->u.pParent = NULL; + pChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + uint32_t cChildRefs = ASMAtomicDecU32(&pChild->cRefs); + if (!cChildRefs) + { + Assert(pChild->fInTree); + if (pChild->fInTree) + { + PSUPDRVNTPROTECT pRemovedChild = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pChild->AvlCore.Key); + Assert(pRemovedChild == pChild); RT_NOREF_PV(pRemovedChild); + pChild->fInTree = false; + } + } + else + pChild = NULL; + } + } + else + AssertRelease(pNtProtect->enmProcessKind != kSupDrvNtProtectKind_VmProcessUnconfirmed); + + RTSpinlockRelease(g_hNtProtectLock); + + if (pNtProtect->pCsrssProcess) + { + ObDereferenceObject(pNtProtect->pCsrssProcess); + pNtProtect->pCsrssProcess = NULL; + } + + RTMemFree(pNtProtect); + if (pChild) + RTMemFree(pChild); + } +} + + +/** + * Looks up a PID in the NT protect tree. + * + * @returns Pointer to a NT protection structure (with a referenced) on success, + * NULL if not found. + * @param hPid The process ID. + */ +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid) +{ + RTSpinlockAcquire(g_hNtProtectLock); + PSUPDRVNTPROTECT pFound = (PSUPDRVNTPROTECT)RTAvlPVGet(&g_NtProtectTree, hPid); + if (pFound) + ASMAtomicIncU32(&pFound->cRefs); + RTSpinlockRelease(g_hNtProtectLock); + return pFound; +} + + +/** + * Validates a few facts about the stub process when the VM process opens + * vboxdrv. + * + * This makes sure the stub process is still around and that it has neither + * debugger nor extra threads in it. + * + * @returns VBox status code. + * @param pNtProtect The unconfirmed VM process currently trying to + * open vboxdrv. + * @param pErrInfo Additional error information. + */ +static int supdrvNtProtectVerifyStubForVmProcess(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * Grab a reference to the parent stub process. + */ + SUPDRVNTPROTECTKIND enmStub = kSupDrvNtProtectKind_Invalid; + PSUPDRVNTPROTECT pNtStub = NULL; + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + pNtStub = pNtProtect->u.pParent; /* weak reference. */ + if (pNtStub) + { + enmStub = pNtStub->enmProcessKind; + if (enmStub == kSupDrvNtProtectKind_StubParent) + { + uint32_t cRefs = ASMAtomicIncU32(&pNtStub->cRefs); + Assert(cRefs > 0 && cRefs < 1024); RT_NOREF_PV(cRefs); + } + else + pNtStub = NULL; + } + } + RTSpinlockRelease(g_hNtProtectLock); + + /* + * We require the stub process to be present. + */ + if (!pNtStub) + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, "Missing stub process (enmStub=%d).", enmStub); + + /* + * Open the parent process and thread so we can check for debuggers and unwanted threads. + */ + int rc; + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(pNtStub->AvlCore.Key, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubProcess; + rcNt = ObOpenObjectByPointer(pStubProcess, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsProcessType, KernelMode, &hStubProcess); + if (NT_SUCCESS(rcNt)) + { + PETHREAD pStubThread; + rcNt = PsLookupThreadByThreadId(pNtStub->hOpenTid, &pStubThread); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubThread; + rcNt = ObOpenObjectByPointer(pStubThread, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsThreadType, KernelMode, &hStubThread); + if (NT_SUCCESS(rcNt)) + { + /* + * Do some simple sanity checking. + */ + rc = supHardNtVpDebugger(hStubProcess, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpThread(hStubProcess, hStubThread, pErrInfo); + + /* Clean up. */ + rcNt = NtClose(hStubThread); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_OPEN_ERROR, + "Error opening stub thread %p (tid %p, pid %p): %#x", + pStubThread, pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_NOT_FOUND, + "Failed to locate thread %p in %p: %#x", pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + rcNt = NtClose(hStubProcess); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_OPEN_ERROR, + "Error opening stub process %p (pid %p): %#x", pStubProcess, pNtStub->AvlCore.Key, rcNt); + ObDereferenceObject(pStubProcess); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, + "Failed to locate stub process %p: %#x", pNtStub->AvlCore.Key, rcNt); + + supdrvNtProtectRelease(pNtStub); + return rc; +} + + +static const char *supdrvNtProtectHandleTypeIndexToName(ULONG idxType, char *pszName, size_t cbName) +{ + /* + * Query the object types. + */ + uint32_t cbBuf = _8K; + uint8_t *pbBuf = (uint8_t *)RTMemAllocZ(_8K); + ULONG cbNeeded = cbBuf; + NTSTATUS rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + while (rcNt == STATUS_INFO_LENGTH_MISMATCH) + { + cbBuf = RT_ALIGN_32(cbNeeded + 256, _64K); + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAllocZ(cbBuf); + if (pbBuf) + rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + else + break; + } + if (NT_SUCCESS(rcNt)) + { + Assert(cbNeeded <= cbBuf); + + POBJECT_TYPES_INFORMATION pObjTypes = (OBJECT_TYPES_INFORMATION *)pbBuf; + POBJECT_TYPE_INFORMATION pCurType = &pObjTypes->FirstType; + ULONG cLeft = pObjTypes->NumberOfTypes; + while (cLeft-- > 0 && (uintptr_t)&pCurType[1] - (uintptr_t)pbBuf < cbNeeded) + { + if (pCurType->TypeIndex == idxType) + { + PCRTUTF16 const pwszSrc = pCurType->TypeName.Buffer; + AssertBreak(pwszSrc); + size_t idxName = pCurType->TypeName.Length / sizeof(RTUTF16); + AssertBreak(idxName > 0); + AssertBreak(idxName < 128); + if (idxName >= cbName) + idxName = cbName - 1; + pszName[idxName] = '\0'; + while (idxName-- > 0) + pszName[idxName] = (char )pwszSrc[idxName]; + RTMemFree(pbBuf); + return pszName; + } + + /* next */ + pCurType = (POBJECT_TYPE_INFORMATION)( (uintptr_t)pCurType->TypeName.Buffer + + RT_ALIGN_32(pCurType->TypeName.MaximumLength, sizeof(uintptr_t))); + } + } + + RTMemFree(pbBuf); + return "unknown"; +} + + +/** + * Worker for supdrvNtProtectVerifyProcess that verifies the handles to a VM + * process and its thread. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure for getting information + * about special processes. + * @param pErrInfo Where to return additional error details. + */ +static int supdrvNtProtectRestrictHandlesToProcessAndThread(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * What to protect. + */ + PEPROCESS pProtectedProcess = PsGetCurrentProcess(); + HANDLE hProtectedPid = PsGetProcessId(pProtectedProcess); + PETHREAD pProtectedThread = PsGetCurrentThread(); + AssertReturn(pNtProtect->AvlCore.Key == hProtectedPid, VERR_INTERNAL_ERROR_5); + + /* + * Take a snapshot of all the handles in the system. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + uint32_t cbBuf = _256K - 32; + uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + ULONG cbNeeded = cbBuf; + NTSTATUS rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + if (!NT_SUCCESS(rcNt)) + { + while ( rcNt == STATUS_INFO_LENGTH_MISMATCH + && cbNeeded > cbBuf + && cbBuf <= 32U*_1M) + { + cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K) - 32; + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Error allocating %zu bytes for querying handles.", cbBuf); + rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + } + if (!NT_SUCCESS(rcNt)) + { + RTMemFree(pbBuf); + return RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), + "NtQuerySystemInformation/SystemExtendedHandleInformation failed: %#x\n", rcNt); + } + } + + /* + * Walk the information and look for handles to the two objects we're protecting. + */ + int rc = VINF_SUCCESS; +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + HANDLE idLastDebugger = (HANDLE)~(uintptr_t)0; +# endif + + uint32_t cCsrssProcessHandles = 0; + uint32_t cSystemProcessHandles = 0; + uint32_t cEvilProcessHandles = 0; + uint32_t cBenignProcessHandles = 0; + + uint32_t cCsrssThreadHandles = 0; + uint32_t cEvilThreadHandles = 0; + uint32_t cBenignThreadHandles = 0; + + uint32_t cEvilInheritableHandles = 0; + uint32_t cBenignInheritableHandles = 0; + char szTmpName[32]; + + SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; + ULONG_PTR i = pInfo->NumberOfHandles; + AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded); + while (i-- > 0) + { + const char *pszType; + SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; + if (pHandleInfo->Object == pProtectedProcess) + { + /* Handles within the protected process are fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_PROCESS_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignProcessHandles++; + continue; + } + + /* CSRSS is allowed to have one evil process handle. + See the special cases in the hook code. */ + if ( cCsrssProcessHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssProcessHandles++; + continue; + } + + /* The system process is allowed having two open process handle in + Windows 8.1 and later, and one in earlier. This is probably a + little overly paranoid as I think we can safely trust the + system process... */ + if ( cSystemProcessHandles < (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) ? UINT32_C(2) : UINT32_C(1)) + && pHandleInfo->UniqueProcessId == PsGetProcessId(PsInitialSystemProcess)) + { + cSystemProcessHandles++; + continue; + } + + cEvilProcessHandles++; + pszType = "process"; + } + else if (pHandleInfo->Object == pProtectedThread) + { + /* Handles within the protected process is fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_THREAD_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignThreadHandles++; + continue; + } + + /* CSRSS is allowed to have one evil handle to the primary thread + for LPC purposes. See the hook for special case. */ + if ( cCsrssThreadHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssThreadHandles++; + continue; + } + + cEvilThreadHandles++; + pszType = "thread"; + } + else if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) + && pHandleInfo->UniqueProcessId == hProtectedPid) + { + /* No handles should be marked inheritable, except files and two events. + Handles to NT 'directory' objects are especially evil, because of + KnownDlls faking. See bugref{10294} for details. + + Correlating the ObjectTypeIndex to a type is complicated, so instead + we try referecing the handle and check the type that way. So, only + file and events objects are allowed to be marked inheritable at the + moment. Add more in whitelist fashion if needed. */ + void *pvObject = NULL; + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *IoFileObjectType, KernelMode, &pvObject, NULL); + if (rcNt == STATUS_OBJECT_TYPE_MISMATCH) + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *ExEventObjectType, KernelMode, &pvObject, NULL); + if (NT_SUCCESS(rcNt)) + { + ObDereferenceObject(pvObject); + cBenignInheritableHandles++; + continue; + } + + if (rcNt != STATUS_OBJECT_TYPE_MISMATCH) + { + cBenignInheritableHandles++; + continue; + } + + cEvilInheritableHandles++; + pszType = supdrvNtProtectHandleTypeIndexToName(pHandleInfo->ObjectTypeIndex, szTmpName, sizeof(szTmpName)); + } + else + continue; + +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Ignore whitelisted debuggers. */ + if (pHandleInfo->UniqueProcessId == idLastDebugger) + continue; + PEPROCESS pDbgProc; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pDbgProc); + if (NT_SUCCESS(rcNt)) + { + bool fIsDebugger = supdrvNtProtectIsWhitelistedDebugger(pDbgProc); + ObDereferenceObject(pDbgProc); + if (fIsDebugger) + { + idLastDebugger = pHandleInfo->UniqueProcessId; + continue; + } + } +# endif + + /* Found evil handle. Currently ignoring on pre-Vista. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if ( g_uNtVerCombined >= SUP_NT_VER_VISTA +# else + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0) +# endif + || g_pfnObRegisterCallbacks) + { + LogRel(("vboxdrv: Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)\n", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex)); + rc = RTErrInfoAddF(pErrInfo, VERR_SUPDRV_HARDENING_EVIL_HANDLE, + *pErrInfo->pszMsg + ? "\nFound evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)" + : "Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex); + + /* Try add the process name. */ + PEPROCESS pOffendingProcess; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pOffendingProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszName = (const char *)PsGetProcessImageFileName(pOffendingProcess); + if (pszName && *pszName) + rc = RTErrInfoAddF(pErrInfo, rc, " [%s]", pszName); + + ObDereferenceObject(pOffendingProcess); + } + } + } + + RTMemFree(pbBuf); + return rc; +} + + +/** + * Checks if the current process checks out as a VM process stub. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure. This is upgraded to a + * final protection kind (state) on success. + */ +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect) +{ + AssertReturn(PsGetProcessId(PsGetCurrentProcess()) == pNtProtect->AvlCore.Key, VERR_INTERNAL_ERROR_3); + + /* + * Do the verification. The handle restriction checks are only preformed + * on VM processes. + */ + int rc = VINF_SUCCESS; + PSUPDRVNTERRORINFO pErrorInfo = (PSUPDRVNTERRORINFO)RTMemAllocZ(sizeof(*pErrorInfo)); + if (RT_SUCCESS(rc)) + { + pErrorInfo->hProcessId = PsGetCurrentProcessId(); + pErrorInfo->hThreadId = PsGetCurrentThreadId(); + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo)); + + if (pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectRestrictHandlesToProcessAndThread(pNtProtect, &ErrInfo); + if (RT_SUCCESS(rc)) + { + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_VERIFY_ONLY, 0 /*fFlags*/, + NULL /*pcFixes*/, &ErrInfo); + if (RT_SUCCESS(rc) && pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectVerifyStubForVmProcess(pNtProtect, &ErrInfo); + } + } + else + rc = VERR_NO_MEMORY; + + /* + * Upgrade and return. + */ + HANDLE hOpenTid = PsGetCurrentThreadId(); + RTSpinlockAcquire(g_hNtProtectLock); + + /* Stub process verficiation is pretty much straight forward. */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + { + pNtProtect->enmProcessKind = RT_SUCCESS(rc) ? kSupDrvNtProtectKind_StubSpawning : kSupDrvNtProtectKind_StubDead; + pNtProtect->hOpenTid = hOpenTid; + } + /* The VM process verification is a little bit more complicated + because we need to drop the parent process reference as well. */ + else if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + AssertRelease(pNtProtect->cRefs >= 2); /* Parent + Caller */ + PSUPDRVNTPROTECT pParent = pNtProtect->u.pParent; + AssertRelease(pParent); + AssertRelease(pParent->u.pParent == pNtProtect); + AssertRelease(pParent->enmProcessKind == kSupDrvNtProtectKind_StubParent); + pParent->u.pParent = NULL; + + pNtProtect->u.pParent = NULL; + ASMAtomicDecU32(&pNtProtect->cRefs); + + if (RT_SUCCESS(rc)) + { + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessConfirmed; + pNtProtect->hOpenTid = hOpenTid; + } + else + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* Since the stub and VM processes are only supposed to have one thread, + we're not supposed to be subject to any races from within the processes. + + There is a race between VM process verification and the stub process + exiting, though. We require the stub process to be alive until the new + VM process has made it thru the validation. So, when the stub + terminates the notification handler will change the state of both stub + and VM process to dead. + + Also, I'm not entirely certain where the process + termination notification is triggered from, so that can theorically + create a race in both cases. */ + else + { + AssertReleaseMsg( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubDead + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessDead, + ("enmProcessKind=%d rc=%Rrc\n", pNtProtect->enmProcessKind, rc)); + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; /* There should be no races here. */ + } + + RTSpinlockRelease(g_hNtProtectLock); + + /* + * Free error info on success, keep it on failure. + */ + if (RT_SUCCESS(rc)) + RTMemFree(pErrorInfo); + else if (pErrorInfo) + { + pErrorInfo->cchErrorInfo = (uint32_t)strlen(pErrorInfo->szErrorInfo); + if (!pErrorInfo->cchErrorInfo) + pErrorInfo->cchErrorInfo = (uint32_t)RTStrPrintf(pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo), + "supdrvNtProtectVerifyProcess: rc=%d", rc); + RTLogWriteDebugger(pErrorInfo->szErrorInfo, pErrorInfo->cchErrorInfo); + + int rc2 = RTSemMutexRequest(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc2)) + { + pErrorInfo->uCreatedMsTs = RTTimeMilliTS(); + + /* Free old entries. */ + PSUPDRVNTERRORINFO pCur; + while ( (pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL + && (int64_t)(pErrorInfo->uCreatedMsTs - pCur->uCreatedMsTs) > 60000 /*60sec*/) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + /* Insert our new entry. */ + RTListAppend(&g_ErrorInfoHead, &pErrorInfo->ListEntry); + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + RTMemFree(pErrorInfo); + } + + return rc; +} + + +# ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + +/** + * Checks if the current process is being debugged. + * @return @c true if debugged, @c false if not. + */ +static bool supdrvNtIsDebuggerAttached(void) +{ + return PsIsProcessBeingDebugged(PsGetCurrentProcess()) != FALSE; +} + +# endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** + * Terminates the hardening bits. + */ +static void supdrvNtProtectTerm(void) +{ + /* + * Stop intercepting process and thread handle creation calls. + */ + if (g_pvObCallbacksCookie) + { + g_pfnObUnRegisterCallbacks(g_pvObCallbacksCookie); + g_pvObCallbacksCookie = NULL; + } + + /* + * Stop intercepting process creation and termination notifications. + */ + NTSTATUS rcNt; + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + AssertMsg(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + + Assert(g_NtProtectTree == NULL); + + /* + * Clean up globals. + */ + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + + PSUPDRVNTERRORINFO pCur; + while ((pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + supHardenedWinTermImageVerifier(); +} + +# ifdef RT_ARCH_X86 +DECLASM(void) supdrvNtQueryVirtualMemory_0xAF(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB0(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB1(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB2(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB3(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB4(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB5(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB6(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB7(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB8(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB9(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBA(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBB(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBC(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBD(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBE(void); +# elif defined(RT_ARCH_AMD64) +DECLASM(void) supdrvNtQueryVirtualMemory_0x1F(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x20(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x21(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x22(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x23(void); +extern "C" NTSYSAPI NTSTATUS NTAPI ZwRequestWaitReplyPort(HANDLE, PVOID, PVOID); +# endif + + +/** + * Initalizes the hardening bits. + * + * @returns NT status code. + */ +static NTSTATUS supdrvNtProtectInit(void) +{ + /* + * Initialize the globals. + */ + + /* The NT version. */ + ULONG uMajor, uMinor, uBuild; + PsGetVersion(&uMajor, &uMinor, &uBuild, NULL); + g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(uMajor, uMinor, uBuild, 0, 0); + + /* Resolve methods we want but isn't available everywhere. */ + UNICODE_STRING RoutineName; + + RtlInitUnicodeString(&RoutineName, L"ObGetObjectType"); + g_pfnObGetObjectType = (PFNOBGETOBJECTTYPE)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObRegisterCallbacks"); + g_pfnObRegisterCallbacks = (PFNOBREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObUnRegisterCallbacks"); + g_pfnObUnRegisterCallbacks = (PFNOBUNREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsSetCreateProcessNotifyRoutineEx"); + g_pfnPsSetCreateProcessNotifyRoutineEx = (PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsReferenceProcessFilePointer"); + g_pfnPsReferenceProcessFilePointer = (PFNPSREFERENCEPROCESSFILEPOINTER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsIsProtectedProcessLight"); + g_pfnPsIsProtectedProcessLight = (PFNPSISPROTECTEDPROCESSLIGHT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwAlpcCreatePort"); + g_pfnZwAlpcCreatePort = (PFNZWALPCCREATEPORT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwQueryVirtualMemory"); /* Yes, using Zw version here. */ + g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)MmGetSystemRoutineAddress(&RoutineName); + if (!g_pfnNtQueryVirtualMemory && g_uNtVerCombined < SUP_NT_VER_VISTA) + { + /* XP & W2K3 doesn't have this function exported, so we've cooked up a + few alternative in the assembly helper file that uses the code in + ZwReadFile with a different eax value. We figure the syscall number + by inspecting ZwQueryVolumeInformationFile as it's the next number. */ +# ifdef RT_ARCH_X86 + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwQueryVolumeInformationFile; + if (*pbCode == 0xb8) /* mov eax, dword */ + switch (*(uint32_t const *)&pbCode[1]) + { + case 0xb0: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xAF; break; /* just in case */ + case 0xb1: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB0; break; /* just in case */ + case 0xb2: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB1; break; /* just in case */ + case 0xb3: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* XP SP3 */ + case 0xb4: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* just in case */ + case 0xb5: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB3; break; /* just in case */ + case 0xb6: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB4; break; /* just in case */ + case 0xb7: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB5; break; /* just in case */ + case 0xb8: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB6; break; /* just in case */ + case 0xb9: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB7; break; /* just in case */ + case 0xba: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB8; break; /* just in case */ + case 0xbb: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBA; break; /* W2K3 R2 SP2 */ + case 0xbc: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBB; break; /* just in case */ + case 0xbd: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBC; break; /* just in case */ + case 0xbe: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBD; break; /* just in case */ + case 0xbf: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBE; break; /* just in case */ + } +# elif defined(RT_ARCH_AMD64) + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwRequestWaitReplyPort; + if ( pbCode[ 0] == 0x48 /* mov rax, rsp */ + && pbCode[ 1] == 0x8b + && pbCode[ 2] == 0xc4 + && pbCode[ 3] == 0xfa /* cli */ + && pbCode[ 4] == 0x48 /* sub rsp, 10h */ + && pbCode[ 5] == 0x83 + && pbCode[ 6] == 0xec + && pbCode[ 7] == 0x10 + && pbCode[ 8] == 0x50 /* push rax */ + && pbCode[ 9] == 0x9c /* pushfq */ + && pbCode[10] == 0x6a /* push 10 */ + && pbCode[11] == 0x10 + && pbCode[12] == 0x48 /* lea rax, [nt!KiServiceLinkage] */ + && pbCode[13] == 0x8d + && pbCode[14] == 0x05 + && pbCode[19] == 0x50 /* push rax */ + && pbCode[20] == 0xb8 /* mov eax,1fh <- the syscall no. */ + /*&& pbCode[21] == 0x1f*/ + && pbCode[22] == 0x00 + && pbCode[23] == 0x00 + && pbCode[24] == 0x00 + && pbCode[25] == 0xe9 /* jmp KiServiceInternal */ + ) + { + uint8_t const *pbKiServiceInternal = &pbCode[30] + *(int32_t const *)&pbCode[26]; + uint8_t const *pbKiServiceLinkage = &pbCode[19] + *(int32_t const *)&pbCode[15]; + if (*pbKiServiceLinkage == 0xc3) + { + g_pfnKiServiceInternal = (PFNRT)pbKiServiceInternal; + g_pfnKiServiceLinkage = (PFNRT)pbKiServiceLinkage; + switch (pbCode[21]) + { + case 0x1e: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x1F; break; + case 0x1f: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x20; break; + case 0x20: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x21; break; + case 0x21: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x22; break; + case 0x22: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x23; break; + } + } + } +# endif + } + if (!g_pfnNtQueryVirtualMemory) + { + LogRel(("vboxdrv: Cannot locate ZwQueryVirtualMemory in ntoskrnl, nor were we able to cook up a replacement.\n")); + return STATUS_PROCEDURE_NOT_FOUND; + } + +# ifdef VBOX_STRICT + if ( g_uNtVerCombined >= SUP_NT_VER_W70 + && ( g_pfnObGetObjectType == NULL + || g_pfnZwAlpcCreatePort == NULL) ) + { + LogRel(("vboxdrv: g_pfnObGetObjectType=%p g_pfnZwAlpcCreatePort=%p.\n", g_pfnObGetObjectType, g_pfnZwAlpcCreatePort)); + return STATUS_PROCEDURE_NOT_FOUND; + } +# endif + + /* LPC object type. */ + g_pAlpcPortObjectType1 = *LpcPortObjectType; + + /* The spinlock protecting our structures. */ + int rc = RTSpinlockCreate(&g_hNtProtectLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtProtectLock"); + if (RT_FAILURE(rc)) + return VBoxDrvNtErr2NtStatus(rc); + g_NtProtectTree = NULL; + + NTSTATUS rcNt; + + /* The mutex protecting the error information. */ + RTListInit(&g_ErrorInfoHead); + rc = RTSemMutexCreate(&g_hErrorInfoLock); + if (RT_SUCCESS(rc)) + { + /* Image stuff + certificates. */ + rc = supHardenedWinInitImageVerifier(NULL); + if (RT_SUCCESS(rc)) + { + /* + * Intercept process creation and termination. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, FALSE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, FALSE /*fRemove*/); + if (NT_SUCCESS(rcNt)) + { + /* + * Intercept process and thread handle creation calls. + * The preferred method is only available on Vista SP1+. + */ + if (g_pfnObRegisterCallbacks && g_pfnObUnRegisterCallbacks) + { + static OB_OPERATION_REGISTRATION s_aObOperations[] = + { + { + 0, /* PsProcessType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ProcessHandlePre, + supdrvNtProtectCallback_ProcessHandlePost, + }, + { + 0, /* PsThreadType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ThreadHandlePre, + supdrvNtProtectCallback_ThreadHandlePost, + }, + }; + s_aObOperations[0].ObjectType = PsProcessType; + s_aObOperations[1].ObjectType = PsThreadType; + + static OB_CALLBACK_REGISTRATION s_ObCallbackReg = + { + /* .Version = */ OB_FLT_REGISTRATION_VERSION, + /* .OperationRegistrationCount = */ RT_ELEMENTS(s_aObOperations), + /* .Altitude.Length = */ { 0, + /* .Altitude.MaximumLength = */ 0, + /* .Altitude.Buffer = */ NULL }, + /* .RegistrationContext = */ NULL, + /* .OperationRegistration = */ &s_aObOperations[0] + }; + static WCHAR const *s_apwszAltitudes[] = /** @todo get a valid number */ + { + L"48596.98940", L"46935.19485", L"49739.39704", L"40334.74976", + L"66667.98940", L"69888.19485", L"69889.39704", L"60364.74976", + L"85780.98940", L"88978.19485", L"89939.39704", L"80320.74976", + L"329879.98940", L"326787.19485", L"328915.39704", L"320314.74976", + }; + + rcNt = STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszAltitudes) && rcNt == STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; i++) + { + s_ObCallbackReg.Altitude.Buffer = (WCHAR *)s_apwszAltitudes[i]; + s_ObCallbackReg.Altitude.Length = (uint16_t)RTUtf16Len(s_apwszAltitudes[i]) * sizeof(WCHAR); + s_ObCallbackReg.Altitude.MaximumLength = s_ObCallbackReg.Altitude.Length + sizeof(WCHAR); + + rcNt = g_pfnObRegisterCallbacks(&s_ObCallbackReg, &g_pvObCallbacksCookie); + if (NT_SUCCESS(rcNt)) + { + /* + * Happy ending. + */ + return STATUS_SUCCESS; + } + } + LogRel(("vboxdrv: ObRegisterCallbacks failed with rcNt=%#x\n", rcNt)); + g_pvObCallbacksCookie = NULL; + } + else + { + /* + * For the time being, we do not implement extra process + * protection on pre-Vista-SP1 systems as they are lacking + * necessary KPIs. XP is end of life, we do not wish to + * spend more time on it, so we don't put up a fuss there. + * Vista users without SP1 can install SP1 (or later), darn it, + * so refuse to load. + */ + /** @todo Hack up an XP solution - will require hooking kernel APIs or doing bad + * stuff to a couple of object types. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if (g_uNtVerCombined >= SUP_NT_VER_VISTA) +# else + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) +# endif + { + DbgPrint("vboxdrv: ObRegisterCallbacks was not found. Please make sure you got the latest updates and service packs installed\n"); + rcNt = STATUS_SXS_VERSION_CONFLICT; + } + else + { + Log(("vboxdrv: ObRegisterCallbacks was not found; ignored pre-Vista\n")); + return rcNt = STATUS_SUCCESS; + } + g_pvObCallbacksCookie = NULL; + } + + /* + * Drop process create/term notifications. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + } + else + LogRel(("vboxdrv: PsSetCreateProcessNotifyRoutine%s failed with rcNt=%#x\n", + g_pfnPsSetCreateProcessNotifyRoutineEx ? "Ex" : "", rcNt)); + supHardenedWinTermImageVerifier(); + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + return rcNt; +} + +#endif /* VBOX_WITH_HARDENING */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm new file mode 100644 index 00000000..f899c815 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm @@ -0,0 +1,119 @@ +; $Id: SUPDrvA-win.asm $ +;; @file +; VirtualBox Support Driver - Windows NT specific assembly parts. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%include "iprt/asmdefs.mac" + +BEGINCODE + +%ifdef VBOX_WITH_HARDENING + + %ifdef RT_ARCH_X86 +; +; Faking up ZwQueryVirtualMemory on XP and W2K3 where it's not exported. +; Using ZwOpenFile as a helper as it has the name number of parameters. +; +extern IMPNAME(ZwOpenFile@24) + +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(ZwOpenFile@24) + 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 + +%endif ; VBOX_WITH_HARDENING + 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 00000000..0f953e38 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h @@ -0,0 +1,303 @@ +/* $Id: SUPHardenedVerify-win.h $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h +#define VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/crypto/x509.h> +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +# include <iprt/ldr.h> +# endif +#endif + + +RT_C_DECLS_BEGIN + +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +DECLHIDDEN(int) supHardenedWinInitImageVerifier(PRTERRINFO pErrInfo); +DECLHIDDEN(void) supHardenedWinTermImageVerifier(void); +DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName); +DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName); + + +typedef enum SUPHARDNTVPKIND +{ + SUPHARDNTVPKIND_VERIFY_ONLY = 1, + SUPHARDNTVPKIND_CHILD_PURIFICATION, + SUPHARDNTVPKIND_SELF_PURIFICATION, + SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED, + SUPHARDNTVPKIND_32BIT_HACK = 0x7fffffff +} SUPHARDNTVPKIND; +/** @name SUPHARDNTVP_F_XXX - Flags for supHardenedWinVerifyProcess + * @{ */ +/** Replace unwanted executable memory allocations with a new one that's filled + * with a safe read-write copy (default is just to free it). + * + * This is one way we attempt to work around buggy protection software that + * either result in host BSOD or VBox application malfunction. Here the current + * shit list: + * - Trend Micro's data protection software includes a buggy driver called + * sakfile.sys that has been observed crashing accessing user memory that we + * probably freed. I'd love to report this to Trend Micro, but unfortunately + * they doesn't advertise (or have?) an email address for reporting security + * vulnerabilities in the their software. Having wasted time looking and not + * very sorry for having to disclosing the bug here. + * - Maybe one more. + */ +#define SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW RT_BIT_32(0) +/** @} */ +DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, + uint32_t fFlags, uint32_t *pcFixes, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo); + +DECLHIDDEN(bool) supHardViUtf16PathIsEqualEx(PCRTUTF16 pawcLeft, size_t cwcLeft, const char *pszRight); +DECLHIDDEN(bool) supHardViUniStrPathStartsWithUniStr(UNICODE_STRING const *pUniStrLeft, + UNICODE_STRING const *pUniStrRight, bool fCheckSlash); +DECLHIDDEN(bool) supHardViUtf16PathStartsWithEx(PCRTUTF16 pwszLeft, uint32_t cwcLeft, + PCRTUTF16 pwszRight, uint32_t cwcRight, bool fCheckSlash); +DECLHIDDEN(bool) supHardViIsAppPatchDir(PCRTUTF16 pwszPath, uint32_t cwcName); + + +/** + * SUP image verifier loader reader instance. + */ +typedef struct SUPHNTVIRDR +{ + /** The core reader structure. */ + RTLDRREADER Core; + /** The file handle. */ + HANDLE hFile; + /** Handle to event sempahore in case we're force to deal with asynchronous I/O. */ + HANDLE hEvent; + /** Current file offset. */ + RTFOFF off; + /** The file size. */ + uint64_t cbFile; + /** Flags for the verification callback, SUPHNTVI_F_XXX. */ + uint32_t fFlags; + /** Number of signatures that verified okay. */ + uint16_t cOkaySignatures; + /** Number of signatures that couldn't be successfully verified (time stamp + * issues, no certificate path, etc) but weren't fatal. */ + uint16_t cNokSignatures; + /** Total number of signatures. */ + uint16_t cTotalSignatures; + /** The current signature (for passing to supHardNtViCertVerifyCallback). */ + uint16_t iCurSignature; + /** The last non-fatal signature failure. */ + int rcLastSignatureFailure; + /** Log name. */ + char szFilename[1]; +} SUPHNTVIRDR; +/** Pointer to an SUP image verifier loader reader instance. */ +typedef SUPHNTVIRDR *PSUPHNTVIRDR; +DECLHIDDEN(int) supHardNtViRdrCreate(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PSUPHNTVIRDR *ppNtViRdr); +DECLHIDDEN(bool) supHardenedWinIsWinVerifyTrustCallable(void); +DECLHIDDEN(int) supHardenedWinVerifyImageTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, int rc, + bool *pfWinVerifyTrust, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByHandleNoName(HANDLE hFile, uint32_t fFlags, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardenedWinVerifyImageByLdrMod(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, PSUPHNTVIRDR pNtViRdr, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, 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) +/** The file owner must be TrustedInstaller on Vista+. */ +# define SUPHNTVI_F_TRUSTED_INSTALLER_OWNER RT_BIT(4) +/** Ignore the image architecture (otherwise it must match the verification + * code). Used with resource images and such. */ +# define SUPHNTVI_F_IGNORE_ARCHITECTURE RT_BIT(30) +/** Raw-mode context image, always 32-bit. */ +# define SUPHNTVI_F_RC_IMAGE RT_BIT(31) +/** @} */ + +/* Array in SUPHardenedVerifyImage-win.cpp */ +extern const RTSTRTUPLE g_aSupNtViBlacklistedDlls[]; + +/** + * Loader cache entry. + * + * This is for avoiding loading and signature checking a file multiple times, + * due to multiple passes thru the process validation code (and syscall import + * code of NTDLL). + */ +typedef struct SUPHNTLDRCACHEENTRY +{ + /** The file name (from g_apszSupNtVpAllowedDlls or + * g_apszSupNtVpAllowedVmExes). */ + const char *pszName; + /** Load module associated with the image during content verfication. */ + RTLDRMOD hLdrMod; + /** The file reader. */ + PSUPHNTVIRDR pNtViRdr; + /** The module file handle, if we've opened it. + * (pNtviRdr does not close the file handle on destruction.) */ + HANDLE hFile; + /** Bits buffer. */ + uint8_t *pbBits; + /** Set if verified. */ + bool fVerified; + /** Whether we've got valid cacheable image bits. */ + bool fValidBits; + /** The image base address. */ + uintptr_t uImageBase; +} SUPHNTLDRCACHEENTRY; +/** Pointer to a loader cache entry. */ +typedef SUPHNTLDRCACHEENTRY *PSUPHNTLDRCACHEENTRY; +DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo); +DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits, RTLDRADDR uBaseAddress, + PFNRTLDRIMPORT pfnGetImport, void *pvUser, PRTERRINFO pErrInfo); + + +/** 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; +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +extern SUPSYSROOTDIRBUF g_ProgramFilesNtPath; +extern SUPSYSROOTDIRBUF g_CommonFilesNtPath; +# if ARCH_BITS == 64 +extern SUPSYSROOTDIRBUF g_ProgramFilesX86NtPath; +extern SUPSYSROOTDIRBUF g_CommonFilesX86NtPath; +# endif +#endif /* IN_RING3 && !VBOX_PERMIT_EVEN_MORE */ +extern SUPSYSROOTDIRBUF g_SupLibHardenedExeNtPath; +extern SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath; + +# ifdef IN_RING0 +/** Pointer to NtQueryVirtualMemory. */ +typedef DECLCALLBACKPTR_EX(NTSTATUS, NTAPI, PFNNTQUERYVIRTUALMEMORY,(HANDLE, void const *, MEMORY_INFORMATION_CLASS, + PVOID, SIZE_T, PSIZE_T)); +extern PFNNTQUERYVIRTUALMEMORY g_pfnNtQueryVirtualMemory; +# endif + +# 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) \ + | (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; + +/** @name NT version constants for less-than checks. + * @{ */ +/** Combined NT version number for XP. */ +#define SUP_NT_VER_XP SUP_MAKE_NT_VER_SIMPLE(5,1) +/** Combined NT version number for Windows server 2003 & XP64. */ +#define SUP_NT_VER_W2K3 SUP_MAKE_NT_VER_SIMPLE(5,2) +/** Combined NT version number for Vista. */ +#define SUP_NT_VER_VISTA SUP_MAKE_NT_VER_SIMPLE(6,0) +/** Combined NT version number for Vista with SP1. */ +#define SUP_NT_VER_VISTA_SP1 SUP_MAKE_NT_VER_COMBINED(6,0,6001,1,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 suplibHardenedMemComp memcmp +# define suplibHardenedMemCopy memcpy +# define suplibHardenedMemSet memset +# define suplibHardenedStrCopy strcpy +# define suplibHardenedStrLen strlen +# define suplibHardenedStrCat strcat +# define suplibHardenedStrCmp strcmp +# define suplibHardenedStrNCmp strncmp +# 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 +# endif +# endif /* IN_SUP_HARDENED_R3 */ + +#endif /* SUP_CERTIFICATES_ONLY */ + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_Support_win_SUPHardenedVerify_win_h */ + 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 00000000..747d975e --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp @@ -0,0 +1,3090 @@ +/* $Id: SUPHardenedVerifyImage-win.cpp $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Image Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IN_RING0 +# ifndef IPRT_NT_MAP_TO_ZW +# define IPRT_NT_MAP_TO_ZW +# endif +# 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/utf16.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 + + +#if defined(VBOX_PERMIT_EVEN_MORE) && !defined(VBOX_PERMIT_MORE) +# error "VBOX_PERMIT_EVEN_MORE without VBOX_PERMIT_MORE!" +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +#ifdef IN_RING3 +typedef DECLCALLBACKPTR_EX(LONG, WINAPI, PFNWINVERIFYTRUST,(HWND hwnd, GUID const *pgActionID, PVOID pWVTData)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINACQUIRECONTEXT,(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINACQUIRECONTEXT2,(HCATADMIN *phCatAdmin, const GUID *pGuidSubsystem, + PCWSTR pwszHashAlgorithm, + struct _CERT_STRONG_SIGN_PARA const *pStrongHashPolicy, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE,(HANDLE hFile, DWORD *pcbHash, BYTE *pbHash, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINCALCHASHFROMFILEHANDLE2,(HCATADMIN hCatAdmin, HANDLE hFile, + DWORD *pcbHash, BYTE *pbHash, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(HCATINFO, WINAPI, PFNCRYPTCATADMINENUMCATALOGFROMHASH,(HCATADMIN hCatAdmin, BYTE *pbHash, DWORD cbHash, + DWORD dwFlags, HCATINFO *phPrevCatInfo)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATADMINRELEASECATALOGCONTEXT,(HCATADMIN hCatAdmin, HCATINFO hCatInfo, + DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATDADMINRELEASECONTEXT,(HCATADMIN hCatAdmin, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCRYPTCATCATALOGINFOFROMCONTEXT,(HCATINFO hCatInfo, CATALOG_INFO *psCatInfo, + DWORD dwFlags)); + +typedef DECLCALLBACKPTR_EX(HCERTSTORE, WINAPI, PFNCERTOPENSTORE,(PCSTR pszStoreProvider, DWORD dwEncodingType, + HCRYPTPROV_LEGACY hCryptProv, DWORD dwFlags, const void *pvParam)); +typedef DECLCALLBACKPTR_EX(BOOL, WINAPI, PFNCERTCLOSESTORE,(HCERTSTORE hCertStore, DWORD dwFlags)); +typedef DECLCALLBACKPTR_EX(PCCERT_CONTEXT, WINAPI, PFNCERTENUMCERTIFICATESINSTORE,(HCERTSTORE hCertStore, + PCCERT_CONTEXT pPrevCertContext)); + +typedef DECLCALLBACKPTR_EX(NTSTATUS, WINAPI, PFNBCRYPTOPENALGORTIHMPROVIDER,(BCRYPT_ALG_HANDLE *phAlgo, PCWSTR pwszAlgoId, + PCWSTR pwszImpl, DWORD dwFlags)); +#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; +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +/** The full 'Program Files' path. */ +SUPSYSROOTDIRBUF g_ProgramFilesNtPath; +# ifdef RT_ARCH_AMD64 +/** The full 'Program Files (x86)' path. */ +SUPSYSROOTDIRBUF g_ProgramFilesX86NtPath; +# endif +/** The full 'Common Files' path. */ +SUPSYSROOTDIRBUF g_CommonFilesNtPath; +# ifdef RT_ARCH_AMD64 +/** The full 'Common Files (x86)' path. */ +SUPSYSROOTDIRBUF g_CommonFilesX86NtPath; +# endif +#endif /* IN_RING3 && !VBOX_PERMIT_MORE*/ + +/** + * Blacklisted DLL names. + */ +const RTSTRTUPLE g_aSupNtViBlacklistedDlls[] = +{ + { RT_STR_TUPLE("SCROBJ.dll") }, + { NULL, 0 } /* terminator entry */ +}; + + +static union +{ + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; +} +/** The TrustedInstaller SID (Vista+). */ + g_TrustedInstallerSid, +/** Local system ID (S-1-5-21). */ + g_LocalSystemSid, +/** Builtin Administrators group alias (S-1-5-32-544). */ + g_AdminsGroupSid; + + +/** 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 +/** Timestamp hack working around issues with old DLLs that we ship. + * See supHardenedWinVerifyImageByHandle() for details. */ +static uint64_t g_uBuildTimestampHack = 0; +#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; + +/** Where we store the TLS entry for detecting WinVerifyTrustRecursion. */ +static uint32_t g_iTlsWinVerifyTrustRecursion = UINT32_MAX; +/** Fallback WinVerifyTrust recursion protection. */ +static uint32_t volatile g_idActiveThread = UINT32_MAX; + +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef IN_RING3 +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust, HRESULT *phrcWinVerifyTrust); +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); + NTSTATUS rcNt; + + /* Check for type overflow (paranoia). */ + if ((ULONG)cb != cb) + return VERR_OUT_OF_RANGE; + +#ifdef IN_RING3 + /* Make sure the event semaphore is reset (normally we don't use one). */ + if (pNtViRdr->hEvent) + { + rcNt = NtClearEvent(pNtViRdr->hEvent); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + } +#endif + + /* Perform the read. */ + LARGE_INTEGER offNt; + offNt.QuadPart = off; + + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + rcNt = NtReadFile(pNtViRdr->hFile, + pNtViRdr->hEvent, + NULL /*ApcRoutine*/, + NULL /*ApcContext*/, + &Ios, + pvBuf, + (ULONG)cb, + &offNt, + NULL); + +#ifdef IN_RING0 + /* In ring-0 the handles shall be synchronized and not alertable. */ + AssertMsg(rcNt == STATUS_SUCCESS || !NT_SUCCESS(rcNt), ("%#x\n", rcNt)); +#else + /* In ring-3 we like our handles synchronized and non-alertable, but we + sometimes have to take what we can get. So, deal with pending I/O as + best we can. */ + if (rcNt == STATUS_PENDING) + rcNt = NtWaitForSingleObject(pNtViRdr->hEvent ? pNtViRdr->hEvent : pNtViRdr->hFile, FALSE /*Alertable*/, NULL); +#endif + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* We require the caller to not read beyond the end of the file since + we don't have any way to communicate that we've read less that + requested. */ + if (Ios.Information == cb) + { + pNtViRdr->off = off + cb; /* (just for show) */ + 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; +} + + +/** @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(uint64_t) 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) +{ + RT_NOREF2(pReader, ppvBits); + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDRREADER::pfnUnmap */ +static DECLCALLBACK(int) supHardNtViRdrUnmap(PRTLDRREADER pReader, const void *pvBits) +{ + RT_NOREF2(pReader, 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; +#ifdef IN_RING3 + if (pNtViRdr->hEvent) + { + NtClose(pNtViRdr->hEvent); + pNtViRdr->hEvent = NULL; + } +#endif + 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. + */ +DECLHIDDEN(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; + + /* + * Figure the file mode so we can see whether we'll be needing an event + * semaphore for waiting on reads. This may happen in very unlikely + * NtCreateSection scenarios. + */ +#if defined(IN_RING3) || defined(VBOX_STRICT) + Ios.Status = STATUS_UNSUCCESSFUL; + ULONG fMode; + rcNt = NtQueryInformationFile(hFile, &Ios, &fMode, sizeof(fMode), FileModeInformation); + if (!NT_SUCCESS(rcNt) || !NT_SUCCESS(Ios.Status)) + return VERR_SUP_VP_FILE_MODE_ERROR; +#endif + + HANDLE hEvent = NULL; +#ifdef IN_RING3 + if (!(fMode & (FILE_SYNCHRONOUS_IO_NONALERT | FILE_SYNCHRONOUS_IO_ALERT))) + { + rcNt = NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, NULL, NotificationEvent, FALSE); + if (!NT_SUCCESS(rcNt)) + return VERR_SUP_VP_CREATE_READ_EVT_SEM_FAILED; + } +#else + Assert(fMode & FILE_SYNCHRONOUS_IO_NONALERT); +#endif + + /* + * 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) + { +#ifdef IN_RING3 + if (hEvent != NULL) + NtClose(hEvent); +#endif + 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->hEvent = hEvent; + pNtViRdr->off = 0; + pNtViRdr->cbFile = (uint64_t)StdInfo.EndOfFile.QuadPart; + pNtViRdr->fFlags = fFlags; + *ppNtViRdr = pNtViRdr; + return VINF_SUCCESS; +} + + +/** + * Checks if the file is owned by TrustedInstaller (Vista+) or similar. + * + * @returns true if owned by TrustedInstaller of pre-Vista, false if not. + * + * @param hFile The handle to the file. + * @param pwszName The name of the file. + */ +static bool supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(HANDLE hFile, PCRTUTF16 pwszName) +{ + if (g_uNtVerCombined < SUP_NT_VER_VISTA) + return true; + + /* + * Get the ownership information. + */ + union + { + SECURITY_DESCRIPTOR_RELATIVE Rel; + SECURITY_DESCRIPTOR Abs; + uint8_t abView[256]; + } uBuf; + ULONG cbActual; + NTSTATUS rcNt = NtQuerySecurityObject(hFile, OWNER_SECURITY_INFORMATION, &uBuf.Abs, sizeof(uBuf), &cbActual); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("NtQuerySecurityObject failed with rcNt=%#x on '%ls'\n", rcNt, pwszName)); + return false; + } + + /* + * Check the owner. + * + * Initially we wished to only allow TrustedInstaller. But a Windows CAPI + * plugin "Program Files\Tumbleweed\Desktop Validator\tmwdcapiclient.dll" + * turned up owned by the local system user, and we cannot operate without + * the plugin loaded once it's installed (WinVerityTrust fails). + * + * We'd like to avoid allowing Builtin\Administrators here since it's the + * default owner of anything an admin user creates (at least when elevated). + * Seems windows update or someone ends up installing or modifying system + * DLL ownership to this group, so for system32 and winsxs it's unavoidable. + * And, not surprise, a bunch of products, including AV, firewalls and similar + * ends up with their files installed with this group as owner. For instance + * if we wish to have NAT continue working, we need to allow this. + * + * Hopefully, we can limit the allowed files to these owners though, so + * we won't be subject to ordinary (non-admin, or not elevated) users + * downloading or be tricked into putting evil DLLs around the place... + */ + PSID pOwner = uBuf.Rel.Control & SE_SELF_RELATIVE ? &uBuf.abView[uBuf.Rel.Owner] : uBuf.Abs.Owner; + Assert((uintptr_t)pOwner - (uintptr_t)&uBuf < sizeof(uBuf) - sizeof(SID)); + if (RtlEqualSid(pOwner, &g_TrustedInstallerSid)) + return true; + if (RtlEqualSid(pOwner, &g_LocalSystemSid)) + return true; + if (RtlEqualSid(pOwner, &g_AdminsGroupSid)) + { + SUP_DPRINTF(("%ls: Owner is administrators group.\n", pwszName)); + return true; + } + + SUP_DPRINTF(("%ls: Owner is not trusted installer (%.*Rhxs)\n", + pwszName, ((uint8_t *)pOwner)[1] /*SubAuthorityCount*/ * sizeof(ULONG) + 8, pOwner)); + RT_NOREF1(pwszName); + return false; +} + + +/** + * Simple case insensitive UTF-16 / ASCII path compare. + * + * @returns true if equal, false if not. + * @param pawcLeft The UTF-16 path string, not necessarily null + * terminated. + * @param cwcLeft The number of chars in the left string, + * RTSTR_MAX if unknown but terminated. + * @param pszRight The ascii string. + */ +DECLHIDDEN(bool) supHardViUtf16PathIsEqualEx(PCRTUTF16 pawcLeft, size_t cwcLeft, const char *pszRight) +{ + for (;;) + { + RTUTF16 wc; + if (cwcLeft-- > 0) + wc =*pawcLeft++; + else + wc = 0; + 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 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) +{ + return supHardViUtf16PathIsEqualEx(pwszLeft, RTSTR_MAX, pszRight); +} + + +#if 0 /* unused */ +/** + * 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; +} +#endif + + +/** + * Simple case insensitive UTF-16 / ASCII starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pwszLeft The UTF-16 path string. + * @param pszRight The ascii prefix string. + */ +static bool supHardViUtf16PathStartsWithAscii(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; + } + } + } + } +} + + +/** + * Simple case insensitive UNICODE_STRING starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pwszLeft The path to check. + * @param cwcLeft The length of @a pwszLeft + * @param pwszRight The starts-with path. + * @param cwcRight The length of @a pwszRight. + * @param fCheckSlash Check for a slash following the prefix. + */ +DECLHIDDEN(bool) supHardViUtf16PathStartsWithEx(PCRTUTF16 pwszLeft, uint32_t cwcLeft, + PCRTUTF16 pwszRight, uint32_t cwcRight, bool fCheckSlash) +{ + if (cwcLeft < cwcRight || !cwcRight || !pwszRight) + return false; + + /* See if we can get away with a case sensitive compare first. */ + if (memcmp(pwszLeft, pwszRight, cwcRight * sizeof(RTUTF16)) == 0) + pwszLeft += cwcRight; + else + { + /* No luck, do a slow case insensitive comapre. */ + uint32_t cLeft = cwcRight; + while (cLeft-- > 0) + { + RTUTF16 wcLeft = *pwszLeft++; + RTUTF16 wcRight = *pwszRight++; + if (wcLeft != wcRight) + { + wcLeft = wcLeft < 0x80 ? wcLeft == '/' ? '\\' : RT_C_TO_LOWER(wcLeft) : wcLeft; + wcRight = wcRight < 0x80 ? wcRight == '/' ? '\\' : RT_C_TO_LOWER(wcRight) : wcRight; + if (wcLeft != wcRight) + return false; + } + } + } + + /* Check for slash following the prefix, if request. */ + if ( !fCheckSlash + || *pwszLeft == '\\' + || *pwszLeft == '/') + return true; + return false; +} + + +/** + * Simple case insensitive UNICODE_STRING starts-with path predicate. + * + * @returns true if starts with given string, false if not. + * @param pUniStrLeft The path to check. + * @param pUniStrRight The starts-with path. + * @param fCheckSlash Check for a slash following the prefix. + */ +DECLHIDDEN(bool) supHardViUniStrPathStartsWithUniStr(UNICODE_STRING const *pUniStrLeft, + UNICODE_STRING const *pUniStrRight, bool fCheckSlash) +{ + return supHardViUtf16PathStartsWithEx(pUniStrLeft->Buffer, pUniStrLeft->Length / sizeof(WCHAR), + pUniStrRight->Buffer, pUniStrRight->Length / sizeof(WCHAR), fCheckSlash); +} + + +#ifndef IN_RING0 +/** + * 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; +} +#endif + + +#ifdef VBOX_PERMIT_MORE +/** + * Checks if the path goes into %windir%\apppatch\. + * + * @returns true if apppatch, false if not. + * @param pwszPath The path to examine. + */ +DECLHIDDEN(bool) supHardViIsAppPatchDir(PCRTUTF16 pwszPath, uint32_t cwcName) +{ + uint32_t cwcWinDir = (g_System32NtPath.UniStr.Length - sizeof(L"System32")) / sizeof(WCHAR); + + if (cwcName <= cwcWinDir + sizeof("AppPatch")) + return false; + + if (memcmp(pwszPath, g_System32NtPath.UniStr.Buffer, cwcWinDir * sizeof(WCHAR))) + return false; + + if (!supHardViUtf16PathStartsWithAscii(&pwszPath[cwcWinDir], "\\AppPatch\\")) + return false; + + return g_uNtVerCombined >= SUP_NT_VER_VISTA; +} +#else +# error should not get here.. +#endif + + + +/** + * 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 hFile The file handle. + * @param rc The status code.. + */ +static int supHardNtViCheckIfNotSignedOk(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, uint32_t fFlags, HANDLE hFile, int rc) +{ + RT_NOREF1(hLdrMod); + + 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 (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_System32NtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + pwsz = pwszName + cwcOther + 1; + + /* Must be owned by trusted installer. (This test is superfuous, thus no relaxation here.) */ + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + + /* 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 VINF_LDRVI_NOT_SIGNED; /* So far, never signed... */ +#ifdef VBOX_PERMIT_VERIFIER_DLL + if (supHardViUtf16PathIsEqual(pwsz, "verifier.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; +#endif +#ifdef VBOX_PERMIT_MORE + if (uNtVer >= SUP_NT_VER_W70) /* hard limit: user32.dll is unwanted prior to w7. */ + { + if (supHardViUtf16PathIsEqual(pwsz, "sfc.dll")) + return uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "sfc_os.dll")) + return uNtVer < SUP_MAKE_NT_VER_SIMPLE(6, 4) ? VINF_LDRVI_NOT_SIGNED : rc; + if (supHardViUtf16PathIsEqual(pwsz, "user32.dll")) + return uNtVer < SUP_NT_VER_W81 ? VINF_LDRVI_NOT_SIGNED : rc; + } +#endif + +#ifndef IN_RING0 + /* 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. */ + return VINF_LDRVI_NOT_SIGNED; +#else + return rc; +#endif /* IN_RING0 */ + } + + +#ifndef IN_RING0 + /* + * The WinSxS white list. + * + * Just like with System32 there are potentially a number of DLLs that + * could be required from WinSxS. + */ + cwcOther = g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR); + if (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_WinSxSNtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + 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; + + /* Must be owned by trusted installer. */ + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + return VINF_LDRVI_NOT_SIGNED; + } +#endif /* !IN_RING0 */ + + +#ifdef VBOX_PERMIT_MORE + /* + * AppPatch whitelist. + */ + if (supHardViIsAppPatchDir(pwszName, cwcName)) + { + cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); /* ASSUMES System32 is called System32. */ + pwsz = pwszName + cwcOther + 1; + + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + +# ifndef VBOX_PERMIT_EVEN_MORE + if (supHardViUtf16PathIsEqual(pwsz, "acres.dll")) + return VINF_LDRVI_NOT_SIGNED; + +# ifdef RT_ARCH_AMD64 + if (supHardViUtf16PathIsEqual(pwsz, "AppPatch64\\AcGenral.dll")) + return VINF_LDRVI_NOT_SIGNED; +# elif defined(RT_ARCH_X86) + if (supHardViUtf16PathIsEqual(pwsz, "AcGenral.dll")) + return VINF_LDRVI_NOT_SIGNED; +# endif +# endif /* !VBOX_PERMIT_EVEN_MORE */ + +# ifdef IN_RING0 + return rc; +# else + return VINF_LDRVI_NOT_SIGNED; +# endif + } +#endif /* VBOX_PERMIT_MORE */ + + +#ifndef IN_RING0 +# if defined(VBOX_PERMIT_MORE) && !defined(VBOX_PERMIT_EVEN_MORE) + /* + * Program files and common files. + * Permit anything that's signed and correctly installed. + */ + if ( supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_ProgramFilesNtPath.UniStr.Buffer, g_ProgramFilesNtPath.UniStr.Length, + true /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_CommonFilesNtPath.UniStr.Buffer, g_CommonFilesNtPath.UniStr.Length, + true /*fCheckSlash*/) +# ifdef RT_ARCH_AMD64 + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_ProgramFilesX86NtPath.UniStr.Buffer, g_ProgramFilesX86NtPath.UniStr.Length, + true /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pwszName, cwcName, + g_CommonFilesX86NtPath.UniStr.Buffer, g_CommonFilesX86NtPath.UniStr.Length, + true /*fCheckSlash*/) +# endif + ) + { + if ( !(fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return rc; + return VINF_LDRVI_NOT_SIGNED; + } + +# elif defined(VBOX_PERMIT_MORE) && defined(VBOX_PERMIT_EVEN_MORE) + /* + * Anything that's owned by the trusted installer. + */ + if ( (fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + || supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(hFile, pwszName)) + return VINF_LDRVI_NOT_SIGNED; + +# endif +#endif /* !IN_RING0 */ + + /* + * Not permitted. + */ + return rc; +} + + +/** + * @callback_method_impl{FNRTDUMPPRINTFV, Formats into RTERRINFO. } + */ +static DECLCALLBACK(void) supHardNtViAsn1DumpToErrInfo(void *pvUser, const char *pszFormat, va_list va) +{ + PRTERRINFO pErrInfo = (PRTERRINFO)pvUser; + RTErrInfoAddV(pErrInfo, pErrInfo->rc, pszFormat, va); +} + + +/** + * Attempts to locate a root certificate in the specified store. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if found. + * @retval VWRN_NOT_FOUND if not found. + * + * @param hRootStore The root certificate store to search. + * @param pSubject The root certificate subject. + * @param pPublicKeyInfo The public key of the root certificate to find. + */ +static int supHardNtViCertVerifyFindRootCert(RTCRSTORE hRootStore, PCRTCRX509NAME pSubject, + PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo) +{ + RTCRSTORECERTSEARCH Search; + int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(hRootStore, pSubject, &Search); + AssertRCReturn(rc, rc); + + rc = VWRN_NOT_FOUND; + PCRTCRCERTCTX pCertCtx; + while ((pCertCtx = RTCrStoreCertSearchNext(hRootStore, &Search)) != NULL) + { + PCRTCRX509SUBJECTPUBLICKEYINFO pCertPubKeyInfo = NULL; + if (pCertCtx->pCert) + pCertPubKeyInfo = &pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo; + else if (pCertCtx->pTaInfo) + pCertPubKeyInfo = &pCertCtx->pTaInfo->PubKey; + else + pCertPubKeyInfo = NULL; + if ( pCertPubKeyInfo + && RTCrX509SubjectPublicKeyInfo_Compare(pCertPubKeyInfo, pPublicKeyInfo) == 0) + { + RTCrCertCtxRelease(pCertCtx); + rc = VINF_SUCCESS; + break; + } + RTCrCertCtxRelease(pCertCtx); + } + + int rc2 = RTCrStoreCertSearchDestroy(hRootStore, &Search); + AssertRC(rc2); + return rc; +} + + +/** + * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK, + * Standard code signing. Use this for Microsoft SPC.} + */ +static DECLCALLBACK(int) supHardNtViCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, + uint32_t fFlags, 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 (RTCrX509Certificate_Compare(pCert, &g_BuildX509Cert) == 0) + { +#ifdef VBOX_STRICT + Assert(RTCrX509CertPathsGetPathCount(hCertPaths) == 1); + bool fTrusted = false; + uint32_t cNodes = UINT32_MAX; + int rcVerify = -1; + int rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, 0, &fTrusted, &cNodes, NULL, NULL, NULL, NULL, &rcVerify); + AssertRC(rc); AssertRC(rcVerify); Assert(fTrusted); Assert(cNodes == 1); +#endif + return VINF_SUCCESS; + } + + /* + * Standard code signing capabilites required. + */ + int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo); + if ( RT_SUCCESS(rc) + && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)) + { + /* + * For kernel code signing there are two options for a valid certificate path: + * 1. Anchored by the microsoft kernel signing root certificate (g_hNtKernelRootStore). + * 2. Anchored by an SPC root and signing entity including a 1.3.6.1.4.1.311.10.3.5 (WHQL) + * or 1.3.6.1.4.1.311.10.3.5.1 (WHQL attestation) extended usage key. + */ + 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++; + + /* + * 1. Search the kernel signing root store for a matching anchor. + */ + rc = supHardNtViCertVerifyFindRootCert(g_hNtKernelRootStore, pSubject, pPublicKeyInfo); + if (rc == VINF_SUCCESS) + cFound++; + /* + * 2. Check for WHQL EKU and make sure it has a SPC root. + */ + else if ( rc == VWRN_NOT_FOUND + && ( pCert->TbsCertificate.T3.fExtKeyUsage + & (RTCRX509CERT_EKU_F_MS_ATTEST_WHQL_CRYPTO | RTCRX509CERT_EKU_F_MS_WHQL_CRYPTO))) + { + rc = supHardNtViCertVerifyFindRootCert(g_hSpcRootStore, pSubject, pPublicKeyInfo); + if (rc == VINF_SUCCESS) + cFound++; + } + AssertRCBreak(rc); + } + } + if (RT_SUCCESS(rc) && cFound == 0) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE, + "Signature #%u/%u: Not valid kernel code signature.", + pNtViRdr->iCurSignature + 1, pNtViRdr->cTotalSignatures); + + + if (RT_SUCCESS(rc) && cValid < 2 && g_fHaveOtherRoots) + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNEXPECTED_VALID_PATH_COUNT, + "Signature #%u/%u: Expected at least %u valid paths, not %u.", + pNtViRdr->iCurSignature + 1, pNtViRdr->cTotalSignatures, 2, cValid); + if (rc == VWRN_NOT_FOUND) + rc = VINF_SUCCESS; + } + } + + /* + * More requirements? NT5 build lab? + */ + + return rc; +} + + +/** + * RTTimeNow equivaltent that handles ring-3 where we cannot use it. + * + * @returns pNow + * @param pNow Where to return the current time. + */ +static PRTTIMESPEC supHardNtTimeNow(PRTTIMESPEC pNow) +{ +#ifdef IN_RING3 + /* + * Just read system time. + */ + KUSER_SHARED_DATA volatile *pUserSharedData = (KUSER_SHARED_DATA volatile *)MM_SHARED_USER_DATA_VA; +# ifdef RT_ARCH_AMD64 + uint64_t uRet = *(uint64_t volatile *)&pUserSharedData->SystemTime; /* This is what KeQuerySystemTime does (missaligned). */ + return RTTimeSpecSetNtTime(pNow, uRet); +# else + + LARGE_INTEGER NtTime; + do + { + NtTime.HighPart = pUserSharedData->SystemTime.High1Time; + NtTime.LowPart = pUserSharedData->SystemTime.LowPart; + } while (pUserSharedData->SystemTime.High2Time != NtTime.HighPart); + return RTTimeSpecSetNtTime(pNow, NtTime.QuadPart); +# endif +#else /* IN_RING0 */ + return RTTimeNow(pNow); +#endif /* IN_RING0 */ +} + + +/** + * @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} + */ +static DECLCALLBACK(int) supHardNtViCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, PRTERRINFO pErrInfo, void *pvUser) +{ + RT_NOREF(hLdrMod); + + /* + * Check out the input. + */ + PSUPHNTVIRDR pNtViRdr = (PSUPHNTVIRDR)pvUser; + Assert(pNtViRdr->Core.uMagic == RTLDRREADER_MAGIC); + pNtViRdr->cTotalSignatures = pInfo->cSignatures; + pNtViRdr->iCurSignature = pInfo->iSignature; + + AssertReturn(pInfo->enmType == RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA, VERR_INTERNAL_ERROR_5); + AssertReturn(!pInfo->pvExternalData, VERR_INTERNAL_ERROR_5); + AssertReturn(pInfo->cbSignature == sizeof(RTCRPKCS7CONTENTINFO), VERR_INTERNAL_ERROR_5); + PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->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.papItems[0]; + + + /* + * If special certificate requirements, check them out before validating + * the signature. These only apply to the first signature (for now). + */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) + && pInfo->iSignature == 0) + { + if (!RTCrX509Certificate_MatchIssuerAndSerialNumber(&g_BuildX509Cert, + &pSignerInfo->IssuerAndSerialNumber.Name, + &pSignerInfo->IssuerAndSerialNumber.SerialNumber)) + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_SIGNED_WITH_BUILD_CERT, + "Signature #%u/%u: Not signed with the build certificate (serial %.*Rhxs, expected %.*Rhxs)", + pInfo->iSignature + 1, pInfo->cSignatures, + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb, + pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv, + g_BuildX509Cert.TbsCertificate.SerialNumber.Asn1Core.cb, + g_BuildX509Cert.TbsCertificate.SerialNumber.Asn1Core.uData.pv); + } + + /* + * We instruction the verifier to use the signing time counter signature + * when present, but provides the linker time then the current time as + * fallbacks should the timestamp be missing or unusable. + * + * Update: Save the first timestamp we validate with build cert and + * use this as a minimum timestamp for further build cert + * validations. This works around issues with old DLLs that + * we sign against with our certificate (crt, sdl, qt). + * + * Update: If the validation fails, retry with the current timestamp. This + * is a workaround for NTDLL.DLL in build 14971 having a weird + * timestamp: 0xDF1E957E (Sat Aug 14 14:05:18 2088). + */ + uint32_t fFlags = RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT + | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT + | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY; + + /* In ring-0 we don't have all the necessary timestamp server root certificate + * info, so we have to allow using counter signatures unverified there. + * Ditto for the early period of ring-3 hardened stub execution. */ +#ifndef IN_RING0 + if (!g_fHaveOtherRoots) +#endif + fFlags |= RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED | RTCRPKCS7VERIFY_SD_F_USE_MS_TIMESTAMP_UNVERIFIED; + + /* Fallback timestamps to try: */ + struct { RTTIMESPEC TimeSpec; const char *pszDesc; } aTimes[2]; + unsigned cTimes = 0; + + /* 1. The linking timestamp: */ + uint64_t uTimestamp = 0; + int rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uTimestamp)); + if (RT_SUCCESS(rc)) + { +#ifdef IN_RING3 /* Hack alert! (see above) */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING) + && (pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT) + && uTimestamp < g_uBuildTimestampHack) + uTimestamp = g_uBuildTimestampHack; +#endif + RTTimeSpecSetSeconds(&aTimes[0].TimeSpec, uTimestamp); + aTimes[0].pszDesc = "link"; + cTimes++; + } + else + SUP_DPRINTF(("RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on %s: %Rrc", pNtViRdr->szFilename, rc)); + + /* 2. Current time. */ + supHardNtTimeNow(&aTimes[cTimes].TimeSpec); + aTimes[cTimes].pszDesc = "now"; + cTimes++; + + /* Make the verfication attempts. */ + for (unsigned i = 0; ; i++) + { + Assert(i < cTimes); + rc = RTCrPkcs7VerifySignedData(pContentInfo, fFlags, g_hSpcAndNtKernelSuppStore, g_hSpcAndNtKernelRootStore, + &aTimes[i].TimeSpec, supHardNtViCertVerifyCallback, pNtViRdr, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (rc != VINF_SUCCESS) + { + SUP_DPRINTF(("%s: Signature #%u/%u: info status: %d\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, rc)); + if (pNtViRdr->rcLastSignatureFailure == VINF_SUCCESS) + pNtViRdr->rcLastSignatureFailure = rc; + } + pNtViRdr->cOkaySignatures++; + +#ifdef IN_RING3 /* Hack alert! (see above) */ + if ((pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) && g_uBuildTimestampHack == 0 && cTimes > 1) + g_uBuildTimestampHack = uTimestamp; +#endif + return VINF_SUCCESS; + } + + if (rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME && i + 1 < cTimes) + SUP_DPRINTF(("%s: Signature #%u/%u: VERR_CR_X509_CPV_NOT_VALID_AT_TIME for %#RX64; retrying against current time: %#RX64.\n", + pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + RTTimeSpecGetSeconds(&aTimes[0].TimeSpec), RTTimeSpecGetSeconds(&aTimes[1].TimeSpec))); + else + { + /* There are a couple of failures we can tollerate if there are more than + one signature and one of them works out fine. The RTLdrVerifySignature + caller will have to check the failure counts though to make sure + something succeeded. + + VERR_CR_PKCS7_KEY_USAGE_MISMATCH: Nvidia 391.35 nvldumpx.dll has an misconfigured + certificate "CN=NVIDIA Corporation PE Sign v2016" without valid Key Usage. It is + rooted by "CN=NVIDIA Subordinate CA 2016 v2,DC=nvidia,DC=com", so homebrewn. + Sysinternals' sigcheck util ignores it, while MS sigtool doesn't trust the root. + It's possible we're being too strict, but well, it's the only case so far, so no + need to relax the Key Usage restrictions just for a certificate w/o a trusted root. + + VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION: Intel 27.20.100.9126 igdumdim64.dll + has three signatures, the first is signed with a certificate (C=US,ST=CA, + L=Santa Clara,O=Intel Corporation,CN=IntelGraphicsPE2021) that has a critical + subject key identifier. This used to trip up the path validator. However, the + other two signatures are from microsoft and checks out fine. So, in future + situations like this it would be nice to simply continue with the next signature. + See bugref{10130} for details. + + VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE: Is related to the above intel problem, + but this is what we get if suppressing the unknown critical subjectKeyIdentifier + in IPRT. We don't need all signatures to be valid kernel signatures, we should be + happy with just one and ignore any additional signatures as long as they don't look + like they've been compromised. Thus continue with this status too. */ + pNtViRdr->rcLastSignatureFailure = rc; + if ( rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME + || rc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS + || rc == VERR_CR_PKCS7_KEY_USAGE_MISMATCH + || rc == VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION + || rc == VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE) + { + SUP_DPRINTF(("%s: Signature #%u/%u: %s (%d) w/ timestamp=%#RX64/%s.\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + rc == VERR_CR_X509_CPV_NOT_VALID_AT_TIME ? "VERR_CR_X509_CPV_NOT_VALID_AT_TIME" + : rc == VERR_CR_X509_CPV_NO_TRUSTED_PATHS ? "VERR_CR_X509_CPV_NO_TRUSTED_PATHS" + : rc == VERR_CR_PKCS7_KEY_USAGE_MISMATCH ? "VERR_CR_PKCS7_KEY_USAGE_MISMATCH" + : rc == VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION ? "VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION" + : "VERR_SUP_VP_NOT_VALID_KERNEL_CODE_SIGNATURE", + rc, RTTimeSpecGetSeconds(&aTimes[i].TimeSpec), aTimes[i].pszDesc)); + + /* This leniency is not applicable to build certificate requirements (signature #1 only). */ + if ( !(pNtViRdr->fFlags & SUPHNTVI_F_REQUIRE_BUILD_CERT) + || pInfo->iSignature != 0) + { + pNtViRdr->cNokSignatures++; + rc = VINF_SUCCESS; + } + } + else + SUP_DPRINTF(("%s: Signature #%u/%u: %Rrc w/ timestamp=%#RX64/%s.\n", pNtViRdr->szFilename, pInfo->iSignature + 1, pInfo->cSignatures, + rc, RTTimeSpecGetSeconds(&aTimes[i].TimeSpec), aTimes[i].pszDesc)); + return rc; + } + } +} + + +/** + * Verifies the given loader image. + * + * @returns IPRT status code. + * @param hLdrMod 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 pNtViRdr The reader instance /w flags. + * @param fAvoidWinVerifyTrust Whether to avoid WinVerifyTrust because of + * deadlock or other loader related dangers. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByLdrMod(RTLDRMOD hLdrMod, PCRTUTF16 pwszName, PSUPHNTVIRDR pNtViRdr, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = false; + +#ifdef IN_RING3 + /* Check that the caller has performed the necessary library initialization. */ + if (!RTCrX509Certificate_IsPresent(&g_BuildX509Cert)) + return RTErrInfoSet(pErrInfo, VERR_WRONG_ORDER, + "supHardenedWinVerifyImageByHandle: supHardenedWinInitImageVerifier was not called."); +#endif + + /* + * Check the trusted installer bit first, if requested as it's somewhat + * cheaper than the rest. + * + * We relax this for system32 and a little for WinSxS, like we used to, as + * there are apparently some systems out there where the user, admin, or + * someone has changed the ownership of core windows DLLs like user32.dll + * and comctl32.dll. Since we need user32.dll and will be checking it's + * digital signature, it's reasonably safe to let this thru. (The report + * was of SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS + * owning user32.dll, see public ticket 13187, VBoxStartup.3.log.) + * + * We've also had problems with graphics driver components like ig75icd64.dll + * and atig6pxx.dll not being owned by TrustedInstaller, with the result + * that 3D got broken (mod by zero issue in test build 5). These were also + * SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS. + * + * In one report by 'thor' the WinSxS resident comctl32.dll was owned by + * SECURITY_BUILTIN_DOMAIN_RID + DOMAIN_ALIAS_RID_ADMINS (with 4.3.16). + */ + /** @todo Since we're now allowing Builtin\\Administrators after all, perhaps we + * could drop these system32 + winsxs hacks?? */ + if ( (pNtViRdr->fFlags & SUPHNTVI_F_TRUSTED_INSTALLER_OWNER) + && !supHardNtViCheckIsOwnedByTrustedInstallerOrSimilar(pNtViRdr->hFile, pwszName)) + { + if (supHardViUtf16PathStartsWithEx(pwszName, (uint32_t)RTUtf16Len(pwszName), + g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR), + true /*fCheckSlash*/)) + SUP_DPRINTF(("%ls: Relaxing the TrustedInstaller requirement for this DLL (it's in system32).\n", pwszName)); + else if (supHardViUtf16PathStartsWithEx(pwszName, (uint32_t)RTUtf16Len(pwszName), + g_WinSxSNtPath.UniStr.Buffer, g_WinSxSNtPath.UniStr.Length / sizeof(WCHAR), + true /*fCheckSlash*/)) + SUP_DPRINTF(("%ls: Relaxing the TrustedInstaller requirement for this DLL (it's in WinSxS).\n", pwszName)); + else + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_NOT_OWNED_BY_TRUSTED_INSTALLER, + "supHardenedWinVerifyImageByHandle: TrustedInstaller is not the owner of '%ls'.", pwszName); + } + + /* + * Verify it. + * + * The PKCS #7 SignedData signature is checked in the callback. Any + * signing certificate restrictions are also enforced there. + */ + pNtViRdr->cOkaySignatures = 0; + pNtViRdr->cNokSignatures = 0; + pNtViRdr->cTotalSignatures = 0; + pNtViRdr->rcLastSignatureFailure = VINF_SUCCESS; + int rc = RTLdrVerifySignature(hLdrMod, supHardNtViCallback, pNtViRdr, pErrInfo); + if (RT_SUCCESS(rc)) + { + Assert(pNtViRdr->cOkaySignatures + pNtViRdr->cNokSignatures == pNtViRdr->cTotalSignatures); + if ( !pNtViRdr->cOkaySignatures + || pNtViRdr->cOkaySignatures + pNtViRdr->cNokSignatures < pNtViRdr->cTotalSignatures /* paranoia */) + { + rc = pNtViRdr->rcLastSignatureFailure; + AssertStmt(RT_FAILURE_NP(rc), rc = VERR_INTERNAL_ERROR_3); + } + else if (rc == VINF_SUCCESS && RT_SUCCESS(pNtViRdr->rcLastSignatureFailure)) + rc = pNtViRdr->rcLastSignatureFailure; + } + + /* + * 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, pNtViRdr->fFlags, pNtViRdr->hFile, 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) && (pNtViRdr->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); + } + +#ifdef IN_RING3 + /* + * Pass it thru WinVerifyTrust when possible. + */ + if (!fAvoidWinVerifyTrust) + rc = supHardenedWinVerifyImageTrust(pNtViRdr->hFile, pwszName, pNtViRdr->fFlags, rc, pfWinVerifyTrust, pErrInfo); +#else + RT_NOREF1(fAvoidWinVerifyTrust); +#endif + + /* + * Check for blacklisted DLLs, both internal name and filename. + */ + if (RT_SUCCESS(rc)) + { + size_t const cwcName = RTUtf16Len(pwszName); + char szIntName[64]; + int rc2 = RTLdrQueryProp(hLdrMod, RTLDRPROP_INTERNAL_NAME, szIntName, sizeof(szIntName)); + if (RT_SUCCESS(rc2)) + { + size_t const cchIntName = strlen(szIntName); + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if ( cchIntName == g_aSupNtViBlacklistedDlls[i].cch + && RTStrICmpAscii(szIntName, g_aSupNtViBlacklistedDlls[i].psz) == 0) + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNDESIRABLE_MODULE, + "The image '%ls' is listed as undesirable.", pwszName); + break; + } + } + if (RT_SUCCESS(rc)) + { + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if (cwcName >= g_aSupNtViBlacklistedDlls[i].cch) + { + PCRTUTF16 pwszTmp = &pwszName[cwcName - g_aSupNtViBlacklistedDlls[i].cch]; + if ( ( cwcName == g_aSupNtViBlacklistedDlls[i].cch + || pwszTmp[-1] == '\\' + || pwszTmp[-1] == '/') + && RTUtf16ICmpAscii(pwszTmp, g_aSupNtViBlacklistedDlls[i].psz) == 0) + { + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_UNDESIRABLE_MODULE, + "The image '%ls' is listed as undesirable.", pwszName); + break; + } + } + } + } + +#ifdef IN_SUP_HARDENED_R3 + /* + * Hook for the LdrLoadDll code to schedule scanning of imports. + */ + if (RT_SUCCESS(rc)) + supR3HardenedWinVerifyCacheScheduleImports(hLdrMod, pwszName); +#endif + + return rc; +} + + +/** + * 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 fAvoidWinVerifyTrust Whether to avoid WinVerifyTrust because of + * deadlock or other loader related dangers. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageByHandle(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, + bool fAvoidWinVerifyTrust, bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + /* + * Create a reader instance. + */ + PSUPHNTVIRDR pNtViRdr; + int rc = supHardNtViRdrCreate(hFile, pwszName, fFlags, &pNtViRdr); + if (RT_SUCCESS(rc)) + { + /* + * Open the image. + */ + RTLDRMOD hLdrMod; + RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST; + uint32_t fLdrFlags = RTLDR_O_FOR_VALIDATION | RTLDR_O_IGNORE_ARCH_IF_NO_CODE; + if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE) + fLdrFlags |= RTLDR_O_IGNORE_ARCH_IF_NO_CODE; + rc = RTLdrOpenWithReader(&pNtViRdr->Core, fLdrFlags, enmArch, &hLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Verify it. + */ + rc = supHardenedWinVerifyImageByLdrMod(hLdrMod, pwszName, pNtViRdr, fAvoidWinVerifyTrust, pfWinVerifyTrust, pErrInfo); + int rc2 = RTLdrClose(hLdrMod); AssertRC(rc2); + } + else + supHardNtViRdrDestroy(&pNtViRdr->Core); + } + SUP_DPRINTF(("supHardenedWinVerifyImageByHandle: -> %d (%ls)%s\n", + rc, pwszName, pfWinVerifyTrust && *pfWinVerifyTrust ? " WinVerifyTrust" : "")); + 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, false /*fAvoidWinVerifyTrust*/, + NULL /*pfWinVerifyTrust*/, 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); + RT_NOREF1(pszErrorTag); + + 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; +} + + +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) +/** + * Initializes the windows paths. + */ +static void supHardenedWinInitImageVerifierWinPaths(void) +{ + /* + * Windows paths that we're interested in. + */ + static const struct + { + SUPSYSROOTDIRBUF *pNtPath; + WCHAR const *pwszRegValue; + const char *pszLogName; + } s_aPaths[] = + { + { &g_ProgramFilesNtPath, L"ProgramFilesDir", "ProgDir" }, + { &g_CommonFilesNtPath, L"CommonFilesDir", "ComDir" }, +# ifdef RT_ARCH_AMD64 + { &g_ProgramFilesX86NtPath, L"ProgramFilesDir (x86)", "ProgDir32" }, + { &g_CommonFilesX86NtPath, L"CommonFilesDir (x86)", "ComDir32" }, +# endif + }; + + /* + * Open the registry key containing the paths. + */ + UNICODE_STRING NtName = RTNT_CONSTANT_UNISTR(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion"); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + HANDLE hKey; + NTSTATUS rcNt = NtOpenKey(&hKey, KEY_QUERY_VALUE, &ObjAttr); + if (NT_SUCCESS(rcNt)) + { + /* + * Loop over the paths and resolve their NT paths. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPaths); i++) + { + /* + * Query the value first. + */ + UNICODE_STRING ValueName; + ValueName.Buffer = (WCHAR *)s_aPaths[i].pwszRegValue; + ValueName.Length = (USHORT)(RTUtf16Len(s_aPaths[i].pwszRegValue) * sizeof(WCHAR)); + ValueName.MaximumLength = ValueName.Length + sizeof(WCHAR); + + union + { + KEY_VALUE_PARTIAL_INFORMATION PartialInfo; + uint8_t abPadding[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + sizeof(WCHAR) * 128]; + uint64_t uAlign; + } uBuf; + + ULONG cbActual = 0; + rcNt = NtQueryValueKey(hKey, &ValueName, KeyValuePartialInformation, &uBuf, sizeof(uBuf) - sizeof(WCHAR), &cbActual); + if (NT_SUCCESS(rcNt)) + { + /* + * Must be a simple string value, terminate it. + */ + if ( uBuf.PartialInfo.Type == REG_EXPAND_SZ + || uBuf.PartialInfo.Type == REG_SZ) + { + /* + * Expand any environment variable references before opening it. + * We use the result buffer as storage for the expaneded path, + * reserving space for the windows name space prefix. + */ + UNICODE_STRING Src; + Src.Buffer = (WCHAR *)uBuf.PartialInfo.Data; + Src.Length = uBuf.PartialInfo.DataLength; + if (Src.Length >= sizeof(WCHAR) && Src.Buffer[Src.Length / sizeof(WCHAR) - 1] == '\0') + Src.Length -= sizeof(WCHAR); + Src.MaximumLength = Src.Length + sizeof(WCHAR); + Src.Buffer[uBuf.PartialInfo.DataLength / sizeof(WCHAR)] = '\0'; + + s_aPaths[i].pNtPath->awcBuffer[0] = '\\'; + s_aPaths[i].pNtPath->awcBuffer[1] = '?'; + s_aPaths[i].pNtPath->awcBuffer[2] = '?'; + s_aPaths[i].pNtPath->awcBuffer[3] = '\\'; + UNICODE_STRING Dst; + Dst.Buffer = &s_aPaths[i].pNtPath->awcBuffer[4]; + Dst.MaximumLength = sizeof(s_aPaths[i].pNtPath->awcBuffer) - sizeof(WCHAR) * 5; + Dst.Length = Dst.MaximumLength; + + if (uBuf.PartialInfo.Type == REG_EXPAND_SZ) + rcNt = RtlExpandEnvironmentStrings_U(NULL, &Src, &Dst, NULL); + else + { + memcpy(Dst.Buffer, Src.Buffer, Src.Length); + Dst.Length = Src.Length; + } + if (NT_SUCCESS(rcNt)) + { + Dst.Buffer[Dst.Length / sizeof(WCHAR)] = '\0'; + + /* + * Include the \\??\\ prefix in the result and open the path. + */ + Dst.Buffer -= 4; + Dst.Length += 4 * sizeof(WCHAR); + Dst.MaximumLength += 4 * sizeof(WCHAR); + InitializeObjectAttributes(&ObjAttr, &Dst, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + HANDLE hFile = INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + 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)) + { + /* + * Query the real NT name. + */ + ULONG cbIgn; + rcNt = NtQueryObject(hFile, + ObjectNameInformation, + s_aPaths[i].pNtPath, + sizeof(*s_aPaths[i].pNtPath) - sizeof(WCHAR), + &cbIgn); + if (NT_SUCCESS(rcNt)) + { + if (s_aPaths[i].pNtPath->UniStr.Length > 0) + { + /* Make sure it's terminated.*/ + s_aPaths[i].pNtPath->UniStr.Buffer[s_aPaths[i].pNtPath->UniStr.Length / sizeof(WCHAR)] = '\0'; + SUP_DPRINTF(("%s:%*s %ls\n", s_aPaths[i].pszLogName, 9 - strlen(s_aPaths[i].pszLogName), "", + s_aPaths[i].pNtPath->UniStr.Buffer)); + } + else + { + SUP_DPRINTF(("%s: NtQueryObject returned empty string\n", s_aPaths[i].pszLogName)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + SUP_DPRINTF(("%s: NtQueryObject failed: %#x\n", s_aPaths[i].pszLogName, rcNt)); + NtClose(hFile); + } + else + SUP_DPRINTF(("%s: NtCreateFile failed: %#x (%ls)\n", + s_aPaths[i].pszLogName, rcNt, Dst.Buffer)); + } + else + SUP_DPRINTF(("%s: RtlExpandEnvironmentStrings_U failed: %#x (%ls)\n", + s_aPaths[i].pszLogName, rcNt, Src.Buffer)); + } + else + { + SUP_DPRINTF(("%s: type mismatch: %#x\n", s_aPaths[i].pszLogName, uBuf.PartialInfo.Type)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + SUP_DPRINTF(("%s: NtQueryValueKey failed: %#x\n", s_aPaths[i].pszLogName, rcNt)); + + /* Stub the entry on failure. */ + if (!NT_SUCCESS(rcNt)) + { + s_aPaths[i].pNtPath->UniStr.Length = 0; + s_aPaths[i].pNtPath->UniStr.Buffer = NULL; + } + } + NtClose(hKey); + } + else + { + SUP_DPRINTF(("NtOpenKey(%ls) failed: %#x\n", NtName.Buffer, rcNt)); + + /* Stub all the entries on failure. */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPaths); i++) + { + s_aPaths[i].pNtPath->UniStr.Length = 0; + s_aPaths[i].pNtPath->UniStr.Buffer = NULL; + } + } +} +#endif /* IN_RING3 && !VBOX_PERMIT_EVEN_MORE */ + + +/** + * 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)) + { + SUP_DPRINTF(("System32: %ls\n", g_System32NtPath.UniStr.Buffer)); + SUP_DPRINTF(("WinSxS: %ls\n", g_WinSxSNtPath.UniStr.Buffer)); +#if defined(IN_RING3) && !defined(VBOX_PERMIT_EVEN_MORE) + supHardenedWinInitImageVerifierWinPaths(); +#endif + + /* + * 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 0 /* For the time being, always trust the build certificate. It bypasses the timestamp issues of CRT and SDL. */ + /* 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) +#else + if (RT_SUCCESS(rc)) +#endif + rc = RTCrStoreCertAddEncoded(g_hSpcAndNtKernelRootStore, RTCRCERTCTX_F_ENC_X509_DER, + g_abSUPBuildCert, g_cbSUPBuildCert, pErrInfo); + + if (RT_SUCCESS(rc)) + { + /* + * Finally initialize known SIDs that we use. + */ + SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; + NTSTATUS rcNt = RtlInitializeSid(&g_TrustedInstallerSid, &s_NtAuth, SECURITY_SERVICE_ID_RID_COUNT); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 0) = SECURITY_SERVICE_ID_BASE_RID; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 1) = 956008885; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 2) = 3418522649; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 3) = 1831038044; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 4) = 1853292631; + *RtlSubAuthoritySid(&g_TrustedInstallerSid, 5) = 2271478464; + + rcNt = RtlInitializeSid(&g_LocalSystemSid, &s_NtAuth, 1); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_LocalSystemSid, 0) = SECURITY_LOCAL_SYSTEM_RID; + + rcNt = RtlInitializeSid(&g_AdminsGroupSid, &s_NtAuth, 2); + if (NT_SUCCESS(rcNt)) + { + *RtlSubAuthoritySid(&g_AdminsGroupSid, 0) = SECURITY_BUILTIN_DOMAIN_RID; + *RtlSubAuthoritySid(&g_AdminsGroupSid, 1) = DOMAIN_ALIAS_RID_ADMINS; + return VINF_SUCCESS; + } + } + } + rc = RTErrConvertFromNtStatus(rcNt); + } + 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) +{ + char szSubject[512]; + szSubject[sizeof(szSubject) - 1] = '\0'; + RTCrX509Name_FormatAsString(&pCert->TbsCertificate.Subject, szSubject, sizeof(szSubject) - 1, NULL); + + /* + * Check that it's a plausible root certificate. + */ + if (!RTCrX509Certificate_IsSelfSigned(pCert)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - not-self-signed: %s\n", szSubject)); + 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) ) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - non-cert-sign: %s\n", szSubject)); + return false; + } + if ( pCert->TbsCertificate.T3.pBasicConstraints + && !pCert->TbsCertificate.T3.pBasicConstraints->CA.fValue) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - non-CA: %s\n", szSubject)); + return false; + } + } + if (pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.cBits < 256) /* mostly for u64KeyId reading. */ + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - key too small: %u bits %s\n", + pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.cBits, szSubject)); + return false; + } + uint64_t const u64KeyId = pCert->TbsCertificate.SubjectPublicKeyInfo.SubjectPublicKey.uBits.pu64[1]; + +# if 0 + /* + * Whitelist - 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" }, + }; + + + 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)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: Adding %#llx %s\n", u64KeyId, szSubject)); + return true; + } + + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping %#llx %s\n", u64KeyId, szSubject)); + return false; +# else + /* + * Blacklist approach. + */ + static struct + { + uint64_t u64KeyId; + const char *pszName; + } const s_aUnwanted[] = + { + { UINT64_C(0xffffffffffffffff), "C=US, O=U.S. Robots and Mechanical Men, Inc., OU=V.I.K.I." }, /* dummy entry */ + }; + + uint32_t i = RT_ELEMENTS(s_aUnwanted); + while (i-- > 0) + if ( s_aUnwanted[i].u64KeyId == u64KeyId + || s_aUnwanted[i].u64KeyId == UINT64_MAX) + if (RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, s_aUnwanted[i].pszName)) + { + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: skipping - blacklisted: %#llx %s\n", u64KeyId, szSubject)); + return false; + } + + SUP_DPRINTF(("supR3HardenedWinIsDesiredRootCA: Adding %#llx %s\n", u64KeyId, szSubject)); + return true; +# endif +} + + +/** + * Loads a module in the system32 directory. + * + * @returns Module handle on success. Won't return on failure if fMandatory = true. + * @param pszName The name of the DLL to load. + * @param fMandatory Whether the library is mandatory. + */ +DECLHIDDEN(HMODULE) supR3HardenedWinLoadSystem32Dll(const char *pszName, bool fMandatory) +{ + WCHAR wszName[200+60]; + UINT cwcDir = GetSystemDirectoryW(wszName, RT_ELEMENTS(wszName) - 60); + wszName[cwcDir] = '\\'; + RTUtf16CopyAscii(&wszName[cwcDir + 1], RT_ELEMENTS(wszName) - cwcDir, pszName); + + DWORD fFlags = 0; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) + fFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; + HMODULE hMod = LoadLibraryExW(wszName, NULL, fFlags); + if ( hMod == NULL + && fFlags + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER) + { + fFlags = 0; + hMod = LoadLibraryExW(wszName, NULL, fFlags); + } + if ( hMod == NULL + && fMandatory) + supR3HardenedFatal("Error loading '%s': %u [%ls]", pszName, RtlGetLastWin32Error(), wszName); + return hMod; +} + + +/** + * Called by supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation to + * import selected root CAs from the system certificate store. + * + * These certificates permits us to correctly validate third party DLLs. + */ +static void supR3HardenedWinRetrieveTrustedRootCAs(void) +{ + uint32_t cAdded = 0; + + /* + * Load crypt32.dll and resolve the APIs we need. + */ + HMODULE hCrypt32 = supR3HardenedWinLoadSystem32Dll("crypt32.dll", true /*fMandatory*/); + +#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", RtlGetLastWin32Error()) + 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) + { + RTERRINFOSTATIC StaticErrInfo; + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, pCurCtx->pbCertEncoded, pCurCtx->cbCertEncoded, + RTErrInfoInitStatic(&StaticErrInfo), + &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx"); + RTCRX509CERTIFICATE MyCert; + int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &MyCert, "Cert"); + 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); + } + /* XP root certificate "C&W HKT SecureNet CA SGC Root" has non-standard validity + timestamps, the UTC formatting isn't Zulu time but specifies timezone offsets. + Ignore these failures and certificates. */ + else if (rc != VERR_ASN1_INVALID_UTC_TIME_ENCODING) + AssertMsgFailed(("RTCrX509Certificate_DecodeAsn1 failed: rc=%#x: %s\n", rc, StaticErrInfo.szMsg)); + } + } + 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. + * + * @param pszProgName The program name. + */ +DECLHIDDEN(void) supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(const char *pszProgName) +{ +# 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]; + supR3HardenedPathAppSharedLibs(szPath, sizeof(szPath) - sizeof("/VBoxSupLib.DLL")); + suplibHardenedStrCat(szPath, "/VBoxSupLib.DLL"); + HMODULE hSupLibMod = (HMODULE)supR3HardenedWinLoadLibrary(szPath, true /*fSystem32Only*/, 0 /*fMainFlags*/); + if (hSupLibMod == NULL) + supR3HardenedFatal("Error loading '%s': %u", szPath, RtlGetLastWin32Error()); +# endif + + /* + * Allocate TLS entry for WinVerifyTrust recursion prevention. + */ + DWORD iTls = TlsAlloc(); + if (iTls != TLS_OUT_OF_INDEXES) + g_iTlsWinVerifyTrustRecursion = iTls; + else + supR3HardenedError(RtlGetLastWin32Error(), false /*fFatal*/, "TlsAlloc failed"); + + /* + * Resolve the imports we need. + */ + HMODULE hWintrust = supR3HardenedWinLoadSystem32Dll("Wintrust.dll", true /*fMandatory*/); +#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", RtlGetLastWin32Error()); \ + } while (0) + + PFNWINVERIFYTRUST pfnWinVerifyTrust = (PFNWINVERIFYTRUST)GetProcAddress(hWintrust, "WinVerifyTrust"); + if (!pfnWinVerifyTrust) + supR3HardenedFatal("Error locating 'WinVerifyTrust' in 'Wintrust.dll': %u", RtlGetLastWin32Error()); + + 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); + +# ifdef IN_SUP_HARDENED_R3 + /* + * Load bcrypt.dll and instantiate a few hashing and signing providers to + * make sure the providers are cached for later us. Avoid recursion issues. + */ + HMODULE hBCrypt = supR3HardenedWinLoadSystem32Dll("bcrypt.dll", false /*fMandatory*/); + if (hBCrypt) + { + PFNBCRYPTOPENALGORTIHMPROVIDER pfnOpenAlgoProvider; + pfnOpenAlgoProvider = (PFNBCRYPTOPENALGORTIHMPROVIDER)GetProcAddress(hBCrypt, "BCryptOpenAlgorithmProvider"); + if (pfnOpenAlgoProvider) + { + SUP_DPRINTF(("bcrypt.dll loaded at %p, BCryptOpenAlgorithmProvider at %p, preloading providers:\n", + hBCrypt, pfnOpenAlgoProvider)); +# define PRELOAD_ALGO_PROVIDER(a_Name) \ + do { \ + BCRYPT_ALG_HANDLE hAlgo = NULL; \ + NTSTATUS rcNt = pfnOpenAlgoProvider(&hAlgo, a_Name, NULL, 0); \ + SUP_DPRINTF(("%sBCryptOpenAlgorithmProvider(,'%ls',0,0) -> %#x (hAlgo=%p)\n", \ + NT_SUCCESS(rcNt) ? " " : "warning: ", a_Name, rcNt, hAlgo)); \ + } while (0) + PRELOAD_ALGO_PROVIDER(BCRYPT_MD2_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_MD4_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_MD5_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA1_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA256_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_SHA512_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_RSA_ALGORITHM); + PRELOAD_ALGO_PROVIDER(BCRYPT_DSA_ALGORITHM); +# undef PRELOAD_ALGO_PROVIDER + } + else + SUP_DPRINTF(("Warning! Failed to find BCryptOpenAlgorithmProvider in bcrypt.dll\n")); + } + else + SUP_DPRINTF(("Warning! Failed to load bcrypt.dll\n")); + + /* + * Call the verification API on ourselves and ntdll to make sure it works + * and loads more stuff it needs, preventing any recursive fun we'd run + * into after we set g_pfnWinVerifyTrust. + */ + RTERRINFOSTATIC ErrInfoStatic; + RTErrInfoInitStatic(&ErrInfoStatic); + int rc = supR3HardNtViCallWinVerifyTrust(NULL, g_SupLibHardenedExeNtPath.UniStr.Buffer, 0, + &ErrInfoStatic.Core, pfnWinVerifyTrust, NULL); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg(pszProgName, kSupInitOp_Integrity, rc, + "WinVerifyTrust failed on stub executable: %s", ErrInfoStatic.szMsg); +# else + RT_NOREF1(pszProgName); +# 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, NULL); + supR3HardNtViCallWinVerifyTrustCatFile(NULL, L"\\SystemRoot\\System32\\ntdll.dll", 0, NULL, pfnWinVerifyTrust); + + g_pfnWinVerifyTrust = pfnWinVerifyTrust; + SUP_DPRINTF(("g_pfnWinVerifyTrust=%p\n", pfnWinVerifyTrust)); + +# ifdef IN_SUP_HARDENED_R3 + /* + * Load some problematic DLLs into the verifier cache to prevent + * recursion trouble. + */ + supR3HardenedWinVerifyCachePreload(L"\\SystemRoot\\System32\\crypt32.dll"); + supR3HardenedWinVerifyCachePreload(L"\\SystemRoot\\System32\\Wintrust.dll"); +# endif + + /* + * Now, get trusted root CAs so we can verify a broader scope of signatures. + */ + supR3HardenedWinRetrieveTrustedRootCAs(); +} + + +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. + * @param phrcWinVerifyTrust Where to WinVerifyTrust error status on failure, + * optional. + */ +static int supR3HardNtViCallWinVerifyTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, PRTERRINFO pErrInfo, + PFNWINVERIFYTRUST pfnWinVerifyTrust, HRESULT *phrcWinVerifyTrust) +{ + RT_NOREF1(fFlags); + if (phrcWinVerifyTrust) + *phrcWinVerifyTrust = S_OK; + + /* + * 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); +# ifdef DEBUG_bird /* TEMP HACK */ + if (hrc == CERT_E_EXPIRED) + hrc = S_OK; +# endif + if (hrc == S_OK) + rc = VINF_SUCCESS; + else + { + /* + * Failed. Format a nice error message. + */ +# ifdef DEBUG_bird + if (hrc != CERT_E_CHAINING /* Un-updated vistas, XPs, ++ */) + __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; + case CERT_E_EXPIRED: pszErrConst = "CERT_E_EXPIRED"; break; + case CERT_E_VALIDITYPERIODNESTING: pszErrConst = "CERT_E_VALIDITYPERIODNESTING"; break; + case CERT_E_ROLE: pszErrConst = "CERT_E_ROLE"; break; + case CERT_E_PATHLENCONST: pszErrConst = "CERT_E_PATHLENCONST"; break; + case CERT_E_CRITICAL: pszErrConst = "CERT_E_CRITICAL"; break; + case CERT_E_PURPOSE: pszErrConst = "CERT_E_PURPOSE"; break; + case CERT_E_ISSUERCHAINING: pszErrConst = "CERT_E_ISSUERCHAINING"; break; + case CERT_E_MALFORMED: pszErrConst = "CERT_E_MALFORMED"; break; + case CERT_E_UNTRUSTEDROOT: pszErrConst = "CERT_E_UNTRUSTEDROOT"; break; + case CERT_E_CHAINING: pszErrConst = "CERT_E_CHAINING"; break; + case CERT_E_REVOKED: pszErrConst = "CERT_E_REVOKED"; break; + case CERT_E_UNTRUSTEDTESTROOT: pszErrConst = "CERT_E_UNTRUSTEDTESTROOT"; break; + case CERT_E_REVOCATION_FAILURE: pszErrConst = "CERT_E_REVOCATION_FAILURE"; break; + case CERT_E_CN_NO_MATCH: pszErrConst = "CERT_E_CN_NO_MATCH"; break; + case CERT_E_WRONG_USAGE: pszErrConst = "CERT_E_WRONG_USAGE"; break; + case CERT_E_UNTRUSTEDCA: pszErrConst = "CERT_E_UNTRUSTEDCA"; break; + case CERT_E_INVALID_POLICY: pszErrConst = "CERT_E_INVALID_POLICY"; break; + case CERT_E_INVALID_NAME: pszErrConst = "CERT_E_INVALID_NAME"; break; + case CRYPT_E_FILE_ERROR: pszErrConst = "CRYPT_E_FILE_ERROR"; break; + case CRYPT_E_REVOKED: pszErrConst = "CRYPT_E_REVOKED"; 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); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrust: WinVerifyTrust failed with %#x (%s) on '%ls'\n", + hrc, pszErrConst, pwszName)); + if (phrcWinVerifyTrust) + *phrcWinVerifyTrust = hrc; + } + + /* 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) +{ + RT_NOREF1(fFlags); + 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 | READ_CONTROL | 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. + */ + int fNoSignedCatalogFound = -1; + 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 fFreshContext = false; + BOOL fRc; + HCATADMIN hCatAdmin = ASMAtomicXchgPtr(&s_aHashes[i].hCachedCatAdmin, NULL); + if (hCatAdmin) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: Cached context %p\n", hCatAdmin)); + fFreshContext = false; + fRc = TRUE; + } + else + { +l_fresh_context: + fFreshContext = true; + 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*/); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: New context %p\n", hCatAdmin)); + } + 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 (!fFreshContext) + { + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: Retrying with fresh context (CryptCATAdminEnumCatalogFromHash -> %u; iCat=%#x)\n", RtlGetLastWin32Error(), iCat)); + if (hCatInfoPrev != NULL) + g_pfnCryptCATAdminReleaseCatalogContext(hCatAdmin, hCatInfoPrev, 0 /*dwFlags*/); + g_pfnCryptCATAdminReleaseContext(hCatAdmin, 0 /*dwFlags*/); + goto l_fresh_context; + } + ULONG ulErr = RtlGetLastWin32Error(); + fNoSignedCatalogFound = ulErr == ERROR_NOT_FOUND && fNoSignedCatalogFound != 0; + if (iCat == 0) + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATAdminEnumCatalogFromHash failed ERROR_NOT_FOUND (%u)\n", ulErr)); + else if (iCat == 0) + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile: CryptCATAdminEnumCatalogFromHash failed %u\n", ulErr)); + break; + } + fNoSignedCatalogFound = 0; + 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'; file='%ls'\n", + hrc, CatInfo.wszCatalogFile, pwszName)); + + 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(RtlGetLastWin32Error()), + "CryptCATCatalogInfoFromContext failed: %d [file=%s]", + RtlGetLastWin32Error(), 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(RtlGetLastWin32Error()), + "CryptCATAdminCalcHashFromFileHandle[2] failed: %d [file=%s]", RtlGetLastWin32Error(), pwszName); + + if (!ASMAtomicCmpXchgPtr(&s_aHashes[i].hCachedCatAdmin, hCatAdmin, NULL)) + if (!g_pfnCryptCATAdminReleaseContext(hCatAdmin, 0 /*dwFlags*/)) + AssertFailed(); + } + else + rc = RTErrInfoSetF(pErrInfo, RTErrConvertFromWin32(RtlGetLastWin32Error()), + "CryptCATAdminAcquireContext[2] failed: %d [file=%s]", RtlGetLastWin32Error(), 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); + + /* + * DLLs that are likely candidates for local modifications. + */ + if (rc == VERR_LDRVI_NOT_SIGNED) + { + bool fCoreSystemDll = false; + PCRTUTF16 pwsz; + uint32_t cwcName = (uint32_t)RTUtf16Len(pwszName); + uint32_t cwcOther = g_System32NtPath.UniStr.Length / sizeof(WCHAR); + if (supHardViUtf16PathStartsWithEx(pwszName, cwcName, g_System32NtPath.UniStr.Buffer, cwcOther, true /*fCheckSlash*/)) + { + pwsz = pwszName + cwcOther + 1; + if ( supHardViUtf16PathIsEqual(pwsz, "uxtheme.dll") + || supHardViUtf16PathIsEqual(pwsz, "user32.dll") + || supHardViUtf16PathIsEqual(pwsz, "gdi32.dll") + || supHardViUtf16PathIsEqual(pwsz, "opengl32.dll") + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "KernelBase.dll")) + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "kernel32.dll")) + || (fCoreSystemDll = supHardViUtf16PathIsEqual(pwsz, "ntdll.dll")) + ) + { + if (RTErrInfoIsSet(pErrInfo)) + RTErrInfoAdd(pErrInfo, rc, "\n"); + RTErrInfoAddF(pErrInfo, rc, "'%ls' is most likely modified.", pwszName); + } + } + + /* Kludge for ancient windows versions we don't want to support but + users still wants to use. Keep things as safe as possible without + unnecessary effort. Problem is that 3rd party catalog files cannot + easily be found. Showstopper for ATI users. */ + if ( fNoSignedCatalogFound == 1 + && g_uNtVerCombined < SUP_NT_VER_VISTA + && !fCoreSystemDll) + { + rc = VINF_LDRVI_NOT_SIGNED; + } + } + + return rc; +} + + +/** + * Verifies the given image using WinVerifyTrust in some way. + * + * This is used by supHardenedWinVerifyImageByLdrMod as well as + * supR3HardenedScreenImage. + * + * @returns IPRT status code, modified @a rc. + * @param hFile Handle of the file to verify. + * @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 SUPHNTVI_F_XXX. + * @param rc The current status code. + * @param pfWinVerifyTrust Where to return whether WinVerifyTrust was + * actually used. + * @param pErrInfo Pointer to error info structure. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyImageTrust(HANDLE hFile, PCRTUTF16 pwszName, uint32_t fFlags, int rc, + bool *pfWinVerifyTrust, PRTERRINFO pErrInfo) +{ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = false; + + /* + * Call the windows verify trust API if we've resolved it and aren't in + * some obvious recursion. + */ + if (g_pfnWinVerifyTrust != NULL) + { + uint32_t const idCurrentThread = RTNtCurrentThreadId(); + + /* Check if loader lock owner. */ + struct _RTL_CRITICAL_SECTION volatile *pLoaderLock = NtCurrentPeb()->LoaderLock; + bool fOwnsLoaderLock = pLoaderLock + && pLoaderLock->OwningThread == (HANDLE)(uintptr_t)idCurrentThread + && pLoaderLock->RecursionCount > 0; + if (!fOwnsLoaderLock) + { + /* Check for recursion. */ + bool fNoRecursion; + if (g_iTlsWinVerifyTrustRecursion != UINT32_MAX) + { + fNoRecursion = TlsGetValue(g_iTlsWinVerifyTrustRecursion) == 0; + if (fNoRecursion) + TlsSetValue(g_iTlsWinVerifyTrustRecursion, (void *)1); + } + else + fNoRecursion = ASMAtomicCmpXchgU32(&g_idActiveThread, idCurrentThread, UINT32_MAX); + + if (fNoRecursion && !fOwnsLoaderLock) + { + /* We can call WinVerifyTrust. */ + if (pfWinVerifyTrust) + *pfWinVerifyTrust = true; + + 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)) + { + HRESULT hrcWinVerifyTrust; + rc = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust, + &hrcWinVerifyTrust); + + /* DLLs signed with special roots, like "Microsoft Digital Media Authority 2005", + may fail here because the root cert is not in the normal certificate stores + (if any). Our verification code has the basics of these certificates included + and can verify them, which is why we end up here instead of in the + VINF_LDRVI_NOT_SIGNED case above. Current workaround is to do as above. + (Intel graphics driver DLLs, like igdusc64.dll. */ + if ( RT_FAILURE(rc) + && hrcWinVerifyTrust == CERT_E_CHAINING + && (fFlags & SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION)) + { + rc = supR3HardNtViCallWinVerifyTrustCatFile(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust); + SUP_DPRINTF(("supR3HardNtViCallWinVerifyTrustCatFile -> %d (was CERT_E_CHAINING)\n", rc)); + } + } + else + { + int rc2 = supR3HardNtViCallWinVerifyTrust(hFile, pwszName, fFlags, pErrInfo, g_pfnWinVerifyTrust, NULL); + AssertMsg(RT_FAILURE_NP(rc2), + ("rc=%Rrc, rc2=%Rrc %s", rc, rc2, pErrInfo ? pErrInfo->pszMsg : "<no-err-info>")); + RT_NOREF_PV(rc2); + } + } + + /* Unwind recursion. */ + if (g_iTlsWinVerifyTrustRecursion != UINT32_MAX) + TlsSetValue(g_iTlsWinVerifyTrustRecursion, (void *)0); + else + ASMAtomicWriteU32(&g_idActiveThread, UINT32_MAX); + } + /* + * No can do. + */ + else + SUP_DPRINTF(("Detected WinVerifyTrust recursion: rc=%Rrc '%ls'.\n", rc, pwszName)); + } + else + SUP_DPRINTF(("Detected loader lock ownership: rc=%Rrc '%ls'.\n", rc, pwszName)); + } + return rc; +} + + +/** + * Checks if WinVerifyTrust is callable on the current thread. + * + * Used by the main code to figure whether it makes sense to try revalidate an + * image that hasn't passed thru WinVerifyTrust yet. + * + * @returns true if callable on current thread, false if not. + */ +DECLHIDDEN(bool) supHardenedWinIsWinVerifyTrustCallable(void) +{ + return g_pfnWinVerifyTrust != NULL + && ( g_iTlsWinVerifyTrustRecursion != UINT32_MAX + ? (uintptr_t)TlsGetValue(g_iTlsWinVerifyTrustRecursion) == 0 + : g_idActiveThread != RTNtCurrentThreadId() ); +} + + + +/** + * Initializes g_uNtVerCombined and g_NtVerInfo. + * Called from suplibHardenedWindowsMain and suplibOsInit. + */ +DECLHIDDEN(void) supR3HardenedWinInitVersion(bool fEarly) +{ + /* + * 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). + * + * Note! Windows 10 build 14267+ touches BSS when calling RtlGetVersion, so we + * have to use the fallback for the call from the early init code. + */ + OSVERSIONINFOEXW NtVerInfo; + + RT_ZERO(NtVerInfo); + NtVerInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + if ( fEarly + || !NT_SUCCESS(RtlGetVersion((PRTL_OSVERSIONINFOW)&NtVerInfo))) + { + RT_ZERO(NtVerInfo); + PPEB pPeb = NtCurrentPeb(); + NtVerInfo.dwMajorVersion = pPeb->OSMajorVersion; + NtVerInfo.dwMinorVersion = pPeb->OSMinorVersion; + NtVerInfo.dwBuildNumber = pPeb->OSBuildNumber; + } + + 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 00000000..ec6b6a0a --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp @@ -0,0 +1,2681 @@ +/* $Id: SUPHardenedVerifyProcess-win.cpp $ */ +/** @file + * VirtualBox Support Library/Driver - Hardened Process Verification, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef IN_RING0 +# ifndef IPRT_NT_MAP_TO_ZW +# define IPRT_NT_MAP_TO_ZW +# endif +# 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/alloca.h> +#include <iprt/ctype.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <iprt/zero.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; + /** This may be a 32-bit resource DLL. */ + bool f32bitResourceDll; + + /** Pointer to the loader cache entry for the image. */ + PSUPHNTLDRCACHEENTRY pCacheEntry; +#ifdef IN_RING0 + /** In ring-0 we don't currently cache images, so put it here. */ + SUPHNTLDRCACHEENTRY CacheEntry; +#endif +} SUPHNTVPIMAGE; +/** Pointer to image info from the virtual address space scan. */ +typedef SUPHNTVPIMAGE *PSUPHNTVPIMAGE; + +/** + * Virtual address space scanning state. + */ +typedef struct SUPHNTVPSTATE +{ + /** Type of verification to perform. */ + SUPHARDNTVPKIND enmKind; + /** Combination of SUPHARDNTVP_F_XXX. */ + uint32_t fFlags; + /** The result. */ + int rcResult; + /** Number of fixes we've done. + * Only applicable in the purification modes. */ + uint32_t cFixes; + /** Number of images in aImages. */ + uint32_t cImages; + /** The index of the last image we looked up. */ + uint32_t iImageHint; + /** The process handle. */ + HANDLE hProcess; + /** 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 + 6 + 1 +#ifdef VBOX_PERMIT_VERIFIER_DLL + + 1 +#endif +#ifdef VBOX_PERMIT_MORE + + 5 +#endif +#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + + 16 +#endif + ]; + /** 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]; + /** Pointer to the error info. */ + PRTERRINFO pErrInfo; +} 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", +#ifdef VBOX_PERMIT_VERIFIER_DLL + "verifier.dll", +#endif +#ifdef VBOX_PERMIT_MORE +# define VBOX_PERMIT_MORE_FIRST_IDX 5 + "sfc.dll", + "sfc_os.dll", + "user32.dll", + "acres.dll", + "acgenral.dll", +#endif +#ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + "psapi.dll", + "msvcrt.dll", + "advapi32.dll", + "sechost.dll", + "rpcrt4.dll", + "SamplingRuntime.dll", +#endif +}; + +/** + * VBox executables allowed to start VMs. + * @remarks Remember to keep in sync with g_aSupInstallFiles in + * SUPR3HardenedVerify.cpp. + */ +static const char *g_apszSupNtVpAllowedVmExes[] = +{ + "VBoxHeadless.exe", + "VirtualBoxVM.exe", + "VBoxSDL.exe", + "VBoxNetDHCP.exe", + "VBoxNetNAT.exe", + "VBoxVMMPreload.exe", + + "tstMicro.exe", + "tstPDMAsyncCompletion.exe", + "tstPDMAsyncCompletionStress.exe", + "tstVMM.exe", + "tstVMREQ.exe", + "tstCFGM.exe", + "tstGIP-2.exe", + "tstIntNet-1.exe", + "tstMMHyperHeap.exe", + "tstRTR0ThreadPreemptionDriver.exe", + "tstRTR0MemUserKernelDriver.exe", + "tstRTR0SemMutexDriver.exe", + "tstRTR0TimerDriver.exe", + "tstSSM.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 + +#ifdef IN_RING3 +/** The number of valid entries in the loader cache. */ +static uint32_t g_cSupNtVpLdrCacheEntries = 0; +/** The loader cache entries. */ +static SUPHNTLDRCACHEENTRY g_aSupNtVpLdrCacheEntries[RT_ELEMENTS(g_apszSupNtVpAllowedDlls) + 1 + 3]; +#endif + + +/** + * Fills in error information. + * + * @returns @a rc. + * @param pErrInfo Pointer to the extended error info structure. + * Can be NULL. + * @param rc The status to return. + * @param pszMsg The format string for the message. + * @param ... The arguments for the format string. + */ +static int supHardNtVpSetInfo1(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; +} + + +/** + * Adds error information. + * + * @returns @a rc. + * @param pErrInfo Pointer to the extended error info structure + * which may contain some details already. Can be + * NULL. + * @param rc The status to return. + * @param pszMsg The format string for the message. + * @param ... The arguments for the format string. + */ +static int supHardNtVpAddInfo1(PRTERRINFO pErrInfo, int rc, const char *pszMsg, ...) +{ + va_list va; +#ifdef IN_RING3 + va_start(va, pszMsg); + if (pErrInfo && pErrInfo->pszMsg) + supR3HardenedError(rc, false /*fFatal*/, "%N - %s\n", pszMsg, &va, pErrInfo->pszMsg); + else + supR3HardenedError(rc, false /*fFatal*/, "%N\n", pszMsg, &va); + va_end(va); +#endif + + va_start(va, pszMsg); + RTErrInfoAddV(pErrInfo, rc, pszMsg, va); + va_end(va); + + return rc; +} + + +/** + * Fills in error information. + * + * @returns @a rc. + * @param pThis The process validator instance. + * @param rc The status to return. + * @param pszMsg The format string for the message. + * @param ... The arguments for the format string. + */ +static int supHardNtVpSetInfo2(PSUPHNTVPSTATE pThis, 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); +#ifdef IN_RING0 + RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va); + pThis->rcResult = rc; +#else + if (RT_SUCCESS(pThis->rcResult)) + { + RTErrInfoSetV(pThis->pErrInfo, rc, pszMsg, va); + pThis->rcResult = rc; + } + else + { + RTErrInfoAddF(pThis->pErrInfo, rc, " \n[rc=%d] ", rc); + RTErrInfoAddV(pThis->pErrInfo, rc, pszMsg, va); + } +#endif + va_end(va); + + return pThis->rcResult; +} + + +static int supHardNtVpReadImage(PSUPHNTVPIMAGE pImage, uint64_t off, void *pvBuf, size_t cbRead) +{ + return pImage->pCacheEntry->pNtViRdr->Core.pfnRead(&pImage->pCacheEntry->pNtViRdr->Core, pvBuf, cbRead, off); +} + + +static NTSTATUS supHardNtVpReadMem(HANDLE hProcess, uintptr_t uPtr, void *pvBuf, size_t cbRead) +{ +#ifdef IN_RING0 + /* ASSUMES hProcess is the current process. */ + RT_NOREF1(hProcess); + /** @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 +} + + +#ifdef IN_RING3 +static NTSTATUS supHardNtVpFileMemRestore(PSUPHNTVPSTATE pThis, PVOID pvRestoreAddr, uint8_t const *pbFile, uint32_t cbToRestore, + uint32_t fCorrectProtection) +{ + PVOID pvProt = pvRestoreAddr; + SIZE_T cbProt = cbToRestore; + ULONG fOldProt = 0; + NTSTATUS rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_READWRITE, &fOldProt); + if (NT_SUCCESS(rcNt)) + { + SIZE_T cbIgnored; + rcNt = NtWriteVirtualMemory(pThis->hProcess, pvRestoreAddr, pbFile, cbToRestore, &cbIgnored); + + pvProt = pvRestoreAddr; + cbProt = cbToRestore; + NTSTATUS rcNt2 = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fCorrectProtection, &fOldProt); + if (NT_SUCCESS(rcNt)) + rcNt = rcNt2; + } + pThis->cFixes++; + return rcNt; +} +#endif /* IN_RING3 */ + + +typedef struct SUPHNTVPSKIPAREA +{ + uint32_t uRva; + uint32_t cb; +} SUPHNTVPSKIPAREA; +typedef SUPHNTVPSKIPAREA *PSUPHNTVPSKIPAREA; + +static int supHardNtVpFileMemCompareSection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, + uint32_t uRva, uint32_t cb, const uint8_t *pbFile, + int32_t iSh, PSUPHNTVPSKIPAREA paSkipAreas, uint32_t cSkipAreas, + uint32_t fCorrectProtection) +{ +#ifndef IN_RING3 + RT_NOREF1(fCorrectProtection); +#endif + AssertCompileAdjacentMembers(SUPHNTVPSTATE, abMemory, abFile); /* Use both the memory and file buffers here. Parfait might hate me for this... */ + uint32_t const cbMemory = sizeof(pThis->abMemory) + sizeof(pThis->abFile); + uint8_t * const pbMemory = &pThis->abMemory[0]; + + while (cb > 0) + { + uint32_t cbThis = RT_MIN(cb, cbMemory); + + /* Clipping. */ + uint32_t uNextRva = uRva + cbThis; + if (cSkipAreas) + { + uint32_t uRvaEnd = uNextRva; + uint32_t i = cSkipAreas; + while (i-- > 0) + { + uint32_t uSkipEnd = paSkipAreas[i].uRva + paSkipAreas[i].cb; + if ( uRva < uSkipEnd + && uRvaEnd > paSkipAreas[i].uRva) + { + if (uRva < paSkipAreas[i].uRva) + { + cbThis = paSkipAreas[i].uRva - uRva; + uRvaEnd = paSkipAreas[i].uRva; + uNextRva = uSkipEnd; + } + else if (uRvaEnd >= uSkipEnd) + { + cbThis -= uSkipEnd - uRva; + pbFile += uSkipEnd - uRva; + uRva = uSkipEnd; + } + else + { + uNextRva = uSkipEnd; + cbThis = 0; + break; + } + } + } + } + + /* Read the memory. */ + NTSTATUS rcNt = supHardNtVpReadMem(pThis->hProcess, pImage->uImageBase + uRva, pbMemory, cbThis); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_READ_ERROR, + "%s: Error reading %#x bytes at %p (rva %#x, #%u, %.8s) from memory: %#x", + pImage->pszName, cbThis, pImage->uImageBase + uRva, uRva, iSh + 1, + iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers", rcNt); + + /* Do the compare. */ + if (memcmp(pbFile, pbMemory, cbThis) != 0) + { + const char *pachSectNm = iSh >= 0 ? (char *)pThis->aSecHdrs[iSh].Name : "headers"; + SUP_DPRINTF(("%s: Differences in section #%u (%s) between file and memory:\n", pImage->pszName, iSh + 1, pachSectNm)); + + uint32_t off = 0; + while (off < cbThis && pbFile[off] == pbMemory[off]) + off++; + SUP_DPRINTF((" %p / %#09x: %02x != %02x\n", + pImage->uImageBase + uRva + off, uRva + off, pbFile[off], pbMemory[off])); + uint32_t offLast = off; + uint32_t cDiffs = 1; + for (uint32_t off2 = off + 1; off2 < cbThis; off2++) + if (pbFile[off2] != pbMemory[off2]) + { + SUP_DPRINTF((" %p / %#09x: %02x != %02x\n", + pImage->uImageBase + uRva + off2, uRva + off2, pbFile[off2], pbMemory[off2])); + cDiffs++; + offLast = off2; + } + +#ifdef IN_RING3 + if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION + || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION + || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) + { + PVOID pvRestoreAddr = (uint8_t *)pImage->uImageBase + uRva; + rcNt = supHardNtVpFileMemRestore(pThis, pvRestoreAddr, pbFile, cbThis, fCorrectProtection); + if (NT_SUCCESS(rcNt)) + SUP_DPRINTF((" Restored %#x bytes of original file content at %p\n", cbThis, pvRestoreAddr)); + else + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH, + "%s: Failed to restore %#x bytes at %p (%#x, #%u, %s): %#x (cDiffs=%#x, first=%#x)", + pImage->pszName, cbThis, pvRestoreAddr, uRva, iSh + 1, pachSectNm, rcNt, + cDiffs, uRva + off); + } + else +#endif /* IN_RING3 */ + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_MEMORY_VS_FILE_MISMATCH, + "%s: %u differences between %#x and %#x in #%u (%.8s), first: %02x != %02x", + pImage->pszName, cDiffs, uRva + off, uRva + offLast, iSh + 1, + pachSectNm, pbFile[off], pbMemory[off]); + } + + /* Advance. The clipping makes it a little bit complicated. */ + cbThis = uNextRva - uRva; + if (cbThis >= cb) + break; + cb -= cbThis; + pbFile += cbThis; + uRva = uNextRva; + } + return VINF_SUCCESS; +} + + + +static int supHardNtVpCheckSectionProtection(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, + uint32_t uRva, uint32_t cb, uint32_t fProt) +{ + uint32_t const cbOrg = cb; + if (!cb) + return VINF_SUCCESS; + if ( pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION + || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION + || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) + 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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); +} + + +DECLINLINE(bool) supHardNtVpIsModuleNameMatch(PSUPHNTVPIMAGE pImage, const char *pszModule) +{ + if (pImage->fDll) + { + const char *pszImageNm = pImage->pszName; + for (;;) + { + char chLeft = *pszImageNm++; + char chRight = *pszModule++; + if (chLeft != chRight) + { + Assert(chLeft == RT_C_TO_LOWER(chLeft)); + if (chLeft != RT_C_TO_LOWER(chRight)) + { + if ( chRight == '\0' + && chLeft == '.' + && pszImageNm[0] == 'd' + && pszImageNm[1] == 'l' + && pszImageNm[2] == 'l' + && pszImageNm[3] == '\0') + return true; + break; + } + } + + if (chLeft == '\0') + return true; + } + } + + return false; +} + + +/** + * Worker for supHardNtVpGetImport that looks up a module in the module table. + * + * @returns Pointer to the module if found, NULL if not found. + * @param pThis The process validator instance. + * @param pszModule The name of the module we're looking for. + */ +static PSUPHNTVPIMAGE supHardNtVpFindModule(PSUPHNTVPSTATE pThis, const char *pszModule) +{ + /* + * Check out the hint first. + */ + if ( pThis->iImageHint < pThis->cImages + && supHardNtVpIsModuleNameMatch(&pThis->aImages[pThis->iImageHint], pszModule)) + return &pThis->aImages[pThis->iImageHint]; + + /* + * Linear array search next. + */ + uint32_t i = pThis->cImages; + while (i-- > 0) + if (supHardNtVpIsModuleNameMatch(&pThis->aImages[i], pszModule)) + { + pThis->iImageHint = i; + return &pThis->aImages[i]; + } + + /* No cigar. */ + return NULL; +} + + +/** + * @callback_method_impl{FNRTLDRIMPORT} + */ +static DECLCALLBACK(int) supHardNtVpGetImport(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, unsigned uSymbol, + PRTLDRADDR pValue, void *pvUser) +{ + RT_NOREF1(hLdrMod); + /*SUP_DPRINTF(("supHardNtVpGetImport: %s / %#x / %s.\n", pszModule, uSymbol, pszSymbol));*/ + PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)pvUser; + + int rc = VERR_MODULE_NOT_FOUND; + PSUPHNTVPIMAGE pImage = supHardNtVpFindModule(pThis, pszModule); + if (pImage) + { + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, + pImage->uImageBase, uSymbol, pszSymbol, pValue); + if (RT_SUCCESS(rc)) + return rc; + } + /* + * API set hacks. + */ + else if (!RTStrNICmp(pszModule, RT_STR_TUPLE("api-ms-win-"))) + { + static const char * const s_apszDlls[] = { "ntdll.dll", "kernelbase.dll", "kernel32.dll" }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apszDlls); i++) + { + pImage = supHardNtVpFindModule(pThis, s_apszDlls[i]); + if (pImage) + { + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, + pImage->uImageBase, uSymbol, pszSymbol, pValue); + if (RT_SUCCESS(rc)) + return rc; + if (rc != VERR_SYMBOL_NOT_FOUND) + break; + } + } + } + + /* + * Deal with forwarders. + * ASSUMES no forwarders thru any api-ms-win-core-*.dll. + * ASSUMES forwarders are resolved after one redirection. + */ + if (rc == VERR_LDR_FORWARDER) + { + size_t cbInfo = RT_MIN((uint32_t)*pValue, sizeof(RTLDRIMPORTINFO) + 32); + PRTLDRIMPORTINFO pInfo = (PRTLDRIMPORTINFO)alloca(cbInfo); + rc = RTLdrQueryForwarderInfo(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, + uSymbol, pszSymbol, pInfo, cbInfo); + if (RT_SUCCESS(rc)) + { + rc = VERR_MODULE_NOT_FOUND; + pImage = supHardNtVpFindModule(pThis, pInfo->szModule); + if (pImage) + { + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pImage->pCacheEntry->pbBits, + pImage->uImageBase, pInfo->iOrdinal, pInfo->pszSymbol, pValue); + if (RT_SUCCESS(rc)) + return rc; + + SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol '%s' in '%s' (forwarded from %s / %s): %Rrc\n", + pInfo->pszSymbol, pInfo->szModule, pszModule, pszSymbol, rc)); + if (rc == VERR_LDR_FORWARDER) + rc = VERR_LDR_FORWARDER_CHAIN_TOO_LONG; + } + else + SUP_DPRINTF(("supHardNtVpGetImport: Failed to find forwarder module '%s' (%#x / %s; originally %s / %#x / %s): %Rrc\n", + pInfo->szModule, pInfo->iOrdinal, pInfo->pszSymbol, pszModule, uSymbol, pszSymbol, rc)); + } + else + SUP_DPRINTF(("supHardNtVpGetImport: RTLdrQueryForwarderInfo failed on symbol %#x/'%s' in '%s': %Rrc\n", + uSymbol, pszSymbol, pszModule, rc)); + } + else + SUP_DPRINTF(("supHardNtVpGetImport: Failed to find symbol %#x / '%s' in '%s': %Rrc\n", + uSymbol, pszSymbol, pszModule, rc)); + return rc; +} + + +/** + * 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. + */ +static int supHardNtVpVerifyImageMemoryCompare(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage) +{ + + /* + * Read and find the file headers. + */ + int rc = supHardNtVpReadImage(pImage, 0 /*off*/, pThis->abFile, sizeof(pThis->abFile)); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_IMAGE_HDR_READ_ERROR, + "%s: Error reading image header: %Rrc", pImage->pszName, rc); + + 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 supHardNtVpSetInfo2(pThis, 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]; + PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)pNtHdrs; + if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) + return supHardNtVpSetInfo2(pThis, 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 && !pImage->f32bitResourceDll) +#else + if (pNtHdrs->FileHeader.Machine != IMAGE_FILE_MACHINE_I386) +#endif + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNEXPECTED_IMAGE_MACHINE, + "%s: Unexpected machine: %#x", pImage->pszName, pNtHdrs->FileHeader.Machine); + bool const fIs32Bit = pNtHdrs->FileHeader.Machine == IMAGE_FILE_MACHINE_I386; + + if (pNtHdrs->FileHeader.SizeOfOptionalHeader != (fIs32Bit ? sizeof(IMAGE_OPTIONAL_HEADER32) : sizeof(IMAGE_OPTIONAL_HEADER64))) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected optional header size: %#x", + pImage->pszName, pNtHdrs->FileHeader.SizeOfOptionalHeader); + + if (pNtHdrs->OptionalHeader.Magic != (fIs32Bit ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected optional header magic: %#x", pImage->pszName, pNtHdrs->OptionalHeader.Magic); + + uint32_t cDirs = (fIs32Bit ? pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes : pNtHdrs->OptionalHeader.NumberOfRvaAndSizes); + if (cDirs != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_OPTIONAL_HEADER, + "%s: Unexpected data dirs: %#x", pImage->pszName, cDirs); + + /* + * 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 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_SECTIONS, + "%s: Too many section headers: %#x", pImage->pszName, cSections); + suplibHardenedMemCopy(pThis->aSecHdrs, (fIs32Bit ? (void *)(pNtHdrs32 + 1) : (void *)(pNtHdrs + 1)), + cSections * sizeof(IMAGE_SECTION_HEADER)); + + uintptr_t const uImageBase = fIs32Bit ? pNtHdrs32->OptionalHeader.ImageBase : pNtHdrs->OptionalHeader.ImageBase; + if (uImageBase & PAGE_OFFSET_MASK) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_BASE, + "%s: Invalid image base: %p", pImage->pszName, uImageBase); + + uint32_t const cbImage = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfImage : pNtHdrs->OptionalHeader.SizeOfImage; + if (RT_ALIGN_32(pImage->cbImage, PAGE_SIZE) != RT_ALIGN_32(cbImage, PAGE_SIZE) && !pImage->fApiSetSchemaOnlySection1) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE, + "%s: SizeOfImage (%#x) isn't close enough to the mapping size (%#x)", + pImage->pszName, cbImage, pImage->cbImage); + if (cbImage != RTLdrSize(pImage->pCacheEntry->hLdrMod)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_IMAGE_SIZE, + "%s: SizeOfImage (%#x) differs from what RTLdrSize returns (%#zx)", + pImage->pszName, cbImage, RTLdrSize(pImage->pCacheEntry->hLdrMod)); + + uint32_t const cbSectAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.SectionAlignment : pNtHdrs->OptionalHeader.SectionAlignment; + if ( !RT_IS_POWER_OF_TWO(cbSectAlign) + || cbSectAlign < PAGE_SIZE + || cbSectAlign > (pImage->fApiSetSchemaOnlySection1 ? _64K : (uint32_t)PAGE_SIZE) ) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_SECTION_ALIGNMENT_VALUE, + "%s: Unexpected SectionAlignment value: %#x", pImage->pszName, cbSectAlign); + + uint32_t const cbFileAlign = fIs32Bit ? pNtHdrs32->OptionalHeader.FileAlignment : pNtHdrs->OptionalHeader.FileAlignment; + if (!RT_IS_POWER_OF_TWO(cbFileAlign) || cbFileAlign < 512 || cbFileAlign > PAGE_SIZE || cbFileAlign > cbSectAlign) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_BAD_FILE_ALIGNMENT_VALUE, + "%s: Unexpected FileAlignment value: %#x (cbSectAlign=%#x)", + pImage->pszName, cbFileAlign, cbSectAlign); + + uint32_t const cbHeaders = fIs32Bit ? pNtHdrs32->OptionalHeader.SizeOfHeaders : pNtHdrs->OptionalHeader.SizeOfHeaders; + uint32_t const cbMinHdrs = offNtHdrs + (fIs32Bit ? sizeof(*pNtHdrs32) : sizeof(*pNtHdrs) ) + + sizeof(IMAGE_SECTION_HEADER) * cSections; + if (cbHeaders < cbMinHdrs) + return supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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)); + + /* + * Save some header fields we might be using later on. + */ + pImage->fImageCharecteristics = pNtHdrs->FileHeader.Characteristics; + pImage->fDllCharecteristics = fIs32Bit ? pNtHdrs32->OptionalHeader.DllCharacteristics : pNtHdrs->OptionalHeader.DllCharacteristics; + + /* + * Correct the apisetschema image base, size and region rva. + */ + if (pImage->fApiSetSchemaOnlySection1) + { + pImage->uImageBase -= pThis->aSecHdrs[0].VirtualAddress; + pImage->cbImage += pThis->aSecHdrs[0].VirtualAddress; + pImage->aRegions[0].uRva = pThis->aSecHdrs[0].VirtualAddress; + } + + /* + * Get relocated bits. + */ + uint8_t *pbBits; + if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, NULL /*pfnGetImport*/, pThis, + pThis->pErrInfo); + else + rc = supHardNtLdrCacheEntryGetBits(pImage->pCacheEntry, &pbBits, pImage->uImageBase, supHardNtVpGetImport, pThis, + pThis->pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + /* XP SP3 does not set ImageBase to load address. It fixes up the image on load time though. */ + if (g_uNtVerCombined >= SUP_NT_VER_VISTA) + { + if (fIs32Bit) + ((PIMAGE_NT_HEADERS32)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = (uint32_t)pImage->uImageBase; + else + ((PIMAGE_NT_HEADERS)&pbBits[offNtHdrs])->OptionalHeader.ImageBase = pImage->uImageBase; + } + + /* + * Figure out areas we should skip during comparison. + */ + uint32_t cSkipAreas = 0; + SUPHNTVPSKIPAREA aSkipAreas[7]; + if (pImage->fNtCreateSectionPatch) + { + RTLDRADDR uValue; + if (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY) + { + /* Ignore our NtCreateSection hack. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "NtCreateSection", &uValue); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'NtCreateSection': %Rrc", pImage->pszName, rc); + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; + aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12; + + /* Ignore our LdrLoadDll hack. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrLoadDll", &uValue); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrLoadDll': %Rrc", pImage->pszName, rc); + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; + aSkipAreas[cSkipAreas++].cb = ARCH_BITS == 32 ? 5 : 12; + } + + /* Ignore our patched LdrInitializeThunk hack. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrInitializeThunk", &uValue); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'LdrInitializeThunk': %Rrc", pImage->pszName, rc); + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; + aSkipAreas[cSkipAreas++].cb = 14; + + /* Ignore our patched KiUserApcDispatcher hack. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserApcDispatcher", &uValue); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserApcDispatcher': %Rrc", pImage->pszName, rc); + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; + aSkipAreas[cSkipAreas++].cb = 14; + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + /* Ignore our patched KiUserExceptionDispatcher hack. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "KiUserExceptionDispatcher", &uValue); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo2(pThis, rc, "%s: Failed to find 'KiUserExceptionDispatcher': %Rrc", pImage->pszName, rc); + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue + (HC_ARCH_BITS == 64); + aSkipAreas[cSkipAreas++].cb = HC_ARCH_BITS == 64 ? 13 : 12; +#endif + + /* LdrSystemDllInitBlock is filled in by the kernel. It mainly contains addresses of 32-bit ntdll method for wow64. */ + rc = RTLdrGetSymbolEx(pImage->pCacheEntry->hLdrMod, pbBits, 0, UINT32_MAX, "LdrSystemDllInitBlock", &uValue); + if (RT_SUCCESS(rc)) + { + aSkipAreas[cSkipAreas].uRva = (uint32_t)uValue; + aSkipAreas[cSkipAreas++].cb = RT_MAX(pbBits[(uint32_t)uValue], 0x50); + } + + Assert(cSkipAreas <= RT_ELEMENTS(aSkipAreas)); + } + + /* + * Compare the file header with the loaded bits. The loader will fiddle + * with image base, changing it to the actual load address. + */ + if (!pImage->fApiSetSchemaOnlySection1) + { + rc = supHardNtVpFileMemCompareSection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, pbBits, -1, NULL, 0, PAGE_READONLY); + if (RT_FAILURE(rc)) + return rc; + + rc = supHardNtVpCheckSectionProtection(pThis, pImage, 0 /*uRva*/, cbHdrsFile, PAGE_READONLY); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Validate sections: + * - Check them against the mapping regions. + * - Check section bits according to enmKind. + */ + uint32_t fPrevProt = PAGE_READONLY; + 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 and bits. */ + if (!pImage->fApiSetSchemaOnlySection1 || i == 0) + { + 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 ( pThis->enmKind != SUPHARDNTVPKIND_VERIFY_ONLY + && pThis->enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION + && !suplibHardenedMemComp(pThis->aSecHdrs[i].Name, ".mrdata", 8)) /* w8.1, ntdll. Changed by proc init. */ + 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; + case IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: + /* Only the executable is allowed to have this section, + and it's protected after we're done patching. */ + if (!pImage->fDll) + { + if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + fProt = PAGE_EXECUTE_READWRITE; + else + fProt = PAGE_EXECUTE_READ; + break; + } + default: + return supHardNtVpSetInfo2(pThis, 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); + } + + /* The section bits. Child purification verifies all, normal + verification verifies all except where the executable is + concerned (due to opening vboxdrv during early process init). */ + if ( ( (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE)) + && !(pThis->aSecHdrs[i].Characteristics & IMAGE_SCN_MEM_WRITE)) + || (pThis->aSecHdrs[i].Characteristics & (IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE)) == IMAGE_SCN_MEM_READ + || (pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY && pImage->fDll) + || pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + { + rc = VINF_SUCCESS; + if (uRva < uSectRva && !pImage->fApiSetSchemaOnlySection1) /* Any gap worth checking? */ + rc = supHardNtVpFileMemCompareSection(pThis, pImage, uRva, uSectRva - uRva, pbBits + uRva, + i - 1, NULL, 0, fPrevProt); + if (RT_SUCCESS(rc)) + rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva, cbMap, pbBits + uSectRva, + i, aSkipAreas, cSkipAreas, fProt); + if (RT_SUCCESS(rc)) + { + uint32_t cbMapAligned = i + 1 < cSections && !pImage->fApiSetSchemaOnlySection1 + ? RT_ALIGN_32(cbMap, cbSectAlign) : RT_ALIGN_32(cbMap, PAGE_SIZE); + if (cbMapAligned > cbMap) + rc = supHardNtVpFileMemCompareSection(pThis, pImage, uSectRva + cbMap, cbMapAligned - cbMap, + g_abRTZeroPage, i, NULL, 0, fProt); + } + if (RT_FAILURE(rc)) + return rc; + } + + /* The protection (must be checked afterwards!). */ + rc = supHardNtVpCheckSectionProtection(pThis, pImage, uSectRva, RT_ALIGN_32(cbMap, PAGE_SIZE), fProt); + if (RT_FAILURE(rc)) + return rc; + + fPrevProt = fProt; + } + + /* Advance the RVA. */ + uRva = uSectRva + RT_ALIGN_32(cbMap, cbSectAlign); + } + + 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. + */ +static int supHardNtVpVerifyImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage) +{ + /* + * Validate the file signature first, then do the memory compare. + */ + int rc; + if ( pImage->pCacheEntry != NULL + && pImage->pCacheEntry->hLdrMod != NIL_RTLDRMOD) + { + rc = supHardNtLdrCacheEntryVerify(pImage->pCacheEntry, pImage->Name.UniStr.Buffer, pThis->pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpVerifyImageMemoryCompare(pThis, pImage); + } + else + rc = supHardNtVpSetInfo2(pThis, VERR_OPEN_FAILED, "pCacheEntry/hLdrMod is NIL! Impossible!"); + 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. + */ +DECLHIDDEN(int) supHardNtVpThread(HANDLE hProcess, HANDLE hThread, PRTERRINFO pErrInfo) +{ + RT_NOREF1(hProcess); + + /* + * Use the ThreadAmILastThread request to check that there is only one + * thread in the process. + * Seems this isn't entirely reliable when hThread isn't the current thread? + */ + ULONG cbIgn = 0; + ULONG fAmI = 0; + NTSTATUS rcNt = NtQueryInformationThread(hThread, ThreadAmILastThread, &fAmI, sizeof(fAmI), &cbIgn); + if (!NT_SUCCESS(rcNt)) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_THREAD_ERROR, + "NtQueryInformationThread/ThreadAmILastThread -> %#x", rcNt); + if (!fAmI) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_THREAD_NOT_ALONE, + "More than one thread in process"); + + /** @todo Would be nice to verify the relationship between hProcess and hThread + * as well... */ + return VINF_SUCCESS; +} + + +/** + * 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. + */ +DECLHIDDEN(int) supHardNtVpDebugger(HANDLE hProcess, PRTERRINFO pErrInfo) +{ +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * 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 supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NT_QI_PROCESS_DBG_PORT_ERROR, + "NtQueryInformationProcess/ProcessDebugPort -> %#x", rcNt); + if (uPtr != 0) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_DEBUGGED, + "Debugger attached (%#zx)", uPtr); +#else + RT_NOREF2(hProcess, pErrInfo); +#endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ + return VINF_SUCCESS; +} + + +/** + * 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; + } +} + + +/** + * Compares two paths, expanding 8.3 short names as needed. + * + * @returns true / false. + * @param pUniStr1 The first path. Must be zero terminated! + * @param pUniStr2 The second path. Must be zero terminated! + */ +static bool supHardNtVpArePathsEqual(PCUNICODE_STRING pUniStr1, PCUNICODE_STRING pUniStr2) +{ + /* Both strings must be null terminated. */ + Assert(pUniStr1->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0'); + Assert(pUniStr2->Buffer[pUniStr1->Length / sizeof(WCHAR)] == '\0'); + + /* Simple compare first.*/ + if (supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2)) + return true; + + /* Make long names if needed. */ + UNICODE_STRING UniStrLong1 = { 0, 0, NULL }; + if (RTNtPathFindPossible8dot3Name(pUniStr1->Buffer)) + { + int rc = RTNtPathExpand8dot3PathA(pUniStr1, false /*fPathOnly*/, &UniStrLong1); + if (RT_SUCCESS(rc)) + pUniStr1 = &UniStrLong1; + } + + UNICODE_STRING UniStrLong2 = { 0, 0, NULL }; + if (RTNtPathFindPossible8dot3Name(pUniStr2->Buffer)) + { + int rc = RTNtPathExpand8dot3PathA(pUniStr2, false /*fPathOnly*/, &UniStrLong2); + if (RT_SUCCESS(rc)) + pUniStr2 = &UniStrLong2; + } + + /* Compare again. */ + bool fCompare = supHardNtVpAreUniStringsEqual(pUniStr1, pUniStr2); + + /* Clean up. */ + if (UniStrLong1.Buffer) + RTUtf16Free(UniStrLong1.Buffer); + if (UniStrLong2.Buffer) + RTUtf16Free(UniStrLong2.Buffer); + + return fCompare; +} + + +/** + * Records an additional memory region for an image. + * + * May trash pThis->abMemory. + * + * @returns VBox status code. + * @retval VINF_OBJECT_DESTROYED if we've unmapped the image (child + * purification only). + * @param pThis The process scanning state structure. + * @param pImage The new image structure. Only the unicode name + * buffer is valid (it's zero-terminated). + * @param pMemInfo The memory information for the image. + */ +static int supHardNtVpNewImage(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo) +{ + /* + * If the filename or path contains short names, we have to get the long + * path so that we will recognize the DLLs and their location. + */ + int rc83Exp = VERR_IGNORED; + PUNICODE_STRING pLongName = &pImage->Name.UniStr; + if (RTNtPathFindPossible8dot3Name(pLongName->Buffer)) + { + AssertCompile(sizeof(pThis->abMemory) > sizeof(pImage->Name)); + PUNICODE_STRING pTmp = (PUNICODE_STRING)pThis->abMemory; + pTmp->MaximumLength = (USHORT)RT_MIN(_64K - 1, sizeof(pThis->abMemory) - sizeof(*pTmp)) - sizeof(RTUTF16); + pTmp->Length = pImage->Name.UniStr.Length; + pTmp->Buffer = (PRTUTF16)(pTmp + 1); + memcpy(pTmp->Buffer, pLongName->Buffer, pLongName->Length + sizeof(RTUTF16)); + + rc83Exp = RTNtPathExpand8dot3Path(pTmp, false /*fPathOnly*/); + Assert(rc83Exp == VINF_SUCCESS); + Assert(pTmp->Buffer[pTmp->Length / sizeof(RTUTF16)] == '\0'); + if (rc83Exp == VINF_SUCCESS) + SUP_DPRINTF(("supHardNtVpNewImage: 8dot3 -> long: '%ls' -> '%ls'\n", pLongName->Buffer, pTmp->Buffer)); + else + SUP_DPRINTF(("supHardNtVpNewImage: RTNtPathExpand8dot3Path returns %Rrc for '%ls' (-> '%ls')\n", + rc83Exp, pLongName->Buffer, pTmp->Buffer)); + + pLongName = pTmp; + } + + /* + * Extract the final component. + */ + RTUTF16 wc; + unsigned cwcDirName = pLongName->Length / sizeof(WCHAR); + PCRTUTF16 pwszFilename = &pLongName->Buffer[cwcDirName]; + while ( cwcDirName > 0 + && (wc = pwszFilename[-1]) != '\\' + && wc != '/' + && wc != ':') + { + pwszFilename--; + cwcDirName--; + } + if (!*pwszFilename) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_IMAGE_MAPPING_NAME, + "Empty filename (len=%u) for image at %p.", pLongName->Length, pMemInfo->BaseAddress); + + /* + * Drop trailing slashes from the directory name. + */ + while ( cwcDirName > 0 + && ( pLongName->Buffer[cwcDirName - 1] == '\\' + || pLongName->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; + +#ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + /* The directory name must match the one we've got for System32. */ + if ( ( cwcDirName * sizeof(WCHAR) != g_System32NtPath.UniStr.Length + || suplibHardenedMemComp(pLongName->Buffer, g_System32NtPath.UniStr.Buffer, cwcDirName * sizeof(WCHAR)) ) +# ifdef VBOX_PERMIT_MORE + && ( pImage->pszName[0] != 'a' + || pImage->pszName[1] != 'c' + || !supHardViIsAppPatchDir(pLongName->Buffer, pLongName->Length / sizeof(WCHAR)) ) +# endif + ) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NON_SYSTEM32_DLL, + "Expected %ls to be loaded from %ls.", + pLongName->Buffer, g_System32NtPath.UniStr.Buffer); +# ifdef VBOX_PERMIT_MORE + if (g_uNtVerCombined < SUP_NT_VER_W70 && i >= VBOX_PERMIT_MORE_FIRST_IDX) + pImage->pszName = NULL; /* hard limit: user32.dll is unwanted prior to w7. */ +# endif + +#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */ + break; + } + if (!pImage->pszName) + { + /* + * Not a known DLL, is it a known 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) + { + /* + * Unknown image. + * + * If we're cleaning up a child process, we can unmap the offending + * DLL... Might have interesting side effects, or at least interesting + * as in "may you live in interesting times". + */ +#ifdef IN_RING3 + if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress + && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + { + SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping image mem at %p (%p LB %#zx) - '%ls'\n", + pMemInfo->AllocationBase, pMemInfo->BaseAddress, pMemInfo->RegionSize, pwszFilename)); + NTSTATUS rcNt = NtUnmapViewOfSection(pThis->hProcess, pMemInfo->AllocationBase); + if (NT_SUCCESS(rcNt)) + return VINF_OBJECT_DESTROYED; + pThis->cFixes++; + SUP_DPRINTF(("supHardNtVpScanVirtualMemory: NtUnmapViewOfSection(,%p) failed: %#x\n", pMemInfo->AllocationBase, rcNt)); + } + else if (pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) + { + SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Ignoring unknown mem at %p LB %#zx (base %p) - '%ls'\n", + pMemInfo->BaseAddress, pMemInfo->RegionSize, pMemInfo->AllocationBase, pwszFilename)); + return VINF_OBJECT_DESTROYED; + } +#endif + /* + * Special error message if we can. + */ + if ( pMemInfo->AllocationBase == pMemInfo->BaseAddress + && ( supHardNtVpAreNamesEqual("sysfer.dll", pwszFilename) + || supHardNtVpAreNamesEqual("sysfer32.dll", pwszFilename) + || supHardNtVpAreNamesEqual("sysfer64.dll", pwszFilename) + || supHardNtVpAreNamesEqual("sysfrethunk.dll", pwszFilename)) ) + { + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SYSFER_DLL, + "Found %ls at %p - This is probably part of Symantec Endpoint Protection. \n" + "You or your admin need to add and exception to the Application and Device Control (ADC) " + "component (or disable it) to prevent ADC from injecting itself into the VirtualBox VM processes. " + "See http://www.symantec.com/connect/articles/creating-application-control-exclusions-symantec-endpoint-protection-121" + , pLongName->Buffer, pMemInfo->BaseAddress); + return pThis->rcResult = VERR_SUP_VP_SYSFER_DLL; /* Try make sure this is what the user sees first! */ + } + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE, + "Unknown image file %ls at %p. (rc83Exp=%Rrc)", + pLongName->Buffer, pMemInfo->BaseAddress, rc83Exp); + } + + /* + * Checks for multiple mappings of the same DLL but with different image file paths. + */ + uint32_t i = pThis->cImages; + while (i-- > 1) + if (pImage->pszName == pThis->aImages[i].pszName) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DUPLICATE_DLL_MAPPING, + "Duplicate image entries for %s: %ls and %ls", + pImage->pszName, pImage->Name.UniStr.Buffer, pThis->aImages[i].Name.UniStr.Buffer); + + /* + * Since it's a new image, we expect to be at the start of the mapping now. + */ + if (pMemInfo->AllocationBase != pMemInfo->BaseAddress) + return supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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->pCacheEntry= NULL; + pImage->cRegions = 1; + pImage->aRegions[0].uRva = 0; + pImage->aRegions[0].cb = (uint32_t)pMemInfo->RegionSize; + pImage->aRegions[0].fProt = pMemInfo->Protect; + + if (suplibHardenedStrCmp(pImage->pszName, "ntdll.dll") == 0) + pImage->fNtCreateSectionPatch = true; + else if (suplibHardenedStrCmp(pImage->pszName, "apisetschema.dll") == 0) + pImage->fApiSetSchemaOnlySection1 = true; /** @todo Check the ApiSetMap field in the PEB. */ +#ifdef VBOX_PERMIT_MORE + else if (suplibHardenedStrCmp(pImage->pszName, "acres.dll") == 0) + pImage->f32bitResourceDll = true; +#endif + + return VINF_SUCCESS; +} + + +/** + * Records an additional memory region for an image. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. + * @param pImage The image. + * @param pMemInfo The memory information for the region. + */ +static int supHardNtVpAddRegion(PSUPHNTVPSTATE pThis, PSUPHNTVPIMAGE pImage, PMEMORY_BASIC_INFORMATION pMemInfo) +{ + /* + * Make sure the base address matches. + */ + if (pImage->uImageBase != (uintptr_t)pMemInfo->AllocationBase) + return supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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++; + pImage->fApiSetSchemaOnlySection1 = false; + + return VINF_SUCCESS; +} + + +#ifdef IN_RING3 +/** + * Frees (or replaces) executable memory of allocation type private. + * + * @returns True if nothing really bad happen, false if to quit ASAP because we + * killed the process being scanned. + * @param pThis The process scanning state structure. Details + * about images are added to this. + * @param hProcess The process to verify. + * @param pMemInfo The information we've got on this private + * executable memory. + */ +static bool supHardNtVpFreeOrReplacePrivateExecMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess, + MEMORY_BASIC_INFORMATION const *pMemInfo) +{ + NTSTATUS rcNt; + + /* + * Try figure the entire allocation size. Free/Alloc may fail otherwise. + */ + PVOID pvFree = pMemInfo->AllocationBase; + SIZE_T cbFree = pMemInfo->RegionSize + ((uintptr_t)pMemInfo->BaseAddress - (uintptr_t)pMemInfo->AllocationBase); + for (;;) + { + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo2 = { 0, 0, 0, 0, 0, 0, 0 }; + uintptr_t uPtrNext = (uintptr_t)pvFree + cbFree; + rcNt = g_pfnNtQueryVirtualMemory(hProcess, + (void const *)uPtrNext, + MemoryBasicInformation, + &MemInfo2, + sizeof(MemInfo2), + &cbActual); + if (!NT_SUCCESS(rcNt)) + break; + if (pMemInfo->AllocationBase != MemInfo2.AllocationBase) + break; + if (MemInfo2.RegionSize == 0) + break; + cbFree += MemInfo2.RegionSize; + } + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: %s exec mem at %p (LB %#zx, %p LB %#zx)\n", + pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW ? "Replacing" : "Freeing", + pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize)); + + /* + * In the BSOD workaround mode, we need to make a copy of the memory before + * freeing it. Bird abuses this code for logging purposes too. + */ + uintptr_t uCopySrc = (uintptr_t)pvFree; + size_t cbCopy = 0; + void *pvCopy = NULL; + //if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW) + { + cbCopy = cbFree; + pvCopy = RTMemAllocZ(cbCopy); + if (!pvCopy) + { + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, "RTMemAllocZ(%#zx) failed", cbCopy); + return true; + } + + rcNt = supHardNtVpReadMem(hProcess, uCopySrc, pvCopy, cbCopy); + if (!NT_SUCCESS(rcNt)) + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, + "Error reading data from original alloc: %#x (%p LB %#zx)", rcNt, uCopySrc, cbCopy, rcNt); + for (size_t off = 0; off < cbCopy; off += 256) + { + size_t const cbChunk = RT_MIN(256, cbCopy - off); + void const *pvChunk = (uint8_t const *)pvCopy + off; + if (!ASMMemIsZero(pvChunk, cbChunk)) + SUP_DPRINTF(("%.*RhxD\n", cbChunk, pvChunk)); + } + if (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW) + supR3HardenedLogFlush(); + } + + /* + * Free the memory. + */ + for (uint32_t i = 0; i < 10; i++) + { + PVOID pvFreeInOut = pvFree; + SIZE_T cbFreeInOut = 0; + rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 succeeded: %#x [%p/%p LB 0/%#zx]\n", + rcNt, pvFree, pvFreeInOut, cbFreeInOut)); + supR3HardenedLogFlush(); + } + else + { + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #1 failed: %#x [%p LB 0]\n", rcNt, pvFree)); + supR3HardenedLogFlush(); + pvFreeInOut = pvFree; + cbFreeInOut = cbFree; + rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 succeeded: %#x [%p/%p LB %#zx/%#zx]\n", + rcNt, pvFree, pvFreeInOut, cbFree, cbFreeInOut)); + supR3HardenedLogFlush(); + } + else + { + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #2 failed: %#x [%p LB %#zx]\n", + rcNt, pvFree, cbFree)); + supR3HardenedLogFlush(); + pvFreeInOut = pMemInfo->BaseAddress; + cbFreeInOut = pMemInfo->RegionSize; + rcNt = NtFreeVirtualMemory(hProcess, &pvFreeInOut, &cbFreeInOut, MEM_RELEASE); + if (NT_SUCCESS(rcNt)) + { + pvFree = pMemInfo->BaseAddress; + cbFree = pMemInfo->RegionSize; + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Free attempt #3 succeeded [%p LB %#zx]\n", + pvFree, cbFree)); + supR3HardenedLogFlush(); + } + else + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED, + "NtFreeVirtualMemory [%p LB %#zx and %p LB %#zx] failed: %#x", + pvFree, cbFree, pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt); + } + } + + /* + * Query the region again, redo the free operation if there's still memory there. + */ + if (!NT_SUCCESS(rcNt)) + break; + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo3 = { 0, 0, 0, 0, 0, 0, 0 }; + NTSTATUS rcNt2 = g_pfnNtQueryVirtualMemory(hProcess, pvFree, MemoryBasicInformation, + &MemInfo3, sizeof(MemInfo3), &cbActual); + if (!NT_SUCCESS(rcNt2)) + break; + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: QVM after free %u: [%p]/%p LB %#zx s=%#x ap=%#x rp=%#p\n", + i, MemInfo3.AllocationBase, MemInfo3.BaseAddress, MemInfo3.RegionSize, MemInfo3.State, + MemInfo3.AllocationProtect, MemInfo3.Protect)); + supR3HardenedLogFlush(); + if (MemInfo3.State == MEM_FREE || !(pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)) + break; + NtYieldExecution(); + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Retrying free...\n")); + supR3HardenedLogFlush(); + } + + /* + * Restore memory as non-executable - Kludge for Trend Micro sakfile.sys + * and Digital Guardian dgmaster.sys BSODs. + */ + if (NT_SUCCESS(rcNt) && (pThis->fFlags & SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW)) + { + PVOID pvAlloc = pvFree; + SIZE_T cbAlloc = cbFree; + rcNt = NtAllocateVirtualMemory(hProcess, &pvAlloc, 0, &cbAlloc, MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(rcNt)) + { + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, + "NtAllocateVirtualMemory (%p LB %#zx) failed with rcNt=%#x allocating " + "replacement memory for working around buggy protection software. " + "See VBoxStartup.log for more details", + pvAlloc, cbFree, rcNt); + supR3HardenedLogFlush(); + NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); + return false; + } + + if ( (uintptr_t)pvFree < (uintptr_t)pvAlloc + || (uintptr_t)pvFree + cbFree > (uintptr_t)pvAlloc + cbFree) + { + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED, + "We wanted NtAllocateVirtualMemory to get us %p LB %#zx, but it returned %p LB %#zx.", + pMemInfo->BaseAddress, pMemInfo->RegionSize, pvFree, cbFree, rcNt); + supR3HardenedLogFlush(); + NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); + return false; + } + + /* + * Copy what we can, considering the 2nd free attempt. + */ + uint8_t *pbDst = (uint8_t *)pvFree; + size_t cbDst = cbFree; + uint8_t *pbSrc = (uint8_t *)pvCopy; + size_t cbSrc = cbCopy; + if ((uintptr_t)pbDst != uCopySrc) + { + if ((uintptr_t)pbDst > uCopySrc) + { + uintptr_t cbAdj = (uintptr_t)pbDst - uCopySrc; + pbSrc += cbAdj; + cbSrc -= cbAdj; + } + else + { + uintptr_t cbAdj = uCopySrc - (uintptr_t)pbDst; + pbDst += cbAdj; + cbDst -= cbAdj; + } + } + if (cbSrc > cbDst) + cbSrc = cbDst; + + SIZE_T cbWritten; + rcNt = NtWriteVirtualMemory(hProcess, pbDst, pbSrc, cbSrc, &cbWritten); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supHardNtVpFreeOrReplacePrivateExecMemory: Restored the exec memory as non-exec.\n")); + supR3HardenedLogFlush(); + } + else + { + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_FREE_VIRTUAL_MEMORY_FAILED, + "NtWriteVirtualMemory (%p LB %#zx) failed: %#x", + pMemInfo->BaseAddress, pMemInfo->RegionSize, rcNt); + supR3HardenedLogFlush(); + NtTerminateProcess(hProcess, VERR_SUP_VP_REPLACE_VIRTUAL_MEMORY_FAILED); + return false; + } + } + if (pvCopy) + RTMemFree(pvCopy); + return true; +} +#endif /* IN_RING3 */ + + +/** + * 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. May trash pThis->abMemory. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. Details + * about images are added to this. + * @param hProcess The process to verify. + */ +static int supHardNtVpScanVirtualMemory(PSUPHNTVPSTATE pThis, HANDLE hProcess) +{ + SUP_DPRINTF(("supHardNtVpScanVirtualMemory: enmKind=%s\n", + pThis->enmKind == SUPHARDNTVPKIND_VERIFY_ONLY ? "VERIFY_ONLY" : + pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION ? "CHILD_PURIFICATION" : "SELF_PURIFICATION")); + + uint32_t cXpExceptions = 0; + uintptr_t cbAdvance = 0; + uintptr_t uPtrWhere = 0; +#ifdef VBOX_PERMIT_VERIFIER_DLL + for (uint32_t i = 0; i < 10240; i++) +#else + for (uint32_t i = 0; i < 1024; i++) +#endif + { + 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 pThis->rcResult; + return supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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'; + SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress + ? " *%p-%p %#06x/%#06x %#09x %ls\n" + : " %p-%p %#06x/%#06x %#09x %ls\n", + MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, + MemInfo.AllocationProtect, MemInfo.Type, pThis->aImages[iImg].Name.UniStr.Buffer)); + + /* 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, &pThis->aImages[iSearch], &MemInfo); + if (RT_FAILURE(rc)) + return rc; + fNew = false; + break; + } + else if (pThis->aImages[iSearch].uImageBase == (uintptr_t)MemInfo.AllocationBase) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_MAPPING_NAME_CHANGED, + "Unexpected base address match"); + + if (fNew) + { + int rc = supHardNtVpNewImage(pThis, &pThis->aImages[iImg], &MemInfo); + if (RT_SUCCESS(rc)) + { + if (rc != VINF_OBJECT_DESTROYED) + { + pThis->cImages++; + if (pThis->cImages >= RT_ELEMENTS(pThis->aImages)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_DLLS_LOADED, + "Internal error: aImages is full.\n"); + } + } +#ifdef IN_RING3 /* Continue and add more information if unknown DLLs are found. */ + else if (rc != VERR_SUP_VP_NOT_KNOWN_DLL_OR_EXE && rc != VERR_SUP_VP_NON_SYSTEM32_DLL) + return rc; +#else + else + return rc; +#endif + } + } + /* + * 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++; + SUP_DPRINTF((" %p-%p %#06x/%#06x %#09x XP CSRSS read-only region\n", MemInfo.BaseAddress, + (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, MemInfo.Protect, + MemInfo.AllocationProtect, MemInfo.Type)); + } + /* + * Executable memory? + */ +#ifndef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + else if (MemInfo.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) + { + SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress + ? " *%p-%p %#06x/%#06x %#09x !!\n" + : " %p-%p %#06x/%#06x %#09x !!\n", + MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, + MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type)); +# ifdef IN_RING3 + if (pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + { + /* + * Free any private executable memory (sysplant.sys allocates executable memory). + */ + if (MemInfo.Type == MEM_PRIVATE) + { + if (!supHardNtVpFreeOrReplacePrivateExecMemory(pThis, hProcess, &MemInfo)) + break; + } + /* + * Unmap mapped memory, failing that, drop exec privileges. + */ + else if (MemInfo.Type == MEM_MAPPED) + { + SUP_DPRINTF(("supHardNtVpScanVirtualMemory: Unmapping exec mem at %p (%p/%p LB %#zx)\n", + uPtrWhere, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize)); + rcNt = NtUnmapViewOfSection(hProcess, MemInfo.AllocationBase); + if (!NT_SUCCESS(rcNt)) + { + PVOID pvCopy = MemInfo.BaseAddress; + SIZE_T cbCopy = MemInfo.RegionSize; + NTSTATUS rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_NOACCESS, NULL); + if (!NT_SUCCESS(rcNt2)) + rcNt2 = NtProtectVirtualMemory(hProcess, &pvCopy, &cbCopy, PAGE_READONLY, NULL); + if (!NT_SUCCESS(rcNt2)) + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNMAP_AND_PROTECT_FAILED, + "NtUnmapViewOfSection (%p/%p LB %#zx) failed: %#x (%#x)", + MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize, rcNt, rcNt2); + } + } + else + supHardNtVpSetInfo2(pThis, VERR_SUP_VP_UNKOWN_MEM_TYPE, + "Unknown executable memory type %#x at %p/%p LB %#zx", + MemInfo.Type, MemInfo.AllocationBase, MemInfo.BaseAddress, MemInfo.RegionSize); + pThis->cFixes++; + } + else if (pThis->enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) +# endif /* IN_RING3 */ + supHardNtVpSetInfo2(pThis, 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); + +# ifndef IN_RING3 + if (RT_FAILURE(pThis->rcResult)) + return pThis->rcResult; +# endif + /* Continue add more information about the problematic process. */ + } +#endif /* VBOX_PERMIT_VISUAL_STUDIO_PROFILING */ + else + SUP_DPRINTF((MemInfo.AllocationBase == MemInfo.BaseAddress + ? " *%p-%p %#06x/%#06x %#09x\n" + : " %p-%p %#06x/%#06x %#09x\n", + MemInfo.BaseAddress, (uintptr_t)MemInfo.BaseAddress + MemInfo.RegionSize - 1, + MemInfo.Protect, MemInfo.AllocationProtect, MemInfo.Type)); + + /* + * Advance. + */ + cbAdvance = MemInfo.RegionSize; + if (uPtrWhere + cbAdvance <= uPtrWhere) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_EMPTY_REGION_TOO_LARGE, + "Empty region at %p.", uPtrWhere); + uPtrWhere += MemInfo.RegionSize; + } + + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_TOO_MANY_MEMORY_REGIONS, + "Too many virtual memory regions.\n"); +} + + +/** + * Verifies the loader image, i.e. check cryptographic signatures if present. + * + * @returns VBox status code. + * @param pEntry The loader cache entry. + * @param pwszName The filename to use in error messages. + * @param pErrInfo Where to return extened error information. + */ +DECLHIDDEN(int) supHardNtLdrCacheEntryVerify(PSUPHNTLDRCACHEENTRY pEntry, PCRTUTF16 pwszName, PRTERRINFO pErrInfo) +{ + int rc = VINF_SUCCESS; + if (!pEntry->fVerified) + { + rc = supHardenedWinVerifyImageByLdrMod(pEntry->hLdrMod, pwszName, pEntry->pNtViRdr, + false /*fAvoidWinVerifyTrust*/, NULL /*pfWinVerifyTrust*/, pErrInfo); + pEntry->fVerified = RT_SUCCESS(rc); + } + return rc; +} + + +/** + * Allocates a image bits buffer and calls RTLdrGetBits on them. + * + * An assumption here is that there won't ever be concurrent use of the cache. + * It's currently 104% single threaded, non-reentrant. Thus, we can't reuse the + * pbBits allocation. + * + * @returns VBox status code + * @param pEntry The loader cache entry. + * @param ppbBits Where to return the pointer to the allocation. + * @param uBaseAddress The image base address, see RTLdrGetBits. + * @param pfnGetImport Import getter, see RTLdrGetBits. + * @param pvUser The user argument for @a pfnGetImport. + * @param pErrInfo Where to return extened error information. + */ +DECLHIDDEN(int) supHardNtLdrCacheEntryGetBits(PSUPHNTLDRCACHEENTRY pEntry, uint8_t **ppbBits, + RTLDRADDR uBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser, + PRTERRINFO pErrInfo) +{ + int rc; + + /* + * First time around we have to allocate memory before we can get the image bits. + */ + if (!pEntry->pbBits) + { + size_t cbBits = RTLdrSize(pEntry->hLdrMod); + if (cbBits >= _1M*32U) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_TOO_BIG, "Image %s is too large: %zu bytes (%#zx).", + pEntry->pszName, cbBits, cbBits); + + pEntry->pbBits = (uint8_t *)RTMemAllocZ(cbBits); + if (!pEntry->pbBits) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes for image %s.", + cbBits, pEntry->pszName); + + pEntry->fValidBits = false; /* paranoia */ + + rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc", + pEntry->pszName, rc); + pEntry->uImageBase = uBaseAddress; + pEntry->fValidBits = pfnGetImport == NULL; + + } + /* + * Cache hit? No? + * + * Note! We cannot currently cache image bits for images with imports as we + * don't control the way they're resolved. Fortunately, NTDLL and + * the VM process images all have no imports. + */ + else if ( !pEntry->fValidBits + || pEntry->uImageBase != uBaseAddress + || pfnGetImport) + { + pEntry->fValidBits = false; + + rc = RTLdrGetBits(pEntry->hLdrMod, pEntry->pbBits, uBaseAddress, pfnGetImport, pvUser); + if (RT_FAILURE(rc)) + return supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY, "RTLdrGetBits failed on image %s: %Rrc", + pEntry->pszName, rc); + pEntry->uImageBase = uBaseAddress; + pEntry->fValidBits = pfnGetImport == NULL; + } + + *ppbBits = pEntry->pbBits; + return VINF_SUCCESS; +} + + +/** + * Frees all resources associated with a cache entry and wipes the members + * clean. + * + * @param pEntry The entry to delete. + */ +static void supHardNTLdrCacheDeleteEntry(PSUPHNTLDRCACHEENTRY pEntry) +{ + if (pEntry->pbBits) + { + RTMemFree(pEntry->pbBits); + pEntry->pbBits = NULL; + } + + if (pEntry->hLdrMod != NIL_RTLDRMOD) + { + RTLdrClose(pEntry->hLdrMod); + pEntry->hLdrMod = NIL_RTLDRMOD; + pEntry->pNtViRdr = NULL; + } + else if (pEntry->pNtViRdr) + { + pEntry->pNtViRdr->Core.pfnDestroy(&pEntry->pNtViRdr->Core); + pEntry->pNtViRdr = NULL; + } + + if (pEntry->hFile) + { + NtClose(pEntry->hFile); + pEntry->hFile = NULL; + } + + pEntry->pszName = NULL; + pEntry->fVerified = false; + pEntry->fValidBits = false; + pEntry->uImageBase = 0; +} + +#ifdef IN_RING3 + +/** + * Flushes the cache. + * + * This is called from one of two points in the hardened main code, first is + * after respawning and the second is when we open the vboxdrv device for + * unrestricted access. + */ +DECLHIDDEN(void) supR3HardenedWinFlushLoaderCache(void) +{ + uint32_t i = g_cSupNtVpLdrCacheEntries; + while (i-- > 0) + supHardNTLdrCacheDeleteEntry(&g_aSupNtVpLdrCacheEntries[i]); + g_cSupNtVpLdrCacheEntries = 0; +} + + +/** + * Searches the cache for a loader image. + * + * @returns Pointer to the cache entry if found, NULL if not. + * @param pszName The name (from g_apszSupNtVpAllowedVmExes or + * g_apszSupNtVpAllowedDlls). + */ +static PSUPHNTLDRCACHEENTRY supHardNtLdrCacheLookupEntry(const char *pszName) +{ + /* + * Since the caller is supplying us a pszName from one of the two tables, + * we can dispense with string compare and simply compare string pointers. + */ + uint32_t i = g_cSupNtVpLdrCacheEntries; + while (i-- > 0) + if (g_aSupNtVpLdrCacheEntries[i].pszName == pszName) + return &g_aSupNtVpLdrCacheEntries[i]; + return NULL; +} + +#endif /* IN_RING3 */ + +static int supHardNtLdrCacheNewEntry(PSUPHNTLDRCACHEENTRY pEntry, const char *pszName, PUNICODE_STRING pUniStrPath, + bool fDll, bool f32bitResourceDll, PRTERRINFO pErrInfo) +{ + /* + * Open the image file. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, pUniStrPath, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); +#ifdef IN_RING0 + ObjAttr.Attributes |= OBJ_KERNEL_HANDLE; +#endif + + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ | 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 supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_IMAGE_FILE_OPEN_ERROR, + "Error opening image for scanning: %#x (name %ls)", rcNt, pUniStrPath->Buffer); + + /* + * Figure out validation flags we'll be using and create the reader + * for this image. + */ + uint32_t fFlags = fDll + ? SUPHNTVI_F_TRUSTED_INSTALLER_OWNER | SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION + : SUPHNTVI_F_REQUIRE_BUILD_CERT; + if (f32bitResourceDll) + fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE; + + PSUPHNTVIRDR pNtViRdr; + int rc = supHardNtViRdrCreate(hFile, pUniStrPath->Buffer, fFlags, &pNtViRdr); + if (RT_FAILURE(rc)) + { + NtClose(hFile); + return rc; + } + + /* + * Finally, open the image with the loader + */ + RTLDRMOD hLdrMod; + RTLDRARCH enmArch = fFlags & SUPHNTVI_F_RC_IMAGE ? RTLDRARCH_X86_32 : RTLDRARCH_HOST; + if (fFlags & SUPHNTVI_F_IGNORE_ARCHITECTURE) + enmArch = RTLDRARCH_WHATEVER; + rc = RTLdrOpenWithReader(&pNtViRdr->Core, RTLDR_O_FOR_VALIDATION, enmArch, &hLdrMod, pErrInfo); + if (RT_FAILURE(rc)) + return supHardNtVpAddInfo1(pErrInfo, rc, "RTLdrOpenWithReader failed: %Rrc (Image='%ls').", + rc, pUniStrPath->Buffer); + + /* + * Fill in the cache entry. + */ + pEntry->pszName = pszName; + pEntry->hLdrMod = hLdrMod; + pEntry->pNtViRdr = pNtViRdr; + pEntry->hFile = hFile; + pEntry->pbBits = NULL; + pEntry->fVerified = false; + pEntry->fValidBits = false; + pEntry->uImageBase = ~(uintptr_t)0; + +#ifdef IN_SUP_HARDENED_R3 + /* + * Log the image timestamp when in the hardened exe. + */ + uint64_t uTimestamp = 0; + rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimestamp, sizeof(uint64_t)); + SUP_DPRINTF(("%s: timestamp %#llx (rc=%Rrc)\n", pszName, uTimestamp, rc)); +#endif + + return VINF_SUCCESS; +} + +#ifdef IN_RING3 +/** + * Opens a loader cache entry. + * + * Currently this is only used by the import code for getting NTDLL. + * + * @returns VBox status code. + * @param pszName The DLL name. Must be one from the + * g_apszSupNtVpAllowedDlls array. + * @param ppEntry Where to return the entry we've opened/found. + * @param pErrInfo Optional buffer where to return additional error + * information. + */ +DECLHIDDEN(int) supHardNtLdrCacheOpen(const char *pszName, PSUPHNTLDRCACHEENTRY *ppEntry, PRTERRINFO pErrInfo) +{ + /* + * Locate the dll. + */ + uint32_t i = 0; + while ( i < RT_ELEMENTS(g_apszSupNtVpAllowedDlls) + && strcmp(pszName, g_apszSupNtVpAllowedDlls[i])) + i++; + if (i >= RT_ELEMENTS(g_apszSupNtVpAllowedDlls)) + return VERR_FILE_NOT_FOUND; + pszName = g_apszSupNtVpAllowedDlls[i]; + + /* + * Try the cache. + */ + *ppEntry = supHardNtLdrCacheLookupEntry(pszName); + if (*ppEntry) + return VINF_SUCCESS; + + /* + * Not in the cache, so open it. + * Note! We cannot assume that g_System32NtPath has been initialized at this point. + */ + if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries)) + return VERR_INTERNAL_ERROR_3; + + static WCHAR s_wszSystem32[] = L"\\SystemRoot\\System32\\"; + WCHAR wszPath[64]; + memcpy(wszPath, s_wszSystem32, sizeof(s_wszSystem32)); + RTUtf16CatAscii(wszPath, sizeof(wszPath), pszName); + + UNICODE_STRING UniStr; + UniStr.Buffer = wszPath; + UniStr.Length = (USHORT)(RTUtf16Len(wszPath) * sizeof(WCHAR)); + UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR); + + int rc = supHardNtLdrCacheNewEntry(&g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries], pszName, &UniStr, + true /*fDll*/, false /*f32bitResourceDll*/, pErrInfo); + if (RT_SUCCESS(rc)) + { + *ppEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries]; + g_cSupNtVpLdrCacheEntries++; + return VINF_SUCCESS; + } + return rc; +} +#endif /* IN_RING3 */ + + +/** + * Opens all the images with the IPRT loader, setting both, hFile, pNtViRdr and + * hLdrMod for each image. + * + * @returns VBox status code. + * @param pThis The process scanning state structure. + */ +static int supHardNtVpOpenImages(PSUPHNTVPSTATE pThis) +{ + unsigned i = pThis->cImages; + while (i-- > 0) + { + PSUPHNTVPIMAGE pImage = &pThis->aImages[i]; + +#ifdef IN_RING3 + /* + * Try the cache first. + */ + pImage->pCacheEntry = supHardNtLdrCacheLookupEntry(pImage->pszName); + if (pImage->pCacheEntry) + continue; + + /* + * Not in the cache, so load it into the cache. + */ + if (g_cSupNtVpLdrCacheEntries >= RT_ELEMENTS(g_aSupNtVpLdrCacheEntries)) + return supHardNtVpSetInfo2(pThis, VERR_INTERNAL_ERROR_3, "Loader cache overflow."); + pImage->pCacheEntry = &g_aSupNtVpLdrCacheEntries[g_cSupNtVpLdrCacheEntries]; +#else + /* + * In ring-0 we don't have a cache at the moment (resource reasons), so + * we have a static cache entry in each image structure that we use instead. + */ + pImage->pCacheEntry = &pImage->CacheEntry; +#endif + + int rc = supHardNtLdrCacheNewEntry(pImage->pCacheEntry, pImage->pszName, &pImage->Name.UniStr, + pImage->fDll, pImage->f32bitResourceDll, pThis->pErrInfo); + if (RT_FAILURE(rc)) + return rc; +#ifdef IN_RING3 + g_cSupNtVpLdrCacheEntries++; +#endif + } + + return VINF_SUCCESS; +} + + +/** + * 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. The hProcess + * member holds the handle to the process that is + * to be verified. + */ +static int supHardNtVpCheckExe(PSUPHNTVPSTATE pThis) +{ + /* + * 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 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_FOUND_NO_EXE_MAPPING, + "No executable mapping found in the virtual address space."); + if (cExecs != 1) + return supHardNtVpSetInfo2(pThis, 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)RTMemAllocZ(cbUniStr); + if (!pUniStr) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, + "Error allocating %zu bytes for process name.", cbUniStr); + ULONG cbIgn = 0; + NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageFileName, pUniStr, cbUniStr - sizeof(WCHAR), &cbIgn); + if (NT_SUCCESS(rcNt)) + { + pUniStr->Buffer[pUniStr->Length / sizeof(WCHAR)] = '\0'; + if (supHardNtVpArePathsEqual(pUniStr, &pImage->Name.UniStr)) + rc = VINF_SUCCESS; + else + rc = supHardNtVpSetInfo2(pThis, 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 = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_NM_ERROR, + "NtQueryInformationProcess/ProcessImageFileName failed: %#x", rcNt); + RTMemFree(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); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check linking requirements. + * This query is only available using the current process pseudo handle on + * older windows versions. The cut-off seems to be Vista. + */ + SECTION_IMAGE_INFORMATION ImageInfo; + rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessImageInformation, &ImageInfo, sizeof(ImageInfo), NULL); + if (!NT_SUCCESS(rcNt)) + { + if ( rcNt == STATUS_INVALID_PARAMETER + && g_uNtVerCombined < SUP_NT_VER_VISTA + && pThis->hProcess != NtCurrentProcess() ) + return VINF_SUCCESS; + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NT_QI_PROCESS_IMG_INFO_ERROR, + "NtQueryInformationProcess/ProcessImageInformation failed: %#x hProcess=%#x", + rcNt, pThis->hProcess); + } + if ( !(ImageInfo.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)) + return supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, 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 supHardNtVpSetInfo2(pThis, VERR_SUP_VP_DLL_CHARECTERISTICS_MISMATCH, + "EXE Info.DllCharacteristics=%#x fDllCharecteristics=%#x.", + ImageInfo.DllCharacteristics, pImage->fDllCharecteristics); + + if (pImage->fImageCharecteristics != ImageInfo.ImageCharacteristics) + return supHardNtVpSetInfo2(pThis, 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. The hProcess + * member holds the handle to the process that is + * to be verified. + */ +static int supHardNtVpCheckDlls(PSUPHNTVPSTATE pThis) +{ + /* + * Check for duplicate entries (paranoia). + */ + 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 supHardNtVpSetInfo2(pThis, 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; + 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; + if (iNtDll == UINT32_MAX) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_NTDLL_MAPPING, + "The process has no NTDLL.DLL."); + if (iKernel32 == UINT32_MAX && ( pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION + || pThis->enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED)) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_KERNEL32_MAPPING, + "The process has no KERNEL32.DLL."); + else if (iKernel32 != UINT32_MAX && pThis->enmKind == SUPHARDNTVPKIND_CHILD_PURIFICATION) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_KERNEL32_ALREADY_MAPPED, + "The process already has KERNEL32.DLL loaded."); + + /* + * Verify that the DLLs are correctly signed (by MS). + */ + i = pThis->cImages; + while (i-- > 0) + { + int rc = supHardNtVpVerifyImage(pThis, &pThis->aImages[i]); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + + +#ifdef IN_RING3 +/** + * Verifies that we don't have any inheritable handles around, other than a few + * ones for file and event objects. + * + * When finding an inheritable handle of a different type, it will change it to + * non-inhertiable. This must NOT be called in the final process prior to + * opening the device! + * + * @returns VBox status code + * @param pThis The process scanning state structure. + */ +static int supHardNtVpCheckHandles(PSUPHNTVPSTATE pThis) +{ + SUP_DPRINTF(("supHardNtVpCheckHandles:\n")); + + /* + * Take a snapshot of all the handles in the system. + * (Because the current process handle snapshot was added in Windows 8, + * so we cannot use that yet.) + */ + 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 <= _32M) + { + cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K); + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf); + rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + } + if (!NT_SUCCESS(rcNt)) + { + RTMemFree(pbBuf); + return supHardNtVpSetInfo2(pThis, VERR_SUP_VP_NO_MEMORY, "Failed to allocate %zu bytes querying handles.", cbBuf); + } + } + + /* + * Examine the snapshot for handles for this process. + */ + int rcRet = VINF_SUCCESS; + HANDLE const idProcess = RTNtCurrentTeb()->ClientId.UniqueProcess; + SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; + ULONG_PTR i = pInfo->NumberOfHandles; + AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded); + while (i-- > 0) + { + SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; + if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) + && pHandleInfo->UniqueProcessId == idProcess) + { + ULONG cbNeeded2 = 0; + rcNt = NtQueryObject(pHandleInfo->HandleValue, ObjectTypeInformation, + pThis->abMemory, sizeof(pThis->abMemory), &cbNeeded2); + if (NT_SUCCESS(rcNt)) + { + POBJECT_TYPE_INFORMATION pTypeInfo = (POBJECT_TYPE_INFORMATION)pThis->abMemory; + if ( pTypeInfo->TypeName.Length == sizeof(L"File") - sizeof(wchar_t) + && memcmp(pTypeInfo->TypeName.Buffer, L"File", sizeof(L"File") - sizeof(wchar_t)) == 0) + SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable file handle: %p\n", pHandleInfo->HandleValue)); + else if ( pTypeInfo->TypeName.Length == sizeof(L"Event") - sizeof(wchar_t) + && memcmp(pTypeInfo->TypeName.Buffer, L"Event", sizeof(L"Event") - sizeof(wchar_t)) == 0) + SUP_DPRINTF(("supHardNtVpCheckHandles: Inheritable event handle: %p\n", pHandleInfo->HandleValue)); + else + { + OBJECT_HANDLE_FLAG_INFORMATION SetInfo; + SetInfo.Inherit = FALSE; + SetInfo.ProtectFromClose = FALSE; + rcNt = NtSetInformationObject(pHandleInfo->HandleValue, ObjectHandleFlagInformation, + &SetInfo, sizeof(SetInfo)); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supHardNtVpCheckHandles: Marked %ls handle non-inheritable: %p\n", + pTypeInfo->TypeName.Buffer, pHandleInfo->HandleValue)); + pThis->cFixes++; + } + else + { + rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_SET_HANDLE_NOINHERIT, + "NtSetInformationObject(%p,,,) -> %#x", pHandleInfo->HandleValue, rcNt); + break; + } + } + } + else + { + rcRet = supHardNtVpSetInfo2(pThis, VERR_SUP_VP_QUERY_HANDLE_TYPE, + "NtQueryObject(%p,,,,) -> %#x", pHandleInfo->HandleValue, rcNt); + break; + } + + } + } + RTMemFree(pbBuf); + return rcRet; +} +#endif /* IN_RING3 */ + + +/** + * 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 enmKind The kind of process verification to perform. + * @param fFlags Valid combination of SUPHARDNTVP_F_XXX flags. + * @param pErrInfo Pointer to error info structure. Optional. + * @param pcFixes Where to return the number of fixes made during + * purification. Optional. + */ +DECLHIDDEN(int) supHardenedWinVerifyProcess(HANDLE hProcess, HANDLE hThread, SUPHARDNTVPKIND enmKind, uint32_t fFlags, + uint32_t *pcFixes, PRTERRINFO pErrInfo) +{ + if (pcFixes) + *pcFixes = 0; + + /* + * Some basic checks regarding threads and debuggers. We don't need + * allocate any state memory for these. + */ + int rc = VINF_SUCCESS; + if ( enmKind != SUPHARDNTVPKIND_CHILD_PURIFICATION + && enmKind != SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) + rc = supHardNtVpThread(hProcess, hThread, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpDebugger(hProcess, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize memory for the state. + */ + PSUPHNTVPSTATE pThis = (PSUPHNTVPSTATE)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->enmKind = enmKind; + pThis->fFlags = fFlags; + pThis->rcResult = VINF_SUCCESS; + pThis->hProcess = hProcess; + pThis->pErrInfo = pErrInfo; + + /* + * Perform the verification. + */ + rc = supHardNtVpScanVirtualMemory(pThis, hProcess); + if (RT_SUCCESS(rc)) + rc = supHardNtVpOpenImages(pThis); + if (RT_SUCCESS(rc)) + rc = supHardNtVpCheckExe(pThis); + if (RT_SUCCESS(rc)) + rc = supHardNtVpCheckDlls(pThis); +#ifdef IN_RING3 + if (enmKind == SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED) + rc = supHardNtVpCheckHandles(pThis); +#endif + + if (pcFixes) + *pcFixes = pThis->cFixes; + + /* + * Clean up the state. + */ +#ifdef IN_RING0 + for (uint32_t i = 0; i < pThis->cImages; i++) + supHardNTLdrCacheDeleteEntry(&pThis->aImages[i].CacheEntry); +#endif + RTMemFree(pThis); + } + else + rc = supHardNtVpSetInfo1(pErrInfo, VERR_SUP_VP_NO_MEMORY_STATE, + "Failed to allocate %zu bytes for state structures.", sizeof(*pThis)); + } + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp new file mode 100644 index 00000000..dfa590a9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPLib-win.cpp @@ -0,0 +1,1002 @@ +/* $Id: SUPLib-win.cpp $ */ +/** @file + * VirtualBox Support Library - Windows NT specific parts. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#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> +# undef LogRelIt +# define LogRelIt(pvInst, fFlags, iGroup, fmtargs) do { } while (0) +#endif + +#define USE_NT_DEVICE_IO_CONTROL_FILE +#include <iprt/nt/nt-and-windows.h> + +#include <VBox/sup.h> +#include <VBox/types.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#ifndef IN_SUP_HARDENED_R3 +# include <iprt/env.h> +# include <iprt/x86.h> +# include <iprt/ldr.h> +#endif +#include <iprt/path.h> +#include <iprt/string.h> +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" +#ifdef VBOX_WITH_HARDENING +# include "win/SUPHardenedVerify-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxSup" + + +/********************************************************************************************************************************* +* 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 +static int suplibConvertWin32Err(int); +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static bool g_fHardenedVerifyInited = false; + + +DECLHIDDEN(int) suplibOsHardenedVerifyInit(void) +{ + if (!g_fHardenedVerifyInited) + { +#if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_HARDENED_R3) && !defined(IN_SUP_R3_STATIC) + supR3HardenedWinInitVersion(false /*fEarly*/); + int rc = supHardenedWinInitImageVerifier(NULL); + if (RT_FAILURE(rc)) + return rc; + supR3HardenedWinResolveVerifyTrustApiAndHookThreadCreation(NULL); +#endif + g_fHardenedVerifyInited = true; + } + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsHardenedVerifyTerm(void) +{ + /** @todo free resources... */ + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) suplibOsInit(PSUPLIBDATA pThis, bool fPreInited, uint32_t fFlags, SUPINITOP *penmWhat, PRTERRINFO pErrInfo) +{ + /* + * Make sure the image verifier is fully initialized. + */ + int rc = suplibOsHardenedVerifyInit(); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "suplibOsHardenedVerifyInit failed: %Rrc", rc); + + /* + * Done if of pre-inited. + */ + if (fPreInited) + { +#if defined(VBOX_WITH_HARDENING) && !defined(IN_SUP_HARDENED_R3) +# ifdef IN_SUP_R3_STATIC + return VERR_NOT_SUPPORTED; +# else + return VINF_SUCCESS; +# endif +#else + return VINF_SUCCESS; +#endif + } + +#if !defined(IN_SUP_HARDENED_R3) + /* + * Driverless? + */ + if (fFlags & SUPR3INIT_F_DRIVERLESS) + { + pThis->fDriverless = true; + return VINF_SUCCESS; + } +#endif + + /* + * Try open the device. + */ +#ifndef 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) * (fFlags & SUPR3INIT_F_UNRESTRICTED ? 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, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT! */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* + * We're good. + */ + pThis->hDevice = hDevice; + pThis->fUnrestricted = RT_BOOL(fFlags & SUPR3INIT_F_UNRESTRICTED); + return VINF_SUCCESS; + } + +#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 + + /* + * Translate the error code. + */ + switch (rcNt) + { + /** @todo someone must test what is actually returned. */ + case STATUS_DEVICE_DOES_NOT_EXIST: + case STATUS_DEVICE_NOT_CONNECTED: + //case ERROR_BAD_DEVICE: + case STATUS_DEVICE_REMOVED: + //case ERROR_DEVICE_NOT_AVAILABLE: + rc = VERR_VM_DRIVER_LOAD_ERROR; + break; + case STATUS_OBJECT_PATH_NOT_FOUND: + case STATUS_NO_SUCH_DEVICE: + case STATUS_NO_SUCH_FILE: + case STATUS_OBJECT_NAME_NOT_FOUND: + rc = VERR_VM_DRIVER_NOT_INSTALLED; + break; + case STATUS_ACCESS_DENIED: + case STATUS_SHARING_VIOLATION: + rc = VERR_VM_DRIVER_NOT_ACCESSIBLE; + break; + case STATUS_UNSUCCESSFUL: + rc = VERR_SUPLIB_NT_PROCESS_UNTRUSTED_0; + break; + case STATUS_TRUST_FAILURE: + rc = VERR_SUPLIB_NT_PROCESS_UNTRUSTED_1; + break; + case STATUS_TOO_LATE: + rc = VERR_SUPDRV_HARDENING_EVIL_HANDLE; + break; + default: + if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */ + rc = SUP_NT_STATUS_TO_VBOX(rcNt); + else + rc = VERR_VM_DRIVER_OPEN_ERROR; + break; + } + +#ifdef IN_SUP_HARDENED_R3 + /* + * Get more details from VBoxDrvErrorInfo if present. + */ + if (pErrInfo && pErrInfo->cbMsg > 32) + { + /* Prefix. */ + size_t cchPrefix; + if (RTErrIsKnown(rc)) + cchPrefix = RTStrPrintf(pErrInfo->pszMsg, pErrInfo->cbMsg / 2, "Integrity error (%#x/%Rrc): ", rcNt, rc); + else + cchPrefix = RTStrPrintf(pErrInfo->pszMsg, pErrInfo->cbMsg / 2, "Integrity error (%#x/%d): ", rcNt, rc); + + /* Get error info. */ + supR3HardenedWinReadErrorInfoDevice(pErrInfo->pszMsg + cchPrefix, pErrInfo->cbMsg - cchPrefix, ""); + if (pErrInfo->pszMsg[cchPrefix] != '\0') + { + pErrInfo->fFlags |= RTERRINFO_FLAGS_SET; + pErrInfo->rc = rc; + *penmWhat = kSupInitOp_Integrity; + } + else + pErrInfo->pszMsg[0] = '\0'; + } + +#else + RT_NOREF1(penmWhat); + + /* + * Do fallback. + */ + if ( (fFlags & SUPR3INIT_F_DRIVERLESS_MASK) + && rcNt != -1 /** @todo */ ) + { + LogRel(("Failed to open '%.*ls' rc=%Rrc rcNt=%#x - Switching to driverless mode.\n", + NtName.Length / sizeof(WCHAR), NtName.Buffer, rc, rcNt)); + pThis->fDriverless = true; + return VINF_SUCCESS; + } +#endif + return rc; + } +} + + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsInstall(void) +{ + int rc = suplibOsCreateService(); + if (RT_SUCCESS(rc)) + { + int rc2 = suplibOsStartService(); + if (rc2 != VINF_SUCCESS) + rc = rc2; + } + return rc; +} + + +DECLHIDDEN(int) suplibOsUninstall(void) +{ + int rc = suplibOsStopService(); + if (RT_SUCCESS(rc)) + rc = suplibOsDeleteService(); + return rc; +} + + +/** + * Creates the service. + * + * @returns VBox status code. + * @retval VWRN_ALREADY_EXISTS if it already exists. + */ +static int suplibOsCreateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc; + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed dwErr=%d\n", dwErr)); + if (hSMgrCreate != NULL) + { + char szDriver[RTPATH_MAX]; + rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxSup.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxSup.sys"); + SC_HANDLE hService = CreateService(hSMgrCreate, + SERVICE_NAME, + "VBox Support Driver", + SERVICE_QUERY_STATUS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL); + dwErr = GetLastError(); + if (hService) + { + CloseServiceHandle(hService); + rc = VINF_SUCCESS; + } + else if (dwErr == ERROR_SERVICE_EXISTS) + rc = VWRN_ALREADY_EXISTS; + else + { + AssertMsgFailed(("CreateService failed! dwErr=%Rwa szDriver=%s\n", dwErr, szDriver)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgrCreate); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + return rc; +} + + +/** + * Stops a possibly running service. + * + * @returns VBox status code. + */ +static int suplibOsStopService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed dwErr=%d\n", dwErr)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (hService) + { + /* + * Stop the service. + */ + SERVICE_STATUS Status; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = VINF_SUCCESS; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + int iWait = 100; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = VINF_SUCCESS; + else + { + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + rc = VERR_GENERAL_FAILURE; + } + } + else + { + dwErr = GetLastError(); + if ( Status.dwCurrentState == SERVICE_STOP_PENDING + && dwErr == ERROR_SERVICE_CANNOT_ACCEPT_CTRL) + rc = VERR_RESOURCE_BUSY; /* better than VERR_GENERAL_FAILURE */ + else + { + AssertMsgFailed(("ControlService failed with dwErr=%Rwa. status=%d\n", dwErr, Status.dwCurrentState)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = VINF_SUCCESS; + else + { + AssertMsgFailed(("OpenService failed dwErr=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgr); + } + else + rc = RTErrConvertFromWin32(dwErr); + return rc; +} + + +/** + * Deletes the service. + * + * @returns VBox status code. + */ +int suplibOsDeleteService(void) +{ + int rcRet = VINF_SUCCESS; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwErr = GetLastError(); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", dwErr)); + if (hSMgr) + { + /* + * Old service name. + */ + SC_HANDLE hService = OpenService(hSMgr, "VBoxDrv", DELETE); + if (hService) + { + if (!DeleteService(hService)) + { + dwErr = GetLastError(); + AssertMsgFailed(("DeleteService failed for VBoxDrv dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (dwErr != ERROR_SERVICE_DOES_NOT_EXIST) + { + AssertMsgFailed(("OpenService failed for VBoxDrv dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + } + + /* + * The new service. + */ + hService = OpenService(hSMgr, SERVICE_NAME, DELETE); + if (hService) + { + if (!DeleteService(hService)) + { + dwErr = GetLastError(); + AssertMsgFailed(("DeleteService for " SERVICE_NAME " failed dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + CloseServiceHandle(hService); + } + else + { + dwErr = GetLastError(); + if (dwErr != ERROR_SERVICE_DOES_NOT_EXIST) + { + AssertMsgFailed(("OpenService failed for " SERVICE_NAME " dwErr=%Rwa\n", dwErr)); + rcRet = RTErrConvertFromWin32(dwErr); + } + } + CloseServiceHandle(hSMgr); + } + else + rcRet = RTErrConvertFromWin32(dwErr); + return rcRet; +} + +#if 0 +/** + * Creates the service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +static int suplibOsUpdateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed LastError=%Rwa\n", LastError)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_CHANGE_CONFIG); + if (hService) + { + char szDriver[RTPATH_MAX]; + int rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxSup.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxSup.sys"); + + SC_LOCK hLock = LockServiceDatabase(hSMgr); + if (ChangeServiceConfig(hService, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL, NULL)) + { + + UnlockServiceDatabase(hLock); + CloseServiceHandle(hService); + CloseServiceHandle(hSMgr); + return 0; + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("ChangeServiceConfig failed LastError=%Rwa\n", LastError)); + } + } + UnlockServiceDatabase(hLock); + CloseServiceHandle(hService); + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hSMgr); + } + return -1; +} +#endif + + +/** + * Attempts to start the service, creating it if necessary. + * + * @returns VBox status code. + */ +static int suplibOsStartService(void) +{ + /* + * Check if the driver service is there. + */ + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_START); + if (hSMgr == NULL) + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("couldn't open service manager in SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS mode! (dwErr=%d)\n", dwErr)); + return RTErrConvertFromWin32(dwErr); + } + + /* + * Try open our service to check it's status. + */ + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_QUERY_STATUS | SERVICE_START); + if (!hService) + { + /* + * Create the service. + */ + int rc = suplibOsCreateService(); + if (RT_FAILURE(rc)) + return rc; + + /* + * Try open the service. + */ + hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_QUERY_STATUS | SERVICE_START); + } + + /* + * Check if open and on demand create succeeded. + */ + int rc; + if (hService) + { + + /* + * Query service status to see if we need to start it or not. + */ + SERVICE_STATUS Status; + BOOL fRc = QueryServiceStatus(hService, &Status); + Assert(fRc); NOREF(fRc); + if (Status.dwCurrentState == SERVICE_RUNNING) + rc = VINF_ALREADY_INITIALIZED; + else + { + if (Status.dwCurrentState == SERVICE_START_PENDING) + rc = VINF_SUCCESS; + else + { + /* + * Start it. + */ + if (StartService(hService, 0, NULL)) + rc = VINF_SUCCESS; + else + { + DWORD dwErr = GetLastError(); + AssertMsg(fRc, ("StartService failed with dwErr=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + } + + /* + * Wait for the service to finish starting. + * We'll wait for 10 seconds then we'll give up. + */ + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_START_PENDING) + { + int iWait; + for (iWait = 100; iWait > 0 && Status.dwCurrentState == SERVICE_START_PENDING; iWait--) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(Status.dwCurrentState != SERVICE_RUNNING, + ("Failed to start. dwErr=%Rwa iWait=%d status=%d\n", dwErr, iWait, Status.dwCurrentState)); + } + + if (Status.dwCurrentState == SERVICE_RUNNING) + rc = VINF_SUCCESS; + else if (RT_SUCCESS_NP(rc)) + rc = VERR_GENERAL_FAILURE; + } + + /* + * Close open handles. + */ + CloseServiceHandle(hService); + } + else + { + DWORD dwErr = GetLastError(); + AssertMsgFailed(("OpenService failed! LastError=%Rwa\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + if (!CloseServiceHandle(hSMgr)) + AssertFailed(); + + return rc; +} + +#endif /* !IN_SUP_HARDENED_R3 */ + +DECLHIDDEN(int) suplibOsTerm(PSUPLIBDATA pThis) +{ + /* + * Check if we're inited at all. + */ + if (pThis->hDevice != NULL) + { + NTSTATUS rcNt = NtClose((HANDLE)pThis->hDevice); + Assert(NT_SUCCESS(rcNt)); RT_NOREF(rcNt); + pThis->hDevice = NIL_RTFILE; /* yes, that's right */ + } + + return VINF_SUCCESS; +} + +#ifndef IN_SUP_HARDENED_R3 + +DECLHIDDEN(int) suplibOsIOCtl(PSUPLIBDATA pThis, uintptr_t uFunction, void *pvReq, size_t cbReq) +{ + RT_NOREF1(cbReq); + + /* + * Issue the device I/O control. + */ + PSUPREQHDR pHdr = (PSUPREQHDR)pvReq; + Assert(cbReq == RT_MAX(pHdr->cbIn, pHdr->cbOut)); +# ifdef USE_NT_DEVICE_IO_CONTROL_FILE + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtDeviceIoControlFile((HANDLE)pThis->hDevice, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios, + (ULONG)uFunction, + pvReq /*pvInput */, pHdr->cbIn /* cbInput */, + pvReq /*pvOutput*/, pHdr->cbOut /* cbOutput */); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + rcNt = Ios.Status; + } + return suplibConvertNtStatus(rcNt); + +# else + DWORD cbReturned = (ULONG)pHdr->cbOut; + if (DeviceIoControl((HANDLE)pThis->hDevice, uFunction, pvReq, pHdr->cbIn, pvReq, cbReturned, &cbReturned, NULL)) + return 0; + return suplibConvertWin32Err(GetLastError()); +# endif +} + + +DECLHIDDEN(int) suplibOsIOCtlFast(PSUPLIBDATA pThis, uintptr_t uFunction, uintptr_t idCpu) +{ + /* + * Issue device I/O control. + */ +# ifdef USE_NT_DEVICE_IO_CONTROL_FILE + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + NTSTATUS rcNt = NtDeviceIoControlFile((HANDLE)pThis->hDevice, NULL /*hEvent*/, NULL /*pfnApc*/, NULL /*pvApcCtx*/, &Ios, + (ULONG)uFunction, + NULL /*pvInput */, 0 /* cbInput */, + (PVOID)idCpu /*pvOutput*/, 0 /* cbOutput */); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + rcNt = Ios.Status; + } + return suplibConvertNtStatus(rcNt); +# else + DWORD cbReturned = 0; + if (DeviceIoControl((HANDLE)pThis->hDevice, uFunction, NULL, 0, (LPVOID)idCpu, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + return suplibConvertWin32Err(GetLastError()); +# endif +} + + +DECLHIDDEN(int) suplibOsPageAlloc(PSUPLIBDATA pThis, size_t cPages, uint32_t fFlags, void **ppvPages) +{ + RT_NOREF(pThis, fFlags); + + /* + * Do some one-time init here wrt large pages. + * + * Large pages requires the SeLockMemoryPrivilege, which by default (Win10, + * Win11) isn't even enabled and must be gpedit'ed to be adjustable here. + */ + if (!(cPages & 511) && (fFlags & SUP_PAGE_ALLOC_F_LARGE_PAGES)) + { + static int volatile s_fCanDoLargePages = -1; + int fCanDoLargePages = s_fCanDoLargePages; + if (s_fCanDoLargePages != -1) + { /* likely */ } + else if (RTEnvExistsUtf8("VBOX_DO_NOT_USE_LARGE_PAGES")) /** @todo add flag for this instead? */ + s_fCanDoLargePages = fCanDoLargePages = 0; + else + { + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) + { + union + { + TOKEN_PRIVILEGES s; + uint8_t abPadding[RT_UOFFSETOF(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES)]; + } Privileges; + RT_ZERO(Privileges); + Privileges.s.PrivilegeCount = 1; + Privileges.s.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (LookupPrivilegeValueW(NULL, L"SeLockMemoryPrivilege", &Privileges.s.Privileges[0].Luid)) + AdjustTokenPrivileges(hToken, FALSE, &Privileges.s, 0, NULL, NULL); + else + AssertFailed(); + CloseHandle(hToken); + } + else + AssertFailed(); + s_fCanDoLargePages = fCanDoLargePages = -2; + } + + /* + * Try allocate with large pages. + */ + if (fCanDoLargePages != 0) + { + *ppvPages = VirtualAlloc(NULL, (size_t)cPages << PAGE_SHIFT, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_EXECUTE_READWRITE); + if (*ppvPages) + { + if (fCanDoLargePages == -2) + { + s_fCanDoLargePages = 1; + LogRel(("SUPLib: MEM_LARGE_PAGES works!\n")); + } + LogRel2(("SUPLib: MEM_LARGE_PAGES for %p LB %p\n", *ppvPages, (size_t)cPages << PAGE_SHIFT)); + return VINF_SUCCESS; + } + + /* This can happen if the above AdjustTokenPrivileges failed (non-admin + user), or if the privilege isn't present in the token (need gpedit). */ + if (GetLastError() == ERROR_PRIVILEGE_NOT_HELD) + { + LogRel(("SUPLib: MEM_LARGE_PAGES privilege not held.\n")); + s_fCanDoLargePages = 0; + } + else + LogRel2(("SUPLib: MEM_LARGE_PAGES allocation failed with odd status: %u\n", GetLastError())); + } + } + + /* + * Do a regular allocation w/o large pages. + */ + *ppvPages = VirtualAlloc(NULL, (size_t)cPages << PAGE_SHIFT, MEM_COMMIT, PAGE_EXECUTE_READWRITE); + if (*ppvPages) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLHIDDEN(int) suplibOsPageFree(PSUPLIBDATA pThis, void *pvPages, size_t /* cPages */) +{ + NOREF(pThis); + if (VirtualFree(pvPages, 0, MEM_RELEASE)) + return VINF_SUCCESS; + return RTErrConvertFromWin32(GetLastError()); +} + + +DECLHIDDEN(bool) suplibOsIsNemSupportedWhenNoVtxOrAmdV(void) +{ +# if ARCH_BITS == 64 + /* + * Check that we're in a VM. + */ + if (!ASMHasCpuId()) + return false; + if (!RTX86IsValidStdRange(ASMCpuId_EAX(0))) + return false; + if (!(ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_HVP)) + return false; + + /* + * Try load WinHvPlatform and resolve API for checking. + * Note! The two size_t arguments and the ssize_t one are all too big, but who cares. + */ + RTLDRMOD hLdrMod = NIL_RTLDRMOD; + int rc = RTLdrLoadSystem("WinHvPlatform.dll", false, &hLdrMod); + if (RT_FAILURE(rc)) + return false; + + bool fRet = false; + typedef HRESULT (WINAPI *PFNWHVGETCAPABILITY)(ssize_t, void *, size_t, size_t *); + PFNWHVGETCAPABILITY pfnWHvGetCapability = (PFNWHVGETCAPABILITY)RTLdrGetFunction(hLdrMod, "WHvGetCapability"); + if (pfnWHvGetCapability) + { + /* + * Query the API. + */ + union + { + BOOL fHypervisorPresent; + uint64_t u64Padding; + } Caps; + RT_ZERO(Caps); + size_t cbRetIgn = 0; + HRESULT hrc = pfnWHvGetCapability(0 /*WHvCapabilityCodeHypervisorPresent*/, &Caps, sizeof(Caps), &cbRetIgn); + if (SUCCEEDED(hrc) && Caps.fHypervisorPresent) + fRet = true; + } + + RTLdrClose(hLdrMod); + return fRet; +# else + return false; +#endif +} + + +# ifndef USE_NT_DEVICE_IO_CONTROL_FILE +/** + * Converts a supdrv win32 error code to an IPRT status code. + * + * @returns corresponding IPRT error code. + * @param rc Win32 error code. + */ +static int suplibConvertWin32Err(int rc) +{ + /* Conversion program (link with ntdll.lib from ddk): + #define _WIN32_WINNT 0x0501 + #include <iprt/win/windows.h> + #include <ntstatus.h> + #include <winternl.h> + #include <stdio.h> + + int main() + { + #define CONVERT(a) printf(#a " %#x -> %d\n", a, RtlNtStatusToDosError((a))) + CONVERT(STATUS_SUCCESS); + CONVERT(STATUS_NOT_SUPPORTED); + CONVERT(STATUS_INVALID_PARAMETER); + CONVERT(STATUS_UNKNOWN_REVISION); + CONVERT(STATUS_INVALID_HANDLE); + CONVERT(STATUS_INVALID_ADDRESS); + CONVERT(STATUS_NOT_LOCKED); + CONVERT(STATUS_IMAGE_ALREADY_LOADED); + CONVERT(STATUS_ACCESS_DENIED); + CONVERT(STATUS_REVISION_MISMATCH); + + return 0; + } + */ + + switch (rc) + { + //case 0: return STATUS_SUCCESS; + case 0: return VINF_SUCCESS; + case ERROR_NOT_SUPPORTED: return VERR_GENERAL_FAILURE; + case ERROR_INVALID_PARAMETER: return VERR_INVALID_PARAMETER; + case ERROR_UNKNOWN_REVISION: return VERR_INVALID_MAGIC; + case ERROR_INVALID_HANDLE: return VERR_INVALID_HANDLE; + case ERROR_UNEXP_NET_ERR: return VERR_INVALID_POINTER; + case ERROR_NOT_LOCKED: return VERR_LOCK_FAILED; + case ERROR_SERVICE_ALREADY_RUNNING: return VERR_ALREADY_LOADED; + case ERROR_ACCESS_DENIED: return VERR_PERMISSION_DENIED; + case ERROR_REVISION_MISMATCH: return VERR_VERSION_MISMATCH; + } + + /* fall back on the default conversion. */ + return RTErrConvertFromWin32(rc); +} +# else +/** + * Reverse of VBoxDrvNtErr2NtStatus + * returns VBox status code. + * @param rcNt NT status code. + */ +static int suplibConvertNtStatus(NTSTATUS rcNt) +{ + switch (rcNt) + { + case STATUS_SUCCESS: return VINF_SUCCESS; + case STATUS_NOT_SUPPORTED: return VERR_GENERAL_FAILURE; + case STATUS_INVALID_PARAMETER: return VERR_INVALID_PARAMETER; + case STATUS_UNKNOWN_REVISION: return VERR_INVALID_MAGIC; + case STATUS_INVALID_HANDLE: return VERR_INVALID_HANDLE; + case STATUS_INVALID_ADDRESS: return VERR_INVALID_POINTER; + case STATUS_NOT_LOCKED: return VERR_LOCK_FAILED; + case STATUS_IMAGE_ALREADY_LOADED: return VERR_ALREADY_LOADED; + case STATUS_ACCESS_DENIED: return VERR_PERMISSION_DENIED; + case STATUS_REVISION_MISMATCH: return VERR_VERSION_MISMATCH; + } + + /* See VBoxDrvNtErr2NtStatus. */ + if (SUP_NT_STATUS_IS_VBOX(rcNt)) + return SUP_NT_STATUS_TO_VBOX(rcNt); + + /* Fall back on IPRT for the rest. */ + return RTErrConvertFromNtStatus(rcNt); +} +# endif + +#endif /* !IN_SUP_HARDENED_R3 */ + diff --git a/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c b/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c new file mode 100644 index 00000000..73fe259d --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c @@ -0,0 +1,167 @@ +/* $Id: SUPR0IdcClient-win.c $ */ +/** @file + * VirtualBox Support Driver - IDC Client Lib, Windows Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "../SUPR0IdcClientInternal.h" +#include <iprt/errcore.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxDrv" + + +/** + * Internal I/O Control call worker. + * + * @returns VBox status code. + * @param pDeviceObject The device object to call. + * @param pFileObject The file object for the connection. + * @param uReq The request. + * @param pReq The request packet. + */ +static int supR0IdcNtCallInternal(PDEVICE_OBJECT pDeviceObject, PFILE_OBJECT pFileObject, uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + int rc; + IO_STATUS_BLOCK IoStatusBlock; + KEVENT Event; + PIRP pIrp; + NTSTATUS rcNt; + + /* + * Build the request. + */ + KeInitializeEvent(&Event, NotificationEvent, FALSE); + pIrp = IoBuildDeviceIoControlRequest(uReq, /* IoControlCode */ + pDeviceObject, + pReq, /* InputBuffer */ + pReq->cb, /* InputBufferLength */ + pReq, /* OutputBuffer */ + pReq->cb, /* OutputBufferLength */ + TRUE, /* InternalDeviceIoControl (=> IRP_MJ_INTERNAL_DEVICE_CONTROL) */ + &Event, /* Event */ + &IoStatusBlock); /* IoStatusBlock */ + if (pIrp) + { + IoGetNextIrpStackLocation(pIrp)->FileObject = pFileObject; + + /* + * Call the driver, wait for an async request to complete (should never happen). + */ + rcNt = IoCallDriver(pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + { + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + rcNt = IoStatusBlock.Status; + } + if (NT_SUCCESS(rcNt)) + rc = pReq->rc; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +int VBOXCALL supR0IdcNativeOpen(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQCONNECT pReq) +{ + PDEVICE_OBJECT pDeviceObject = NULL; + PFILE_OBJECT pFileObject = NULL; + UNICODE_STRING wszDeviceName; + NTSTATUS rcNt; + int rc; + + /* + * Get the device object pointer. + */ + RtlInitUnicodeString(&wszDeviceName, DEVICE_NAME_NT); + rcNt = IoGetDeviceObjectPointer(&wszDeviceName, FILE_ALL_ACCESS, &pFileObject, &pDeviceObject); + if (NT_SUCCESS(rcNt)) + { + /* + * Make the connection call. + */ + rc = supR0IdcNtCallInternal(pDeviceObject, pFileObject, SUPDRV_IDC_REQ_CONNECT, &pReq->Hdr); + if (RT_SUCCESS(rc)) + { + pHandle->s.pDeviceObject = pDeviceObject; + pHandle->s.pFileObject = pFileObject; + return rc; + } + + /* only the file object. */ + ObDereferenceObject(pFileObject); + } + else + rc = RTErrConvertFromNtStatus(rcNt); + + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + return rc; +} + + +int VBOXCALL supR0IdcNativeClose(PSUPDRVIDCHANDLE pHandle, PSUPDRVIDCREQHDR pReq) +{ + PFILE_OBJECT pFileObject = pHandle->s.pFileObject; + int rc = supR0IdcNtCallInternal(pHandle->s.pDeviceObject, pFileObject, SUPDRV_IDC_REQ_DISCONNECT, pReq); + if (RT_SUCCESS(rc)) + { + pHandle->s.pDeviceObject = NULL; + pHandle->s.pFileObject = NULL; + ObDereferenceObject(pFileObject); + } + + return rc; +} + + +int VBOXCALL supR0IdcNativeCall(PSUPDRVIDCHANDLE pHandle, uint32_t uReq, PSUPDRVIDCREQHDR pReq) +{ + return supR0IdcNtCallInternal(pHandle->s.pDeviceObject, pHandle->s.pFileObject, uReq, pReq); +} + 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 00000000..a7dccfd9 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp @@ -0,0 +1,7031 @@ +/* $Id: SUPR3HardenedMain-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <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_DLL_LOAD_DIR UINT32_C(0x100) +# define LOAD_LIBRARY_SEARCH_APPLICATION_DIR UINT32_C(0x200) +# define LOAD_LIBRARY_SEARCH_USER_DIRS UINT32_C(0x400) +# define LOAD_LIBRARY_SEARCH_SYSTEM32 UINT32_C(0x800) +#endif + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <VBox/dis.h> +#include <iprt/ctype.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> +#include <iprt/zero.h> + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" +#include "../SUPDrvIOC.h" + +#ifndef IMAGE_SCN_TYPE_NOLOAD +# define IMAGE_SCN_TYPE_NOLOAD 0x00000002 +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The first argument of a respawed stub when respawned for the first time. + * 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_1_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-2ndchild" + +/** The first argument of a respawed stub when respawned for the second time. + * 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_2_ARG0 "60eaff78-4bdd-042d-2e72-669728efd737-suplib-3rdchild" + +/** Unconditional assertion. */ +#define SUPR3HARDENED_ASSERT(a_Expr) \ + do { \ + if (!(a_Expr)) \ + supR3HardenedFatal("%s: %s\n", __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\n", __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\n", __FUNCTION__, #a_Expr, RtlGetLastWin32Error()); \ + } 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; + /** Next entry in the WinVerifyTrust todo list. */ + struct VERIFIERCACHEENTRY * volatile pNextTodoWvt; + + /** 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; + /** Used for shutting up load and error messages after a while so they don't + * flood the log file and fill up the disk. */ + uint32_t volatile cHits; + /** The validation flags (for WinVerifyTrust retry). */ + uint32_t fFlags; + /** Whether IndexNumber is valid */ + bool fIndexNumberValid; + /** Whether verified by WinVerifyTrust. */ + bool volatile fWinVerifyTrust; + /** 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; + + +/** + * Name of an import DLL that we need to check out. + */ +typedef struct VERIFIERCACHEIMPORT +{ + /** Pointer to the next DLL in the list. */ + struct VERIFIERCACHEIMPORT * volatile pNext; + /** The length of pwszAltSearchDir if available. */ + uint32_t cwcAltSearchDir; + /** This points the directory containing the DLL needing it, this will be + * NULL for a System32 DLL. */ + PWCHAR pwszAltSearchDir; + /** The name of the import DLL (variable length). */ + char szName[1]; +} VERIFIERCACHEIMPORT; +/** Pointer to a import DLL that needs checking out. */ +typedef VERIFIERCACHEIMPORT *PVERIFIERCACHEIMPORT; + + +/** + * Child requests. + */ +typedef enum SUPR3WINCHILDREQ +{ + /** Perform child purification and close full access handles (must be zero). */ + kSupR3WinChildReq_PurifyChildAndCloseHandles = 0, + /** Close the events, we're good on our own from here on. */ + kSupR3WinChildReq_CloseEvents, + /** Reporting error. */ + kSupR3WinChildReq_Error, + /** End of valid requests. */ + kSupR3WinChildReq_End +} SUPR3WINCHILDREQ; + +/** + * Child process parameters. + */ +typedef struct SUPR3WINPROCPARAMS +{ + /** The event semaphore the child will be waiting on. */ + HANDLE hEvtChild; + /** The event semaphore the parent will be waiting on. */ + HANDLE hEvtParent; + + /** The address of the NTDLL. This is only valid during the very early + * initialization as we abuse for thread creation protection. */ + uintptr_t uNtDllAddr; + + /** The requested operation (set by the child). */ + SUPR3WINCHILDREQ enmRequest; + /** The last status. */ + int32_t rc; + /** The init operation the error relates to if message, kSupInitOp_Invalid if + * not message. */ + SUPINITOP enmWhat; + /** Where if message. */ + char szWhere[80]; + /** Error message / path name string space. */ + char szErrorMsg[16384+1024]; +} SUPR3WINPROCPARAMS; + + +/** + * Child process data structure for use during child process init setup and + * purification. + */ +typedef struct SUPR3HARDNTCHILD +{ + /** Process handle. */ + HANDLE hProcess; + /** Primary thread handle. */ + HANDLE hThread; + /** Handle to the parent process, if we're the middle (stub) process. */ + HANDLE hParent; + /** The event semaphore the child will be waiting on. */ + HANDLE hEvtChild; + /** The event semaphore the parent will be waiting on. */ + HANDLE hEvtParent; + /** The address of NTDLL in the child. */ + uintptr_t uNtDllAddr; + /** The address of NTDLL in this process. */ + uintptr_t uNtDllParentAddr; + /** Which respawn number this is (1 = stub, 2 = VM). */ + int iWhich; + /** The basic process info. */ + PROCESS_BASIC_INFORMATION BasicInfo; + /** The probable size of the PEB. */ + size_t cbPeb; + /** The pristine process environment block. */ + PEB Peb; + /** The child process parameters. */ + SUPR3WINPROCPARAMS ProcParams; +} SUPR3HARDNTCHILD; +/** Pointer to a child process data structure. */ +typedef SUPR3HARDNTCHILD *PSUPR3HARDNTCHILD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Process parameters. Specified by parent if VM process, see + * supR3HardenedVmProcessInit. */ +static SUPR3WINPROCPARAMS g_ProcParams = { NULL, NULL, 0, (SUPR3WINCHILDREQ)0, 0 }; +/** Set if supR3HardenedEarlyProcessInit was invoked. */ +bool g_fSupEarlyProcessInit = false; +/** Set if the stub device has been opened (stub process only). */ +bool g_fSupStubOpened = false; + +/** @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 NT path of the application binary directory. */ +SUPSYSROOTDIRBUF g_SupLibHardenedAppBinNtPath; +/** 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. */ +static uint32_t g_offSupLibHardenedExeNtName; +/** Set if we need to use the LOAD_LIBRARY_SEARCH_USER_DIRS option. */ +bool g_fSupLibHardenedDllSearchUserDirs = false; +/** @} */ + +/** @name Hook related variables. + * @{ */ +/** 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); +/** Pointer to the NtCreateSection function in NtDll (for patching purposes). */ +static uint8_t *g_pbNtCreateSection; +/** The patched NtCreateSection bytes (for restoring). */ +static uint8_t g_abNtCreateSectionPatch[16]; +/** Pointer to the bit of assembly code that will perform the original + * LdrLoadDll operation. */ +static NTSTATUS (NTAPI *g_pfnLdrLoadDllReal)(PWSTR, PULONG, PUNICODE_STRING, PHANDLE); +/** Pointer to the LdrLoadDll function in NtDll (for patching purposes). */ +static uint8_t *g_pbLdrLoadDll; +/** The patched LdrLoadDll bytes (for restoring). */ +static uint8_t g_abLdrLoadDllPatch[16]; + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +/** Pointer to the bit of assembly code that will perform the original + * KiUserExceptionDispatcher operation. */ +static VOID (NTAPI *g_pfnKiUserExceptionDispatcherReal)(void); +/** Pointer to the KiUserExceptionDispatcher function in NtDll (for patching purposes). */ +static uint8_t *g_pbKiUserExceptionDispatcher; +/** The patched KiUserExceptionDispatcher bytes (for restoring). */ +static uint8_t g_abKiUserExceptionDispatcherPatch[16]; +#endif + +/** Pointer to the bit of assembly code that will perform the original + * KiUserApcDispatcher operation. */ +static VOID (NTAPI *g_pfnKiUserApcDispatcherReal)(void); +/** Pointer to the KiUserApcDispatcher function in NtDll (for patching purposes). */ +static uint8_t *g_pbKiUserApcDispatcher; +/** The patched KiUserApcDispatcher bytes (for restoring). */ +static uint8_t g_abKiUserApcDispatcherPatch[16]; + +/** Pointer to the LdrInitializeThunk function in NtDll for + * supR3HardenedMonitor_KiUserApcDispatcher_C() to use for APC vetting. */ +static uintptr_t g_pfnLdrInitializeThunk; + +/** The hash table of verifier cache . */ +static PVERIFIERCACHEENTRY volatile g_apVerifierCache[128]; +/** Queue of cached images which needs WinVerifyTrust to check them. */ +static PVERIFIERCACHEENTRY volatile g_pVerifierCacheTodoWvt = NULL; +/** Queue of cached images which needs their imports checked. */ +static PVERIFIERCACHEIMPORT volatile g_pVerifierCacheTodoImports = NULL; + +/** The windows path to dir \\SystemRoot\\System32 directory (technically + * this whatever \\KnownDlls\\KnownDllPath points to). */ +SUPSYSROOTDIRBUF g_System32WinPath; +/** @} */ + +/** Positive if the DLL notification callback has been registered, counts + * registration attempts as negative. */ +static int g_cDllNotificationRegistered = 0; +/** The registration cookie of the DLL notification callback. */ +static PVOID g_pvDllNotificationCookie = NULL; + +/** Static error info structure used during init. */ +static RTERRINFOSTATIC g_ErrInfoStatic; + +/** In the assembly file. */ +extern "C" uint8_t g_abSupHardReadWriteExecPage[PAGE_SIZE]; + +/** Whether we've patched our own LdrInitializeThunk or not. We do this to + * disable thread creation. */ +static bool g_fSupInitThunkSelfPatched; +/** The backup of our own LdrInitializeThunk code, for enabling and disabling + * thread creation in this process. */ +static uint8_t g_abLdrInitThunkSelfBackup[16]; + +/** Mask of adversaries that we've detected (SUPHARDNT_ADVERSARY_XXX). */ +static uint32_t g_fSupAdversaries = 0; +/** @name SUPHARDNT_ADVERSARY_XXX - Adversaries + * @{ */ +/** Symantec endpoint protection or similar including SysPlant.sys. */ +#define SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT RT_BIT_32(0) +/** Symantec Norton 360. */ +#define SUPHARDNT_ADVERSARY_SYMANTEC_N360 RT_BIT_32(1) +/** Avast! */ +#define SUPHARDNT_ADVERSARY_AVAST RT_BIT_32(2) +/** TrendMicro OfficeScan and probably others. */ +#define SUPHARDNT_ADVERSARY_TRENDMICRO RT_BIT_32(3) +/** TrendMicro potentially buggy sakfile.sys. */ +#define SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE RT_BIT_32(4) +/** McAfee. */ +#define SUPHARDNT_ADVERSARY_MCAFEE RT_BIT_32(5) +/** Kaspersky or OEMs of it. */ +#define SUPHARDNT_ADVERSARY_KASPERSKY RT_BIT_32(6) +/** Malwarebytes Anti-Malware (MBAM). */ +#define SUPHARDNT_ADVERSARY_MBAM RT_BIT_32(7) +/** AVG Internet Security. */ +#define SUPHARDNT_ADVERSARY_AVG RT_BIT_32(8) +/** Panda Security. */ +#define SUPHARDNT_ADVERSARY_PANDA RT_BIT_32(9) +/** Microsoft Security Essentials. */ +#define SUPHARDNT_ADVERSARY_MSE RT_BIT_32(10) +/** Comodo. */ +#define SUPHARDNT_ADVERSARY_COMODO RT_BIT_32(11) +/** Check Point's Zone Alarm (may include Kaspersky). */ +#define SUPHARDNT_ADVERSARY_ZONE_ALARM RT_BIT_32(12) +/** Digital guardian, old problematic version. */ +#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD RT_BIT_32(13) +/** Digital guardian, new version. */ +#define SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW RT_BIT_32(14) +/** Cylance protect or something (from googling, no available sample copy). */ +#define SUPHARDNT_ADVERSARY_CYLANCE RT_BIT_32(15) +/** BeyondTrust / PowerBroker / something (googling, no available sample copy). */ +#define SUPHARDNT_ADVERSARY_BEYONDTRUST RT_BIT_32(16) +/** Avecto / Defendpoint / Privilege Guard (details from support guy, hoping to get sample copy). */ +#define SUPHARDNT_ADVERSARY_AVECTO RT_BIT_32(17) +/** Sophos Endpoint Defense. */ +#define SUPHARDNT_ADVERSARY_SOPHOS RT_BIT_32(18) +/** VMware horizon view agent. */ +#define SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT RT_BIT_32(19) +/** Unknown adversary detected while waiting on child. */ +#define SUPHARDNT_ADVERSARY_UNKNOWN RT_BIT_32(31) +/** @} */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static NTSTATUS supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect, + bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, + bool *pfQuiet) RT_NOTHROW_PROTO; +static void supR3HardenedWinRegisterDllNotificationCallback(void); +static void supR3HardenedWinReInstallHooks(bool fFirst) RT_NOTHROW_PROTO; +DECLASM(void) supR3HardenedEarlyProcessInitThunk(void); +DECLASM(void) supR3HardenedMonitor_KiUserApcDispatcher(void); +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +DECLASM(void) supR3HardenedMonitor_KiUserExceptionDispatcher(void); +#endif +extern "C" void __stdcall suplibHardenedWindowsMain(void); + + +#if 0 /* unused */ + +/** + * 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; +} + +#endif /* unused */ + + +/** + * Our version of GetTickCount. + * @returns Millisecond timestamp. + */ +static uint64_t supR3HardenedWinGetMilliTS(void) +{ + PKUSER_SHARED_DATA pUserSharedData = (PKUSER_SHARED_DATA)(uintptr_t)0x7ffe0000; + + /* use interrupt time */ + LARGE_INTEGER Time; + do + { + Time.HighPart = pUserSharedData->InterruptTime.High1Time; + Time.LowPart = pUserSharedData->InterruptTime.LowPart; + } while (pUserSharedData->InterruptTime.High2Time != Time.HighPart); + + return (uint64_t)Time.QuadPart / 10000; +} + + +/** + * Called when there is some /GS (or maybe /RTCsu) related stack problem. + * + * We don't want the CRT version living in gshandle.obj, as it uses a lot of + * kernel32 imports, we want to report this error ourselves. + */ +extern "C" __declspec(noreturn guard(nosspro) guard(nossepi)) +void __cdecl __report_rangecheckfailure(void) +{ + supR3HardenedFatal("__report_rangecheckfailure called from %p", ASMReturnAddress()); +} + + +/** + * Called when there is some /GS problem has been detected. + * + * We don't want the CRT version living in gshandle.obj, as it uses a lot of + * kernel32 imports, we want to report this error ourselves. + */ +extern "C" __declspec(noreturn guard(nosspro) guard(nossepi)) +#ifdef RT_ARCH_X86 +void __cdecl __report_gsfailure(void) +#else +void __report_gsfailure(uintptr_t uCookie) +#endif +{ +#ifdef RT_ARCH_X86 + supR3HardenedFatal("__report_gsfailure called from %p", ASMReturnAddress()); +#else + supR3HardenedFatal("__report_gsfailure called from %p, cookie=%p", ASMReturnAddress(), uCookie); +#endif +} + + +/** + * 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. + * @param fMainFlags The main flags (giving the location), if the DLL + * being loaded is loaded from the app bin + * directory and import other DLLs from there. Pass + * 0 (= SUPSECMAIN_FLAGS_LOC_APP_BIN) if not + * applicable. Ignored if @a fSystem32Only is set. + * + * This is only needed to load VBoxRT.dll when + * executing a testcase from the testcase/ subdir. + */ +DECLHIDDEN(void *) supR3HardenedWinLoadLibrary(const char *pszName, bool fSystem32Only, uint32_t fMainFlags) +{ + 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; + if (g_fSupLibHardenedDllSearchUserDirs) + fFlags |= LOAD_LIBRARY_SEARCH_USER_DIRS; + if ((fMainFlags & SUPSECMAIN_FLAGS_LOC_MASK) != SUPSECMAIN_FLAGS_LOC_APP_BIN) + fFlags |= LOAD_LIBRARY_SEARCH_DLL_LOAD_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) + && RtlGetLastWin32Error() == ERROR_INVALID_PARAMETER) + pvRet = (void *)LoadLibraryExW(wszPath, NULL /*hFile*/, 0); + + return pvRet; + } + supR3HardenedFatal("RTStrToUtf16Ex failed on '%s': %Rrc", pszName, rc); + /* not reached */ +} + + +/** + * 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) RT_NOTHROW_DEF +{ + 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 path string. + * + * @returns Hash value. + * @param pUniStr String to hash. + */ +static uint32_t supR3HardenedWinVerifyCacheHashPath(PCUNICODE_STRING pUniStr) RT_NOTHROW_DEF +{ + uint32_t uHash = 0; + unsigned cwcLeft = pUniStr->Length / sizeof(WCHAR); + PRTUTF16 pwc = pUniStr->Buffer; + + while (cwcLeft-- > 0) + { + RTUTF16 wc = *pwc++; + if (wc < 0x80) + wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\'; + uHash = wc + (uHash << 6) + (uHash << 16) - uHash; + } + return uHash; +} + + +/** + * Calculates the hash value for a directory + filename combo as if they were + * one single string. + * + * @returns Hash value. + * @param pawcDir The directory name. + * @param cwcDir The length of the directory name. RTSTR_MAX if + * not available. + * @param pszName The import name (UTF-8). + */ +static uint32_t supR3HardenedWinVerifyCacheHashDirAndFile(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) RT_NOTHROW_DEF +{ + uint32_t uHash = 0; + while (cwcDir-- > 0) + { + RTUTF16 wc = *pawcDir++; + if (wc < 0x80) + wc = wc != '/' ? RT_C_TO_LOWER(wc) : '\\'; + uHash = wc + (uHash << 6) + (uHash << 16) - uHash; + } + + unsigned char ch = '\\'; + uHash = ch + (uHash << 6) + (uHash << 16) - uHash; + + while ((ch = *pszName++) != '\0') + { + ch = RT_C_TO_LOWER(ch); + uHash = ch + (uHash << 6) + (uHash << 16) - uHash; + } + + return uHash; +} + + +/** + * Verify string cache compare function. + * + * @returns true if the strings match, false if not. + * @param pawcLeft The left hand string. + * @param pawcRight The right hand string. + * @param cwcToCompare The number of chars to compare. + */ +static bool supR3HardenedWinVerifyCacheIsMatch(PCRTUTF16 pawcLeft, PCRTUTF16 pawcRight, uint32_t cwcToCompare) RT_NOTHROW_DEF +{ + /* Try a quick memory compare first. */ + if (memcmp(pawcLeft, pawcRight, cwcToCompare * sizeof(RTUTF16)) == 0) + return true; + + /* Slow char by char compare. */ + while (cwcToCompare-- > 0) + { + RTUTF16 wcLeft = *pawcLeft++; + RTUTF16 wcRight = *pawcRight++; + if (wcLeft != wcRight) + { + wcLeft = wcLeft != '/' ? RT_C_TO_LOWER(wcLeft) : '\\'; + wcRight = wcRight != '/' ? RT_C_TO_LOWER(wcRight) : '\\'; + if (wcLeft != wcRight) + return false; + } + } + + return true; +} + + + +/** + * 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 fWinVerifyTrust Whether verified by WinVerifyTrust or not. + * @param fFlags The image verification flags. + */ +static void supR3HardenedWinVerifyCacheInsert(PCUNICODE_STRING pUniStr, HANDLE hFile, int rc, + bool fWinVerifyTrust, uint32_t fFlags) RT_NOTHROW_DEF +{ + /* + * Allocate and initalize a new entry. + */ + PVERIFIERCACHEENTRY pEntry = (PVERIFIERCACHEENTRY)RTMemAllocZ(sizeof(VERIFIERCACHEENTRY) + pUniStr->Length); + if (pEntry) + { + pEntry->pNext = NULL; + pEntry->pNextTodoWvt = NULL; + pEntry->hFile = hFile; + pEntry->uHash = supR3HardenedWinVerifyCacheHashPath(pUniStr); + pEntry->rc = rc; + pEntry->fFlags = fFlags; + pEntry->cHits = 0; + pEntry->fWinVerifyTrust = fWinVerifyTrust; + 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)) + { + if (!fWinVerifyTrust) + do + pEntry->pNextTodoWvt = g_pVerifierCacheTodoWvt; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pEntry, pEntry->pNextTodoWvt)); + + SUP_DPRINTF(("supR3HardenedWinVerifyCacheInsert: %ls\n", pUniStr->Buffer)); + return; + } + + PVERIFIERCACHEENTRY pOther = *ppEntry; + if (!pOther) + continue; + if ( pOther->uHash == pEntry->uHash + && pOther->cbPath == pEntry->cbPath + && supR3HardenedWinVerifyCacheIsMatch(pOther->wszPath, pEntry->wszPath, pEntry->cbPath / sizeof(RTUTF16))) + break; + ppEntry = &pOther->pNext; + } + + /* Duplicate entry (may happen due to races). */ + RTMemFree(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) RT_NOTHROW_DEF +{ + 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 + && supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pwszPath, cbPath / sizeof(RTUTF16))) + { + + 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; +} + + +/** + * Looks up an import DLL in the verifier hash table. + * + * @return Pointer to the entry on if found, NULL if not. + * @param pawcDir The directory name. + * @param cwcDir The length of the directory name. + * @param pszName The import name (UTF-8). + */ +static PVERIFIERCACHEENTRY supR3HardenedWinVerifyCacheLookupImport(PCRTUTF16 pawcDir, uint32_t cwcDir, const char *pszName) +{ + uint32_t uHash = supR3HardenedWinVerifyCacheHashDirAndFile(pawcDir, cwcDir, pszName); + uint32_t iHashTab = uHash % RT_ELEMENTS(g_apVerifierCache); + uint32_t const cbPath = (uint32_t)((cwcDir + 1 + strlen(pszName)) * sizeof(RTUTF16)); + PVERIFIERCACHEENTRY pCur = g_apVerifierCache[iHashTab]; + while (pCur) + { + if ( pCur->uHash == uHash + && pCur->cbPath == cbPath) + { + if (supR3HardenedWinVerifyCacheIsMatch(pCur->wszPath, pawcDir, cwcDir)) + { + if (pCur->wszPath[cwcDir] == '\\' || pCur->wszPath[cwcDir] == '/') + { + if (RTUtf16ICmpAscii(&pCur->wszPath[cwcDir + 1], pszName)) + { + return pCur; + } + } + } + } + + pCur = pCur->pNext; + } + return NULL; +} + + +/** + * Schedules the import DLLs for verification and entry into the cache. + * + * @param hLdrMod The loader module which imports should be + * scheduled for verification. + * @param pwszName The full NT path of the module. + */ +DECLHIDDEN(void) supR3HardenedWinVerifyCacheScheduleImports(RTLDRMOD hLdrMod, PCRTUTF16 pwszName) +{ + /* + * Any imports? + */ + uint32_t cImports; + int rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_COUNT, NULL /*pvBits*/, &cImports, sizeof(cImports), NULL); + if (RT_SUCCESS(rc)) + { + if (cImports) + { + /* + * Figure out the DLL directory from pwszName. + */ + PCRTUTF16 pawcDir = pwszName; + uint32_t cwcDir = 0; + uint32_t i = 0; + RTUTF16 wc; + while ((wc = pawcDir[i++]) != '\0') + if ((wc == '\\' || wc == '/' || wc == ':') && cwcDir + 2 != i) + cwcDir = i - 1; + if ( g_System32NtPath.UniStr.Length / sizeof(WCHAR) == cwcDir + && supR3HardenedWinVerifyCacheIsMatch(pawcDir, g_System32NtPath.UniStr.Buffer, cwcDir)) + pawcDir = NULL; + + /* + * Enumerate the imports. + */ + for (i = 0; i < cImports; i++) + { + union + { + char szName[256]; + uint32_t iImport; + } uBuf; + uBuf.iImport = i; + rc = RTLdrQueryPropEx(hLdrMod, RTLDRPROP_IMPORT_MODULE, NULL /*pvBits*/, &uBuf, sizeof(uBuf), NULL); + if (RT_SUCCESS(rc)) + { + /* + * Skip kernel32, ntdll and API set stuff. + */ + RTStrToLower(uBuf.szName); + if ( RTStrCmp(uBuf.szName, "kernel32.dll") == 0 + || RTStrCmp(uBuf.szName, "kernelbase.dll") == 0 + || RTStrCmp(uBuf.szName, "ntdll.dll") == 0 + || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("api-ms-win-")) == 0 + || RTStrNCmp(uBuf.szName, RT_STR_TUPLE("ext-ms-win-")) == 0 + ) + { + continue; + } + + /* + * Skip to the next one if it's already in the cache. + */ + if (supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length / sizeof(WCHAR), + uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for system32\n", uBuf.szName)); + continue; + } + if (supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, + g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(CHAR), + uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for appdir\n", uBuf.szName)); + continue; + } + if (pawcDir && supR3HardenedWinVerifyCacheLookupImport(pawcDir, cwcDir, uBuf.szName) != NULL) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: '%s' cached for dll dir\n", uBuf.szName)); + continue; + } + + /* We could skip already scheduled modules, but that'll require serialization and extra work... */ + + /* + * Add it to the todo list. + */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheScheduleImports: Import todo: #%u '%s'.\n", i, uBuf.szName)); + uint32_t cbName = (uint32_t)strlen(uBuf.szName) + 1; + uint32_t cbNameAligned = RT_ALIGN_32(cbName, sizeof(RTUTF16)); + uint32_t cbNeeded = RT_UOFFSETOF_DYN(VERIFIERCACHEIMPORT, szName[cbNameAligned]) + + (pawcDir ? (cwcDir + 1) * sizeof(RTUTF16) : 0); + PVERIFIERCACHEIMPORT pImport = (PVERIFIERCACHEIMPORT)RTMemAllocZ(cbNeeded); + if (pImport) + { + /* Init it. */ + memcpy(pImport->szName, uBuf.szName, cbName); + if (!pawcDir) + { + pImport->cwcAltSearchDir = 0; + pImport->pwszAltSearchDir = NULL; + } + else + { + pImport->cwcAltSearchDir = cwcDir; + pImport->pwszAltSearchDir = (PRTUTF16)&pImport->szName[cbNameAligned]; + memcpy(pImport->pwszAltSearchDir, pawcDir, cwcDir * sizeof(RTUTF16)); + pImport->pwszAltSearchDir[cwcDir] = '\0'; + } + + /* Insert it. */ + do + pImport->pNext = g_pVerifierCacheTodoImports; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoImports, pImport, pImport->pNext)); + } + } + else + SUP_DPRINTF(("RTLDRPROP_IMPORT_MODULE failed with rc=%Rrc i=%#x on '%ls'\n", rc, i, pwszName)); + } + } + else + SUP_DPRINTF(("'%ls' has no imports\n", pwszName)); + } + else + SUP_DPRINTF(("RTLDRPROP_IMPORT_COUNT failed with rc=%Rrc on '%ls'\n", rc, pwszName)); +} + + +/** + * Processes the list of import todos. + */ +static void supR3HardenedWinVerifyCacheProcessImportTodos(void) +{ + /* + * Work until we've got nothing more todo. + */ + for (;;) + { + PVERIFIERCACHEIMPORT pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoImports, NULL, PVERIFIERCACHEIMPORT); + if (!pTodo) + break; + do + { + PVERIFIERCACHEIMPORT pCur = pTodo; + pTodo = pTodo->pNext; + + /* + * Not in the cached already? + */ + if ( !supR3HardenedWinVerifyCacheLookupImport(g_System32NtPath.UniStr.Buffer, + g_System32NtPath.UniStr.Length / sizeof(WCHAR), + pCur->szName) + && !supR3HardenedWinVerifyCacheLookupImport(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, + g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR), + pCur->szName) + && ( pCur->cwcAltSearchDir == 0 + || !supR3HardenedWinVerifyCacheLookupImport(pCur->pwszAltSearchDir, pCur->cwcAltSearchDir, pCur->szName)) ) + { + /* + * Try locate the imported DLL and open it. + */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Processing '%s'...\n", pCur->szName)); + + NTSTATUS rcNt; + NTSTATUS rcNtRedir = 0x22222222; + HANDLE hFile = INVALID_HANDLE_VALUE; + RTUTF16 wszPath[260 + 260]; /* Assumes we've limited the import name length to 256. */ + AssertCompile(sizeof(wszPath) > sizeof(g_System32NtPath)); + + /* + * Check for DLL isolation / redirection / mapping. + */ + size_t cwcName = 260; + PRTUTF16 pwszName = &wszPath[0]; + int rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName); + if (RT_SUCCESS(rc)) + { + UNICODE_STRING UniStrName; + UniStrName.Buffer = wszPath; + UniStrName.Length = (USHORT)cwcName * sizeof(WCHAR); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + + UNICODE_STRING UniStrStatic; + UniStrStatic.Buffer = &wszPath[cwcName + 1]; + UniStrStatic.Length = 0; + UniStrStatic.MaximumLength = (USHORT)(sizeof(wszPath) - cwcName * sizeof(WCHAR) - sizeof(WCHAR)); + + static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll"); + UNICODE_STRING UniStrDynamic = { 0, 0, NULL }; + PUNICODE_STRING pUniStrResult = NULL; + + rcNtRedir = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + &UniStrName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtRedir)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, pUniStrResult, + OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + /* For accurate logging. */ + size_t cwcCopy = RT_MIN(pUniStrResult->Length / sizeof(RTUTF16), RT_ELEMENTS(wszPath) - 1); + memcpy(wszPath, pUniStrResult->Buffer, cwcCopy * sizeof(RTUTF16)); + wszPath[cwcCopy] = '\0'; + } + else + hFile = INVALID_HANDLE_VALUE; + RtlFreeUnicodeString(&UniStrDynamic); + } + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #1 failed: %Rrc\n", rc)); + + /* + * If not something that gets remapped, do the half normal searching we need. + */ + if (hFile == INVALID_HANDLE_VALUE) + { + struct + { + PRTUTF16 pawcDir; + uint32_t cwcDir; + } Tmp, aDirs[] = + { + { g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length / sizeof(WCHAR) }, + { g_SupLibHardenedExeNtPath.UniStr.Buffer, g_SupLibHardenedAppBinNtPath.UniStr.Length / sizeof(WCHAR) }, + { pCur->pwszAltSearchDir, pCur->cwcAltSearchDir }, + }; + + /* Search System32 first, unless it's a 'V*' or 'm*' name, the latter for msvcrt. */ + if ( pCur->szName[0] == 'v' + || pCur->szName[0] == 'V' + || pCur->szName[0] == 'm' + || pCur->szName[0] == 'M') + { + Tmp = aDirs[0]; + aDirs[0] = aDirs[1]; + aDirs[1] = Tmp; + } + + for (uint32_t i = 0; i < RT_ELEMENTS(aDirs); i++) + { + if (aDirs[i].pawcDir && aDirs[i].cwcDir && aDirs[i].cwcDir < RT_ELEMENTS(wszPath) / 3 * 2) + { + memcpy(wszPath, aDirs[i].pawcDir, aDirs[i].cwcDir * sizeof(RTUTF16)); + uint32_t cwc = aDirs[i].cwcDir; + wszPath[cwc++] = '\\'; + cwcName = RT_ELEMENTS(wszPath) - cwc; + pwszName = &wszPath[cwc]; + rc = RTStrToUtf16Ex(pCur->szName, RTSTR_MAX, &pwszName, cwcName, &cwcName); + if (RT_SUCCESS(rc)) + { + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING NtName; + NtName.Buffer = wszPath; + NtName.Length = (USHORT)((cwc + cwcName) * sizeof(WCHAR)); + NtName.MaximumLength = NtName.Length + sizeof(WCHAR); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + break; + hFile = INVALID_HANDLE_VALUE; + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: RTStrToUtf16Ex #2 failed: %Rrc\n", rc)); + } + } + } + + /* + * If we successfully opened it, verify it and cache the result. + */ + if (hFile != INVALID_HANDLE_VALUE) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' -> '%ls' [rcNtRedir=%#x]\n", + pCur->szName, wszPath, rcNtRedir)); + + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, false /*fIgnoreArch*/, &fAccess, &fProtect, + &fCallRealApi, "Imports", false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + NtClose(hFile); + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: Failed to locate '%s'\n", pCur->szName)); + } + else + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessImportTodos: '%s' is in the cache.\n", pCur->szName)); + + RTMemFree(pCur); + } while (pTodo); + } +} + + +/** + * Processes the list of WinVerifyTrust todos. + */ +static void supR3HardenedWinVerifyCacheProcessWvtTodos(void) +{ + PVERIFIERCACHEENTRY pReschedule = NULL; + PVERIFIERCACHEENTRY volatile *ppReschedLastNext = &pReschedule; + + /* + * Work until we've got nothing more todo. + */ + for (;;) + { + if (!supHardenedWinIsWinVerifyTrustCallable()) + break; + PVERIFIERCACHEENTRY pTodo = ASMAtomicXchgPtrT(&g_pVerifierCacheTodoWvt, NULL, PVERIFIERCACHEENTRY); + if (!pTodo) + break; + do + { + PVERIFIERCACHEENTRY pCur = pTodo; + pTodo = pTodo->pNextTodoWvt; + pCur->pNextTodoWvt = NULL; + + if ( !pCur->fWinVerifyTrust + && RT_SUCCESS(pCur->rc)) + { + bool fWinVerifyTrust = false; + int rc = supHardenedWinVerifyImageTrust(pCur->hFile, pCur->wszPath, pCur->fFlags, pCur->rc, + &fWinVerifyTrust, NULL /* pErrInfo*/); + if (RT_FAILURE(rc) || fWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls'\n", + rc, pCur->rc, fWinVerifyTrust, pCur->wszPath)); + pCur->fWinVerifyTrust = true; + pCur->rc = rc; + } + else + { + /* Retry it at a later time. */ + SUP_DPRINTF(("supR3HardenedWinVerifyCacheProcessWvtTodos: %d (was %d) fWinVerifyTrust=%d for '%ls' [rescheduled]\n", + rc, pCur->rc, fWinVerifyTrust, pCur->wszPath)); + *ppReschedLastNext = pCur; + ppReschedLastNext = &pCur->pNextTodoWvt; + } + } + /* else: already processed. */ + } while (pTodo); + } + + /* + * Anything to reschedule. + */ + if (pReschedule) + { + do + *ppReschedLastNext = g_pVerifierCacheTodoWvt; + while (!ASMAtomicCmpXchgPtr(&g_pVerifierCacheTodoWvt, pReschedule, *ppReschedLastNext)); + } +} + + +/** + * Translates VBox status code (from supHardenedWinVerifyImageTrust) to an NT + * status. + * + * @returns NT status. + * @param rc VBox status code. + */ +static NTSTATUS supR3HardenedScreenImageCalcStatus(int rc) RT_NOTHROW_DEF +{ + /* This seems to be what LdrLoadDll returns when loading a 32-bit DLL into + a 64-bit process. At least here on windows 10 (2015-11-xx). + + NtCreateSection probably returns something different, possibly a warning, + we currently don't distinguish between the too, so we stick with the + LdrLoadDll one as it's definitely an error.*/ + if (rc == VERR_LDR_ARCH_MISMATCH) + return STATUS_INVALID_IMAGE_FORMAT; + + return STATUS_TRUST_FAILURE; +} + + +/** + * Screens an image file or file mapped with execute access. + * + * @returns NT status code. + * @param hFile The file handle. + * @param fImage Set if image file mapping being made + * (NtCreateSection thing). + * @param fIgnoreArch Using the DONT_RESOLVE_DLL_REFERENCES flag, + * which also implies that DLL init / term code + * isn't called, so the architecture should be + * ignored. + * @param pfAccess Pointer to the NtCreateSection access flags, + * so we can modify them if necessary. + * @param pfProtect Pointer to the NtCreateSection protection + * flags, so we can modify them if necessary. + * @param pfCallRealApi Whether it's ok to go on to the real API. + * @param pszCaller Who is calling (for debugging / logging). + * @param fAvoidWinVerifyTrust Whether we should avoid WinVerifyTrust. + * @param pfQuiet Where to return whether to be quiet about + * this image in the log (i.e. we've seen it + * lots of times already). Optional. + */ +static NTSTATUS +supR3HardenedScreenImage(HANDLE hFile, bool fImage, bool fIgnoreArch, PULONG pfAccess, PULONG pfProtect, + bool *pfCallRealApi, const char *pszCaller, bool fAvoidWinVerifyTrust, bool *pfQuiet) RT_NOTHROW_DEF +{ + *pfCallRealApi = false; + if (pfQuiet) + *pfQuiet = false; + + /* + * 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, + "supR3HardenedScreenImage/%s: NtQueryObject -> %#x (fImage=%d fProtect=%#x fAccess=%#x)\n", + pszCaller, fImage, *pfProtect, *pfAccess); + return rcNt; + } + + if (!RTNtPathFindPossible8dot3Name(uBuf.UniStr.Buffer)) + cbNameBuf += sizeof(WCHAR); + else + { + uBuf.UniStr.MaximumLength = sizeof(uBuf) - 128; + RTNtPathExpand8dot3Path(&uBuf.UniStr, true /*fPathOnly*/); + cbNameBuf = (uintptr_t)uBuf.UniStr.Buffer + uBuf.UniStr.Length + sizeof(WCHAR) - (uintptr_t)&uBuf.abBuffer[0]; + } + + /* + * Check the cache. + */ + PVERIFIERCACHEENTRY pCacheHit = supR3HardenedWinVerifyCacheLookup(&uBuf.UniStr, hFile); + if (pCacheHit) + { + /* Do hit accounting and figure whether we need to be quiet or not. */ + uint32_t cHits = ASMAtomicIncU32(&pCacheHit->cHits); + bool const fQuiet = cHits >= 8 && !RT_IS_POWER_OF_TWO(cHits); + if (pfQuiet) + *pfQuiet = fQuiet; + + /* If we haven't done the WinVerifyTrust thing, do it if we can. */ + if ( !pCacheHit->fWinVerifyTrust + && RT_SUCCESS(pCacheHit->rc) + && supHardenedWinIsWinVerifyTrustCallable() ) + { + if (!fAvoidWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [redoing WinVerifyTrust]\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath)); + + bool fWinVerifyTrust = false; + int rc = supHardenedWinVerifyImageTrust(pCacheHit->hFile, pCacheHit->wszPath, pCacheHit->fFlags, pCacheHit->rc, + &fWinVerifyTrust, NULL /* pErrInfo*/); + if (RT_FAILURE(rc) || fWinVerifyTrust) + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: %d (was %d) fWinVerifyTrust=%d for '%ls'\n", + pszCaller, rc, pCacheHit->rc, fWinVerifyTrust, pCacheHit->wszPath)); + pCacheHit->fWinVerifyTrust = true; + pCacheHit->rc = rc; + } + else + SUP_DPRINTF(("supR3HardenedScreenImage/%s: WinVerifyTrust not available, rescheduling %ls\n", + pszCaller, pCacheHit->wszPath)); + } + else + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls [avoiding WinVerifyTrust]\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath)); + } + else if (!fQuiet || !pCacheHit->fWinVerifyTrust) + SUP_DPRINTF(("supR3HardenedScreenImage/%s: cache hit (%Rrc) on %ls%s\n", + pszCaller, pCacheHit->rc, pCacheHit->wszPath, pCacheHit->fWinVerifyTrust ? "" : " [lacks WinVerifyTrust]")); + + /* Return the cached value. */ + if (RT_SUCCESS(pCacheHit->rc)) + { + *pfCallRealApi = true; + return STATUS_SUCCESS; + } + + if (!fQuiet) + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: cached rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x cHits=%u %ls\n", + pszCaller, pCacheHit->rc, fImage, *pfProtect, *pfAccess, cHits, uBuf.UniStr.Buffer); + return supR3HardenedScreenImageCalcStatus(pCacheHit->rc); + } + + /* + * On XP the loader might hand us handles with just FILE_EXECUTE and + * SYNCHRONIZE, the means reading will fail later on. Also, we need + * READ_CONTROL access to check the file ownership later on, and non + * of the OS versions seems be giving us that. So, in effect we + * more or less always reopen the file here. + */ + HANDLE hMyFile = NULL; + rcNt = NtDuplicateObject(NtCurrentProcess(), hFile, NtCurrentProcess(), + &hMyFile, + FILE_READ_DATA | READ_CONTROL | SYNCHRONIZE, + 0 /* Handle attributes*/, 0 /* Options */); + if (!NT_SUCCESS(rcNt)) + { + if (rcNt == STATUS_ACCESS_DENIED) + { + 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 | READ_CONTROL | 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)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Failed to duplicate and open the file: rcNt=%#x hFile=%p %ls\n", + pszCaller, rcNt, hFile, uBuf.UniStr.Buffer); + return rcNt; + } + + /* Check that we've got the same file. */ + LARGE_INTEGER idMyFile, idInFile; + bool fMyValid = supR3HardenedWinVerifyCacheGetIndexNumber(hMyFile, &idMyFile); + bool fInValid = supR3HardenedWinVerifyCacheGetIndexNumber(hFile, &idInFile); + if ( fMyValid + && ( fMyValid != fInValid + || idMyFile.QuadPart != idInFile.QuadPart)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Re-opened has different ID that input: %#llx vx %#llx (%ls)\n", + pszCaller, rcNt, idMyFile.QuadPart, idInFile.QuadPart, uBuf.UniStr.Buffer); + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + } + else + { + SUP_DPRINTF(("supR3HardenedScreenImage/%s: NtDuplicateObject -> %#x\n", pszCaller, rcNt)); +#ifdef DEBUG + + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: NtDuplicateObject(,%#x,) failed: %#x\n", pszCaller, 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(("supR3HardenedScreenImage/%s: Applying the drop-exec-kludge for '%ls'\n", pszCaller, uBuf.UniStr.Buffer)); + if (*pfAccess & SECTION_MAP_EXECUTE) + *pfAccess = (*pfAccess & ~SECTION_MAP_EXECUTE) | SECTION_MAP_READ; + if (*pfProtect & PAGE_EXECUTE) + *pfProtect = (*pfProtect & ~PAGE_EXECUTE) | PAGE_READONLY; + *pfProtect = (*pfProtect & ~UINT32_C(0xf0)) | ((*pfProtect & UINT32_C(0xe0)) >> 4); + if (hMyFile != hFile) + NtClose(hMyFile); + *pfCallRealApi = true; + return STATUS_SUCCESS; + } + } + +#ifndef VBOX_PERMIT_EVEN_MORE + /* + * Check the path. We don't allow DLLs to be loaded from just anywhere: + * 1. System32 - normal code or cat signing, owner TrustedInstaller. + * 2. WinSxS - normal code or cat signing, owner TrustedInstaller. + * 3. VirtualBox - kernel code signing and integrity checks. + * 4. AppPatchDir - normal code or cat signing, owner TrustedInstaller. + * 5. Program Files - normal code or cat signing, owner TrustedInstaller. + * 6. Common Files - normal code or cat signing, owner TrustedInstaller. + * 7. x86 variations of 4 & 5 - ditto. + */ + uint32_t fFlags = 0; + if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_System32NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_WinSxSNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT; +# ifdef VBOX_PERMIT_MORE + else if (supHardViIsAppPatchDir(uBuf.UniStr.Buffer, uBuf.UniStr.Length / sizeof(WCHAR))) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +# ifdef RT_ARCH_AMD64 + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_ProgramFilesX86NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; + else if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_CommonFilesX86NtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +# endif +# endif +# ifdef VBOX_PERMIT_VISUAL_STUDIO_PROFILING + /* Hack to allow profiling our code with Visual Studio. */ + else if ( uBuf.UniStr.Length > sizeof(L"\\SamplingRuntime.dll") + && memcmp(uBuf.UniStr.Buffer + (uBuf.UniStr.Length - sizeof(L"\\SamplingRuntime.dll") + sizeof(WCHAR)) / sizeof(WCHAR), + L"\\SamplingRuntime.dll", sizeof(L"\\SamplingRuntime.dll") - sizeof(WCHAR)) == 0 ) + { + if (hMyFile != hFile) + NtClose(hMyFile); + *pfCallRealApi = true; + return STATUS_SUCCESS; + } +# endif + else + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: Not a trusted location: '%ls' (fImage=%d fProtect=%#x fAccess=%#x)\n", + pszCaller, uBuf.UniStr.Buffer, fImage, *pfAccess, *pfProtect); + if (hMyFile != hFile) + NtClose(hMyFile); + return STATUS_TRUST_FAILURE; + } + +#else /* VBOX_PERMIT_EVEN_MORE */ + /* + * Require trusted installer + some kind of signature on everything, except + * for the VBox bits where we require kernel code signing and special + * integrity checks. + */ + uint32_t fFlags = 0; + if (supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &g_SupLibHardenedAppBinNtPath.UniStr, true /*fCheckSlash*/)) + fFlags |= SUPHNTVI_F_REQUIRE_KERNEL_CODE_SIGNING | SUPHNTVI_F_REQUIRE_SIGNATURE_ENFORCEMENT; + else + fFlags |= SUPHNTVI_F_ALLOW_CAT_FILE_VERIFICATION | SUPHNTVI_F_TRUSTED_INSTALLER_OWNER; +#endif /* VBOX_PERMIT_EVEN_MORE */ + + /* + * Do the verification. For better error message we borrow what's + * left of the path buffer for an RTERRINFO buffer. + */ + if (fIgnoreArch) + fFlags |= SUPHNTVI_F_IGNORE_ARCHITECTURE; + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, (char *)&uBuf.abBuffer[cbNameBuf], sizeof(uBuf) - cbNameBuf); + + int rc; + bool fWinVerifyTrust = false; + rc = supHardenedWinVerifyImageByHandle(hMyFile, uBuf.UniStr.Buffer, fFlags, fAvoidWinVerifyTrust, &fWinVerifyTrust, &ErrInfo); + if (RT_FAILURE(rc)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedScreenImage/%s: rc=%Rrc fImage=%d fProtect=%#x fAccess=%#x %ls: %s\n", + pszCaller, rc, fImage, *pfAccess, *pfProtect, uBuf.UniStr.Buffer, ErrInfo.pszMsg); + if (hMyFile != hFile) + supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags); + return supR3HardenedScreenImageCalcStatus(rc); + } + + /* + * Insert into the cache. + */ + if (hMyFile != hFile) + supR3HardenedWinVerifyCacheInsert(&uBuf.UniStr, hMyFile, rc, fWinVerifyTrust, fFlags); + + *pfCallRealApi = true; + return STATUS_SUCCESS; +} + + +/** + * Preloads a file into the verify cache if possible. + * + * This is used to avoid known cyclic LoadLibrary issues with WinVerifyTrust. + * + * @param pwszName The name of the DLL to verify. + */ +DECLHIDDEN(void) supR3HardenedWinVerifyCachePreload(PCRTUTF16 pwszName) +{ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + UNICODE_STRING UniStr; + UniStr.Buffer = (PWCHAR)pwszName; + UniStr.Length = (USHORT)(RTUtf16Len(pwszName) * sizeof(WCHAR)); + UniStr.MaximumLength = UniStr.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: Error %#x opening '%ls'.\n", rcNt, pwszName)); + return; + } + + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi; + //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: scanning %ls\n", pwszName)); + supR3HardenedScreenImage(hFile, false, false /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, "preload", + false /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + //SUP_DPRINTF(("supR3HardenedWinVerifyCachePreload: done %ls\n", pwszName)); + + NtClose(hFile); +} + + + +/** + * 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). + */ +__declspec(guard(ignore)) /* don't barf when calling g_pfnNtCreateSectionReal */ +static NTSTATUS NTAPI +supR3HardenedMonitor_NtCreateSection(PHANDLE phSection, ACCESS_MASK fAccess, POBJECT_ATTRIBUTES pObjAttribs, + PLARGE_INTEGER pcbSection, ULONG fProtect, ULONG fAttribs, HANDLE hFile) +{ + bool fNeedUncChecking = false; + 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) + { + fNeedUncChecking = true; + DWORD dwSavedLastError = RtlGetLastWin32Error(); + + bool fCallRealApi; + //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 1\n")); + NTSTATUS rcNt = supR3HardenedScreenImage(hFile, fImage, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, + "NtCreateSection", true /*fAvoidWinVerifyTrust*/, NULL /*pfQuiet*/); + //SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: 2 rcNt=%#x fCallRealApi=%#x\n", rcNt, fCallRealApi)); + + RtlRestoreLastWin32Error(dwSavedLastError); + + if (!NT_SUCCESS(rcNt)) + return rcNt; + Assert(fCallRealApi); + if (!fCallRealApi) + return STATUS_TRUST_FAILURE; + + } + } + + /* + * Call checked out OK, call the original. + */ + NTSTATUS rcNtReal = g_pfnNtCreateSectionReal(phSection, fAccess, pObjAttribs, pcbSection, fProtect, fAttribs, hFile); + + /* + * Check that the image that got mapped bear some resemblance to the one that was + * requested. Apparently there are ways to trick the NT cache manager to map a + * file different from hFile into memory using local UNC accesses. + */ + if ( NT_SUCCESS(rcNtReal) + && fNeedUncChecking) + { + DWORD dwSavedLastError = RtlGetLastWin32Error(); + + bool fOkay = false; + + /* To get the name of the file backing the section, we unfortunately have to map it. */ + SIZE_T cbView = 0; + PVOID pvTmpMap = NULL; + NTSTATUS rcNt = NtMapViewOfSection(*phSection, NtCurrentProcess(), &pvTmpMap, 0, 0, NULL /*poffSection*/, &cbView, + ViewUnmap, MEM_TOP_DOWN, PAGE_EXECUTE); + if (NT_SUCCESS(rcNt)) + { + /* Query the name. */ + union + { + UNICODE_STRING UniStr; + RTUTF16 awcBuf[512]; + } uBuf; + RT_ZERO(uBuf); + SIZE_T cbActual = 0; + NTSTATUS rcNtQuery = NtQueryVirtualMemory(NtCurrentProcess(), pvTmpMap, MemorySectionName, + &uBuf, sizeof(uBuf) - sizeof(RTUTF16), &cbActual); + + /* Unmap the view. */ + rcNt = NtUnmapViewOfSection(NtCurrentProcess(), pvTmpMap); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtUnmapViewOfSection failed on %p (hSection=%p, hFile=%p) with %#x!\n", + pvTmpMap, *phSection, hFile, rcNt)); + + /* Process the name query result. */ + if (NT_SUCCESS(rcNtQuery)) + { + static UNICODE_STRING const s_UncPrefix = RTNT_CONSTANT_UNISTR(L"\\Device\\Mup"); + if (!supHardViUniStrPathStartsWithUniStr(&uBuf.UniStr, &s_UncPrefix, true /*fCheckSlash*/)) + fOkay = true; + else + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_NtCreateSection: Image section with UNC path is not trusted: '%.*ls'\n", + uBuf.UniStr.Length / sizeof(RTUTF16), uBuf.UniStr.Buffer); + } + else + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtQueryVirtualMemory failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n", + *phSection, hFile, rcNt)); + } + else + SUP_DPRINTF(("supR3HardenedMonitor_NtCreateSection: NtMapViewOfSection failed on %p (hFile=%p) with %#x -> STATUS_TRUST_FAILURE\n", + *phSection, hFile, rcNt)); + if (!fOkay) + { + NtClose(*phSection); + *phSection = INVALID_HANDLE_VALUE; + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_TRUST_FAILURE; + } + + RtlRestoreLastWin32Error(dwSavedLastError); + } + return rcNtReal; +} + + +/** + * Checks if the given name is a valid ApiSet name. + * + * This is only called on likely looking names. + * + * @returns true if ApiSet name, false if not. + * @param pName The name to check out. + */ +static bool supR3HardenedIsApiSetDll(PUNICODE_STRING pName) +{ + /* + * API added in Windows 8, or so they say. + */ + if (ApiSetQueryApiSetPresence != NULL) + { + BOOLEAN fPresent = FALSE; + NTSTATUS rcNt = ApiSetQueryApiSetPresence(pName, &fPresent); + SUP_DPRINTF(("supR3HardenedIsApiSetDll: ApiSetQueryApiSetPresence(%.*ls) -> %#x, fPresent=%d\n", + pName->Length / sizeof(WCHAR), pName->Buffer, rcNt, fPresent)); + return fPresent != 0; + } + + /* + * Fallback needed for Windows 7. Fortunately, there aren't too many fake DLLs here. + */ + if ( g_uNtVerCombined >= SUP_NT_VER_W70 + && ( supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR), + L"api-ms-win-", 11, false /*fCheckSlash*/) + || supHardViUtf16PathStartsWithEx(pName->Buffer, pName->Length / sizeof(WCHAR), + L"ext-ms-win-", 11, false /*fCheckSlash*/) )) + { +#define MY_ENTRY(a) { a, sizeof(a) - 1 } + static const struct { const char *psz; size_t cch; } s_aKnownSets[] = + { + MY_ENTRY("api-ms-win-core-console-l1-1-0 "), + MY_ENTRY("api-ms-win-core-datetime-l1-1-0"), + MY_ENTRY("api-ms-win-core-debug-l1-1-0"), + MY_ENTRY("api-ms-win-core-delayload-l1-1-0"), + MY_ENTRY("api-ms-win-core-errorhandling-l1-1-0"), + MY_ENTRY("api-ms-win-core-fibers-l1-1-0"), + MY_ENTRY("api-ms-win-core-file-l1-1-0"), + MY_ENTRY("api-ms-win-core-handle-l1-1-0"), + MY_ENTRY("api-ms-win-core-heap-l1-1-0"), + MY_ENTRY("api-ms-win-core-interlocked-l1-1-0"), + MY_ENTRY("api-ms-win-core-io-l1-1-0"), + MY_ENTRY("api-ms-win-core-libraryloader-l1-1-0"), + MY_ENTRY("api-ms-win-core-localization-l1-1-0"), + MY_ENTRY("api-ms-win-core-localregistry-l1-1-0"), + MY_ENTRY("api-ms-win-core-memory-l1-1-0"), + MY_ENTRY("api-ms-win-core-misc-l1-1-0"), + MY_ENTRY("api-ms-win-core-namedpipe-l1-1-0"), + MY_ENTRY("api-ms-win-core-processenvironment-l1-1-0"), + MY_ENTRY("api-ms-win-core-processthreads-l1-1-0"), + MY_ENTRY("api-ms-win-core-profile-l1-1-0"), + MY_ENTRY("api-ms-win-core-rtlsupport-l1-1-0"), + MY_ENTRY("api-ms-win-core-string-l1-1-0"), + MY_ENTRY("api-ms-win-core-synch-l1-1-0"), + MY_ENTRY("api-ms-win-core-sysinfo-l1-1-0"), + MY_ENTRY("api-ms-win-core-threadpool-l1-1-0"), + MY_ENTRY("api-ms-win-core-ums-l1-1-0"), + MY_ENTRY("api-ms-win-core-util-l1-1-0"), + MY_ENTRY("api-ms-win-core-xstate-l1-1-0"), + MY_ENTRY("api-ms-win-security-base-l1-1-0"), + MY_ENTRY("api-ms-win-security-lsalookup-l1-1-0"), + MY_ENTRY("api-ms-win-security-sddl-l1-1-0"), + MY_ENTRY("api-ms-win-service-core-l1-1-0"), + MY_ENTRY("api-ms-win-service-management-l1-1-0"), + MY_ENTRY("api-ms-win-service-management-l2-1-0"), + MY_ENTRY("api-ms-win-service-winsvc-l1-1-0"), + }; +#undef MY_ENTRY + + /* drop the dll suffix if present. */ + PCRTUTF16 pawcName = pName->Buffer; + size_t cwcName = pName->Length / sizeof(WCHAR); + if ( cwcName > 5 + && (pawcName[cwcName - 1] == 'l' || pawcName[cwcName - 1] == 'L') + && (pawcName[cwcName - 2] == 'l' || pawcName[cwcName - 2] == 'L') + && (pawcName[cwcName - 3] == 'd' || pawcName[cwcName - 3] == 'D') + && pawcName[cwcName - 4] == '.') + cwcName -= 4; + + /* Search the table. */ + for (size_t i = 0; i < RT_ELEMENTS(s_aKnownSets); i++) + if ( cwcName == s_aKnownSets[i].cch + && RTUtf16NICmpAscii(pawcName, s_aKnownSets[i].psz, cwcName) == 0) + { + SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> true\n", pName->Length / sizeof(WCHAR))); + return true; + } + + SUP_DPRINTF(("supR3HardenedIsApiSetDll: Warning! '%.*ls' looks like an API set, but it's not in the list!\n", + pName->Length / sizeof(WCHAR), pName->Buffer)); + } + + SUP_DPRINTF(("supR3HardenedIsApiSetDll: '%.*ls' -> false\n", pName->Length / sizeof(WCHAR))); + return false; +} + + +/** + * Checks whether the given unicode string contains a path separator and at + * least one dash. + * + * This is used to check for likely ApiSet name. So far, all the pseudo DLL + * names include multiple dashes, so we use that as a criteria for recognizing + * them. By happy coincident, most regular DLLs doesn't include dashes. + * + * @returns true if it contains path separator, false if only a name. + * @param pPath The path to check. + */ +static bool supR3HardenedHasDashButNoPath(PUNICODE_STRING pPath) +{ + size_t cDashes = 0; + size_t cwcLeft = pPath->Length / sizeof(WCHAR); + PCRTUTF16 pwc = pPath->Buffer; + while (cwcLeft-- > 0) + { + RTUTF16 wc = *pwc++; + switch (wc) + { + default: + break; + + case '-': + cDashes++; + break; + + case '\\': + case '/': + case ':': + return false; + } + } + return cDashes > 0; +} + + +/** + * Helper for supR3HardenedMonitor_LdrLoadDll. + * + * @returns NT status code. + * @param pwszPath The path destination buffer. + * @param cwcPath The size of the path buffer. + * @param pUniStrResult The result string. + * @param pOrgName The orignal name (for errors). + * @param pcwc Where to return the actual length. + */ +static NTSTATUS supR3HardenedCopyRedirectionResult(WCHAR *pwszPath, size_t cwcPath, PUNICODE_STRING pUniStrResult, + PUNICODE_STRING pOrgName, UINT *pcwc) +{ + UINT cwc; + *pcwc = cwc = pUniStrResult->Length / sizeof(WCHAR); + if (pUniStrResult->Buffer == pwszPath) + pwszPath[cwc] = '\0'; + else + { + if (cwc > cwcPath - 1) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long: %.*ls -> %.*ls (RtlDosApplyFileIoslationRedirection_Ustr)\n", + pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, + pUniStrResult->Length / sizeof(WCHAR), pUniStrResult->Buffer); + return STATUS_NAME_TOO_LONG; + } + memcpy(&pwszPath[0], pUniStrResult->Buffer, pUniStrResult->Length); + pwszPath[cwc] = '\0'; + } + return STATUS_SUCCESS; +} + + +/** + * Helper for supR3HardenedMonitor_LdrLoadDll that compares the name part of the + * input path against a ASCII name string of a given length. + * + * @returns true if the name part matches + * @param pPath The LdrLoadDll input path. + * @param pszName The name to try match it with. + * @param cchName The name length. + */ +static bool supR3HardenedIsFilenameMatchDll(PUNICODE_STRING pPath, const char *pszName, size_t cchName) +{ + if (pPath->Length < cchName * 2) + return false; + PCRTUTF16 pwszTmp = &pPath->Buffer[pPath->Length / sizeof(RTUTF16) - cchName]; + if ( pPath->Length != cchName + && pwszTmp[-1] != '\\' + && pwszTmp[-1] != '/') + return false; + return RTUtf16ICmpAscii(pwszTmp, pszName) == 0; +} + + +/** + * Hooks that intercepts LdrLoadDll calls. + * + * Two purposes: + * -# Enforce our own search path restrictions. + * -# Prevalidate DLLs about to be loaded so we don't upset the loader data + * by doing it from within the NtCreateSection hook (WinVerifyTrust + * seems to be doing harm there on W7/32). + * + * @returns + * @param pwszSearchPath The search path to use. + * @param pfFlags Flags on input. DLL characteristics or something + * on return? + * @param pName The name of the module. + * @param phMod Where the handle of the loaded DLL is to be + * returned to the caller. + */ +__declspec(guard(ignore)) /* don't barf when calling g_pfnLdrLoadDllReal */ +static NTSTATUS NTAPI +supR3HardenedMonitor_LdrLoadDll(PWSTR pwszSearchPath, PULONG pfFlags, PUNICODE_STRING pName, PHANDLE phMod) +{ + DWORD dwSavedLastError = RtlGetLastWin32Error(); + PUNICODE_STRING const pOrgName = pName; + NTSTATUS rcNt; + + /* + * Make sure the DLL notification callback is registered. If we could, we + * would've done this during early process init, but due to lack of heap + * and uninitialized loader lock, it's not possible that early on. + * + * The callback protects our NtDll hooks from getting unhooked by + * "friendly" fire from the AV crowd. + */ + supR3HardenedWinRegisterDllNotificationCallback(); + + /* + * Process WinVerifyTrust todo before and after. + */ + supR3HardenedWinVerifyCacheProcessWvtTodos(); + + /* + * Reject things we don't want to deal with. + */ + if (!pName || pName->Length == 0) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: name is NULL or have a zero length.\n"); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x (pName=%p)\n", STATUS_INVALID_PARAMETER, pName)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_INVALID_PARAMETER; + } + PCWCHAR const pawcOrgName = pName->Buffer; + uint32_t const cwcOrgName = pName->Length / sizeof(WCHAR); + + /*SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls *pfFlags=%#x pwszSearchPath=%p:%ls\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>"));*/ + + /* + * Reject long paths that's close to the 260 limit without looking. + */ + if (cwcOrgName > 256) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: too long name: %#x bytes\n", pName->Length); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + + /* + * Reject all UNC-like paths as we cannot trust non-local files at all. + * Note! We may have to relax this to deal with long path specifications and NT pass thrus. + */ + if ( cwcOrgName >= 3 + && RTPATH_IS_SLASH(pawcOrgName[0]) + && RTPATH_IS_SLASH(pawcOrgName[1]) + && !RTPATH_IS_SLASH(pawcOrgName[2])) + { + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting UNC name '%.*ls'\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_REDIRECTOR_NOT_STARTED)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_REDIRECTOR_NOT_STARTED; + } + + /* + * Reject PGHook.dll as it creates a thread from its DllMain that breaks + * our preconditions respawning the 2nd process, resulting in + * VERR_SUP_VP_THREAD_NOT_ALONE. The DLL is being loaded by a user APC + * scheduled during kernel32.dll load notification from a kernel driver, + * so failing the load attempt should not upset anyone. + */ + if (g_enmSupR3HardenedMainState == SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED) + { + static const struct { const char *psz; size_t cch; } s_aUnwantedEarlyDlls[] = + { + { RT_STR_TUPLE("PGHook.dll") }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aUnwantedEarlyDlls); i++) + if (supR3HardenedIsFilenameMatchDll(pName, s_aUnwantedEarlyDlls[i].psz, s_aUnwantedEarlyDlls[i].cch)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load '%.*ls' as it is expected to create undesirable threads that will upset our respawn checks (returning STATUS_TOO_MANY_THREADS)\n", + pName->Length / sizeof(RTUTF16), pName->Buffer)); + return STATUS_TOO_MANY_THREADS; + } + } + + /* + * Resolve the path, copying the result into wszPath + */ + NTSTATUS rcNtResolve = STATUS_SUCCESS; + bool fSkipValidation = false; + bool fCheckIfLoaded = false; + WCHAR wszPath[260]; + static UNICODE_STRING const s_DefaultSuffix = RTNT_CONSTANT_UNISTR(L".dll"); + UNICODE_STRING UniStrStatic = { 0, (USHORT)sizeof(wszPath) - sizeof(WCHAR), wszPath }; + UNICODE_STRING UniStrDynamic = { 0, 0, NULL }; + PUNICODE_STRING pUniStrResult = NULL; + UNICODE_STRING ResolvedName; + + /* + * Process the name a little, checking if it needs a DLL suffix and is pathless. + */ + uint32_t offLastSlash = UINT32_MAX; + uint32_t offLastDot = UINT32_MAX; + for (uint32_t i = 0; i < cwcOrgName; i++) + switch (pawcOrgName[i]) + { + case '\\': + case '/': + offLastSlash = i; + offLastDot = UINT32_MAX; + break; + case '.': + offLastDot = i; + break; + } + bool const fNeedDllSuffix = offLastDot == UINT32_MAX; + //bool const fTrailingDot = offLastDot == cwcOrgName - 1; + + /* + * Absolute path? + */ + if ( ( cwcOrgName >= 4 + && RT_C_IS_ALPHA(pawcOrgName[0]) + && pawcOrgName[1] == ':' + && RTPATH_IS_SLASH(pawcOrgName[2]) ) + || ( cwcOrgName >= 1 + && RTPATH_IS_SLASH(pawcOrgName[0]) ) + ) + { + rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + pName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtResolve)) + { + UINT cwc; + rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc); + RtlFreeUnicodeString(&UniStrDynamic); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt)); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + + ResolvedName.Buffer = wszPath; + ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR)); + ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR); + + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: '%.*ls' -> '%.*ls' [redir]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, + ResolvedName.Length / sizeof(WCHAR), ResolvedName.Buffer, rcNt)); + pName = &ResolvedName; + } + else + { + /* Copy the path. */ + memcpy(wszPath, pawcOrgName, cwcOrgName * sizeof(WCHAR)); + if (!fNeedDllSuffix) + wszPath[cwcOrgName] = '\0'; + else + { + if (cwcOrgName + 4 >= RT_ELEMENTS(wszPath)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long (abs): %.*ls\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + memcpy(&wszPath[cwcOrgName], L".dll", 5 * sizeof(WCHAR)); + } + } + } + /* + * Not an absolute path. Check if it's one of those special API set DLLs + * or something we're known to use but should be taken from WinSxS. + */ + else if ( supR3HardenedHasDashButNoPath(pName) + && supR3HardenedIsApiSetDll(pName)) + { + memcpy(wszPath, pName->Buffer, pName->Length); + wszPath[pName->Length / sizeof(WCHAR)] = '\0'; + fSkipValidation = true; + } + /* + * Not an absolute path or special API set. There are two alternatives + * now, either there is no path at all or there is a relative path. We + * will resolve it to an absolute path in either case, failing the call + * if we can't. + */ + else + { + /* + * Reject relative paths for now as they might be breakout attempts. + */ + if (offLastSlash != UINT32_MAX) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: relative name not permitted: %.*ls\n", + cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_OBJECT_NAME_INVALID; + } + + /* + * Perform dll redirection to WinSxS such. We using an undocumented + * API here, which as always is a bit risky... ASSUMES that the API + * returns a full DOS path. + */ + UINT cwc; + rcNtResolve = RtlDosApplyFileIsolationRedirection_Ustr(1 /*fFlags*/, + pName, + (PUNICODE_STRING)&s_DefaultSuffix, + &UniStrStatic, + &UniStrDynamic, + &pUniStrResult, + NULL /*pNewFlags*/, + NULL /*pcbFilename*/, + NULL /*pcbNeeded*/); + if (NT_SUCCESS(rcNtResolve)) + { + rcNt = supR3HardenedCopyRedirectionResult(wszPath, RT_ELEMENTS(wszPath), pUniStrResult, pName, &cwc); + RtlFreeUnicodeString(&UniStrDynamic); + if (!NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", rcNt)); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + } + else + { + /* + * Search for the DLL. Only System32 is allowed as the target of + * a search on the API level, all VBox calls will have full paths. + * If the DLL is not in System32, we will resort to check if it's + * refering to an already loaded DLL (fCheckIfLoaded). + */ + AssertCompile(sizeof(g_System32WinPath.awcBuffer) <= sizeof(wszPath)); + cwc = g_System32WinPath.UniStr.Length / sizeof(RTUTF16); Assert(cwc > 2); + if (cwc + 1 + cwcOrgName + fNeedDllSuffix * 4 >= RT_ELEMENTS(wszPath)) + { + supR3HardenedError(VINF_SUCCESS, false, + "supR3HardenedMonitor_LdrLoadDll: Name too long (system32): %.*ls\n", cwcOrgName, pawcOrgName); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_NAME_TOO_LONG)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_NAME_TOO_LONG; + } + memcpy(wszPath, g_System32WinPath.UniStr.Buffer, cwc * sizeof(RTUTF16)); + wszPath[cwc++] = '\\'; + memcpy(&wszPath[cwc], pawcOrgName, cwcOrgName * sizeof(WCHAR)); + cwc += cwcOrgName; + if (!fNeedDllSuffix) + wszPath[cwc] = '\0'; + else + { + memcpy(&wszPath[cwc], L".dll", 5 * sizeof(WCHAR)); + cwc += 4; + } + fCheckIfLoaded = true; + } + + ResolvedName.Buffer = wszPath; + ResolvedName.Length = (USHORT)(cwc * sizeof(WCHAR)); + ResolvedName.MaximumLength = ResolvedName.Length + sizeof(WCHAR); + pName = &ResolvedName; + } + +#ifndef IN_SUP_R3_STATIC + /* + * Reject blacklisted DLLs based on input name. + */ + for (unsigned i = 0; g_aSupNtViBlacklistedDlls[i].psz != NULL; i++) + if (supR3HardenedIsFilenameMatchDll(pName, g_aSupNtViBlacklistedDlls[i].psz, g_aSupNtViBlacklistedDlls[i].cch)) + { + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: Refusing to load blacklisted DLL: '%.*ls'\n", + pName->Length / sizeof(RTUTF16), pName->Buffer)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_TOO_MANY_THREADS; + } +#endif + + bool fQuiet = false; + if (!fSkipValidation) + { + /* + * Try open the file. If this fails, never mind, just pass it on to + * the real API as we've replaced any searchable name with a full name + * and the real API can come up with a fitting status code for it. + */ + HANDLE hRootDir; + UNICODE_STRING NtPathUniStr; + int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, wszPath, RTSTR_MAX); + if (RT_FAILURE(rc)) + { + supR3HardenedError(rc, false, + "supR3HardenedMonitor_LdrLoadDll: RTNtPathFromWinUtf16Ex failed on '%ls': %Rrc\n", wszPath, rc); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x\n", STATUS_OBJECT_NAME_INVALID)); + RtlRestoreLastWin32Error(dwSavedLastError); + return STATUS_OBJECT_NAME_INVALID; + } + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/); + + rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, RT_VALID_PTR(pfFlags) && (*pfFlags & 0x2) /*fIgnoreArch*/, + &fAccess, &fProtect, &fCallRealApi, + "LdrLoadDll", false /*fAvoidWinVerifyTrust*/, &fQuiet); + NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + if (!fQuiet) + { + if (pOrgName != pName) + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls': rcNt=%#x\n", + wszPath, rcNt); + else + supR3HardenedError(VINF_SUCCESS, false, "supR3HardenedMonitor_LdrLoadDll: rejecting '%ls' (%.*ls): rcNt=%#x\n", + wszPath, pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNt); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + } + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNt; + } + + supR3HardenedWinVerifyCacheProcessImportTodos(); + } + else + { + DWORD dwErr = RtlGetLastWin32Error(); + + /* + * Deal with special case where the caller (first case was MS LifeCam) + * is using LoadLibrary instead of GetModuleHandle to find a loaded DLL. + */ + NTSTATUS rcNtGetDll = STATUS_SUCCESS; + if ( fCheckIfLoaded + && ( rcNt == STATUS_OBJECT_NAME_NOT_FOUND + || rcNt == STATUS_OBJECT_PATH_NOT_FOUND)) + { + rcNtGetDll = LdrGetDllHandle(NULL /*DllPath*/, NULL /*pfFlags*/, pOrgName, phMod); + if (NT_SUCCESS(rcNtGetDll)) + { + RTNtPathFree(&NtPathUniStr, &hRootDir); + RtlRestoreLastWin32Error(dwSavedLastError); + return rcNtGetDll; + } + } + + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: error opening '%ls': %u (NtPath=%.*ls; Input=%.*ls; rcNtGetDll=%#x\n", + wszPath, dwErr, NtPathUniStr.Length / sizeof(RTUTF16), NtPathUniStr.Buffer, + pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtGetDll)); + + RTNtPathFree(&NtPathUniStr, &hRootDir); + RtlRestoreLastWin32Error(dwSavedLastError); + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + return rcNt; + } + RTNtPathFree(&NtPathUniStr, &hRootDir); + } + + /* + * Screened successfully enough. Call the real thing. + */ + if (!fQuiet) + { + if (pOrgName != pName) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (Input=%.*ls, rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, + (unsigned)pOrgName->Length / sizeof(WCHAR), pOrgName->Buffer, rcNtResolve, + pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>")); + else + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: pName=%.*ls (rcNtResolve=%#x) *pfFlags=%#x pwszSearchPath=%p:%ls [calling]\n", + (unsigned)pName->Length / sizeof(WCHAR), pName->Buffer, rcNtResolve, + pfFlags ? *pfFlags : UINT32_MAX, pwszSearchPath, + !((uintptr_t)pwszSearchPath & 1) && (uintptr_t)pwszSearchPath >= 0x2000U ? pwszSearchPath : L"<flags>")); + } + + RtlRestoreLastWin32Error(dwSavedLastError); + rcNt = g_pfnLdrLoadDllReal(pwszSearchPath, pfFlags, pName, phMod); + + /* + * Log the result and process pending WinVerifyTrust work if we can. + */ + dwSavedLastError = RtlGetLastWin32Error(); + + if (NT_SUCCESS(rcNt) && phMod) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x hMod=%p '%ls'\n", rcNt, *phMod, wszPath)); + else if (!NT_SUCCESS(rcNt) || !fQuiet) + SUP_DPRINTF(("supR3HardenedMonitor_LdrLoadDll: returns rcNt=%#x '%ls'\n", rcNt, wszPath)); + + supR3HardenedWinVerifyCacheProcessWvtTodos(); + + RtlRestoreLastWin32Error(dwSavedLastError); + + return rcNt; +} + + +/** + * DLL load and unload notification callback. + * + * This is a safety against our LdrLoadDll hook being replaced by protection + * software. Though, we prefer the LdrLoadDll hook to this one as it allows us + * to call WinVerifyTrust more freely. + * + * @param ulReason The reason we're called, see + * LDR_DLL_NOTIFICATION_REASON_XXX. + * @param pData Reason specific data. (Format is currently the same for + * both load and unload.) + * @param pvUser User parameter (ignored). + * + * @remarks Vista and later. + * @remarks The loader lock is held when we're called, at least on Windows 7. + */ +static VOID CALLBACK +supR3HardenedDllNotificationCallback(ULONG ulReason, PCLDR_DLL_NOTIFICATION_DATA pData, PVOID pvUser) RT_NOTHROW_DEF +{ + NOREF(pvUser); + + /* + * Screen the image on load. We will normally get a verification cache + * hit here because of the LdrLoadDll and NtCreateSection hooks, so it + * should be relatively cheap to recheck. In case our NtDll patches + * got re + * + * This ASSUMES that we get informed after the fact as indicated by the + * available documentation. + */ + if (ulReason == LDR_DLL_NOTIFICATION_REASON_LOADED) + { + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: load %p LB %#010x %.*ls [fFlags=%#x]\n", + pData->Loaded.DllBase, pData->Loaded.SizeOfImage, + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + pData->Loaded.Flags)); + + /* Convert the windows path to an NT path and open it. */ + HANDLE hRootDir; + UNICODE_STRING NtPathUniStr; + int rc = RTNtPathFromWinUtf16Ex(&NtPathUniStr, &hRootDir, pData->Loaded.FullDllName->Buffer, + pData->Loaded.FullDllName->Length / sizeof(WCHAR)); + if (RT_FAILURE(rc)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: RTNtPathFromWinUtf16Ex failed on '%.*ls': %Rrc\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, rc); + return; + } + + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtPathUniStr, OBJ_CASE_INSENSITIVE, hRootDir, NULL /*pSecDesc*/); + + NTSTATUS rcNt = NtCreateFile(&hFile, + FILE_READ_DATA | READ_CONTROL | 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)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: NtCreateFile failed on '%.*ls' / '%.*ls': %#x\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt); + /* not reached */ + } + + /* Do the screening. */ + ULONG fAccess = 0; + ULONG fProtect = 0; + bool fCallRealApi = false; + bool fQuietFailure = false; + rcNt = supR3HardenedScreenImage(hFile, true /*fImage*/, true /*fIgnoreArch*/, &fAccess, &fProtect, &fCallRealApi, + "LdrLoadDll", true /*fAvoidWinVerifyTrust*/, &fQuietFailure); + NtClose(hFile); + if (!NT_SUCCESS(rcNt)) + { + supR3HardenedFatal("supR3HardenedDllNotificationCallback: supR3HardenedScreenImage failed on '%.*ls' / '%.*ls': %#x\n", + pData->Loaded.FullDllName->Length / sizeof(WCHAR), pData->Loaded.FullDllName->Buffer, + NtPathUniStr.Length / sizeof(WCHAR), NtPathUniStr.Buffer, rcNt); + /* not reached */ + } + RTNtPathFree(&NtPathUniStr, &hRootDir); + } + /* + * Log the unload call. + */ + else if (ulReason == LDR_DLL_NOTIFICATION_REASON_UNLOADED) + { + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: Unload %p LB %#010x %.*ls [flags=%#x]\n", + pData->Unloaded.DllBase, pData->Unloaded.SizeOfImage, + pData->Unloaded.FullDllName->Length / sizeof(WCHAR), pData->Unloaded.FullDllName->Buffer, + pData->Unloaded.Flags)); + } + /* + * Just log things we don't know and then return without caching anything. + */ + else + { + static uint32_t s_cLogEntries = 0; + if (s_cLogEntries++ < 32) + SUP_DPRINTF(("supR3HardenedDllNotificationCallback: ulReason=%u pData=%p\n", ulReason, pData)); + return; + } + + /* + * Use this opportunity to make sure our NtDll patches are still in place, + * since they may be replaced by indecent protection software solutions. + */ + supR3HardenedWinReInstallHooks(false /*fFirstCall */); +} + + +/** + * Registers the DLL notification callback if it hasn't already been registered. + */ +static void supR3HardenedWinRegisterDllNotificationCallback(void) +{ + /* + * The notification API was added in Vista, so it's an optional (weak) import. + */ + if ( LdrRegisterDllNotification != NULL + && g_cDllNotificationRegistered <= 0 + && g_cDllNotificationRegistered > -32) + { + NTSTATUS rcNt = LdrRegisterDllNotification(0, supR3HardenedDllNotificationCallback, NULL, &g_pvDllNotificationCookie); + if (NT_SUCCESS(rcNt)) + { + SUP_DPRINTF(("Registered Dll notification callback with NTDLL.\n")); + g_cDllNotificationRegistered = 1; + } + else + { + supR3HardenedError(rcNt, false /*fFatal*/, "LdrRegisterDllNotification failed: %#x\n", rcNt); + g_cDllNotificationRegistered--; + } + } +} + + +/** + * Dummy replacement routine we use for passifying unwanted user APC + * callbacks during early process initialization. + * + * @sa supR3HardenedMonitor_KiUserApcDispatcher_C + */ +static VOID NTAPI supR3HardenedWinDummyApcRoutine(PVOID pvArg1, PVOID pvArg2, PVOID pvArg3) +{ + SUP_DPRINTF(("supR3HardenedWinDummyApcRoutine: pvArg1=%p pvArg2=%p pvArg3=%p\n", pvArg1, pvArg2, pvArg3)); + RT_NOREF(pvArg1, pvArg2, pvArg3); +} + + +/** + * This is called when ntdll!KiUserApcDispatcher is invoked (via + * supR3HardenedMonitor_KiUserApcDispatcher). + * + * The parent process hooks KiUserApcDispatcher before the guest starts + * executing. There should only be one APC request dispatched while the process + * is being initialized, and that's the one calling ntdll!LdrInitializeThunk. + * + * @returns Where to go to run the original code. + * @param pvApcArgs The APC dispatcher arguments. + */ +DECLASM(uintptr_t) supR3HardenedMonitor_KiUserApcDispatcher_C(void *pvApcArgs) +{ +#ifdef RT_ARCH_AMD64 + PCONTEXT pCtx = (PCONTEXT)pvApcArgs; + uintptr_t *ppfnRoutine = (uintptr_t *)&pCtx->P4Home; +#else + struct X86APCCTX + { + uintptr_t pfnRoutine; + uintptr_t pvCtx; + uintptr_t pvUser1; + uintptr_t pvUser2; + CONTEXT Ctx; + } *pCtx = (struct X86APCCTX *)pvApcArgs; + uintptr_t *ppfnRoutine = &pCtx->pfnRoutine; +#endif + uintptr_t pfnRoutine = *ppfnRoutine; + + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_HARDENED_MAIN_CALLED) + { + if (pfnRoutine == g_pfnLdrInitializeThunk) /* Note! we could use this to detect thread creation too. */ + SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d - okay\n", + pfnRoutine, g_enmSupR3HardenedMainState)); + else + { + *ppfnRoutine = (uintptr_t)supR3HardenedWinDummyApcRoutine; + SUP_DPRINTF(("supR3HardenedMonitor_KiUserApcDispatcher_C: pfnRoutine=%p enmState=%d -> supR3HardenedWinDummyApcRoutine\n", + pfnRoutine, g_enmSupR3HardenedMainState)); + } + } + return (uintptr_t)g_pfnKiUserApcDispatcherReal; +} + + +/** + * SUP_DPRINTF on pCtx, with lead-in text. + */ +static void supR3HardNtDprintCtx(PCONTEXT pCtx, const char *pszLeadIn) +{ +#ifdef RT_ARCH_AMD64 + SUP_DPRINTF(("%s\n" + " rax=%016RX64 rbx=%016RX64 rcx=%016RX64 rdx=%016RX64\n" + " rsi=%016RX64 rdi=%016RX64 r8 =%016RX64 r9 =%016RX64\n" + " r10=%016RX64 r11=%016RX64 r12=%016RX64 r13=%016RX64\n" + " r14=%016RX64 r15=%016RX64 P1=%016RX64 P2=%016RX64\n" + " rip=%016RX64 rsp=%016RX64 rbp=%016RX64 ctxflags=%08x\n" + " cs=%04x ss=%04x ds=%04x es=%04x fs=%04x gs=%04x eflags=%08x mxcrx=%08x\n" + " P3=%016RX64 P4=%016RX64 P5=%016RX64 P6=%016RX64\n" + " dr0=%016RX64 dr1=%016RX64 dr2=%016RX64 dr3=%016RX64\n" + " dr6=%016RX64 dr7=%016RX64 vcr=%016RX64 dcr=%016RX64\n" + " lbt=%016RX64 lbf=%016RX64 lxt=%016RX64 lxf=%016RX64\n" + , + pszLeadIn, + pCtx->Rax, pCtx->Rbx, pCtx->Rcx, pCtx->Rdx, + pCtx->Rsi, pCtx->Rdi, pCtx->R8, pCtx->R9, + pCtx->R10, pCtx->R11, pCtx->R12, pCtx->R13, + pCtx->R14, pCtx->R15, pCtx->P1Home, pCtx->P2Home, + pCtx->Rip, pCtx->Rsp, pCtx->Rbp, pCtx->ContextFlags, + pCtx->SegCs, pCtx->SegSs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, pCtx->EFlags, pCtx->MxCsr, + pCtx->P3Home, pCtx->P4Home, pCtx->P5Home, pCtx->P6Home, + pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, + pCtx->Dr6, pCtx->Dr7, pCtx->VectorControl, pCtx->DebugControl, + pCtx->LastBranchToRip, pCtx->LastBranchFromRip, pCtx->LastExceptionToRip, pCtx->LastExceptionFromRip )); +#elif defined(RT_ARCH_X86) + SUP_DPRINTF(("%s\n" + " eax=%08RX32 ebx=%08RX32 ecx=%08RX32 edx=%08RX32 esi=%08rx64 edi=%08RX32\n" + " eip=%08RX32 esp=%08RX32 ebp=%08RX32 eflags=%08RX32\n" + " cs=%04RX16 ds=%04RX16 es=%04RX16 fs=%04RX16 gs=%04RX16\n" + " dr0=%08RX32 dr1=%08RX32 dr2=%08RX32 dr3=%08RX32 dr6=%08RX32 dr7=%08RX32\n", + pszLeadIn, + pCtx->Eax, pCtx->Ebx, pCtx->Ecx, pCtx->Edx, pCtx->Esi, pCtx->Edi, + pCtx->Eip, pCtx->Esp, pCtx->Ebp, pCtx->EFlags, + pCtx->SegCs, pCtx->SegDs, pCtx->SegEs, pCtx->SegFs, pCtx->SegGs, + pCtx->Dr0, pCtx->Dr1, pCtx->Dr2, pCtx->Dr3, pCtx->Dr6, pCtx->Dr7)); +#else +# error "Unsupported arch." +#endif + RT_NOREF(pCtx, pszLeadIn); +} + + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +/** + * This is called when ntdll!KiUserExceptionDispatcher is invoked (via + * supR3HardenedMonitor_KiUserExceptionDispatcher). + * + * For 64-bit processes there is a return and two parameters on the stack. + * + * @returns Where to go to run the original code. + * @param pXcptRec The exception record. + * @param pCtx The exception context. + */ +DECLASM(uintptr_t) supR3HardenedMonitor_KiUserExceptionDispatcher_C(PEXCEPTION_RECORD pXcptRec, PCONTEXT pCtx) +{ + /* + * Ignore the guard page violation. + */ + if (pXcptRec->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) + return (uintptr_t)g_pfnKiUserExceptionDispatcherReal; + + /* + * Log the exception and context. + */ + char szLeadIn[384]; + if (pXcptRec->NumberParameters == 0) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 1) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 2) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else if (pXcptRec->NumberParameters == 3) + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (%p, %p, %p) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + else + RTStrPrintf(szLeadIn, sizeof(szLeadIn), "KiUserExceptionDispatcher: %#x (#%u: %p, %p, %p, %p, %p, %p, %p, %p, ...) @ %p (flags=%#x)", + pXcptRec->ExceptionCode, pXcptRec->NumberParameters, + pXcptRec->ExceptionInformation[0], pXcptRec->ExceptionInformation[1], + pXcptRec->ExceptionInformation[2], pXcptRec->ExceptionInformation[3], + pXcptRec->ExceptionInformation[4], pXcptRec->ExceptionInformation[5], + pXcptRec->ExceptionInformation[6], pXcptRec->ExceptionInformation[7], + pXcptRec->ExceptionAddress, pXcptRec->ExceptionFlags); + supR3HardNtDprintCtx(pCtx, szLeadIn); + + return (uintptr_t)g_pfnKiUserExceptionDispatcherReal; +} +#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */ + + +static void supR3HardenedWinHookFailed(const char *pszWhich, uint8_t const *pbPrologue) +{ + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_NO_MEMORY, + "Failed to install %s 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 + , + pszWhich, + pbPrologue[0], pbPrologue[1], pbPrologue[2], pbPrologue[3], + pbPrologue[4], pbPrologue[5], pbPrologue[6], pbPrologue[7], + pbPrologue[8], pbPrologue[9], pbPrologue[10], pbPrologue[11], + pbPrologue[12], pbPrologue[13], pbPrologue[14], pbPrologue[15]); +} + + +/** + * IPRT thread that waits for the parent process to terminate and reacts by + * exiting the current process. + * + * @returns VINF_SUCCESS + * @param hSelf The current thread. Ignored. + * @param pvUser The handle of the parent process. + */ +static DECLCALLBACK(int) supR3HardenedWinParentWatcherThread(RTTHREAD hSelf, void *pvUser) +{ + HANDLE hProcWait = (HANDLE)pvUser; + NOREF(hSelf); + + /* + * Wait for the parent to terminate. + */ + NTSTATUS rcNt; + for (;;) + { + rcNt = NtWaitForSingleObject(hProcWait, TRUE /*Alertable*/, NULL /*pTimeout*/); + if ( rcNt == STATUS_WAIT_0 + || rcNt == STATUS_ABANDONED_WAIT_0) + break; + if ( rcNt != STATUS_TIMEOUT + && rcNt != STATUS_USER_APC + && rcNt != STATUS_ALERTED) + supR3HardenedFatal("NtWaitForSingleObject returned %#x\n", rcNt); + } + + /* + * Proxy the termination code of the child, if it exited already. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt2 = NtQueryInformationProcess(hProcWait, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + if ( !NT_SUCCESS(rcNt2) + || BasicInfo.ExitStatus == STATUS_PENDING) + BasicInfo.ExitStatus = RTEXITCODE_FAILURE; + + NtClose(hProcWait); + SUP_DPRINTF(("supR3HardenedWinParentWatcherThread: Quitting: ExitCode=%#x rcNt=%#x\n", BasicInfo.ExitStatus, rcNt)); + suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus); + /* not reached */ +} + + +/** + * Creates the parent watcher thread that will make sure this process exits when + * the parent does. + * + * This is a necessary evil to make VBoxNetDhcp and VBoxNetNat termination from + * Main work without too much new magic. It also makes Ctrl-C or similar work + * in on the hardened processes in the windows console. + * + * @param hVBoxRT The VBoxRT.dll handle. We use RTThreadCreate to + * spawn the thread to avoid duplicating thread + * creation and thread naming code from IPRT. + */ +DECLHIDDEN(void) supR3HardenedWinCreateParentWatcherThread(HMODULE hVBoxRT) +{ + /* + * Resolve runtime methods that we need. + */ + PFNRTTHREADCREATE pfnRTThreadCreate = (PFNRTTHREADCREATE)GetProcAddress(hVBoxRT, "RTThreadCreate"); + SUPR3HARDENED_ASSERT(pfnRTThreadCreate != NULL); + + /* + * Find the parent process ID. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: NtQueryInformationProcess failed: %#x\n", rcNt); + + /* + * Open the parent process for waiting and exitcode query. + */ + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)BasicInfo.InheritedFromUniqueProcessId; + ClientId.UniqueThread = NULL; + + HANDLE hParent; + rcNt = NtOpenProcess(&hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinCreateParentWatcherThread", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtOpenProcess(%p.0) failed: %#x\n", ClientId.UniqueProcess, rcNt); + + /* + * Create the thread that should do the waiting. + */ + int rc = pfnRTThreadCreate(NULL, supR3HardenedWinParentWatcherThread, hParent, _64K /* stack */, + RTTHREADTYPE_DEFAULT, 0 /*fFlags*/, "ParentWatcher"); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedWinCreateParentWatcherThread: RTThreadCreate failed: %Rrc\n", rc); +} + + +/** + * Checks if the calling thread is the only one in the process. + * + * @returns true if we're positive we're alone, false if not. + */ +static bool supR3HardenedWinAmIAlone(void) RT_NOTHROW_DEF +{ + ULONG fAmIAlone = 0; + ULONG cbIgn = 0; + NTSTATUS rcNt = NtQueryInformationThread(NtCurrentThread(), ThreadAmILastThread, &fAmIAlone, sizeof(fAmIAlone), &cbIgn); + Assert(NT_SUCCESS(rcNt)); + return NT_SUCCESS(rcNt) && fAmIAlone != 0; +} + + +/** + * Simplify NtProtectVirtualMemory interface. + * + * Modifies protection for the current process. Caller must know the current + * protection as it's not returned. + * + * @returns NT status code. + * @param pvMem The memory to change protection for. + * @param cbMem The amount of memory to change. + * @param fNewProt The new protection. + */ +static NTSTATUS supR3HardenedWinProtectMemory(PVOID pvMem, SIZE_T cbMem, ULONG fNewProt) RT_NOTHROW_DEF +{ + ULONG fOldProt = 0; + return NtProtectVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, fNewProt, &fOldProt); +} + + +/** + * Installs or reinstalls the NTDLL patches. + */ +static void supR3HardenedWinReInstallHooks(bool fFirstCall) RT_NOTHROW_DEF +{ + struct + { + size_t cbPatch; + uint8_t const *pabPatch; + uint8_t **ppbApi; + const char *pszName; + } const s_aPatches[] = + { + { sizeof(g_abNtCreateSectionPatch), g_abNtCreateSectionPatch, &g_pbNtCreateSection, "NtCreateSection" }, + { sizeof(g_abLdrLoadDllPatch), g_abLdrLoadDllPatch, &g_pbLdrLoadDll, "LdrLoadDll" }, + { sizeof(g_abKiUserApcDispatcherPatch), g_abKiUserApcDispatcherPatch, &g_pbKiUserApcDispatcher, "KiUserApcDispatcher" }, +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + { sizeof(g_abKiUserExceptionDispatcherPatch), g_abKiUserExceptionDispatcherPatch, &g_pbKiUserExceptionDispatcher, "KiUserExceptionDispatcher" }, +#endif + }; + + ULONG fAmIAlone = ~(ULONG)0; + + for (uint32_t i = 0; i < RT_ELEMENTS(s_aPatches); i++) + { + uint8_t *pbApi = *s_aPatches[i].ppbApi; + if (memcmp(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch) != 0) + { + /* + * Log the incident if it's not the initial call. + */ + static uint32_t volatile s_cTimes = 0; + if (!fFirstCall && s_cTimes < 128) + { + s_cTimes++; + SUP_DPRINTF(("supR3HardenedWinReInstallHooks: Reinstalling %s (%p: %.*Rhxs).\n", + s_aPatches[i].pszName, pbApi, s_aPatches[i].cbPatch, pbApi)); + } + + Assert(s_aPatches[i].cbPatch >= 4); + + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READWRITE)); + + /* + * If we're alone, just memcpy the patch in. + */ + + if (fAmIAlone == ~(ULONG)0) + fAmIAlone = supR3HardenedWinAmIAlone(); + if (fAmIAlone) + memcpy(pbApi, s_aPatches[i].pabPatch, s_aPatches[i].cbPatch); + else + { + /* + * Not alone. Start by injecting a JMP $-2, then waste some + * CPU cycles to get the other threads a good chance of getting + * out of the code before we replace it. + */ + RTUINT32U uJmpDollarMinus; + uJmpDollarMinus.au8[0] = 0xeb; + uJmpDollarMinus.au8[1] = 0xfe; + uJmpDollarMinus.au8[2] = pbApi[2]; + uJmpDollarMinus.au8[3] = pbApi[3]; + ASMAtomicXchgU32((uint32_t volatile *)pbApi, uJmpDollarMinus.u); + + NtYieldExecution(); + NtYieldExecution(); + + /* Copy in the tail bytes of the patch, then xchg the jmp $-2. */ + if (s_aPatches[i].cbPatch > 4) + memcpy(&pbApi[4], &s_aPatches[i].pabPatch[4], s_aPatches[i].cbPatch - 4); + ASMAtomicXchgU32((uint32_t volatile *)pbApi, *(uint32_t *)s_aPatches[i].pabPatch); + } + + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pbApi, s_aPatches[i].cbPatch, PAGE_EXECUTE_READ)); + } + } +} + + +/** + * 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. + * + * @remarks We would normally just copy the prologue sequence somewhere and add + * a jump back at the end of it. But because we wish to avoid + * allocating executable memory, we need to have preprepared assembly + * "copies". This makes the non-system call patching a little tedious + * and inflexible. + */ +static void supR3HardenedWinInstallHooks(void) +{ + NTSTATUS rcNt; + + /* + * Disable hard error popups so we can quietly refuse images to be loaded. + */ + ULONG fHardErr = 0; + rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr), NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtQueryInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt); + if (fHardErr & PROCESS_HARDERR_CRITICAL_ERROR) + { + fHardErr &= ~PROCESS_HARDERR_CRITICAL_ERROR; + rcNt = NtSetInformationProcess(NtCurrentProcess(), ProcessDefaultHardErrorMode, &fHardErr, sizeof(fHardErr)); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInstallHooks", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtSetInformationProcess/ProcessDefaultHardErrorMode failed: %#x\n", rcNt); + } + + /* + * Locate the routines first so we can allocate memory that's near enough. + */ + PFNRT pfnNtCreateSection = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtCreateSection"); + SUPR3HARDENED_ASSERT(pfnNtCreateSection != NULL); + //SUPR3HARDENED_ASSERT(pfnNtCreateSection == (FARPROC)NtCreateSection); + + PFNRT pfnLdrLoadDll = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrLoadDll"); + SUPR3HARDENED_ASSERT(pfnLdrLoadDll != NULL); + //SUPR3HARDENED_ASSERT(pfnLdrLoadDll == (FARPROC)LdrLoadDll); + + PFNRT pfnKiUserApcDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserApcDispatcher"); + SUPR3HARDENED_ASSERT(pfnKiUserApcDispatcher != NULL); + g_pfnLdrInitializeThunk = (uintptr_t)supR3HardenedWinGetRealDllSymbol("ntdll.dll", "LdrInitializeThunk"); + SUPR3HARDENED_ASSERT(g_pfnLdrInitializeThunk != NULL); + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + PFNRT pfnKiUserExceptionDispatcher = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "KiUserExceptionDispatcher"); + SUPR3HARDENED_ASSERT(pfnKiUserExceptionDispatcher != NULL); +#endif + + /* + * Exec page setup & management. + */ + uint32_t offExecPage = 0; + memset(g_abSupHardReadWriteExecPage, 0xcc, PAGE_SIZE); + + /* + * Hook #1 - NtCreateSection. + * Purpose: Validate everything that can be mapped into the process before + * it's mapped and we still have a file handle to work with. + */ + uint8_t * const pbNtCreateSection = (uint8_t *)(uintptr_t)pfnNtCreateSection; + g_pbNtCreateSection = pbNtCreateSection; + memcpy(g_abNtCreateSectionPatch, pbNtCreateSection, sizeof(g_abNtCreateSectionPatch)); + + g_pfnNtCreateSectionReal = NtCreateSection; /* our direct syscall */ + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* 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 */ + + /* Assemble the patch. */ + g_abNtCreateSectionPatch[0] = 0x48; /* mov rax, qword */ + g_abNtCreateSectionPatch[1] = 0xb8; + *(uint64_t *)&g_abNtCreateSectionPatch[2] = (uint64_t)supR3HardenedMonitor_NtCreateSection; + g_abNtCreateSectionPatch[10] = 0xff; /* jmp rax */ + g_abNtCreateSectionPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* 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 */ + + /* Assemble the patch. */ + g_abNtCreateSectionPatch[0] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abNtCreateSectionPatch[1] = (uintptr_t)supR3HardenedMonitor_NtCreateSection + - (uintptr_t)&pbNtCreateSection[1+4]; + +#endif + + /* + * Hook #2 - LdrLoadDll + * Purpose: (a) Enforce LdrLoadDll search path constraints, and (b) pre-validate + * DLLs so we can avoid calling WinVerifyTrust from the first hook, + * and thus avoiding messing up the loader data on some installations. + * + * This differs from the above function in that is no a system call and + * we're at the mercy of the compiler. + */ + uint8_t * const pbLdrLoadDll = (uint8_t *)(uintptr_t)pfnLdrLoadDll; + g_pbLdrLoadDll = pbLdrLoadDll; + memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch)); + + DISSTATE Dis; + uint32_t cbInstr; + uint32_t offJmpBack = 0; + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* Just use the disassembler to skip 12 bytes or more. */ + while (offJmpBack < 12) + { + cbInstr = 1; + int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) + || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) ) + supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */ + g_abSupHardReadWriteExecPage[offExecPage++] = 0x25; + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4); + offExecPage = RT_ALIGN_32(offExecPage + 4, 8); + *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack]; + offExecPage = RT_ALIGN_32(offExecPage + 8, 16); + + /* Assemble the LdrLoadDll patch. */ + Assert(offJmpBack >= 12); + g_abLdrLoadDllPatch[0] = 0x48; /* mov rax, qword */ + g_abLdrLoadDllPatch[1] = 0xb8; + *(uint64_t *)&g_abLdrLoadDllPatch[2] = (uint64_t)supR3HardenedMonitor_LdrLoadDll; + g_abLdrLoadDllPatch[10] = 0xff; /* jmp rax */ + g_abLdrLoadDllPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbLdrLoadDll + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + supR3HardenedWinHookFailed("LdrLoadDll", pbLdrLoadDll); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnLdrLoadDllReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbLdrLoadDll, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbLdrLoadDll[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the LdrLoadDll patch. */ + memcpy(g_abLdrLoadDllPatch, pbLdrLoadDll, sizeof(g_abLdrLoadDllPatch)); + Assert(offJmpBack >= 5); + g_abLdrLoadDllPatch[0] = 0xe9; + *(uint32_t *)&g_abLdrLoadDllPatch[1] = (uintptr_t)supR3HardenedMonitor_LdrLoadDll - (uintptr_t)&pbLdrLoadDll[1+4]; +#endif + + /* + * Hook #3 - KiUserApcDispatcher + * Purpose: Prevent user APC to memory we (or our parent) has freed from + * crashing the process. Also ensures no code injection via user + * APC during process init given the way we're vetting the APCs. + * + * This differs from the first function in that is no a system call and + * we're at the mercy of the handwritten assembly. + * + * Note! We depend on all waits up past the patching to be non-altertable, + * otherwise an APC might slip by us. + */ + uint8_t * const pbKiUserApcDispatcher = (uint8_t *)(uintptr_t)pfnKiUserApcDispatcher; + g_pbKiUserApcDispatcher = pbKiUserApcDispatcher; + memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch)); + +#ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + */ + /* Just use the disassembler to skip 12 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 12) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_64BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) + || (Dis.ModRM.Bits.Mod == 0 && Dis.ModRM.Bits.Rm == 5 /* wrt RIP */) ) + supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xff; /* jmp qword [$+8 wrt RIP] */ + g_abSupHardReadWriteExecPage[offExecPage++] = 0x25; + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = RT_ALIGN_32(offExecPage + 4, 8) - (offExecPage + 4); + offExecPage = RT_ALIGN_32(offExecPage + 4, 8); + *(uint64_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack]; + offExecPage = RT_ALIGN_32(offExecPage + 8, 16); + + /* Assemble the KiUserApcDispatcher patch. */ + Assert(offJmpBack >= 12); + g_abKiUserApcDispatcherPatch[0] = 0x48; /* mov rax, qword */ + g_abKiUserApcDispatcherPatch[1] = 0xb8; + *(uint64_t *)&g_abKiUserApcDispatcherPatch[2] = (uint64_t)supR3HardenedMonitor_KiUserApcDispatcher; + g_abKiUserApcDispatcherPatch[10] = 0xff; /* jmp rax */ + g_abKiUserApcDispatcherPatch[11] = 0xe0; + +#else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserApcDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + supR3HardenedWinHookFailed("KiUserApcDispatcher", pbKiUserApcDispatcher); + offJmpBack += cbInstr; + } + + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserApcDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserApcDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserApcDispatcher[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the KiUserApcDispatcher patch. */ + memcpy(g_abKiUserApcDispatcherPatch, pbKiUserApcDispatcher, sizeof(g_abKiUserApcDispatcherPatch)); + Assert(offJmpBack >= 5); + g_abKiUserApcDispatcherPatch[0] = 0xe9; + *(uint32_t *)&g_abKiUserApcDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserApcDispatcher - (uintptr_t)&pbKiUserApcDispatcher[1+4]; +#endif + +#ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + /* + * Hook #4 - KiUserExceptionDispatcher + * Purpose: Logging crashes. + * + * This differs from the first function in that is no a system call and + * we're at the mercy of the handwritten assembly. This is not mandatory, + * so we ignore failures here. + */ + uint8_t * const pbKiUserExceptionDispatcher = (uint8_t *)(uintptr_t)pfnKiUserExceptionDispatcher; + g_pbKiUserExceptionDispatcher = pbKiUserExceptionDispatcher; + memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch)); + +# ifdef RT_ARCH_AMD64 + /* + * Patch 64-bit hosts. + * + * Assume the following sequence and replacing the loaded Wow64PrepareForException + * function pointer with our callback: + * cld + * mov rax, Wow64PrepareForException ; Wow64PrepareForException(PCONTEXT, PEXCEPTION_RECORD) + * test rax, rax + * jz skip_wow64_callout + * <do_callout_thru_rax> + * (We're not a WOW64 process, so the callout should normally never happen.) + */ + if ( pbKiUserExceptionDispatcher[ 0] == 0xfc /* CLD */ + && pbKiUserExceptionDispatcher[ 1] == 0x48 /* MOV RAX, symbol wrt rip */ + && pbKiUserExceptionDispatcher[ 2] == 0x8b + && pbKiUserExceptionDispatcher[ 3] == 0x05 + && pbKiUserExceptionDispatcher[ 8] == 0x48 /* TEST RAX, RAX */ + && pbKiUserExceptionDispatcher[ 9] == 0x85 + && pbKiUserExceptionDispatcher[10] == 0xc0 + && pbKiUserExceptionDispatcher[11] == 0x74) + { + /* Assemble the KiUserExceptionDispatcher patch. */ + g_abKiUserExceptionDispatcherPatch[1] = 0x48; /* MOV RAX, supR3HardenedMonitor_KiUserExceptionDispatcher */ + g_abKiUserExceptionDispatcherPatch[2] = 0xb8; + *(uint64_t *)&g_abKiUserExceptionDispatcherPatch[3] = (uint64_t)supR3HardenedMonitor_KiUserExceptionDispatcher; + g_abKiUserExceptionDispatcherPatch[11] = 0x90; /* NOP (was JZ) */ + g_abKiUserExceptionDispatcherPatch[12] = 0x90; /* NOP (was DISP8 of JZ) */ + } + else + SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (%.20Rhxs)\n", + pbKiUserExceptionDispatcher)); +# else + /* + * Patch 32-bit hosts. + */ + /* Just use the disassembler to skip 5 bytes or more. */ + offJmpBack = 0; + while (offJmpBack < 5) + { + cbInstr = 1; + int rc = DISInstr(pbKiUserExceptionDispatcher + offJmpBack, DISCPUMODE_32BIT, &Dis, &cbInstr); + if ( RT_FAILURE(rc) + || (Dis.pCurInstr->fOpType & (DISOPTYPE_CONTROLFLOW)) ) + { + SUP_DPRINTF(("supR3HardenedWinInstallHooks: failed to patch KiUserExceptionDispatcher (off %#x in %.20Rhxs)\n", + offJmpBack, pbKiUserExceptionDispatcher)); + break; + } + offJmpBack += cbInstr; + } + if (offJmpBack >= 5) + { + /* Assemble the code for resuming the call.*/ + *(PFNRT *)&g_pfnKiUserExceptionDispatcherReal = (PFNRT)(uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage]; + + memcpy(&g_abSupHardReadWriteExecPage[offExecPage], pbKiUserExceptionDispatcher, offJmpBack); + offExecPage += offJmpBack; + + g_abSupHardReadWriteExecPage[offExecPage++] = 0xe9; /* jmp rel32 */ + *(uint32_t *)&g_abSupHardReadWriteExecPage[offExecPage] = (uintptr_t)&pbKiUserExceptionDispatcher[offJmpBack] + - (uintptr_t)&g_abSupHardReadWriteExecPage[offExecPage + 4]; + offExecPage = RT_ALIGN_32(offExecPage + 4, 16); + + /* Assemble the KiUserExceptionDispatcher patch. */ + memcpy(g_abKiUserExceptionDispatcherPatch, pbKiUserExceptionDispatcher, sizeof(g_abKiUserExceptionDispatcherPatch)); + Assert(offJmpBack >= 5); + g_abKiUserExceptionDispatcherPatch[0] = 0xe9; + *(uint32_t *)&g_abKiUserExceptionDispatcherPatch[1] = (uintptr_t)supR3HardenedMonitor_KiUserExceptionDispatcher - (uintptr_t)&pbKiUserExceptionDispatcher[1+4]; + } +# endif +#endif /* !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING */ + + /* + * Seal the rwx page. + */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(g_abSupHardReadWriteExecPage, PAGE_SIZE, PAGE_EXECUTE_READ)); + + /* + * Install the patches. + */ + supR3HardenedWinReInstallHooks(true /*fFirstCall*/); +} + + + + + + +/* + * + * T h r e a d c r e a t i o n c o n t r o l + * T h r e a d c r e a t i o n c o n t r o l + * T h r e a d c r e a t i o n c o n t r o l + * + */ + + +/** + * Common code used for child and parent to make new threads exit immediately. + * + * This patches the LdrInitializeThunk code to call NtTerminateThread with + * STATUS_SUCCESS instead of doing the NTDLL initialization. + * + * @returns VBox status code. + * @param hProcess The process to do this to. + * @param pvLdrInitThunk The address of the LdrInitializeThunk code to + * override. + * @param pvNtTerminateThread The address of the NtTerminateThread function in + * the NTDLL instance we're patching. (Must be +/- + * 2GB from the thunk code.) + * @param pabBackup Where to back up the original instruction bytes + * at pvLdrInitThunk. + * @param cbBackup The size of the backup area. Must be 16 bytes. + * @param pErrInfo Where to return extended error information. + * Optional. + */ +static int supR3HardNtDisableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, void *pvNtTerminateThread, + uint8_t *pabBackup, size_t cbBackup, PRTERRINFO pErrInfo) +{ + SUP_DPRINTF(("supR3HardNtDisableThreadCreation: pvLdrInitThunk=%p pvNtTerminateThread=%p\n", pvLdrInitThunk, pvNtTerminateThread)); + SUPR3HARDENED_ASSERT(cbBackup == 16); + SUPR3HARDENED_ASSERT(RT_ABS((intptr_t)pvLdrInitThunk - (intptr_t)pvNtTerminateThread) < 16*_1M); + + /* + * Back up the thunk code. + */ + SIZE_T cbIgnored; + NTSTATUS rcNt = NtReadVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreation: NtReadVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + /* + * Cook up replacement code that calls NtTerminateThread. + */ + uint8_t abReplacement[16]; + memcpy(abReplacement, pabBackup, sizeof(abReplacement)); + +#ifdef RT_ARCH_AMD64 + abReplacement[0] = 0x31; /* xor ecx, ecx */ + abReplacement[1] = 0xc9; + abReplacement[2] = 0x31; /* xor edx, edx */ + abReplacement[3] = 0xd2; + abReplacement[4] = 0xe8; /* call near NtTerminateThread */ + *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9)); + abReplacement[9] = 0xcc; /* int3 */ +#elif defined(RT_ARCH_X86) + abReplacement[0] = 0x6a; /* push 0 */ + abReplacement[1] = 0x00; + abReplacement[2] = 0x6a; /* push 0 */ + abReplacement[3] = 0x00; + abReplacement[4] = 0xe8; /* call near NtTerminateThread */ + *(int32_t *)&abReplacement[5] = (int32_t)((uintptr_t)pvNtTerminateThread - ((uintptr_t)pvLdrInitThunk + 9)); + abReplacement[9] = 0xcc; /* int3 */ +#else +# error "Unsupported arch." +#endif + + /* + * Install the replacment code. + */ + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = cbBackup; + ULONG fOldProt = 0; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, abReplacement, sizeof(abReplacement), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + pvProt = pvLdrInitThunk; + cbProt = cbBackup; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtDisableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk/2 failed: %#x", rcNt); + + return VINF_SUCCESS; +} + + +/** + * Undo the effects of supR3HardNtDisableThreadCreationEx. + * + * @returns VBox status code. + * @param hProcess The process to do this to. + * @param pvLdrInitThunk The address of the LdrInitializeThunk code to + * override. + * @param pabBackup Where to back up the original instruction bytes + * at pvLdrInitThunk. + * @param cbBackup The size of the backup area. Must be 16 bytes. + * @param pErrInfo Where to return extended error information. + * Optional. + */ +static int supR3HardNtEnableThreadCreationEx(HANDLE hProcess, void *pvLdrInitThunk, uint8_t const *pabBackup, size_t cbBackup, + PRTERRINFO pErrInfo) +{ + SUP_DPRINTF(("supR3HardNtEnableThreadCreationEx:\n")); + SUPR3HARDENED_ASSERT(cbBackup == 16); + + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = cbBackup; + ULONG fOldProt = 0; + NTSTATUS rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + SIZE_T cbIgnored; + rcNt = NtWriteVirtualMemory(hProcess, pvLdrInitThunk, pabBackup, cbBackup, &cbIgnored); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtWriteVirtualMemory/LdrInitializeThunk[restore] failed: %#x", + rcNt); + + pvProt = pvLdrInitThunk; + cbProt = cbBackup; + rcNt = NtProtectVirtualMemory(hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + return RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, + "supR3HardNtEnableThreadCreationEx: NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", + rcNt); + + return VINF_SUCCESS; +} + + +/** + * Disable thread creation for the current process. + * + * @remarks Doesn't really disables it, just makes the threads exit immediately + * without executing any real code. + */ +static void supR3HardenedWinDisableThreadCreation(void) +{ + /* Cannot use the imported NtTerminateThread as it's pointing to our own + syscall assembly code. */ + static PFNRT s_pfnNtTerminateThread = NULL; + if (s_pfnNtTerminateThread == NULL) + s_pfnNtTerminateThread = supR3HardenedWinGetRealDllSymbol("ntdll.dll", "NtTerminateThread"); + SUPR3HARDENED_ASSERT(s_pfnNtTerminateThread); + + int rc = supR3HardNtDisableThreadCreationEx(NtCurrentProcess(), + (void *)(uintptr_t)&LdrInitializeThunk, + (void *)(uintptr_t)s_pfnNtTerminateThread, + g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup), + NULL /* pErrInfo*/); + g_fSupInitThunkSelfPatched = RT_SUCCESS(rc); +} + + +/** + * Undoes the effects of supR3HardenedWinDisableThreadCreation. + */ +DECLHIDDEN(void) supR3HardenedWinEnableThreadCreation(void) +{ + if (g_fSupInitThunkSelfPatched) + { + int rc = supR3HardNtEnableThreadCreationEx(NtCurrentProcess(), + (void *)(uintptr_t)&LdrInitializeThunk, + g_abLdrInitThunkSelfBackup, sizeof(g_abLdrInitThunkSelfBackup), + RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedError(rc, true /*fFatal*/, "%s", g_ErrInfoStatic.szMsg); + g_fSupInitThunkSelfPatched = false; + } +} + + + + +/* + * + * R e s p a w n + * R e s p a w n + * R e s p a w n + * + */ + + +/** + * 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 supR3HardNtChildGetUserAndLogSids(PSID pSidUser, ULONG cbSidUser, PSID pSidLogin, ULONG cbSidLogin) +{ + HANDLE hToken; + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), 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 supR3HardNtChildInitSecAttrs(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; + 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 = supR3HardNtChildGetUserAndLogSids(&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)RTMemAllocZ(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). + * @param pString Unicode string structure to initialize to the + * command line. Optional. + * @param iWhich Which respawn we're to check for, 1 being the first + * one, and 2 the second and final. + */ +static PRTUTF16 supR3HardNtChildConstructCmdLine(PUNICODE_STRING pString, int iWhich) +{ + SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2); + + /* + * Get the command line and skip the executable name. + */ + PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine; + PCRTUTF16 pawcArgs = pCmdLineStr->Buffer; + uint32_t cwcArgs = pCmdLineStr->Length / sizeof(WCHAR); + + /* Skip leading space (shouldn't be any, but whatever). */ + while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs) ) + cwcArgs--, pawcArgs++; + SUPR3HARDENED_ASSERT(cwcArgs > 0 && *pawcArgs != '\0'); + + /* Walk to the end of it. */ + int fQuoted = false; + do + { + if (*pawcArgs == '"') + { + fQuoted = !fQuoted; + cwcArgs--; pawcArgs++; + } + else if (*pawcArgs != '\\' || (pawcArgs[1] != '\\' && pawcArgs[1] != '"')) + cwcArgs--, pawcArgs++; + else + { + unsigned cSlashes = 0; + do + { + cSlashes++; + cwcArgs--; + pawcArgs++; + } + while (cwcArgs > 0 && *pawcArgs == '\\'); + if (cwcArgs > 0 && *pawcArgs == '"' && (cSlashes & 1)) + cwcArgs--, pawcArgs++; /* odd number of slashes == escaped quote */ + } + } while (cwcArgs > 0 && (fQuoted || !suplibCommandLineIsArgSeparator(*pawcArgs))); + + /* Skip trailing spaces. */ + while (cwcArgs > 0 && suplibCommandLineIsArgSeparator(*pawcArgs)) + cwcArgs--, pawcArgs++; + + /* + * Allocate a new buffer. + */ + AssertCompile(sizeof(SUPR3_RESPAWN_1_ARG0) == sizeof(SUPR3_RESPAWN_2_ARG0)); + size_t cwcCmdLine = (sizeof(SUPR3_RESPAWN_1_ARG0) - 1) / sizeof(SUPR3_RESPAWN_1_ARG0[0]) /* Respawn exe name. */ + + !!cwcArgs + cwcArgs; /* if arguments present, add space + arguments. */ + if (cwcCmdLine * sizeof(WCHAR) >= 0xfff0) + supR3HardenedFatalMsg("supR3HardNtChildConstructCmdLine", kSupInitOp_Misc, VERR_OUT_OF_RANGE, + "Command line is too long (%u chars)!", cwcCmdLine); + + PRTUTF16 pwszCmdLine = (PRTUTF16)RTMemAlloc((cwcCmdLine + 1) * sizeof(RTUTF16)); + SUPR3HARDENED_ASSERT(pwszCmdLine != NULL); + + /* + * Construct the new command line. + */ + PRTUTF16 pwszDst = pwszCmdLine; + for (const char *pszSrc = iWhich == 1 ? SUPR3_RESPAWN_1_ARG0 : SUPR3_RESPAWN_2_ARG0; *pszSrc; pszSrc++) + *pwszDst++ = *pszSrc; + + if (cwcArgs) + { + *pwszDst++ = ' '; + suplibHardenedMemCopy(pwszDst, pawcArgs, cwcArgs * sizeof(RTUTF16)); + pwszDst += cwcArgs; + } + + *pwszDst = '\0'; + SUPR3HARDENED_ASSERT((uintptr_t)(pwszDst - pwszCmdLine) == cwcCmdLine); + + if (pString) + { + pString->Buffer = pwszCmdLine; + pString->Length = (USHORT)(cwcCmdLine * sizeof(WCHAR)); + pString->MaximumLength = pString->Length + sizeof(WCHAR); + } + return pwszCmdLine; +} + + +/** + * Terminates the child process. + * + * @param hProcess The process handle. + * @param pszWhere Who's having child rasing troubles. + * @param rc The status code to report. + * @param pszFormat The message format string. + * @param ... Message format arguments. + */ +static void supR3HardenedWinKillChild(HANDLE hProcess, const char *pszWhere, int rc, const char *pszFormat, ...) +{ + /* + * Terminate the process ASAP and display error. + */ + NtTerminateProcess(hProcess, RTEXITCODE_FAILURE); + + va_list va; + va_start(va, pszFormat); + supR3HardenedErrorV(rc, false /*fFatal*/, pszFormat, va); + va_end(va); + + /* + * Wait for the process to really go away. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + bool fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING; + if (!fExitOk) + { + NTSTATUS rcNtWait; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtTerminateProcess(hProcess, DBG_TERMINATE_PROCESS); + + LARGE_INTEGER Timeout; + Timeout.QuadPart = -20000000; /* 2 second */ + rcNtWait = NtWaitForSingleObject(hProcess, TRUE /*Alertable*/, &Timeout); + + rcNtExit = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + fExitOk = NT_SUCCESS(rcNtExit) && BasicInfo.ExitStatus != STATUS_PENDING; + } while ( !fExitOk + && ( rcNtWait == STATUS_TIMEOUT + || rcNtWait == STATUS_USER_APC + || rcNtWait == STATUS_ALERTED) + && supR3HardenedWinGetMilliTS() - uMsTsStart < 60 * 1000); + if (fExitOk) + supR3HardenedError(rc, false /*fFatal*/, + "NtDuplicateObject failed and we failed to kill child: rc=%u (%#x) rcNtWait=%#x hProcess=%p\n", + rc, rc, rcNtWait, hProcess); + } + + /* + * Final error message. + */ + va_start(va, pszFormat); + supR3HardenedFatalMsgV(pszWhere, kSupInitOp_Misc, rc, pszFormat, va); + /* not reached */ +} + + +/** + * Checks the child process when hEvtParent is signalled. + * + * This will read the request data from the child and check it against expected + * request. If an error is signalled, we'll raise it and make sure the child + * terminates before terminating the calling process. + * + * @param pThis The child process data structure. + * @param enmExpectedRequest The expected child request. + * @param pszWhat What we're waiting for. + */ +static void supR3HardNtChildProcessRequest(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, const char *pszWhat) +{ + /* + * Read the process parameters from the child. + */ + uintptr_t uChildAddr = (uintptr_t)pThis->Peb.ImageBaseAddress + + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + SIZE_T cbIgnored = 0; + RT_ZERO(pThis->ProcParams); + NTSTATUS rcNt = NtReadVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, + &pThis->ProcParams, sizeof(pThis->ProcParams), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, + "NtReadVirtualMemory(,%p,) failed reading child process status: %#x\n", uChildAddr, rcNt); + + /* + * Is it the expected request? + */ + if (pThis->ProcParams.enmRequest == enmExpectedRequest) + return; + + /* + * No, not the expected request. If it's an error request, tell the child + * to terminate itself, otherwise we'll have to terminate it. + */ + pThis->ProcParams.szErrorMsg[sizeof(pThis->ProcParams.szErrorMsg) - 1] = '\0'; + pThis->ProcParams.szWhere[sizeof(pThis->ProcParams.szWhere) - 1] = '\0'; + SUP_DPRINTF(("supR3HardenedWinCheckChild: enmRequest=%d rc=%d enmWhat=%d %s: %s\n", + pThis->ProcParams.enmRequest, pThis->ProcParams.rc, pThis->ProcParams.enmWhat, + pThis->ProcParams.szWhere, pThis->ProcParams.szErrorMsg)); + + if (pThis->ProcParams.enmRequest != kSupR3WinChildReq_Error) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinCheckChild", VERR_INVALID_PARAMETER, + "Unexpected child request #%d. Was expecting #%d (%s).\n", + pThis->ProcParams.enmRequest, enmExpectedRequest, pszWhat); + + rcNt = NtSetEvent(pThis->hEvtChild, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildProcessRequest", rcNt, "NtSetEvent failed: %#x\n", rcNt); + + /* Wait for it to terminate. */ + LARGE_INTEGER Timeout; + Timeout.QuadPart = -50000000; /* 5 seconds */ + rcNt = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, &Timeout); + if (rcNt != STATUS_WAIT_0) + { + SUP_DPRINTF(("supR3HardNtChildProcessRequest: Child is taking too long to quit (rcWait=%#x), killing it...\n", rcNt)); + NtTerminateProcess(pThis->hProcess, DBG_TERMINATE_PROCESS); + } + + /* + * Report the error in the same way as it occured in the guest. + */ + if (pThis->ProcParams.enmWhat == kSupInitOp_Invalid) + supR3HardenedFatalMsg("supR3HardenedWinCheckChild", kSupInitOp_Misc, pThis->ProcParams.rc, + "%s", pThis->ProcParams.szErrorMsg); + else + supR3HardenedFatalMsg(pThis->ProcParams.szWhere, pThis->ProcParams.enmWhat, pThis->ProcParams.rc, + "%s", pThis->ProcParams.szErrorMsg); +} + + +/** + * Waits for the child to make a certain request or terminate. + * + * The stub process will also wait on it's parent to terminate. + * This call will only return if the child made the expected request. + * + * @param pThis The child process data structure. + * @param enmExpectedRequest The child request to wait for. + * @param cMsTimeout The number of milliseconds to wait (at least). + * @param pszWhat What we're waiting for. + */ +static void supR3HardNtChildWaitFor(PSUPR3HARDNTCHILD pThis, SUPR3WINCHILDREQ enmExpectedRequest, RTMSINTERVAL cMsTimeout, + const char *pszWhat) +{ + /* + * The wait loop. + * Will return when the expected request arrives. + * Will break out when one of the processes terminates. + */ + NTSTATUS rcNtWait; + LARGE_INTEGER Timeout; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + uint64_t cMsElapsed = 0; + for (;;) + { + /* + * Assemble handles to wait for. + */ + ULONG cHandles = 1; + HANDLE ahHandles[3]; + ahHandles[0] = pThis->hProcess; + if (pThis->hEvtParent) + ahHandles[cHandles++] = pThis->hEvtParent; + if (pThis->hParent) + ahHandles[cHandles++] = pThis->hParent; + + /* + * Do the waiting according to the callers wishes. + */ + if ( enmExpectedRequest == kSupR3WinChildReq_End + || cMsTimeout == RT_INDEFINITE_WAIT) + rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, NULL /*Timeout*/); + else + { + Timeout.QuadPart = -(int64_t)(cMsTimeout - cMsElapsed) * 10000; + rcNtWait = NtWaitForMultipleObjects(cHandles, &ahHandles[0], WaitAnyObject, TRUE /*Alertable*/, &Timeout); + } + + /* + * Process child request. + */ + if (rcNtWait == STATUS_WAIT_0 + 1 && pThis->hEvtParent != NULL) + { + supR3HardNtChildProcessRequest(pThis, enmExpectedRequest, pszWhat); + SUP_DPRINTF(("supR3HardNtChildWaitFor: Found expected request %d (%s) after %llu ms.\n", + enmExpectedRequest, pszWhat, supR3HardenedWinGetMilliTS() - uMsTsStart)); + return; /* Expected request received. */ + } + + /* + * Process termination? + */ + if ( (ULONG)rcNtWait - (ULONG)STATUS_WAIT_0 < cHandles + || (ULONG)rcNtWait - (ULONG)STATUS_ABANDONED_WAIT_0 < cHandles) + break; + + /* + * Check sanity. + */ + if ( rcNtWait != STATUS_TIMEOUT + && rcNtWait != STATUS_USER_APC + && rcNtWait != STATUS_ALERTED) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait, + "NtWaitForMultipleObjects returned %#x waiting for #%d (%s)\n", + rcNtWait, enmExpectedRequest, pszWhat); + + /* + * Calc elapsed time for the next timeout calculation, checking to see + * if we've timed out already. + */ + cMsElapsed = supR3HardenedWinGetMilliTS() - uMsTsStart; + if ( cMsElapsed > cMsTimeout + && cMsTimeout != RT_INDEFINITE_WAIT + && enmExpectedRequest != kSupR3WinChildReq_End) + { + if (rcNtWait == STATUS_USER_APC || rcNtWait == STATUS_ALERTED) + cMsElapsed = cMsTimeout - 1; /* try again */ + else + { + /* We timed out. */ + supR3HardenedWinKillChild(pThis, "supR3HardNtChildWaitFor", rcNtWait, + "Timed out after %llu ms waiting for child request #%d (%s).\n", + cMsElapsed, enmExpectedRequest, pszWhat); + } + } + } + + /* + * Proxy the termination code of the child, if it exited already. + */ + PROCESS_BASIC_INFORMATION BasicInfo; + NTSTATUS rcNt1 = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL); + NTSTATUS rcNt2 = STATUS_PENDING; + NTSTATUS rcNt3 = STATUS_PENDING; + if ( !NT_SUCCESS(rcNt1) + || BasicInfo.ExitStatus == STATUS_PENDING) + { + rcNt2 = NtTerminateProcess(pThis->hProcess, RTEXITCODE_FAILURE); + Timeout.QuadPart = NT_SUCCESS(rcNt2) ? -20000000 /* 2 sec */ : -1280000 /* 128 ms */; + rcNt3 = NtWaitForSingleObject(pThis->hProcess, FALSE /*Alertable*/, NULL /*Timeout*/); + BasicInfo.ExitStatus = RTEXITCODE_FAILURE; + } + + SUP_DPRINTF(("supR3HardNtChildWaitFor[%d]: Quitting: ExitCode=%#x (rcNtWait=%#x, rcNt1=%#x, rcNt2=%#x, rcNt3=%#x, %llu ms, %s);\n", + pThis->iWhich, BasicInfo.ExitStatus, rcNtWait, rcNt1, rcNt2, rcNt3, + supR3HardenedWinGetMilliTS() - uMsTsStart, pszWhat)); + suplibHardenedExit((RTEXITCODE)BasicInfo.ExitStatus); +} + + +/** + * Closes full access child thread and process handles, making a harmless + * duplicate of the process handle first. + * + * The hProcess member of the child process data structure will be change to the + * harmless handle, while the hThread will be set to NULL. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildCloseFullAccessHandles(PSUPR3HARDNTCHILD pThis) +{ + /* + * The thread handle. + */ + NTSTATUS rcNt = NtClose(pThis->hThread); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, "NtClose(hThread) failed: %#x", rcNt); + pThis->hThread = NULL; + + /* + * Duplicate the process handle into a harmless one. + */ + HANDLE hProcWait; + ULONG fRights = SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_VM_READ; + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0)) /* Introduced in Vista. */ + fRights |= PROCESS_QUERY_LIMITED_INFORMATION; + else + fRights |= PROCESS_QUERY_INFORMATION; + rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess, + NtCurrentProcess(), &hProcWait, + fRights, 0 /*HandleAttributes*/, 0); + if (rcNt == STATUS_ACCESS_DENIED) + { + supR3HardenedError(rcNt, false /*fFatal*/, + "supR3HardenedWinDoReSpawn: NtDuplicateObject(,,,,%#x,,) -> %#x, retrying with only %#x...\n", + fRights, rcNt, SYNCHRONIZE); + rcNt = NtDuplicateObject(NtCurrentProcess(), pThis->hProcess, + NtCurrentProcess(), &hProcWait, + SYNCHRONIZE, 0 /*HandleAttributes*/, 0); + } + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", rcNt, + "NtDuplicateObject failed on child process handle: %#x\n", rcNt); + /* + * Close the process handle and replace it with the harmless one. + */ + rcNt = NtClose(pThis->hProcess); + pThis->hProcess = hProcWait; + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinReSpawn", VERR_INVALID_NAME, + "NtClose failed on child process handle: %#x\n", rcNt); +} + + +/** + * This restores the child PEB and tweaks a couple of fields before we do the + * child purification and let the process run normally. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildSanitizePeb(PSUPR3HARDNTCHILD pThis) +{ + /* + * Make a copy of the pre-execution PEB. + */ + PEB Peb = pThis->Peb; + +#if 0 + /* + * There should not be any activation context, so if there is, we scratch the memory associated with it. + */ + int rc = 0; + if (RT_SUCCESS(rc) && Peb.pShimData && !((uintptr_t)Peb.pShimData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.pShimData, PAGE_SIZE, "pShimData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.ActivationContextData && !((uintptr_t)Peb.ActivationContextData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ActivationContextData, PAGE_SIZE, "ActivationContextData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.ProcessAssemblyStorageMap && !((uintptr_t)Peb.ProcessAssemblyStorageMap & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "ProcessAssemblyStorageMap", pErrInfo); + if (RT_SUCCESS(rc) && Peb.SystemDefaultActivationContextData && !((uintptr_t)Peb.SystemDefaultActivationContextData & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.ProcessAssemblyStorageMap, PAGE_SIZE, "SystemDefaultActivationContextData", pErrInfo); + if (RT_SUCCESS(rc) && Peb.SystemAssemblyStorageMap && !((uintptr_t)Peb.SystemAssemblyStorageMap & PAGE_OFFSET_MASK)) + rc = supR3HardenedWinScratchChildMemory(hProcess, Peb.SystemAssemblyStorageMap, PAGE_SIZE, "SystemAssemblyStorageMap", pErrInfo); + if (RT_FAILURE(rc)) + return rc; +#endif + + /* + * Clear compatibility and activation related fields. + */ + Peb.AppCompatFlags.QuadPart = 0; + Peb.AppCompatFlagsUser.QuadPart = 0; + Peb.pShimData = NULL; + Peb.AppCompatInfo = NULL; +#if 0 + Peb.ActivationContextData = NULL; + Peb.ProcessAssemblyStorageMap = NULL; + Peb.SystemDefaultActivationContextData = NULL; + Peb.SystemAssemblyStorageMap = NULL; + /*Peb.Diff0.W6.IsProtectedProcess = 1;*/ +#endif + + /* + * Write back the PEB. + */ + SIZE_T cbActualMem = pThis->cbPeb; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildSanitizePeb", rcNt, + "NtWriteVirtualMemory/Peb failed: %#x", rcNt); + +} + + +/** + * Purifies the child process after very early init has been performed. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildPurify(PSUPR3HARDNTCHILD pThis) +{ + /* + * We loop until we no longer make any fixes. This is similar to what + * we do (or used to do, really) in the fAvastKludge case of + * supR3HardenedWinInit. We might be up against asynchronous changes, + * which we fudge by waiting a short while before earch purification. This + * is arguably a fragile technique, but it's currently the best we've got. + * Fortunately, most AVs seems to either favor immediate action on initial + * load events or (much better for us) later events like kernel32. + */ + uint64_t uMsTsOuterStart = supR3HardenedWinGetMilliTS(); + uint32_t cMsFudge = g_fSupAdversaries ? 512 : 256; + uint32_t cTotalFixes = 0; + uint32_t cFixes = 0; /* (MSC wrongly thinks this maybe used uninitialized) */ + for (uint32_t iLoop = 0; iLoop < 16; iLoop++) + { + /* + * Delay. + */ + uint32_t cSleeps = 0; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtYieldExecution(); + LARGE_INTEGER Time; + Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */ + NtDelayExecution(FALSE, &Time); + cSleeps++; + } while ( supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge + || cSleeps < 8); + SUP_DPRINTF(("supR3HardNtChildPurify: Startup delay kludge #1/%u: %u ms, %u sleeps\n", + iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps)); + + /* + * Purify. + */ + cFixes = 0; + int rc = supHardenedWinVerifyProcess(pThis->hProcess, pThis->hThread, SUPHARDNTVPKIND_CHILD_PURIFICATION, + g_fSupAdversaries & ( SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE + | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD) + ? SUPHARDNTVP_F_EXEC_ALLOC_REPLACE_WITH_RW : 0, + &cFixes, RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", rc, + "supHardenedWinVerifyProcess failed with %Rrc: %s", rc, g_ErrInfoStatic.szMsg); + if (cFixes == 0) + { + SUP_DPRINTF(("supR3HardNtChildPurify: Done after %llu ms and %u fixes (loop #%u).\n", + supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cTotalFixes, iLoop)); + return; /* We're probably good. */ + } + cTotalFixes += cFixes; + + if (!g_fSupAdversaries) + g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN; + cMsFudge = 512; + + /* + * Log the KiOpPrefetchPatchCount value if available, hoping it might + * sched some light on spider38's case. + */ + ULONG cPatchCount = 0; + NTSTATUS rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount, + &cPatchCount, sizeof(cPatchCount), NULL); + if (NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n", + cFixes, g_fSupAdversaries, cPatchCount)); + else + SUP_DPRINTF(("supR3HardNtChildPurify: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries)); + } + + /* + * We've given up fixing the child process. Probably fighting someone + * that monitors their patches or/and our activities. + */ + supR3HardenedWinKillChild(pThis, "supR3HardNtChildPurify", VERR_TRY_AGAIN, + "Unable to purify child process! After 16 tries over %llu ms, we still %u fix(es) in the last pass.", + supR3HardenedWinGetMilliTS() - uMsTsOuterStart, cFixes); +} + + +/** + * Sets up the early process init. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildSetUpChildInit(PSUPR3HARDNTCHILD pThis) +{ + uintptr_t const uChildExeAddr = (uintptr_t)pThis->Peb.ImageBaseAddress; + + /* + * Plant the process parameters. This ASSUMES the handle inheritance is + * performed when creating the child process. + */ + RT_ZERO(pThis->ProcParams); + pThis->ProcParams.hEvtChild = pThis->hEvtChild; + pThis->ProcParams.hEvtParent = pThis->hEvtParent; + pThis->ProcParams.uNtDllAddr = pThis->uNtDllAddr; + pThis->ProcParams.enmRequest = kSupR3WinChildReq_Error; + pThis->ProcParams.rc = VINF_SUCCESS; + + uintptr_t uChildAddr = uChildExeAddr + ((uintptr_t)&g_ProcParams - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + SIZE_T cbIgnored; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, (PVOID)uChildAddr, &pThis->ProcParams, + sizeof(pThis->ProcParams), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtWriteVirtualMemory(,%p,) failed writing child process parameters: %#x\n", uChildAddr, rcNt); + + /* + * Locate the LdrInitializeThunk address in the child as well as pristine + * code bits for it. + */ + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheOpen failed on NTDLL: %Rrc\n", rc); + + uint8_t *pbChildNtDllBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbChildNtDllBits, pThis->uNtDllAddr, NULL, NULL, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc\n", rc); + + RTLDRADDR uLdrInitThunk; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX, + "LdrInitializeThunk", &uLdrInitThunk); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "Error locating LdrInitializeThunk in NTDLL: %Rrc", rc); + PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uLdrInitThunk; + SUP_DPRINTF(("supR3HardenedWinSetupChildInit: uLdrInitThunk=%p\n", (uintptr_t)uLdrInitThunk)); + + /* + * Calculate the address of our code in the child process. + */ + uintptr_t uEarlyProcInitEP = uChildExeAddr + ( (uintptr_t)&supR3HardenedEarlyProcessInitThunk + - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + + /* + * Compose the LdrInitializeThunk replacement bytes. + * Note! The amount of code we replace here must be less or equal to what + * the process verification code ignores. + */ + uint8_t abNew[16]; + memcpy(abNew, pbChildNtDllBits + ((uintptr_t)uLdrInitThunk - pThis->uNtDllAddr), sizeof(abNew)); +#ifdef RT_ARCH_AMD64 + abNew[0] = 0xff; + abNew[1] = 0x25; + *(uint32_t *)&abNew[2] = 0; + *(uint64_t *)&abNew[6] = uEarlyProcInitEP; +#elif defined(RT_ARCH_X86) + abNew[0] = 0xe9; + *(uint32_t *)&abNew[1] = uEarlyProcInitEP - ((uint32_t)uLdrInitThunk + 5); +#else +# error "Unsupported arch." +#endif + + /* + * Install the LdrInitializeThunk replacement code in the child process. + */ + PVOID pvProt = pvLdrInitThunk; + SIZE_T cbProt = sizeof(abNew); + ULONG fOldProt; + rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, PAGE_EXECUTE_READWRITE, &fOldProt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtProtectVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + rcNt = NtWriteVirtualMemory(pThis->hProcess, pvLdrInitThunk, abNew, sizeof(abNew), &cbIgnored); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtWriteVirtualMemory/LdrInitializeThunk failed: %#x", rcNt); + + pvProt = pvLdrInitThunk; + cbProt = sizeof(abNew); + rcNt = NtProtectVirtualMemory(pThis->hProcess, &pvProt, &cbProt, fOldProt, &fOldProt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rcNt, + "NtProtectVirtualMemory/LdrInitializeThunk[restore] failed: %#x", rcNt); + + /* + * Check the sanity of the thread context. + */ + CONTEXT Ctx; + RT_ZERO(Ctx); + Ctx.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; + rcNt = NtGetContextThread(pThis->hThread, &Ctx); + if (NT_SUCCESS(rcNt)) + { +#ifdef RT_ARCH_AMD64 + DWORD64 *pPC = &Ctx.Rip; +#elif defined(RT_ARCH_X86) + DWORD *pPC = &Ctx.Eip; +#else +# error "Unsupported arch." +#endif + supR3HardNtDprintCtx(&Ctx, "supR3HardenedWinSetupChildInit: Initial context:"); + + /* Entrypoint for the executable: */ + uintptr_t const uChildMain = uChildExeAddr + ( (uintptr_t)&suplibHardenedWindowsMain + - (uintptr_t)NtCurrentPeb()->ImageBaseAddress); + + /* NtDll size and the more recent default thread start entrypoint (Vista+?): */ + RTLDRADDR uSystemThreadStart; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pThis->uNtDllAddr, UINT32_MAX, + "RtlUserThreadStart", &uSystemThreadStart); + if (RT_FAILURE(rc)) + uSystemThreadStart = 0; + + /* Kernel32 for thread start of older windows version, only XP64/W2K3-64 has an actual + export for it. Unfortunately, it is not yet loaded into the child, so we have to + assume same location as in the parent (safe): */ + PSUPHNTLDRCACHEENTRY pLdrEntryKernel32; + rc = supHardNtLdrCacheOpen("kernel32.dll", &pLdrEntryKernel32, NULL /*pErrInfo*/); + if (RT_FAILURE(rc)) + supR3HardenedWinKillChild(pThis, "supR3HardenedWinSetupChildInit", rc, + "supHardNtLdrCacheOpen failed on KERNEL32: %Rrc\n", rc); + size_t const cbKernel32 = RTLdrSize(pLdrEntryKernel32->hLdrMod); + +#ifdef RT_ARCH_AMD64 + if (!uSystemThreadStart) + { + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbChildNtDllBits, pLdrEntryKernel32->uImageBase, UINT32_MAX, + "BaseProcessStart", &uSystemThreadStart); + if (RT_FAILURE(rc)) + uSystemThreadStart = 0; + } +#endif + + bool fUpdateContext = false; + + /* Check if the RIP looks half sane, try correct it if it isn't. + It should point to RtlUserThreadStart (Vista and later it seem), though only + tested on win10. The first parameter is the executable entrypoint, the 2nd + is probably the PEB. Before Vista it should point to Kernel32!BaseProcessStart, + though the symbol is only exported in 5.2/AMD64. */ + if ( ( uSystemThreadStart + ? *pPC == uSystemThreadStart + : *pPC - ( pLdrEntryKernel32->uImageBase != ~(uintptr_t)0 ? pLdrEntryKernel32->uImageBase + : (uintptr_t)GetModuleHandleW(L"kernel32.dll")) <= cbKernel32) + || *pPC == uChildMain) + { } + else + { + SUP_DPRINTF(("Warning! Bogus RIP: %p (uSystemThreadStart=%p; kernel32 %p LB %p; uChildMain=%p)\n", + *pPC, uSystemThreadStart, pLdrEntryKernel32->uImageBase, cbKernel32, uChildMain)); + if (uSystemThreadStart) + { + SUP_DPRINTF(("Correcting RIP from to %p hoping that it might work...\n", (uintptr_t)uSystemThreadStart)); + *pPC = uSystemThreadStart; + fUpdateContext = true; + } + } +#ifdef RT_ARCH_AMD64 + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0)) /* W2K3: CS=33 SS=DS=ES=GS=2b FS=53 */ + { + if (Ctx.SegDs != 0) + SUP_DPRINTF(("Warning! Bogus DS: %04x, expected zero\n", Ctx.SegDs)); + if (Ctx.SegEs != 0) + SUP_DPRINTF(("Warning! Bogus ES: %04x, expected zero\n", Ctx.SegEs)); + if (Ctx.SegFs != 0) + SUP_DPRINTF(("Warning! Bogus FS: %04x, expected zero\n", Ctx.SegFs)); + if (Ctx.SegGs != 0) + SUP_DPRINTF(("Warning! Bogus GS: %04x, expected zero\n", Ctx.SegGs)); + } + if (Ctx.Rcx != uChildMain) + SUP_DPRINTF(("Warning! Bogus RCX: %016RX64, expected %016RX64\n", Ctx.Rcx, uChildMain)); + if (Ctx.Rdx & PAGE_OFFSET_MASK) + SUP_DPRINTF(("Warning! Bogus RDX: %016RX64, expected page aligned\n", Ctx.Rdx)); /* PEB */ + if ((Ctx.Rsp & 15) != 8) + SUP_DPRINTF(("Warning! Misaligned RSP: %016RX64\n", Ctx.Rsp)); +#endif + if (Ctx.SegCs != ASMGetCS()) + SUP_DPRINTF(("Warning! Bogus CS: %04x, expected %04x\n", Ctx.SegCs, ASMGetCS())); + if (Ctx.SegSs != ASMGetSS()) + SUP_DPRINTF(("Warning! Bogus SS: %04x, expected %04x\n", Ctx.SegSs, ASMGetSS())); + if (Ctx.Dr0 != 0) + SUP_DPRINTF(("Warning! Bogus DR0: %016RX64, expected zero\n", Ctx.Dr0)); + if (Ctx.Dr1 != 0) + SUP_DPRINTF(("Warning! Bogus DR1: %016RX64, expected zero\n", Ctx.Dr1)); + if (Ctx.Dr2 != 0) + SUP_DPRINTF(("Warning! Bogus DR2: %016RX64, expected zero\n", Ctx.Dr2)); + if (Ctx.Dr3 != 0) + SUP_DPRINTF(("Warning! Bogus DR3: %016RX64, expected zero\n", Ctx.Dr3)); + if (Ctx.Dr6 != 0) + SUP_DPRINTF(("Warning! Bogus DR6: %016RX64, expected zero\n", Ctx.Dr6)); + if (Ctx.Dr7 != 0) + { + SUP_DPRINTF(("Warning! Bogus DR7: %016RX64, expected zero\n", Ctx.Dr7)); + Ctx.Dr7 = 0; + fUpdateContext = true; + } + + if (fUpdateContext) + { + rcNt = NtSetContextThread(pThis->hThread, &Ctx); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("Error! NtSetContextThread failed: %#x\n", rcNt)); + } + } + + /* Caller starts child execution. */ + SUP_DPRINTF(("supR3HardenedWinSetupChildInit: Start child.\n")); +} + + + +/** + * This messes with the child PEB before we trigger the initial image events. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildScrewUpPebForInitialImageEvents(PSUPR3HARDNTCHILD pThis) +{ + /* + * Not sure if any of the cracker software uses the PEB at this point, but + * just in case they do make some of the PEB fields a little less useful. + */ + PEB Peb = pThis->Peb; + + /* Make ImageBaseAddress useless. */ + Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress ^ UINT32_C(0x5f139000)); +#ifdef RT_ARCH_AMD64 + Peb.ImageBaseAddress = (PVOID)((uintptr_t)Peb.ImageBaseAddress | UINT64_C(0x0313000000000000)); +#endif + + /* + * Write the PEB. + */ + SIZE_T cbActualMem = pThis->cbPeb; + NTSTATUS rcNt = NtWriteVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &Peb, pThis->cbPeb, &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildScrewUpPebForInitialImageEvents", rcNt, + "NtWriteVirtualMemory/Peb failed: %#x", rcNt); +} + + +/** + * Check if the zero terminated NT unicode string is the path to the given + * system32 DLL. + * + * @returns true if it is, false if not. + * @param pUniStr The zero terminated NT unicode string path. + * @param pszName The name of the system32 DLL. + */ +static bool supR3HardNtIsNamedSystem32Dll(PUNICODE_STRING pUniStr, const char *pszName) +{ + if (pUniStr->Length > g_System32NtPath.UniStr.Length) + { + if (memcmp(pUniStr->Buffer, g_System32NtPath.UniStr.Buffer, g_System32NtPath.UniStr.Length) == 0) + { + if (pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR)] == '\\') + { + if (RTUtf16ICmpAscii(&pUniStr->Buffer[g_System32NtPath.UniStr.Length / sizeof(WCHAR) + 1], pszName) == 0) + return true; + } + } + } + + return false; +} + + +/** + * Worker for supR3HardNtChildGatherData that locates NTDLL in the child + * process. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildFindNtdll(PSUPR3HARDNTCHILD pThis) +{ + /* + * Find NTDLL in this process first and take that as a starting point. + */ + pThis->uNtDllParentAddr = (uintptr_t)GetModuleHandleW(L"ntdll.dll"); + SUPR3HARDENED_ASSERT(pThis->uNtDllParentAddr != 0 && !(pThis->uNtDllParentAddr & PAGE_OFFSET_MASK)); + pThis->uNtDllAddr = pThis->uNtDllParentAddr; + + /* + * Scan the virtual memory of the child. + */ + uintptr_t cbAdvance = 0; + uintptr_t uPtrWhere = 0; + for (uint32_t i = 0; i < 1024; i++) + { + /* Query information. */ + SIZE_T cbActual = 0; + MEMORY_BASIC_INFORMATION MemInfo = { 0, 0, 0, 0, 0, 0, 0 }; + NTSTATUS rcNt = NtQueryVirtualMemory(pThis->hProcess, + (void const *)uPtrWhere, + MemoryBasicInformation, + &MemInfo, + sizeof(MemInfo), + &cbActual); + if (!NT_SUCCESS(rcNt)) + break; + + if ( MemInfo.Type == SEC_IMAGE + || MemInfo.Type == SEC_PROTECTED_IMAGE + || MemInfo.Type == (SEC_IMAGE | SEC_PROTECTED_IMAGE)) + { + if (MemInfo.BaseAddress == MemInfo.AllocationBase) + { + /* Get the image name. */ + union + { + UNICODE_STRING UniStr; + uint8_t abPadding[4096]; + } uBuf; + rcNt = NtQueryVirtualMemory(pThis->hProcess, + MemInfo.BaseAddress, + MemorySectionName, + &uBuf, + sizeof(uBuf) - sizeof(WCHAR), + &cbActual); + if (NT_SUCCESS(rcNt)) + { + uBuf.UniStr.Buffer[uBuf.UniStr.Length / sizeof(WCHAR)] = '\0'; + if (supR3HardNtIsNamedSystem32Dll(&uBuf.UniStr, "ntdll.dll")) + { + pThis->uNtDllAddr = (uintptr_t)MemInfo.AllocationBase; + SUP_DPRINTF(("supR3HardNtPuChFindNtdll: uNtDllParentAddr=%p uNtDllChildAddr=%p\n", + pThis->uNtDllParentAddr, pThis->uNtDllAddr)); + return; + } + } + } + } + + /* + * Advance. + */ + cbAdvance = MemInfo.RegionSize; + if (uPtrWhere + cbAdvance <= uPtrWhere) + break; + uPtrWhere += MemInfo.RegionSize; + } + + supR3HardenedWinKillChild(pThis, "supR3HardNtChildFindNtdll", VERR_MODULE_NOT_FOUND, "ntdll.dll not found in child process."); +} + + +/** + * Gather child data. + * + * @param pThis The child process data structure. + */ +static void supR3HardNtChildGatherData(PSUPR3HARDNTCHILD pThis) +{ + /* + * Basic info. + */ + ULONG cbActual = 0; + NTSTATUS rcNt = NtQueryInformationProcess(pThis->hProcess, ProcessBasicInformation, + &pThis->BasicInfo, sizeof(pThis->BasicInfo), &cbActual); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt, + "NtQueryInformationProcess/ProcessBasicInformation failed: %#x", rcNt); + + /* + * If this is the middle (stub) process, we wish to wait for both child + * and parent. So open the parent process. Not fatal if we cannnot. + */ + if (pThis->iWhich > 1) + { + PROCESS_BASIC_INFORMATION SelfInfo; + rcNt = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &SelfInfo, sizeof(SelfInfo), &cbActual); + if (NT_SUCCESS(rcNt)) + { + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, NULL, 0, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + CLIENT_ID ClientId; + ClientId.UniqueProcess = (HANDLE)SelfInfo.InheritedFromUniqueProcessId; + ClientId.UniqueThread = NULL; + + rcNt = NtOpenProcess(&pThis->hParent, SYNCHRONIZE | PROCESS_QUERY_INFORMATION, &ObjAttr, &ClientId); +#ifdef DEBUG + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (!NT_SUCCESS(rcNt)) + { + pThis->hParent = NULL; + SUP_DPRINTF(("supR3HardNtChildGatherData: Failed to open parent process (%#p): %#x\n", ClientId.UniqueProcess, rcNt)); + } + } + + } + + /* + * Process environment block. + */ + if (g_uNtVerCombined < SUP_NT_VER_W2K3) + pThis->cbPeb = PEB_SIZE_W51; + else if (g_uNtVerCombined < SUP_NT_VER_VISTA) + pThis->cbPeb = PEB_SIZE_W52; + else if (g_uNtVerCombined < SUP_NT_VER_W70) + pThis->cbPeb = PEB_SIZE_W6; + else if (g_uNtVerCombined < SUP_NT_VER_W80) + pThis->cbPeb = PEB_SIZE_W7; + else if (g_uNtVerCombined < SUP_NT_VER_W81) + pThis->cbPeb = PEB_SIZE_W80; + else + pThis->cbPeb = PEB_SIZE_W81; + + SUP_DPRINTF(("supR3HardNtChildGatherData: PebBaseAddress=%p cbPeb=%#x\n", + pThis->BasicInfo.PebBaseAddress, pThis->cbPeb)); + + SIZE_T cbActualMem; + RT_ZERO(pThis->Peb); + rcNt = NtReadVirtualMemory(pThis->hProcess, pThis->BasicInfo.PebBaseAddress, &pThis->Peb, sizeof(pThis->Peb), &cbActualMem); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(pThis, "supR3HardNtChildGatherData", rcNt, + "NtReadVirtualMemory/Peb failed: %#x", rcNt); + + /* + * Locate NtDll. + */ + supR3HardNtChildFindNtdll(pThis); +} + + +/** + * Does the actually respawning. + * + * @returns Never, will call exit or raise fatal error. + * @param iWhich Which respawn we're to check for, 1 being the + * first one, and 2 the second and final. + */ +static DECL_NO_RETURN(void) supR3HardenedWinDoReSpawn(int iWhich) +{ + NTSTATUS rcNt; + PPEB pPeb = NtCurrentPeb(); + PRTL_USER_PROCESS_PARAMETERS pParentProcParams = pPeb->ProcessParameters; + + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + + /* + * Init the child process data structure, creating the child communication + * event sempahores. + */ + SUPR3HARDNTCHILD This; + RT_ZERO(This); + This.iWhich = iWhich; + + OBJECT_ATTRIBUTES ObjAttrs; + This.hEvtChild = NULL; + InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/); + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtChild, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE)); + + This.hEvtParent = NULL; + InitializeObjectAttributes(&ObjAttrs, NULL /*pName*/, OBJ_INHERIT, NULL /*hRootDir*/, NULL /*pSecDesc*/); + SUPR3HARDENED_ASSERT_NT_SUCCESS(NtCreateEvent(&This.hEvtParent, EVENT_ALL_ACCESS, &ObjAttrs, SynchronizationEvent, FALSE)); + + /* + * Set up security descriptors. + */ + SECURITY_ATTRIBUTES ProcessSecAttrs; + MYSECURITYCLEANUP ProcessSecAttrsCleanup; + supR3HardNtChildInitSecAttrs(&ProcessSecAttrs, &ProcessSecAttrsCleanup, true /*fProcess*/); + + SECURITY_ATTRIBUTES ThreadSecAttrs; + MYSECURITYCLEANUP ThreadSecAttrsCleanup; + supR3HardNtChildInitSecAttrs(&ThreadSecAttrs, &ThreadSecAttrsCleanup, false /*fProcess*/); + +#if 1 + /* + * Configure the startup info and creation flags. + */ + DWORD dwCreationFlags = CREATE_SUSPENDED; + + 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 |= pParentProcParams->WindowFlags & STARTF_USESHOWWINDOW; + SiEx.StartupInfo.wShowWindow = (WORD)pParentProcParams->ShowWindowFlags; + + SiEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + SiEx.StartupInfo.hStdInput = pParentProcParams->StandardInput; + SiEx.StartupInfo.hStdOutput = pParentProcParams->StandardOutput; + SiEx.StartupInfo.hStdError = pParentProcParams->StandardError; + + /* + * Construct the command line and launch the process. + */ + PRTUTF16 pwszCmdLine = supR3HardNtChildConstructCmdLine(NULL, iWhich); + + supR3HardenedWinEnableThreadCreation(); + PROCESS_INFORMATION ProcessInfoW32 = { NULL, NULL, 0, 0 }; + if (!CreateProcessW(g_wszSupLibHardenedExePath, + pwszCmdLine, + &ProcessSecAttrs, + &ThreadSecAttrs, + TRUE /*fInheritHandles*/, + dwCreationFlags, + NULL /*pwszzEnvironment*/, + NULL /*pwszCurDir*/, + &SiEx.StartupInfo, + &ProcessInfoW32)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "Error relaunching VirtualBox VM process: %u\n" + "Command line: '%ls'", + RtlGetLastWin32Error(), pwszCmdLine); + supR3HardenedWinDisableThreadCreation(); + + SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [kernel32].\n", + iWhich, ProcessInfoW32.dwProcessId, ProcessInfoW32.dwThreadId)); + This.hProcess = ProcessInfoW32.hProcess; + This.hThread = ProcessInfoW32.hThread; + +#else + + /* + * Construct the process parameters. + */ + UNICODE_STRING W32ImageName; + W32ImageName.Buffer = g_wszSupLibHardenedExePath; /* Yes the windows name for the process parameters. */ + W32ImageName.Length = (USHORT)RTUtf16Len(g_wszSupLibHardenedExePath) * sizeof(WCHAR); + W32ImageName.MaximumLength = W32ImageName.Length + sizeof(WCHAR); + + UNICODE_STRING CmdLine; + supR3HardNtChildConstructCmdLine(&CmdLine, iWhich); + + PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL; + SUPR3HARDENED_ASSERT_NT_SUCCESS(RtlCreateProcessParameters(&pProcParams, + &W32ImageName, + NULL /* DllPath - inherit from this process */, + NULL /* CurrentDirectory - inherit from this process */, + &CmdLine, + NULL /* Environment - inherit from this process */, + NULL /* WindowsTitle - none */, + NULL /* DesktopTitle - none. */, + NULL /* ShellInfo - none. */, + NULL /* RuntimeInfo - none (byte array for MSVCRT file info) */) + ); + + /** @todo this doesn't work. :-( */ + pProcParams->ConsoleHandle = pParentProcParams->ConsoleHandle; + pProcParams->ConsoleFlags = pParentProcParams->ConsoleFlags; + pProcParams->StandardInput = pParentProcParams->StandardInput; + pProcParams->StandardOutput = pParentProcParams->StandardOutput; + pProcParams->StandardError = pParentProcParams->StandardError; + + RTL_USER_PROCESS_INFORMATION ProcessInfoNt = { sizeof(ProcessInfoNt) }; + rcNt = RtlCreateUserProcess(&g_SupLibHardenedExeNtPath.UniStr, + OBJ_INHERIT | OBJ_CASE_INSENSITIVE /*Attributes*/, + pProcParams, + NULL, //&ProcessSecAttrs, + NULL, //&ThreadSecAttrs, + NtCurrentProcess() /* ParentProcess */, + FALSE /*fInheritHandles*/, + NULL /* DebugPort */, + NULL /* ExceptionPort */, + &ProcessInfoNt); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, VERR_INVALID_NAME, + "Error relaunching VirtualBox VM process: %#x\n" + "Command line: '%ls'", + rcNt, CmdLine.Buffer); + + SUP_DPRINTF(("supR3HardenedWinDoReSpawn(%d): New child %x.%x [ntdll].\n", + iWhich, ProcessInfo.ClientId.UniqueProcess, ProcessInfo.ClientId.UniqueThread)); + RtlDestroyProcessParameters(pProcParams); + + This.hProcess = ProcessInfoNt.ProcessHandle; + This.hThread = ProcessInfoNt.ThreadHandle; +#endif + +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Apply anti debugger notification trick to the thread. (Also done in + * supR3HardenedWinInit.) This may fail with STATUS_ACCESS_DENIED and + * maybe other errors. (Unfortunately, recent (SEP 12.1) of symantec's + * sysplant.sys driver will cause process deadlocks and a shutdown/reboot + * denial of service problem if we hide the initial thread, so we postpone + * this action if we've detected SEP.) + */ + if (!(g_fSupAdversaries & (SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT | SUPHARDNT_ADVERSARY_SYMANTEC_N360))) + { + rcNt = NtSetInformationThread(This.hThread, ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedWinReSpawn: NtSetInformationThread/ThreadHideFromDebugger failed: %#x (harmless)\n", rcNt)); + } +#endif + + /* + * Perform very early child initialization. + */ + supR3HardNtChildGatherData(&This); + supR3HardNtChildScrewUpPebForInitialImageEvents(&This); + supR3HardNtChildSetUpChildInit(&This); + + ULONG cSuspendCount = 0; + rcNt = NtResumeThread(This.hThread, &cSuspendCount); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(&This, "supR3HardenedWinDoReSpawn", rcNt, "NtResumeThread failed: %#x", rcNt); + + /* + * Santizie the pre-NTDLL child when it's ready. + * + * AV software and other things injecting themselves into the embryonic + * and budding process to intercept API calls and what not. Unfortunately + * this is also the behavior of viruses, malware and other unfriendly + * software, so we won't stand for it. AV software can scan our image + * as they are loaded via kernel hooks, that's sufficient. No need for + * patching half of NTDLL or messing with the import table of the + * process executable. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_PurifyChildAndCloseHandles, 2000 /*ms*/, "PurifyChildAndCloseHandles"); + supR3HardNtChildPurify(&This); + supR3HardNtChildSanitizePeb(&This); + + /* + * 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. + */ + supR3HardNtChildCloseFullAccessHandles(&This); + + /* + * Signal the child that we've closed the unrestricted handles and it can + * safely try open the driver. + */ + rcNt = NtSetEvent(This.hEvtChild, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedWinKillChild(&This, "supR3HardenedWinReSpawn", VERR_INVALID_NAME, + "NtSetEvent failed on child process handle: %#x\n", rcNt); + + /* + * Ditch the loader cache so we don't sit on too much memory while waiting. + */ + supR3HardenedWinFlushLoaderCache(); + supR3HardenedWinCompactHeaps(); + + /* + * Enable thread creation at this point so Ctrl-C and Ctrl-Break can be processed. + */ + supR3HardenedWinEnableThreadCreation(); + + /* + * Wait for the child to get to suplibHardenedWindowsMain so we can close the handles. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_CloseEvents, 60000 /*ms*/, "CloseEvents"); + + NtClose(This.hEvtChild); + NtClose(This.hEvtParent); + This.hEvtChild = NULL; + This.hEvtParent = NULL; + + /* + * Wait for the process to terminate. + */ + supR3HardNtChildWaitFor(&This, kSupR3WinChildReq_End, RT_INDEFINITE_WAIT, "the end"); + supR3HardenedFatal("supR3HardenedWinDoReSpawn: supR3HardNtChildWaitFor unexpectedly returned!\n"); + /* not reached*/ +} + + +/** + * Logs the content of the given object directory. + * + * @returns true if it exists, false if not. + * @param pszDir The path of the directory to log (ASCII). + */ +static void supR3HardenedWinLogObjDir(const char *pszDir) +{ + /* + * Open the driver object directory. + */ + RTUTF16 wszDir[128]; + int rc = RTUtf16CopyAscii(wszDir, RT_ELEMENTS(wszDir), pszDir); + if (RT_FAILURE(rc)) + { + SUP_DPRINTF(("supR3HardenedWinLogObjDir: RTUtf16CopyAscii -> %Rrc on '%s'\n", rc, pszDir)); + return; + } + + UNICODE_STRING NtDirName; + NtDirName.Buffer = (WCHAR *)wszDir; + NtDirName.Length = (USHORT)(RTUtf16Len(wszDir) * sizeof(WCHAR)); + NtDirName.MaximumLength = NtDirName.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); + SUP_DPRINTF(("supR3HardenedWinLogObjDir: %ls => %#x\n", wszDir, rcNt)); + if (!NT_SUCCESS(rcNt)) + return; + + /* + * Enumerate it, looking for the driver. + */ + ULONG uObjDirCtx = 0; + for (;;) + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + { + SUP_DPRINTF(("supR3HardenedWinLogObjDir: NtQueryDirectoryObject => rcNt=%#x cbActual=%#x\n", rcNt, cbActual)); + break; + } + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + SUP_DPRINTF((" %.*ls %.*ls\n", + pObjDir->TypeName.Length / sizeof(WCHAR), pObjDir->TypeName.Buffer, + pObjDir->Name.Length / sizeof(WCHAR), pObjDir->Name.Buffer)); + + /* Next directory entry. */ + pObjDir++; + } + } + + /* + * Clean up and return. + */ + NtClose(hDir); +} + + +/** + * Tries to open VBoxDrvErrorInfo and read extra error info from it. + * + * @returns pszErrorInfo. + * @param pszErrorInfo The destination buffer. Will always be + * terminated. + * @param cbErrorInfo The size of the destination buffer. + * @param pszPrefix What to prefix the error info with, if we got + * anything. + */ +DECLHIDDEN(char *) supR3HardenedWinReadErrorInfoDevice(char *pszErrorInfo, size_t cbErrorInfo, const char *pszPrefix) +{ + RT_BZERO(pszErrorInfo, cbErrorInfo); + + /* + * Try open the device. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING NtName = RTNT_CONSTANT_UNISTR(SUPDRV_NT_DEVICE_NAME_ERROR_INFO); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + if (NT_SUCCESS(rcNt)) + { + /* + * Try read error info. + */ + size_t cchPrefix = strlen(pszPrefix); + if (cchPrefix + 3 < cbErrorInfo) + { + LARGE_INTEGER offRead; + offRead.QuadPart = 0; + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &pszErrorInfo[cchPrefix], (ULONG)(cbErrorInfo - cchPrefix - 1), &offRead, NULL); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status) && Ios.Information > 0) + { + memcpy(pszErrorInfo, pszPrefix, cchPrefix); + pszErrorInfo[RT_MIN(cbErrorInfo - 1, cchPrefix + Ios.Information)] = '\0'; + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: '%s'", &pszErrorInfo[cchPrefix])); + } + else + { + *pszErrorInfo = '\0'; + if (rcNt != STATUS_END_OF_FILE || Ios.Status != STATUS_END_OF_FILE) + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtReadFile -> %#x / %#x / %p\n", + rcNt, Ios.Status, Ios.Information)); + } + } + else + RTStrCopy(pszErrorInfo, cbErrorInfo, "error info buffer too small"); + NtClose(hFile); + } + else + SUP_DPRINTF(("supR3HardenedWinReadErrorInfoDevice: NtCreateFile -> %#x\n", rcNt)); + + return pszErrorInfo; +} + + + +/** + * Checks if the driver exists. + * + * This checks whether the driver is present in the /Driver object directory. + * Drivers being initialized or terminated will have an object there + * before/after their devices nodes are created/deleted. + * + * @returns true if it exists, false if not. + * @param pszDriver The driver name. + */ +static bool supR3HardenedWinDriverExists(const char *pszDriver) +{ + /* + * Open the driver object directory. + */ + UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver"); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); +#ifdef VBOX_STRICT + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (!NT_SUCCESS(rcNt)) + return true; + + /* + * Enumerate it, looking for the driver. + */ + bool fFound = true; + ULONG uObjDirCtx = 0; + do + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + break; + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)]; + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0'; + if ( pObjDir->Name.Length > 1 + && RTUtf16ICmpAscii(pObjDir->Name.Buffer, pszDriver) == 0) + { + fFound = true; + break; + } + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved; + + /* Next directory entry. */ + pObjDir++; + } + } while (!fFound); + + /* + * Clean up and return. + */ + NtClose(hDir); + + return fFound; +} + + +/** + * Open the stub device before the 2nd respawn. + */ +static void supR3HardenedWinOpenStubDevice(void) +{ + if (g_fSupStubOpened) + return; + + /* + * Retry if we think driver might still be initializing (STATUS_NO_SUCH_DEVICE + \Drivers\VBoxDrv). + */ + static const WCHAR s_wszName[] = SUPDRV_NT_DEVICE_NAME_STUB; + uint64_t const uMsTsStart = supR3HardenedWinGetMilliTS(); + NTSTATUS rcNt; + uint32_t iTry; + + for (iTry = 0;; iTry++) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + + 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*/); + + rcNt = NtCreateFile(&hFile, + GENERIC_READ | GENERIC_WRITE, /* No SYNCHRONIZE. */ + &ObjAttr, + &Ios, + NULL /* Allocation Size*/, + FILE_ATTRIBUTE_NORMAL, + FILE_SHARE_READ | FILE_SHARE_WRITE, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, /* No FILE_SYNCHRONOUS_IO_NONALERT. */ + NULL /*EaBuffer*/, + 0 /*EaLength*/); + if (NT_SUCCESS(rcNt)) + rcNt = Ios.Status; + + /* The STATUS_NO_SUCH_DEVICE might be returned if the device is not + completely initialized. Delay a little bit and try again. */ + if (rcNt != STATUS_NO_SUCH_DEVICE) + break; + if (iTry > 0 && supR3HardenedWinGetMilliTS() - uMsTsStart > 5000) /* 5 sec, at least two tries */ + break; + if (!supR3HardenedWinDriverExists("VBoxDrv")) + { + /** @todo Consider starting the VBoxdrv.sys service. Requires 2nd process + * though, rather complicated actually as CreateProcess causes all + * kind of things to happen to this process which would make it hard to + * pass the process verification tests... :-/ */ + break; + } + + LARGE_INTEGER Time; + if (iTry < 8) + Time.QuadPart = -1000000 / 100; /* 1ms in 100ns units, relative time. */ + else + Time.QuadPart = -32000000 / 100; /* 32ms in 100ns units, relative time. */ + NtDelayExecution(TRUE, &Time); + } + + if (NT_SUCCESS(rcNt)) + g_fSupStubOpened = true; + else + { + /* + * Report trouble (fatal). For some errors codes we try gather some + * extra information that goes into VBoxStartup.log so that we stand a + * better chance resolving the issue. + */ + char szErrorInfo[16384]; + int rc = VERR_OPEN_FAILED; + if (SUP_NT_STATUS_IS_VBOX(rcNt)) /* See VBoxDrvNtErr2NtStatus. */ + { + rc = SUP_NT_STATUS_TO_VBOX(rcNt); + + /* + * \Windows\ApiPort open trouble. So far only + * STATUS_OBJECT_TYPE_MISMATCH has been observed. + */ + if (rc == VERR_SUPDRV_APIPORT_OPEN_ERROR) + { + SUP_DPRINTF(("Error opening VBoxDrvStub: VERR_SUPDRV_APIPORT_OPEN_ERROR\n")); + + uint32_t uSessionId = NtCurrentPeb()->SessionId; + SUP_DPRINTF((" SessionID=%#x\n", uSessionId)); + char szDir[64]; + if (uSessionId == 0) + RTStrCopy(szDir, sizeof(szDir), "\\Windows"); + else + { + RTStrPrintf(szDir, sizeof(szDir), "\\Sessions\\%u\\Windows", uSessionId); + supR3HardenedWinLogObjDir(szDir); + } + supR3HardenedWinLogObjDir("\\Windows"); + supR3HardenedWinLogObjDir("\\Sessions"); + + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Misc, rc, + "NtCreateFile(%ls) failed: VERR_SUPDRV_APIPORT_OPEN_ERROR\n" + "\n" + "Error getting %s\\ApiPort in the driver from vboxsup.\n" + "\n" + "Could be due to security software is redirecting access to it, so please include full " + "details of such software in a bug report. VBoxStartup.log may contain details important " + "to resolving the issue.%s" + , s_wszName, szDir, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\n\nVBoxDrvStub error: ")); + } + + /* + * Generic VBox failure message. + */ + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, rc, + "NtCreateFile(%ls) failed: %Rrc (rcNt=%#x)%s", s_wszName, rc, rcNt, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + else + { + const char *pszDefine; + switch (rcNt) + { + case STATUS_NO_SUCH_DEVICE: pszDefine = " STATUS_NO_SUCH_DEVICE"; break; + case STATUS_OBJECT_NAME_NOT_FOUND: pszDefine = " STATUS_OBJECT_NAME_NOT_FOUND"; break; + case STATUS_ACCESS_DENIED: pszDefine = " STATUS_ACCESS_DENIED"; break; + case STATUS_TRUST_FAILURE: pszDefine = " STATUS_TRUST_FAILURE"; break; + default: pszDefine = ""; break; + } + + /* + * Problems opening the device is generally due to driver load/ + * unload issues. Check whether the driver is loaded and make + * suggestions accordingly. + */ +/** @todo don't fail during early init, wait till later and try load the driver if missing or at least query the service manager for additional information. */ + if ( rcNt == STATUS_NO_SUCH_DEVICE + || rcNt == STATUS_OBJECT_NAME_NOT_FOUND) + { + SUP_DPRINTF(("Error opening VBoxDrvStub: %s\n", pszDefine)); + if (supR3HardenedWinDriverExists("VBoxDrv")) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)\n" + "\n" + "Driver is probably stuck stopping/starting. Try 'sc.exe query vboxsup' to get more " + "information about its state. Rebooting may actually help.%s" + , s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + else + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)\n" + "\n" + "Driver is does not appear to be loaded. Try 'sc.exe start vboxsup', reinstall " + "VirtualBox or reboot.%s" + , s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + + /* Generic NT failure message. */ + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Driver, VERR_OPEN_FAILED, + "NtCreateFile(%ls) failed: %#x%s (%u retries)%s", + s_wszName, rcNt, pszDefine, iTry, + supR3HardenedWinReadErrorInfoDevice(szErrorInfo, sizeof(szErrorInfo), + "\nVBoxDrvStub error: ")); + } + } +} + + +/** + * Called by the main code if supR3HardenedWinIsReSpawnNeeded returns @c true. + * + * @returns Program exit code. + */ +DECLHIDDEN(int) supR3HardenedWinReSpawn(int iWhich) +{ + /* + * Before the 2nd respawn we set up a child protection deal with the + * support driver via /Devices/VBoxDrvStub. (We tried to do this + * during the early init, but in case we had trouble accessing vboxdrv + * (renamed to vboxsup in 7.0 and 6.1.34) we retry it here where we + * have kernel32.dll and others to pull in for better diagnostics.) + */ + if (iWhich == 2) + supR3HardenedWinOpenStubDevice(); + + /* + * Make sure we're alone in the stub process before creating the VM process + * and that there aren't any debuggers attached. + */ + if (iWhich == 2) + { + int rc = supHardNtVpDebugger(NtCurrentProcess(), RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_SUCCESS(rc)) + rc = supHardNtVpThread(NtCurrentProcess(), NtCurrentThread(), RTErrInfoInitStatic(&g_ErrInfoStatic)); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinReSpawn", kSupInitOp_Integrity, rc, "%s", g_ErrInfoStatic.szMsg); + } + + + /* + * Respawn the process with kernel protection for the new process. + */ + supR3HardenedWinDoReSpawn(iWhich); + /* not reached! */ +} + + +/** + * 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 iWhich Which respawn we're to check for, 1 being the + * first one, and 2 the second and final. + * @param cArgs The number of arguments. + * @param papszArgs Pointer to the argument vector. + */ +DECLHIDDEN(bool) supR3HardenedWinIsReSpawnNeeded(int iWhich, int cArgs, char **papszArgs) +{ + SUPR3HARDENED_ASSERT(g_cSuplibHardenedWindowsMainCalls == 1); + SUPR3HARDENED_ASSERT(iWhich == 1 || iWhich == 2); + + if (cArgs < 1) + return true; + + if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0) + { + if (iWhich > 1) + return true; + } + else if (suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0) + { + if (iWhich < 2) + return false; + } + else + return true; + + /* Replace the argument. */ + papszArgs[0] = g_szSupLibHardenedExePath; + return false; +} + + +/** + * Initializes the windows verficiation bits and other things we're better off + * doing after main() has passed on it's data. + * + * @param fFlags The main flags. + * @param fAvastKludge Whether to apply the avast kludge. + */ +DECLHIDDEN(void) supR3HardenedWinInit(uint32_t fFlags, bool fAvastKludge) +{ + NTSTATUS rcNt; + +#ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + /* + * Install a anti debugging hack before we continue. This prevents most + * notifications from ending up in the debugger. (Also applied to the + * child process when respawning.) + */ + rcNt = NtSetInformationThread(NtCurrentThread(), ThreadHideFromDebugger, NULL, 0); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, VERR_GENERAL_FAILURE, + "NtSetInformationThread/ThreadHideFromDebugger failed: %#x\n", rcNt); +#endif + + /* + * Init the verifier. + */ + RTErrInfoInitStatic(&g_ErrInfoStatic); + int rc = supHardenedWinInitImageVerifier(&g_ErrInfoStatic.Core); + if (RT_FAILURE(rc)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rc, + "supHardenedWinInitImageVerifier failed: %s", g_ErrInfoStatic.szMsg); + + /* + * Get the windows system directory from the KnownDlls dir. + */ + HANDLE hSymlink = INVALID_HANDLE_VALUE; + UNICODE_STRING UniStr = RTNT_CONSTANT_UNISTR(L"\\KnownDlls\\KnownDllPath"); + OBJECT_ATTRIBUTES ObjAttrs; + InitializeObjectAttributes(&ObjAttrs, &UniStr, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + rcNt = NtOpenSymbolicLinkObject(&hSymlink, SYMBOLIC_LINK_QUERY, &ObjAttrs); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error opening '%ls': %#x", UniStr.Buffer, rcNt); + + g_System32WinPath.UniStr.Buffer = g_System32WinPath.awcBuffer; + g_System32WinPath.UniStr.Length = 0; + g_System32WinPath.UniStr.MaximumLength = sizeof(g_System32WinPath.awcBuffer) - sizeof(RTUTF16); + rcNt = NtQuerySymbolicLinkObject(hSymlink, &g_System32WinPath.UniStr, NULL); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatalMsg("supR3HardenedWinInit", kSupInitOp_Misc, rcNt, "Error querying '%ls': %#x", UniStr.Buffer, rcNt); + g_System32WinPath.UniStr.Buffer[g_System32WinPath.UniStr.Length / sizeof(RTUTF16)] = '\0'; + + SUP_DPRINTF(("KnownDllPath: %ls\n", g_System32WinPath.UniStr.Buffer)); + NtClose(hSymlink); + + if (!(fFlags & SUPSECMAIN_FLAGS_DONT_OPEN_DEV)) + { + if (fAvastKludge) + { + /* + * Do a self purification to cure avast's weird NtOpenFile write-thru + * change in GetBinaryTypeW change in kernel32. Unfortunately, avast + * uses a system thread to perform the process modifications, which + * means it's hard to make sure it had the chance to make them... + * + * We have to resort to kludge doing yield and sleep fudging for a + * number of milliseconds and schedulings before we can hope that avast + * and similar products have done what they need to do. If we do any + * fixes, we wait for a while again and redo it until we're clean. + * + * This is unfortunately kind of fragile. + */ + uint32_t cMsFudge = g_fSupAdversaries ? 512 : 128; + uint32_t cFixes; + for (uint32_t iLoop = 0; iLoop < 16; iLoop++) + { + uint32_t cSleeps = 0; + uint64_t uMsTsStart = supR3HardenedWinGetMilliTS(); + do + { + NtYieldExecution(); + LARGE_INTEGER Time; + Time.QuadPart = -8000000 / 100; /* 8ms in 100ns units, relative time. */ + NtDelayExecution(FALSE, &Time); + cSleeps++; + } while ( supR3HardenedWinGetMilliTS() - uMsTsStart <= cMsFudge + || cSleeps < 8); + SUP_DPRINTF(("supR3HardenedWinInit: Startup delay kludge #2/%u: %u ms, %u sleeps\n", + iLoop, supR3HardenedWinGetMilliTS() - uMsTsStart, cSleeps)); + + cFixes = 0; + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION, + 0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/); + if (RT_FAILURE(rc) || cFixes == 0) + break; + + if (!g_fSupAdversaries) + g_fSupAdversaries |= SUPHARDNT_ADVERSARY_UNKNOWN; + cMsFudge = 512; + + /* Log the KiOpPrefetchPatchCount value if available, hoping it might sched some light on spider38's case. */ + ULONG cPatchCount = 0; + rcNt = NtQuerySystemInformation(SystemInformation_KiOpPrefetchPatchCount, + &cPatchCount, sizeof(cPatchCount), NULL); + if (NT_SUCCESS(rcNt)) + SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x cPatchCount=%#u\n", + cFixes, g_fSupAdversaries, cPatchCount)); + else + SUP_DPRINTF(("supR3HardenedWinInit: cFixes=%u g_fSupAdversaries=%#x\n", cFixes, g_fSupAdversaries)); + } + } + + /* + * Install the hooks. + */ + supR3HardenedWinInstallHooks(); + } + else if (fFlags & SUPSECMAIN_FLAGS_FIRST_PROCESS) + { + /* + * Try shake anyone (e.g. easyhook) patching process creation code in + * kernelbase, kernel32 or ntdll so they won't so easily cause the child + * to crash when we respawn and purify it. + */ + SUP_DPRINTF(("supR3HardenedWinInit: Performing a limited self purification...\n")); + uint32_t cFixes = 0; + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED, + 0 /*fFlags*/, &cFixes, NULL /*pErrInfo*/); + SUP_DPRINTF(("supR3HardenedWinInit: SUPHARDNTVPKIND_SELF_PURIFICATION_LIMITED -> %Rrc, cFixes=%d\n", rc, cFixes)); + RT_NOREF(rc); /* ignored on purpose */ + } + +#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 +} + + +/** + * Modifies the DLL search path for testcases. + * + * This makes sure the application binary path is in the search path. When + * starting a testcase executable in the testcase/ subdirectory this isn't the + * case by default. So, unless we do something about it we won't be able to + * import VBox DLLs. + * + * @param fFlags The main flags (giving the location). + * @param pszAppBinPath The path to the application binary directory + * (windows style). + */ +DECLHIDDEN(void) supR3HardenedWinModifyDllSearchPath(uint32_t fFlags, const char *pszAppBinPath) +{ + /* + * For the testcases to work, we must add the app bin directory to the + * DLL search list before the testcase dll is loaded or it won't be + * able to find the VBox DLLs. This is done _after_ VBoxRT.dll is + * initialized and sets its defaults. + */ + switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + break; + default: + return; + } + + /* + * Dynamically resolve the two APIs we need (the latter uses forwarders on w7). + */ + HMODULE hModKernel32 = GetModuleHandleW(L"kernel32.dll"); + + typedef BOOL (WINAPI *PFNSETDLLDIRECTORY)(LPCWSTR); + PFNSETDLLDIRECTORY pfnSetDllDir; + pfnSetDllDir = (PFNSETDLLDIRECTORY)GetProcAddress(hModKernel32, "SetDllDirectoryW"); + + typedef BOOL (WINAPI *PFNSETDEFAULTDLLDIRECTORIES)(DWORD); + PFNSETDEFAULTDLLDIRECTORIES pfnSetDefDllDirs; + pfnSetDefDllDirs = (PFNSETDEFAULTDLLDIRECTORIES)GetProcAddress(hModKernel32, "SetDefaultDllDirectories"); + + if (pfnSetDllDir != NULL) + { + /* + * Convert the path to UTF-16 and try set it. + */ + PRTUTF16 pwszAppBinPath = NULL; + int rc = RTStrToUtf16(pszAppBinPath, &pwszAppBinPath); + if (RT_SUCCESS(rc)) + { + if (pfnSetDllDir(pwszAppBinPath)) + { + SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Set dll dir to '%ls'\n", pwszAppBinPath)); + g_fSupLibHardenedDllSearchUserDirs = true; + + /* + * We set it alright, on W7 and later we also must modify the + * default DLL search order. See @bugref{6861} for details on + * why we don't do this on Vista (also see init-win.cpp in IPRT). + */ + if ( pfnSetDefDllDirs + && g_uNtVerCombined >= SUP_NT_VER_W70) + { + if (pfnSetDefDllDirs( LOAD_LIBRARY_SEARCH_APPLICATION_DIR + | LOAD_LIBRARY_SEARCH_SYSTEM32 + | LOAD_LIBRARY_SEARCH_USER_DIRS)) + SUP_DPRINTF(("supR3HardenedWinModifyDllSearchPath: Successfully modified search dirs.\n")); + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n", + pwszAppBinPath, RtlGetLastWin32Error()); + } + } + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: SetDllDirectoryW(%ls) failed: %d\n", + pwszAppBinPath, RtlGetLastWin32Error()); + RTUtf16Free(pwszAppBinPath); + } + else + supR3HardenedFatal("supR3HardenedWinModifyDllSearchPath: RTStrToUtf16(%s) failed: %d\n", pszAppBinPath, rc); + } +} + + +/** + * Initializes the application binary directory path. + * + * This is called once or twice. + * + * @param fFlags The main flags (giving the location). + */ +DECLHIDDEN(void) supR3HardenedWinInitAppBin(uint32_t fFlags) +{ + USHORT cwc = (USHORT)g_offSupLibHardenedExeNtName - 1; + g_SupLibHardenedAppBinNtPath.UniStr.Buffer = g_SupLibHardenedAppBinNtPath.awcBuffer; + memcpy(g_SupLibHardenedAppBinNtPath.UniStr.Buffer, g_SupLibHardenedExeNtPath.UniStr.Buffer, cwc * sizeof(WCHAR)); + + switch (fFlags & SUPSECMAIN_FLAGS_LOC_MASK) + { + case SUPSECMAIN_FLAGS_LOC_APP_BIN: + break; + case SUPSECMAIN_FLAGS_LOC_TESTCASE: + { + /* Drop one directory level. */ + USHORT off = cwc; + WCHAR wc; + while ( off > 1 + && (wc = g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 1]) != '\0') + if (wc != '\\' && wc != '/') + off--; + else + { + if (g_SupLibHardenedAppBinNtPath.UniStr.Buffer[off - 2] == ':') + cwc = off; + else + cwc = off - 1; + break; + } + break; + } + default: + supR3HardenedFatal("supR3HardenedWinInitAppBin: Unknown program binary location: %#x\n", fFlags); + } + + g_SupLibHardenedAppBinNtPath.UniStr.Buffer[cwc] = '\0'; + g_SupLibHardenedAppBinNtPath.UniStr.Length = cwc * sizeof(WCHAR); + g_SupLibHardenedAppBinNtPath.UniStr.MaximumLength = sizeof(g_SupLibHardenedAppBinNtPath.awcBuffer); + SUP_DPRINTF(("supR3HardenedWinInitAppBin(%#x): '%ls'\n", fFlags, g_SupLibHardenedAppBinNtPath.UniStr.Buffer)); +} + + +/** + * 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 pawcCmdLine The UTF-16 windows command line to parse. + * @param cwcCmdLine The length of the command line. + * @param pcArgs Where to return the number of arguments. + */ +static char **suplibCommandLineToArgvWStub(PCRTUTF16 pawcCmdLine, size_t cwcCmdLine, int *pcArgs) +{ + /* + * Convert the command line string to UTF-8. + */ + char *pszCmdLine = NULL; + SUPR3HARDENED_ASSERT(RT_SUCCESS(RTUtf16ToUtf8Ex(pawcCmdLine, cwcCmdLine, &pszCmdLine, 0, NULL))); + + /* + * Parse the command line, carving argument strings out of it. + */ + int cArgs = 0; + int cArgsAllocated = 4; + char **papszArgs = (char **)RTMemAllocZ(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 **)RTMemRealloc(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; +} + + +/** + * Worker for supR3HardenedFindVersionRsrcOffset. + * + * @returns RVA the version resource data, UINT32_MAX if not found. + * @param pRootDir The root resource directory. Expects data to + * follow. + * @param cbBuf The amount of data at pRootDir. + * @param offData The offset to the data entry. + * @param pcbData Where to return the size of the data. + */ +static uint32_t supR3HardenedGetRvaFromRsrcDataEntry(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t offData, + uint32_t *pcbData) +{ + if ( offData <= cbBuf + && offData + sizeof(IMAGE_RESOURCE_DATA_ENTRY) <= cbBuf) + { + PIMAGE_RESOURCE_DATA_ENTRY pRsrcData = (PIMAGE_RESOURCE_DATA_ENTRY)((uintptr_t)pRootDir + offData); + SUP_DPRINTF((" [Raw version resource data: %#x LB %#x, codepage %#x (reserved %#x)]\n", + pRsrcData->OffsetToData, pRsrcData->Size, pRsrcData->CodePage, pRsrcData->Reserved)); + if (pRsrcData->Size > 0) + { + *pcbData = pRsrcData->Size; + return pRsrcData->OffsetToData; + } + } + else + SUP_DPRINTF((" Version resource data (%#x) is outside the buffer (%#x)! :-(\n", offData, cbBuf)); + + *pcbData = 0; + return UINT32_MAX; +} + + +/** @def SUP_RSRC_DPRINTF + * Dedicated debug printf for resource directory parsing. + * @sa SUP_DPRINTF + */ +#if 0 /* more details */ +# define SUP_RSRC_DPRINTF(a) SUP_DPRINTF(a) +#else +# define SUP_RSRC_DPRINTF(a) do { } while (0) +#endif + +/** + * Scans the resource directory for a version resource. + * + * @returns RVA of the version resource data, UINT32_MAX if not found. + * @param pRootDir The root resource directory. Expects data to + * follow. + * @param cbBuf The amount of data at pRootDir. + * @param pcbData Where to return the size of the version data. + */ +static uint32_t supR3HardenedFindVersionRsrcRva(PIMAGE_RESOURCE_DIRECTORY pRootDir, uint32_t cbBuf, uint32_t *pcbData) +{ + SUP_RSRC_DPRINTF((" ResDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + pRootDir->Characteristics, + pRootDir->TimeDateStamp, + pRootDir->MajorVersion, + pRootDir->MinorVersion, + pRootDir->NumberOfNamedEntries, + pRootDir->NumberOfIdEntries)); + + PIMAGE_RESOURCE_DIRECTORY_ENTRY paEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRootDir + 1); + unsigned cMaxEntries = (cbBuf - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cEntries = pRootDir->NumberOfNamedEntries + pRootDir->NumberOfIdEntries; + if (cEntries > cMaxEntries) + cEntries = cMaxEntries; + for (unsigned i = 0; i < cEntries; i++) + { + if (!paEntries[i].NameIsString) + { + if (!paEntries[i].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + i, paEntries[i].Id, paEntries[i].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + i, paEntries[i].Id, paEntries[i].OffsetToDirectory)); + } + else + { + if (!paEntries[i].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + i, paEntries[i].NameOffset, paEntries[i].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + i, paEntries[i].NameOffset, paEntries[i].OffsetToDirectory)); + } + + /* + * Look for the version resource type. Skip to the next entry if not found. + */ + if (paEntries[i].NameIsString) + continue; + if (paEntries[i].Id != 0x10 /*RT_VERSION*/) + continue; + if (!paEntries[i].DataIsDirectory) + { + SUP_DPRINTF((" #%u: ID: #%#06x Data: %#010x - WEIRD!\n", i, paEntries[i].Id, paEntries[i].OffsetToData)); + continue; + } + SUP_RSRC_DPRINTF((" Version resource dir entry #%u: dir offset: %#x (cbBuf=%#x)\n", + i, paEntries[i].OffsetToDirectory, cbBuf)); + + /* + * Locate the sub-resource directory for it. + */ + if (paEntries[i].OffsetToDirectory >= cbBuf) + { + SUP_DPRINTF((" Version resource dir is outside the buffer! :-(\n")); + continue; + } + uint32_t cbMax = cbBuf - paEntries[i].OffsetToDirectory; + if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) + { + SUP_DPRINTF((" Version resource dir entry #0 is outside the buffer! :-(\n")); + continue; + } + PIMAGE_RESOURCE_DIRECTORY pVerDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paEntries[i].OffsetToDirectory); + SUP_RSRC_DPRINTF((" VerDir: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + pVerDir->Characteristics, + pVerDir->TimeDateStamp, + pVerDir->MajorVersion, + pVerDir->MinorVersion, + pVerDir->NumberOfNamedEntries, + pVerDir->NumberOfIdEntries)); + PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerDir + 1); + unsigned cMaxVerEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cVerEntries = pVerDir->NumberOfNamedEntries + pVerDir->NumberOfIdEntries; + if (cVerEntries > cMaxVerEntries) + cVerEntries = cMaxVerEntries; + for (unsigned iVer = 0; iVer < cVerEntries; iVer++) + { + if (!paVerEntries[iVer].NameIsString) + { + if (!paVerEntries[iVer].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + iVer, paVerEntries[iVer].Id, paVerEntries[iVer].OffsetToDirectory)); + } + else + { + if (!paVerEntries[iVer].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + iVer, paVerEntries[iVer].NameOffset, paVerEntries[iVer].OffsetToDirectory)); + } + if (!paVerEntries[iVer].DataIsDirectory) + { + SUP_DPRINTF((" [Version info resource found at %#x! (ID/Name: #%#x)]\n", + paVerEntries[iVer].OffsetToData, paVerEntries[iVer].Name)); + return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerEntries[iVer].OffsetToData, pcbData); + } + + /* + * Check out the next directory level. + */ + if (paVerEntries[iVer].OffsetToDirectory >= cbBuf) + { + SUP_DPRINTF((" Version resource subdir is outside the buffer! :-(\n")); + continue; + } + cbMax = cbBuf - paVerEntries[iVer].OffsetToDirectory; + if (cbMax < sizeof(IMAGE_RESOURCE_DIRECTORY) + sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY)) + { + SUP_DPRINTF((" Version resource subdir entry #0 is outside the buffer! :-(\n")); + continue; + } + PIMAGE_RESOURCE_DIRECTORY pVerSubDir = (PIMAGE_RESOURCE_DIRECTORY)((uintptr_t)pRootDir + paVerEntries[iVer].OffsetToDirectory); + SUP_RSRC_DPRINTF((" VerSubDir#%u: Char=%#x Time=%#x Ver=%d%d #NamedEntries=%#x #IdEntries=%#x\n", + iVer, + pVerSubDir->Characteristics, + pVerSubDir->TimeDateStamp, + pVerSubDir->MajorVersion, + pVerSubDir->MinorVersion, + pVerSubDir->NumberOfNamedEntries, + pVerSubDir->NumberOfIdEntries)); + PIMAGE_RESOURCE_DIRECTORY_ENTRY paVerSubEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pVerSubDir + 1); + unsigned cMaxVerSubEntries = (cbMax - sizeof(IMAGE_RESOURCE_DIRECTORY)) / sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY); + unsigned cVerSubEntries = pVerSubDir->NumberOfNamedEntries + pVerSubDir->NumberOfIdEntries; + if (cVerSubEntries > cMaxVerSubEntries) + cVerSubEntries = cMaxVerSubEntries; + for (unsigned iVerSub = 0; iVerSub < cVerSubEntries; iVerSub++) + { + if (!paVerSubEntries[iVerSub].NameIsString) + { + if (!paVerSubEntries[iVerSub].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Data: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: ID: #%#06x Dir: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].Id, paVerSubEntries[iVerSub].OffsetToDirectory)); + } + else + { + if (!paVerSubEntries[iVerSub].DataIsDirectory) + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Data: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToData)); + else + SUP_RSRC_DPRINTF((" #%u: Name: #%#06x Dir: %#010x\n", + iVerSub, paVerSubEntries[iVerSub].NameOffset, paVerSubEntries[iVerSub].OffsetToDirectory)); + } + if (!paVerSubEntries[iVerSub].DataIsDirectory) + { + SUP_DPRINTF((" [Version info resource found at %#x! (ID/Name: %#x; SubID/SubName: %#x)]\n", + paVerSubEntries[iVerSub].OffsetToData, paVerEntries[iVer].Name, paVerSubEntries[iVerSub].Name)); + return supR3HardenedGetRvaFromRsrcDataEntry(pRootDir, cbBuf, paVerSubEntries[iVerSub].OffsetToData, pcbData); + } + } + } + } + + *pcbData = 0; + return UINT32_MAX; +} + + +/** + * Logs information about a file from a protection product or from Windows, + * optionally returning the file version. + * + * The purpose here is to better see which version of the product is installed + * and not needing to depend on the user supplying the correct information. + * + * @param pwszFile The NT path to the file. + * @param pwszFileVersion Where to return the file version, if found. NULL if + * not interested. + * @param cwcFileVersion The size of the file version buffer (UTF-16 units). + */ +static void supR3HardenedLogFileInfo(PCRTUTF16 pwszFile, PRTUTF16 pwszFileVersion, size_t cwcFileVersion) +{ + /* + * Make sure the file version is always set when we return. + */ + if (pwszFileVersion && cwcFileVersion) + *pwszFileVersion = '\0'; + + /* + * Open the file. + */ + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING UniStrName; + UniStrName.Buffer = (WCHAR *)pwszFile; + UniStrName.Length = (USHORT)(RTUtf16Len(pwszFile) * sizeof(WCHAR)); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + NTSTATUS rcNt = NtCreateFile(&hFile, + GENERIC_READ | 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)) + { + SUP_DPRINTF(("%ls:\n", pwszFile)); + union + { + uint64_t u64AlignmentInsurance; + FILE_BASIC_INFORMATION BasicInfo; + FILE_STANDARD_INFORMATION StdInfo; + uint8_t abBuf[32768]; + RTUTF16 awcBuf[16384]; + IMAGE_DOS_HEADER MzHdr; + IMAGE_RESOURCE_DIRECTORY ResDir; + } u; + RTTIMESPEC TimeSpec; + char szTmp[64]; + + /* + * Print basic file information available via NtQueryInformationFile. + */ + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryInformationFile(hFile, &Ios, &u.BasicInfo, sizeof(u.BasicInfo), FileBasicInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + SUP_DPRINTF((" CreationTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.CreationTime.QuadPart), szTmp, sizeof(szTmp)))); + /*SUP_DPRINTF((" LastAccessTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastAccessTime.QuadPart), szTmp, sizeof(szTmp))));*/ + SUP_DPRINTF((" LastWriteTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.LastWriteTime.QuadPart), szTmp, sizeof(szTmp)))); + SUP_DPRINTF((" ChangeTime: %s\n", RTTimeSpecToString(RTTimeSpecSetNtTime(&TimeSpec, u.BasicInfo.ChangeTime.QuadPart), szTmp, sizeof(szTmp)))); + SUP_DPRINTF((" FileAttributes: %#x\n", u.BasicInfo.FileAttributes)); + } + else + SUP_DPRINTF((" FileBasicInformation -> %#x %#x\n", rcNt, Ios.Status)); + + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + rcNt = NtQueryInformationFile(hFile, &Ios, &u.StdInfo, sizeof(u.StdInfo), FileStandardInformation); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + SUP_DPRINTF((" Size: %#llx\n", u.StdInfo.EndOfFile.QuadPart)); + else + SUP_DPRINTF((" FileStandardInformation -> %#x %#x\n", rcNt, Ios.Status)); + + /* + * Read the image header and extract the timestamp and other useful info. + */ + RT_ZERO(u); + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + LARGE_INTEGER offRead; + offRead.QuadPart = 0; + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + uint32_t offNtHdrs = 0; + if (u.MzHdr.e_magic == IMAGE_DOS_SIGNATURE) + offNtHdrs = u.MzHdr.e_lfanew; + if (offNtHdrs < sizeof(u) - sizeof(IMAGE_NT_HEADERS)) + { + PIMAGE_NT_HEADERS64 pNtHdrs64 = (PIMAGE_NT_HEADERS64)&u.abBuf[offNtHdrs]; + PIMAGE_NT_HEADERS32 pNtHdrs32 = (PIMAGE_NT_HEADERS32)&u.abBuf[offNtHdrs]; + if (pNtHdrs64->Signature == IMAGE_NT_SIGNATURE) + { + SUP_DPRINTF((" NT Headers: %#x\n", offNtHdrs)); + SUP_DPRINTF((" Timestamp: %#x\n", pNtHdrs64->FileHeader.TimeDateStamp)); + SUP_DPRINTF((" Machine: %#x%s\n", pNtHdrs64->FileHeader.Machine, + pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_I386 ? " - i386" + : pNtHdrs64->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ? " - amd64" : "")); + SUP_DPRINTF((" Timestamp: %#x\n", pNtHdrs64->FileHeader.TimeDateStamp)); + SUP_DPRINTF((" Image Version: %u.%u\n", + pNtHdrs64->OptionalHeader.MajorImageVersion, pNtHdrs64->OptionalHeader.MinorImageVersion)); + SUP_DPRINTF((" SizeOfImage: %#x (%u)\n", pNtHdrs64->OptionalHeader.SizeOfImage, pNtHdrs64->OptionalHeader.SizeOfImage)); + + /* + * Very crude way to extract info from the file version resource. + */ + PIMAGE_SECTION_HEADER paSectHdrs = (PIMAGE_SECTION_HEADER)( (uintptr_t)&pNtHdrs64->OptionalHeader + + pNtHdrs64->FileHeader.SizeOfOptionalHeader); + IMAGE_DATA_DIRECTORY RsrcDir = { 0, 0 }; + if ( pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER64) + && pNtHdrs64->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE) + RsrcDir = pNtHdrs64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; + else if ( pNtHdrs64->FileHeader.SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32) + && pNtHdrs32->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE) + RsrcDir = pNtHdrs32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]; + SUP_DPRINTF((" Resource Dir: %#x LB %#x\n", RsrcDir.VirtualAddress, RsrcDir.Size)); + if ( RsrcDir.VirtualAddress > offNtHdrs + && RsrcDir.Size > 0 + && (uintptr_t)&u + sizeof(u) - (uintptr_t)paSectHdrs + >= pNtHdrs64->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) ) + { + uint32_t uRvaRsrcSect = 0; + uint32_t cbRsrcSect = 0; + uint32_t offRsrcSect = 0; + offRead.QuadPart = 0; + for (uint32_t i = 0; i < pNtHdrs64->FileHeader.NumberOfSections; i++) + { + uRvaRsrcSect = paSectHdrs[i].VirtualAddress; + cbRsrcSect = paSectHdrs[i].Misc.VirtualSize; + offRsrcSect = paSectHdrs[i].PointerToRawData; + if ( RsrcDir.VirtualAddress - uRvaRsrcSect < cbRsrcSect + && offRsrcSect > offNtHdrs) + { + offRead.QuadPart = offRsrcSect + (RsrcDir.VirtualAddress - uRvaRsrcSect); + break; + } + } + if (offRead.QuadPart > 0) + { + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + RT_ZERO(u); + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + PCRTUTF16 pwcVersionData = &u.awcBuf[0]; + size_t cbVersionData = sizeof(u); + + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + /* Make it less crude by try find the version resource data. */ + uint32_t cbVersion; + uint32_t uRvaVersion = supR3HardenedFindVersionRsrcRva(&u.ResDir, sizeof(u), &cbVersion); + NOREF(uRvaVersion); + if ( uRvaVersion != UINT32_MAX + && cbVersion < cbRsrcSect + && uRvaVersion - uRvaRsrcSect <= cbRsrcSect - cbVersion) + { + uint32_t const offVersion = uRvaVersion - uRvaRsrcSect; + if ( offVersion < sizeof(u) + && offVersion + cbVersion <= sizeof(u)) + { + pwcVersionData = (PCRTUTF16)&u.abBuf[offVersion]; + cbVersionData = cbVersion; + } + else + { + offRead.QuadPart = offVersion + offRsrcSect; + RT_ZERO(u); + rcNt = NtReadFile(hFile, NULL /*hEvent*/, NULL /*ApcRoutine*/, NULL /*ApcContext*/, &Ios, + &u, (ULONG)sizeof(u), &offRead, NULL); + pwcVersionData = &u.awcBuf[0]; + cbVersionData = RT_MIN(cbVersion, sizeof(u)); + } + } + } + + if (NT_SUCCESS(rcNt) && NT_SUCCESS(Ios.Status)) + { + static const struct { PCRTUTF16 pwsz; size_t cb; bool fRet; } s_abFields[] = + { +#define MY_WIDE_STR_TUPLE(a_sz, a_fRet) { L ## a_sz, sizeof(L ## a_sz) - sizeof(RTUTF16), a_fRet } + MY_WIDE_STR_TUPLE("ProductName", false), + MY_WIDE_STR_TUPLE("ProductVersion", false), + MY_WIDE_STR_TUPLE("FileVersion", true), + MY_WIDE_STR_TUPLE("SpecialBuild", false), + MY_WIDE_STR_TUPLE("PrivateBuild", false), + MY_WIDE_STR_TUPLE("FileDescription", false), +#undef MY_WIDE_STR_TUPLE + }; + for (uint32_t i = 0; i < RT_ELEMENTS(s_abFields); i++) + { + if (cbVersionData <= s_abFields[i].cb + 10) + continue; + size_t cwcLeft = (cbVersionData - s_abFields[i].cb - 10) / sizeof(RTUTF16); + PCRTUTF16 pwc = pwcVersionData; + RTUTF16 const wcFirst = *s_abFields[i].pwsz; + while (cwcLeft-- > 0) + { + if ( pwc[0] == 1 /* wType == text */ + && pwc[1] == wcFirst) + { + if (memcmp(pwc + 1, s_abFields[i].pwsz, s_abFields[i].cb + sizeof(RTUTF16)) == 0) + { + size_t cwcField = s_abFields[i].cb / sizeof(RTUTF16); + pwc += cwcField + 2; + cwcLeft -= cwcField + 2; + for (uint32_t iPadding = 0; iPadding < 3; iPadding++, pwc++, cwcLeft--) + if (*pwc) + break; + int rc = RTUtf16ValidateEncodingEx(pwc, cwcLeft, + RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); + if (RT_SUCCESS(rc)) + { + SUP_DPRINTF((" %ls:%*s %ls", + s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", pwc)); + if ( s_abFields[i].fRet + && pwszFileVersion + && cwcFileVersion > 1) + RTUtf16Copy(pwszFileVersion, cwcFileVersion, pwc); + } + else + SUP_DPRINTF((" %ls:%*s rc=%Rrc", + s_abFields[i].pwsz, cwcField < 15 ? 15 - cwcField : 0, "", rc)); + + break; + } + } + pwc++; + } + } + } + else + SUP_DPRINTF((" NtReadFile @%#llx -> %#x %#x\n", offRead.QuadPart, rcNt, Ios.Status)); + } + else + SUP_DPRINTF((" Resource section not found.\n")); + } + } + else + SUP_DPRINTF((" Nt Headers @%#x: Invalid signature\n", offNtHdrs)); + } + else + SUP_DPRINTF((" Nt Headers @%#x: out side buffer\n", offNtHdrs)); + } + else + SUP_DPRINTF((" NtReadFile @0 -> %#x %#x\n", rcNt, Ios.Status)); + NtClose(hFile); + } +} + + +/** + * Scans the Driver directory for drivers which may invade our processes. + * + * @returns Mask of SUPHARDNT_ADVERSARY_XXX flags. + * + * @remarks The enumeration of \\Driver normally requires administrator + * privileges. So, the detection we're doing here isn't always gonna + * work just based on that. + * + * @todo Find drivers in \\FileSystems as well, then we could detect VrNsdDrv + * from ViRobot APT Shield 2.0. + */ +static uint32_t supR3HardenedWinFindAdversaries(void) +{ + static const struct + { + uint32_t fAdversary; + const char *pszDriver; + } s_aDrivers[] = + { + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, "SysPlant" }, + + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SRTSPX" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymDS" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymEvent" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymIRON" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, "SymNetS" }, + + { SUPHARDNT_ADVERSARY_AVAST, "aswHwid" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswMonFlt" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswRdr2" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswRvrt" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswSnx" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswsp" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswStm" }, + { SUPHARDNT_ADVERSARY_AVAST, "aswVmm" }, + + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmcomm" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmactmon" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmevtmgr" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmtdi" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmebc64" }, /* Titanium internet security, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmeevw" }, /* Titanium internet security, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, "tmciesc" }, /* Titanium internet security, not officescan. */ + + { SUPHARDNT_ADVERSARY_MCAFEE, "cfwids" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "McPvDrv" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfeapfk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfeavfk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfefirek" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfehidk" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfencbdc" }, + { SUPHARDNT_ADVERSARY_MCAFEE, "mfewfpk" }, + + { SUPHARDNT_ADVERSARY_KASPERSKY, "kl1" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klif" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "KLIM6" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klkbdflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "klmouflt" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "kltdi" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, "kneps" }, + + { SUPHARDNT_ADVERSARY_MBAM, "MBAMWebAccessControl" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbam" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbamchameleon" }, + { SUPHARDNT_ADVERSARY_MBAM, "mwav" }, + { SUPHARDNT_ADVERSARY_MBAM, "mbamswissarmy" }, + + { SUPHARDNT_ADVERSARY_AVG, "avgfwfd" }, + { SUPHARDNT_ADVERSARY_AVG, "avgtdia" }, + + { SUPHARDNT_ADVERSARY_PANDA, "PSINAflt" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINFile" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINKNC" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINProc" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINProt" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSINReg" }, + { SUPHARDNT_ADVERSARY_PANDA, "PSKMAD" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSAlpc" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSHttp" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNShttps" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSIds" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSNAHSL" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSpicc" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPihsw" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPop3" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSProt" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSPrv" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSSmtp" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNSStrm" }, + { SUPHARDNT_ADVERSARY_PANDA, "NNStlsc" }, + + { SUPHARDNT_ADVERSARY_MSE, "NisDrv" }, + + /*{ SUPHARDNT_ADVERSARY_COMODO, "cmdguard" }, file system */ + { SUPHARDNT_ADVERSARY_COMODO, "inspect" }, + { SUPHARDNT_ADVERSARY_COMODO, "cmdHlp" }, + + { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, "dgmaster" }, + + { SUPHARDNT_ADVERSARY_CYLANCE, "cyprotectdrv" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_BEYONDTRUST, "privman" }, /* Not verified. */ + { SUPHARDNT_ADVERSARY_BEYONDTRUST, "privmanfi" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_AVECTO, "PGDriver" }, + + { SUPHARDNT_ADVERSARY_SOPHOS, "SophosED" }, /* Not verified. */ + + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, "vmwicpdr" }, + }; + + static const struct + { + uint32_t fAdversary; + PCRTUTF16 pwszFile; + } s_aFiles[] = + { + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\drivers\\SysPlant.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysfer.dll" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_SYSPLANT, L"\\SystemRoot\\System32\\sysferThunk.dll" }, + + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ccsetx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\ironx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtsp64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\srtspx64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symds64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symefa64.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symelam.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\N360x64\\1505000.013\\symnets.sys" }, + { SUPHARDNT_ADVERSARY_SYMANTEC_N360, L"\\SystemRoot\\System32\\drivers\\symevent64x86.sys" }, + + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswHwid.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswMonFlt.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRdr2.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswRvrt.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswSnx.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswsp.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswStm.sys" }, + { SUPHARDNT_ADVERSARY_AVAST, L"\\SystemRoot\\System32\\drivers\\aswVmm.sys" }, + + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmcomm.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmactmon.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmevtmgr.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmtdi.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmebc64.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmeevw.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\tmciesc.sys" }, + { SUPHARDNT_ADVERSARY_TRENDMICRO_SAKFILE, L"\\SystemRoot\\System32\\drivers\\sakfile.sys" }, /* Data Loss Prevention, not officescan. */ + { SUPHARDNT_ADVERSARY_TRENDMICRO, L"\\SystemRoot\\System32\\drivers\\sakcd.sys" }, /* Data Loss Prevention, not officescan. */ + + + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\cfwids.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\McPvDrv.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeapfk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfeavfk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfefirek.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfehidk.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfencbdc.sys" }, + { SUPHARDNT_ADVERSARY_MCAFEE, L"\\SystemRoot\\System32\\drivers\\mfewfpk.sys" }, + + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kl1.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klif.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klim6.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klkbdflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\klmouflt.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kltdi.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\drivers\\kneps.sys" }, + { SUPHARDNT_ADVERSARY_KASPERSKY, L"\\SystemRoot\\System32\\klfphc.dll" }, + + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\MBAMSwissArmy.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mwac.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbamchameleon.sys" }, + { SUPHARDNT_ADVERSARY_MBAM, L"\\SystemRoot\\System32\\drivers\\mbam.sys" }, + + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgrkx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgmfx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsdrivera.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgidsha.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgtdia.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgloga.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgldx64.sys" }, + { SUPHARDNT_ADVERSARY_AVG, L"\\SystemRoot\\System32\\drivers\\avgdiska.sys" }, + + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINAflt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINFile.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINKNC.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINProt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSINReg.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\PSKMAD.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSAlpc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSHttp.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNShttps.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSIds.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSNAHSL.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSpicc.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPihsw.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPop3.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSProt.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSPrv.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSSmtp.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNSStrm.sys" }, + { SUPHARDNT_ADVERSARY_PANDA, L"\\SystemRoot\\System32\\drivers\\NNStlsc.sys" }, + + { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\MpFilter.sys" }, + { SUPHARDNT_ADVERSARY_MSE, L"\\SystemRoot\\System32\\drivers\\NisDrvWFP.sys" }, + + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdguard.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmderd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\inspect.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cmdhlp.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\cfrmd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\drivers\\hmd.sys" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\guard64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdvrt64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdkbd64.dll" }, + { SUPHARDNT_ADVERSARY_COMODO, L"\\SystemRoot\\System32\\cmdcsr.dll" }, + + { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\drivers\\vsdatant.sys" }, + { SUPHARDNT_ADVERSARY_ZONE_ALARM, L"\\SystemRoot\\System32\\AntiTheftCredentialProvider.dll" }, + + { SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD, L"\\SystemRoot\\System32\\drivers\\dgmaster.sys" }, + + { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv32.sys" }, + { SUPHARDNT_ADVERSARY_CYLANCE, L"\\SystemRoot\\System32\\drivers\\cyprotectdrv64.sys" }, + + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privman.sys" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\drivers\\privmanfi.sys" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman64.dll" }, + { SUPHARDNT_ADVERSARY_BEYONDTRUST, L"\\SystemRoot\\System32\\privman32.dll" }, + + { SUPHARDNT_ADVERSARY_AVECTO, L"\\SystemRoot\\System32\\drivers\\PGDriver.sys" }, + + { SUPHARDNT_ADVERSARY_SOPHOS, L"\\SystemRoot\\System32\\drivers\\SophosED.sys" }, // not verified + + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\vmwicpdr.sys" }, + { SUPHARDNT_ADVERSARY_HORIZON_VIEW_AGENT, L"\\SystemRoot\\System32\\drivers\\ftsjail.sys" }, + }; + + uint32_t fFound = 0; + + /* + * Open the driver object directory. + */ + UNICODE_STRING NtDirName = RTNT_CONSTANT_UNISTR(L"\\Driver"); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + + HANDLE hDir; + NTSTATUS rcNt = NtOpenDirectoryObject(&hDir, DIRECTORY_QUERY | FILE_LIST_DIRECTORY, &ObjAttr); +#ifdef VBOX_STRICT + if (rcNt != STATUS_ACCESS_DENIED) /* non-admin */ + SUPR3HARDENED_ASSERT_NT_SUCCESS(rcNt); +#endif + if (NT_SUCCESS(rcNt)) + { + /* + * Enumerate it, looking for the driver. + */ + ULONG uObjDirCtx = 0; + for (;;) + { + uint32_t abBuffer[_64K + _1K]; + ULONG cbActual; + rcNt = NtQueryDirectoryObject(hDir, + abBuffer, + sizeof(abBuffer) - 4, /* minus four for string terminator space. */ + FALSE /*ReturnSingleEntry */, + FALSE /*RestartScan*/, + &uObjDirCtx, + &cbActual); + if (!NT_SUCCESS(rcNt) || cbActual < sizeof(OBJECT_DIRECTORY_INFORMATION)) + break; + + POBJECT_DIRECTORY_INFORMATION pObjDir = (POBJECT_DIRECTORY_INFORMATION)abBuffer; + while (pObjDir->Name.Length != 0) + { + WCHAR wcSaved = pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)]; + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = '\0'; + + for (uint32_t i = 0; i < RT_ELEMENTS(s_aDrivers); i++) + if (RTUtf16ICmpAscii(pObjDir->Name.Buffer, s_aDrivers[i].pszDriver) == 0) + { + fFound |= s_aDrivers[i].fAdversary; + SUP_DPRINTF(("Found driver %s (%#x)\n", s_aDrivers[i].pszDriver, s_aDrivers[i].fAdversary)); + break; + } + + pObjDir->Name.Buffer[pObjDir->Name.Length / sizeof(WCHAR)] = wcSaved; + + /* Next directory entry. */ + pObjDir++; + } + } + + NtClose(hDir); + } + else + SUP_DPRINTF(("NtOpenDirectoryObject failed on \\Driver: %#x\n", rcNt)); + + /* + * Look for files. + */ + for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++) + { + HANDLE hFile = RTNT_INVALID_HANDLE_VALUE; + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + UNICODE_STRING UniStrName; + UniStrName.Buffer = (WCHAR *)s_aFiles[i].pwszFile; + UniStrName.Length = (USHORT)(RTUtf16Len(s_aFiles[i].pwszFile) * sizeof(WCHAR)); + UniStrName.MaximumLength = UniStrName.Length + sizeof(WCHAR); + InitializeObjectAttributes(&ObjAttr, &UniStrName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/); + rcNt = NtCreateFile(&hFile, GENERIC_READ | 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) && NT_SUCCESS(Ios.Status)) + { + fFound |= s_aFiles[i].fAdversary; + NtClose(hFile); + } + } + + /* + * Log details and upgrade select adversaries. + */ + SUP_DPRINTF(("supR3HardenedWinFindAdversaries: %#x\n", fFound)); + for (uint32_t i = 0; i < RT_ELEMENTS(s_aFiles); i++) + if (s_aFiles[i].fAdversary & fFound) + { + if (!(s_aFiles[i].fAdversary & SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD)) + supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, NULL, 0); + else + { + /* + * See if it's a newer version of the driver which doesn't BSODs when we free + * its memory. To use RTStrVersionCompare we do a rough UTF-16 -> ASCII conversion. + */ + union + { + char szFileVersion[64]; + RTUTF16 wszFileVersion[32]; + } uBuf; + supR3HardenedLogFileInfo(s_aFiles[i].pwszFile, uBuf.wszFileVersion, RT_ELEMENTS(uBuf.wszFileVersion)); + if (uBuf.wszFileVersion[0]) + { + for (uint32_t off = 0; off < RT_ELEMENTS(uBuf.wszFileVersion); off++) + { + RTUTF16 wch = uBuf.wszFileVersion[off]; + uBuf.szFileVersion[off] = (char)wch; + if (!wch) + break; + } + uBuf.szFileVersion[RT_ELEMENTS(uBuf.wszFileVersion)] = '\0'; +#define VER_IN_RANGE(a_pszFirst, a_pszLast) \ + (RTStrVersionCompare(uBuf.szFileVersion, a_pszFirst) >= 0 && RTStrVersionCompare(uBuf.szFileVersion, a_pszLast) <= 0) + if ( VER_IN_RANGE("7.3.2.0000", "999999999.9.9.9999") + || VER_IN_RANGE("7.3.1.1000", "7.3.1.3000") + || VER_IN_RANGE("7.3.0.3000", "7.3.0.999999999") + || VER_IN_RANGE("7.2.1.3000", "7.2.999999999.999999999") ) + { + uint32_t const fOldFound = fFound; + fFound = (fOldFound & ~SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_OLD) + | SUPHARDNT_ADVERSARY_DIGITAL_GUARDIAN_NEW; + SUP_DPRINTF(("supR3HardenedWinFindAdversaries: Found newer version: %#x -> %#x\n", fOldFound, fFound)); + } + } + } + } + + return fFound; +} + + +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++; + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED; + + /* + * Initialize the NTDLL API wrappers. This aims at bypassing patched NTDLL + * in all the processes leading up the VM process. + */ + supR3HardenedWinInitImports(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED; + + /* + * Notify the parent process that we're probably capable of reporting our + * own errors. + */ + if (g_ProcParams.hEvtParent || g_ProcParams.hEvtChild) + { + SUPR3HARDENED_ASSERT(g_fSupEarlyProcessInit); + + g_ProcParams.enmRequest = kSupR3WinChildReq_CloseEvents; + NtSetEvent(g_ProcParams.hEvtParent, NULL); + + NtClose(g_ProcParams.hEvtParent); + NtClose(g_ProcParams.hEvtChild); + g_ProcParams.hEvtParent = NULL; + g_ProcParams.hEvtChild = NULL; + } + else + SUPR3HARDENED_ASSERT(!g_fSupEarlyProcessInit); + + /* + * After having resolved imports we patch the LdrInitializeThunk code so + * that it's more difficult to invade our privacy by CreateRemoteThread. + * We'll re-enable this after opening the driver or temporarily while respawning. + */ + supR3HardenedWinDisableThreadCreation(); + + /* + * Init g_uNtVerCombined. (The code is shared with SUPR3.lib and lives in + * SUPHardenedVerfiyImage-win.cpp.) + */ + supR3HardenedWinInitVersion(false /*fEarly*/); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_VERSION_INITIALIZED; + + /* + * Convert the arguments to UTF-8 and open the log file if specified. + * This must be done as early as possible since the code below may fail. + */ + PUNICODE_STRING pCmdLineStr = &NtCurrentPeb()->ProcessParameters->CommandLine; + int cArgs; + char **papszArgs = suplibCommandLineToArgvWStub(pCmdLineStr->Buffer, pCmdLineStr->Length / sizeof(WCHAR), &cArgs); + + supR3HardenedOpenLog(&cArgs, papszArgs); + + /* + * Log information about important system files. + */ + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\ntdll.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\kernel32.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\KernelBase.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + supR3HardenedLogFileInfo(L"\\SystemRoot\\System32\\apisetschema.dll", NULL /*pwszFileVersion*/, 0 /*cwcFileVersion*/); + + /* + * Scan the system for adversaries, logging information about them. + */ + g_fSupAdversaries = supR3HardenedWinFindAdversaries(); + + /* + * Get the executable name, make sure it's the long version. + */ + DWORD cwcExecName = GetModuleFileNameW(GetModuleHandleW(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."); + + RTUTF16 wszLong[RT_ELEMENTS(g_wszSupLibHardenedExePath)]; + DWORD cwcLong = GetLongPathNameW(g_wszSupLibHardenedExePath, wszLong, RT_ELEMENTS(wszLong)); + if (cwcLong > 0) + { + memcpy(g_wszSupLibHardenedExePath, wszLong, (cwcLong + 1) * sizeof(RTUTF16)); + cwcExecName = cwcLong; + } + + /* The NT version of it. */ + 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(RtlGetLastWin32Error()), + "Error opening the executable: %u (%ls).", RtlGetLastWin32Error()); + 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); + NtClose(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--; + + /* + * Preliminary app binary path init. May change when SUPR3HardenedMain is + * called (via main below). + */ + supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN); + + /* + * If we've done early init already, register the DLL load notification + * callback and reinstall the NtDll patches. + */ + if (g_fSupEarlyProcessInit) + { + supR3HardenedWinRegisterDllNotificationCallback(); + supR3HardenedWinReInstallHooks(false /*fFirstCall */); + + /* + * Flush user APCs before the g_enmSupR3HardenedMainState changes + * and disables the APC restrictions. + */ + NtTestAlert(); + } + + /* + * Call the C/C++ main function. + */ + SUP_DPRINTF(("Calling main()\n")); + rcExit = (RTEXITCODE)main(cArgs, papszArgs, NULL); + + /* + * Exit the process (never return). + */ + SUP_DPRINTF(("Terminating the normal way: rcExit=%d\n", rcExit)); + suplibHardenedExit(rcExit); +} + + +/** + * Reports an error to the parent process via the process parameter structure. + * + * @param pszWhere Where this error occured, if fatal message. NULL + * if not message. + * @param enmWhat Which init operation went wrong if fatal + * message. kSupInitOp_Invalid if not message. + * @param rc The status code to report. + * @param pszFormat The format string. + * @param va The format arguments. + */ +DECLHIDDEN(void) supR3HardenedWinReportErrorToParent(const char *pszWhere, SUPINITOP enmWhat, int rc, + const char *pszFormat, va_list va) +{ + if (pszWhere) + RTStrCopy(g_ProcParams.szWhere, sizeof(g_ProcParams.szWhere), pszWhere); + else + g_ProcParams.szWhere[0] = '\0'; + RTStrPrintfV(g_ProcParams.szErrorMsg, sizeof(g_ProcParams.szErrorMsg), pszFormat, va); + g_ProcParams.enmWhat = enmWhat; + g_ProcParams.rc = RT_SUCCESS(rc) ? VERR_INTERNAL_ERROR_2 : rc; + g_ProcParams.enmRequest = kSupR3WinChildReq_Error; + + NtClearEvent(g_ProcParams.hEvtChild); + NTSTATUS rcNt = NtSetEvent(g_ProcParams.hEvtParent, NULL); + if (NT_SUCCESS(rcNt)) + { + LARGE_INTEGER Timeout; + Timeout.QuadPart = -300000000; /* 30 second */ + /*NTSTATUS rcNt =*/ NtWaitForSingleObject(g_ProcParams.hEvtChild, FALSE /*Alertable*/, &Timeout); + } +} + + +/** + * Routine called by the supR3HardenedEarlyProcessInitThunk assembly routine + * when LdrInitializeThunk is executed during process initialization. + * + * This initializes the Stub and VM processes, hooking NTDLL APIs and opening + * the device driver before any other DLLs gets loaded into the process. This + * greately reduces and controls the trusted code base of the process compared + * to opening the driver from SUPR3HardenedMain. It also avoids issues with so + * call protection software that is in the habit of patching half of the ntdll + * and kernel32 APIs in the process, making it almost indistinguishable from + * software that is up to no good. Once we've opened vboxdrv (renamed to + * vboxsup in 7.0 and 6.1.34), the process should be locked down so tightly + * that only kernel software and csrss can mess with the process. + */ +DECLASM(uintptr_t) supR3HardenedEarlyProcessInit(void) +{ + /* + * When the first thread gets here we wait for the parent to continue with + * the process purifications. The primary thread must execute for image + * load notifications to trigger, at least in more recent windows versions. + * The old trick of starting a different thread that terminates immediately + * thus doesn't work. + * + * We are not allowed to modify any data at this point because it will be + * reset by the child process purification the parent does when we stop. To + * sabotage thread creation during purification, and to avoid unnecessary + * work for the parent, we reset g_ProcParams before signalling the parent + * here. + */ + if (g_enmSupR3HardenedMainState != SUPR3HARDENEDMAINSTATE_NOT_YET_CALLED) + { + NtTerminateThread(0, 0); + return 0x22; /* crash */ + } + + /* Retrieve the data we need. */ + uintptr_t uNtDllAddr = ASMAtomicXchgPtrT(&g_ProcParams.uNtDllAddr, 0, uintptr_t); + if (!RT_VALID_PTR(uNtDllAddr)) + { + NtTerminateThread(0, 0); + return 0x23; /* crash */ + } + + HANDLE hEvtChild = g_ProcParams.hEvtChild; + HANDLE hEvtParent = g_ProcParams.hEvtParent; + if ( hEvtChild == NULL + || hEvtChild == RTNT_INVALID_HANDLE_VALUE + || hEvtParent == NULL + || hEvtParent == RTNT_INVALID_HANDLE_VALUE) + { + NtTerminateThread(0, 0); + return 0x24; /* crash */ + } + + /* Resolve the APIs we need. */ + PFNNTWAITFORSINGLEOBJECT pfnNtWaitForSingleObject; + PFNNTSETEVENT pfnNtSetEvent; + supR3HardenedWinGetVeryEarlyImports(uNtDllAddr, &pfnNtWaitForSingleObject, &pfnNtSetEvent); + + /* Signal the parent that we're ready for purification. */ + RT_ZERO(g_ProcParams); + g_ProcParams.enmRequest = kSupR3WinChildReq_PurifyChildAndCloseHandles; + NTSTATUS rcNt = pfnNtSetEvent(hEvtParent, NULL); + if (rcNt != STATUS_SUCCESS) + return 0x33; /* crash */ + + /* Wait up to 2 mins for the parent to exorcise evil. */ + LARGE_INTEGER Timeout; + Timeout.QuadPart = -1200000000; /* 120 second */ + rcNt = pfnNtWaitForSingleObject(hEvtChild, FALSE /*Alertable (never alertable before hooking!) */, &Timeout); + if (rcNt != STATUS_SUCCESS) + return 0x34; /* crash */ + + /* + * We're good to go, work global state and restore process parameters. + * Note that we will not restore uNtDllAddr since that is our first defence + * against unwanted threads (see above). + */ + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_INIT_CALLED; + g_fSupEarlyProcessInit = true; + + g_ProcParams.hEvtChild = hEvtChild; + g_ProcParams.hEvtParent = hEvtParent; + g_ProcParams.enmRequest = kSupR3WinChildReq_Error; + g_ProcParams.rc = VINF_SUCCESS; + + /* + * Initialize the NTDLL imports that we consider usable before the + * process has been initialized. + */ + supR3HardenedWinInitImportsEarly(uNtDllAddr); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_IMPORTS_RESOLVED; + + /* + * Init g_uNtVerCombined as well as we can at this point. + */ + supR3HardenedWinInitVersion(true /*fEarly*/); + + /* + * Convert the arguments to UTF-8 so we can open the log file if specified. + * We may have to normalize the pointer on older windows version (not w7/64 +). + * Note! This leaks memory at present. + */ + PRTL_USER_PROCESS_PARAMETERS pUserProcParams = NtCurrentPeb()->ProcessParameters; + UNICODE_STRING CmdLineStr = pUserProcParams->CommandLine; + if ( CmdLineStr.Buffer != NULL + && !(pUserProcParams->Flags & RTL_USER_PROCESS_PARAMS_FLAG_NORMALIZED) ) + CmdLineStr.Buffer = (WCHAR *)((uintptr_t)CmdLineStr.Buffer + (uintptr_t)pUserProcParams); + int cArgs; + char **papszArgs = suplibCommandLineToArgvWStub(CmdLineStr.Buffer, CmdLineStr.Length / sizeof(WCHAR), &cArgs); + supR3HardenedOpenLog(&cArgs, papszArgs); + SUP_DPRINTF(("supR3HardenedVmProcessInit: uNtDllAddr=%p g_uNtVerCombined=%#x (stack ~%p)\n", + uNtDllAddr, g_uNtVerCombined, &Timeout)); + + /* + * Set up the direct system calls so we can more easily hook NtCreateSection. + */ + RTERRINFOSTATIC ErrInfo; + supR3HardenedWinInitSyscalls(true /*fReportErrors*/, RTErrInfoInitStatic(&ErrInfo)); + + /* + * Determine the executable path and name. Will NOT determine the windows style + * executable path here as we don't need it. + */ + SIZE_T cbActual = 0; + rcNt = NtQueryVirtualMemory(NtCurrentProcess(), &g_ProcParams, MemorySectionName, &g_SupLibHardenedExeNtPath, + sizeof(g_SupLibHardenedExeNtPath) - sizeof(WCHAR), &cbActual); + if ( !NT_SUCCESS(rcNt) + || g_SupLibHardenedExeNtPath.UniStr.Length == 0 + || g_SupLibHardenedExeNtPath.UniStr.Length & 1) + supR3HardenedFatal("NtQueryVirtualMemory/MemorySectionName failed in supR3HardenedVmProcessInit: %#x\n", rcNt); + + /* 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--; + + /* + * Preliminary app binary path init. May change when SUPR3HardenedMain is called. + */ + supR3HardenedWinInitAppBin(SUPSECMAIN_FLAGS_LOC_APP_BIN); + + /* + * Initialize the image verification stuff (hooks LdrLoadDll and NtCreateSection). + */ + supR3HardenedWinInit(0, false /*fAvastKludge*/); + + /* + * Open the driver. + */ + if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_1_ARG0) == 0) + { + SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxsup stub...\n")); + supR3HardenedWinOpenStubDevice(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_STUB_DEVICE_OPENED; + } + else if (cArgs >= 1 && suplibHardenedStrCmp(papszArgs[0], SUPR3_RESPAWN_2_ARG0) == 0) + { + SUP_DPRINTF(("supR3HardenedVmProcessInit: Opening vboxsup...\n")); + supR3HardenedMainOpenDevice(); + g_enmSupR3HardenedMainState = SUPR3HARDENEDMAINSTATE_WIN_EARLY_REAL_DEVICE_OPENED; + } + else + supR3HardenedFatal("Unexpected first argument '%s'!\n", papszArgs[0]); + + /* + * Reinstall the NtDll patches since there is a slight possibility that + * someone undid them while we where busy opening the device. + */ + supR3HardenedWinReInstallHooks(false /*fFirstCall */); + + /* + * Restore the LdrInitializeThunk code so we can initialize the process + * normally when we return. + */ + SUP_DPRINTF(("supR3HardenedVmProcessInit: Restoring LdrInitializeThunk...\n")); + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen("ntdll.dll", &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheOpen failed on NTDLL: %Rrc %s\n", + rc, ErrInfo.Core.pszMsg); + + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, uNtDllAddr, NULL, NULL, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: supHardNtLdrCacheEntryGetBits failed on NTDLL: %Rrc %s\n", + rc, ErrInfo.Core.pszMsg); + + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, uNtDllAddr, UINT32_MAX, "LdrInitializeThunk", &uValue); + if (RT_FAILURE(rc)) + supR3HardenedFatal("supR3HardenedVmProcessInit: Failed to find LdrInitializeThunk (%Rrc).\n", rc); + + PVOID pvLdrInitThunk = (PVOID)(uintptr_t)uValue; + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READWRITE)); + memcpy(pvLdrInitThunk, pbBits + ((uintptr_t)uValue - uNtDllAddr), 16); + SUPR3HARDENED_ASSERT_NT_SUCCESS(supR3HardenedWinProtectMemory(pvLdrInitThunk, 16, PAGE_EXECUTE_READ)); + + SUP_DPRINTF(("supR3HardenedVmProcessInit: Returning to LdrInitializeThunk...\n")); + return (uintptr_t)pvLdrInitThunk; +} + 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 00000000..752eddbe --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm @@ -0,0 +1,404 @@ +; $Id: SUPR3HardenedMainA-win.asm $ +;; @file +; VirtualBox Support Library - Hardened main(), Windows assembly bits. +; + +; +; Copyright (C) 2012-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +;******************************************************************************* +;* Header Files * +;******************************************************************************* +%define RT_ASM_WITH_SEH64 +%include "iprt/asmdefs.mac" + + +; External code. +extern NAME(supR3HardenedEarlyProcessInit) +extern NAME(supR3HardenedMonitor_KiUserApcDispatcher_C) +%ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +extern NAME(supR3HardenedMonitor_KiUserExceptionDispatcher_C) +%endif + + +BEGINCODE + + +;; +; Alternative code for LdrInitializeThunk that performs the early process startup +; for the Stub and VM processes. +; +; This does not concern itself with any arguments on stack or in registers that +; may be passed to the LdrIntializeThunk routine as we just save and restore +; them all before we restart the restored LdrInitializeThunk routine. +; +; @sa supR3HardenedEarlyProcessInit +; +BEGINPROC supR3HardenedEarlyProcessInitThunk + ; + ; Prologue. + ; + + ; Reserve space for the "return" address. + push 0 + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX +%ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 +%endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. This returns the + ; resume address in xAX, which we put in the "return" stack position. + ; + call NAME(supR3HardenedEarlyProcessInit) + mov [xBP + xCB], xAX + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] +%ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] +%endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to resume process + ; initializaton. + ; + leave + ret +ENDPROC supR3HardenedEarlyProcessInitThunk + + +;; +; Hook for KiUserApcDispatcher that validates user APC calls during early process +; init to prevent calls going to or referring to executable memory we've freed +; already. +; +; We just call C code here, just like supR3HardenedEarlyProcessInitThunk does. +; +; @sa supR3HardenedMonitor_KiUserApcDispatcher_C +; +BEGINPROC supR3HardenedMonitor_KiUserApcDispatcher + ; + ; Prologue. + ; + + ; Reserve space for the "return" address. + push 0 + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX +%ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 +%endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. This returns the + ; resume address in xAX, which we put in the "return" stack position. + ; + ; On AMD64, a CONTEXT structure is found at our RSP address when we're called. + ; On x86, there a 16 byte structure containing the two routines and their + ; arguments followed by a CONTEXT structure. + ; + lea xCX, [xBP + xCB + xCB] +%ifdef RT_ARCH_X86 + mov [xSP], xCX +%endif + call NAME(supR3HardenedMonitor_KiUserApcDispatcher_C) + mov [xBP + xCB], xAX + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] +%ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] +%endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to execute the + ; original KiUserApcDispatcher code as if we've never been here... + ; + leave + ret +ENDPROC supR3HardenedMonitor_KiUserApcDispatcher + + +%ifndef VBOX_WITHOUT_HARDENDED_XCPT_LOGGING +;; +; Hook for KiUserExceptionDispatcher that logs exceptions. +; +; For the AMD64 variant, we're not directly intercepting the function itself, but +; patching into a Wow64 callout that's done at the very start of the routine. RCX +; and RDX are set to PEXCEPTION_RECORD and PCONTEXT respectively and there is a +; return address. Also, we don't need to do any return-via-copied-out-code stuff. +; +; For X86 we hook the function and have PEXCEPTION_RECORD and PCONTEXT pointers on +; the stack, but no return address. + +; We just call C code here, just like supR3HardenedEarlyProcessInitThunk and +; supR3HardenedMonitor_KiUserApcDispatcher does. +; +; @sa supR3HardenedMonitor_KiUserExceptionDispatcher_C +; +BEGINPROC supR3HardenedMonitor_KiUserExceptionDispatcher + ; + ; Prologue. + ; + + %ifndef RT_ARCH_AMD64 + ; Reserve space for the "return" address. + push 0 + %endif + + ; Create a stack frame, saving xBP. + push xBP + SEH64_PUSH_xBP + mov xBP, xSP + SEH64_SET_FRAME_xBP 0 ; probably wrong... + + ; Save all volatile registers. + push xAX + push xCX + push xDX + %ifdef RT_ARCH_AMD64 + push r8 + push r9 + push r10 + push r11 + %endif + + ; Reserve spill space and align the stack. + sub xSP, 20h + and xSP, ~0fh + SEH64_END_PROLOGUE + + ; + ; Call the C/C++ code that does the actual work. For x86 this returns + ; the resume address in xAX, which we put in the "return" stack position. + ; + ; On both AMD64 and X86 we have two parameters on the stack that we + ; passes along to the C code (see function description for details). + ; + %ifdef RT_ARCH_X86 + mov xCX, [xBP + xCB*2] + mov xDX, [xBP + xCB*3] + mov [xSP], xCX + mov [xSP+4], xDX + %endif + call NAME(supR3HardenedMonitor_KiUserExceptionDispatcher_C) + %ifdef RT_ARCH_X86 + mov [xBP + xCB], xAX + %endif + + ; + ; Restore volatile registers. + ; + mov xAX, [xBP - xCB*1] + mov xCX, [xBP - xCB*2] + mov xDX, [xBP - xCB*3] + %ifdef RT_ARCH_AMD64 + mov r8, [xBP - xCB*4] + mov r9, [xBP - xCB*5] + mov r10, [xBP - xCB*6] + mov r11, [xBP - xCB*7] + %endif + ; + ; Use the leave instruction to restore xBP and set up xSP to point at + ; the resume address. Then use the 'ret' instruction to execute the + ; original KiUserExceptionDispatcher code as if we've never been here... + ; + leave + ret +ENDPROC supR3HardenedMonitor_KiUserExceptionDispatcher +%endif ; !VBOX_WITHOUT_HARDENDED_XCPT_LOGGING + +;; +; Composes a standard call name. +%ifdef RT_ARCH_X86 + %define SUPHNTIMP_STDCALL_NAME(a,b) _ %+ a %+ @ %+ b +%else + %define SUPHNTIMP_STDCALL_NAME(a,b) NAME(a) +%endif + +;; Concats two litterals. +%define SUPHNTIMP_CONCAT(a,b) a %+ b + + +;; +; Import data and code for an API call. +; +; @param 1 The plain API name. +; @param 2 The parameter frame size on x86. Multiple of dword. +; @param 3 Non-zero expression if system call. +; @param 4 Non-zero expression if early available call +; +%define SUPHNTIMP_SYSCALL 1 +%macro SupHardNtImport 4 + ; + ; The data. + ; +BEGINDATA +global __imp_ %+ SUPHNTIMP_STDCALL_NAME(%1,%2) ; The import name used via dllimport. +__imp_ %+ SUPHNTIMP_STDCALL_NAME(%1,%2): +GLOBALNAME g_pfn %+ %1 ; The name we like to refer to. + RTCCPTR_DEF 0 +%if %3 +GLOBALNAME g_uApiNo %+ %1 + RTCCPTR_DEF 0 +%endif + + ; + ; The code: First a call stub. + ; +BEGINCODE +global SUPHNTIMP_STDCALL_NAME(%1, %2) +SUPHNTIMP_STDCALL_NAME(%1, %2): + jmp RTCCPTR_PRE [NAME(g_pfn %+ %1) xWrtRIP] + +%if %3 + ; + ; Make system calls. + ; + %ifdef RT_ARCH_AMD64 +BEGINPROC %1 %+ _SyscallType1 + SEH64_END_PROLOGUE + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + mov r10, rcx + syscall + ret +ENDPROC %1 %+ _SyscallType1 +BEGINPROC %1 %+ _SyscallType2 ; Introduced with build 10525 + SEH64_END_PROLOGUE + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + test byte [07ffe0308h], 1 ; SharedUserData!Something + mov r10, rcx + jnz .int_alternative + syscall + ret +.int_alternative: + int 2eh + ret +ENDPROC %1 %+ _SyscallType2 + %else +BEGINPROC %1 %+ _SyscallType1 + mov edx, 07ffe0300h ; SharedUserData!SystemCallStub + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + call dword [edx] + ret %2 +ENDPROC %1 %+ _SyscallType1 +BEGINPROC %1 %+ _SyscallType2 + push .return + mov edx, esp + mov eax, [NAME(g_uApiNo %+ %1) xWrtRIP] + sysenter + add esp, 4 +.return: + ret %2 +ENDPROC %1 %+ _SyscallType2 + %endif +%endif + +%if %4 == 0 +global NAME(SUPHNTIMP_CONCAT(%1,_Early)) +NAME(SUPHNTIMP_CONCAT(%1,_Early)): + int3 + %ifdef RT_ARCH_AMD64 + ret + %else + ret %2 + %endif +%endif +%endmacro + +%define SUPHARNT_COMMENT(a_Comment) +%define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, SUPHNTIMP_SYSCALL, 1 +%define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, 0, 0 +%define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +%define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) SupHardNtImport a_Name, a_cbParamsX86, 0, 1 +%define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) +%include "import-template-ntdll.h" +%include "import-template-kernel32.h" + + +; +; For simplified LdrLoadDll patching we define a special writable, readable and +; exectuable section of 4KB where we can put jump back code. +; +section .rwxpg bss execute read write align=4096 +GLOBALNAME g_abSupHardReadWriteExecPage + resb 4096 + diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp new file mode 100644 index 00000000..414188a3 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp @@ -0,0 +1,873 @@ +/* $Id: SUPR3HardenedMainImports-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened Main, Windows Import Trickery. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <VBox/sup.h> +#include <VBox/err.h> +#include <iprt/ctype.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include "SUPLibInternal.h" +#include "SUPHardenedVerify-win.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define SUPHARNT_COMMENT(a_Blah) /* nothing */ + +#define VBOX_HARDENED_STUB_WITHOUT_IMPORTS +#ifdef VBOX_HARDENED_STUB_WITHOUT_IMPORTS +# define SUPHNTIMP_ERROR(a_fReportErrors, a_id, a_szWhere, a_enmOp, a_rc, ...) \ + do { \ + if (a_fReportErrors) supR3HardenedFatalMsg(a_szWhere, a_enmOp, a_rc, __VA_ARGS__); \ + else { static const char s_szWhere[] = a_szWhere; *(char *)(uintptr_t)(a_id) += 1; __debugbreak(); } \ + } while (0) +#else +# define SUPHNTIMP_ERROR(a_fReportErrors, a_id, a_szWhere, a_enmOp, a_rc, ...) \ + supR3HardenedFatalMsg(a_szWhere, a_enmOp, a_rc, __VA_ARGS__) + +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** + * Import function entry. + */ +typedef struct SUPHNTIMPFUNC +{ + /** The name of the function we're importing. */ + const char *pszName; + /** Where to store the function address (think __imp_ApiName). */ + PFNRT *ppfnImport; + /** Pointer to an early dummy function for imports that aren't available + * during early process initialization. */ + PFNRT pfnEarlyDummy; + /** Indicates whether this is an optional import and failure to locate it + * should set it to NULL instead of freaking out. */ + bool fOptional; +} SUPHNTIMPFUNC; +/** Pointer to an import table entry. */ +typedef SUPHNTIMPFUNC const *PCSUPHNTIMPFUNC; + +/** + * Information for constructing a direct system call. + */ +typedef struct SUPHNTIMPSYSCALL +{ + /** Where to store the system call number. + * NULL if this import doesn't stupport direct system call. */ + uint32_t *puApiNo; + /** Assembly system call routine, type 1. */ + PFNRT pfnType1; + /** Assembly system call routine, type 2. */ + PFNRT pfnType2; +#ifdef RT_ARCH_X86 + /** The parameter size in bytes for a standard call. */ + uint32_t cbParams; +#endif +} SUPHNTIMPSYSCALL; +/** Pointer to a system call entry. */ +typedef SUPHNTIMPSYSCALL const *PCSUPHNTIMPSYSCALL; + +/** + * Import DLL. + * + * This contains both static (like name & imports) and runtime information (like + * load and export table locations). + * + * @sa RTDBGNTKRNLMODINFO + */ +typedef struct SUPHNTIMPDLL +{ + /** @name Static data. + * @{ */ + const wchar_t *pwszName; + const char *pszName; + size_t cImports; + PCSUPHNTIMPFUNC paImports; + /** Array running parallel to paImports if present. */ + PCSUPHNTIMPSYSCALL paSyscalls; + /** @} */ + + + /** The image base. */ + uint8_t const *pbImageBase; + /** The NT headers. */ + PIMAGE_NT_HEADERS pNtHdrs; + /** The NT header offset/RVA. */ + uint32_t offNtHdrs; + /** The end of the section headers. */ + uint32_t offEndSectHdrs; + /** The end of the image. */ + uint32_t cbImage; + /** Offset of the export directory. */ + uint32_t offExportDir; + /** Size of the export directory. */ + uint32_t cbExportDir; + + /** Exported functions and data by ordinal (RVAs). */ + uint32_t const *paoffExports; + /** The number of exports. */ + uint32_t cExports; + /** The number of exported names. */ + uint32_t cNamedExports; + /** Pointer to the array of exported names (RVAs to strings). */ + uint32_t const *paoffNamedExports; + /** Array parallel to paoffNamedExports with the corresponding ordinals + * (indexes into paoffExports). */ + uint16_t const *pau16NameOrdinals; + + /** Number of patched export table entries. */ + uint32_t cPatchedExports; + +} SUPHNTIMPDLL; +/** Pointer to an import DLL entry. */ +typedef SUPHNTIMPDLL *PSUPHNTIMPDLL; + + + +/* + * Declare assembly symbols. + */ +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + extern PFNRT RT_CONCAT(g_pfn, a_Name); +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + extern uint32_t RT_CONCAT(g_uApiNo, a_Name); \ + extern FNRT RT_CONCAT(a_Name, _SyscallType1); \ + extern FNRT RT_CONCAT(a_Name, _SyscallType2); +#define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + extern PFNRT RT_CONCAT(g_pfn, a_Name); \ + extern FNRT RT_CONCAT(a_Name, _Early); +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) + +RT_C_DECLS_BEGIN +#include "import-template-ntdll.h" +#include "import-template-kernel32.h" +RT_C_DECLS_END + +/* + * Import functions. + */ +#undef SUPHARNT_IMPORT_SYSCALL +#undef SUPHARNT_IMPORT_STDCALL_EARLY +#undef SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL +#undef SUPHARNT_IMPORT_STDCALL +#undef SUPHARNT_IMPORT_STDCALL_OPTIONAL +#define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, false }, +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, false }, +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), NULL, true }, +#define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), RT_CONCAT(a_Name,_Early), false }, +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) \ + { #a_Name, &RT_CONCAT(g_pfn, a_Name), RT_CONCAT(a_Name,_Early), true }, +static const SUPHNTIMPFUNC g_aSupNtImpNtDllFunctions[] = +{ +#include "import-template-ntdll.h" +}; + +static const SUPHNTIMPFUNC g_aSupNtImpKernel32Functions[] = +{ +#include "import-template-kernel32.h" +}; + + + +/* + * Syscalls in ntdll. + */ +#undef SUPHARNT_IMPORT_SYSCALL +#undef SUPHARNT_IMPORT_STDCALL_EARLY +#undef SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL +#undef SUPHARNT_IMPORT_STDCALL +#undef SUPHARNT_IMPORT_STDCALL_OPTIONAL +#ifdef RT_ARCH_AMD64 +# define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { NULL, NULL }, +# define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { &RT_CONCAT(g_uApiNo, a_Name), &RT_CONCAT(a_Name, _SyscallType1), &RT_CONCAT(a_Name, _SyscallType2) }, +#elif defined(RT_ARCH_X86) +# define SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) \ + { NULL, NULL, NULL, 0 }, +# define SUPHARNT_IMPORT_SYSCALL(a_Name, a_cbParamsX86) \ + { &RT_CONCAT(g_uApiNo, a_Name), &RT_CONCAT(a_Name,_SyscallType1), &RT_CONCAT(a_Name, _SyscallType2), a_cbParamsX86 }, +#endif +#define SUPHARNT_IMPORT_STDCALL_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_STDCALL_EARLY(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +#define SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(a_Name, a_cbParamsX86) SUPHARNT_IMPORT_STDCALL(a_Name, a_cbParamsX86) +static const SUPHNTIMPSYSCALL g_aSupNtImpNtDllSyscalls[] = +{ +#include "import-template-ntdll.h" +}; + + +/** + * All the DLLs we import from. + * @remarks Code ASSUMES that ntdll is the first entry. + */ +static SUPHNTIMPDLL g_aSupNtImpDlls[] = +{ + { L"ntdll.dll", "ntdll.dll", RT_ELEMENTS(g_aSupNtImpNtDllFunctions), g_aSupNtImpNtDllFunctions, g_aSupNtImpNtDllSyscalls }, + { L"kernelbase.dll", "kernelbase.dll", 0 /* optional module, forwarders only */, NULL, NULL }, + { L"kernel32.dll", "kernel32.dll", RT_ELEMENTS(g_aSupNtImpKernel32Functions), g_aSupNtImpKernel32Functions, NULL }, +}; + + +static void supR3HardenedFindOrLoadModule(PSUPHNTIMPDLL pDll) +{ +#ifdef VBOX_HARDENED_STUB_WITHOUT_IMPORTS + uint32_t const cbName = (uint32_t)RTUtf16Len(pDll->pwszName) * sizeof(WCHAR); + PPEB_LDR_DATA pLdrData = NtCurrentPeb()->Ldr; + LIST_ENTRY *pList = &pLdrData->InMemoryOrderModuleList; + LIST_ENTRY *pListEntry = pList->Flink; + uint32_t cLoops = 0; + while (pListEntry != pList && cLoops < 1024) + { + PLDR_DATA_TABLE_ENTRY pLdrEntry = RT_FROM_MEMBER(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + + if ( pLdrEntry->FullDllName.Length > cbName + sizeof(WCHAR) + && ( pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '\\' + || pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR) - 1] == '/') + && RTUtf16ICmpAscii(&pLdrEntry->FullDllName.Buffer[(pLdrEntry->FullDllName.Length - cbName) / sizeof(WCHAR)], + pDll->pszName) == 0) + { + pDll->pbImageBase = (uint8_t *)pLdrEntry->DllBase; + return; + } + + pListEntry = pListEntry->Flink; + cLoops++; + } + + if (!pDll->cImports) + pDll->pbImageBase = NULL; /* optional */ + else + SUPHNTIMP_ERROR(false, 1, "supR3HardenedFindOrLoadModule", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "Failed to locate %ls", pDll->pwszName); +#else + HMODULE hmod = GetModuleHandleW(pDll->pwszName); + if (RT_UNLIKELY(!hmod && pDll->cImports)) + SUPHNTIMP_ERROR(true, 1, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "Failed to locate %ls", pDll->pwszName); + pDll->pbImageBase = (uint8_t *)hmod; +#endif +} + + +/** @sa rtR0DbgKrnlNtParseModule */ +static void supR3HardenedParseModule(PSUPHNTIMPDLL pDll) +{ + /* + * Locate the PE header, do some basic validations. + */ + IMAGE_DOS_HEADER const *pMzHdr = (IMAGE_DOS_HEADER const *)pDll->pbImageBase; + uint32_t offNtHdrs = 0; + PIMAGE_NT_HEADERS pNtHdrs; + if (pMzHdr->e_magic == IMAGE_DOS_SIGNATURE) + { + offNtHdrs = pMzHdr->e_lfanew; + if (offNtHdrs > _2K) + SUPHNTIMP_ERROR(false, 2, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "%ls: e_lfanew=%#x, expected a lower value", pDll->pwszName, offNtHdrs); + } + pDll->pNtHdrs = pNtHdrs = (PIMAGE_NT_HEADERS)&pDll->pbImageBase[offNtHdrs]; + + if (pNtHdrs->Signature != IMAGE_NT_SIGNATURE) + SUPHNTIMP_ERROR(false, 3, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Invalid PE signature: %#x", pDll->pwszName, pNtHdrs->Signature); + if (pNtHdrs->FileHeader.SizeOfOptionalHeader != sizeof(pNtHdrs->OptionalHeader)) + SUPHNTIMP_ERROR(false, 4, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected optional header size: %#x", pDll->pwszName, pNtHdrs->FileHeader.SizeOfOptionalHeader); + if (pNtHdrs->OptionalHeader.Magic != RT_CONCAT3(IMAGE_NT_OPTIONAL_HDR,ARCH_BITS,_MAGIC)) + SUPHNTIMP_ERROR(false, 5, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected optional header magic: %#x", pDll->pwszName, pNtHdrs->OptionalHeader.Magic); + if (pNtHdrs->OptionalHeader.NumberOfRvaAndSizes != IMAGE_NUMBEROF_DIRECTORY_ENTRIES) + SUPHNTIMP_ERROR(false, 6, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Unexpected number of RVA and sizes: %#x", pDll->pwszName, pNtHdrs->OptionalHeader.NumberOfRvaAndSizes); + + pDll->offNtHdrs = offNtHdrs; + pDll->offEndSectHdrs = offNtHdrs + + sizeof(*pNtHdrs) + + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + pDll->cbImage = pNtHdrs->OptionalHeader.SizeOfImage; + + /* + * Find the export directory. + */ + IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if ( ExpDir.Size < sizeof(IMAGE_EXPORT_DIRECTORY) + || ExpDir.VirtualAddress < pDll->offEndSectHdrs + || ExpDir.VirtualAddress >= pNtHdrs->OptionalHeader.SizeOfImage + || ExpDir.VirtualAddress + ExpDir.Size > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 7, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Missing or invalid export directory: %#lx LB %#x", pDll->pwszName, ExpDir.VirtualAddress, ExpDir.Size); + pDll->offExportDir = ExpDir.VirtualAddress; + pDll->cbExportDir = ExpDir.Size; + + IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pDll->pbImageBase[ExpDir.VirtualAddress]; + + if ( pExpDir->NumberOfFunctions >= _1M + || pExpDir->NumberOfFunctions < 1 + || pExpDir->NumberOfNames >= _1M + || pExpDir->NumberOfNames < 1) + SUPHNTIMP_ERROR(false, 8, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: NumberOfNames or/and NumberOfFunctions are outside the expected range: nof=%#x non=%#x\n", + pDll->pwszName, pExpDir->NumberOfFunctions, pExpDir->NumberOfNames); + pDll->cNamedExports = pExpDir->NumberOfNames; + pDll->cExports = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions); + + if ( pExpDir->AddressOfFunctions < pDll->offEndSectHdrs + || pExpDir->AddressOfFunctions >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfFunctions + pDll->cExports * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 9, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfFunctions: %#x\n", pDll->pwszName, pExpDir->AddressOfFunctions); + pDll->paoffExports = (uint32_t const *)&pDll->pbImageBase[pExpDir->AddressOfFunctions]; + + if ( pExpDir->AddressOfNames < pDll->offEndSectHdrs + || pExpDir->AddressOfNames >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfNames + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 10, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfNames: %#x\n", pDll->pwszName, pExpDir->AddressOfNames); + pDll->paoffNamedExports = (uint32_t const *)&pDll->pbImageBase[pExpDir->AddressOfNames]; + + if ( pExpDir->AddressOfNameOrdinals < pDll->offEndSectHdrs + || pExpDir->AddressOfNameOrdinals >= pNtHdrs->OptionalHeader.SizeOfImage + || pExpDir->AddressOfNameOrdinals + pExpDir->NumberOfNames * sizeof(uint32_t) > pNtHdrs->OptionalHeader.SizeOfImage) + SUPHNTIMP_ERROR(false, 11, "supR3HardenedParseModule", kSupInitOp_Misc, VERR_INVALID_EXE_SIGNATURE, + "%ls: Bad AddressOfNameOrdinals: %#x\n", pDll->pwszName, pExpDir->AddressOfNameOrdinals); + pDll->pau16NameOrdinals = (uint16_t const *)&pDll->pbImageBase[pExpDir->AddressOfNameOrdinals]; +} + + +/** @sa rtR0DbgKrnlInfoLookupSymbol */ +static const char *supR3HardenedResolveImport(PSUPHNTIMPDLL pDll, PCSUPHNTIMPFUNC pImport, bool fReportErrors) +{ + /* + * Binary search. + */ + uint32_t iStart = 0; + uint32_t iEnd = pDll->cNamedExports; + while (iStart < iEnd) + { + uint32_t iCur = iStart + (iEnd - iStart) / 2; + uint32_t offExpName = pDll->paoffNamedExports[iCur]; + if (RT_UNLIKELY(offExpName < pDll->offEndSectHdrs || offExpName >= pDll->cbImage)) + SUPHNTIMP_ERROR(fReportErrors, 12, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_SYMBOL_NOT_FOUND, + "%ls: Bad export name entry: %#x (iCur=%#x)", pDll->pwszName, offExpName, iCur); + + const char *pszExpName = (const char *)&pDll->pbImageBase[offExpName]; + int iDiff = strcmp(pszExpName, pImport->pszName); + if (iDiff > 0) /* pszExpName > pszSymbol: search chunck before i */ + iEnd = iCur; + else if (iDiff < 0) /* pszExpName < pszSymbol: search chunk after i */ + iStart = iCur + 1; + else /* pszExpName == pszSymbol */ + { + uint16_t iExpOrdinal = pDll->pau16NameOrdinals[iCur]; + if (iExpOrdinal < pDll->cExports) + { + uint32_t offExport = pDll->paoffExports[iExpOrdinal]; + + /* detect export table patching. */ + if (offExport >= pDll->cbImage) + pDll->cPatchedExports++; + + if (offExport - pDll->offExportDir >= pDll->cbExportDir) + { + *pImport->ppfnImport = (PFNRT)&pDll->pbImageBase[offExport]; + return NULL; + } + + /* Forwarder. */ + return (const char *)&pDll->pbImageBase[offExport]; + } + SUPHNTIMP_ERROR(fReportErrors, 14, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_BAD_EXE_FORMAT, + "%ls: Name ordinal for '%s' is out of bounds: %#x (max %#x)", + pDll->pwszName, iExpOrdinal, pDll->cExports); + return NULL; + } + } + + if (!pImport->fOptional) + SUPHNTIMP_ERROR(fReportErrors, 15, "supR3HardenedResolveImport", kSupInitOp_Misc, VERR_SYMBOL_NOT_FOUND, + "%ls: Failed to resolve '%s'.", pDll->pwszName, pImport->pszName); + *pImport->ppfnImport = NULL; + return NULL; +} + + +static void supR3HardenedDirectSyscall(PSUPHNTIMPDLL pDll, PCSUPHNTIMPFUNC pImport, PCSUPHNTIMPSYSCALL pSyscall, + PSUPHNTLDRCACHEENTRY pLdrEntry, uint8_t *pbBits, bool fReportErrors) +{ + /* + * Skip non-syscall entries. + */ + if (!pSyscall->puApiNo) + return; + + /* + * Locate the virgin bits. + */ + RTLDRADDR uValue; + int rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)pDll->pbImageBase, UINT32_MAX, pImport->pszName, &uValue); + if (RT_FAILURE(rc)) + { + SUPHNTIMP_ERROR(fReportErrors, 16, "supR3HardenedDirectSyscall", kSupInitOp_Misc, rc, + "%s: RTLdrGetSymbolEx failed on %s: %Rrc", pDll->pszName, pImport->pszName, rc); + return; + } + uintptr_t offSymbol = (uintptr_t)uValue - (uintptr_t)pDll->pbImageBase; + uint8_t const *pbFunction = &pbBits[offSymbol]; + + /* + * Parse the code and extract the API call number. + */ +#ifdef RT_ARCH_AMD64 + /* Pattern #1: XP64/W2K3-64 thru Windows 10 build 10240. + 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] + + Pattern #2: Windows 10 build 10525+. + 0:000> u ntdll_7ffc26300000!NtCreateSection + ntdll_7ffc26300000!ZwCreateSection: + 00007ffc`263943e0 4c8bd1 mov r10,rcx + 00007ffc`263943e3 b84a000000 mov eax,4Ah + 00007ffc`263943e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 + 00007ffc`263943f0 7503 jne ntdll_7ffc26300000!ZwCreateSection+0x15 (00007ffc`263943f5) + 00007ffc`263943f2 0f05 syscall + 00007ffc`263943f4 c3 ret + 00007ffc`263943f5 cd2e int 2Eh + 00007ffc`263943f7 c3 ret + */ + if ( pbFunction[ 0] == 0x4c /* mov r10, rcx */ + && pbFunction[ 1] == 0x8b + && pbFunction[ 2] == 0xd1 + && pbFunction[ 3] == 0xb8 /* mov eax, 0000yyzzh */ + //&& pbFunction[ 4] == 0xZZ + //&& pbFunction[ 5] == 0xYY + && pbFunction[ 6] == 0x00 + && pbFunction[ 7] == 0x00) + { + if ( pbFunction[ 8] == 0x0f /* syscall */ + && pbFunction[ 9] == 0x05 + && pbFunction[10] == 0xc3 /* ret */ ) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[4], pbFunction[5]); + *pImport->ppfnImport = pSyscall->pfnType1; + return; + } + if ( pbFunction[ 8] == 0xf6 /* test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 */ + && pbFunction[ 9] == 0x04 + && pbFunction[10] == 0x25 + && pbFunction[11] == 0x08 + && pbFunction[12] == 0x03 + && pbFunction[13] == 0xfe + && pbFunction[14] == 0x7f + && pbFunction[15] == 0x01 + && pbFunction[16] == 0x75 /* jnz +3 */ + && pbFunction[17] == 0x03 + && pbFunction[18] == 0x0f /* syscall*/ + && pbFunction[19] == 0x05 + && pbFunction[20] == 0xc3 /* ret */ + && pbFunction[21] == 0xcd /* int 2eh */ + && pbFunction[22] == 0x2e + && pbFunction[23] == 0xc3 /* ret */ ) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[4], pbFunction[5]); + *pImport->ppfnImport = pSyscall->pfnType2; + return; + } + } +#else + /* 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 ( pbFunction[ 0] == 0xb8 /* mov eax, 0000yyzzh*/ + //&& pbFunction[ 1] <= 0xZZ + //&& pbFunction[ 2] <= 0xYY + && pbFunction[ 3] == 0x00 + && pbFunction[ 4] == 0x00) + { + *pSyscall->puApiNo = RT_MAKE_U16(pbFunction[1], pbFunction[2]); + if ( pbFunction[5] == 0xba /* mov edx, offset SharedUserData!SystemCallStub */ + && pbFunction[ 6] == 0x00 + && pbFunction[ 7] == 0x03 + && pbFunction[ 8] == 0xfe + && pbFunction[ 9] == 0x7f + && pbFunction[10] == 0xff /* call [edx] */ + && pbFunction[11] == 0x12 + && ( ( pbFunction[12] == 0xc2 /* ret 1ch */ + && pbFunction[13] == pSyscall->cbParams + && pbFunction[14] == 0x00) + || ( pbFunction[12] == 0xc3 /* ret */ + && pSyscall->cbParams == 0) + ) + ) + { + *pImport->ppfnImport = pSyscall->pfnType1; + return; + } + + if ( pbFunction[ 5] == 0xe8 /* call [$+3] */ + && RT_ABS(*(int32_t *)&pbFunction[6]) < 0x10 + && ( ( pbFunction[10] == 0xc2 /* ret 1ch */ + && pbFunction[11] == pSyscall->cbParams + && pbFunction[12] == 0x00) + || ( pbFunction[10] == 0xc3 /* ret */ + && pSyscall->cbParams == 0) + ) + ) + { + *pImport->ppfnImport = pSyscall->pfnType2; + return; + } + } +#endif + + /* + * Failed to parse it. + */ + volatile uint8_t abCopy[16]; + memcpy((void *)&abCopy[0], pbFunction, sizeof(abCopy)); + SUPHNTIMP_ERROR(fReportErrors, 17, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: failed to parse syscall: '%s': %.16Rhxs", + pDll->pwszName, pImport->pszName, &abCopy[0]); +} + + +/** + * Check out system calls and do the directly instead of via NtDll. + * + * We need to have access to the on disk NTDLL.DLL file as we do not trust the + * stuff we find in memory. Too early to verify signatures though. + * + * @param fReportErrors Whether we've got the machinery for reporting + * errors going already. + * @param pErrInfo Buffer for gathering additional error info. This + * is mainly to avoid consuming lots of stacks with + * RTERRINFOSTATIC structures. + */ +DECLHIDDEN(void) supR3HardenedWinInitSyscalls(bool fReportErrors, PRTERRINFO pErrInfo) +{ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (g_aSupNtImpDlls[iDll].paSyscalls) + { + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, pErrInfo); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + NULL, NULL, pErrInfo); + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + supR3HardenedDirectSyscall(&g_aSupNtImpDlls[iDll], &g_aSupNtImpDlls[iDll].paImports[i], + &g_aSupNtImpDlls[iDll].paSyscalls[i], pLdrEntry, pbBits, fReportErrors); + } + else + SUPHNTIMP_ERROR(fReportErrors, 20, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: supHardNtLdrCacheEntryGetBits failed: %Rrc %s", + g_aSupNtImpDlls[iDll].pwszName, rc, pErrInfo ? pErrInfo->pszMsg : ""); + } + else + SUPHNTIMP_ERROR(fReportErrors, 21, "supR3HardenedWinInitImports", kSupInitOp_Misc, rc, + "%ls: supHardNtLdrCacheOpen failed: %Rrc %s", + g_aSupNtImpDlls[iDll].pwszName, rc, pErrInfo ? pErrInfo->pszMsg : ""); + } +} + + +/** + * Resolves a few NtDll functions we need before child purification is executed. + * + * We must not permanently modify any global data here. + * + * @param uNtDllAddr The address of the NTDLL. + * @param ppfnNtWaitForSingleObject Where to store the NtWaitForSingleObject + * address. + * @param ppfnNtSetEvent Where to store the NtSetEvent address. + */ +DECLHIDDEN(void) supR3HardenedWinGetVeryEarlyImports(uintptr_t uNtDllAddr, + PFNNTWAITFORSINGLEOBJECT *ppfnNtWaitForSingleObject, + PFNNTSETEVENT *ppfnNtSetEvent) +{ + /* + * NTDLL is the first entry in the list. Save it and do the parsing. + */ + SUPHNTIMPDLL SavedDllEntry = g_aSupNtImpDlls[0]; + + g_aSupNtImpDlls[0].pbImageBase = (uint8_t const *)uNtDllAddr; + supR3HardenedParseModule(&g_aSupNtImpDlls[0]); + + /* + * Create a temporary import table for the requested APIs and resolve them. + */ + SUPHNTIMPFUNC aImports[] = + { + { "NtWaitForSingleObject", (PFNRT *)ppfnNtWaitForSingleObject, NULL, false }, + { "NtSetEvent", (PFNRT *)ppfnNtSetEvent, NULL, false }, + }; + + for (uint32_t i = 0; i < RT_ELEMENTS(aImports); i++) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &aImports[i], false); + if (pszForwarder) + SUPHNTIMP_ERROR(false, 31, "supR3HardenedWinGetVeryEarlyImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "ntdll: Failed to resolve forwarder '%s'.", pszForwarder); + } + + /* + * Restore the NtDll entry. + */ + g_aSupNtImpDlls[0] = SavedDllEntry; +} + + +/** + * Resolves NtDll functions we can trust calling before process init. + * + * @param uNtDllAddr The address of the NTDLL. + */ +DECLHIDDEN(void) supR3HardenedWinInitImportsEarly(uintptr_t uNtDllAddr) +{ + /* + * NTDLL is the first entry in the list. + */ + g_aSupNtImpDlls[0].pbImageBase = (uint8_t const *)uNtDllAddr; + supR3HardenedParseModule(&g_aSupNtImpDlls[0]); + for (uint32_t i = 0; i < g_aSupNtImpDlls[0].cImports; i++) + if (!g_aSupNtImpDlls[0].paImports[i].pfnEarlyDummy) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &g_aSupNtImpDlls[0].paImports[i], false); + if (pszForwarder) + SUPHNTIMP_ERROR(false, 32, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "ntdll: Failed to resolve forwarder '%s'.", pszForwarder); + } + else + *g_aSupNtImpDlls[0].paImports[i].ppfnImport = g_aSupNtImpDlls[0].paImports[i].pfnEarlyDummy; + + /* + * Point the other imports at the early init stubs. + */ + for (uint32_t iDll = 1; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + if (!g_aSupNtImpDlls[iDll].paImports[i].fOptional) + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = g_aSupNtImpDlls[iDll].paImports[i].pfnEarlyDummy; + else + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = NULL; +} + + +/** + * Resolves imported functions, esp. system calls from NTDLL. + * + * This crap is necessary because there are sandboxing products out there that + * will mess with system calls we make, just like any other wannabe userland + * rootkit. Kudos to microsoft for not providing a generic system call hook API + * in the kernel mode, which I guess is what forcing these kind of products to + * do ugly userland hacks that doesn't really hold water. + */ +DECLHIDDEN(void) supR3HardenedWinInitImports(void) +{ + RTERRINFOSTATIC ErrInfo; + + /* + * Find the DLLs we will be needing first (forwarders). + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + { + supR3HardenedFindOrLoadModule(&g_aSupNtImpDlls[iDll]); + if (g_aSupNtImpDlls[iDll].pbImageBase) + supR3HardenedParseModule(&g_aSupNtImpDlls[iDll]); + } + + /* + * Resolve the functions. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + { + const char *pszForwarder = supR3HardenedResolveImport(&g_aSupNtImpDlls[iDll], &g_aSupNtImpDlls[iDll].paImports[i], + false); + if (pszForwarder) + { + const char *pszDot = strchr(pszForwarder, '.'); + size_t cchDllName = pszDot - pszForwarder; + SUPHNTIMPFUNC Tmp = g_aSupNtImpDlls[iDll].paImports[i]; + Tmp.pszName = pszDot + 1; + if (cchDllName == sizeof("ntdll") - 1 && RTStrNICmp(pszForwarder, RT_STR_TUPLE("ntdll")) == 0) + supR3HardenedResolveImport(&g_aSupNtImpDlls[0], &Tmp, false); + else if (cchDllName == sizeof("kernelbase") - 1 && RTStrNICmp(pszForwarder, RT_STR_TUPLE("kernelbase")) == 0) + supR3HardenedResolveImport(&g_aSupNtImpDlls[1], &Tmp, false); + else + SUPHNTIMP_ERROR(false, 18, "supR3HardenedWinInitImports", kSupInitOp_Misc, VERR_MODULE_NOT_FOUND, + "%ls: Failed to resolve forwarder '%s'.", g_aSupNtImpDlls[iDll].pwszName, pszForwarder); + } + } + + /* + * Do system calls directly. + */ + supR3HardenedWinInitSyscalls(false, RTErrInfoInitStatic(&ErrInfo)); + + /* + * Use the on disk image to avoid export table patching. Currently + * ignoring errors here as can live normally without this step. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (g_aSupNtImpDlls[iDll].cPatchedExports > 0) + { + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + for (uint32_t i = 0; i < g_aSupNtImpDlls[iDll].cImports; i++) + { + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + UINT32_MAX, g_aSupNtImpDlls[iDll].paImports[i].pszName, &uValue); + if (RT_SUCCESS(rc)) + *g_aSupNtImpDlls[iDll].paImports[i].ppfnImport = (PFNRT)(uintptr_t)uValue; + } + } + } + + +#if 0 /* Win7/32 ntdll!LdrpDebugFlags. */ + *(uint8_t *)&g_aSupNtImpDlls[0].pbImageBase[0xdd770] = 0x3; +#endif +} + + +/** + * Gets the address of a procedure in a DLL, ignoring our own syscall + * implementations. + * + * Currently restricted to NTDLL and KERNEL32 + * + * @returns The procedure address. + * @param pszDll The DLL name. + * @param pszProcedure The procedure name. + */ +DECLHIDDEN(PFNRT) supR3HardenedWinGetRealDllSymbol(const char *pszDll, const char *pszProcedure) +{ + RTERRINFOSTATIC ErrInfo; + + /* + * Look the DLL up in the import DLL table. + */ + for (uint32_t iDll = 0; iDll < RT_ELEMENTS(g_aSupNtImpDlls); iDll++) + if (RTStrICmp(g_aSupNtImpDlls[iDll].pszName, pszDll) == 0) + { + + PSUPHNTLDRCACHEENTRY pLdrEntry; + int rc = supHardNtLdrCacheOpen(g_aSupNtImpDlls[iDll].pszName, &pLdrEntry, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + uint8_t *pbBits; + rc = supHardNtLdrCacheEntryGetBits(pLdrEntry, &pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, NULL, NULL, + RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc)) + { + RTLDRADDR uValue; + rc = RTLdrGetSymbolEx(pLdrEntry->hLdrMod, pbBits, (uintptr_t)g_aSupNtImpDlls[iDll].pbImageBase, + UINT32_MAX, pszProcedure, &uValue); + if (RT_SUCCESS(rc)) + return (PFNRT)(uintptr_t)uValue; + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: Error getting %s in %s -> %Rrc\n", pszProcedure, pszDll, rc)); + } + else + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheEntryAllocBits failed on %s: %Rrc %s\n", + pszDll, rc, ErrInfo.Core.pszMsg)); + } + else + SUP_DPRINTF(("supR3HardenedWinGetRealDllSymbol: supHardNtLdrCacheOpen failed on %s: %Rrc %s\n", + pszDll, rc, ErrInfo.Core.pszMsg)); + + /* Complications, just call GetProcAddress. */ + if (g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + return (PFNRT)GetProcAddress(GetModuleHandleW(g_aSupNtImpDlls[iDll].pwszName), pszProcedure); + return NULL; + } + + supR3HardenedFatal("supR3HardenedWinGetRealDllSymbol: Unknown DLL %s (proc: %s)\n", pszDll, pszProcedure); + /* not reached */ +} + 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 00000000..711c6077 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp @@ -0,0 +1,501 @@ +/* $Id: SUPR3HardenedNoCrt-win.cpp $ */ +/** @file + * VirtualBox Support Library - Hardened main(), windows bits. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <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 <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> +#include <iprt/heap.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/utf16.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. + */ + +/** The handle of the heap we're using. */ +static HANDLE g_hSupR3HardenedHeap = NULL; +/** Number of heaps used during early process init. */ +static uint32_t g_cSupR3HardenedEarlyHeaps = 0; +/** Early process init heaps. */ +static struct +{ + /** The heap handle. */ + RTHEAPSIMPLE hHeap; + /** The heap block pointer. */ + void *pvBlock; + /** The size of the heap block. */ + size_t cbBlock; + /** Number of active allocations on this heap. */ + size_t cAllocations; +} g_aSupR3HardenedEarlyHeaps[8]; + + +static uint32_t supR3HardenedEarlyFind(void *pv) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if ((uintptr_t)pv - (uintptr_t)g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock < g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock) + return iHeap; + return UINT32_MAX; +} + + +static void supR3HardenedEarlyCompact(void) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations == 0) + { + PVOID pvMem = g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock; + SIZE_T cbMem = g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock; + if (iHeap + 1 < g_cSupR3HardenedEarlyHeaps) + g_aSupR3HardenedEarlyHeaps[iHeap] = g_aSupR3HardenedEarlyHeaps[g_cSupR3HardenedEarlyHeaps - 1]; + g_cSupR3HardenedEarlyHeaps--; + + NTSTATUS rcNt = NtFreeVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, MEM_RELEASE); + Assert(NT_SUCCESS(rcNt)); RT_NOREF_PV(rcNt); + SUP_DPRINTF(("supR3HardenedEarlyCompact: Removed heap %#u (%#p LB %#zx)\n", iHeap, pvMem, cbMem)); + } +} + + +static void *supR3HardenedEarlyAlloc(size_t cb, bool fZero) RT_NOTHROW_DEF +{ + /* + * Try allocate on existing heaps. + */ + void *pv; + uint32_t iHeap = 0; + while (iHeap < g_cSupR3HardenedEarlyHeaps) + { + if (fZero) + pv = RTHeapSimpleAllocZ(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + if (pv) + { + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations++; +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; + } + iHeap++; + } + + /* + * Add another heap. + */ + if (iHeap == RT_ELEMENTS(g_aSupR3HardenedEarlyHeaps)) + supR3HardenedFatal("Early heap table is full (cb=%#zx).\n", cb); + SIZE_T cbBlock = iHeap == 0 ? _1M : g_aSupR3HardenedEarlyHeaps[iHeap - 1].cbBlock * 2; + while (cbBlock <= cb * 2) + cbBlock *= 2; + + PVOID pvBlock = NULL; + NTSTATUS rcNt = NtAllocateVirtualMemory(NtCurrentProcess(), &pvBlock, 0 /*ZeroBits*/, &cbBlock, MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatal("NtAllocateVirtualMemory(,,,%#zx,,) failed: rcNt=%#x\n", cbBlock, rcNt); + SUP_DPRINTF(("New simple heap: #%u %p LB %#zx (for %zu allocation)\n", iHeap, pvBlock, cbBlock, cb)); + + RTHEAPSIMPLE hHeap; + int rc = RTHeapSimpleInit(&hHeap, pvBlock, cbBlock); + if (RT_FAILURE(rc)) + supR3HardenedFatal("RTHeapSimpleInit(,%p,%#zx) failed: rc=%#x\n", pvBlock, cbBlock, rc); + + if (fZero) + pv = RTHeapSimpleAllocZ(hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(hHeap, cb, 0); + if (!pv) + supR3HardenedFatal("RTHeapSimpleAlloc[Z] failed allocating %#zx bytes on a %#zu heap.\n", cb, cbBlock); + + g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock = pvBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock = cbBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations = 1; + g_aSupR3HardenedEarlyHeaps[iHeap].hHeap = hHeap; + + Assert(g_cSupR3HardenedEarlyHeaps == iHeap); + g_cSupR3HardenedEarlyHeaps = iHeap + 1; + +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; +} + + +/** + * Lazy heap initialization function. + * + * @returns Heap handle. + */ +static HANDLE supR3HardenedHeapInit(void) RT_NOTHROW_DEF +{ + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED); + HANDLE hHeap = RtlCreateHeap(HEAP_GROWABLE | HEAP_CLASS_PRIVATE, NULL /*HeapBase*/, + 0 /*ReserveSize*/, 0 /*CommitSize*/, NULL /*Lock*/, NULL /*Parameters*/); + if (hHeap) + { + g_hSupR3HardenedHeap = hHeap; + return hHeap; + } + + supR3HardenedFatal("RtlCreateHeap failed.\n"); + /* not reached */ +} + + +/** + * Compacts the heaps before enter wait for parent/child. + */ +DECLHIDDEN(void) supR3HardenedWinCompactHeaps(void) +{ + if (g_hSupR3HardenedHeap) + RtlCompactHeap(g_hSupR3HardenedHeap, 0 /*dwFlags*/); + RtlCompactHeap(GetProcessHeap(), 0 /*dwFlags*/); + supR3HardenedEarlyCompact(); +} + + + +#undef RTMemTmpAllocTag +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocTag(cb, pszTag); +} + + +#undef RTMemTmpAllocZTag +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocZTag(cb, pszTag); +} + + +#undef RTMemTmpFree +RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW_DEF +{ + RTMemFree(pv); +} + + +#undef RTMemAllocTag +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, false /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, 0 /*fFlags*/, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocZTag +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, true /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, HEAP_ZERO_MEMORY, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocVarTag +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocTag(cbAligned, pszTag); +} + + +#undef RTMemAllocZVarTag +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocZTag(cbAligned, pszTag); +} + + +#undef RTMemReallocTag +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + if (!pvOld) + return RTMemAllocZTag(cbNew, pszTag); + + void *pv; + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pvOld); + if (iHeap != UINT32_MAX) + { +#if 0 /* RTHeapSimpleRealloc is not implemented */ + /* If this is before we can use a regular heap, we try resize + within the simple heap. (There are a lot of array growing in + the ASN.1 code.) */ + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + { + pv = RTHeapSimpleRealloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld, cbNew, 0); + if (pv) + { +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p - realloc\n", pvNew, cbNew, pvOld)); +# endif + return pv; + } + } +#endif + + /* Either we can't reallocate it on the same simple heap, or we're + past hardened main and wish to migrate everything over on the + real heap. */ + size_t cbOld = RTHeapSimpleSize(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + pv = RTMemAllocTag(cbNew, pszTag); + if (pv) + { + memcpy(pv, pvOld, RT_MIN(cbOld, cbNew)); + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + } +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p %LB %#zx - realloc\n", pv, cbNew, pvOld, cbOld)); +# endif + return pv; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + /* Allocate from the regular heap. */ + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + pv = RtlReAllocateHeap(hHeap, 0 /*dwFlags*/, pvOld, cbNew); + if (!pv) + supR3HardenedFatal("RtlReAllocateHeap failed to allocate %zu bytes.\n", cbNew); + return pv; +} + + +#undef RTMemFree +RTDECL(void) RTMemFree(void *pv) RT_NO_THROW_DEF +{ + if (pv) + { + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pv); + if (iHeap != UINT32_MAX) + { +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p - free\n", pv)); +#endif + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pv); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + return; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + RtlFreeHeap(hHeap, 0 /* dwFlags*/, pv); + } +} + + +/* + * Simplified version of RTMemWipeThoroughly that avoids dragging in the + * random number code. + */ + +RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF +{ + size_t cPasses = RT_MIN(cMinPasses, 6); + static const uint32_t s_aPatterns[] = { 0x00, 0xaa, 0x55, 0xff, 0xf0, 0x0f, 0xcc, 0x3c, 0xc3 }; + uint32_t iPattern = 0; + do + { + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + } while (cPasses-- > 0); + + memset(pv, 0xff, cb); + ASMMemoryFence(); +} + + + +/* + * path-win.cpp + */ + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cbPath) +{ + int rc; + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) +/** @todo Rainy day: improve this by checking the process parameter block + * (needs to be normalized). */ + rc = RTStrCopy(pszPath, cbPath, "C:\\"); + else + { + /* + * GetCurrentDirectory may in some cases omit the drive letter, according + * to MSDN, thus the GetFullPathName call. + */ + RTUTF16 wszCurPath[RTPATH_MAX]; + if (GetCurrentDirectoryW(RTPATH_MAX, wszCurPath)) + { + RTUTF16 wszFullPath[RTPATH_MAX]; + if (GetFullPathNameW(wszCurPath, RTPATH_MAX, wszFullPath, NULL)) + rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cbPath, NULL); + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + return rc; +} + diff --git a/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp b/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp new file mode 100644 index 00000000..8eb05451 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp @@ -0,0 +1,908 @@ +/* $Id: SUPSvc-win.cpp $ */ +/** @file + * VirtualBox Support Service - Windows Specific Code. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SUP +#include <iprt/win/windows.h> + +#include <VBox/log.h> +#include <VBox/version.h> +#include <iprt/string.h> +#include <iprt/mem.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/getopt.h> +#include <iprt/semaphore.h> +#ifdef DEBUG_bird +# include <iprt/env.h> +#endif + +#include "../SUPSvcInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The service name. */ +#define SUPSVC_SERVICE_NAME "VBoxSupSvc" +/** The service display name. */ +#define SUPSVC_SERVICE_DISPLAY_NAME "VirtualBox Support Service" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The service control handler handle. */ +static SERVICE_STATUS_HANDLE g_hSupSvcWinCtrlHandler = NULL; +/** The service status. */ +static uint32_t volatile g_u32SupSvcWinStatus = SERVICE_STOPPED; +/** The semaphore the main service thread is waiting on in supSvcWinServiceMain. */ +static RTSEMEVENTMULTI g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static SC_HANDLE supSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess); + + +/** + * Opens the service control manager. + * + * When this fails, an error message will be displayed. + * + * @returns Valid handle on success. + * NULL on failure, will display an error message. + * + * @param pszAction The action which is requesting access to SCM. + * @param dwAccess The desired access. + */ +static SC_HANDLE supSvcWinOpenSCManager(const char *pszAction, DWORD dwAccess) +{ + SC_HANDLE hSCM = OpenSCManager(NULL /* lpMachineName*/, NULL /* lpDatabaseName */, dwAccess); + if (hSCM == NULL) + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_ACCESS_DENIED: + supSvcDisplayError("%s - OpenSCManager failure: access denied\n", pszAction); + break; + default: + supSvcDisplayError("%s - OpenSCManager failure: %d\n", pszAction, err); + break; + } + } + return hSCM; +} + + +/** + * Opens the service. + * + * Last error is preserved on failure and set to 0 on success. + * + * @returns Valid service handle on success. + * NULL on failure, will display an error message unless it's ignored. + * + * @param pszAction The action which is requesting access to the service. + * @param dwSCMAccess The service control manager access. + * @param dwSVCAccess The desired service access. + * @param cIgnoredErrors The number of ignored errors. + * @param ... Errors codes that should not cause a message to be displayed. + */ +static SC_HANDLE supSvcWinOpenService(const char *pszAction, DWORD dwSCMAccess, DWORD dwSVCAccess, + unsigned cIgnoredErrors, ...) +{ + SC_HANDLE hSCM = supSvcWinOpenSCManager(pszAction, dwSCMAccess); + if (!hSCM) + return NULL; + + SC_HANDLE hSvc = OpenService(hSCM, SUPSVC_SERVICE_NAME, dwSVCAccess); + if (hSvc) + { + CloseServiceHandle(hSCM); + SetLastError(0); + } + else + { + DWORD err = GetLastError(); + bool fIgnored = false; + va_list va; + va_start(va, cIgnoredErrors); + while (!fIgnored && cIgnoredErrors-- > 0) + fIgnored = va_arg(va, long) == err; + va_end(va); + if (!fIgnored) + { + switch (err) + { + case ERROR_ACCESS_DENIED: + supSvcDisplayError("%s - OpenService failure: access denied\n", pszAction); + break; + case ERROR_SERVICE_DOES_NOT_EXIST: + supSvcDisplayError("%s - OpenService failure: The service does not exist. Reinstall it.\n", pszAction); + break; + default: + supSvcDisplayError("%s - OpenService failure: %d\n", pszAction, err); + break; + } + } + + CloseServiceHandle(hSCM); + SetLastError(err); + } + return hSvc; +} + + + +void supSvcOsLogErrorStr(const char *pszMsg) +{ + HANDLE hEventLog = RegisterEventSource(NULL /* local computer */, "VBoxSupSvc"); + AssertReturnVoid(hEventLog != NULL); + const char *apsz[2]; + apsz[0] = "VBoxSupSvc"; + apsz[1] = pszMsg; + BOOL fRc = ReportEvent(hEventLog, /* hEventLog */ + EVENTLOG_ERROR_TYPE, /* wType */ + 0, /* wCategory */ + 0 /** @todo mc */, /* dwEventID */ + NULL, /* lpUserSid */ + RT_ELEMENTS(apsz), /* wNumStrings */ + 0, /* dwDataSize */ + apsz, /* lpStrings */ + NULL); /* lpRawData */ + AssertMsg(fRc, ("%d\n", GetLastError())); + DeregisterEventSource(hEventLog); +} + + +static int supSvcWinInterrogate(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"interrogate\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinStop(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"stop\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinContinue(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"continue\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinPause(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"pause\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinStart(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"start\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinQueryDescription(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"qdescription\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinQueryConfig(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"qconfig\" action is not implemented.\n"); + return 1; +} + + +static int supSvcWinDisable(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"disable\" action is not implemented.\n"); + return 1; +} + +static int supSvcWinEnable(int argc, char **argv) +{ + RTPrintf("VBoxSupSvc: The \"enable\" action is not implemented.\n"); + return 1; +} + + +/** + * Handle the 'delete' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinDelete(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + static const RTGETOPTDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + int ch; + RTGETOPTUNION Value; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS); + while ((ch = RTGetOpt(&GetState, &Value))) + switch (ch) + { + case 'v': + fVerbose = true; + break; + case VINF_GETOPT_NOT_OPTION: + return supSvcDisplayTooManyArgsError("delete", argc, argv, iArg); + default: + return supSvcDisplayGetOptError("delete", ch, argc, argv, iArg, &Value); + } + + /* + * Create the service. + */ + int rc = 1; + SC_HANDLE hSvc = supSvcWinOpenService("delete", SERVICE_CHANGE_CONFIG, DELETE, + 1, ERROR_SERVICE_DOES_NOT_EXIST); + if (hSvc) + { + if (DeleteService(hSvc)) + { + RTPrintf("Successfully deleted the %s service.\n", SUPSVC_SERVICE_NAME); + rc = 0; + } + else + supSvcDisplayError("delete - DeleteService failed, err=%d.\n", GetLastError()); + CloseServiceHandle(hSvc); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + { + + if (fVerbose) + RTPrintf("The service %s was not installed, nothing to be done.", SUPSVC_SERVICE_NAME); + else + RTPrintf("Successfully deleted the %s service.\n", SUPSVC_SERVICE_NAME); + rc = 0; + } + return rc; +} + + +/** + * Handle the 'create' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinCreate(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fVerbose = false; + static const RTOPTIONDEF s_aOptions[] = + { + { "--verbose", 'v', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + case 'v': fVerbose = true; break; + default: return supSvcDisplayGetOptError("create", ch, argc, argv, iArg, &Value); + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("create", argc, argv, iArg); + + /* + * Create the service. + */ + int rc = 1; + SC_HANDLE hSCM = supSvcWinOpenSCManager("create", SC_MANAGER_CREATE_SERVICE); /*SC_MANAGER_ALL_ACCESS*/ + if (hSCM) + { + char szExecPath[MAX_PATH]; + if (GetModuleFileName(NULL /* the executable */, szExecPath, sizeof(szExecPath))) + { + if (fVerbose) + RTPrintf("Creating the %s service, binary \"%s\"...\n", + SUPSVC_SERVICE_NAME, szExecPath); /* yea, the binary name isn't UTF-8, but wtf. */ + + SC_HANDLE hSvc = CreateService(hSCM, /* hSCManager */ + SUPSVC_SERVICE_NAME, /* lpServiceName */ + SUPSVC_SERVICE_DISPLAY_NAME, /* lpDisplayName */ + SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG, /* dwDesiredAccess */ + SERVICE_WIN32_OWN_PROCESS, /* dwServiceType ( | SERVICE_INTERACTIVE_PROCESS? ) */ + SERVICE_DEMAND_START/*_AUTO*/, /* dwStartType */ + SERVICE_ERROR_NORMAL, /* dwErrorControl */ + szExecPath, /* lpBinaryPathName */ + NULL, /* lpLoadOrderGroup */ + NULL, /* lpdwTagId */ + NULL, /* lpDependencies */ + NULL, /* lpServiceStartName (=> LocalSystem) */ + NULL); /* lpPassword */ + if (hSvc) + { + RTPrintf("Successfully created the %s service.\n", SUPSVC_SERVICE_NAME); + /** @todo Set the service description or it'll look weird in the vista service manager. + * Anything else that should be configured? Start access or something? */ + rc = 0; + CloseServiceHandle(hSvc); + } + else + { + DWORD err = GetLastError(); + switch (err) + { + case ERROR_SERVICE_EXISTS: + supSvcDisplayError("create - The service already exists.\n"); + break; + default: + supSvcDisplayError("create - CreateService failed, err=%d.\n", GetLastError()); + break; + } + } + CloseServiceHandle(hSvc); + } + else + supSvcDisplayError("create - Failed to obtain the executable path: %d\n", GetLastError()); + } + return rc; +} + + +/** + * Sets the service status, just a SetServiceStatus Wrapper. + * + * @returns See SetServiceStatus. + * @param dwStatus The current status. + * @param iWaitHint The wait hint, if < 0 then supply a default. + * @param dwExitCode The service exit code. + */ +static bool supSvcWinSetServiceStatus(DWORD dwStatus, int iWaitHint, DWORD dwExitCode) +{ + SERVICE_STATUS SvcStatus; + SvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + SvcStatus.dwWin32ExitCode = dwExitCode; + SvcStatus.dwServiceSpecificExitCode = 0; + SvcStatus.dwWaitHint = iWaitHint >= 0 ? iWaitHint : 3000; + SvcStatus.dwCurrentState = dwStatus; + LogFlow(("supSvcWinSetServiceStatus: %d -> %d\n", g_u32SupSvcWinStatus, dwStatus)); + g_u32SupSvcWinStatus = dwStatus; + switch (dwStatus) + { + case SERVICE_START_PENDING: + SvcStatus.dwControlsAccepted = 0; + break; + default: + SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + break; + } + + static DWORD dwCheckPoint = 0; + switch (dwStatus) + { + case SERVICE_RUNNING: + case SERVICE_STOPPED: + SvcStatus.dwCheckPoint = 0; + default: + SvcStatus.dwCheckPoint = ++dwCheckPoint; + break; + } + return SetServiceStatus(g_hSupSvcWinCtrlHandler, &SvcStatus) != FALSE; +} + + +/** + * Service control handler (extended). + * + * @returns Windows status (see HandlerEx). + * @retval NO_ERROR if handled. + * @retval ERROR_CALL_NOT_IMPLEMENTED if not handled. + * + * @param dwControl The control code. + * @param dwEventType Event type. (specific to the control?) + * @param pvEventData Event data, specific to the event. + * @param pvContext The context pointer registered with the handler. + * Currently not used. + */ +static DWORD WINAPI supSvcWinServiceCtrlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID pvEventData, LPVOID pvContext) +{ + LogFlow(("supSvcWinServiceCtrlHandlerEx: dwControl=%#x dwEventType=%#x pvEventData=%p\n", + dwControl, dwEventType, pvEventData)); + + switch (dwControl) + { + /* + * Interrogate the service about it's current status. + * MSDN says that this should just return NO_ERROR and does + * not need to set the status again. + */ + case SERVICE_CONTROL_INTERROGATE: + return NO_ERROR; + + /* + * Request to stop the service. + */ + case SERVICE_CONTROL_STOP: + { + /* + * Check if the real services can be stopped and then tell them to stop. + */ + supSvcWinSetServiceStatus(SERVICE_STOP_PENDING, 3000, NO_ERROR); + int rc = supSvcTryStopServices(); + if (RT_SUCCESS(rc)) + { + /* + * Notify the main thread that we're done, it will wait for the + * real services to stop, destroy them, and finally set the windows + * service status to SERVICE_STOPPED and return. + */ + rc = RTSemEventMultiSignal(g_hSupSvcWinEvent); + if (RT_FAILURE(rc)) + supSvcLogError("SERVICE_CONTROL_STOP: RTSemEventMultiSignal failed, %Rrc\n", rc); + } + return NO_ERROR; + } + + case SERVICE_CONTROL_PAUSE: + case SERVICE_CONTROL_CONTINUE: + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_PARAMCHANGE: + case SERVICE_CONTROL_NETBINDADD: + case SERVICE_CONTROL_NETBINDREMOVE: + case SERVICE_CONTROL_NETBINDENABLE: + case SERVICE_CONTROL_NETBINDDISABLE: + case SERVICE_CONTROL_DEVICEEVENT: + case SERVICE_CONTROL_HARDWAREPROFILECHANGE: + case SERVICE_CONTROL_POWEREVENT: + case SERVICE_CONTROL_SESSIONCHANGE: +#ifdef SERVICE_CONTROL_PRESHUTDOWN /* vista */ + case SERVICE_CONTROL_PRESHUTDOWN: +#endif + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } + + NOREF(dwEventType); + NOREF(pvEventData); + NOREF(pvContext); + return NO_ERROR; +} + + +/** + * Windows Service Main. + * + * This is invoked when the service is started and should not return until + * the service has been stopped. + * + * @param cArgs Argument count. + * @param papszArgs Argument vector. + */ +static VOID WINAPI supSvcWinServiceMain(DWORD cArgs, LPSTR *papszArgs) +{ + LogFlowFuncEnter(); + + /* + * Register the control handler function for the service and report to SCM. + */ + Assert(g_u32SupSvcWinStatus == SERVICE_STOPPED); + g_hSupSvcWinCtrlHandler = RegisterServiceCtrlHandlerEx(SUPSVC_SERVICE_NAME, supSvcWinServiceCtrlHandlerEx, NULL); + if (g_hSupSvcWinCtrlHandler) + { + DWORD err = ERROR_GEN_FAILURE; + if (supSvcWinSetServiceStatus(SERVICE_START_PENDING, 3000, NO_ERROR)) + { + /* + * Parse arguments. + */ + static const RTOPTIONDEF s_aOptions[] = + { + { "--dummy", 'd', RTGETOPT_REQ_NOTHING } + }; + int iArg = 1; /* the first arg is the service name */ + int ch; + int rc = 0; + RTGETOPTUNION Value; + while ( !rc + && (ch = RTGetOpt(cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + default: rc = supSvcLogGetOptError("main", ch, cArgs, papszArgs, iArg, &Value); break; + } + if (iArg != cArgs) + rc = supSvcLogTooManyArgsError("main", cArgs, papszArgs, iArg); + if (!rc) + { + /* + * Create the event semaphore we'll be waiting on and + * then instantiate the actual services. + */ + int rc = RTSemEventMultiCreate(&g_hSupSvcWinEvent); + if (RT_SUCCESS(rc)) + { + rc = supSvcCreateAndStartServices(); + if (RT_SUCCESS(rc)) + { + /* + * Update the status and enter the work loop. + * + * The work loop is just a dummy wait here as the services run + * in independent threads. + */ + if (supSvcWinSetServiceStatus(SERVICE_RUNNING, 0, 0)) + { + LogFlow(("supSvcWinServiceMain: calling RTSemEventMultiWait\n")); + rc = RTSemEventMultiWait(g_hSupSvcWinEvent, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + LogFlow(("supSvcWinServiceMain: woke up\n")); + err = NO_ERROR; + } + else + supSvcLogError("RTSemEventWait failed, rc=%Rrc", rc); + } + else + { + err = GetLastError(); + supSvcLogError("SetServiceStatus failed, err=%d", err); + } + + /* + * Destroy the service instances, stopping them if + * they're still running (weird failure cause). + */ + supSvcStopAndDestroyServices(); + } + + RTSemEventMultiDestroy(g_hSupSvcWinEvent); + g_hSupSvcWinEvent = NIL_RTSEMEVENTMULTI; + } + else + supSvcLogError("RTSemEventMultiCreate failed, rc=%Rrc", rc); + } + /* else: bad args */ + } + else + { + err = GetLastError(); + supSvcLogError("SetServiceStatus failed, err=%d", err); + } + supSvcWinSetServiceStatus(SERVICE_STOPPED, 0, err); + } + else + supSvcLogError("RegisterServiceCtrlHandlerEx failed, err=%d", GetLastError()); + LogFlowFuncLeave(); +} + + +/** + * Handle the 'create' action. + * + * @returns 0 or 1. + * @param argc The action argument count. + * @param argv The action argument vector. + */ +static int supSvcWinRunIt(int argc, char **argv) +{ + LogFlowFuncEnter(); + + /* + * Initialize release logging. + */ + /** @todo release logging of the system-wide service. */ + + /* + * Parse the arguments. + */ + static const RTOPTIONDEF s_aOptions[] = + { + { "--dummy", 'd', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + default: return supSvcDisplayGetOptError("runit", ch, argc, argv, iArg, &Value); + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("runit", argc, argv, iArg); + + /* + * Register the service with the service control manager + * and start dispatching requests from it (all done by the API). + */ + static SERVICE_TABLE_ENTRY const s_aServiceStartTable[] = + { + { SUPSVC_SERVICE_NAME, supSvcWinServiceMain }, + { NULL, NULL} + }; + if (StartServiceCtrlDispatcher(&s_aServiceStartTable[0])) + { + LogFlowFuncLeave(); + return 0; /* told to quit, so quit. */ + } + + DWORD err = GetLastError(); + switch (err) + { + case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: + supSvcDisplayError("Cannot run a service from the command line. Use the 'start' action to start it the right way.\n"); + break; + default: + supSvcLogError("StartServiceCtrlDispatcher failed, err=%d", err); + break; + } + return 1; +} + + +/** + * Show the version info. + * + * @returns 0. + */ +static int supSvcWinShowVersion(int argc, char **argv) +{ + /* + * Parse the arguments. + */ + bool fBrief = false; + static const RTOPTIONDEF s_aOptions[] = + { + { "--brief", 'b', RTGETOPT_REQ_NOTHING } + }; + int iArg = 0; + int ch; + RTGETOPTUNION Value; + while ((ch = RTGetOpt(argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), &iArg, &Value))) + switch (ch) + { + case 'b': fBrief = true; break; + default: return supSvcDisplayGetOptError("version", ch, argc, argv, iArg, &Value); + + } + if (iArg != argc) + return supSvcDisplayTooManyArgsError("version", argc, argv, iArg); + + /* + * Do the printing. + */ + if (fBrief) + RTPrintf("%s\n", VBOX_VERSION_STRING); + else + RTPrintf("VirtualBox System Service Version %s\n" + "Copyright (C) 2008-" VBOX_C_YEAR " Oracle and/or its affiliates\n\n", + VBOX_VERSION_STRING); + return 0; +} + + +/** + * Show the usage help screen. + * + * @returns 0. + */ +static int supSvcWinShowHelp(void) +{ + RTPrintf("VirtualBox System Service Version %s\n" + "Copyright (C) 2008-" VBOX_C_YEAR " Oracle and/or its affiliates\n\n", + VBOX_VERSION_STRING); + RTPrintf("Usage:\n" + "\n" + "VBoxSupSvc\n" + " Runs the service.\n" + "VBoxSupSvc <version|-v|--version> [-brief]\n" + " Displays the version.\n" + "VBoxSupSvc <help|-?|-h|--help> [...]\n" + " Displays this help screen.\n" + "\n" + "VBoxSupSvc <install|/RegServer|/i>\n" + " Installs the service.\n" + "VBoxSupSvc <install|delete|/UnregServer|/u>\n" + " Uninstalls the service.\n" + ); + return 0; +} + + +/** + * VBoxSUPSvc main(), Windows edition. + * + * + * @returns 0 on success. + * + * @param argc Number of arguments in argv. + * @param argv Argument vector. + */ +int main(int argc, char **argv) +{ + /* + * Initialize the IPRT first of all. + */ +#ifdef DEBUG_bird + RTEnvSet("VBOX_LOG", "sup=~0"); + RTEnvSet("VBOX_LOG_DEST", "file=E:\\temp\\VBoxSupSvc.log"); + RTEnvSet("VBOX_LOG_FLAGS", "unbuffered thread msprog"); +#endif + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + { + supSvcLogError("RTR3InitExe failed with rc=%Rrc", rc); + return 1; + } + + /* + * Parse the initial arguments to determine the desired action. + */ + enum + { + kSupSvcAction_RunIt, + + kSupSvcAction_Create, + kSupSvcAction_Delete, + + kSupSvcAction_Enable, + kSupSvcAction_Disable, + kSupSvcAction_QueryConfig, + kSupSvcAction_QueryDescription, + + kSupSvcAction_Start, + kSupSvcAction_Pause, + kSupSvcAction_Continue, + kSupSvcAction_Stop, + kSupSvcAction_Interrogate, + + kSupSvcAction_End + } enmAction = kSupSvcAction_RunIt; + int iArg = 1; + if (argc > 1) + { + if ( !stricmp(argv[iArg], "/RegServer") + || !stricmp(argv[iArg], "install") + || !stricmp(argv[iArg], "/i")) + enmAction = kSupSvcAction_Create; + else if ( !stricmp(argv[iArg], "/UnregServer") + || !stricmp(argv[iArg], "/u") + || !stricmp(argv[iArg], "uninstall") + || !stricmp(argv[iArg], "delete")) + enmAction = kSupSvcAction_Delete; + + else if (!stricmp(argv[iArg], "enable")) + enmAction = kSupSvcAction_Enable; + else if (!stricmp(argv[iArg], "disable")) + enmAction = kSupSvcAction_Disable; + else if (!stricmp(argv[iArg], "qconfig")) + enmAction = kSupSvcAction_QueryConfig; + else if (!stricmp(argv[iArg], "qdescription")) + enmAction = kSupSvcAction_QueryDescription; + + else if ( !stricmp(argv[iArg], "start") + || !stricmp(argv[iArg], "/t")) + enmAction = kSupSvcAction_Start; + else if (!stricmp(argv[iArg], "pause")) + enmAction = kSupSvcAction_Start; + else if (!stricmp(argv[iArg], "continue")) + enmAction = kSupSvcAction_Continue; + else if (!stricmp(argv[iArg], "stop")) + enmAction = kSupSvcAction_Stop; + else if (!stricmp(argv[iArg], "interrogate")) + enmAction = kSupSvcAction_Interrogate; + else if ( !stricmp(argv[iArg], "help") + || !stricmp(argv[iArg], "?") + || !stricmp(argv[iArg], "/?") + || !stricmp(argv[iArg], "-?") + || !stricmp(argv[iArg], "/h") + || !stricmp(argv[iArg], "-h") + || !stricmp(argv[iArg], "/help") + || !stricmp(argv[iArg], "-help") + || !stricmp(argv[iArg], "--help")) + return supSvcWinShowHelp(); + else if ( !stricmp(argv[iArg], "version") + || !stricmp(argv[iArg], "/v") + || !stricmp(argv[iArg], "-v") + || !stricmp(argv[iArg], "/version") + || !stricmp(argv[iArg], "-version") + || !stricmp(argv[iArg], "--version")) + return supSvcWinShowVersion(argc - iArg - 1, argv + iArg + 1); + else + iArg--; + iArg++; + } + + /* + * Dispatch it. + */ + switch (enmAction) + { + case kSupSvcAction_RunIt: + return supSvcWinRunIt(argc - iArg, argv + iArg); + + case kSupSvcAction_Create: + return supSvcWinCreate(argc - iArg, argv + iArg); + case kSupSvcAction_Delete: + return supSvcWinDelete(argc - iArg, argv + iArg); + + case kSupSvcAction_Enable: + return supSvcWinEnable(argc - iArg, argv + iArg); + case kSupSvcAction_Disable: + return supSvcWinDisable(argc - iArg, argv + iArg); + case kSupSvcAction_QueryConfig: + return supSvcWinQueryConfig(argc - iArg, argv + iArg); + case kSupSvcAction_QueryDescription: + return supSvcWinQueryDescription(argc - iArg, argv + iArg); + + case kSupSvcAction_Start: + return supSvcWinStart(argc - iArg, argv + iArg); + case kSupSvcAction_Pause: + return supSvcWinPause(argc - iArg, argv + iArg); + case kSupSvcAction_Continue: + return supSvcWinContinue(argc - iArg, argv + iArg); + case kSupSvcAction_Stop: + return supSvcWinStop(argc - iArg, argv + iArg); + case kSupSvcAction_Interrogate: + return supSvcWinInterrogate(argc - iArg, argv + iArg); + + default: + AssertMsgFailed(("enmAction=%d\n", enmAction)); + return 1; + } +} + diff --git a/src/VBox/HostDrivers/Support/win/VBoxDrv.rc b/src/VBox/HostDrivers/Support/win/VBoxDrv.rc new file mode 100644 index 00000000..5600f4db --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxDrv.rc @@ -0,0 +1,71 @@ +/* $Id: VBoxDrv.rc $ */ +/** @file + * VBoxDrv - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Support Driver\0" + VALUE "InternalName", "VBoxSup\0" + VALUE "OriginalFilename", "VBoxSup.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + diff --git a/src/VBox/HostDrivers/Support/win/VBoxSup.inf b/src/VBox/HostDrivers/Support/win/VBoxSup.inf new file mode 100644 index 00000000..2309fb06 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSup.inf @@ -0,0 +1,100 @@ +; $Id: VBoxSup.inf $ +;; @file +; VirtualBox Support Driver - Windows Driver INF file. +; + +; +; Copyright (C) 2006-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see <https://www.gnu.org/licenses>. +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature="$Windows NT$" +Class=System +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Provider=%ORACLE% +;edit-DriverVer=08/26/2008,2.00.0000 +DriverPackageType=KernelService +;cat CatalogFile=VBoxSup.cat + +[DestinationDirs] +DefaultDestDir = 12 + +[DefaultInstall@DOT-NT-ARCH@] +CopyFiles=VBoxSup_CopyFiles + +[DefaultInstall@DOT-NT-ARCH@.Services] +AddService=VBoxSup,0x00000002,VBoxSup_Service + +[DefaultUninstall@DOT-NT-ARCH@] +DefFiles=VBoxSup_CopyFiles +LegacyUninstall=1 + +[DefaultUninstall@DOT-NT-ARCH@.Services] +DelService=VBoxSup,0x00000200 +LegacyUninstall=1 + +;; This does not actually work either (see VBoxUSBMon), though it triggers in the +;; installer rather at manual installation on my test system (could be polluted & confused). +;; This may apparently also causes unloading trouble due to 'root\VBoxSup' or something related to that. +;; [Manufacturer] +;; %ORACLE%=VBoxSup@COMMA-NT-ARCH@ +;; +;; ; Models section (referenced by [Manufacturer]). +;; [VBoxSup@DOT-NT-ARCH@] +;; %VBoxSup.DRVDESC%=VBoxSup_Install,root\VBoxSup +;; +;; [VBoxSup_Install@DOT-NT-ARCH@] +;; CopyFiles=VBoxSup_CopyFiles +;; +;; [VBoxSup_Install@DOT-NT-ARCH@.Services] +;; AddService=VBoxSup,0x00000002,VBoxSup_Service + +[SourceDisksFiles] +VBoxSup.sys=1 + +[SourceDisksNames] +1=%VBoxSup.DSKDESC%, + +[VBoxSup_CopyFiles] +VBoxSup.sys + +[VBoxSup_Service] +DisplayName = %VBoxSup.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +;StartType = 3 ; SERVICE_DEMAND_START +StartType = 1 ; autostart to fix Vista problem +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxSup.sys + +[Strings] +ORACLE = "Oracle Corporation" +VBoxSup.SVCDESC = "VirtualBox Service" +VBoxSup.DRVDESC = "VirtualBox Support Driver" +VBoxSup.DSKDESC = "VirtualBox Support Driver Installation Disk" 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 00000000..f50cf9c5 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp @@ -0,0 +1,108 @@ +/* $Id: VBoxSupLib-win.cpp $ */ +/** @file + * IPRT - VBoxSupLib.dll, Windows. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/nt/nt-and-windows.h> + +#include <iprt/path.h> + + +/** + * The Dll main entry point. + * @remarks The dllexport is for forcing the linker to generate an import + * library, so the build system doesn't get confused. + */ +extern "C" __declspec(dllexport) +BOOL __stdcall DllMainEntrypoint(HANDLE hModule, DWORD dwReason, PVOID pvReserved) +{ + RT_NOREF1(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 + && RtlGetLastWin32Error() == NO_ERROR) + { + int cExtraLoads = 2; + while (cExtraLoads-- > 0) + LoadLibraryW(wszName); + } + break; + } + + case DLL_THREAD_ATTACH: + { +#ifdef VBOX_WITH_HARDENING +# 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 +#endif + break; + } + + case DLL_THREAD_DETACH: + /* Nothing to do. */ + break; + + case DLL_PROCESS_DETACH: + /* Nothing to do. */ + break; + + default: + /* ignore */ + break; + } + return TRUE; +} + diff --git a/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc b/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc new file mode 100644 index 00000000..6e3d3525 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/VBoxSupLib.rc @@ -0,0 +1,70 @@ +/* $Id: VBoxSupLib.rc $ */ +/** @file + * VBoxSupLib - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2015-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox Kernel Support\0" + VALUE "InternalName", "VBoxSupLib\0" + VALUE "OriginalFilename", "VBoxSupLib.dll\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/Support/win/import-template-kernel32.h b/src/VBox/HostDrivers/Support/win/import-template-kernel32.h new file mode 100644 index 00000000..f761d734 --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/import-template-kernel32.h @@ -0,0 +1,21 @@ +SUPHARNT_IMPORT_STDCALL(CreateFileW, 28) +SUPHARNT_IMPORT_STDCALL(CreateProcessW, 40) +SUPHARNT_IMPORT_STDCALL(ExitProcess, 4) +SUPHARNT_IMPORT_STDCALL(GetFullPathNameA, 16) +SUPHARNT_IMPORT_STDCALL(GetFullPathNameW, 16) +SUPHARNT_IMPORT_STDCALL(GetCurrentDirectoryW, 8) +SUPHARNT_IMPORT_STDCALL(GetLongPathNameW, 12) +SUPHARNT_IMPORT_STDCALL(GetModuleFileNameW, 12) +SUPHARNT_IMPORT_STDCALL(GetModuleHandleA, 4) +SUPHARNT_IMPORT_STDCALL(GetModuleHandleW, 4) +SUPHARNT_IMPORT_STDCALL(GetProcAddress, 8) +SUPHARNT_IMPORT_STDCALL(GetProcessHeap, 0) +SUPHARNT_IMPORT_STDCALL(GetSystemDirectoryW, 8) +SUPHARNT_IMPORT_STDCALL(GetTickCount, 0) +SUPHARNT_IMPORT_STDCALL(LoadLibraryExW, 12) +SUPHARNT_IMPORT_STDCALL(OutputDebugStringA, 4) +SUPHARNT_IMPORT_STDCALL(TlsAlloc, 0) +SUPHARNT_IMPORT_STDCALL(TlsGetValue, 4) +SUPHARNT_IMPORT_STDCALL(TlsSetValue, 8) +SUPHARNT_IMPORT_STDCALL(WriteFile, 20) + diff --git a/src/VBox/HostDrivers/Support/win/import-template-ntdll.h b/src/VBox/HostDrivers/Support/win/import-template-ntdll.h new file mode 100644 index 00000000..fcd7a24b --- /dev/null +++ b/src/VBox/HostDrivers/Support/win/import-template-ntdll.h @@ -0,0 +1,99 @@ +SUPHARNT_IMPORT_SYSCALL(NtAllocateVirtualMemory, 24) +SUPHARNT_IMPORT_SYSCALL(NtClearEvent, 4) +SUPHARNT_IMPORT_SYSCALL(NtClose, 4) +SUPHARNT_IMPORT_SYSCALL(NtCreateEvent, 20) +SUPHARNT_IMPORT_SYSCALL(NtCreateFile, 44) +SUPHARNT_IMPORT_SYSCALL(NtCreateSymbolicLinkObject, 16) +SUPHARNT_IMPORT_SYSCALL(NtDelayExecution, 8) +SUPHARNT_IMPORT_SYSCALL(NtDeviceIoControlFile, 40) +SUPHARNT_IMPORT_SYSCALL(NtDuplicateObject, 28) +SUPHARNT_IMPORT_SYSCALL(NtFlushBuffersFile, 8) +SUPHARNT_IMPORT_SYSCALL(NtFreeVirtualMemory, 16) +SUPHARNT_IMPORT_SYSCALL(NtGetContextThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtMapViewOfSection, 40) +SUPHARNT_IMPORT_SYSCALL(NtOpenDirectoryObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenEvent, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenKey, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenProcess, 16) +SUPHARNT_IMPORT_SYSCALL(NtOpenProcessToken, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenSymbolicLinkObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtOpenThread, 16) +SUPHARNT_IMPORT_SYSCALL(NtOpenThreadToken, 16) +SUPHARNT_IMPORT_SYSCALL(NtProtectVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryDirectoryFile, 44) +SUPHARNT_IMPORT_SYSCALL(NtQueryDirectoryObject, 28) +SUPHARNT_IMPORT_SYSCALL(NtQueryEvent, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationFile, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationProcess, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationThread, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryInformationToken, 20) +SUPHARNT_IMPORT_SYSCALL(NtQueryObject, 20) +SUPHARNT_IMPORT_SYSCALL(NtQuerySecurityObject, 20) +SUPHARNT_IMPORT_SYSCALL(NtQuerySymbolicLinkObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtQuerySystemInformation, 16) +SUPHARNT_IMPORT_SYSCALL(NtQueryTimerResolution, 12) +SUPHARNT_IMPORT_SYSCALL(NtQueryValueKey, 24) +SUPHARNT_IMPORT_SYSCALL(NtQueryVirtualMemory, 24) +SUPHARNT_IMPORT_SYSCALL(NtReadFile, 36) +SUPHARNT_IMPORT_SYSCALL(NtReadVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtResetEvent, 8) +SUPHARNT_IMPORT_SYSCALL(NtResumeProcess, 4) +SUPHARNT_IMPORT_SYSCALL(NtResumeThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetContextThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetEvent, 8) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationFile, 20) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationObject, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationProcess, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetInformationThread, 16) +SUPHARNT_IMPORT_SYSCALL(NtSetTimerResolution, 12) +SUPHARNT_IMPORT_SYSCALL(NtSuspendProcess, 4) +SUPHARNT_IMPORT_SYSCALL(NtSuspendThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtTerminateProcess, 8) +SUPHARNT_IMPORT_SYSCALL(NtTerminateThread, 8) +SUPHARNT_IMPORT_SYSCALL(NtTestAlert, 0) +SUPHARNT_IMPORT_SYSCALL(NtUnmapViewOfSection, 8) +SUPHARNT_IMPORT_SYSCALL(NtWaitForMultipleObjects, 20) +SUPHARNT_IMPORT_SYSCALL(NtWaitForSingleObject, 12) +SUPHARNT_IMPORT_SYSCALL(NtWriteFile, 36) +SUPHARNT_IMPORT_SYSCALL(NtWriteVirtualMemory, 20) +SUPHARNT_IMPORT_SYSCALL(NtYieldExecution, 0) +SUPHARNT_IMPORT_SYSCALL(NtCreateSection, 28) +SUPHARNT_IMPORT_SYSCALL(NtQueryVolumeInformationFile, 20) + +SUPHARNT_IMPORT_STDCALL_EARLY(LdrInitializeThunk, 12) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(LdrRegisterDllNotification, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(LdrGetDllHandle, 16) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(ApiSetQueryApiSetPresence, 8) + +SUPHARNT_IMPORT_STDCALL(RtlAddAccessAllowedAce, 16) +SUPHARNT_IMPORT_STDCALL(RtlAddAccessDeniedAce, 16) +SUPHARNT_IMPORT_STDCALL(RtlAllocateHeap, 12) +SUPHARNT_IMPORT_STDCALL(RtlCompactHeap, 8) +SUPHARNT_IMPORT_STDCALL(RtlCopySid, 12) +SUPHARNT_IMPORT_STDCALL(RtlCreateAcl, 12) +SUPHARNT_IMPORT_STDCALL(RtlCreateHeap, 24) +SUPHARNT_IMPORT_STDCALL(RtlCreateProcessParameters, 40) +SUPHARNT_IMPORT_STDCALL(RtlCreateSecurityDescriptor, 8) +SUPHARNT_IMPORT_STDCALL(RtlCreateUserProcess, 40) +SUPHARNT_IMPORT_STDCALL(RtlCreateUserThread, 40) +SUPHARNT_IMPORT_STDCALL(RtlDestroyProcessParameters, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlDosApplyFileIsolationRedirection_Ustr, 36) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlEqualSid, 8) +SUPHARNT_IMPORT_STDCALL_EARLY_OPTIONAL(RtlExitUserProcess, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlExitUserThread, 4) +SUPHARNT_IMPORT_STDCALL(RtlExpandEnvironmentStrings_U, 16) +SUPHARNT_IMPORT_STDCALL(RtlFreeHeap, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlFreeUnicodeString, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetLastNtStatus, 0) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetLastWin32Error, 0) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlGetVersion, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlInitializeSid, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlNtStatusToDosError, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlReAllocateHeap, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlRestoreLastWin32Error, 4) +SUPHARNT_IMPORT_STDCALL(RtlSetDaclSecurityDescriptor, 16) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSetLastWin32Error, 4) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSetLastWin32ErrorAndNtStatusFromNtStatus, 4) +SUPHARNT_IMPORT_STDCALL(RtlSizeHeap, 12) +SUPHARNT_IMPORT_STDCALL_EARLY(RtlSubAuthoritySid, 8) + |