From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/HostDrivers/.scm-settings | 73 + src/VBox/HostDrivers/Makefile.kmk | 80 + ...ot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf | Bin 0 -> 422 bytes ...G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf | Bin 0 -> 683 bytes .../HostDrivers/Support/Certificates/Makefile.kup | 0 ...edIDRootCA-589567a6c1944d68f11ff3d86576092b.taf | Bin 0 -> 425 bytes ...ceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf | Bin 0 -> 432 bytes ...cationRoot-729404101f3e0ca347837fca175a8438.taf | Bin 0 -> 708 bytes ...oot-MicrosoftAuthenticodeTmRootAuthority-01.taf | Bin 0 -> 400 bytes ...hority2014-078f0a9d03df119e434e4fec1bf0235a.taf | Bin 0 -> 730 bytes ...hority2005-6eff330eb6e7569740680870104baaba.taf | Bin 0 -> 629 bytes ...ootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf | Bin 0 -> 432 bytes ...eAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf | Bin 0 -> 675 bytes ...hority2010-28cc3a25bfba44ac449a9b586b4339aa.taf | Bin 0 -> 718 bytes ...hority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf | Bin 0 -> 718 bytes ...10-8a334aa8052dd244a647306a76b8178fa215f344.taf | Bin 0 -> 726 bytes .../Timestamp-CopyrightC1997MicrosoftCorp-01.taf | Bin 0 -> 332 bytes .../Certificates/Timestamp-VBoxLegacyWinCA.crt | Bin 0 -> 1593 bytes .../Certificates/Timestamp-VBoxLegacyWinCA.taf | Bin 0 -> 731 bytes .../Certificates/Timestamp-VBoxLegacyWinSha1CA.crt | Bin 0 -> 1603 bytes .../Certificates/Timestamp-VBoxLegacyWinSha1CA.taf | Bin 0 -> 736 bytes ...VirtualBox-05308b76ac2e15b29720fb4395f65f38.cer | Bin 0 -> 1339 bytes ...VirtualBox-05308b76ac2e15b29720fb4395f65f38.taf | Bin 0 -> 456 bytes src/VBox/HostDrivers/Support/Makefile.kmk | 938 +++ src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp | 1201 ++++ src/VBox/HostDrivers/Support/SUPDrv.cpp | 7204 ++++++++++++++++++++ src/VBox/HostDrivers/Support/SUPDrv.d | 51 + src/VBox/HostDrivers/Support/SUPDrvGip.cpp | 5120 ++++++++++++++ src/VBox/HostDrivers/Support/SUPDrvIDC.h | 288 + src/VBox/HostDrivers/Support/SUPDrvIOC.h | 1725 +++++ src/VBox/HostDrivers/Support/SUPDrvInternal.h | 1296 ++++ src/VBox/HostDrivers/Support/SUPDrvSem.cpp | 451 ++ src/VBox/HostDrivers/Support/SUPDrvTracer.cpp | 2494 +++++++ src/VBox/HostDrivers/Support/SUPDrvTracerA.asm | 65 + src/VBox/HostDrivers/Support/SUPLib.cpp | 2438 +++++++ src/VBox/HostDrivers/Support/SUPLibAll.cpp | 429 ++ src/VBox/HostDrivers/Support/SUPLibInternal.h | 524 ++ src/VBox/HostDrivers/Support/SUPLibLdr.cpp | 1153 ++++ src/VBox/HostDrivers/Support/SUPLibSem.cpp | 366 + src/VBox/HostDrivers/Support/SUPLibTracerA.asm | 232 + src/VBox/HostDrivers/Support/SUPR0-asm-files.sed | 66 + src/VBox/HostDrivers/Support/SUPR0-asm.sed | 62 + src/VBox/HostDrivers/Support/SUPR0-def-lx.sed | 91 + src/VBox/HostDrivers/Support/SUPR0-def-pe.sed | 92 + src/VBox/HostDrivers/Support/SUPR0IdcClient.c | 222 + .../HostDrivers/Support/SUPR0IdcClientComponent.c | 101 + .../HostDrivers/Support/SUPR0IdcClientInternal.h | 80 + src/VBox/HostDrivers/Support/SUPR0IdcClientStubs.c | 153 + src/VBox/HostDrivers/Support/SUPR3HardenedIPRT.cpp | 152 + src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp | 2687 ++++++++ .../Support/SUPR3HardenedMainTemplate.cpp | 44 + .../Support/SUPR3HardenedMainTemplateTestcase.cpp | 44 + .../HostDrivers/Support/SUPR3HardenedNoCrt.cpp | 189 + .../HostDrivers/Support/SUPR3HardenedVerify.cpp | 2149 ++++++ src/VBox/HostDrivers/Support/SUPSvc.cpp | 456 ++ src/VBox/HostDrivers/Support/SUPSvcGlobal.cpp | 79 + src/VBox/HostDrivers/Support/SUPSvcGrant.cpp | 1012 +++ src/VBox/HostDrivers/Support/SUPSvcInternal.h | 104 + src/VBox/HostDrivers/Support/bldSUPSignedDummy.cpp | 42 + src/VBox/HostDrivers/Support/darwin/Info.plist | 46 + src/VBox/HostDrivers/Support/darwin/Makefile.kup | 0 .../HostDrivers/Support/darwin/SUPDrv-darwin.cpp | 2410 +++++++ .../HostDrivers/Support/darwin/SUPLib-darwin.cpp | 333 + .../Support/darwin/SUPR0IdcClient-darwin.c | 66 + .../Support/darwin/SUPR3HardenedEntitlements.plist | 37 + .../darwin/SUPR3HardenedEntitlementsVM.plist | 35 + .../Support/darwin/SUPR3HardenedMain-darwin.cpp | 287 + src/VBox/HostDrivers/Support/darwin/load.sh | 164 + .../HostDrivers/Support/darwin/sys/dtrace_glue.h | 49 + src/VBox/HostDrivers/Support/freebsd/Makefile | 207 + src/VBox/HostDrivers/Support/freebsd/Makefile.kup | 0 .../HostDrivers/Support/freebsd/SUPDrv-freebsd.c | 679 ++ .../HostDrivers/Support/freebsd/SUPDrv-freebsd.def | 2 + .../HostDrivers/Support/freebsd/SUPLib-freebsd.cpp | 197 + .../Support/freebsd/SUPR0IdcClient-freebsd.c | 66 + src/VBox/HostDrivers/Support/freebsd/files_vboxdrv | 240 + src/VBox/HostDrivers/Support/linux/LnxPerfHack.cpp | 446 ++ src/VBox/HostDrivers/Support/linux/Makefile | 208 + .../Support/linux/Makefile-vbox_vboxddr0.gmk | 43 + .../Support/linux/Makefile-vbox_vmmr0.gmk | 44 + .../HostDrivers/Support/linux/Makefile-wrapper.gmk | 166 + src/VBox/HostDrivers/Support/linux/Makefile.kup | 0 src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c | 1796 +++++ .../HostDrivers/Support/linux/SUPDrv-linux.mod.c | 97 + .../HostDrivers/Support/linux/SUPLib-linux.cpp | 388 ++ .../Support/linux/SUPR0IdcClient-linux.c | 66 + .../Support/linux/SUPWrapperMod-linux.c | 211 + .../HostDrivers/Support/linux/VBoxR0-wrapped.lds | 49 + .../HostDrivers/Support/linux/combined-agnostic1.c | 99 + .../HostDrivers/Support/linux/combined-agnostic2.c | 116 + .../Support/linux/combined-os-specific.c | 68 + src/VBox/HostDrivers/Support/linux/files_vboxdrv | 242 + src/VBox/HostDrivers/Support/os2/Makefile.kup | 0 src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp | 570 ++ src/VBox/HostDrivers/Support/os2/SUPDrv-os2.def | 55 + src/VBox/HostDrivers/Support/os2/SUPDrvA-os2.asm | 973 +++ src/VBox/HostDrivers/Support/os2/SUPLib-os2.cpp | 204 + .../HostDrivers/Support/os2/SUPR0IdcClient-os2.c | 61 + .../Support/posix/SUPR3HardenedMain-posix.cpp | 708 ++ .../Support/posix/SUPR3HardenedMainA-posix.asm | 170 + src/VBox/HostDrivers/Support/solaris/Makefile.kup | 0 .../HostDrivers/Support/solaris/SUPDrv-solaris.c | 1347 ++++ .../HostDrivers/Support/solaris/SUPLib-solaris.cpp | 245 + .../Support/solaris/SUPR0IdcClient-solaris.c | 66 + src/VBox/HostDrivers/Support/solaris/load.sh | 128 + src/VBox/HostDrivers/Support/solaris/vboxdrv.conf | 43 + src/VBox/HostDrivers/Support/testcase/Makefile.kmk | 158 + .../HostDrivers/Support/testcase/SUPInstall.cpp | 69 + .../HostDrivers/Support/testcase/SUPLoggerCtl.cpp | 196 + .../HostDrivers/Support/testcase/SUPUninstall.cpp | 63 + .../HostDrivers/Support/testcase/tstContiguous.cpp | 118 + src/VBox/HostDrivers/Support/testcase/tstGIP-2.cpp | 356 + .../Support/testcase/tstGetPagingMode.cpp | 104 + src/VBox/HostDrivers/Support/testcase/tstInit.cpp | 61 + src/VBox/HostDrivers/Support/testcase/tstInt.cpp | 240 + src/VBox/HostDrivers/Support/testcase/tstLow.cpp | 164 + .../Support/testcase/tstNtQueryStuff.cpp | 440 ++ src/VBox/HostDrivers/Support/testcase/tstPage.cpp | 101 + src/VBox/HostDrivers/Support/testcase/tstPin.cpp | 223 + .../Support/testcase/tstSupLoadModule.cpp | 128 + .../Support/testcase/tstSupSem-Zombie.cpp | 232 + .../HostDrivers/Support/testcase/tstSupSem.cpp | 649 ++ .../Support/testcase/tstSupTscDelta.cpp | 235 + .../HostDrivers/Support/testcase/tstSupVerify.cpp | 162 + src/VBox/HostDrivers/Support/win/Makefile.kup | 0 src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp | 5719 ++++++++++++++++ src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm | 119 + .../Support/win/SUPHardenedVerify-win.h | 303 + .../Support/win/SUPHardenedVerifyImage-win.cpp | 3090 +++++++++ .../Support/win/SUPHardenedVerifyProcess-win.cpp | 2681 ++++++++ src/VBox/HostDrivers/Support/win/SUPLib-win.cpp | 1002 +++ .../HostDrivers/Support/win/SUPR0IdcClient-win.c | 167 + .../Support/win/SUPR3HardenedMain-win.cpp | 7031 +++++++++++++++++++ .../Support/win/SUPR3HardenedMainA-win.asm | 404 ++ .../Support/win/SUPR3HardenedMainImports-win.cpp | 873 +++ .../Support/win/SUPR3HardenedNoCrt-win.cpp | 501 ++ src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp | 908 +++ src/VBox/HostDrivers/Support/win/VBoxDrv.rc | 71 + src/VBox/HostDrivers/Support/win/VBoxSup.inf | 100 + .../HostDrivers/Support/win/VBoxSupLib-win.cpp | 108 + src/VBox/HostDrivers/Support/win/VBoxSupLib.rc | 70 + .../Support/win/import-template-kernel32.h | 21 + .../Support/win/import-template-ntdll.h | 99 + src/VBox/HostDrivers/VBoxNetAdp/Makefile.kmk | 228 + src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c | 238 + .../HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h | 202 + src/VBox/HostDrivers/VBoxNetAdp/darwin/Info.plist | 26 + .../HostDrivers/VBoxNetAdp/darwin/Makefile.kup | 0 .../VBoxNetAdp/darwin/VBoxNetAdp-darwin.cpp | 529 ++ .../HostDrivers/VBoxNetAdp/darwin/loadnetadp.sh | 133 + src/VBox/HostDrivers/VBoxNetAdp/freebsd/Makefile | 54 + .../VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c | 339 + .../VBoxNetAdp/freebsd/files_vboxnetadp | 94 + src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile | 78 + src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile.kup | 0 .../VBoxNetAdp/linux/VBoxNetAdp-linux.c | 583 ++ .../HostDrivers/VBoxNetAdp/linux/files_vboxnetadp | 112 + .../VBoxNetAdp/solaris/VBoxNetAdp-solaris.c | 581 ++ .../VBoxNetAdp/solaris/hostname.vboxnet0 | 1 + .../HostDrivers/VBoxNetAdp/solaris/vboxnet.conf | 43 + src/VBox/HostDrivers/VBoxNetAdp/win/Makefile.kup | 0 .../HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.cpp | 1888 +++++ .../HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.h | 71 + .../HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.rc | 72 + .../HostDrivers/VBoxNetAdp/win/VBoxNetAdp6.inf | 101 + src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk | 556 ++ src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c | 1591 +++++ src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc | 69 + .../HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h | 499 ++ src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist | 38 + .../HostDrivers/VBoxNetFlt/darwin/Makefile.kup | 0 .../VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp | 1754 +++++ .../HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh | 133 + src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile | 57 + .../VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c | 817 +++ .../VBoxNetFlt/freebsd/files_vboxnetflt | 99 + src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile | 81 + src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup | 0 .../VBoxNetFlt/linux/VBoxNetFlt-linux.c | 2609 +++++++ .../HostDrivers/VBoxNetFlt/linux/files_vboxnetflt | 113 + .../VBoxNetFlt/solaris/VBoxNetFlt-solaris.c | 4042 +++++++++++ .../VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c | 1702 +++++ .../HostDrivers/VBoxNetFlt/solaris/vboxbow.conf | 43 + .../HostDrivers/VBoxNetFlt/solaris/vboxflt.conf | 56 + src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup | 0 .../HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup | 0 .../HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp | 3770 ++++++++++ .../HostDrivers/VBoxNetFlt/win/drv/Makefile.kup | 0 .../HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf | 95 + .../VBoxNetFlt/win/drv/VBoxNetFlt-win.rc | 77 + .../HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf | 117 + .../VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h | 533 ++ .../VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp | 1570 +++++ .../VBoxNetFlt/win/drv/VBoxNetFltM-win.h | 67 + .../HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf | 82 + .../VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp | 1595 +++++ .../VBoxNetFlt/win/drv/VBoxNetFltP-win.h | 52 + .../VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp | 3650 ++++++++++ .../VBoxNetFlt/win/drv/VBoxNetFltRt-win.h | 972 +++ .../VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp | 2736 ++++++++ .../VBoxNetFlt/win/drv/VBoxNetLwf-win.h | 55 + .../HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf | 120 + .../HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup | 0 .../VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp | 747 ++ .../VBoxNetFlt/win/nobj/VBoxNetFltNobj.def | 42 + .../VBoxNetFlt/win/nobj/VBoxNetFltNobj.h | 96 + .../VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc | 78 + .../VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs | 13 + .../VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h | 46 + .../VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl | 55 + .../HostDrivers/VBoxNetFlt/win/tools/Makefile.kup | 0 .../VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp | 341 + .../VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp | 107 + .../VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp | 201 + .../VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp | 133 + .../VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp | 186 + .../VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp | 130 + src/VBox/HostDrivers/VBoxPci/Makefile.kmk | 93 + src/VBox/HostDrivers/VBoxPci/VBoxPci.c | 800 +++ src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h | 213 + src/VBox/HostDrivers/VBoxPci/linux/Makefile | 81 + src/VBox/HostDrivers/VBoxPci/linux/Makefile.kup | 0 src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c | 1186 ++++ src/VBox/HostDrivers/VBoxPci/linux/files_vboxpci | 111 + src/VBox/HostDrivers/VBoxUSB/Makefile.kmk | 85 + src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp | 1860 +++++ src/VBox/HostDrivers/VBoxUSB/USBLib.cpp | 71 + src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp | 571 ++ src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h | 68 + src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup | 0 src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c | 1278 ++++ src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h | 242 + src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk | 67 + .../HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp | 279 + .../HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c | 4054 +++++++++++ .../VBoxUSB/solaris/VBoxUSBMon-solaris.c | 1064 +++ .../VBoxUSB/solaris/include/usbai_private.h | 161 + src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf | 37 + .../HostDrivers/VBoxUSB/solaris/vboxusbmon.conf | 37 + src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup | 0 .../HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp | 391 ++ .../HostDrivers/VBoxUSB/win/Install/Makefile.kup | 0 .../HostDrivers/VBoxUSB/win/Install/USBInstall.cpp | 256 + .../VBoxUSB/win/Install/USBUninstall.cpp | 215 + src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk | 204 + src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup | 0 .../HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp | 247 + src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h | 113 + src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h | 95 + .../HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp | 428 ++ src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h | 89 + src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup | 0 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf | 88 + src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h | 103 + .../HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp | 357 + src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h | 218 + src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc | 70 + .../HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp | 274 + src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h | 46 + .../HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp | 427 ++ src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h | 51 + src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp | 1593 +++++ src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h | 97 + src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup | 0 .../HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp | 2221 ++++++ src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup | 0 .../HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf | 98 + .../HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp | 1763 +++++ src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h | 75 + .../HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp | 218 + src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h | 96 + .../HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp | 1568 +++++ src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h | 77 + src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc | 70 + .../HostDrivers/VBoxUSB/win/testcase/Makefile.kup | 0 .../HostDrivers/VBoxUSB/win/testcase/USBTest.cpp | 384 ++ src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup | 0 src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def | 74 + src/VBox/HostDrivers/adpctl/Makefile.kmk | 44 + src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp | 1178 ++++ src/VBox/HostDrivers/darwin/Makefile.kmk | 49 + src/VBox/HostDrivers/darwin/VBoxNetSend.h | 132 + src/VBox/HostDrivers/darwin/loadall.sh | 48 + src/VBox/HostDrivers/freebsd/Makefile | 94 + src/VBox/HostDrivers/freebsd/Makefile.kmk | 45 + src/VBox/HostDrivers/linux/Makefile | 248 + src/VBox/HostDrivers/linux/Makefile.kmk | 50 + src/VBox/HostDrivers/linux/build_in_tmp | 104 + src/VBox/HostDrivers/linux/export_modules.sh | 258 + src/VBox/HostDrivers/linux/load.sh | 65 + src/VBox/HostDrivers/linux/loadall.sh | 85 + src/VBox/HostDrivers/win/Makefile.kmk | 66 + src/VBox/HostDrivers/win/VBoxDbgLog.h | 150 + src/VBox/HostDrivers/win/cfg/Makefile.kup | 0 src/VBox/HostDrivers/win/cfg/VBoxDrvCfg.cpp | 1006 +++ src/VBox/HostDrivers/win/load.cmd | 117 + src/VBox/HostDrivers/win/loadall.cmd | 142 + 297 files changed, 139889 insertions(+) create mode 100644 src/VBox/HostDrivers/.scm-settings create mode 100644 src/VBox/HostDrivers/Makefile.kmk create mode 100644 src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt create mode 100644 src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt create mode 100644 src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf create mode 100644 src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer create mode 100644 src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf create mode 100644 src/VBox/HostDrivers/Support/Makefile.kmk create mode 100644 src/VBox/HostDrivers/Support/SUPDrv-dtrace.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPDrv.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPDrv.d create mode 100644 src/VBox/HostDrivers/Support/SUPDrvGip.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPDrvIDC.h create mode 100644 src/VBox/HostDrivers/Support/SUPDrvIOC.h create mode 100644 src/VBox/HostDrivers/Support/SUPDrvInternal.h create mode 100644 src/VBox/HostDrivers/Support/SUPDrvSem.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPDrvTracer.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPDrvTracerA.asm create mode 100644 src/VBox/HostDrivers/Support/SUPLib.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPLibAll.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPLibInternal.h create mode 100644 src/VBox/HostDrivers/Support/SUPLibLdr.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPLibSem.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPLibTracerA.asm create mode 100644 src/VBox/HostDrivers/Support/SUPR0-asm-files.sed create mode 100644 src/VBox/HostDrivers/Support/SUPR0-asm.sed create mode 100644 src/VBox/HostDrivers/Support/SUPR0-def-lx.sed create mode 100644 src/VBox/HostDrivers/Support/SUPR0-def-pe.sed create mode 100644 src/VBox/HostDrivers/Support/SUPR0IdcClient.c create mode 100644 src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c create mode 100644 src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h create mode 100644 src/VBox/HostDrivers/Support/SUPR0IdcClientStubs.c create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedIPRT.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedMain.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplate.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedMainTemplateTestcase.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedNoCrt.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPR3HardenedVerify.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPSvc.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPSvcGlobal.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPSvcGrant.cpp create mode 100644 src/VBox/HostDrivers/Support/SUPSvcInternal.h create mode 100644 src/VBox/HostDrivers/Support/bldSUPSignedDummy.cpp create mode 100644 src/VBox/HostDrivers/Support/darwin/Info.plist create mode 100644 src/VBox/HostDrivers/Support/darwin/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPDrv-darwin.cpp create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPLib-darwin.cpp create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPR0IdcClient-darwin.c create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlements.plist create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPR3HardenedEntitlementsVM.plist create mode 100644 src/VBox/HostDrivers/Support/darwin/SUPR3HardenedMain-darwin.cpp create mode 100755 src/VBox/HostDrivers/Support/darwin/load.sh create mode 100644 src/VBox/HostDrivers/Support/darwin/sys/dtrace_glue.h create mode 100644 src/VBox/HostDrivers/Support/freebsd/Makefile create mode 100644 src/VBox/HostDrivers/Support/freebsd/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.c create mode 100644 src/VBox/HostDrivers/Support/freebsd/SUPDrv-freebsd.def create mode 100644 src/VBox/HostDrivers/Support/freebsd/SUPLib-freebsd.cpp create mode 100644 src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c create mode 100755 src/VBox/HostDrivers/Support/freebsd/files_vboxdrv create mode 100644 src/VBox/HostDrivers/Support/linux/LnxPerfHack.cpp create mode 100644 src/VBox/HostDrivers/Support/linux/Makefile create mode 100644 src/VBox/HostDrivers/Support/linux/Makefile-vbox_vboxddr0.gmk create mode 100644 src/VBox/HostDrivers/Support/linux/Makefile-vbox_vmmr0.gmk create mode 100644 src/VBox/HostDrivers/Support/linux/Makefile-wrapper.gmk create mode 100644 src/VBox/HostDrivers/Support/linux/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/linux/SUPDrv-linux.c create mode 100644 src/VBox/HostDrivers/Support/linux/SUPDrv-linux.mod.c create mode 100644 src/VBox/HostDrivers/Support/linux/SUPLib-linux.cpp create mode 100644 src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c create mode 100644 src/VBox/HostDrivers/Support/linux/SUPWrapperMod-linux.c create mode 100644 src/VBox/HostDrivers/Support/linux/VBoxR0-wrapped.lds create mode 100644 src/VBox/HostDrivers/Support/linux/combined-agnostic1.c create mode 100644 src/VBox/HostDrivers/Support/linux/combined-agnostic2.c create mode 100644 src/VBox/HostDrivers/Support/linux/combined-os-specific.c create mode 100755 src/VBox/HostDrivers/Support/linux/files_vboxdrv create mode 100644 src/VBox/HostDrivers/Support/os2/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/os2/SUPDrv-os2.cpp create mode 100644 src/VBox/HostDrivers/Support/os2/SUPDrv-os2.def create mode 100644 src/VBox/HostDrivers/Support/os2/SUPDrvA-os2.asm create mode 100644 src/VBox/HostDrivers/Support/os2/SUPLib-os2.cpp create mode 100644 src/VBox/HostDrivers/Support/os2/SUPR0IdcClient-os2.c create mode 100644 src/VBox/HostDrivers/Support/posix/SUPR3HardenedMain-posix.cpp create mode 100644 src/VBox/HostDrivers/Support/posix/SUPR3HardenedMainA-posix.asm create mode 100644 src/VBox/HostDrivers/Support/solaris/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/solaris/SUPDrv-solaris.c create mode 100644 src/VBox/HostDrivers/Support/solaris/SUPLib-solaris.cpp create mode 100644 src/VBox/HostDrivers/Support/solaris/SUPR0IdcClient-solaris.c create mode 100755 src/VBox/HostDrivers/Support/solaris/load.sh create mode 100644 src/VBox/HostDrivers/Support/solaris/vboxdrv.conf create mode 100644 src/VBox/HostDrivers/Support/testcase/Makefile.kmk create mode 100644 src/VBox/HostDrivers/Support/testcase/SUPInstall.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/SUPLoggerCtl.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/SUPUninstall.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstContiguous.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstGIP-2.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstGetPagingMode.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstInit.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstInt.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstLow.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstNtQueryStuff.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstPage.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstPin.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstSupLoadModule.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstSupSem-Zombie.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstSupSem.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstSupTscDelta.cpp create mode 100644 src/VBox/HostDrivers/Support/testcase/tstSupVerify.cpp create mode 100644 src/VBox/HostDrivers/Support/win/Makefile.kup create mode 100644 src/VBox/HostDrivers/Support/win/SUPDrv-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm create mode 100644 src/VBox/HostDrivers/Support/win/SUPHardenedVerify-win.h create mode 100644 src/VBox/HostDrivers/Support/win/SUPHardenedVerifyImage-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPHardenedVerifyProcess-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPLib-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPR0IdcClient-win.c create mode 100644 src/VBox/HostDrivers/Support/win/SUPR3HardenedMain-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm create mode 100644 src/VBox/HostDrivers/Support/win/SUPR3HardenedMainImports-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPR3HardenedNoCrt-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/SUPSvc-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/VBoxDrv.rc create mode 100644 src/VBox/HostDrivers/Support/win/VBoxSup.inf create mode 100644 src/VBox/HostDrivers/Support/win/VBoxSupLib-win.cpp create mode 100644 src/VBox/HostDrivers/Support/win/VBoxSupLib.rc create mode 100644 src/VBox/HostDrivers/Support/win/import-template-kernel32.h create mode 100644 src/VBox/HostDrivers/Support/win/import-template-ntdll.h create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/darwin/Info.plist create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/darwin/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/darwin/VBoxNetAdp-darwin.cpp create mode 100755 src/VBox/HostDrivers/VBoxNetAdp/darwin/loadnetadp.sh create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/freebsd/Makefile create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c create mode 100755 src/VBox/HostDrivers/VBoxNetAdp/freebsd/files_vboxnetadp create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/linux/VBoxNetAdp-linux.c create mode 100755 src/VBox/HostDrivers/VBoxNetAdp/linux/files_vboxnetadp create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/solaris/VBoxNetAdp-solaris.c create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/solaris/hostname.vboxnet0 create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/solaris/vboxnet.conf create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/win/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.rc create mode 100644 src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp6.inf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp create mode 100755 src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c create mode 100755 src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c create mode 100755 src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxPci/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxPci/VBoxPci.c create mode 100644 src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h create mode 100644 src/VBox/HostDrivers/VBoxPci/linux/Makefile create mode 100644 src/VBox/HostDrivers/VBoxPci/linux/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c create mode 100755 src/VBox/HostDrivers/VBoxPci/linux/files_vboxpci create mode 100644 src/VBox/HostDrivers/VBoxUSB/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/USBLib.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c create mode 100644 src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf create mode 100644 src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf create mode 100644 src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/testcase/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup create mode 100644 src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def create mode 100644 src/VBox/HostDrivers/adpctl/Makefile.kmk create mode 100644 src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp create mode 100644 src/VBox/HostDrivers/darwin/Makefile.kmk create mode 100644 src/VBox/HostDrivers/darwin/VBoxNetSend.h create mode 100755 src/VBox/HostDrivers/darwin/loadall.sh create mode 100644 src/VBox/HostDrivers/freebsd/Makefile create mode 100644 src/VBox/HostDrivers/freebsd/Makefile.kmk create mode 100644 src/VBox/HostDrivers/linux/Makefile create mode 100644 src/VBox/HostDrivers/linux/Makefile.kmk create mode 100755 src/VBox/HostDrivers/linux/build_in_tmp create mode 100755 src/VBox/HostDrivers/linux/export_modules.sh create mode 100755 src/VBox/HostDrivers/linux/load.sh create mode 100755 src/VBox/HostDrivers/linux/loadall.sh create mode 100644 src/VBox/HostDrivers/win/Makefile.kmk create mode 100644 src/VBox/HostDrivers/win/VBoxDbgLog.h create mode 100644 src/VBox/HostDrivers/win/cfg/Makefile.kup create mode 100644 src/VBox/HostDrivers/win/cfg/VBoxDrvCfg.cpp create mode 100644 src/VBox/HostDrivers/win/load.cmd create mode 100644 src/VBox/HostDrivers/win/loadall.cmd (limited to 'src/VBox/HostDrivers') diff --git a/src/VBox/HostDrivers/.scm-settings b/src/VBox/HostDrivers/.scm-settings new file mode 100644 index 00000000..34c50941 --- /dev/null +++ b/src/VBox/HostDrivers/.scm-settings @@ -0,0 +1,73 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the host installers. +# + +# +# 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 . +# +# 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 +# + +# Driver code is dual licensed. +--license-ose-dual + +/adpctl/*: --license-ose-gpl + +# External stuff. +/Support/freebsd/SUPDrv-freebsd.c: --external-copyright +/Support/freebsd/SUPDrv-freebsd.def: --no-update-copyright-year --no-update-license +/Support/os2/SUPDrv-os2.cpp: --external-copyright +/Support/os2/SUPDrvA-os2.asm: --external-copyright +/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c: --external-copyright +/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c: --external-copyright +--filter-out-files /VBoxUSB/solaris/include/usbai_private.h + +# No license header (public domain). +/VBoxUSB/os2/usbcalls.c: --external-copyright +/VBoxUSB/os2/usbcalls.h: --external-copyright --no-convert-tabs + +# Trivial dual C and assembly include files which cannot have comments. +/Support/win/import-template-kernel32.h: --no-update-copyright-year --no-update-license --no-fix-header-guards +/Support/win/import-template-ntdll.h: --no-update-copyright-year --no-update-license --no-fix-header-guards + +# Ignore binary files +--filter-out-files /Support/Certificates/*.taf +--filter-out-files /Support/Certificates/*.cer + +# Misc +/linux/build_in_tmp: --treat-as .sh +/*/solaris/*.conf: --treat-as .sh +--filter-out-files /VBoxNetAdp/solaris/hostname.vboxnet0 +--filter-out-files /VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs +/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl: --treat-as .h --no-fix-header-guards +/Support/solaris/mod.sh: --no-convert-tabs +/Support/linux/SUPDrv-linux.mod.c: --no-convert-tabs + +/*.h: --guard-relative-to-dir . + diff --git a/src/VBox/HostDrivers/Makefile.kmk b/src/VBox/HostDrivers/Makefile.kmk new file mode 100644 index 00000000..df62eb0d --- /dev/null +++ b/src/VBox/HostDrivers/Makefile.kmk @@ -0,0 +1,80 @@ +# $Id: Makefile.kmk $ +## @file +# Top-level makefile for the VBox Host drivers. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +include $(PATH_SUB_CURRENT)/Support/Makefile.kmk + +if !defined(VBOX_ONLY_DOCS) \ + && !defined(VBOX_ONLY_EXTPACKS) \ + && !defined(VBOX_ONLY_VALIDATIONKIT) \ + && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" + if1of ($(KBUILD_TARGET), win) + include $(PATH_SUB_CURRENT)/win/Makefile.kmk + endif + ifdef VBOX_WITH_USB + include $(PATH_SUB_CURRENT)/VBoxUSB/Makefile.kmk + endif + if1of ($(KBUILD_TARGET), darwin solaris win linux freebsd) + ifdef VBOX_WITH_NETFLT + include $(PATH_SUB_CURRENT)/VBoxNetFlt/Makefile.kmk + endif + endif + if1of ($(KBUILD_TARGET), darwin solaris win linux freebsd) + ifdef VBOX_WITH_NETADP + include $(PATH_SUB_CURRENT)/VBoxNetAdp/Makefile.kmk + endif + endif + if1of ($(KBUILD_TARGET), darwin freebsd linux solaris) + include $(PATH_SUB_CURRENT)/adpctl/Makefile.kmk + endif + if1of ($(KBUILD_TARGET), darwin freebsd linux) + include $(PATH_SUB_CURRENT)/$(KBUILD_TARGET)/Makefile.kmk + endif + + if1of ($(KBUILD_TARGET), linux) + ifdef VBOX_WITH_PCI_PASSTHROUGH + include $(PATH_SUB_CURRENT)/VBoxPci/Makefile.kmk + endif + endif + +endif # !defined(VBOX_ONLY_DOCS) && !defined(VBOX_ONLY_VALIDATIONKIT) + +# Let kBuild generate the rules. +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf new file mode 100644 index 00000000..82a8d623 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-2bd06947947609fef46b8d2e40a6f7474d7f085e.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf new file mode 100644 index 00000000..71aaf14d Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/AppleRoot-G2-c499136c1803c27bc0a3a00d7f72807a1c77268d.taf differ 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 diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf new file mode 100644 index 00000000..d8754c9a Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertAssuredIDRootCA-589567a6c1944d68f11ff3d86576092b.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf new file mode 100644 index 00000000..c1d18a26 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/NtRoot-CrossSign-DigiCertHighAssuranceEVRootCA-f4a38dbe86386c554d25f1ce2557a4fe.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf b/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf new file mode 100644 index 00000000..486b6fd7 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/NtRoot-MicrosoftCodeVerificationRoot-729404101f3e0ca347837fca175a8438.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf new file mode 100644 index 00000000..7b46d69b Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftAuthenticodeTmRootAuthority-01.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf new file mode 100644 index 00000000..f2fa96a3 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDevelopmentRootCertificateAuthority2014-078f0a9d03df119e434e4fec1bf0235a.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf new file mode 100644 index 00000000..e55039ab Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftDigitalMediaAuthority2005-6eff330eb6e7569740680870104baaba.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf new file mode 100644 index 00000000..46c6250a Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootAuthority-00c1008b3c3c8811d13ef663ecdf40.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf new file mode 100644 index 00000000..3131fc27 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority-79ad16a14aa0a5ad4c7358f407132e65.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf new file mode 100644 index 00000000..945de02c Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2010-28cc3a25bfba44ac449a9b586b4339aa.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf new file mode 100644 index 00000000..610b14f1 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftRootCertificateAuthority2011-3f8bc8b5fc9fb29643b569d66c42e144.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf new file mode 100644 index 00000000..0c1a1af7 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/SpcRoot-MicrosoftTestingRootCertificateAuthority2010-8a334aa8052dd244a647306a76b8178fa215f344.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf new file mode 100644 index 00000000..b9d8f7b8 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Timestamp-CopyrightC1997MicrosoftCorp-01.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt new file mode 100644 index 00000000..bb6c0bcf Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.crt differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf new file mode 100644 index 00000000..f7d4e6ba Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinCA.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt new file mode 100644 index 00000000..57d117d1 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.crt differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf new file mode 100644 index 00000000..a3216fb8 Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Timestamp-VBoxLegacyWinSha1CA.taf differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer new file mode 100644 index 00000000..e47fb25a Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.cer differ diff --git a/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf new file mode 100644 index 00000000..a592810b Binary files /dev/null and b/src/VBox/HostDrivers/Support/Certificates/Trusted-OracleCorporationVirtualBox-05308b76ac2e15b29720fb4395f65f38.taf differ 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 . +# +# 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 ' \ + '' + $(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 . + * + * 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 +#include +#include + +#include +#include +#include +#include +#ifdef RT_OS_DARWIN +# include +#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 +# include /* Missing from provider.h. */ +# include /* Missing from provider.h. */ +# endif +# include +/** Status code fixer (UEK uses linux convension unlike the others). */ +# define FIX_UEK_RC(a_rc) (-(a_rc)) +#else +# include +# 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 +# include +# 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 . + * + * 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 +#endif +#include +#include +#include +#include +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS) +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include +# include +#endif +#include +#include + +#include +#include +#include +#include + +#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 +# pragma warning(pop) + +# elif defined(RT_OS_DARWIN) +# define _LIBCPP_CSTDDEF +# include <__nullptr> +# include + +# else +# include +# 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 +constexpr std::integral_constant +CountFunctionArguments(RetType(RTCALL *)(Types ...)) +{ + return std::integral_constant{}; +} +# 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 . + * + * 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 . + * + * 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 +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD) +# include +# include +#endif +#include +#include + +#include +#include +#include + +#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 . + * + * 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 + +/** @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 . + * + * 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 +#include + +/* + * 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 +# 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 +# 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 +# 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 +# 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 . + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(SUPDRV_AGNOSTIC) && !defined(RT_OS_LINUX) +/* do nothing */ + +#elif defined(RT_OS_WINDOWS) +# include +# include + +#elif defined(RT_OS_LINUX) +# include +# if RTLNX_VER_MIN(2,6,33) +# include +# else +# ifndef AUTOCONF_INCLUDED +# include +# endif +# endif +# if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS) +# define MODVERSIONS +# if RTLNX_VER_MAX(2,5,71) +# include +# 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 +# include +# include +# if RTLNX_VER_MIN(2,6,27) +# include +# else /* older kernels */ +# include +# endif /* older kernels */ +# include +# endif +# if RTLNX_VER_MIN(3,2,0) +# include +# else +# include +# 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 +# include + +#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 +# undef memset +# undef memcmp +# undef strchr +# undef strrchr +# undef ffs +# undef ffsl +# undef fls +# undef flsl +# include + +#elif defined(RT_OS_SOLARIS) +# include +# include + +#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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +/** + * 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 . + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . +; +; 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 . + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#ifdef IN_RC +# include +# include +#endif +#ifdef IN_RING0 +# include +#endif +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#endif +#include +#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 . + * + * 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 +#include +#include + + +/******************************************************************************* +* 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 . + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 + +#include +#include +#include +#include +#include + +#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 . +; +; 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 . +# +# 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 . +# +# 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 . +# +# 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 . +# +# 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 . + * + * 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 +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + + +/** + * 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 . + * + * 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 +#include + +#ifdef RT_OS_WINDOWS +# include +#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 +#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 . + * + * 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 +#include + + +/** + * 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +# include +# include +# include +# include + +#elif RT_OS_WINDOWS +# include + +#else /* UNIXes */ +# ifdef RT_OS_DARWIN +# define _POSIX_C_SOURCE 1 /* pick the correct prototype for unsetenv. */ +# endif +# include /* stdint fun on darwin. */ + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# 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 /* sys/capabilities from uek-headers require this */ +# include +# include +# ifndef CAP_TO_MASK +# define CAP_TO_MASK(cap) RT_BIT(cap) +# endif +# elif defined(RT_OS_FREEBSD) +# include +# include +# elif defined(RT_OS_SOLARIS) +# include +# endif +# include +# ifdef RT_OS_DARWIN +# include +# endif + +#endif + +#include +#include +#ifdef RT_OS_WINDOWS +# include +# include +#endif +#include +#include +#include +#include +#include + +#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(""); + } + else + { + const char *pszStr = va_arg(va, const char *); + if (!RT_VALID_PTR(pszStr)) + pszStr = ""; + 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 . + * + * 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 + + +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 . + * + * 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 + + +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 . + * + * 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 +#endif + +#include + +#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 . + * + * 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 +# include +# include +# include +# include +# include +# include + +#elif defined(RT_OS_WINDOWS) +# include +# ifndef IN_SUP_HARDENED_R3 +# include +# endif + +#else /* UNIXes */ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# ifdef RT_OS_DARWIN +# include +# endif + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#include +#include +#include +#include + +#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] : ""); + 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] : ""); + 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 . + * + * 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 +#include + + +/** @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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 +#include +#include +#include + +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 . + * + * 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 @@ + + + + + CFBundleDevelopmentRegion English + CFBundleExecutable VBoxDrv + CFBundleIdentifier org.virtualbox.kext.VBoxDrv + CFBundleInfoDictionaryVersion 6.0 + CFBundleName VBoxDrv + CFBundlePackageType KEXT + CFBundleSignature ???? + CFBundleGetInfoString @VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@ + CFBundleVersion @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + CFBundleShortVersionString @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + OSBundleCompatibleVersion @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + IOKitPersonalities + + VBoxDrv + + CFBundleIdentifier org.virtualbox.kext.VBoxDrv + IOClass org_virtualbox_SupDrv + IOMatchCategory org_virtualbox_SupDrv + IOProviderClass IOResources + IOResourceMatch IOKit + IOUserClientClass org_virtualbox_SupDrvClient + + + OSBundleLibraries + + com.apple.kpi.bsd 9.0.0 + com.apple.kpi.mach 9.0.0 + com.apple.kpi.libkern 9.0.0 + com.apple.kpi.unsupported 9.0.0 + com.apple.kpi.iokit 9.0.0 + + OSBundleLibraries_x86_64 + + com.apple.kpi.bsd 10.0.0d4 + com.apple.kpi.mach 10.0.0d3 + com.apple.kpi.libkern 10.0.0d3 + com.apple.kpi.iokit 10.0.0d3 + com.apple.kpi.unsupported 10.0.0d3 + + + + 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 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101100 +# include +#endif +#include +#include + +#ifdef VBOX_WITH_HOST_VMX +# include +RT_C_DECLS_BEGIN +# include +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 . + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + + +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 @@ + + + + + + + + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + 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 @@ + + + + + + + com.apple.security.cs.allow-unsigned-executable-memory + com.apple.security.cs.disable-executable-page-protection + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.device.usb + + com.apple.vm.networking + + com.apple.vm.device-access + + + + + 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include + +#include +#include +#include +#include /* sysctlbyname() */ +#include +#include +#include /* issetugid() */ +#include + +#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 . +# +# 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 . + * + * 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 . +# +# 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 + 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 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 + * + * 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 +#undef PVM +#include +#include +#include +#include +#include +#include +#include +#include +#include /* for pmap_map() */ + +#include "../SUPDrvInternal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + + +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 . +# +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 -o -m vmmr0.r0 [-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 . +# +# 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 . +# +# 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 . +# +# 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 . +# +# 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 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @todo figure out the exact version number */ +#if RTLNX_VER_MIN(2,6,16) +# include +# define VBOX_WITH_SUSPEND_NOTIFICATION +#endif + +#include +#include +#ifdef VBOX_WITH_SUSPEND_NOTIFICATION +# include +#endif +#if (RTLNX_VER_MIN(2,6,28)) && defined(SUPDRV_WITH_MSR_PROBER) +# define SUPDRV_LINUX_HAS_SAFE_MSR_API +# include +#endif + +#include + +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + +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 . + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 . + * + * 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 + + +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 . + * + * 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 + + +/********************************************************************************************************************************* +* 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 . + * + * 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 . + * + * 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 + +#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 . + * + * 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 + +#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 . + * + * 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 . +# +# 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 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 + * + * 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 +#undef RT_MAX + +#include "SUPDrvInternal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . +; +; 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 +; +; 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 . + * + * 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 +#undef RT_MAX + +#ifdef IN_SUP_HARDENED_R3 +# undef DEBUG /* Warning: disables RT_STRICT */ +# define LOG_DISABLED +# define RTLOG_REL_DISABLED +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include + +#include +#include +#if defined(RT_OS_SOLARIS) +# include +#endif +#include +#include + +#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 . +; +; 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 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#undef u /* /usr/include/sys/user.h:249:1 is where this is defined to (curproc->p_user). very cool. */ + +#include "../SUPDrvInternal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../SUPLibInternal.h" +#include "../SUPDrvIOC.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * 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 + + +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 . +# +# 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 . +# +# 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 . +# +# 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Prints the usage. + * @returns 1. + */ +static int usage(void) +{ + RTPrintf("usage: SUPLoggerCtl [-f|--flags ] \\\n" + " [-g|--groups ] \\\n" + " [-d|--dest ] \\\n" + " [-l|--which ] \\\n" + " [-o|--what ]\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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include +#else +# define ASMReadTSC RTTimeSystemNanoTS +#endif +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + + + +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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include +#include +#include + + +//#define DYNAMIC +#ifdef DYNAMIC +# include + +# 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 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifndef IPRT_NT_MAP_TO_ZW +# define IPRT_NT_MAP_TO_ZW +#endif +#define LOG_GROUP LOG_GROUP_SUP_DRV +#include "../SUPDrvInternal.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef VBOX_WITH_HARDENING +# include "SUPHardenedVerify-win.h" +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxDrv" +/** The Pool tag (VBox). */ +#define SUPDRV_NT_POOL_TAG 'xoBV' + +/** NT device name for user access. */ +#define DEVICE_NAME_NT_USR L"\\Device\\VBoxDrvU" +#ifdef VBOX_WITH_HARDENING +/** Macro for checking for deflecting calls to the stub device. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +/** Macro for checking for deflecting calls to the stub and error info + * devices. */ +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) \ + do { if ((a_pDevObj) == g_pDevObjStub || (a_pDevObj) == g_pDevObjErrorInfo) \ + return supdrvNtCompleteRequest(STATUS_ACCESS_DENIED, a_pIrp); \ + } while (0) +#else +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_DEV(a_pDevObj, a_pIrp) do {} while (0) +# define VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(a_pDevObj, a_pIrp) do {} while (0) +#endif + +/** Enables the fast I/O control code path. */ +#define VBOXDRV_WITH_FAST_IO + +/** Enables generating UID from NT SIDs so the GMM can share free memory + * among VMs running as the same user. */ +#define VBOXDRV_WITH_SID_TO_UID_MAPPING + +/* Missing if we're compiling against older WDKs. */ +#ifndef NonPagedPoolNx +# define NonPagedPoolNx ((POOL_TYPE)512) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** + * SID to User ID mapping. + * + * This is used to generate a RTUID value for a NT security identifier. + * Normally, the UID is the hash of the SID string, but due to collisions it may + * differ. See g_NtUserIdHashTree and g_NtUserIdUidTree. + */ +typedef struct SUPDRVNTUSERID +{ + /** Hash tree node, key: RTStrHash1 of szSid. */ + AVLLU32NODECORE HashCore; + /** UID three node, key: The UID. */ + AVLU32NODECORE UidCore; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The length of the SID string. */ + uint16_t cchSid; + /** The SID string for the user. */ + char szSid[RT_FLEXIBLE_ARRAY]; +} SUPDRVNTUSERID; +/** Pointer to a SID to UID mapping. */ +typedef SUPDRVNTUSERID *PSUPDRVNTUSERID; +#endif + +/** + * Device extension used by VBoxDrvU. + */ +typedef struct SUPDRVDEVEXTUSR +{ + /** Global cookie (same location as in SUPDRVDEVEXT, different value). */ + uint32_t u32Cookie; + /** Pointer to the main driver extension. */ + PSUPDRVDEVEXT pMainDrvExt; +} SUPDRVDEVEXTUSR; +AssertCompileMembersAtSameOffset(SUPDRVDEVEXT, u32Cookie, SUPDRVDEVEXTUSR, u32Cookie); +/** Pointer to the VBoxDrvU device extension. */ +typedef SUPDRVDEVEXTUSR *PSUPDRVDEVEXTUSR; +/** Value of SUPDRVDEVEXTUSR::u32Cookie. */ +#define SUPDRVDEVEXTUSR_COOKIE UINT32_C(0x12345678) + +/** Get the main device extension. */ +#define SUPDRVNT_GET_DEVEXT(pDevObj) \ + ( pDevObj != g_pDevObjUsr \ + ? (PSUPDRVDEVEXT)pDevObj->DeviceExtension \ + : ((PSUPDRVDEVEXTUSR)pDevObj->DeviceExtension)->pMainDrvExt ) + +#ifdef VBOX_WITH_HARDENING + +/** + * Device extension used by VBoxDrvStub. + */ +typedef struct SUPDRVDEVEXTSTUB +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTSTUB; +/** Pointer to the VBoxDrvStub device extension. */ +typedef SUPDRVDEVEXTSTUB *PSUPDRVDEVEXTSTUB; +/** Value of SUPDRVDEVEXTSTUB::Common.u32Cookie. */ +#define SUPDRVDEVEXTSTUB_COOKIE UINT32_C(0x90abcdef) + + +/** + * Device extension used by VBoxDrvErrorInfo. + */ +typedef struct SUPDRVDEVEXTERRORINFO +{ + /** Common header. */ + SUPDRVDEVEXTUSR Common; +} SUPDRVDEVEXTERRORINFO; +/** Pointer to the VBoxDrvErrorInfo device extension. */ +typedef SUPDRVDEVEXTERRORINFO *PSUPDRVDEVEXTERRORINFO; +/** Value of SUPDRVDEVEXTERRORINFO::Common.u32Cookie. */ +#define SUPDRVDEVEXTERRORINFO_COOKIE UINT32_C(0xBadC0ca0) + +/** + * Error info for a failed VBoxDrv or VBoxDrvStub open attempt. + */ +typedef struct SUPDRVNTERRORINFO +{ + /** The list entry (in g_ErrorInfoHead). */ + RTLISTNODE ListEntry; + /** The ID of the process this error info belongs to. */ + HANDLE hProcessId; + /** The ID of the thread owning this info. */ + HANDLE hThreadId; + /** Milliseconds createion timestamp (for cleaning up). */ + uint64_t uCreatedMsTs; + /** Number of bytes of valid info. */ + uint32_t cchErrorInfo; + /** The error info. */ + char szErrorInfo[16384 - sizeof(RTLISTNODE) - sizeof(HANDLE)*2 - sizeof(uint64_t) - sizeof(uint32_t) - 0x20]; +} SUPDRVNTERRORINFO; +/** Pointer to error info. */ +typedef SUPDRVNTERRORINFO *PSUPDRVNTERRORINFO; + + +/** + * The kind of process we're protecting. + */ +typedef enum SUPDRVNTPROTECTKIND +{ + kSupDrvNtProtectKind_Invalid = 0, + + /** Stub process protection while performing process verification. + * Next: StubSpawning (or free) */ + kSupDrvNtProtectKind_StubUnverified, + /** Stub process protection before it creates the VM process. + * Next: StubParent, StubDead. */ + kSupDrvNtProtectKind_StubSpawning, + /** Stub process protection while having a VM process as child. + * Next: StubDead */ + kSupDrvNtProtectKind_StubParent, + /** Dead stub process. */ + kSupDrvNtProtectKind_StubDead, + + /** Potential VM process. + * Next: VmProcessConfirmed, VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessUnconfirmed, + /** Confirmed VM process. + * Next: VmProcessDead. */ + kSupDrvNtProtectKind_VmProcessConfirmed, + /** Dead VM process. */ + kSupDrvNtProtectKind_VmProcessDead, + + /** End of valid protection kinds. */ + kSupDrvNtProtectKind_End +} SUPDRVNTPROTECTKIND; + +/** + * A NT process protection structure. + */ +typedef struct SUPDRVNTPROTECT +{ + /** The AVL node core structure. The process ID is the pid. */ + AVLPVNODECORE AvlCore; + /** Magic value (SUPDRVNTPROTECT_MAGIC). */ + uint32_t volatile u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** The kind of process we're protecting. */ + SUPDRVNTPROTECTKIND volatile enmProcessKind; + /** Whether this structure is in the tree. */ + bool fInTree : 1; + /** 7,: Hack to allow the supid themes service duplicate handle privileges to + * our process. */ + bool fThemesFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fFirstThreadCreateHandle : 1; + /** 8.1: Hack to allow more rights to the handle returned by + * NtCreateUserProcess. Only applicable to VmProcessUnconfirmed. */ + bool fCsrssFirstProcessCreateHandle : 1; + /** Vista, 7 & 8: Hack to allow more rights to the handle duplicated by CSRSS + * during process creation. Only applicable to VmProcessUnconfirmed. On + * 32-bit systems we allow two as ZoneAlarm's system call hooks has been + * observed to do some seemingly unnecessary duplication work. */ + int32_t volatile cCsrssFirstProcessDuplicateHandle; + + /** The parent PID for VM processes, otherwise NULL. */ + HANDLE hParentPid; + /** The TID of the thread opening VBoxDrv or VBoxDrvStub, NULL if not opened. */ + HANDLE hOpenTid; + /** The PID of the CSRSS process associated with this process. */ + HANDLE hCsrssPid; + /** Pointer to the CSRSS process structure (referenced). */ + PEPROCESS pCsrssProcess; + /** State dependent data. */ + union + { + /** A stub process in the StubParent state will keep a reference to a child + * while it's in the VmProcessUnconfirmed state so that it can be cleaned up + * correctly if things doesn't work out. */ + struct SUPDRVNTPROTECT *pChild; + /** A process in the VmProcessUnconfirmed state will keep a weak + * reference to the parent's protection structure so it can clean up the pChild + * reference the parent has to it. */ + struct SUPDRVNTPROTECT *pParent; + } u; +} SUPDRVNTPROTECT; +/** Pointer to a NT process protection record. */ +typedef SUPDRVNTPROTECT *PSUPDRVNTPROTECT; +/** The SUPDRVNTPROTECT::u32Magic value (Robert A. Heinlein). */ +# define SUPDRVNTPROTECT_MAGIC UINT32_C(0x19070707) +/** The SUPDRVNTPROTECT::u32Magic value of a dead structure. */ +# define SUPDRVNTPROTECT_MAGIC_DEAD UINT32_C(0x19880508) + +/** Pointer to ObGetObjectType. */ +typedef POBJECT_TYPE (NTAPI *PFNOBGETOBJECTTYPE)(PVOID); +/** Pointer to ObRegisterCallbacks. */ +typedef NTSTATUS (NTAPI *PFNOBREGISTERCALLBACKS)(POB_CALLBACK_REGISTRATION, PVOID *); +/** Pointer to ObUnregisterCallbacks. */ +typedef VOID (NTAPI *PFNOBUNREGISTERCALLBACKS)(PVOID); +/** Pointer to PsSetCreateProcessNotifyRoutineEx. */ +typedef NTSTATUS (NTAPI *PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)(PCREATE_PROCESS_NOTIFY_ROUTINE_EX, BOOLEAN); +/** Pointer to PsReferenceProcessFilePointer. */ +typedef NTSTATUS (NTAPI *PFNPSREFERENCEPROCESSFILEPOINTER)(PEPROCESS, PFILE_OBJECT *); +/** Pointer to PsIsProtectedProcessLight. */ +typedef BOOLEAN (NTAPI *PFNPSISPROTECTEDPROCESSLIGHT)(PEPROCESS); +/** Pointer to ZwAlpcCreatePort. */ +typedef NTSTATUS (NTAPI *PFNZWALPCCREATEPORT)(PHANDLE, POBJECT_ATTRIBUTES, struct _ALPC_PORT_ATTRIBUTES *); + +#endif /* VBOX_WITH_HARDENINIG */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj); +static NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp); +#ifdef VBOXDRV_WITH_FAST_IO +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj); +#endif +static NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack); +static NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pArgument1, PVOID pArgument2); +static NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp); +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc); +#ifdef VBOX_WITH_HARDENING +static NTSTATUS supdrvNtProtectInit(void); +static void supdrvNtProtectTerm(void); +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, + SUPDRVNTPROTECTKIND enmProcessKind, bool fLink); +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect); +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid); +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect); +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect); + +static bool supdrvNtIsDebuggerAttached(void); +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId); + +#endif + + +/********************************************************************************************************************************* +* Exported Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The non-paged pool type to use, NonPagedPool or NonPagedPoolNx. */ +static POOL_TYPE g_enmNonPagedPoolType = NonPagedPool; +/** Pointer to the system device instance. */ +static PDEVICE_OBJECT g_pDevObjSys = NULL; +/** Pointer to the user device instance. */ +static PDEVICE_OBJECT g_pDevObjUsr = NULL; +#ifdef VBOXDRV_WITH_FAST_IO +/** Fast I/O dispatch table. */ +static FAST_IO_DISPATCH const g_VBoxDrvFastIoDispatch = +{ + /* .SizeOfFastIoDispatch = */ sizeof(g_VBoxDrvFastIoDispatch), + /* .FastIoCheckIfPossible = */ NULL, + /* .FastIoRead = */ NULL, + /* .FastIoWrite = */ NULL, + /* .FastIoQueryBasicInfo = */ NULL, + /* .FastIoQueryStandardInfo = */ NULL, + /* .FastIoLock = */ NULL, + /* .FastIoUnlockSingle = */ NULL, + /* .FastIoUnlockAll = */ NULL, + /* .FastIoUnlockAllByKey = */ NULL, + /* .FastIoDeviceControl = */ VBoxDrvNtFastIoDeviceControl, + /* .AcquireFileForNtCreateSection = */ NULL, + /* .ReleaseFileForNtCreateSection = */ NULL, + /* .FastIoDetachDevice = */ NULL, + /* .FastIoQueryNetworkOpenInfo = */ NULL, + /* .AcquireForModWrite = */ NULL, + /* .MdlRead = */ NULL, + /* .MdlReadComplete = */ NULL, + /* .PrepareMdlWrite = */ NULL, + /* .MdlWriteComplete = */ NULL, + /* .FastIoReadCompressed = */ NULL, + /* .FastIoWriteCompressed = */ NULL, + /* .MdlReadCompleteCompressed = */ NULL, + /* .MdlWriteCompleteCompressed = */ NULL, + /* .FastIoQueryOpen = */ NULL, + /* .ReleaseForModWrite = */ NULL, + /* .AcquireForCcFlush = */ NULL, + /* .ReleaseForCcFlush = */ NULL, +}; +#endif /* VBOXDRV_WITH_FAST_IO */ + +/** Default ZERO value. */ +static ULONG g_fOptDefaultZero = 0; +/** Registry values. + * We wrap these in a struct to ensure they are followed by a little zero + * padding in order to limit the chance of trouble on unpatched systems. */ +struct +{ + /** The ForceAsync registry value. */ + ULONG fOptForceAsyncTsc; + /** Padding. */ + uint64_t au64Padding[2]; +} g_Options = { FALSE, 0, 0 }; +/** Registry query table for RtlQueryRegistryValues. */ +static RTL_QUERY_REGISTRY_TABLE g_aRegValues[] = +{ + { + /* .QueryRoutine = */ NULL, + /* .Flags = */ RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_TYPECHECK, + /* .Name = */ L"ForceAsyncTsc", + /* .EntryContext = */ &g_Options.fOptForceAsyncTsc, + /* .DefaultType = */ (REG_DWORD << RTL_QUERY_REGISTRY_TYPECHECK_SHIFT) | REG_DWORD, + /* .DefaultData = */ &g_fOptDefaultZero, + /* .DefaultLength = */ sizeof(g_fOptDefaultZero), + }, + { NULL, 0, NULL, NULL, 0, NULL, 0 } /* terminator entry. */ +}; + +/** Pointer to KeQueryMaximumGroupCount. */ +static PFNKEQUERYMAXIMUMGROUPCOUNT g_pfnKeQueryMaximumGroupCount = NULL; +/** Pointer to KeGetProcessorIndexFromNumber. */ +static PFNKEGETPROCESSORINDEXFROMNUMBER g_pfnKeGetProcessorIndexFromNumber = NULL; +/** Pointer to KeGetProcessorNumberFromIndex. */ +static PFNKEGETPROCESSORNUMBERFROMINDEX g_pfnKeGetProcessorNumberFromIndex = NULL; + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING +/** Spinlock protecting g_NtUserIdHashTree and g_NtUserIdUidTree. */ +static RTSPINLOCK g_hNtUserIdLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTUSERID structures by hash value. */ +static PAVLLU32NODECORE g_NtUserIdHashTree = NULL; +/** AVL tree of SUPDRVNTUSERID structures by UID. */ +static PAVLU32NODECORE g_NtUserIdUidTree = NULL; +#endif + +#ifdef VBOX_WITH_HARDENING +/** Pointer to the stub device instance. */ +static PDEVICE_OBJECT g_pDevObjStub = NULL; +/** Spinlock protecting g_NtProtectTree as well as the releasing of protection + * structures. */ +static RTSPINLOCK g_hNtProtectLock = NIL_RTSPINLOCK; +/** AVL tree of SUPDRVNTPROTECT structures. */ +static AVLPVTREE g_NtProtectTree = NULL; +/** Cookie returned by ObRegisterCallbacks for the callbacks. */ +static PVOID g_pvObCallbacksCookie = NULL; +/** Combined windows NT version number. See SUP_MAKE_NT_VER_COMBINED. */ +uint32_t g_uNtVerCombined = 0; +/** Pointer to ObGetObjectType if available.. */ +static PFNOBGETOBJECTTYPE g_pfnObGetObjectType = NULL; +/** Pointer to ObRegisterCallbacks if available.. */ +static PFNOBREGISTERCALLBACKS g_pfnObRegisterCallbacks = NULL; +/** Pointer to ObUnregisterCallbacks if available.. */ +static PFNOBUNREGISTERCALLBACKS g_pfnObUnRegisterCallbacks = NULL; +/** Pointer to PsSetCreateProcessNotifyRoutineEx if available.. */ +static PFNPSSETCREATEPROCESSNOTIFYROUTINEEX g_pfnPsSetCreateProcessNotifyRoutineEx = NULL; +/** Pointer to PsReferenceProcessFilePointer if available. */ +static PFNPSREFERENCEPROCESSFILEPOINTER g_pfnPsReferenceProcessFilePointer = NULL; +/** Pointer to PsIsProtectedProcessLight. */ +static PFNPSISPROTECTEDPROCESSLIGHT g_pfnPsIsProtectedProcessLight = NULL; +/** Pointer to ZwAlpcCreatePort. */ +static PFNZWALPCCREATEPORT g_pfnZwAlpcCreatePort = NULL; + +# ifdef RT_ARCH_AMD64 +extern "C" { +/** Pointer to KiServiceLinkage (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64). */ +PFNRT g_pfnKiServiceLinkage = NULL; +/** Pointer to KiServiceInternal (used to fake missing ZwQueryVirtualMemory on + * XP64 / W2K3-64) */ +PFNRT g_pfnKiServiceInternal = NULL; +} +# endif +/** The primary ALPC port object type. (LpcPortObjectType at init time.) */ +static POBJECT_TYPE g_pAlpcPortObjectType1 = NULL; +/** The secondary ALPC port object type. (Sampled at runtime.) */ +static POBJECT_TYPE volatile g_pAlpcPortObjectType2 = NULL; + +/** Pointer to the error information device instance. */ +static PDEVICE_OBJECT g_pDevObjErrorInfo = NULL; +/** Fast mutex semaphore protecting the error info list. */ +static RTSEMMUTEX g_hErrorInfoLock = NIL_RTSEMMUTEX; +/** Head of the error info (SUPDRVNTERRORINFO). */ +static RTLISTANCHOR g_ErrorInfoHead; + +#endif + + +/** + * Takes care of creating the devices and their symbolic links. + * + * @returns NT status code. + * @param pDrvObj Pointer to driver object. + */ +static NTSTATUS vboxdrvNtCreateDevices(PDRIVER_OBJECT pDrvObj) +{ + /* + * System device. + */ + UNICODE_STRING DevName; + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_SYS); + NTSTATUS rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXT), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjSys); + if (NT_SUCCESS(rcNt)) + { + /* + * User device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_USR); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTUSR), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjUsr); + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtUsr->u32Cookie = SUPDRVDEVEXTUSR_COOKIE; + +#ifdef VBOX_WITH_HARDENING + /* + * Hardened stub device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_STUB); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTSTUB), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &g_pDevObjStub); + if (NT_SUCCESS(rcNt)) + { + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtStub->Common.u32Cookie = SUPDRVDEVEXTSTUB_COOKIE; + + /* + * Hardened error information device. + */ + RtlInitUnicodeString(&DevName, SUPDRV_NT_DEVICE_NAME_ERROR_INFO); + rcNt = IoCreateDevice(pDrvObj, sizeof(SUPDRVDEVEXTERRORINFO), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, + &g_pDevObjErrorInfo); + if (NT_SUCCESS(rcNt)) + { + g_pDevObjErrorInfo->Flags |= DO_BUFFERED_IO; + + if (NT_SUCCESS(rcNt)) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrInf = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrInf->Common.pMainDrvExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + pDevExtErrInf->Common.u32Cookie = SUPDRVDEVEXTERRORINFO_COOKIE; + +#endif + /* Done. */ + return rcNt; +#ifdef VBOX_WITH_HARDENING + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + } + } + + /* Bail out. */ + IoDeleteDevice(g_pDevObjStub); + g_pDevObjUsr = NULL; + } + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; +#endif + } + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; + } + return rcNt; +} + +/** + * Destroys the devices and links created by vboxdrvNtCreateDevices. + */ +static void vboxdrvNtDestroyDevices(void) +{ + if (g_pDevObjUsr) + { + PSUPDRVDEVEXTUSR pDevExtUsr = (PSUPDRVDEVEXTUSR)g_pDevObjUsr->DeviceExtension; + pDevExtUsr->pMainDrvExt = NULL; + } +#ifdef VBOX_WITH_HARDENING + if (g_pDevObjStub) + { + PSUPDRVDEVEXTSTUB pDevExtStub = (PSUPDRVDEVEXTSTUB)g_pDevObjStub->DeviceExtension; + pDevExtStub->Common.pMainDrvExt = NULL; + } + if (g_pDevObjErrorInfo) + { + PSUPDRVDEVEXTERRORINFO pDevExtErrorInfo = (PSUPDRVDEVEXTERRORINFO)g_pDevObjStub->DeviceExtension; + pDevExtErrorInfo->Common.pMainDrvExt = NULL; + } +#endif + +#ifdef VBOX_WITH_HARDENING + IoDeleteDevice(g_pDevObjErrorInfo); + g_pDevObjErrorInfo = NULL; + IoDeleteDevice(g_pDevObjStub); + g_pDevObjStub = NULL; +#endif + IoDeleteDevice(g_pDevObjUsr); + g_pDevObjUsr = NULL; + IoDeleteDevice(g_pDevObjSys); + g_pDevObjSys = NULL; +} + + +/** + * Driver entry point. + * + * @returns appropriate status code. + * @param pDrvObj Pointer to driver object. + * @param pRegPath Registry base path. + */ +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + RT_NOREF1(pRegPath); + + /* + * Sanity checks. + */ +#ifdef VBOXDRV_WITH_FAST_IO + if (g_VBoxDrvFastIoDispatch.FastIoDeviceControl != VBoxDrvNtFastIoDeviceControl) + { + DbgPrint("VBoxDrv: FastIoDeviceControl=%p instead of %p\n", + g_VBoxDrvFastIoDispatch.FastIoDeviceControl, VBoxDrvNtFastIoDeviceControl); + return STATUS_INTERNAL_ERROR; + } +#endif + + /* + * Figure out if we can use NonPagedPoolNx or not. + */ + ULONG ulMajorVersion, ulMinorVersion, ulBuildNumber; + PsGetVersion(&ulMajorVersion, &ulMinorVersion, &ulBuildNumber, NULL); + if (ulMajorVersion > 6 || (ulMajorVersion == 6 && ulMinorVersion >= 2)) /* >= 6.2 (W8)*/ + g_enmNonPagedPoolType = NonPagedPoolNx; + + /* + * Query options first so any overflows on unpatched machines will do less + * harm (see MS11-011 / 2393802 / 2011-03-18). + * + * Unfortunately, pRegPath isn't documented as zero terminated, even if it + * quite likely always is, so we have to make a copy here. + */ + NTSTATUS rcNt; + PWSTR pwszCopy = (PWSTR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, pRegPath->Length + sizeof(WCHAR), 'VBox'); + if (pwszCopy) + { + memcpy(pwszCopy, pRegPath->Buffer, pRegPath->Length); + pwszCopy[pRegPath->Length / sizeof(WCHAR)] = '\0'; + rcNt = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL, pwszCopy, + g_aRegValues, NULL /*pvContext*/, NULL /*pvEnv*/); + ExFreePoolWithTag(pwszCopy, 'VBox'); + /* Probably safe to ignore rcNt here. */ + } + + /* + * Resolve methods we want but isn't available everywhere. + */ + UNICODE_STRING RoutineName; + RtlInitUnicodeString(&RoutineName, L"KeQueryMaximumGroupCount"); + g_pfnKeQueryMaximumGroupCount = (PFNKEQUERYMAXIMUMGROUPCOUNT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorIndexFromNumber"); + g_pfnKeGetProcessorIndexFromNumber = (PFNKEGETPROCESSORINDEXFROMNUMBER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"KeGetProcessorNumberFromIndex"); + g_pfnKeGetProcessorNumberFromIndex = (PFNKEGETPROCESSORNUMBERFROMINDEX)MmGetSystemRoutineAddress(&RoutineName); + + Assert( (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeGetProcessorIndexFromNumber != NULL) + && (g_pfnKeGetProcessorNumberFromIndex != NULL) == (g_pfnKeQueryMaximumGroupCount != NULL)); /* all or nothing. */ + + /* + * Initialize the runtime (IPRT). + */ + int vrc = RTR0Init(0); + if (RT_SUCCESS(vrc)) + { + Log(("VBoxDrv::DriverEntry\n")); + +#ifdef VBOX_WITH_HARDENING + /* + * Initialize process protection. + */ + rcNt = supdrvNtProtectInit(); + if (NT_SUCCESS(rcNt)) +#endif + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + /* + * Create the spinlock for the SID -> UID mappings. + */ + vrc = RTSpinlockCreate(&g_hNtUserIdLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtUserId"); + if (RT_SUCCESS(vrc)) +#endif + { + /* + * Create device. + * (That means creating a device object and a symbolic link so the DOS + * subsystems (OS/2, win32, ++) can access the device.) + */ + rcNt = vboxdrvNtCreateDevices(pDrvObj); + if (NT_SUCCESS(rcNt)) + { + /* + * Initialize the device extension. + */ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); + + vrc = supdrvInitDevExt(pDevExt, sizeof(SUPDRVSESSION)); + if (!vrc) + { + /* + * Setup the driver entry points in pDrvObj. + */ + pDrvObj->DriverUnload = VBoxDrvNtUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxDrvNtCreate; + pDrvObj->MajorFunction[IRP_MJ_CLEANUP] = VBoxDrvNtCleanup; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxDrvNtClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxDrvNtDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxDrvNtInternalDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_READ] = VBoxDrvNtRead; + pDrvObj->MajorFunction[IRP_MJ_WRITE] = VBoxDrvNtNotSupportedStub; + +#ifdef VBOXDRV_WITH_FAST_IO + /* Fast I/O to speed up guest execution roundtrips. */ + pDrvObj->FastIoDispatch = (PFAST_IO_DISPATCH)&g_VBoxDrvFastIoDispatch; +#endif + + /* + * Register ourselves for power state changes. We don't + * currently care if this fails. + */ + UNICODE_STRING CallbackName; + RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState"); + + OBJECT_ATTRIBUTES Attr; + InitializeObjectAttributes(&Attr, &CallbackName, OBJ_CASE_INSENSITIVE, NULL, NULL); + + rcNt = ExCreateCallback(&pDevExt->pObjPowerCallback, &Attr, TRUE, TRUE); + if (rcNt == STATUS_SUCCESS) + pDevExt->hPowerCallback = ExRegisterCallback(pDevExt->pObjPowerCallback, + VBoxPowerDispatchCallback, + g_pDevObjSys); + + /* + * Done! Returning success! + */ + Log(("VBoxDrv::DriverEntry returning STATUS_SUCCESS\n")); + return STATUS_SUCCESS; + } + + /* + * Failed. Clean up. + */ + Log(("supdrvInitDevExit failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + + vboxdrvNtDestroyDevices(); + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif + } +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + else + rcNt = VBoxDrvNtErr2NtStatus(vrc); +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + } + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + } + else + { + Log(("RTR0Init failed with vrc=%d!\n", vrc)); + rcNt = VBoxDrvNtErr2NtStatus(vrc); + } + if (NT_SUCCESS(rcNt)) + rcNt = STATUS_INVALID_PARAMETER; + return rcNt; +} + + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +void _stdcall VBoxDrvNtUnload(PDRIVER_OBJECT pDrvObj) +{ + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + + Log(("VBoxDrvNtUnload at irql %d\n", KeGetCurrentIrql())); + + /* Clean up the power callback registration. */ + if (pDevExt->hPowerCallback) + ExUnregisterCallback(pDevExt->hPowerCallback); + if (pDevExt->pObjPowerCallback) + ObDereferenceObject(pDevExt->pObjPowerCallback); + + /* + * We ASSUME that it's not possible to unload a driver with open handles. + */ + supdrvDeleteDevExt(pDevExt); +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + RTSpinlockDestroy(g_hNtUserIdLock); + g_hNtUserIdLock = NIL_RTSPINLOCK; +#endif +#ifdef VBOX_WITH_HARDENING + supdrvNtProtectTerm(); +#endif + RTTermRunCallbacks(RTTERMREASON_UNLOAD, 0); + RTR0Term(); + vboxdrvNtDestroyDevices(); + + NOREF(pDrvObj); +} + +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + +/** + * Worker for supdrvNtUserIdMakeForSession. + */ +static bool supdrvNtUserIdMakeUid(PSUPDRVNTUSERID pNtUserId) +{ + pNtUserId->UidCore.Key = pNtUserId->HashCore.Key; + for (uint32_t cTries = 0; cTries < _4K; cTries++) + { + bool fRc = RTAvlU32Insert(&g_NtUserIdUidTree, &pNtUserId->UidCore); + if (fRc) + return true; + pNtUserId->UidCore.Key += pNtUserId->cchSid | 1; + } + return false; +} + + +/** + * Try create a RTUID value for the session. + * + * @returns VBox status code. + * @param pSession The session to try set SUPDRVSESSION::Uid for. + */ +static int supdrvNtUserIdMakeForSession(PSUPDRVSESSION pSession) +{ + /* + * Get the current security context and query the User SID for it. + */ + SECURITY_SUBJECT_CONTEXT Ctx = { NULL, SecurityIdentification, NULL, NULL }; + SeCaptureSubjectContext(&Ctx); + + int rc; + TOKEN_USER *pTokenUser = NULL; + NTSTATUS rcNt = SeQueryInformationToken(SeQuerySubjectContextToken(&Ctx) /* or always PrimaryToken?*/, + TokenUser, (PVOID *)&pTokenUser); + if (NT_SUCCESS(rcNt)) + { + /* + * Convert the user SID to a string to make it easier to handle, then prepare + * a user ID entry for it as that way we can combine lookup and insertion and + * avoid needing to deal with races. + */ + UNICODE_STRING UniStr = RTNT_NULL_UNISTR(); + rcNt = RtlConvertSidToUnicodeString(&UniStr, pTokenUser->User.Sid, TRUE /*AllocateDesitnationString*/); + if (NT_SUCCESS(rcNt)) + { + size_t cchSid = 0; + rc = RTUtf16CalcUtf8LenEx(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &cchSid); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTUSERID const pNtUserIdNew = (PSUPDRVNTUSERID)RTMemAlloc(RT_UOFFSETOF_DYN(SUPDRVNTUSERID, szSid[cchSid + 1])); + if (pNtUserIdNew) + { + char *pszSid = pNtUserIdNew->szSid; + rc = RTUtf16ToUtf8Ex(UniStr.Buffer, UniStr.Length / sizeof(RTUTF16), &pszSid, cchSid + 1, NULL); + if (RT_SUCCESS(rc)) + { + pNtUserIdNew->HashCore.Key = RTStrHash1(pNtUserIdNew->szSid); + pNtUserIdNew->cchSid = (uint16_t)cchSid; + pNtUserIdNew->cRefs = 1; + Log5Func(("pNtUserId=%p cchSid=%u hash=%#x '%s'\n", pNtUserIdNew, cchSid, pNtUserIdNew->HashCore.Key, pszSid)); + + /* + * Do the lookup / insert. + */ + RTSpinlockAcquire(g_hNtUserIdLock); + AssertCompileMemberOffset(SUPDRVNTUSERID, HashCore, 0); + PSUPDRVNTUSERID pNtUserId = (PSUPDRVNTUSERID)RTAvllU32Get(&g_NtUserIdHashTree, pNtUserIdNew->HashCore.Key); + if (pNtUserId) + { + /* Match the strings till we reach the end of the collision list. */ + PSUPDRVNTUSERID const pNtUserIdHead = pNtUserId; + while ( pNtUserId + && ( pNtUserId->cchSid != cchSid + || memcmp(pNtUserId->szSid, pNtUserId->szSid, cchSid) != 0)) + pNtUserId = (PSUPDRVNTUSERID)pNtUserId->HashCore.pList; + if (pNtUserId) + { + /* Found matching: Retain reference and free the new entry we prepared. */ + uint32_t const cRefs = ASMAtomicIncU32(&pNtUserId->cRefs); + Assert(cRefs < _16K); RT_NOREF(cRefs); + RTSpinlockRelease(g_hNtUserIdLock); + Log5Func(("Using %p / %#x instead\n", pNtUserId, pNtUserId->UidCore.Key)); + } + else + { + /* No match: Try insert prepared entry after the head node. */ + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + pNtUserIdNew->HashCore.pList = pNtUserIdHead->HashCore.pList; + pNtUserIdHead->HashCore.pList = &pNtUserIdNew->HashCore; + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("supdrvNtUserIdMakeForSession: failed to insert new\n")); + } + } + else + { + /* No matching hash: Try insert the prepared entry. */ + pNtUserIdNew->UidCore.Key = pNtUserIdNew->HashCore.Key; + if (supdrvNtUserIdMakeUid(pNtUserIdNew)) + { + RTAvllU32Insert(&g_NtUserIdHashTree, &pNtUserIdNew->HashCore); + pNtUserId = pNtUserIdNew; + } + RTSpinlockRelease(g_hNtUserIdLock); + if (pNtUserId) + Log5Func(("Using %p / %#x (the prepared one, no conflict)\n", pNtUserId, pNtUserId->UidCore.Key)); + else + LogRelFunc(("failed to insert!! WTF!?!\n")); + } + + if (pNtUserId != pNtUserIdNew) + RTMemFree(pNtUserIdNew); + + /* + * Update the session info. + */ + pSession->pNtUserId = pNtUserId; + pSession->Uid = pNtUserId ? (RTUID)pNtUserId->UidCore.Key : NIL_RTUID; + } + else + RTMemFree(pNtUserIdNew); + } + else + rc = VERR_NO_MEMORY; + } + RtlFreeUnicodeString(&UniStr); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("RtlConvertSidToUnicodeString failed: %#x / %Rrc\n", rcNt, rc)); + } + ExFreePool(pTokenUser); + } + else + { + rc = RTErrConvertFromNtStatus(rcNt); + LogFunc(("SeQueryInformationToken failed: %#x / %Rrc\n", rcNt, rc)); + } + + SeReleaseSubjectContext(&Ctx); + return rc; +} + + +/** + * Releases a reference to @a pNtUserId. + * + * @param pNtUserId The NT user ID entry to release. + */ +static void supdrvNtUserIdRelease(PSUPDRVNTUSERID pNtUserId) +{ + if (pNtUserId) + { + uint32_t const cRefs = ASMAtomicDecU32(&pNtUserId->cRefs); + Log5Func(("%p / %#x: cRefs=%d\n", pNtUserId, pNtUserId->cRefs)); + Assert(cRefs < _8K); + if (cRefs == 0) + { + RTSpinlockAcquire(g_hNtUserIdLock); + if (pNtUserId->cRefs == 0) + { + PAVLLU32NODECORE pAssert1 = RTAvllU32RemoveNode(&g_NtUserIdHashTree, &pNtUserId->HashCore); + PAVLU32NODECORE pAssert2 = RTAvlU32Remove(&g_NtUserIdUidTree, pNtUserId->UidCore.Key); + + RTSpinlockRelease(g_hNtUserIdLock); + + Assert(pAssert1 == &pNtUserId->HashCore); + Assert(pAssert2 == &pNtUserId->UidCore); + RT_NOREF(pAssert1, pAssert2); + + RTMemFree(pNtUserId); + } + else + RTSpinlockRelease(g_hNtUserIdLock); + } + } +} + +#endif /* VBOXDRV_WITH_SID_TO_UID_MAPPING */ + +/** + * For simplifying request completion into a simple return statement, extended + * version. + * + * @returns rcNt + * @param rcNt The status code. + * @param uInfo Extra info value. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequestEx(NTSTATUS rcNt, ULONG_PTR uInfo, PIRP pIrp) +{ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = uInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * For simplifying request completion into a simple return statement. + * + * @returns rcNt + * @param rcNt The status code. + * @param pIrp The IRP. + */ +DECLINLINE(NTSTATUS) supdrvNtCompleteRequest(NTSTATUS rcNt, PIRP pIrp) +{ + return supdrvNtCompleteRequestEx(rcNt, 0 /*uInfo*/, pIrp); +} + + +/** + * Create (i.e. Open) file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtCreate: RequestorMode=%d\n", pIrp->RequestorMode)); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + + /* + * We are not remotely similar to a directory... + * (But this is possible.) + */ + if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) + return supdrvNtCompleteRequest(STATUS_NOT_A_DIRECTORY, pIrp); + + /* + * Don't create a session for kernel clients, they'll close the handle + * immediately and work with the file object via + * VBoxDrvNtInternalDeviceControl. The first request will be one to + * create a session. + */ + NTSTATUS rcNt; + if (pIrp->RequestorMode == KernelMode) + { + if (pDevObj == g_pDevObjSys) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + rcNt = STATUS_ACCESS_DENIED; + } +#ifdef VBOX_WITH_HARDENING + /* + * Anyone can open the error device. + */ + else if (pDevObj == g_pDevObjErrorInfo) + { + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } +#endif + else + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + /* + * Make sure no debuggers are attached to non-user processes. + */ + if ( pDevObj != g_pDevObjUsr + && supdrvNtIsDebuggerAttached()) + { + LogRel(("vboxdrv: Process %p is being debugged, access to vboxdrv / vboxdrvu declined.\n", + PsGetProcessId(PsGetCurrentProcess()))); + rcNt = STATUS_TRUST_FAILURE; + } + else +#endif + { + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_HARDENING + /* + * Access to the stub device is only granted to processes which + * passes verification. + * + * Note! The stub device has no need for a SUPDRVSESSION structure, + * so the it uses the SUPDRVNTPROTECT directly instead. + */ + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = NULL; + rc = supdrvNtProtectCreate(&pNtProtect, PsGetProcessId(PsGetCurrentProcess()), + kSupDrvNtProtectKind_StubUnverified, true /*fLink*/); + if (RT_SUCCESS(rc)) + { + rc = supdrvNtProtectFindAssociatedCsrss(pNtProtect); + if (RT_SUCCESS(rc)) + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + + supdrvNtProtectRelease(pNtProtect); + } + LogRel(("vboxdrv: Declined %p access to VBoxDrvStub: rc=%d\n", PsGetProcessId(PsGetCurrentProcess()), rc)); + } + /* + * Unrestricted access is only granted to a process in the + * VmProcessUnconfirmed state that checks out correctly and is + * allowed to transition to VmProcessConfirmed. Again, only one + * session per process. + */ + else if (pDevObj != g_pDevObjUsr) + { + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(PsGetCurrentProcess())); + if (pNtProtect) + { + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + rc = supdrvNtProtectVerifyProcess(pNtProtect); + if (RT_SUCCESS(rc)) + { + /* Create a session. */ + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, + &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pSession->pNtProtect = pNtProtect; /* Keeps reference. */ + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + + /* No second attempt. */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + LogRel(("vboxdrv: supdrvCreateSession failed for process %p: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + LogRel(("vboxdrv: Process %p failed process verification: rc=%d.\n", + PsGetProcessId(PsGetCurrentProcess()), rc)); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process (enmProcessKind=%d).\n", + PsGetProcessId(PsGetCurrentProcess()), pNtProtect->enmProcessKind)); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_2; + } + supdrvNtProtectRelease(pNtProtect); + } + else + { + LogRel(("vboxdrv: %p is not a budding VM process.\n", PsGetProcessId(PsGetCurrentProcess()))); + rc = VERR_SUPDRV_NOT_BUDDING_VM_PROCESS_1; + } + } + /* + * Call common code to create an unprivileged session. + */ + else + { + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, false /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +#endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + { + pFileObj->FsContext = pSession; /* Keeps reference. No race. */ + pSession->pNtProtect = NULL; + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + } + } + } + +#else /* !VBOX_WITH_HARDENING */ + /* + * Call common code to create a session. + */ + pFileObj->FsContext = NULL; + PSUPDRVSESSION pSession; + rc = supdrvCreateSession(pDevExt, true /*fUser*/, pDevObj == g_pDevObjSys /*fUnrestricted*/, &pSession); + if (RT_SUCCESS(rc)) + { +# ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + rc = supdrvNtUserIdMakeForSession(pSession); + if (RT_SUCCESS(rc)) +# endif + rc = supdrvSessionHashTabInsert(pDevExt, pSession, (PSUPDRVSESSION *)&pFileObj->FsContext, NULL); + supdrvSessionRelease(pSession); + if (RT_SUCCESS(rc)) + return supdrvNtCompleteRequestEx(STATUS_SUCCESS, FILE_OPENED, pIrp); + + } +#endif /* !VBOX_WITH_HARDENING */ + + /* bail out */ + rcNt = VBoxDrvNtErr2NtStatus(rc); + } + } + + Assert(!NT_SUCCESS(rcNt)); + pFileObj->FsContext = NULL; + return supdrvNtCompleteRequest(rcNt, pIrp); /* Note. the IoStatus is completely ignored on error. */ +} + + +/** + * Clean up file handle entry point. + * + * This is called when the last handle reference is released, or something like + * that. In the case of IoGetDeviceObjectPointer, this is called as it closes + * the handle, however it will go on using the file object afterwards... + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtCleanup(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +/** + * Close file entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + +#ifdef VBOX_WITH_HARDENING + if (pDevObj == g_pDevObjStub) + { + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)pFileObj->FsContext; + Log(("VBoxDrvNtClose: pDevExt=%p pFileObj=%p pNtProtect=%p\n", pDevExt, pFileObj, pNtProtect)); + if (pNtProtect) + { + supdrvNtProtectRelease(pNtProtect); + pFileObj->FsContext = NULL; + } + } + else if (pDevObj == g_pDevObjErrorInfo) + supdrvNtErrorInfoCleanupProcess(PsGetCurrentProcessId()); + else +#endif + { + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + Log(("VBoxDrvNtCleanup: pDevExt=%p pFileObj=%p pSession=%p\n", pDevExt, pFileObj, pSession)); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + + return supdrvNtCompleteRequest(STATUS_SUCCESS, pIrp); +} + + +#ifdef VBOXDRV_WITH_FAST_IO +/** + * Fast I/O device control callback. + * + * This performs no buffering, neither on the way in or out. + * + * @returns TRUE if handled, FALSE if the normal I/O control routine should be + * called. + * @param pFileObj The file object. + * @param fWait Whether it's a blocking call + * @param pvInput The input buffer as specified by the user. + * @param cbInput The size of the input buffer. + * @param pvOutput The output buffer as specfied by the user. + * @param cbOutput The size of the output buffer. + * @param uCmd The I/O command/function being invoked. + * @param pIoStatus Where to return the status of the operation. + * @param pDevObj The device object.. + */ +static BOOLEAN _stdcall VBoxDrvNtFastIoDeviceControl(PFILE_OBJECT pFileObj, BOOLEAN fWait, PVOID pvInput, ULONG cbInput, + PVOID pvOutput, ULONG cbOutput, ULONG uCmd, + PIO_STATUS_BLOCK pIoStatus, PDEVICE_OBJECT pDevObj) +{ + RT_NOREF1(fWait); + + /* + * Only the normal devices, not the stub or error info ones. + */ + if (pDevObj != g_pDevObjSys && pDevObj != g_pDevObjUsr) + { + pIoStatus->Status = STATUS_NOT_SUPPORTED; + pIoStatus->Information = 0; + return TRUE; + } + + /* + * Check the input a little bit and get a the session references. + */ + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pFileObj->FsContext); + if (!pSession) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + return TRUE; + } + + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + pIoStatus->Status = STATUS_TRUST_FAILURE; + pIoStatus->Information = 0; + supdrvSessionRelease(pSession); + return TRUE; + } +#endif + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pvOutput/* VMCPU id */, + pDevExt, pSession); + pIoStatus->Status = RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER; + pIoStatus->Information = 0; /* Could be used to pass rc if we liked. */ + supdrvSessionRelease(pSession); + return TRUE; + } + } + + /* + * The normal path. + */ + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtFastIoDeviceControl(%p): ioctl=%#x pvIn=%p cbIn=%#x pvOut=%p cbOut=%#x pSession=%p\n", + pDevExt, uCmd, pvInput, cbInput, pvOutput, cbOutput, pSession)); + +# ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(NULL)) +# endif + { + /* + * In this fast I/O device control path we have to do our own buffering. + */ + /* Verify that the I/O control function matches our pattern. */ + if ((uCmd & 0x3) == METHOD_BUFFERED) + { + /* Get the header so we can validate it a little bit against the + parameters before allocating any memory kernel for the reqest. */ + SUPREQHDR Hdr; + if (cbInput >= sizeof(Hdr) && cbOutput >= sizeof(Hdr)) + { + __try + { + RtlCopyMemory(&Hdr, pvInput, sizeof(Hdr)); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + } + } + else + { + Hdr.cbIn = Hdr.cbOut = 0; /* shut up MSC */ + rcNt = STATUS_INVALID_PARAMETER; + } + if (NT_SUCCESS(rcNt)) + { + /* Verify that the sizes in the request header are correct. */ + ULONG cbBuf = RT_MAX(cbInput, cbOutput); + if ( cbInput == Hdr.cbIn + && cbOutput == Hdr.cbOut + && cbBuf < _1M*16) + { + /* Allocate a buffer and copy all the input into it. */ + PSUPREQHDR pHdr = (PSUPREQHDR)ExAllocatePoolWithTag(g_enmNonPagedPoolType, cbBuf, 'VBox'); + if (pHdr) + { + __try + { + RtlCopyMemory(pHdr, pvInput, cbInput); + if (cbInput < cbBuf) + RtlZeroMemory((uint8_t *)pHdr + cbInput, cbBuf - cbInput); + if (!memcmp(pHdr, &Hdr, sizeof(Hdr))) + rcNt = STATUS_SUCCESS; + else + rcNt = STATUS_INVALID_PARAMETER; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + if (NT_SUCCESS(rcNt)) + { + /* + * Now call the common code to do the real work. + */ + rc = supdrvIOCtl(uCmd, pDevExt, pSession, pHdr, cbBuf); + if (RT_SUCCESS(rc)) + { + /* + * Copy back the result. + */ + cbOut = pHdr->cbOut; + if (cbOut > cbOutput) + { + cbOut = cbOutput; + OSDBGPRINT(("VBoxDrvNtFastIoDeviceControl: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, uCmd)); + } + if (cbOut) + { + __try + { + RtlCopyMemory(pvOutput, pHdr, cbOut); + rcNt = STATUS_SUCCESS; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + rcNt = GetExceptionCode(); + } + } + else + rcNt = STATUS_SUCCESS; + } + else if (rc == VERR_INVALID_PARAMETER) + rcNt = STATUS_INVALID_PARAMETER; + else + rcNt = STATUS_NOT_SUPPORTED; + Log2(("VBoxDrvNtFastIoDeviceControl: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + Log(("VBoxDrvNtFastIoDeviceControl: Error reading %u bytes of user memory at %p (uCmd=%#x)\n", + cbInput, pvInput, uCmd)); + ExFreePoolWithTag(pHdr, 'VBox'); + } + else + rcNt = STATUS_NO_MEMORY; + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + uCmd, Hdr.cbIn, Hdr.cbOut, cbInput, cbOutput)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + } + else + { + Log(("VBoxDrvNtFastIoDeviceControl: not buffered request (%#x) - not supported\n", uCmd)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +# ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtFastIoDeviceControl: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +# endif + + /* complete the request. */ + pIoStatus->Status = rcNt; + pIoStatus->Information = cbOut; + supdrvSessionRelease(pSession); + return TRUE; /* handled. */ +} +#endif /* VBOXDRV_WITH_FAST_IO */ + + +/** + * Device I/O Control entry point. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, RTProcSelf(), RTR0ProcHandleSelf(), + (PSUPDRVSESSION *)&pStack->FileObject->FsContext); + + if (!RT_VALID_PTR(pSession)) + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + + /* + * Deal with the 2-3 high-speed IOCtl that takes their arguments from + * the session and iCmd, and does not return anything. + */ + if (pSession->fUnrestricted) + { +#if defined(VBOX_WITH_HARDENING) && !defined(VBOX_WITHOUT_DEBUGGER_CHECKS) + if (supdrvNtIsDebuggerAttached()) + { + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(STATUS_TRUST_FAILURE, pIrp); + } +#endif + + ULONG uCmd = pStack->Parameters.DeviceIoControl.IoControlCode; + if ( (uCmd & 3) == METHOD_NEITHER + && (uint32_t)((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2) < (uint32_t)32) + { + int rc = supdrvIOCtlFast((uCmd - SUP_IOCTL_FAST_DO_FIRST) >> 2, + (unsigned)(uintptr_t)pIrp->UserBuffer /* VMCPU id */, + pDevExt, pSession); + + /* Complete the I/O request. */ + supdrvSessionRelease(pSession); + return supdrvNtCompleteRequest(RT_SUCCESS(rc) ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER, pIrp); + } + } + + return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack); +} + + +/** + * Worker for VBoxDrvNtDeviceControl that takes the slow IOCtl functions. + * + * @returns NT status code. + * + * @param pDevExt Device extension. + * @param pSession The session. + * @param pIrp Request packet. + * @param pStack The stack location containing the DeviceControl parameters. + */ +static int VBoxDrvNtDeviceControlSlow(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, PIRP pIrp, PIO_STACK_LOCATION pStack) +{ + NTSTATUS rcNt; + uint32_t cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtDeviceControlSlow(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + +#ifdef RT_ARCH_AMD64 + /* Don't allow 32-bit processes to do any I/O controls. */ + if (!IoIs32bitProcess(pIrp)) +#endif + { + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify that the sizes in the request header are correct. */ + PSUPREQHDR pHdr = (PSUPREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cbIn + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cbOut) + { + /* Zero extra output bytes to make sure we don't leak anything. */ + if (pHdr->cbIn < pHdr->cbOut) + RtlZeroMemory((uint8_t *)pHdr + pHdr->cbIn, pHdr->cbOut - pHdr->cbIn); + + /* + * Do the job. + */ + rc = supdrvIOCtl(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr, + RT_MAX(pHdr->cbIn, pHdr->cbOut)); + if (!rc) + { + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cbOut; + if (cbOut > pStack->Parameters.DeviceIoControl.OutputBufferLength) + { + cbOut = pStack->Parameters.DeviceIoControl.OutputBufferLength; + OSDBGPRINT(("VBoxDrvNtDeviceControlSlow: too much output! %#x > %#x; uCmd=%#x!\n", + pHdr->cbOut, cbOut, pStack->Parameters.DeviceIoControl.IoControlCode)); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + Log2(("VBoxDrvNtDeviceControlSlow: returns %#x cbOut=%d rc=%#x\n", rcNt, cbOut, rc)); + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: Mismatching sizes (%#x) - Hdr=%#lx/%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbIn : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cbOut : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + Log(("VBoxDrvNtDeviceControlSlow: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + } +#ifdef RT_ARCH_AMD64 + else + { + Log(("VBoxDrvNtDeviceControlSlow: WOW64 req - not supported\n")); + rcNt = STATUS_NOT_SUPPORTED; + } +#endif + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + supdrvSessionRelease(pSession); + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Internal Device I/O Control entry point, used for IDC. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +NTSTATUS _stdcall VBoxDrvNtInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + VBOXDRV_COMPLETE_IRP_AND_RETURN_IF_STUB_OR_ERROR_INFO_DEV(pDevObj, pIrp); + + PSUPDRVDEVEXT pDevExt = SUPDRVNT_GET_DEVEXT(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack ? pStack->FileObject : NULL; + PSUPDRVSESSION pSession = pFileObj ? (PSUPDRVSESSION)pFileObj->FsContext : NULL; + NTSTATUS rcNt; + unsigned cbOut = 0; + int rc = 0; + Log2(("VBoxDrvNtInternalDeviceControl(%p,%p): ioctl=%#x pBuf=%p cbIn=%#x cbOut=%#x pSession=%p\n", + pDevExt, pIrp, pStack->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength, pSession)); + + /* Verify that it's a buffered CTL. */ + if ((pStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED) + { + /* Verify the pDevExt in the session. */ + if ( pStack->Parameters.DeviceIoControl.IoControlCode != SUPDRV_IDC_REQ_CONNECT + ? RT_VALID_PTR(pSession) && pSession->pDevExt == pDevExt + : !pSession + ) + { + /* Verify that the size in the request header is correct. */ + PSUPDRVIDCREQHDR pHdr = (PSUPDRVIDCREQHDR)pIrp->AssociatedIrp.SystemBuffer; + if ( pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) + && pStack->Parameters.DeviceIoControl.InputBufferLength == pHdr->cb + && pStack->Parameters.DeviceIoControl.OutputBufferLength == pHdr->cb) + { + /* + * Call the generic code. + * + * Note! Connect and disconnect requires some extra attention + * in order to get the session handling right. + */ + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = NULL; + + rc = supdrvIDC(pStack->Parameters.DeviceIoControl.IoControlCode, pDevExt, pSession, pHdr); + if (!rc) + { + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_CONNECT) + pFileObj->FsContext = ((PSUPDRVIDCREQCONNECT)pHdr)->u.Out.pSession; + + rcNt = STATUS_SUCCESS; + cbOut = pHdr->cb; + } + else + { + rcNt = STATUS_INVALID_PARAMETER; + if (pStack->Parameters.DeviceIoControl.IoControlCode == SUPDRV_IDC_REQ_DISCONNECT) + pFileObj->FsContext = pSession; + } + Log2(("VBoxDrvNtInternalDeviceControl: returns %#x/rc=%#x\n", rcNt, rc)); + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: Mismatching sizes (%#x) - Hdr=%#lx Irp=%#lx/%#lx!\n", + pStack->Parameters.DeviceIoControl.IoControlCode, + pStack->Parameters.DeviceIoControl.InputBufferLength >= sizeof(*pHdr) ? pHdr->cb : 0, + pStack->Parameters.DeviceIoControl.InputBufferLength, + pStack->Parameters.DeviceIoControl.OutputBufferLength)); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + rcNt = STATUS_NOT_SUPPORTED; + } + else + { + Log(("VBoxDrvNtInternalDeviceControl: not buffered request (%#x) - not supported\n", + pStack->Parameters.DeviceIoControl.IoControlCode)); + rcNt = STATUS_NOT_SUPPORTED; + } + + /* complete the request. */ + pIrp->IoStatus.Status = rcNt; + pIrp->IoStatus.Information = cbOut; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Implementation of the read major function for VBoxDrvErrorInfo. + * + * This is a stub function for the other devices. + * + * @returns NT status code. + * @param pDevObj The device object. + * @param pIrp The I/O request packet. + */ +NTSTATUS _stdcall VBoxDrvNtRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtRead\n")); + RT_NOREF1(pDevObj); + + NTSTATUS rcNt; + pIrp->IoStatus.Information = 0; + +#ifdef VBOX_WITH_HARDENING + /* + * VBoxDrvErrorInfo? + */ + if (pDevObj == g_pDevObjErrorInfo) + { + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + if ( pStack + && (pIrp->Flags & IRP_BUFFERED_IO)) + { + /* + * Look up the process error information. + */ + HANDLE hCurThreadId = PsGetCurrentThreadId(); + HANDLE hCurProcessId = PsGetCurrentProcessId(); + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pMatch = NULL; + PSUPDRVNTERRORINFO pCur; + RTListForEach(&g_ErrorInfoHead, pCur, SUPDRVNTERRORINFO, ListEntry) + { + if ( pCur->hProcessId == hCurProcessId + && pCur->hThreadId == hCurThreadId) + { + pMatch = pCur; + break; + } + } + + /* + * Did we find error info and is the caller requesting data within it? + * If so, check the destination buffer and copy the data into it. + */ + if ( pMatch + && pStack->Parameters.Read.ByteOffset.QuadPart < pMatch->cchErrorInfo + && pStack->Parameters.Read.ByteOffset.QuadPart >= 0) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if (pvDstBuf) + { + uint32_t offRead = (uint32_t)pStack->Parameters.Read.ByteOffset.QuadPart; + uint32_t cbToRead = pMatch->cchErrorInfo - offRead; + if (cbToRead < pStack->Parameters.Read.Length) + RT_BZERO((uint8_t *)pvDstBuf + cbToRead, pStack->Parameters.Read.Length - cbToRead); + else + cbToRead = pStack->Parameters.Read.Length; + memcpy(pvDstBuf, &pMatch->szErrorInfo[offRead], cbToRead); + pIrp->IoStatus.Information = cbToRead; + + rcNt = STATUS_SUCCESS; + } + else + rcNt = STATUS_INVALID_ADDRESS; + } + /* + * End of file. Free the info. + */ + else if (pMatch) + { + RTListNodeRemove(&pMatch->ListEntry); + RTMemFree(pMatch); + rcNt = STATUS_END_OF_FILE; + } + /* + * We found no error info. Return EOF. + */ + else + rcNt = STATUS_END_OF_FILE; + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + rcNt = STATUS_UNSUCCESSFUL; + + /* Paranoia: Clear the buffer on failure. */ + if (!NT_SUCCESS(rcNt)) + { + PVOID pvDstBuf = pIrp->AssociatedIrp.SystemBuffer; + if ( pvDstBuf + && pStack->Parameters.Read.Length) + RT_BZERO(pvDstBuf, pStack->Parameters.Read.Length); + } + } + else + rcNt = STATUS_INVALID_PARAMETER; + } + else +#endif /* VBOX_WITH_HARDENING */ + { + /* + * Stub. + */ + rcNt = STATUS_NOT_SUPPORTED; + } + + /* + * Complete the request. + */ + pIrp->IoStatus.Status = rcNt; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return rcNt; +} + + +/** + * Stub function for functions we don't implemented. + * + * @returns STATUS_NOT_SUPPORTED + * @param pDevObj Device object. + * @param pIrp IRP. + */ +NTSTATUS _stdcall VBoxDrvNtNotSupportedStub(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + Log(("VBoxDrvNtNotSupportedStub\n")); + NOREF(pDevObj); + + pIrp->IoStatus.Information = 0; + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return STATUS_NOT_SUPPORTED; +} + + +/** + * ExRegisterCallback handler for power events + * + * @param pCallbackContext User supplied parameter (pDevObj) + * @param pvArgument1 First argument + * @param pvArgument2 Second argument + */ +VOID _stdcall VBoxPowerDispatchCallback(PVOID pCallbackContext, PVOID pvArgument1, PVOID pvArgument2) +{ + /*PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pCallbackContext;*/ RT_NOREF1(pCallbackContext); + Log(("VBoxPowerDispatchCallback: %x %x\n", pvArgument1, pvArgument2)); + + /* Power change imminent? */ + if ((uintptr_t)pvArgument1 == PO_CB_SYSTEM_STATE_LOCK) + { + if (pvArgument2 == NULL) + Log(("VBoxPowerDispatchCallback: about to go into suspend mode!\n")); + else + Log(("VBoxPowerDispatchCallback: resumed!\n")); + + /* Inform any clients that have registered themselves with IPRT. */ + RTPowerSignalEvent(pvArgument2 == NULL ? RTPOWEREVENT_SUSPEND : RTPOWEREVENT_RESUME); + } +} + + +/** + * Called to clean up the session structure before it's freed. + * + * @param pDevExt The device globals. + * @param pSession The session that's being cleaned up. + */ +void VBOXCALL supdrvOSCleanupSession(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession) +{ +#ifdef VBOX_WITH_HARDENING + if (pSession->pNtProtect) + { + supdrvNtProtectRelease(pSession->pNtProtect); + pSession->pNtProtect = NULL; + } + RT_NOREF1(pDevExt); +#else + RT_NOREF2(pDevExt, pSession); +#endif +#ifdef VBOXDRV_WITH_SID_TO_UID_MAPPING + if (pSession->pNtUserId) + { + supdrvNtUserIdRelease(pSession->pNtUserId); + pSession->pNtUserId = NULL; + } +#endif +} + + +void VBOXCALL supdrvOSSessionHashTabInserted(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +void VBOXCALL supdrvOSSessionHashTabRemoved(PSUPDRVDEVEXT pDevExt, PSUPDRVSESSION pSession, void *pvUser) +{ + NOREF(pDevExt); NOREF(pSession); NOREF(pvUser); +} + + +size_t VBOXCALL supdrvOSGipGetGroupTableSize(PSUPDRVDEVEXT pDevExt) +{ + NOREF(pDevExt); + uint32_t cMaxCpus = RTMpGetCount(); + uint32_t cGroups = RTMpGetMaxCpuGroupCount(); + + return cGroups * RT_UOFFSETOF(SUPGIPCPUGROUP, aiCpuSetIdxs) + + RT_SIZEOFMEMB(SUPGIPCPUGROUP, aiCpuSetIdxs[0]) * cMaxCpus; +} + + +int VBOXCALL supdrvOSInitGipGroupTable(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, size_t cbGipCpuGroups) +{ + Assert(cbGipCpuGroups > 0); NOREF(cbGipCpuGroups); NOREF(pDevExt); + + unsigned const cGroups = RTMpGetMaxCpuGroupCount(); + AssertReturn(cGroups > 0 && cGroups < RT_ELEMENTS(pGip->aoffCpuGroup), VERR_INTERNAL_ERROR_2); + pGip->cPossibleCpuGroups = cGroups; + + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)&pGip->aCPUs[pGip->cCpus]; + for (uint32_t idxGroup = 0; idxGroup < cGroups; idxGroup++) + { + uint32_t cActive = 0; + uint32_t const cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + uint32_t const cbNeeded = RT_UOFFSETOF_DYN(SUPGIPCPUGROUP, aiCpuSetIdxs[cMax]); + uintptr_t const offGroup = (uintptr_t)pGroup - (uintptr_t)pGip; + AssertReturn(cbNeeded <= cbGipCpuGroups, VERR_INTERNAL_ERROR_3); + AssertReturn(cActive <= cMax, VERR_INTERNAL_ERROR_4); + AssertReturn(offGroup == (uint32_t)offGroup, VERR_INTERNAL_ERROR_5); + + pGip->aoffCpuGroup[idxGroup] = offGroup; + pGroup->cMembers = cActive; + pGroup->cMaxMembers = cMax; + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + pGroup->aiCpuSetIdxs[idxMember] = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + Assert((unsigned)pGroup->aiCpuSetIdxs[idxMember] < pGip->cPossibleCpus); + } + + /* advance. */ + cbGipCpuGroups -= cbNeeded; + pGroup = (PSUPGIPCPUGROUP)&pGroup->aiCpuSetIdxs[cMax]; + } + + return VINF_SUCCESS; +} + + +void VBOXCALL supdrvOSGipInitGroupBitsForCpu(PSUPDRVDEVEXT pDevExt, PSUPGLOBALINFOPAGE pGip, PSUPGIPCPU pGipCpu) +{ + NOREF(pDevExt); + + /* + * Translate the CPU index into a group and member. + */ + PROCESSOR_NUMBER ProcNum = { 0, (UCHAR)pGipCpu->iCpuSet, 0 }; + if (g_pfnKeGetProcessorNumberFromIndex) + { + NTSTATUS rcNt = g_pfnKeGetProcessorNumberFromIndex(pGipCpu->iCpuSet, &ProcNum); + if (NT_SUCCESS(rcNt)) + Assert(ProcNum.Group < g_pfnKeQueryMaximumGroupCount()); + else + { + AssertFailed(); + ProcNum.Group = 0; + ProcNum.Number = pGipCpu->iCpuSet; + } + } + pGipCpu->iCpuGroup = ProcNum.Group; + pGipCpu->iCpuGroupMember = ProcNum.Number; + + /* + * Update the group info. Just do this wholesale for now (doesn't scale well). + */ + for (uint32_t idxGroup = 0; idxGroup < pGip->cPossibleCpuGroups; idxGroup++) + { + uint32_t offGroup = pGip->aoffCpuGroup[idxGroup]; + if (offGroup != UINT32_MAX) + { + PSUPGIPCPUGROUP pGroup = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offGroup); + uint32_t cActive = 0; + uint32_t cMax = RTMpGetCpuGroupCounts(idxGroup, &cActive); + + AssertStmt(cMax == pGroup->cMaxMembers, cMax = pGroup->cMaxMembers); + AssertStmt(cActive <= cMax, cActive = cMax); + if (pGroup->cMembers != cActive) + ASMAtomicWriteU16(&pGroup->cMembers, cActive); + + for (uint32_t idxMember = 0; idxMember < cMax; idxMember++) + { + int idxCpuSet = RTMpSetIndexFromCpuGroupMember(idxGroup, idxMember); + AssertMsg((unsigned)idxCpuSet < pGip->cPossibleCpus, + ("%d vs %d for %u.%u\n", idxCpuSet, pGip->cPossibleCpus, idxGroup, idxMember)); + + if (pGroup->aiCpuSetIdxs[idxMember] != idxCpuSet) + ASMAtomicWriteS16(&pGroup->aiCpuSetIdxs[idxMember], idxCpuSet); + } + } + } +} + + +/** + * Initializes any OS specific object creator fields. + */ +void VBOXCALL supdrvOSObjInitCreator(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession) +{ + NOREF(pObj); + NOREF(pSession); +} + + +/** + * Checks if the session can access the object. + * + * @returns true if a decision has been made. + * @returns false if the default access policy should be applied. + * + * @param pObj The object in question. + * @param pSession The session wanting to access the object. + * @param pszObjName The object name, can be NULL. + * @param prc Where to store the result when returning true. + */ +bool VBOXCALL supdrvOSObjCanAccess(PSUPDRVOBJ pObj, PSUPDRVSESSION pSession, const char *pszObjName, int *prc) +{ + NOREF(pObj); + NOREF(pSession); + NOREF(pszObjName); + NOREF(prc); + return false; +} + + +/** + * Force async tsc mode (stub). + */ +bool VBOXCALL supdrvOSGetForcedAsyncTscMode(PSUPDRVDEVEXT pDevExt) +{ + RT_NOREF1(pDevExt); + return g_Options.fOptForceAsyncTsc != 0; +} + + +/** + * Whether the host takes CPUs offline during a suspend/resume operation. + */ +bool VBOXCALL supdrvOSAreCpusOfflinedOnSuspend(void) +{ + return false; +} + + +/** + * Whether the hardware TSC has been synchronized by the OS. + */ +bool VBOXCALL supdrvOSAreTscDeltasInSync(void) +{ + /* If IPRT didn't find KeIpiGenericCall we pretend windows(, the firmware, + or whoever) always configures TSCs perfectly. */ + return !RTMpOnPairIsConcurrentExecSupported(); +} + + +#define MY_SystemLoadGdiDriverInSystemSpaceInformation 54 +#define MY_SystemUnloadGdiDriverInformation 27 + +typedef struct MYSYSTEMGDIDRIVERINFO +{ + UNICODE_STRING Name; /**< In: image file name. */ + PVOID ImageAddress; /**< Out: the load address. */ + PVOID SectionPointer; /**< Out: section object. */ + PVOID EntryPointer; /**< Out: entry point address. */ + PVOID ExportSectionPointer; /**< Out: export directory/section. */ + ULONG ImageLength; /**< Out: SizeOfImage. */ +} MYSYSTEMGDIDRIVERINFO; + +extern "C" __declspec(dllimport) NTSTATUS NTAPI ZwSetSystemInformation(ULONG, PVOID, ULONG); + +int VBOXCALL supdrvOSLdrOpen(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + pImage->pvNtSectionObj = NULL; + pImage->hMemLock = NIL_RTR0MEMOBJ; + +#ifdef VBOX_WITHOUT_NATIVE_R0_LOADER +# ifndef RT_ARCH_X86 +# error "VBOX_WITHOUT_NATIVE_R0_LOADER is only safe on x86." +# endif + NOREF(pDevExt); NOREF(pszFilename); NOREF(pImage); + return VERR_NOT_SUPPORTED; + +#else + /* + * Convert the filename from DOS UTF-8 to NT UTF-16. + */ + size_t cwcFilename; + int rc = RTStrCalcUtf16LenEx(pszFilename, RTSTR_MAX, &cwcFilename); + if (RT_FAILURE(rc)) + return rc; + + PRTUTF16 pwcsFilename = (PRTUTF16)RTMemTmpAlloc((4 + cwcFilename + 1) * sizeof(RTUTF16)); + if (!pwcsFilename) + return VERR_NO_TMP_MEMORY; + + pwcsFilename[0] = '\\'; + pwcsFilename[1] = '?'; + pwcsFilename[2] = '?'; + pwcsFilename[3] = '\\'; + PRTUTF16 pwcsTmp = &pwcsFilename[4]; + rc = RTStrToUtf16Ex(pszFilename, RTSTR_MAX, &pwcsTmp, cwcFilename + 1, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Try load it. + */ + MYSYSTEMGDIDRIVERINFO Info; + RtlInitUnicodeString(&Info.Name, pwcsFilename); + Info.ImageAddress = NULL; + Info.SectionPointer = NULL; + Info.EntryPointer = NULL; + Info.ExportSectionPointer = NULL; + Info.ImageLength = 0; + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemLoadGdiDriverInSystemSpaceInformation, &Info, sizeof(Info)); + if (NT_SUCCESS(rcNt)) + { + pImage->pvImage = Info.ImageAddress; + pImage->pvNtSectionObj = Info.SectionPointer; + Log(("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer)); +# ifdef DEBUG_bird + SUPR0Printf("ImageAddress=%p SectionPointer=%p ImageLength=%#x cbImageBits=%#x rcNt=%#x '%ls'\n", + Info.ImageAddress, Info.SectionPointer, Info.ImageLength, pImage->cbImageBits, rcNt, Info.Name.Buffer); +# endif + if (pImage->cbImageBits == Info.ImageLength) + { + /* + * Lock down the entire image, just to be on the safe side. + */ + rc = RTR0MemObjLockKernel(&pImage->hMemLock, pImage->pvImage, pImage->cbImageBits, RTMEM_PROT_READ); + if (RT_FAILURE(rc)) + { + pImage->hMemLock = NIL_RTR0MEMOBJ; + supdrvOSLdrUnload(pDevExt, pImage); + } + } + else + { + supdrvOSLdrUnload(pDevExt, pImage); + rc = VERR_LDR_MISMATCH_NATIVE; + } + } + else + { + Log(("rcNt=%#x '%ls'\n", rcNt, pwcsFilename)); + SUPR0Printf("VBoxDrv: rcNt=%x '%ws'\n", rcNt, pwcsFilename); + switch (rcNt) + { + case /* 0xc0000003 */ STATUS_INVALID_INFO_CLASS: +# ifdef RT_ARCH_AMD64 + /* Unwind will crash and BSOD, so no fallback here! */ + rc = VERR_NOT_IMPLEMENTED; +# else + /* + * Use the old way of loading the modules. + * + * Note! We do *NOT* try class 26 because it will probably + * not work correctly on terminal servers and such. + */ + rc = VERR_NOT_SUPPORTED; +# endif + break; + case /* 0xc0000034 */ STATUS_OBJECT_NAME_NOT_FOUND: + rc = VERR_MODULE_NOT_FOUND; + break; + case /* 0xC0000263 */ STATUS_DRIVER_ENTRYPOINT_NOT_FOUND: + rc = VERR_LDR_IMPORTED_SYMBOL_NOT_FOUND; + break; + case /* 0xC0000428 */ STATUS_INVALID_IMAGE_HASH: + rc = VERR_LDR_IMAGE_HASH; + break; + case /* 0xC000010E */ STATUS_IMAGE_ALREADY_LOADED: + Log(("WARNING: see @bugref{4853} for cause of this failure on Windows 7 x64\n")); + rc = VERR_ALREADY_LOADED; + break; + default: + rc = VERR_LDR_GENERAL_FAILURE; + break; + } + + pImage->pvNtSectionObj = NULL; + } + } + + RTMemTmpFree(pwcsFilename); + NOREF(pDevExt); + return rc; +#endif +} + + +void VBOXCALL supdrvOSLdrNotifyOpened(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const char *pszFilename) +{ + NOREF(pDevExt); NOREF(pImage); NOREF(pszFilename); +} + + +void VBOXCALL supdrvOSLdrNotifyUnloaded(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + NOREF(pDevExt); NOREF(pImage); +} + + +/** + * Common worker for supdrvOSLdrQuerySymbol and supdrvOSLdrValidatePointer. + * + * @note Similar code in rtR0DbgKrnlNtParseModule. + */ +static int supdrvOSLdrValidatePointerOrQuerySymbol(PSUPDRVLDRIMAGE pImage, void *pv, const char *pszSymbol, + size_t cchSymbol, void **ppvSymbol) +{ + AssertReturn(pImage->pvNtSectionObj, VERR_INVALID_STATE); + Assert(pszSymbol || !ppvSymbol); + + /* + * Locate the export directory in the loaded image. + */ + uint8_t const *pbMapping = (uint8_t const *)pImage->pvImage; + uint32_t const cbMapping = pImage->cbImageBits; + uint32_t const uRvaToValidate = (uint32_t)((uintptr_t)pv - (uintptr_t)pbMapping); + AssertReturn(uRvaToValidate < cbMapping || ppvSymbol, VERR_INTERNAL_ERROR_3); + + uint32_t const offNtHdrs = *(uint16_t *)pbMapping == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbMapping)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < cbMapping, VERR_INTERNAL_ERROR_5); + + IMAGE_NT_HEADERS const *pNtHdrs = (IMAGE_NT_HEADERS const *)((uintptr_t)pbMapping + offNtHdrs); + AssertLogRelReturn(pNtHdrs->Signature == IMAGE_NT_SIGNATURE, VERR_INVALID_EXE_SIGNATURE); + AssertLogRelReturn(pNtHdrs->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, VERR_BAD_EXE_FORMAT); + AssertLogRelReturn(pNtHdrs->OptionalHeader.NumberOfRvaAndSizes == IMAGE_NUMBEROF_DIRECTORY_ENTRIES, VERR_BAD_EXE_FORMAT); + + uint32_t const offEndSectHdrs = offNtHdrs + + sizeof(*pNtHdrs) + + pNtHdrs->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); + AssertReturn(offEndSectHdrs < cbMapping, VERR_BAD_EXE_FORMAT); + + /* + * Find the export directory. + */ + IMAGE_DATA_DIRECTORY ExpDir = pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + if (!ExpDir.Size) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + AssertReturn( ExpDir.Size >= sizeof(IMAGE_EXPORT_DIRECTORY) + && ExpDir.VirtualAddress >= offEndSectHdrs + && ExpDir.VirtualAddress < cbMapping + && ExpDir.VirtualAddress + ExpDir.Size <= cbMapping, VERR_BAD_EXE_FORMAT); + + IMAGE_EXPORT_DIRECTORY const *pExpDir = (IMAGE_EXPORT_DIRECTORY const *)&pbMapping[ExpDir.VirtualAddress]; + + uint32_t const cNamedExports = pExpDir->NumberOfNames; + AssertReturn(cNamedExports < _1M, VERR_BAD_EXE_FORMAT); + AssertReturn(pExpDir->NumberOfFunctions < _1M, VERR_BAD_EXE_FORMAT); + if (pExpDir->NumberOfFunctions == 0 || cNamedExports == 0) + { + SUPR0Printf("SUPDrv: No exports in %s!\n", pImage->szName); + return ppvSymbol ? VERR_SYMBOL_NOT_FOUND : VERR_NOT_FOUND; + } + + uint32_t const cExports = RT_MAX(cNamedExports, pExpDir->NumberOfFunctions); + + AssertReturn( pExpDir->AddressOfFunctions >= offEndSectHdrs + && pExpDir->AddressOfFunctions < cbMapping + && pExpDir->AddressOfFunctions + cExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfFunctions]; + + AssertReturn( pExpDir->AddressOfNames >= offEndSectHdrs + && pExpDir->AddressOfNames < cbMapping + && pExpDir->AddressOfNames + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint32_t const * const paoffNamedExports = (uint32_t const *)&pbMapping[pExpDir->AddressOfNames]; + + AssertReturn( pExpDir->AddressOfNameOrdinals >= offEndSectHdrs + && pExpDir->AddressOfNameOrdinals < cbMapping + && pExpDir->AddressOfNameOrdinals + cNamedExports * sizeof(uint32_t) <= cbMapping, + VERR_BAD_EXE_FORMAT); + uint16_t const * const pau16NameOrdinals = (uint16_t const *)&pbMapping[pExpDir->AddressOfNameOrdinals]; + + /* + * Validate the entrypoint RVA by scanning the export table. + */ + uint32_t iExportOrdinal = UINT32_MAX; + if (!ppvSymbol) + { + for (uint32_t i = 0; i < cExports; i++) + if (paoffExports[i] == uRvaToValidate) + { + iExportOrdinal = i; + break; + } + if (iExportOrdinal == UINT32_MAX) + { + SUPR0Printf("SUPDrv: No export with rva %#x (%s) in %s!\n", uRvaToValidate, pszSymbol, pImage->szName); + return VERR_NOT_FOUND; + } + } + + /* + * Can we validate the symbol name too or should we find a name? + * If so, just do a linear search. + */ + if (pszSymbol && (RT_C_IS_UPPER(*pszSymbol) || ppvSymbol)) + { + for (uint32_t i = 0; i < cNamedExports; i++) + { + uint32_t const offName = paoffNamedExports[i]; + AssertReturn(offName < cbMapping, VERR_BAD_EXE_FORMAT); + uint32_t const cchMaxName = cbMapping - offName; + const char * const pszName = (const char *)&pbMapping[offName]; + const char * const pszEnd = (const char *)memchr(pszName, '\0', cchMaxName); + AssertReturn(pszEnd, VERR_BAD_EXE_FORMAT); + + if ( cchSymbol == (size_t)(pszEnd - pszName) + && memcmp(pszName, pszSymbol, cchSymbol) == 0) + { + if (ppvSymbol) + { + iExportOrdinal = pau16NameOrdinals[i]; + if ( iExportOrdinal < cExports + && paoffExports[iExportOrdinal] < cbMapping) + { + *ppvSymbol = (void *)(paoffExports[iExportOrdinal] + pbMapping); + return VINF_SUCCESS; + } + } + else if (pau16NameOrdinals[i] == iExportOrdinal) + return VINF_SUCCESS; + else + SUPR0Printf("SUPDrv: Different exports found for %s and rva %#x in %s: %#x vs %#x\n", + pszSymbol, uRvaToValidate, pImage->szName, pau16NameOrdinals[i], iExportOrdinal); + return VERR_LDR_BAD_FIXUP; + } + } + if (!ppvSymbol) + SUPR0Printf("SUPDrv: No export named %s (%#x) in %s!\n", pszSymbol, uRvaToValidate, pImage->szName); + return VERR_SYMBOL_NOT_FOUND; + } + return VINF_SUCCESS; +} + + +int VBOXCALL supdrvOSLdrValidatePointer(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, void *pv, + const uint8_t *pbImageBits, const char *pszSymbol) +{ + RT_NOREF(pDevExt, pbImageBits); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, pv, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0, NULL); +} + + +int VBOXCALL supdrvOSLdrQuerySymbol(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, + const char *pszSymbol, size_t cchSymbol, void **ppvSymbol) +{ + RT_NOREF(pDevExt); + AssertReturn(ppvSymbol, VERR_INVALID_PARAMETER); + AssertReturn(pszSymbol, VERR_INVALID_PARAMETER); + return supdrvOSLdrValidatePointerOrQuerySymbol(pImage, NULL, pszSymbol, cchSymbol, ppvSymbol); +} + + +/** + * memcmp + errormsg + log. + * + * @returns Same as memcmp. + * @param pImage The image. + * @param pbImageBits The image bits ring-3 uploads. + * @param uRva The RVA to start comparing at. + * @param cb The number of bytes to compare. + * @param pReq The load request. + */ +static int supdrvNtCompare(PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, uint32_t uRva, uint32_t cb, PSUPLDRLOAD pReq) +{ + int iDiff = memcmp((uint8_t const *)pImage->pvImage + uRva, pbImageBits + uRva, cb); + if (iDiff) + { + uint32_t cbLeft = cb; + const uint8_t *pbNativeBits = (const uint8_t *)pImage->pvImage; + for (size_t off = uRva; cbLeft > 0; off++, cbLeft--) + if (pbNativeBits[off] != pbImageBits[off]) + { + /* Note! We need to copy image bits into a temporary stack buffer here as we'd + otherwise risk overwriting them while formatting the error message. */ + uint8_t abBytes[64]; + memcpy(abBytes, &pbImageBits[off], RT_MIN(64, cbLeft)); + supdrvLdrLoadError(VERR_LDR_MISMATCH_NATIVE, pReq, + "Mismatch at %#x (%p) of %s loaded at %p:\n" + "ntld: %.*Rhxs\n" + "iprt: %.*Rhxs", + off, &pbNativeBits[off], pImage->szName, pImage->pvImage, + RT_MIN(64, cbLeft), &pbNativeBits[off], + RT_MIN(64, cbLeft), &abBytes[0]); + SUPR0Printf("VBoxDrv: %s", pReq->u.Out.szError); + break; + } + } + return iDiff; +} + +/** Image compare exclusion regions. */ +typedef struct SUPDRVNTEXCLREGIONS +{ + /** Number of regions. */ + uint32_t cRegions; + /** The regions. */ + struct SUPDRVNTEXCLREGION + { + uint32_t uRva; + uint32_t cb; + } aRegions[20]; +} SUPDRVNTEXCLREGIONS; + +/** + * Adds an exclusion region to the collection. + */ +static bool supdrvNtAddExclRegion(SUPDRVNTEXCLREGIONS *pRegions, uint32_t uRvaRegion, uint32_t cbRegion) +{ + uint32_t const cRegions = pRegions->cRegions; + AssertReturn(cRegions + 1 <= RT_ELEMENTS(pRegions->aRegions), false); + uint32_t i = 0; + for (; i < cRegions; i++) + if (uRvaRegion < pRegions->aRegions[i].uRva) + break; + if (i != cRegions) + memmove(&pRegions->aRegions[i + 1], &pRegions->aRegions[i], (cRegions - i) * sizeof(pRegions->aRegions[0])); + pRegions->aRegions[i].uRva = uRvaRegion; + pRegions->aRegions[i].cb = cbRegion; + pRegions->cRegions++; + return true; +} + + +int VBOXCALL supdrvOSLdrLoad(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage, const uint8_t *pbImageBits, PSUPLDRLOAD pReq) +{ + NOREF(pDevExt); + if (pImage->pvNtSectionObj) + { + /* + * Usually, the entire image matches exactly. + */ + if (!memcmp(pImage->pvImage, pbImageBits, pImage->cbImageBits)) + return VINF_SUCCESS; + + /* + * On Windows 10 the ImageBase member of the optional header is sometimes + * updated with the actual load address and sometimes not. + * On older windows versions (builds <= 9200?), a user mode address is + * sometimes found in the image base field after upgrading to VC++ 14.2. + */ + uint32_t const offNtHdrs = *(uint16_t *)pbImageBits == IMAGE_DOS_SIGNATURE + ? ((IMAGE_DOS_HEADER const *)pbImageBits)->e_lfanew + : 0; + AssertLogRelReturn(offNtHdrs + sizeof(IMAGE_NT_HEADERS) < pImage->cbImageBits, VERR_INTERNAL_ERROR_5); + IMAGE_NT_HEADERS const *pNtHdrsIprt = (IMAGE_NT_HEADERS const *)(pbImageBits + offNtHdrs); + IMAGE_NT_HEADERS const *pNtHdrsNtLd = (IMAGE_NT_HEADERS const *)((uintptr_t)pImage->pvImage + offNtHdrs); + + uint32_t const offImageBase = offNtHdrs + RT_UOFFSETOF(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + uint32_t const cbImageBase = RT_SIZEOFMEMB(IMAGE_NT_HEADERS, OptionalHeader.ImageBase); + if ( pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase + && pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && !memcmp(pImage->pvImage, pbImageBits, offImageBase) + && !memcmp((uint8_t const *)pImage->pvImage + offImageBase + cbImageBase, + pbImageBits + offImageBase + cbImageBase, + pImage->cbImageBits - offImageBase - cbImageBase)) + return VINF_SUCCESS; + + /* + * On Windows Server 2003 (sp2 x86) both import thunk tables are fixed + * up and we typically get a mismatch in the INIT section. + * + * So, lets see if everything matches when excluding the + * OriginalFirstThunk tables and (maybe) the ImageBase member. + * For simplicity the max number of exclusion regions is set to 16. + */ + if ( pNtHdrsIprt->Signature == IMAGE_NT_SIGNATURE + && pNtHdrsIprt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR_MAGIC + && pNtHdrsIprt->OptionalHeader.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size >= sizeof(IMAGE_IMPORT_DESCRIPTOR) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress < pImage->cbImageBits + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size >= sizeof(IMAGE_LOAD_CONFIG_DIRECTORY) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress > sizeof(IMAGE_NT_HEADERS) + && pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress < pImage->cbImageBits) + { + SUPDRVNTEXCLREGIONS ExcludeRegions; + ExcludeRegions.cRegions = 0; + + /* ImageBase: */ + if (pNtHdrsNtLd->OptionalHeader.ImageBase != pNtHdrsIprt->OptionalHeader.ImageBase) + supdrvNtAddExclRegion(&ExcludeRegions, offImageBase, cbImageBase); + + /* Imports: */ + uint32_t cImpsLeft = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + / sizeof(IMAGE_IMPORT_DESCRIPTOR); + uint32_t offImps = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + AssertLogRelReturn(offImps + cImpsLeft * sizeof(IMAGE_IMPORT_DESCRIPTOR) <= pImage->cbImageBits, VERR_INTERNAL_ERROR_3); + IMAGE_IMPORT_DESCRIPTOR const *pImp = (IMAGE_IMPORT_DESCRIPTOR const *)(pbImageBits + offImps); + while ( cImpsLeft-- > 0 + && ExcludeRegions.cRegions < RT_ELEMENTS(ExcludeRegions.aRegions)) + { + uint32_t uRvaThunk = pImp->OriginalFirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA) + && uRvaThunk != pImp->FirstThunk) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + +#if 0 /* Useful for VMMR0 hacking, not for production use. See also SUPDrvLdr.cpp. */ + /* Exclude the other thunk table if ntoskrnl.exe. */ + uint32_t uRvaName = pImp->Name; + if ( uRvaName > sizeof(IMAGE_NT_HEADERS) + && uRvaName < pImage->cbImageBits - sizeof("ntoskrnl.exe") + && memcmp(&pbImageBits[uRvaName], RT_STR_TUPLE("ntoskrnl.exe")) == 0) + { + uRvaThunk = pImp->FirstThunk; + if ( uRvaThunk > sizeof(IMAGE_NT_HEADERS) + && uRvaThunk <= pImage->cbImageBits - sizeof(IMAGE_THUNK_DATA)) + { + /* Find the size of the thunk table. */ + IMAGE_THUNK_DATA const *paThunk = (IMAGE_THUNK_DATA const *)(pbImageBits + uRvaThunk); + uint32_t cMaxThunks = (pImage->cbImageBits - uRvaThunk) / sizeof(IMAGE_THUNK_DATA); + uint32_t cThunks = 0; + while (cThunks < cMaxThunks && paThunk[cThunks].u1.Function != 0) + cThunks++; + supdrvNtAddExclRegion(&ExcludeRegions, uRvaThunk, cThunks * sizeof(IMAGE_THUNK_DATA)); + } + } +#endif + + /* advance */ + pImp++; + } + + /* Exclude the security cookie if present. */ + uint32_t const cbCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].Size; + uint32_t const offCfg = pNtHdrsIprt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress; + IMAGE_LOAD_CONFIG_DIRECTORY const * const pCfg = (IMAGE_LOAD_CONFIG_DIRECTORY const *)&pbImageBits[offCfg]; + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie) + && pCfg->SecurityCookie != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->SecurityCookie - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Also exclude the GuardCFCheckFunctionPointer and GuardCFDispatchFunctionPointer pointer variables. */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFCheckFunctionPointer) + && pCfg->GuardCFCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardCFDispatchFunctionPointer) + && pCfg->GuardCFDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardCFDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /* Ditto for the XFG variants: */ + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGCheckFunctionPointer) + && pCfg->GuardXFGCheckFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGCheckFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + if ( pCfg->Size >= RT_UOFFSET_AFTER(IMAGE_LOAD_CONFIG_DIRECTORY, GuardXFGDispatchFunctionPointer) + && pCfg->GuardXFGDispatchFunctionPointer != NULL) + supdrvNtAddExclRegion(&ExcludeRegions, (uintptr_t)pCfg->GuardXFGDispatchFunctionPointer - (uintptr_t)pImage->pvImage, sizeof(void *)); + + /** @todo What about GuardRFVerifyStackPointerFunctionPointer and + * GuardRFFailureRoutineFunctionPointer? Ignore for now as the compiler we're + * using (19.26.28805) sets them to zero from what I can tell. */ + + /* + * Ok, do the comparison. + */ + int iDiff = 0; + uint32_t uRvaNext = 0; + for (unsigned i = 0; !iDiff && i < ExcludeRegions.cRegions; i++) + { + if (uRvaNext < ExcludeRegions.aRegions[i].uRva) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, ExcludeRegions.aRegions[i].uRva - uRvaNext, pReq); + uRvaNext = ExcludeRegions.aRegions[i].uRva + ExcludeRegions.aRegions[i].cb; + } + if (!iDiff && uRvaNext < pImage->cbImageBits) + iDiff = supdrvNtCompare(pImage, pbImageBits, uRvaNext, pImage->cbImageBits - uRvaNext, pReq); + if (!iDiff) + { + /* + * If there is a cookie init export, call it. + * + * This typically just does: + * __security_cookie = (rdtsc ^ &__security_cookie) & 0xffffffffffff; + * __security_cookie_complement = ~__security_cookie; + */ + PFNRT pfnModuleInitSecurityCookie = NULL; + int rcSym = supdrvOSLdrQuerySymbol(pDevExt, pImage, RT_STR_TUPLE("ModuleInitSecurityCookie"), + (void **)&pfnModuleInitSecurityCookie); + if (RT_SUCCESS(rcSym) && pfnModuleInitSecurityCookie) + pfnModuleInitSecurityCookie(); + + return VINF_SUCCESS; + } + } + else + supdrvNtCompare(pImage, pbImageBits, 0, pImage->cbImageBits, pReq); + return VERR_LDR_MISMATCH_NATIVE; + } + return supdrvLdrLoadError(VERR_INTERNAL_ERROR_4, pReq, "No NT section object! Impossible!"); +} + + +void VBOXCALL supdrvOSLdrUnload(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + if (pImage->pvNtSectionObj) + { + if (pImage->hMemLock != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pImage->hMemLock, false /*fFreeMappings*/); + pImage->hMemLock = NIL_RTR0MEMOBJ; + } + + NTSTATUS rcNt = ZwSetSystemInformation(MY_SystemUnloadGdiDriverInformation, + &pImage->pvNtSectionObj, sizeof(pImage->pvNtSectionObj)); + if (rcNt != STATUS_SUCCESS) + SUPR0Printf("VBoxDrv: failed to unload '%s', rcNt=%#x\n", pImage->szName, rcNt); + pImage->pvNtSectionObj = NULL; + } + NOREF(pDevExt); +} + + +void VBOXCALL supdrvOSLdrRetainWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +void VBOXCALL supdrvOSLdrReleaseWrapperModule(PSUPDRVDEVEXT pDevExt, PSUPDRVLDRIMAGE pImage) +{ + RT_NOREF(pDevExt, pImage); + AssertFailed(); +} + + +#ifdef SUPDRV_WITH_MSR_PROBER + +#if 1 +/** @todo make this selectable. */ +# define AMD_MSR_PASSCODE 0x9c5a203a +#else +# define ASMRdMsrEx(a, b, c) ASMRdMsr(a) +# define ASMWrMsrEx(a, b, c) ASMWrMsr(a,c) +#endif + + +/** + * Argument package used by supdrvOSMsrProberRead and supdrvOSMsrProberWrite. + */ +typedef struct SUPDRVNTMSPROBERARGS +{ + uint32_t uMsr; + uint64_t uValue; + bool fGp; +} SUPDRVNTMSPROBERARGS; + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberRead.} */ +static DECLCALLBACK(void) supdrvNtMsProberReadOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + pArgs->uValue = ASMRdMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + pArgs->uValue = 0; + } + ASMSetFlags(fOldFlags); +} + + +int VBOXCALL supdrvOSMsrProberRead(uint32_t uMsr, RTCPUID idCpu, uint64_t *puValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = 0; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberReadOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberReadOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + *puValue = Args.uValue; + return VINF_SUCCESS; +} + + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberWrite.} */ +static DECLCALLBACK(void) supdrvNtMsProberWriteOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + /* + * rdmsr and wrmsr faults can be caught even with interrupts disabled. + * (At least on 32-bit XP.) + */ + SUPDRVNTMSPROBERARGS *pArgs = (SUPDRVNTMSPROBERARGS *)pvUser1; NOREF(idCpu); NOREF(pvUser2); + RTCCUINTREG fOldFlags = ASMIntDisableFlags(); + __try + { + ASMWrMsrEx(pArgs->uMsr, AMD_MSR_PASSCODE, pArgs->uValue); + pArgs->fGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + pArgs->fGp = true; + } + ASMSetFlags(fOldFlags); +} + +int VBOXCALL supdrvOSMsrProberWrite(uint32_t uMsr, RTCPUID idCpu, uint64_t uValue) +{ + SUPDRVNTMSPROBERARGS Args; + Args.uMsr = uMsr; + Args.uValue = uValue; + Args.fGp = true; + + if (idCpu == NIL_RTCPUID) + supdrvNtMsProberWriteOnCpu(idCpu, &Args, NULL); + else + { + int rc = RTMpOnSpecific(idCpu, supdrvNtMsProberWriteOnCpu, &Args, NULL); + if (RT_FAILURE(rc)) + return rc; + } + + if (Args.fGp) + return VERR_ACCESS_DENIED; + return VINF_SUCCESS; +} + +/** @callback_method_impl{FNRTMPWORKER, Worker for supdrvOSMsrProberModify.} */ +static DECLCALLBACK(void) supdrvNtMsProberModifyOnCpu(RTCPUID idCpu, void *pvUser1, void *pvUser2) +{ + PSUPMSRPROBER pReq = (PSUPMSRPROBER)pvUser1; + register uint32_t uMsr = pReq->u.In.uMsr; + bool const fFaster = pReq->u.In.enmOp == SUPMSRPROBEROP_MODIFY_FASTER; + uint64_t uBefore = 0; + uint64_t uWritten = 0; + uint64_t uAfter = 0; + bool fBeforeGp = true; + bool fModifyGp = true; + bool fAfterGp = true; + bool fRestoreGp = true; + RTCCUINTREG fOldFlags; + RT_NOREF2(idCpu, pvUser2); + + /* + * Do the job. + */ + fOldFlags = ASMIntDisableFlags(); + ASMCompilerBarrier(); /* paranoia */ + if (!fFaster) + ASMWriteBackAndInvalidateCaches(); + + __try + { + uBefore = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fBeforeGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fBeforeGp = true; + } + if (!fBeforeGp) + { + register uint64_t uRestore = uBefore; + + /* Modify. */ + uWritten = uRestore; + uWritten &= pReq->u.In.uArgs.Modify.fAndMask; + uWritten |= pReq->u.In.uArgs.Modify.fOrMask; + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uWritten); + fModifyGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fModifyGp = true; + } + + /* Read modified value. */ + __try + { + uAfter = ASMRdMsrEx(uMsr, AMD_MSR_PASSCODE); + fAfterGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fAfterGp = true; + } + + /* Restore original value. */ + __try + { + ASMWrMsrEx(uMsr, AMD_MSR_PASSCODE, uRestore); + fRestoreGp = false; + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fRestoreGp = true; + } + + /* Invalid everything we can. */ + if (!fFaster) + { + ASMWriteBackAndInvalidateCaches(); + ASMReloadCR3(); + ASMNopPause(); + } + } + + ASMCompilerBarrier(); /* paranoia */ + ASMSetFlags(fOldFlags); + + /* + * Write out the results. + */ + pReq->u.Out.uResults.Modify.uBefore = uBefore; + pReq->u.Out.uResults.Modify.uWritten = uWritten; + pReq->u.Out.uResults.Modify.uAfter = uAfter; + pReq->u.Out.uResults.Modify.fBeforeGp = fBeforeGp; + pReq->u.Out.uResults.Modify.fModifyGp = fModifyGp; + pReq->u.Out.uResults.Modify.fAfterGp = fAfterGp; + pReq->u.Out.uResults.Modify.fRestoreGp = fRestoreGp; + RT_ZERO(pReq->u.Out.uResults.Modify.afReserved); +} + + +int VBOXCALL supdrvOSMsrProberModify(RTCPUID idCpu, PSUPMSRPROBER pReq) +{ + if (idCpu == NIL_RTCPUID) + { + supdrvNtMsProberModifyOnCpu(idCpu, pReq, NULL); + return VINF_SUCCESS; + } + return RTMpOnSpecific(idCpu, supdrvNtMsProberModifyOnCpu, pReq, NULL); +} + +#endif /* SUPDRV_WITH_MSR_PROBER */ + + +/** + * Converts an IPRT error code to an nt status code. + * + * @returns corresponding nt status code. + * @param rc IPRT error status code. + */ +static NTSTATUS VBoxDrvNtErr2NtStatus(int rc) +{ + switch (rc) + { + case VINF_SUCCESS: return STATUS_SUCCESS; + case VERR_GENERAL_FAILURE: return STATUS_NOT_SUPPORTED; + case VERR_INVALID_PARAMETER: return STATUS_INVALID_PARAMETER; + case VERR_INVALID_MAGIC: return STATUS_UNKNOWN_REVISION; + case VERR_INVALID_HANDLE: return STATUS_INVALID_HANDLE; + case VERR_INVALID_POINTER: return STATUS_INVALID_ADDRESS; + case VERR_LOCK_FAILED: return STATUS_NOT_LOCKED; + case VERR_ALREADY_LOADED: return STATUS_IMAGE_ALREADY_LOADED; + case VERR_PERMISSION_DENIED: return STATUS_ACCESS_DENIED; + case VERR_VERSION_MISMATCH: return STATUS_REVISION_MISMATCH; + } + + if (rc < 0) + { + if (((uint32_t)rc & UINT32_C(0xffff0000)) == UINT32_C(0xffff0000)) + return (NTSTATUS)( ((uint32_t)rc & UINT32_C(0xffff)) | SUP_NT_STATUS_BASE ); + } + return STATUS_UNSUCCESSFUL; +} + + +SUPR0DECL(int) SUPR0PrintfV(const char *pszFormat, va_list va) +{ + char szMsg[384]; + size_t cch = RTStrPrintfV(szMsg, sizeof(szMsg) - 1, pszFormat, va); + szMsg[sizeof(szMsg) - 1] = '\0'; + + RTLogWriteDebugger(szMsg, cch); + return 0; +} + + +SUPR0DECL(uint32_t) SUPR0GetKernelFeatures(void) +{ + return 0; +} + + +SUPR0DECL(bool) SUPR0FpuBegin(bool fCtxHook) +{ + RT_NOREF(fCtxHook); + return false; +} + + +SUPR0DECL(void) SUPR0FpuEnd(bool fCtxHook) +{ + RT_NOREF(fCtxHook); +} + + +SUPR0DECL(int) SUPR0IoCtlSetupForHandle(PSUPDRVSESSION pSession, intptr_t hHandle, uint32_t fFlags, PSUPR0IOCTLCTX *ppCtx) +{ + /* + * Validate input. + */ + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; + AssertReturn(SUP_IS_SESSION_VALID(pSession), VERR_INVALID_PARAMETER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + + /* + * Turn the partition handle into a file object and related device object + * so that we can issue direct I/O control calls to the pair later. + */ + PFILE_OBJECT pFileObject = NULL; + OBJECT_HANDLE_INFORMATION HandleInfo = { 0, 0 }; + NTSTATUS rcNt = ObReferenceObjectByHandle((HANDLE)hHandle, /*FILE_WRITE_DATA*/0, *IoFileObjectType, + UserMode, (void **)&pFileObject, &HandleInfo); + if (!NT_SUCCESS(rcNt)) + return RTErrConvertFromNtStatus(rcNt); + AssertPtrReturn(pFileObject, VERR_INTERNAL_ERROR_3); + + PDEVICE_OBJECT pDevObject = IoGetRelatedDeviceObject(pFileObject); + AssertMsgReturnStmt(RT_VALID_PTR(pDevObject), ("pDevObject=%p\n", pDevObject), + ObDereferenceObject(pFileObject), VERR_INTERNAL_ERROR_2); + + /* + * Allocate a context structure and fill it in. + */ + PSUPR0IOCTLCTX pCtx = (PSUPR0IOCTLCTX)RTMemAllocZ(sizeof(*pCtx)); + if (pCtx) + { + pCtx->u32Magic = SUPR0IOCTLCTX_MAGIC; + pCtx->cRefs = 1; + pCtx->pFileObject = pFileObject; + pCtx->pDeviceObject = pDevObject; + + PDRIVER_OBJECT pDrvObject = pDevObject->DriverObject; + if ( RT_VALID_PTR(pDrvObject->FastIoDispatch) + && RT_VALID_PTR(pDrvObject->FastIoDispatch->FastIoDeviceControl)) + pCtx->pfnFastIoDeviceControl = pDrvObject->FastIoDispatch->FastIoDeviceControl; + else + pCtx->pfnFastIoDeviceControl = NULL; + *ppCtx = pCtx; + return VINF_SUCCESS; + } + + ObDereferenceObject(pFileObject); + return VERR_NO_MEMORY; +} + + +/** + * I/O control destructor for NT. + * + * @param pCtx The context to destroy. + */ +static void supdrvNtIoCtlContextDestroy(PSUPR0IOCTLCTX pCtx) +{ + PFILE_OBJECT pFileObject = pCtx->pFileObject; + pCtx->pfnFastIoDeviceControl = NULL; + pCtx->pFileObject = NULL; + pCtx->pDeviceObject = NULL; + ASMAtomicWriteU32(&pCtx->u32Magic, ~SUPR0IOCTLCTX_MAGIC); + + if (RT_VALID_PTR(pFileObject)) + ObDereferenceObject(pFileObject); + RTMemFree(pCtx); +} + + +SUPR0DECL(int) SUPR0IoCtlCleanup(PSUPR0IOCTLCTX pCtx) +{ + if (pCtx != NULL) + { + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + uint32_t cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + } + return VINF_SUCCESS; +} + + +SUPR0DECL(int) SUPR0IoCtlPerform(PSUPR0IOCTLCTX pCtx, uintptr_t uFunction, + void *pvInput, RTR3PTR pvInputUser, size_t cbInput, + void *pvOutput, RTR3PTR pvOutputUser, size_t cbOutput, + int32_t *piNativeRc) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->u32Magic == SUPR0IOCTLCTX_MAGIC, VERR_INVALID_PARAMETER); + + /* Reference the context. */ + uint32_t cRefs = ASMAtomicIncU32(&pCtx->cRefs); + Assert(cRefs > 1 && cRefs < _4K); + + /* + * Try fast I/O control path first. + */ + IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER; + if (pCtx->pfnFastIoDeviceControl) + { + /* Must pass user addresses here as that's what's being expected. */ + BOOLEAN fHandled = pCtx->pfnFastIoDeviceControl(pCtx->pFileObject, + TRUE /*Wait*/, + (void *)pvInputUser, (ULONG)cbInput, + (void *)pvOutputUser, (ULONG)cbOutput, + uFunction, + &Ios, + pCtx->pDeviceObject); + if (fHandled) + { + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + /* Set/convert status and return. */ + if (piNativeRc) + { + *piNativeRc = Ios.Status; + return VINF_SUCCESS; + } + if (NT_SUCCESS(Ios.Status)) + return VINF_SUCCESS; + return RTErrConvertFromNtStatus(Ios.Status); + } + + /* + * Fall back on IRP if not handled. + * + * Note! Perhaps we should rather fail, because VID.SYS will crash getting + * the partition ID with the code below. It tries to zero the output + * buffer as if it were as system buffer... + */ + RTNT_IO_STATUS_BLOCK_REINIT(&Ios); + } + + /* + * For directly accessed buffers we must supply user mode addresses or + * we'll fail ProbeForWrite validation. + */ + switch (uFunction & 3) + { + case METHOD_BUFFERED: + /* For buffered accesses, we can supply kernel buffers. */ + break; + + case METHOD_IN_DIRECT: + pvInput = (void *)pvInputUser; + break; + + case METHOD_NEITHER: + pvInput = (void *)pvInputUser; + RT_FALL_THRU(); + + case METHOD_OUT_DIRECT: + pvOutput = (void *)pvOutputUser; + break; + } + + /* + * Build the request. + */ + int rc; + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + PIRP pIrp = IoBuildDeviceIoControlRequest(uFunction, pCtx->pDeviceObject, + pvInput, (ULONG)cbInput, pvOutput, (ULONG)cbOutput, + FALSE /* InternalDeviceControl */, &Event, &Ios); + if (pIrp) + { + IoGetNextIrpStackLocation(pIrp)->FileObject = pCtx->pFileObject; + + /* + * Make the call. + */ + NTSTATUS rcNt = IoCallDriver(pCtx->pDeviceObject, pIrp); + if (rcNt == STATUS_PENDING) + { + rcNt = KeWaitForSingleObject(&Event, /* Object */ + Executive, /* WaitReason */ + KernelMode, /* WaitMode */ + FALSE, /* Alertable */ + NULL); /* TimeOut */ + AssertMsg(rcNt == STATUS_SUCCESS, ("rcNt=%#x\n", rcNt)); + rcNt = Ios.Status; + } + else if (NT_SUCCESS(rcNt) && Ios.Status != STATUS_SUCCESS) + rcNt = Ios.Status; + + /* Set/convert return code. */ + if (piNativeRc) + { + *piNativeRc = rcNt; + rc = VINF_SUCCESS; + } + else if (NT_SUCCESS(rcNt)) + rc = VINF_SUCCESS; + else + rc = RTErrConvertFromNtStatus(rcNt); + } + else + { + if (piNativeRc) + *piNativeRc = STATUS_NO_MEMORY; + rc = VERR_NO_MEMORY; + } + + /* Relase the context. */ + cRefs = ASMAtomicDecU32(&pCtx->cRefs); + Assert(cRefs < _4K); + if (cRefs == 0) + supdrvNtIoCtlContextDestroy(pCtx); + + return rc; +} + + +#ifdef VBOX_WITH_HARDENING + +/** @name Identifying Special Processes: CSRSS.EXE + * @{ */ + + +/** + * Checks if the process is a system32 process by the given name. + * + * @returns true / false. + * @param pProcess The process to check. + * @param pszName The lower case process name (no path!). + */ +static bool supdrvNtProtectIsSystem32ProcessMatch(PEPROCESS pProcess, const char *pszName) +{ + Assert(strlen(pszName) < 16); /* see buffer below */ + + /* + * This test works on XP+. + */ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (RTStrICmp(pszImageFile, pszName) != 0) + return false; + + /* + * This test requires a Vista+ API. + */ + if (g_pfnPsReferenceProcessFilePointer) + { + PFILE_OBJECT pFile = NULL; + NTSTATUS rcNt = g_pfnPsReferenceProcessFilePointer(pProcess, &pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + union + { + OBJECT_NAME_INFORMATION Info; + uint8_t abBuffer[sizeof(g_System32NtPath) + 16 * sizeof(WCHAR)]; + } Buf; + ULONG cbIgn; + rcNt = ObQueryNameString(pFile, &Buf.Info, sizeof(Buf) - sizeof(WCHAR), &cbIgn); + ObDereferenceObject(pFile); + if (!NT_SUCCESS(rcNt)) + return false; + + /* Terminate the name. */ + PRTUTF16 pwszName = Buf.Info.Name.Buffer; + pwszName[Buf.Info.Name.Length / sizeof(RTUTF16)] = '\0'; + + /* Match the name against the system32 directory path. */ + uint32_t cbSystem32 = g_System32NtPath.UniStr.Length; + if (Buf.Info.Name.Length < cbSystem32) + return false; + if (memcmp(pwszName, g_System32NtPath.UniStr.Buffer, cbSystem32)) + return false; + pwszName += cbSystem32 / sizeof(RTUTF16); + if (*pwszName++ != '\\') + return false; + + /* Compare the name. */ + const char *pszRight = pszName; + for (;;) + { + WCHAR wchLeft = *pwszName++; + char chRight = *pszRight++; + Assert(chRight == RT_C_TO_LOWER(chRight)); + + if ( wchLeft != chRight + && RT_C_TO_LOWER(wchLeft) != chRight) + return false; + if (!chRight) + break; + } + } + + return true; +} + + +/** + * Checks if the current process is likely to be CSRSS. + * + * @returns true/false. + * @param pProcess The process. + */ +static bool supdrvNtProtectIsCsrssByProcess(PEPROCESS pProcess) +{ + /* + * On Windows 8.1 CSRSS.EXE is a protected process. + */ + if (g_pfnPsIsProtectedProcessLight) + { + if (!g_pfnPsIsProtectedProcessLight(pProcess)) + return false; + } + + /* + * The name tests. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pProcess, "csrss.exe")) + return false; + + /** @todo Could extend the CSRSS.EXE check with that the TokenUser of the + * current process must be "NT AUTHORITY\SYSTEM" (S-1-5-18). */ + + return true; +} + + +/** + * Helper for supdrvNtProtectGetAlpcPortObjectType that tries out a name. + * + * @returns true if done, false if not. + * @param pwszPortNm The port path. + * @param ppObjType The object type return variable, updated when + * returning true. + */ +static bool supdrvNtProtectGetAlpcPortObjectType2(PCRTUTF16 pwszPortNm, POBJECT_TYPE *ppObjType) +{ + bool fDone = false; + + UNICODE_STRING UniStrPortNm; + UniStrPortNm.Buffer = (WCHAR *)pwszPortNm; + UniStrPortNm.Length = (USHORT)(RTUtf16Len(pwszPortNm) * sizeof(WCHAR)); + UniStrPortNm.MaximumLength = UniStrPortNm.Length + sizeof(WCHAR); + + OBJECT_ATTRIBUTES ObjAttr; + InitializeObjectAttributes(&ObjAttr, &UniStrPortNm, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + HANDLE hPort; + NTSTATUS rcNt = g_pfnZwAlpcCreatePort(&hPort, &ObjAttr, NULL /*pPortAttribs*/); + if (NT_SUCCESS(rcNt)) + { + PVOID pvObject; + rcNt = ObReferenceObjectByHandle(hPort, 0 /*DesiredAccess*/, NULL /*pObjectType*/, + KernelMode, &pvObject, NULL /*pHandleInfo*/); + if (NT_SUCCESS(rcNt)) + { + POBJECT_TYPE pObjType = g_pfnObGetObjectType(pvObject); + if (pObjType) + { + SUPR0Printf("vboxdrv: ALPC Port Object Type %p (vs %p)\n", pObjType, *ppObjType); + *ppObjType = pObjType; + fDone = true; + } + ObDereferenceObject(pvObject); + } + NtClose(hPort); + } + return fDone; +} + + +/** + * Attempts to retrieve the ALPC Port object type. + * + * We've had at least three reports that using LpcPortObjectType when trying to + * get at the ApiPort object results in STATUS_OBJECT_TYPE_MISMATCH errors. + * It's not known who has modified LpcPortObjectType or AlpcPortObjectType (not + * exported) so that it differs from the actual ApiPort type, or maybe this + * unknown entity is intercepting our attempt to reference the port and + * tries to mislead us. The paranoid explanataion is of course that some evil + * root kit like software is messing with the OS, however, it's possible that + * this is valid kernel behavior that 99.8% of our users and 100% of the + * developers are not triggering for some reason. + * + * The code here creates an ALPC port object and gets it's type. It will cache + * the result in g_pAlpcPortObjectType2 on success. + * + * @returns Object type. + * @param uSessionId The session id. + * @param pszSessionId The session id formatted as a string. + */ +static POBJECT_TYPE supdrvNtProtectGetAlpcPortObjectType(uint32_t uSessionId, const char *pszSessionId) +{ + POBJECT_TYPE pObjType = *LpcPortObjectType; + + if ( g_pfnZwAlpcCreatePort + && g_pfnObGetObjectType) + { + int rc; + ssize_t cchTmp; NOREF(cchTmp); + char szTmp[16]; + RTUTF16 wszPortNm[128]; + size_t offRand; + + /* + * First attempt is in the session directory. + */ + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\VBoxDrv-"); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + bool fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + if (!fDone) + { + /* + * Try base names. + */ + if (uSessionId == 0) + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + else + { + rc = RTUtf16CopyAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\Sessions\\"); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), pszSessionId); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "\\BaseNamedObjects\\VBoxDrv-"); + } + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), (uint32_t)(uintptr_t)PsGetProcessId(PsGetCurrentProcess()), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), "-"); + offRand = RTUtf16Len(wszPortNm); + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + if (!fDone) + { + wszPortNm[offRand] = '\0'; + cchTmp = RTStrFormatU32(szTmp, sizeof(szTmp), RTRandU32(), 16, 0, 0, 0); + Assert(cchTmp > 0); + rc |= RTUtf16CatAscii(wszPortNm, RT_ELEMENTS(wszPortNm), szTmp); + AssertRCSuccess(rc); + + fDone = supdrvNtProtectGetAlpcPortObjectType2(wszPortNm, &pObjType); + } + } + + /* Cache the result in g_pAlpcPortObjectType2. */ + if ( g_pAlpcPortObjectType2 == NULL + && pObjType != g_pAlpcPortObjectType1 + && fDone) + g_pAlpcPortObjectType2 = pObjType; + + } + + return pObjType; +} + + +/** + * Called in the context of VBoxDrvNtCreate to determin the CSRSS for the + * current process. + * + * The Client/Server Runtime Subsystem (CSRSS) process needs to be allowed some + * additional access right so we need to make 101% sure we correctly identify + * the CSRSS process a process is associated with. + * + * @returns IPRT status code. + * @param pNtProtect The NT protected process structure. The + * hCsrssPid member will be updated on success. + */ +static int supdrvNtProtectFindAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect) +{ + Assert(pNtProtect->AvlCore.Key == PsGetCurrentProcessId()); + Assert(pNtProtect->pCsrssProcess == NULL); + Assert(pNtProtect->hCsrssPid == NULL); + + /* + * We'll try use the ApiPort LPC object for the session we're in to track + * down the CSRSS process. So, we start by constructing a path to it. + */ + int rc; + uint32_t uSessionId = PsGetProcessSessionId(PsGetCurrentProcess()); + char szSessionId[16]; + WCHAR wszApiPort[48]; + if (uSessionId == 0) + { + szSessionId[0] = '0'; + szSessionId[1] = '\0'; + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + else + { + ssize_t cchTmp = RTStrFormatU32(szSessionId, sizeof(szSessionId), uSessionId, 10, 0, 0, 0); + AssertReturn(cchTmp > 0, (int)cchTmp); + rc = RTUtf16CopyAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Sessions\\"); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), szSessionId); + if (RT_SUCCESS(rc)) + rc = RTUtf16CatAscii(wszApiPort, RT_ELEMENTS(wszApiPort), "\\Windows\\ApiPort"); + } + AssertRCReturn(rc, rc); + + UNICODE_STRING ApiPortStr; + ApiPortStr.Buffer = wszApiPort; + ApiPortStr.Length = (USHORT)(RTUtf16Len(wszApiPort) * sizeof(RTUTF16)); + ApiPortStr.MaximumLength = ApiPortStr.Length + sizeof(RTUTF16); + + /* + * The object cannot be opened, but we can reference it by name. + */ + void *pvApiPortObj = NULL; + NTSTATUS rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType1, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pAlpcPortObjectType2 != NULL) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + g_pAlpcPortObjectType2, + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if ( rcNt == STATUS_OBJECT_TYPE_MISMATCH + && g_pfnObGetObjectType + && g_pfnZwAlpcCreatePort) + rcNt = ObReferenceObjectByName(&ApiPortStr, + 0, + NULL /*pAccessState*/, + STANDARD_RIGHTS_READ, + supdrvNtProtectGetAlpcPortObjectType(uSessionId, szSessionId), + KernelMode, + NULL /*pvParseContext*/, + &pvApiPortObj); + if (!NT_SUCCESS(rcNt)) + { + SUPR0Printf("vboxdrv: Error opening '%ls': %#x\n", wszApiPort, rcNt); + return rcNt == STATUS_OBJECT_TYPE_MISMATCH ? VERR_SUPDRV_APIPORT_OPEN_ERROR_TYPE : VERR_SUPDRV_APIPORT_OPEN_ERROR; + } + + /* + * Query the processes in the system so we can locate CSRSS.EXE candidates. + * Note! Attempts at using SystemSessionProcessInformation failed with + * STATUS_ACCESS_VIOLATION. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + ULONG cbNeeded = _64K - 32; + uint32_t cbBuf; + uint8_t *pbBuf = NULL; + do + { + cbBuf = RT_ALIGN(cbNeeded + _4K, _64K) - 32; + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + break; + + cbNeeded = 0; +#if 0 /* doesn't work. */ + SYSTEM_SESSION_PROCESS_INFORMATION Req; + Req.SessionId = uSessionId; + Req.BufferLength = cbBuf; + Req.Buffer = pbBuf; + rcNt = NtQuerySystemInformation(SystemSessionProcessInformation, &Req, sizeof(Req), &cbNeeded); +#else + rcNt = NtQuerySystemInformation(SystemProcessInformation, pbBuf, cbBuf, &cbNeeded); +#endif + if (NT_SUCCESS(rcNt)) + break; + + RTMemFree(pbBuf); + pbBuf = NULL; + } while ( rcNt == STATUS_INFO_LENGTH_MISMATCH + && cbNeeded > cbBuf + && cbNeeded < 32U*_1M); + + if ( pbBuf + && NT_SUCCESS(rcNt) + && cbNeeded >= sizeof(SYSTEM_PROCESS_INFORMATION)) + { + /* + * Walk the returned data and look for the process associated with the + * ApiPort object. The ApiPort object keeps the EPROCESS address of + * the owner process (i.e. CSRSS) relatively early in the structure. On + * 64-bit windows 8.1 it's at offset 0x18. So, obtain the EPROCESS + * pointer to likely CSRSS processes and check for a match in the first + * 0x40 bytes of the ApiPort object. + */ + rc = VERR_SUPDRV_CSRSS_NOT_FOUND; + for (uint32_t offBuf = 0; offBuf <= cbNeeded - sizeof(SYSTEM_PROCESS_INFORMATION);) + { + PRTNT_SYSTEM_PROCESS_INFORMATION pProcInfo = (PRTNT_SYSTEM_PROCESS_INFORMATION)&pbBuf[offBuf]; + if ( pProcInfo->ProcessName.Length == 9 * sizeof(WCHAR) + && pProcInfo->NumberOfThreads > 2 /* Very low guess. */ + && pProcInfo->HandleCount > 32 /* Very low guess, I hope. */ + && (uintptr_t)pProcInfo->ProcessName.Buffer - (uintptr_t)pbBuf < cbNeeded + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[0]) == 'c' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[1]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[2]) == 'r' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[3]) == 's' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[4]) == 's' + && pProcInfo->ProcessName.Buffer[5] == '.' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[6]) == 'e' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[7]) == 'x' + && RT_C_TO_LOWER(pProcInfo->ProcessName.Buffer[8]) == 'e' ) + { + + /* Get the process structure and perform some more thorough + process checks. */ + PEPROCESS pProcess; + rcNt = PsLookupProcessByProcessId(pProcInfo->UniqueProcessId, &pProcess); + if (NT_SUCCESS(rcNt)) + { + if (supdrvNtProtectIsCsrssByProcess(pProcess)) + { + if (PsGetProcessSessionId(pProcess) == uSessionId) + { + /* Final test, check the ApiPort. + Note! The old LPC (pre Vista) objects has the PID + much earlier in the structure. Might be + worth looking for it instead. */ + bool fThatsIt = false; + __try + { + PEPROCESS *ppPortProc = (PEPROCESS *)pvApiPortObj; + uint32_t cTests = g_uNtVerCombined >= SUP_NT_VER_VISTA ? 16 : 38; /* ALPC since Vista. */ + do + { + fThatsIt = *ppPortProc == pProcess; + ppPortProc++; + } while (!fThatsIt && --cTests > 0); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fThatsIt = false; + } + if (fThatsIt) + { + /* Ok, we found it! Keep the process structure + reference as well as the PID so we can + safely identify it later on. */ + pNtProtect->hCsrssPid = pProcInfo->UniqueProcessId; + pNtProtect->pCsrssProcess = pProcess; + rc = VINF_SUCCESS; + break; + } + } + } + + ObDereferenceObject(pProcess); + } + } + + /* Advance. */ + if (!pProcInfo->NextEntryOffset) + break; + offBuf += pProcInfo->NextEntryOffset; + } + } + else + rc = VERR_SUPDRV_SESSION_PROCESS_ENUM_ERROR; + RTMemFree(pbBuf); + ObDereferenceObject(pvApiPortObj); + return rc; +} + + +/** + * Checks that the given process is the CSRSS process associated with protected + * process. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pCsrss The process structure of the alleged CSRSS.EXE + * process. + */ +static bool supdrvNtProtectIsAssociatedCsrss(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pCsrss) +{ + if (pNtProtect->pCsrssProcess == pCsrss) + { + if (pNtProtect->hCsrssPid == PsGetProcessId(pCsrss)) + { + return true; + } + } + return false; +} + + +/** + * Checks if the given process is the stupid themes service. + * + * The caller does some screening of access masks and what not. We do the rest. + * + * @returns true / false. + * @param pNtProtect The NT protection structure. + * @param pAnnoyingProcess The process structure of an process that might + * happen to be the annoying themes process. + */ +static bool supdrvNtProtectIsFrigginThemesService(PSUPDRVNTPROTECT pNtProtect, PEPROCESS pAnnoyingProcess) +{ + RT_NOREF1(pNtProtect); + + /* + * Check the process name. + */ + if (!supdrvNtProtectIsSystem32ProcessMatch(pAnnoyingProcess, "svchost.exe")) + return false; + + /** @todo Come up with more checks. */ + + return true; +} + + +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS +/** + * Checks if the given process is one of the whitelisted debuggers. + * + * @returns true / false. + * @param pProcess The process to check. + */ +static bool supdrvNtProtectIsWhitelistedDebugger(PEPROCESS pProcess) +{ + const char *pszImageFile = (const char *)PsGetProcessImageFileName(pProcess); + if (!pszImageFile) + return false; + + if (pszImageFile[0] == 'w' || pszImageFile[0] == 'W') + { + if (RTStrICmp(pszImageFile, "windbg.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfault.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "werfaultsecure.exe") == 0) + return true; + } + else if (pszImageFile[0] == 'd' || pszImageFile[0] == 'D') + { + if (RTStrICmp(pszImageFile, "drwtsn32.exe") == 0) + return true; + if (RTStrICmp(pszImageFile, "dwwin.exe") == 0) + return true; + } + + return false; +} +#endif /* VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** @} */ + + +/** @name Process Creation Callbacks. + * @{ */ + + +/** + * Cleans up VBoxDrv or VBoxDrvStub error info not collected by the dead process. + * + * @param hProcessId The ID of the dead process. + */ +static void supdrvNtErrorInfoCleanupProcess(HANDLE hProcessId) +{ + int rc = RTSemMutexRequestNoResume(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + PSUPDRVNTERRORINFO pCur, pNext; + RTListForEachSafe(&g_ErrorInfoHead, pCur, pNext, SUPDRVNTERRORINFO, ListEntry) + { + if (pCur->hProcessId == hProcessId) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + } + RTSemMutexRelease(g_hErrorInfoLock); + } +} + + +/** + * Common worker used by the process creation hooks as well as the process + * handle creation hooks to check if a VM process is being created. + * + * @returns true if likely to be a VM process, false if not. + * @param pNtStub The NT protection structure for the possible + * stub process. + * @param hParentPid The parent pid. + * @param hChildPid The child pid. + */ +static bool supdrvNtProtectIsSpawningStubProcess(PSUPDRVNTPROTECT pNtStub, HANDLE hParentPid, HANDLE hChildPid) +{ + bool fRc = false; + if (pNtStub->AvlCore.Key == hParentPid) /* paranoia */ + { + if (pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + /* Compare short names. */ + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(hParentPid, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + PEPROCESS pChildProcess; + rcNt = PsLookupProcessByProcessId(hChildPid, &pChildProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszStub = (const char *)PsGetProcessImageFileName(pStubProcess); + const char *pszChild = (const char *)PsGetProcessImageFileName(pChildProcess); + fRc = pszStub != NULL + && pszChild != NULL + && strcmp(pszStub, pszChild) == 0; + + /** @todo check that the full image names matches. */ + + ObDereferenceObject(pChildProcess); + } + ObDereferenceObject(pStubProcess); + } + } + } + return fRc; +} + + +/** + * Common code used by the notifies to protect a child process. + * + * @returns VBox status code. + * @param pNtStub The NT protect structure for the parent. + * @param hChildPid The child pid. + */ +static int supdrvNtProtectProtectNewStubChild(PSUPDRVNTPROTECT pNtParent, HANDLE hChildPid) +{ + /* + * Create a child protection struction. + */ + PSUPDRVNTPROTECT pNtChild; + int rc = supdrvNtProtectCreate(&pNtChild, hChildPid, kSupDrvNtProtectKind_VmProcessUnconfirmed, false /*fLink*/); + if (RT_SUCCESS(rc)) + { + pNtChild->fFirstProcessCreateHandle = true; + pNtChild->fFirstThreadCreateHandle = true; + pNtChild->fCsrssFirstProcessCreateHandle = true; + pNtChild->cCsrssFirstProcessDuplicateHandle = ARCH_BITS == 32 ? 2 : 1; + pNtChild->fThemesFirstProcessCreateHandle = true; + pNtChild->hParentPid = pNtParent->AvlCore.Key; + pNtChild->hCsrssPid = pNtParent->hCsrssPid; + pNtChild->pCsrssProcess = pNtParent->pCsrssProcess; + if (pNtChild->pCsrssProcess) + ObReferenceObject(pNtChild->pCsrssProcess); + + /* + * Take the spinlock, recheck parent conditions and link things. + */ + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtParent->enmProcessKind == kSupDrvNtProtectKind_StubSpawning) + { + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtChild->AvlCore); + if (fSuccess) + { + pNtChild->fInTree = true; + pNtParent->u.pChild = pNtChild; /* Parent keeps the initial reference. */ + pNtParent->enmProcessKind = kSupDrvNtProtectKind_StubParent; + pNtChild->u.pParent = pNtParent; + + RTSpinlockRelease(g_hNtProtectLock); + return VINF_SUCCESS; + } + + rc = VERR_INTERNAL_ERROR_2; + } + else + rc = VERR_WRONG_ORDER; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtChild); + } + return rc; +} + + +/** + * Common process termination code. + * + * Transitions protected process to the dead states, protecting against handle + * PID reuse (esp. with unconfirmed VM processes) and handle cleanup issues. + * + * @param hDeadPid The PID of the dead process. + */ +static void supdrvNtProtectUnprotectDeadProcess(HANDLE hDeadPid) +{ + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hDeadPid); + if (pNtProtect) + { + PSUPDRVNTPROTECT pNtChild = NULL; + + RTSpinlockAcquire(g_hNtProtectLock); + + /* + * If this is an unconfirmed VM process, we must release the reference + * the parent structure holds. + */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + PSUPDRVNTPROTECT pNtParent = pNtProtect->u.pParent; + AssertRelease(pNtParent); AssertRelease(pNtParent->u.pChild == pNtProtect); + pNtParent->u.pChild = NULL; + pNtProtect->u.pParent = NULL; + pNtChild = pNtProtect; + } + /* + * If this is a stub exitting before the VM process gets confirmed, + * release the protection of the potential VM process as this is not + * the prescribed behavior. + */ + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtProtect->u.pChild) + { + pNtChild = pNtProtect->u.pChild; + pNtProtect->u.pChild = NULL; + pNtChild->u.pParent = NULL; + pNtChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* + * Transition it to the dead state to prevent it from opening the + * support driver again or be posthumously abused as a vm process parent. + */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessConfirmed) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + else if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubSpawning + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_StubDead; + + RTSpinlockRelease(g_hNtProtectLock); + + supdrvNtProtectRelease(pNtProtect); + supdrvNtProtectRelease(pNtChild); + + /* + * Do session cleanups. + */ + AssertReturnVoid((HANDLE)(uintptr_t)RTProcSelf() == hDeadPid); + if (g_pDevObjSys) + { + PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)g_pDevObjSys->DeviceExtension; + PSUPDRVSESSION pSession = supdrvSessionHashTabLookup(pDevExt, (RTPROCESS)(uintptr_t)hDeadPid, + RTR0ProcHandleSelf(), NULL); + if (pSession) + { + supdrvSessionHashTabRemove(pDevExt, pSession, NULL); + supdrvSessionRelease(pSession); /* Drops the reference from supdrvSessionHashTabLookup. */ + } + } + } +} + + +/** + * Common worker for the process creation callback that verifies a new child + * being created by the handle creation callback code. + * + * @param pNtStub The parent. + * @param pNtVm The child. + * @param fCallerChecks The result of any additional tests the caller made. + * This is in order to avoid duplicating the failure + * path code. + */ +static void supdrvNtProtectVerifyNewChildProtection(PSUPDRVNTPROTECT pNtStub, PSUPDRVNTPROTECT pNtVm, bool fCallerChecks) +{ + if ( fCallerChecks + && pNtStub->enmProcessKind == kSupDrvNtProtectKind_StubParent + && pNtVm->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtVm->u.pParent == pNtStub + && pNtStub->u.pChild == pNtVm) + { + /* Fine, reset the CSRSS hack (fixes ViRobot APT Shield 2.0 issue). */ + pNtVm->fFirstProcessCreateHandle = true; + return; + } + + LogRel(("vboxdrv: Misdetected vm stub; hParentPid=%p hChildPid=%p\n", pNtStub->AvlCore.Key, pNtVm->AvlCore.Key)); + if (pNtStub->enmProcessKind != kSupDrvNtProtectKind_VmProcessConfirmed) + supdrvNtProtectUnprotectDeadProcess(pNtVm->AvlCore.Key); +} + + +/** + * Old style callback (since forever). + * + * @param hParentPid The parent PID. + * @param hNewPid The PID of the new child. + * @param fCreated TRUE if it's a creation notification, + * FALSE if termination. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotify(HANDLE hParentPid, HANDLE hNewPid, BOOLEAN fCreated) +{ + /* + * Is it a new process that needs protection? + */ + if (fCreated) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + else + { + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, true); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + + +/** + * New style callback (Vista SP1+ / w2k8). + * + * @param pNewProcess The new process. + * @param hNewPid The PID of the new process. + * @param pInfo Process creation details. NULL if process + * termination notification. + * @remarks ASSUMES this arrives before the handle creation callback. + */ +static VOID __stdcall +supdrvNtProtectCallback_ProcessCreateNotifyEx(PEPROCESS pNewProcess, HANDLE hNewPid, PPS_CREATE_NOTIFY_INFO pInfo) +{ + RT_NOREF1(pNewProcess); + + /* + * Is it a new process that needs protection? + */ + if (pInfo) + { + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(pInfo->CreatingThreadId.UniqueProcess); + + Log(("vboxdrv/NewProcessEx: ctx=%04zx/%p pid=%04zx ppid=%04zx ctor=%04zx/%04zx rcNt=%#x %.*ls\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + hNewPid, pInfo->ParentProcessId, + pInfo->CreatingThreadId.UniqueProcess, pInfo->CreatingThreadId.UniqueThread, pInfo->CreationStatus, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? (size_t)pInfo->ImageFileName->Length / 2 : 0, + pInfo->FileOpenNameAvailable && pInfo->ImageFileName ? pInfo->ImageFileName->Buffer : NULL)); + + if (pNtStub) + { + PSUPDRVNTPROTECT pNtVm = supdrvNtProtectLookup(hNewPid); + if (!pNtVm) + { + /* Parent must be creator. */ + if (pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, pInfo->ParentProcessId, hNewPid)) + supdrvNtProtectProtectNewStubChild(pNtStub, hNewPid); + } + } + else + { + /* Parent must be creator (as above). */ + supdrvNtProtectVerifyNewChildProtection(pNtStub, pNtVm, + pInfo->CreatingThreadId.UniqueProcess == pInfo->ParentProcessId); + supdrvNtProtectRelease(pNtVm); + } + supdrvNtProtectRelease(pNtStub); + } + } + /* + * Process termination, do clean ups. + */ + else + { + supdrvNtProtectUnprotectDeadProcess(hNewPid); + supdrvNtErrorInfoCleanupProcess(hNewPid); + } +} + +/** @} */ + + +/** @name Process Handle Callbacks. + * @{ */ + +/** Process rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOW_PROCESS_RIGHTS \ + ( PROCESS_TERMINATE \ + | PROCESS_VM_READ \ + | PROCESS_QUERY_INFORMATION \ + | PROCESS_QUERY_LIMITED_INFORMATION \ + | PROCESS_SUSPEND_RESUME \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) + +/** Evil process rights. */ +# define SUPDRV_NT_EVIL_PROCESS_RIGHTS \ + ( PROCESS_CREATE_THREAD \ + | PROCESS_SET_SESSIONID /*?*/ \ + | PROCESS_VM_OPERATION \ + | PROCESS_VM_WRITE \ + | PROCESS_DUP_HANDLE \ + | PROCESS_CREATE_PROCESS /*?*/ \ + | PROCESS_SET_QUOTA /*?*/ \ + | PROCESS_SET_INFORMATION \ + | PROCESS_SET_LIMITED_INFORMATION /*?*/ \ + | 0) +AssertCompile((SUPDRV_NT_ALLOW_PROCESS_RIGHTS & SUPDRV_NT_EVIL_PROCESS_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ProcessHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + /* + * Protected? Kludge required for NtOpenProcess calls comming in before + * the create process hook triggers on Windows 8.1 (possibly others too). + */ + HANDLE hObjPid = PsGetProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(hObjPid); + if (!pNtProtect) + { + HANDLE hParentPid = PsGetProcessInheritedFromUniqueProcessId((PEPROCESS)pOpInfo->Object); + PSUPDRVNTPROTECT pNtStub = supdrvNtProtectLookup(hParentPid); + if (pNtStub) + { + if (supdrvNtProtectIsSpawningStubProcess(pNtStub, hParentPid, hObjPid)) + { + supdrvNtProtectProtectNewStubChild(pNtStub, hObjPid); + pNtProtect = supdrvNtProtectLookup(hObjPid); + } + supdrvNtProtectRelease(pNtStub); + } + } + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + /* + * Ok, it's a protected process. Strip rights as required or possible. + */ + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOW_PROCESS_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing itself. */ + if ((PEPROCESS)pOpInfo->Object == PsGetCurrentProcess()) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstProcessCreateHandle = false; + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + } +#endif + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->CreateHandleInformation.DesiredAccess; + + /* Special case 1 on Vista, 7 & 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. + + Special case 2 on 8.1: + The CreateProcess code requires additional rights for + something, we'll drop these in the stub code. */ + if ( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) + && ExGetPreviousMode() != KernelMode) + { + if ( !pOpInfo->KernelHandle + && fDesiredAccess == s_fCsrssStupidDesires) + { + if (g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3)) + fAllowedRights |= s_fCsrssStupidDesires; + else + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_SET_INFORMATION + | PROCESS_SET_LIMITED_INFORMATION + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstProcessCreateHandle = false; + } + + /* Special case 3 on 8.1: + The interaction between the CreateProcess code and CSRSS.EXE + has changed to the better with Windows 8.1. CSRSS.EXE no + longer duplicates the process (thread too) handle, but opens + it, thus allowing us to do our job. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fCsrssFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fCsrssFirstProcessCreateHandle = false; + if (fDesiredAccess == s_fCsrssStupidDesires) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for CreateProcess/VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 4, Windows 7, Vista, possibly 8, but not 8.1: + The Themes service requires PROCESS_DUP_HANDLE access to our + process or we won't get any menus and dialogs will be half + unreadable. This is _very_ unfortunate and more work will + go into making this more secure. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 0) + && g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 2) + && fDesiredAccess == 0x1478 /* 6.1.7600.16385 (win7_rtm.090713-1255) */ + && pNtProtect->fThemesFirstProcessCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsFrigginThemesService(pNtProtect, PsGetCurrentProcess()) ) + { + pNtProtect->fThemesFirstProcessCreateHandle = true; /* Only once! */ + fAllowedRights |= PROCESS_DUP_HANDLE; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + /* Special case 6a, Windows 10+: AudioDG.exe opens the process with the + PROCESS_SET_LIMITED_INFORMATION right. It seems like it need it for + some myserious and weirdly placed cpu set management of our process. + I'd love to understand what that's all about... + Currently playing safe and only grand this right, however limited, to + audiodg.exe. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p wants %#x to %p/pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + fDesiredAccess, pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + fAllowedRights, fDesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode() )); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing itself. */ + if ( (PEPROCESS)pOpInfo->Object == PsGetCurrentProcess() + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pOpInfo->Object) + { + Log(("vboxdrv/ProcessHandlePre: ctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + ACCESS_MASK const fDesiredAccess = pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess; + + /* Special case 5 on Vista, 7 & 8: + This is the CSRSS.EXE end of special case #1. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->cCsrssFirstProcessDuplicateHandle > 0 + && pOpInfo->KernelHandle == 0 + && fDesiredAccess == s_fCsrssStupidDesires + && pNtProtect->hParentPid + == PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess())) + { + if (ASMAtomicDecS32(&pNtProtect->cCsrssFirstProcessDuplicateHandle) >= 0) + { + /* Not needed: PROCESS_CREATE_THREAD, PROCESS_SET_SESSIONID, + PROCESS_CREATE_PROCESS, PROCESS_DUP_HANDLE */ + fAllowedRights = fAllowedRights + | PROCESS_VM_OPERATION + | PROCESS_VM_WRITE + | PROCESS_DUP_HANDLE /* Needed for launching VBoxTestOGL. */ + | 0; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + } + + /* Special case 6b, Windows 10+: AudioDG.exe duplicates the handle it opened above. */ + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(10, 0) + && ( fDesiredAccess == PROCESS_SET_LIMITED_INFORMATION + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION) /* expected fix #1 */ + || fDesiredAccess == (PROCESS_SET_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION) /* expected fix #2 */ + ) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsSystem32ProcessMatch(PsGetCurrentProcess(), "audiodg.exe") ) + { + fAllowedRights |= PROCESS_SET_LIMITED_INFORMATION; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ProcessHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + fDesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ProcessHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsProcessType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE + ? pOpInfo->Parameters->CreateHandleInformation.GrantedAccess + : pOpInfo->Parameters->DuplicateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOW_PROCESS_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | PROCESS_UNKNOWN_4000 /* Seen set on win 8.1 */ + /*| PROCESS_UNKNOWN_8000 */ ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOW_PROCESS_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOW_PROCESS_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOW_PROCESS_RIGHTS + +/** @} */ + + +/** @name Thread Handle Callbacks + * @{ */ + +/* From ntifs.h */ +extern "C" NTKERNELAPI PEPROCESS __stdcall IoThreadToProcess(PETHREAD); + +/** Thread rights that we allow for handles to stub and VM processes. */ +# define SUPDRV_NT_ALLOWED_THREAD_RIGHTS \ + ( THREAD_TERMINATE \ + | THREAD_GET_CONTEXT \ + | THREAD_QUERY_INFORMATION \ + | THREAD_QUERY_LIMITED_INFORMATION \ + | DELETE \ + | READ_CONTROL \ + | SYNCHRONIZE) +/** @todo consider THREAD_SET_LIMITED_INFORMATION & THREAD_RESUME */ + +/** Evil thread rights. + * @remarks THREAD_RESUME is not included as it seems to be forced upon us by + * Windows 8.1, at least for some processes. We dont' actively + * allow it though, just tollerate it when forced to. */ +# define SUPDRV_NT_EVIL_THREAD_RIGHTS \ + ( THREAD_SUSPEND_RESUME \ + | THREAD_SET_CONTEXT \ + | THREAD_SET_INFORMATION \ + | THREAD_SET_LIMITED_INFORMATION /*?*/ \ + | THREAD_SET_THREAD_TOKEN /*?*/ \ + | THREAD_IMPERSONATE /*?*/ \ + | THREAD_DIRECT_IMPERSONATION /*?*/ \ + /*| THREAD_RESUME - see remarks. */ \ + | 0) +AssertCompile((SUPDRV_NT_EVIL_THREAD_RIGHTS & SUPDRV_NT_ALLOWED_THREAD_RIGHTS) == 0); + + +static OB_PREOP_CALLBACK_STATUS __stdcall +supdrvNtProtectCallback_ThreadHandlePre(PVOID pvUser, POB_PRE_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + PEPROCESS pProcess = IoThreadToProcess((PETHREAD)pOpInfo->Object); + PSUPDRVNTPROTECT pNtProtect = supdrvNtProtectLookup(PsGetProcessId(pProcess)); + pOpInfo->CallContext = pNtProtect; /* Just for reference. */ + if (pNtProtect) + { + static ACCESS_MASK const s_fCsrssStupidDesires = 0x1fffff; + ACCESS_MASK fAllowedRights = SUPDRV_NT_ALLOWED_THREAD_RIGHTS; + + if (pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE) + { + /* Don't restrict the process accessing its own threads. */ + if (pProcess == PsGetCurrentProcess()) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind)); + pOpInfo->CallContext = NULL; /* don't assert */ + pNtProtect->fFirstThreadCreateHandle = false; + } +#ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Allow debuggers full access. */ + else if (supdrvNtProtectIsWhitelistedDebugger(PsGetCurrentProcess())) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d] %s [debugger]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } +#endif + else + { + /* Special case 1 on Vista, 7, 8: + The CreateProcess code passes the handle over to CSRSS.EXE + and the code inBaseSrvCreateProcess will duplicate the + handle with 0x1fffff as access mask. NtDuplicateObject will + fail this call before it ever gets down here. */ + if ( g_uNtVerCombined < SUP_MAKE_NT_VER_SIMPLE(6, 3) + && pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed + && pNtProtect->fFirstThreadCreateHandle + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && pNtProtect->hParentPid == PsGetProcessId(PsGetCurrentProcess()) ) + { + if ( !pOpInfo->KernelHandle + && pOpInfo->Parameters->CreateHandleInformation.DesiredAccess == s_fCsrssStupidDesires) + { + fAllowedRights |= s_fCsrssStupidDesires; + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + pNtProtect->fFirstThreadCreateHandle = false; + } + + /* Special case 2 on 8.1, possibly also Vista, 7, 8: + When creating a process like VBoxTestOGL from the VM process, + CSRSS.EXE will try talk to the calling thread and, it + appears, impersonate it. We unfortunately need to allow + this or there will be no 3D support. Typical DbgPrint: + "SXS: BasepCreateActCtx() Calling csrss server failed. Status = 0xc00000a5" */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p wants %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s [prev=%#x]\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()), ExGetPreviousMode())); + + pOpInfo->Parameters->CreateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + else + { + /* Don't restrict the process accessing its own threads. */ + if ( pProcess == PsGetCurrentProcess() + && (PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == pProcess) + { + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d] self\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + pOpInfo->CallContext = NULL; /* don't assert */ + } + else + { + /* Special case 3 on Vista, 7, 8: + This is the follow up to special case 1. */ + SUPDRVNTPROTECTKIND enmProcessKind; + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 0, 0, 0) + && ( (enmProcessKind = pNtProtect->enmProcessKind) == kSupDrvNtProtectKind_VmProcessConfirmed + || enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + && pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess == PsGetCurrentProcess() + && pOpInfo->KernelHandle == 0 + && ExGetPreviousMode() == UserMode + && supdrvNtProtectIsAssociatedCsrss(pNtProtect, PsGetCurrentProcess()) ) + { + fAllowedRights |= THREAD_IMPERSONATE; + fAllowedRights |= THREAD_DIRECT_IMPERSONATION; + //fAllowedRights |= THREAD_SET_LIMITED_INFORMATION; - try without this one + pOpInfo->CallContext = NULL; /* don't assert this. */ + } + + Log(("vboxdrv/ThreadHandlePre: %sctx=%04zx/%p[%p] dup from %04zx/%p with %#x to %p in pid=%04zx [%d], allow %#x => %#x; %s\n", + pOpInfo->KernelHandle ? "k" : "", PsGetProcessId(PsGetCurrentProcess()), PsGetCurrentProcess(), + pOpInfo->Parameters->DuplicateHandleInformation.TargetProcess, + PsGetProcessId((PEPROCESS)pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess), + pOpInfo->Parameters->DuplicateHandleInformation.SourceProcess, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess, + pOpInfo->Object, pNtProtect->AvlCore.Key, pNtProtect->enmProcessKind, fAllowedRights, + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess & fAllowedRights, + PsGetProcessImageFileName(PsGetCurrentProcess()) )); + + pOpInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= fAllowedRights; + } + } + + supdrvNtProtectRelease(pNtProtect); + } + + return OB_PREOP_SUCCESS; +} + + +static VOID __stdcall +supdrvNtProtectCallback_ThreadHandlePost(PVOID pvUser, POB_POST_OPERATION_INFORMATION pOpInfo) +{ + Assert(pvUser == NULL); RT_NOREF1(pvUser); + Assert(pOpInfo->Operation == OB_OPERATION_HANDLE_CREATE || pOpInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE); + Assert(pOpInfo->ObjectType == *PsThreadType); + + if ( pOpInfo->CallContext + && NT_SUCCESS(pOpInfo->ReturnStatus)) + { + ACCESS_MASK const fGrantedAccess = pOpInfo->Parameters->CreateHandleInformation.GrantedAccess; + AssertReleaseMsg( !(fGrantedAccess & ~( SUPDRV_NT_ALLOWED_THREAD_RIGHTS + | WRITE_OWNER | WRITE_DAC /* these two might be forced upon us */ + | THREAD_RESUME /* This seems to be force upon us too with 8.1. */ + ) ) + || pOpInfo->KernelHandle, + ("GrantedAccess=%#x - we allow %#x - we did not allow %#x\n", + fGrantedAccess, SUPDRV_NT_ALLOWED_THREAD_RIGHTS, fGrantedAccess & ~SUPDRV_NT_ALLOWED_THREAD_RIGHTS)); + } +} + +# undef SUPDRV_NT_ALLOWED_THREAD_RIGHTS + +/** @} */ + + +/** + * Creates a new process protection structure. + * + * @returns VBox status code. + * @param ppNtProtect Where to return the pointer to the structure + * on success. + * @param hPid The process ID of the process to protect. + * @param enmProcessKind The kind of process we're protecting. + * @param fLink Whether to link the structure into the tree. + */ +static int supdrvNtProtectCreate(PSUPDRVNTPROTECT *ppNtProtect, HANDLE hPid, SUPDRVNTPROTECTKIND enmProcessKind, bool fLink) +{ + AssertReturn(g_hNtProtectLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); + + PSUPDRVNTPROTECT pNtProtect = (PSUPDRVNTPROTECT)RTMemAllocZ(sizeof(*pNtProtect)); + if (!pNtProtect) + return VERR_NO_MEMORY; + + pNtProtect->AvlCore.Key = hPid; + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC; + pNtProtect->cRefs = 1; + pNtProtect->enmProcessKind = enmProcessKind; + pNtProtect->hParentPid = NULL; + pNtProtect->hOpenTid = NULL; + pNtProtect->hCsrssPid = NULL; + pNtProtect->pCsrssProcess = NULL; + + if (fLink) + { + RTSpinlockAcquire(g_hNtProtectLock); + bool fSuccess = RTAvlPVInsert(&g_NtProtectTree, &pNtProtect->AvlCore); + pNtProtect->fInTree = fSuccess; + RTSpinlockRelease(g_hNtProtectLock); + + if (!fSuccess) + { + /* Duplicate entry, fail. */ + pNtProtect->u32Magic = SUPDRVNTPROTECT_MAGIC_DEAD; + LogRel(("supdrvNtProtectCreate: Duplicate (%#x).\n", pNtProtect->AvlCore.Key)); + RTMemFree(pNtProtect); + return VERR_DUPLICATE; + } + } + + *ppNtProtect = pNtProtect; + return VINF_SUCCESS; +} + + +/** + * Releases a reference to a NT protection structure. + * + * @param pNtProtect The NT protection structure. + */ +static void supdrvNtProtectRelease(PSUPDRVNTPROTECT pNtProtect) +{ + if (!pNtProtect) + return; + AssertReturnVoid(pNtProtect->u32Magic == SUPDRVNTPROTECT_MAGIC); + + RTSpinlockAcquire(g_hNtProtectLock); + uint32_t cRefs = ASMAtomicDecU32(&pNtProtect->cRefs); + if (cRefs != 0) + RTSpinlockRelease(g_hNtProtectLock); + else + { + /* + * That was the last reference. Remove it from the tree, invalidate it + * and free the resources associated with it. Also, release any + * child/parent references related to this protection structure. + */ + ASMAtomicWriteU32(&pNtProtect->u32Magic, SUPDRVNTPROTECT_MAGIC_DEAD); + if (pNtProtect->fInTree) + { + PSUPDRVNTPROTECT pRemoved = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pNtProtect->AvlCore.Key); + Assert(pRemoved == pNtProtect); RT_NOREF_PV(pRemoved); + pNtProtect->fInTree = false; + } + + PSUPDRVNTPROTECT pChild = NULL; + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubParent) + { + pChild = pNtProtect->u.pChild; + if (pChild) + { + pNtProtect->u.pChild = NULL; + pChild->u.pParent = NULL; + pChild->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + uint32_t cChildRefs = ASMAtomicDecU32(&pChild->cRefs); + if (!cChildRefs) + { + Assert(pChild->fInTree); + if (pChild->fInTree) + { + PSUPDRVNTPROTECT pRemovedChild = (PSUPDRVNTPROTECT)RTAvlPVRemove(&g_NtProtectTree, pChild->AvlCore.Key); + Assert(pRemovedChild == pChild); RT_NOREF_PV(pRemovedChild); + pChild->fInTree = false; + } + } + else + pChild = NULL; + } + } + else + AssertRelease(pNtProtect->enmProcessKind != kSupDrvNtProtectKind_VmProcessUnconfirmed); + + RTSpinlockRelease(g_hNtProtectLock); + + if (pNtProtect->pCsrssProcess) + { + ObDereferenceObject(pNtProtect->pCsrssProcess); + pNtProtect->pCsrssProcess = NULL; + } + + RTMemFree(pNtProtect); + if (pChild) + RTMemFree(pChild); + } +} + + +/** + * Looks up a PID in the NT protect tree. + * + * @returns Pointer to a NT protection structure (with a referenced) on success, + * NULL if not found. + * @param hPid The process ID. + */ +static PSUPDRVNTPROTECT supdrvNtProtectLookup(HANDLE hPid) +{ + RTSpinlockAcquire(g_hNtProtectLock); + PSUPDRVNTPROTECT pFound = (PSUPDRVNTPROTECT)RTAvlPVGet(&g_NtProtectTree, hPid); + if (pFound) + ASMAtomicIncU32(&pFound->cRefs); + RTSpinlockRelease(g_hNtProtectLock); + return pFound; +} + + +/** + * Validates a few facts about the stub process when the VM process opens + * vboxdrv. + * + * This makes sure the stub process is still around and that it has neither + * debugger nor extra threads in it. + * + * @returns VBox status code. + * @param pNtProtect The unconfirmed VM process currently trying to + * open vboxdrv. + * @param pErrInfo Additional error information. + */ +static int supdrvNtProtectVerifyStubForVmProcess(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * Grab a reference to the parent stub process. + */ + SUPDRVNTPROTECTKIND enmStub = kSupDrvNtProtectKind_Invalid; + PSUPDRVNTPROTECT pNtStub = NULL; + RTSpinlockAcquire(g_hNtProtectLock); + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + pNtStub = pNtProtect->u.pParent; /* weak reference. */ + if (pNtStub) + { + enmStub = pNtStub->enmProcessKind; + if (enmStub == kSupDrvNtProtectKind_StubParent) + { + uint32_t cRefs = ASMAtomicIncU32(&pNtStub->cRefs); + Assert(cRefs > 0 && cRefs < 1024); RT_NOREF_PV(cRefs); + } + else + pNtStub = NULL; + } + } + RTSpinlockRelease(g_hNtProtectLock); + + /* + * We require the stub process to be present. + */ + if (!pNtStub) + return RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, "Missing stub process (enmStub=%d).", enmStub); + + /* + * Open the parent process and thread so we can check for debuggers and unwanted threads. + */ + int rc; + PEPROCESS pStubProcess; + NTSTATUS rcNt = PsLookupProcessByProcessId(pNtStub->AvlCore.Key, &pStubProcess); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubProcess; + rcNt = ObOpenObjectByPointer(pStubProcess, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsProcessType, KernelMode, &hStubProcess); + if (NT_SUCCESS(rcNt)) + { + PETHREAD pStubThread; + rcNt = PsLookupThreadByThreadId(pNtStub->hOpenTid, &pStubThread); + if (NT_SUCCESS(rcNt)) + { + HANDLE hStubThread; + rcNt = ObOpenObjectByPointer(pStubThread, OBJ_KERNEL_HANDLE, NULL /*PassedAccessState*/, + 0 /*DesiredAccess*/, *PsThreadType, KernelMode, &hStubThread); + if (NT_SUCCESS(rcNt)) + { + /* + * Do some simple sanity checking. + */ + rc = supHardNtVpDebugger(hStubProcess, pErrInfo); + if (RT_SUCCESS(rc)) + rc = supHardNtVpThread(hStubProcess, hStubThread, pErrInfo); + + /* Clean up. */ + rcNt = NtClose(hStubThread); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_OPEN_ERROR, + "Error opening stub thread %p (tid %p, pid %p): %#x", + pStubThread, pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_THREAD_NOT_FOUND, + "Failed to locate thread %p in %p: %#x", pNtStub->hOpenTid, pNtStub->AvlCore.Key, rcNt); + rcNt = NtClose(hStubProcess); AssertMsg(NT_SUCCESS(rcNt), ("%#x\n", rcNt)); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_OPEN_ERROR, + "Error opening stub process %p (pid %p): %#x", pStubProcess, pNtStub->AvlCore.Key, rcNt); + ObDereferenceObject(pStubProcess); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_SUP_VP_STUB_NOT_FOUND, + "Failed to locate stub process %p: %#x", pNtStub->AvlCore.Key, rcNt); + + supdrvNtProtectRelease(pNtStub); + return rc; +} + + +static const char *supdrvNtProtectHandleTypeIndexToName(ULONG idxType, char *pszName, size_t cbName) +{ + /* + * Query the object types. + */ + uint32_t cbBuf = _8K; + uint8_t *pbBuf = (uint8_t *)RTMemAllocZ(_8K); + ULONG cbNeeded = cbBuf; + NTSTATUS rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + while (rcNt == STATUS_INFO_LENGTH_MISMATCH) + { + cbBuf = RT_ALIGN_32(cbNeeded + 256, _64K); + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAllocZ(cbBuf); + if (pbBuf) + rcNt = NtQueryObject(NULL, ObjectTypesInformation, pbBuf, cbBuf, &cbNeeded); + else + break; + } + if (NT_SUCCESS(rcNt)) + { + Assert(cbNeeded <= cbBuf); + + POBJECT_TYPES_INFORMATION pObjTypes = (OBJECT_TYPES_INFORMATION *)pbBuf; + POBJECT_TYPE_INFORMATION pCurType = &pObjTypes->FirstType; + ULONG cLeft = pObjTypes->NumberOfTypes; + while (cLeft-- > 0 && (uintptr_t)&pCurType[1] - (uintptr_t)pbBuf < cbNeeded) + { + if (pCurType->TypeIndex == idxType) + { + PCRTUTF16 const pwszSrc = pCurType->TypeName.Buffer; + AssertBreak(pwszSrc); + size_t idxName = pCurType->TypeName.Length / sizeof(RTUTF16); + AssertBreak(idxName > 0); + AssertBreak(idxName < 128); + if (idxName >= cbName) + idxName = cbName - 1; + pszName[idxName] = '\0'; + while (idxName-- > 0) + pszName[idxName] = (char )pwszSrc[idxName]; + RTMemFree(pbBuf); + return pszName; + } + + /* next */ + pCurType = (POBJECT_TYPE_INFORMATION)( (uintptr_t)pCurType->TypeName.Buffer + + RT_ALIGN_32(pCurType->TypeName.MaximumLength, sizeof(uintptr_t))); + } + } + + RTMemFree(pbBuf); + return "unknown"; +} + + +/** + * Worker for supdrvNtProtectVerifyProcess that verifies the handles to a VM + * process and its thread. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure for getting information + * about special processes. + * @param pErrInfo Where to return additional error details. + */ +static int supdrvNtProtectRestrictHandlesToProcessAndThread(PSUPDRVNTPROTECT pNtProtect, PRTERRINFO pErrInfo) +{ + /* + * What to protect. + */ + PEPROCESS pProtectedProcess = PsGetCurrentProcess(); + HANDLE hProtectedPid = PsGetProcessId(pProtectedProcess); + PETHREAD pProtectedThread = PsGetCurrentThread(); + AssertReturn(pNtProtect->AvlCore.Key == hProtectedPid, VERR_INTERNAL_ERROR_5); + + /* + * Take a snapshot of all the handles in the system. + * Note! The 32 bytes on the size of to counteract the allocation header + * that rtR0MemAllocEx slaps on everything. + */ + uint32_t cbBuf = _256K - 32; + uint8_t *pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + ULONG cbNeeded = cbBuf; + NTSTATUS rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + if (!NT_SUCCESS(rcNt)) + { + while ( rcNt == STATUS_INFO_LENGTH_MISMATCH + && cbNeeded > cbBuf + && cbBuf <= 32U*_1M) + { + cbBuf = RT_ALIGN_32(cbNeeded + _4K, _64K) - 32; + RTMemFree(pbBuf); + pbBuf = (uint8_t *)RTMemAlloc(cbBuf); + if (!pbBuf) + return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Error allocating %zu bytes for querying handles.", cbBuf); + rcNt = NtQuerySystemInformation(SystemExtendedHandleInformation, pbBuf, cbBuf, &cbNeeded); + } + if (!NT_SUCCESS(rcNt)) + { + RTMemFree(pbBuf); + return RTErrInfoSetF(pErrInfo, RTErrConvertFromNtStatus(rcNt), + "NtQuerySystemInformation/SystemExtendedHandleInformation failed: %#x\n", rcNt); + } + } + + /* + * Walk the information and look for handles to the two objects we're protecting. + */ + int rc = VINF_SUCCESS; +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + HANDLE idLastDebugger = (HANDLE)~(uintptr_t)0; +# endif + + uint32_t cCsrssProcessHandles = 0; + uint32_t cSystemProcessHandles = 0; + uint32_t cEvilProcessHandles = 0; + uint32_t cBenignProcessHandles = 0; + + uint32_t cCsrssThreadHandles = 0; + uint32_t cEvilThreadHandles = 0; + uint32_t cBenignThreadHandles = 0; + + uint32_t cEvilInheritableHandles = 0; + uint32_t cBenignInheritableHandles = 0; + char szTmpName[32]; + + SYSTEM_HANDLE_INFORMATION_EX const *pInfo = (SYSTEM_HANDLE_INFORMATION_EX const *)pbBuf; + ULONG_PTR i = pInfo->NumberOfHandles; + AssertRelease(RT_UOFFSETOF_DYN(SYSTEM_HANDLE_INFORMATION_EX, Handles[i]) == cbNeeded); + while (i-- > 0) + { + const char *pszType; + SYSTEM_HANDLE_ENTRY_INFO_EX const *pHandleInfo = &pInfo->Handles[i]; + if (pHandleInfo->Object == pProtectedProcess) + { + /* Handles within the protected process are fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_PROCESS_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignProcessHandles++; + continue; + } + + /* CSRSS is allowed to have one evil process handle. + See the special cases in the hook code. */ + if ( cCsrssProcessHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssProcessHandles++; + continue; + } + + /* The system process is allowed having two open process handle in + Windows 8.1 and later, and one in earlier. This is probably a + little overly paranoid as I think we can safely trust the + system process... */ + if ( cSystemProcessHandles < (g_uNtVerCombined >= SUP_MAKE_NT_VER_SIMPLE(6, 3) ? UINT32_C(2) : UINT32_C(1)) + && pHandleInfo->UniqueProcessId == PsGetProcessId(PsInitialSystemProcess)) + { + cSystemProcessHandles++; + continue; + } + + cEvilProcessHandles++; + pszType = "process"; + } + else if (pHandleInfo->Object == pProtectedThread) + { + /* Handles within the protected process is fine. */ + if ( !(pHandleInfo->GrantedAccess & SUPDRV_NT_EVIL_THREAD_RIGHTS) + || pHandleInfo->UniqueProcessId == hProtectedPid) + { + cBenignThreadHandles++; + continue; + } + + /* CSRSS is allowed to have one evil handle to the primary thread + for LPC purposes. See the hook for special case. */ + if ( cCsrssThreadHandles < 1 + && pHandleInfo->UniqueProcessId == pNtProtect->hCsrssPid) + { + cCsrssThreadHandles++; + continue; + } + + cEvilThreadHandles++; + pszType = "thread"; + } + else if ( (pHandleInfo->HandleAttributes & OBJ_INHERIT) + && pHandleInfo->UniqueProcessId == hProtectedPid) + { + /* No handles should be marked inheritable, except files and two events. + Handles to NT 'directory' objects are especially evil, because of + KnownDlls faking. See bugref{10294} for details. + + Correlating the ObjectTypeIndex to a type is complicated, so instead + we try referecing the handle and check the type that way. So, only + file and events objects are allowed to be marked inheritable at the + moment. Add more in whitelist fashion if needed. */ + void *pvObject = NULL; + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *IoFileObjectType, KernelMode, &pvObject, NULL); + if (rcNt == STATUS_OBJECT_TYPE_MISMATCH) + rcNt = ObReferenceObjectByHandle(pHandleInfo->HandleValue, 0, *ExEventObjectType, KernelMode, &pvObject, NULL); + if (NT_SUCCESS(rcNt)) + { + ObDereferenceObject(pvObject); + cBenignInheritableHandles++; + continue; + } + + if (rcNt != STATUS_OBJECT_TYPE_MISMATCH) + { + cBenignInheritableHandles++; + continue; + } + + cEvilInheritableHandles++; + pszType = supdrvNtProtectHandleTypeIndexToName(pHandleInfo->ObjectTypeIndex, szTmpName, sizeof(szTmpName)); + } + else + continue; + +# ifdef VBOX_WITHOUT_DEBUGGER_CHECKS + /* Ignore whitelisted debuggers. */ + if (pHandleInfo->UniqueProcessId == idLastDebugger) + continue; + PEPROCESS pDbgProc; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pDbgProc); + if (NT_SUCCESS(rcNt)) + { + bool fIsDebugger = supdrvNtProtectIsWhitelistedDebugger(pDbgProc); + ObDereferenceObject(pDbgProc); + if (fIsDebugger) + { + idLastDebugger = pHandleInfo->UniqueProcessId; + continue; + } + } +# endif + + /* Found evil handle. Currently ignoring on pre-Vista. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if ( g_uNtVerCombined >= SUP_NT_VER_VISTA +# else + if ( g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0) +# endif + || g_pfnObRegisterCallbacks) + { + LogRel(("vboxdrv: Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)\n", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex)); + rc = RTErrInfoAddF(pErrInfo, VERR_SUPDRV_HARDENING_EVIL_HANDLE, + *pErrInfo->pszMsg + ? "\nFound evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)" + : "Found evil handle to budding VM process: pid=%p h=%p acc=%#x attr=%#x type=%s (%u)", + pHandleInfo->UniqueProcessId, pHandleInfo->HandleValue, + pHandleInfo->GrantedAccess, pHandleInfo->HandleAttributes, pszType, pHandleInfo->ObjectTypeIndex); + + /* Try add the process name. */ + PEPROCESS pOffendingProcess; + rcNt = PsLookupProcessByProcessId(pHandleInfo->UniqueProcessId, &pOffendingProcess); + if (NT_SUCCESS(rcNt)) + { + const char *pszName = (const char *)PsGetProcessImageFileName(pOffendingProcess); + if (pszName && *pszName) + rc = RTErrInfoAddF(pErrInfo, rc, " [%s]", pszName); + + ObDereferenceObject(pOffendingProcess); + } + } + } + + RTMemFree(pbBuf); + return rc; +} + + +/** + * Checks if the current process checks out as a VM process stub. + * + * @returns VBox status code. + * @param pNtProtect The NT protect structure. This is upgraded to a + * final protection kind (state) on success. + */ +static int supdrvNtProtectVerifyProcess(PSUPDRVNTPROTECT pNtProtect) +{ + AssertReturn(PsGetProcessId(PsGetCurrentProcess()) == pNtProtect->AvlCore.Key, VERR_INTERNAL_ERROR_3); + + /* + * Do the verification. The handle restriction checks are only preformed + * on VM processes. + */ + int rc = VINF_SUCCESS; + PSUPDRVNTERRORINFO pErrorInfo = (PSUPDRVNTERRORINFO)RTMemAllocZ(sizeof(*pErrorInfo)); + if (RT_SUCCESS(rc)) + { + pErrorInfo->hProcessId = PsGetCurrentProcessId(); + pErrorInfo->hThreadId = PsGetCurrentThreadId(); + RTERRINFO ErrInfo; + RTErrInfoInit(&ErrInfo, pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo)); + + if (pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectRestrictHandlesToProcessAndThread(pNtProtect, &ErrInfo); + if (RT_SUCCESS(rc)) + { + rc = supHardenedWinVerifyProcess(NtCurrentProcess(), NtCurrentThread(), SUPHARDNTVPKIND_VERIFY_ONLY, 0 /*fFlags*/, + NULL /*pcFixes*/, &ErrInfo); + if (RT_SUCCESS(rc) && pNtProtect->enmProcessKind >= kSupDrvNtProtectKind_VmProcessUnconfirmed) + rc = supdrvNtProtectVerifyStubForVmProcess(pNtProtect, &ErrInfo); + } + } + else + rc = VERR_NO_MEMORY; + + /* + * Upgrade and return. + */ + HANDLE hOpenTid = PsGetCurrentThreadId(); + RTSpinlockAcquire(g_hNtProtectLock); + + /* Stub process verficiation is pretty much straight forward. */ + if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubUnverified) + { + pNtProtect->enmProcessKind = RT_SUCCESS(rc) ? kSupDrvNtProtectKind_StubSpawning : kSupDrvNtProtectKind_StubDead; + pNtProtect->hOpenTid = hOpenTid; + } + /* The VM process verification is a little bit more complicated + because we need to drop the parent process reference as well. */ + else if (pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessUnconfirmed) + { + AssertRelease(pNtProtect->cRefs >= 2); /* Parent + Caller */ + PSUPDRVNTPROTECT pParent = pNtProtect->u.pParent; + AssertRelease(pParent); + AssertRelease(pParent->u.pParent == pNtProtect); + AssertRelease(pParent->enmProcessKind == kSupDrvNtProtectKind_StubParent); + pParent->u.pParent = NULL; + + pNtProtect->u.pParent = NULL; + ASMAtomicDecU32(&pNtProtect->cRefs); + + if (RT_SUCCESS(rc)) + { + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessConfirmed; + pNtProtect->hOpenTid = hOpenTid; + } + else + pNtProtect->enmProcessKind = kSupDrvNtProtectKind_VmProcessDead; + } + + /* Since the stub and VM processes are only supposed to have one thread, + we're not supposed to be subject to any races from within the processes. + + There is a race between VM process verification and the stub process + exiting, though. We require the stub process to be alive until the new + VM process has made it thru the validation. So, when the stub + terminates the notification handler will change the state of both stub + and VM process to dead. + + Also, I'm not entirely certain where the process + termination notification is triggered from, so that can theorically + create a race in both cases. */ + else + { + AssertReleaseMsg( pNtProtect->enmProcessKind == kSupDrvNtProtectKind_StubDead + || pNtProtect->enmProcessKind == kSupDrvNtProtectKind_VmProcessDead, + ("enmProcessKind=%d rc=%Rrc\n", pNtProtect->enmProcessKind, rc)); + if (RT_SUCCESS(rc)) + rc = VERR_INVALID_STATE; /* There should be no races here. */ + } + + RTSpinlockRelease(g_hNtProtectLock); + + /* + * Free error info on success, keep it on failure. + */ + if (RT_SUCCESS(rc)) + RTMemFree(pErrorInfo); + else if (pErrorInfo) + { + pErrorInfo->cchErrorInfo = (uint32_t)strlen(pErrorInfo->szErrorInfo); + if (!pErrorInfo->cchErrorInfo) + pErrorInfo->cchErrorInfo = (uint32_t)RTStrPrintf(pErrorInfo->szErrorInfo, sizeof(pErrorInfo->szErrorInfo), + "supdrvNtProtectVerifyProcess: rc=%d", rc); + RTLogWriteDebugger(pErrorInfo->szErrorInfo, pErrorInfo->cchErrorInfo); + + int rc2 = RTSemMutexRequest(g_hErrorInfoLock, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc2)) + { + pErrorInfo->uCreatedMsTs = RTTimeMilliTS(); + + /* Free old entries. */ + PSUPDRVNTERRORINFO pCur; + while ( (pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL + && (int64_t)(pErrorInfo->uCreatedMsTs - pCur->uCreatedMsTs) > 60000 /*60sec*/) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + /* Insert our new entry. */ + RTListAppend(&g_ErrorInfoHead, &pErrorInfo->ListEntry); + + RTSemMutexRelease(g_hErrorInfoLock); + } + else + RTMemFree(pErrorInfo); + } + + return rc; +} + + +# ifndef VBOX_WITHOUT_DEBUGGER_CHECKS + +/** + * Checks if the current process is being debugged. + * @return @c true if debugged, @c false if not. + */ +static bool supdrvNtIsDebuggerAttached(void) +{ + return PsIsProcessBeingDebugged(PsGetCurrentProcess()) != FALSE; +} + +# endif /* !VBOX_WITHOUT_DEBUGGER_CHECKS */ + + +/** + * Terminates the hardening bits. + */ +static void supdrvNtProtectTerm(void) +{ + /* + * Stop intercepting process and thread handle creation calls. + */ + if (g_pvObCallbacksCookie) + { + g_pfnObUnRegisterCallbacks(g_pvObCallbacksCookie); + g_pvObCallbacksCookie = NULL; + } + + /* + * Stop intercepting process creation and termination notifications. + */ + NTSTATUS rcNt; + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + AssertMsg(NT_SUCCESS(rcNt), ("rcNt=%#x\n", rcNt)); + + Assert(g_NtProtectTree == NULL); + + /* + * Clean up globals. + */ + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + + PSUPDRVNTERRORINFO pCur; + while ((pCur = RTListGetFirst(&g_ErrorInfoHead, SUPDRVNTERRORINFO, ListEntry)) != NULL) + { + RTListNodeRemove(&pCur->ListEntry); + RTMemFree(pCur); + } + + supHardenedWinTermImageVerifier(); +} + +# ifdef RT_ARCH_X86 +DECLASM(void) supdrvNtQueryVirtualMemory_0xAF(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB0(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB1(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB2(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB3(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB4(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB5(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB6(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB7(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB8(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xB9(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBA(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBB(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBC(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBD(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0xBE(void); +# elif defined(RT_ARCH_AMD64) +DECLASM(void) supdrvNtQueryVirtualMemory_0x1F(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x20(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x21(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x22(void); +DECLASM(void) supdrvNtQueryVirtualMemory_0x23(void); +extern "C" NTSYSAPI NTSTATUS NTAPI ZwRequestWaitReplyPort(HANDLE, PVOID, PVOID); +# endif + + +/** + * Initalizes the hardening bits. + * + * @returns NT status code. + */ +static NTSTATUS supdrvNtProtectInit(void) +{ + /* + * Initialize the globals. + */ + + /* The NT version. */ + ULONG uMajor, uMinor, uBuild; + PsGetVersion(&uMajor, &uMinor, &uBuild, NULL); + g_uNtVerCombined = SUP_MAKE_NT_VER_COMBINED(uMajor, uMinor, uBuild, 0, 0); + + /* Resolve methods we want but isn't available everywhere. */ + UNICODE_STRING RoutineName; + + RtlInitUnicodeString(&RoutineName, L"ObGetObjectType"); + g_pfnObGetObjectType = (PFNOBGETOBJECTTYPE)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObRegisterCallbacks"); + g_pfnObRegisterCallbacks = (PFNOBREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ObUnRegisterCallbacks"); + g_pfnObUnRegisterCallbacks = (PFNOBUNREGISTERCALLBACKS)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsSetCreateProcessNotifyRoutineEx"); + g_pfnPsSetCreateProcessNotifyRoutineEx = (PFNPSSETCREATEPROCESSNOTIFYROUTINEEX)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsReferenceProcessFilePointer"); + g_pfnPsReferenceProcessFilePointer = (PFNPSREFERENCEPROCESSFILEPOINTER)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"PsIsProtectedProcessLight"); + g_pfnPsIsProtectedProcessLight = (PFNPSISPROTECTEDPROCESSLIGHT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwAlpcCreatePort"); + g_pfnZwAlpcCreatePort = (PFNZWALPCCREATEPORT)MmGetSystemRoutineAddress(&RoutineName); + + RtlInitUnicodeString(&RoutineName, L"ZwQueryVirtualMemory"); /* Yes, using Zw version here. */ + g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)MmGetSystemRoutineAddress(&RoutineName); + if (!g_pfnNtQueryVirtualMemory && g_uNtVerCombined < SUP_NT_VER_VISTA) + { + /* XP & W2K3 doesn't have this function exported, so we've cooked up a + few alternative in the assembly helper file that uses the code in + ZwReadFile with a different eax value. We figure the syscall number + by inspecting ZwQueryVolumeInformationFile as it's the next number. */ +# ifdef RT_ARCH_X86 + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwQueryVolumeInformationFile; + if (*pbCode == 0xb8) /* mov eax, dword */ + switch (*(uint32_t const *)&pbCode[1]) + { + case 0xb0: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xAF; break; /* just in case */ + case 0xb1: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB0; break; /* just in case */ + case 0xb2: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB1; break; /* just in case */ + case 0xb3: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* XP SP3 */ + case 0xb4: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB2; break; /* just in case */ + case 0xb5: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB3; break; /* just in case */ + case 0xb6: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB4; break; /* just in case */ + case 0xb7: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB5; break; /* just in case */ + case 0xb8: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB6; break; /* just in case */ + case 0xb9: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB7; break; /* just in case */ + case 0xba: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xB8; break; /* just in case */ + case 0xbb: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBA; break; /* W2K3 R2 SP2 */ + case 0xbc: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBB; break; /* just in case */ + case 0xbd: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBC; break; /* just in case */ + case 0xbe: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBD; break; /* just in case */ + case 0xbf: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0xBE; break; /* just in case */ + } +# elif defined(RT_ARCH_AMD64) + uint8_t const *pbCode = (uint8_t const *)(uintptr_t)ZwRequestWaitReplyPort; + if ( pbCode[ 0] == 0x48 /* mov rax, rsp */ + && pbCode[ 1] == 0x8b + && pbCode[ 2] == 0xc4 + && pbCode[ 3] == 0xfa /* cli */ + && pbCode[ 4] == 0x48 /* sub rsp, 10h */ + && pbCode[ 5] == 0x83 + && pbCode[ 6] == 0xec + && pbCode[ 7] == 0x10 + && pbCode[ 8] == 0x50 /* push rax */ + && pbCode[ 9] == 0x9c /* pushfq */ + && pbCode[10] == 0x6a /* push 10 */ + && pbCode[11] == 0x10 + && pbCode[12] == 0x48 /* lea rax, [nt!KiServiceLinkage] */ + && pbCode[13] == 0x8d + && pbCode[14] == 0x05 + && pbCode[19] == 0x50 /* push rax */ + && pbCode[20] == 0xb8 /* mov eax,1fh <- the syscall no. */ + /*&& pbCode[21] == 0x1f*/ + && pbCode[22] == 0x00 + && pbCode[23] == 0x00 + && pbCode[24] == 0x00 + && pbCode[25] == 0xe9 /* jmp KiServiceInternal */ + ) + { + uint8_t const *pbKiServiceInternal = &pbCode[30] + *(int32_t const *)&pbCode[26]; + uint8_t const *pbKiServiceLinkage = &pbCode[19] + *(int32_t const *)&pbCode[15]; + if (*pbKiServiceLinkage == 0xc3) + { + g_pfnKiServiceInternal = (PFNRT)pbKiServiceInternal; + g_pfnKiServiceLinkage = (PFNRT)pbKiServiceLinkage; + switch (pbCode[21]) + { + case 0x1e: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x1F; break; + case 0x1f: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x20; break; + case 0x20: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x21; break; + case 0x21: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x22; break; + case 0x22: g_pfnNtQueryVirtualMemory = (PFNNTQUERYVIRTUALMEMORY)supdrvNtQueryVirtualMemory_0x23; break; + } + } + } +# endif + } + if (!g_pfnNtQueryVirtualMemory) + { + LogRel(("vboxdrv: Cannot locate ZwQueryVirtualMemory in ntoskrnl, nor were we able to cook up a replacement.\n")); + return STATUS_PROCEDURE_NOT_FOUND; + } + +# ifdef VBOX_STRICT + if ( g_uNtVerCombined >= SUP_NT_VER_W70 + && ( g_pfnObGetObjectType == NULL + || g_pfnZwAlpcCreatePort == NULL) ) + { + LogRel(("vboxdrv: g_pfnObGetObjectType=%p g_pfnZwAlpcCreatePort=%p.\n", g_pfnObGetObjectType, g_pfnZwAlpcCreatePort)); + return STATUS_PROCEDURE_NOT_FOUND; + } +# endif + + /* LPC object type. */ + g_pAlpcPortObjectType1 = *LpcPortObjectType; + + /* The spinlock protecting our structures. */ + int rc = RTSpinlockCreate(&g_hNtProtectLock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "NtProtectLock"); + if (RT_FAILURE(rc)) + return VBoxDrvNtErr2NtStatus(rc); + g_NtProtectTree = NULL; + + NTSTATUS rcNt; + + /* The mutex protecting the error information. */ + RTListInit(&g_ErrorInfoHead); + rc = RTSemMutexCreate(&g_hErrorInfoLock); + if (RT_SUCCESS(rc)) + { + /* Image stuff + certificates. */ + rc = supHardenedWinInitImageVerifier(NULL); + if (RT_SUCCESS(rc)) + { + /* + * Intercept process creation and termination. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + rcNt = g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, FALSE /*fRemove*/); + else + rcNt = PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, FALSE /*fRemove*/); + if (NT_SUCCESS(rcNt)) + { + /* + * Intercept process and thread handle creation calls. + * The preferred method is only available on Vista SP1+. + */ + if (g_pfnObRegisterCallbacks && g_pfnObUnRegisterCallbacks) + { + static OB_OPERATION_REGISTRATION s_aObOperations[] = + { + { + 0, /* PsProcessType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ProcessHandlePre, + supdrvNtProtectCallback_ProcessHandlePost, + }, + { + 0, /* PsThreadType - imported, need runtime init, better do it explicitly. */ + OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE, + supdrvNtProtectCallback_ThreadHandlePre, + supdrvNtProtectCallback_ThreadHandlePost, + }, + }; + s_aObOperations[0].ObjectType = PsProcessType; + s_aObOperations[1].ObjectType = PsThreadType; + + static OB_CALLBACK_REGISTRATION s_ObCallbackReg = + { + /* .Version = */ OB_FLT_REGISTRATION_VERSION, + /* .OperationRegistrationCount = */ RT_ELEMENTS(s_aObOperations), + /* .Altitude.Length = */ { 0, + /* .Altitude.MaximumLength = */ 0, + /* .Altitude.Buffer = */ NULL }, + /* .RegistrationContext = */ NULL, + /* .OperationRegistration = */ &s_aObOperations[0] + }; + static WCHAR const *s_apwszAltitudes[] = /** @todo get a valid number */ + { + L"48596.98940", L"46935.19485", L"49739.39704", L"40334.74976", + L"66667.98940", L"69888.19485", L"69889.39704", L"60364.74976", + L"85780.98940", L"88978.19485", L"89939.39704", L"80320.74976", + L"329879.98940", L"326787.19485", L"328915.39704", L"320314.74976", + }; + + rcNt = STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; + for (uint32_t i = 0; i < RT_ELEMENTS(s_apwszAltitudes) && rcNt == STATUS_FLT_INSTANCE_ALTITUDE_COLLISION; i++) + { + s_ObCallbackReg.Altitude.Buffer = (WCHAR *)s_apwszAltitudes[i]; + s_ObCallbackReg.Altitude.Length = (uint16_t)RTUtf16Len(s_apwszAltitudes[i]) * sizeof(WCHAR); + s_ObCallbackReg.Altitude.MaximumLength = s_ObCallbackReg.Altitude.Length + sizeof(WCHAR); + + rcNt = g_pfnObRegisterCallbacks(&s_ObCallbackReg, &g_pvObCallbacksCookie); + if (NT_SUCCESS(rcNt)) + { + /* + * Happy ending. + */ + return STATUS_SUCCESS; + } + } + LogRel(("vboxdrv: ObRegisterCallbacks failed with rcNt=%#x\n", rcNt)); + g_pvObCallbacksCookie = NULL; + } + else + { + /* + * For the time being, we do not implement extra process + * protection on pre-Vista-SP1 systems as they are lacking + * necessary KPIs. XP is end of life, we do not wish to + * spend more time on it, so we don't put up a fuss there. + * Vista users without SP1 can install SP1 (or later), darn it, + * so refuse to load. + */ + /** @todo Hack up an XP solution - will require hooking kernel APIs or doing bad + * stuff to a couple of object types. */ +# ifndef VBOX_WITH_VISTA_NO_SP + if (g_uNtVerCombined >= SUP_NT_VER_VISTA) +# else + if (g_uNtVerCombined >= SUP_MAKE_NT_VER_COMBINED(6, 0, 6001, 0, 0)) +# endif + { + DbgPrint("vboxdrv: ObRegisterCallbacks was not found. Please make sure you got the latest updates and service packs installed\n"); + rcNt = STATUS_SXS_VERSION_CONFLICT; + } + else + { + Log(("vboxdrv: ObRegisterCallbacks was not found; ignored pre-Vista\n")); + return rcNt = STATUS_SUCCESS; + } + g_pvObCallbacksCookie = NULL; + } + + /* + * Drop process create/term notifications. + */ + if (g_pfnPsSetCreateProcessNotifyRoutineEx) + g_pfnPsSetCreateProcessNotifyRoutineEx(supdrvNtProtectCallback_ProcessCreateNotifyEx, TRUE /*fRemove*/); + else + PsSetCreateProcessNotifyRoutine(supdrvNtProtectCallback_ProcessCreateNotify, TRUE /*fRemove*/); + } + else + LogRel(("vboxdrv: PsSetCreateProcessNotifyRoutine%s failed with rcNt=%#x\n", + g_pfnPsSetCreateProcessNotifyRoutineEx ? "Ex" : "", rcNt)); + supHardenedWinTermImageVerifier(); + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSemMutexDestroy(g_hErrorInfoLock); + g_hErrorInfoLock = NIL_RTSEMMUTEX; + } + else + rcNt = VBoxDrvNtErr2NtStatus(rc); + + RTSpinlockDestroy(g_hNtProtectLock); + g_NtProtectTree = NIL_RTSPINLOCK; + return rcNt; +} + +#endif /* VBOX_WITH_HARDENING */ + 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 . +; +; 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 . + * + * 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 +#include +#ifndef SUP_CERTIFICATES_ONLY +# ifdef RT_OS_WINDOWS +# include +# 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 +# include + +# 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 +# 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 . + * + * 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 +# include +#else +# include +# include "Wintrust.h" +# include "Softpub.h" +# include "mscat.h" +# ifndef LOAD_LIBRARY_SEARCH_APPLICATION_DIR +# define LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 : "")); + 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 . + * + * 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 +# include +#else +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * 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 +# undef LogRelIt +# define LogRelIt(pvInst, fFlags, iGroup, fmtargs) do { } while (0) +#endif + +#define USE_NT_DEVICE_IO_CONTROL_FILE +#include + +#include +#include +#include +#include +#include +#include +#ifndef IN_SUP_HARDENED_R3 +# include +# include +# include +#endif +#include +#include +#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 + #include + #include + #include + + 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 . + * + * 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 + + +/********************************************************************************************************************************* +* 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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""));*/ + + /* + * 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"")); + 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"")); + } + + 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 + * + * (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 . +; +; 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#ifndef PROCESS_SET_LIMITED_INFORMATION +# define PROCESS_SET_LIMITED_INFORMATION 0x2000 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SUPLibInternal.h" +#include "win/SUPHardenedVerify-win.h" + + +/* + * assert.cpp + */ + +RTDATADECL(char) g_szRTAssertMsg1[1024]; +RTDATADECL(char) g_szRTAssertMsg2[4096]; +RTDATADECL(const char * volatile) g_pszRTAssertExpr; +RTDATADECL(const char * volatile) g_pszRTAssertFile; +RTDATADECL(uint32_t volatile) g_u32RTAssertLine; +RTDATADECL(const char * volatile) g_pszRTAssertFunction; + + +RTDECL(bool) RTAssertMayPanic(void) +{ + return true; +} + + +RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction) +{ + /* + * Fill in the globals. + */ + g_pszRTAssertExpr = pszExpr; + g_pszRTAssertFile = pszFile; + g_pszRTAssertFunction = pszFunction; + g_u32RTAssertLine = uLine; + RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1), + "\n!!Assertion Failed!!\n" + "Expression: %s\n" + "Location : %s(%d) %s\n", + pszExpr, pszFile, uLine, pszFunction); +} + + +RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va) +{ + RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, va); + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_CALLED_TRUSTED_MAIN) + supR3HardenedFatalMsg(g_pszRTAssertExpr, kSupInitOp_Misc, VERR_INTERNAL_ERROR, + "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); + else + supR3HardenedError(VERR_INTERNAL_ERROR, false/*fFatal*/, "%s%s", g_szRTAssertMsg1, g_szRTAssertMsg2); +} + + +/* + * Memory allocator. + */ + +/** The handle of the heap we're using. */ +static HANDLE g_hSupR3HardenedHeap = NULL; +/** Number of heaps used during early process init. */ +static uint32_t g_cSupR3HardenedEarlyHeaps = 0; +/** Early process init heaps. */ +static struct +{ + /** The heap handle. */ + RTHEAPSIMPLE hHeap; + /** The heap block pointer. */ + void *pvBlock; + /** The size of the heap block. */ + size_t cbBlock; + /** Number of active allocations on this heap. */ + size_t cAllocations; +} g_aSupR3HardenedEarlyHeaps[8]; + + +static uint32_t supR3HardenedEarlyFind(void *pv) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if ((uintptr_t)pv - (uintptr_t)g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock < g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock) + return iHeap; + return UINT32_MAX; +} + + +static void supR3HardenedEarlyCompact(void) RT_NOTHROW_DEF +{ + uint32_t iHeap = g_cSupR3HardenedEarlyHeaps; + while (iHeap-- > 0) + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations == 0) + { + PVOID pvMem = g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock; + SIZE_T cbMem = g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock; + if (iHeap + 1 < g_cSupR3HardenedEarlyHeaps) + g_aSupR3HardenedEarlyHeaps[iHeap] = g_aSupR3HardenedEarlyHeaps[g_cSupR3HardenedEarlyHeaps - 1]; + g_cSupR3HardenedEarlyHeaps--; + + NTSTATUS rcNt = NtFreeVirtualMemory(NtCurrentProcess(), &pvMem, &cbMem, MEM_RELEASE); + Assert(NT_SUCCESS(rcNt)); RT_NOREF_PV(rcNt); + SUP_DPRINTF(("supR3HardenedEarlyCompact: Removed heap %#u (%#p LB %#zx)\n", iHeap, pvMem, cbMem)); + } +} + + +static void *supR3HardenedEarlyAlloc(size_t cb, bool fZero) RT_NOTHROW_DEF +{ + /* + * Try allocate on existing heaps. + */ + void *pv; + uint32_t iHeap = 0; + while (iHeap < g_cSupR3HardenedEarlyHeaps) + { + if (fZero) + pv = RTHeapSimpleAllocZ(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, cb, 0); + if (pv) + { + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations++; +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; + } + iHeap++; + } + + /* + * Add another heap. + */ + if (iHeap == RT_ELEMENTS(g_aSupR3HardenedEarlyHeaps)) + supR3HardenedFatal("Early heap table is full (cb=%#zx).\n", cb); + SIZE_T cbBlock = iHeap == 0 ? _1M : g_aSupR3HardenedEarlyHeaps[iHeap - 1].cbBlock * 2; + while (cbBlock <= cb * 2) + cbBlock *= 2; + + PVOID pvBlock = NULL; + NTSTATUS rcNt = NtAllocateVirtualMemory(NtCurrentProcess(), &pvBlock, 0 /*ZeroBits*/, &cbBlock, MEM_COMMIT, PAGE_READWRITE); + if (!NT_SUCCESS(rcNt)) + supR3HardenedFatal("NtAllocateVirtualMemory(,,,%#zx,,) failed: rcNt=%#x\n", cbBlock, rcNt); + SUP_DPRINTF(("New simple heap: #%u %p LB %#zx (for %zu allocation)\n", iHeap, pvBlock, cbBlock, cb)); + + RTHEAPSIMPLE hHeap; + int rc = RTHeapSimpleInit(&hHeap, pvBlock, cbBlock); + if (RT_FAILURE(rc)) + supR3HardenedFatal("RTHeapSimpleInit(,%p,%#zx) failed: rc=%#x\n", pvBlock, cbBlock, rc); + + if (fZero) + pv = RTHeapSimpleAllocZ(hHeap, cb, 0); + else + pv = RTHeapSimpleAlloc(hHeap, cb, 0); + if (!pv) + supR3HardenedFatal("RTHeapSimpleAlloc[Z] failed allocating %#zx bytes on a %#zu heap.\n", cb, cbBlock); + + g_aSupR3HardenedEarlyHeaps[iHeap].pvBlock = pvBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cbBlock = cbBlock; + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations = 1; + g_aSupR3HardenedEarlyHeaps[iHeap].hHeap = hHeap; + + Assert(g_cSupR3HardenedEarlyHeaps == iHeap); + g_cSupR3HardenedEarlyHeaps = iHeap + 1; + +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx - alloc\n", pv, cb)); +#endif + return pv; +} + + +/** + * Lazy heap initialization function. + * + * @returns Heap handle. + */ +static HANDLE supR3HardenedHeapInit(void) RT_NOTHROW_DEF +{ + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED); + HANDLE hHeap = RtlCreateHeap(HEAP_GROWABLE | HEAP_CLASS_PRIVATE, NULL /*HeapBase*/, + 0 /*ReserveSize*/, 0 /*CommitSize*/, NULL /*Lock*/, NULL /*Parameters*/); + if (hHeap) + { + g_hSupR3HardenedHeap = hHeap; + return hHeap; + } + + supR3HardenedFatal("RtlCreateHeap failed.\n"); + /* not reached */ +} + + +/** + * Compacts the heaps before enter wait for parent/child. + */ +DECLHIDDEN(void) supR3HardenedWinCompactHeaps(void) +{ + if (g_hSupR3HardenedHeap) + RtlCompactHeap(g_hSupR3HardenedHeap, 0 /*dwFlags*/); + RtlCompactHeap(GetProcessHeap(), 0 /*dwFlags*/); + supR3HardenedEarlyCompact(); +} + + + +#undef RTMemTmpAllocTag +RTDECL(void *) RTMemTmpAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocTag(cb, pszTag); +} + + +#undef RTMemTmpAllocZTag +RTDECL(void *) RTMemTmpAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + return RTMemAllocZTag(cb, pszTag); +} + + +#undef RTMemTmpFree +RTDECL(void) RTMemTmpFree(void *pv) RT_NO_THROW_DEF +{ + RTMemFree(pv); +} + + +#undef RTMemAllocTag +RTDECL(void *) RTMemAllocTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, false /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, 0 /*fFlags*/, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocZTag +RTDECL(void *) RTMemAllocZTag(size_t cb, const char *pszTag) RT_NO_THROW_DEF +{ + RT_NOREF1(pszTag); + HANDLE hHeap = g_hSupR3HardenedHeap; + if (!hHeap) + { + if ( g_fSupEarlyProcessInit + && g_enmSupR3HardenedMainState <= SUPR3HARDENEDMAINSTATE_WIN_EP_CALLED) + return supR3HardenedEarlyAlloc(cb, true /*fZero*/); + hHeap = supR3HardenedHeapInit(); + } + + void *pv = RtlAllocateHeap(hHeap, HEAP_ZERO_MEMORY, cb); + if (!pv) + supR3HardenedFatal("RtlAllocateHeap failed to allocate %zu bytes.\n", cb); + return pv; +} + + +#undef RTMemAllocVarTag +RTDECL(void *) RTMemAllocVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocTag(cbAligned, pszTag); +} + + +#undef RTMemAllocZVarTag +RTDECL(void *) RTMemAllocZVarTag(size_t cbUnaligned, const char *pszTag) RT_NO_THROW_DEF +{ + size_t cbAligned; + if (cbUnaligned >= 16) + cbAligned = RT_ALIGN_Z(cbUnaligned, 16); + else + cbAligned = RT_ALIGN_Z(cbUnaligned, sizeof(void *)); + return RTMemAllocZTag(cbAligned, pszTag); +} + + +#undef RTMemReallocTag +RTDECL(void *) RTMemReallocTag(void *pvOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF +{ + if (!pvOld) + return RTMemAllocZTag(cbNew, pszTag); + + void *pv; + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pvOld); + if (iHeap != UINT32_MAX) + { +#if 0 /* RTHeapSimpleRealloc is not implemented */ + /* If this is before we can use a regular heap, we try resize + within the simple heap. (There are a lot of array growing in + the ASN.1 code.) */ + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + { + pv = RTHeapSimpleRealloc(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld, cbNew, 0); + if (pv) + { +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p - realloc\n", pvNew, cbNew, pvOld)); +# endif + return pv; + } + } +#endif + + /* Either we can't reallocate it on the same simple heap, or we're + past hardened main and wish to migrate everything over on the + real heap. */ + size_t cbOld = RTHeapSimpleSize(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + pv = RTMemAllocTag(cbNew, pszTag); + if (pv) + { + memcpy(pv, pvOld, RT_MIN(cbOld, cbNew)); + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pvOld); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + } +# ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p LB %#zx, was %p %LB %#zx - realloc\n", pv, cbNew, pvOld, cbOld)); +# endif + return pv; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + /* Allocate from the regular heap. */ + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + pv = RtlReAllocateHeap(hHeap, 0 /*dwFlags*/, pvOld, cbNew); + if (!pv) + supR3HardenedFatal("RtlReAllocateHeap failed to allocate %zu bytes.\n", cbNew); + return pv; +} + + +#undef RTMemFree +RTDECL(void) RTMemFree(void *pv) RT_NO_THROW_DEF +{ + if (pv) + { + if (g_fSupEarlyProcessInit) + { + uint32_t iHeap = supR3HardenedEarlyFind(pv); + if (iHeap != UINT32_MAX) + { +#ifdef SUPR3HARDENED_EARLY_HEAP_TRACE + SUP_DPRINTF(("Early heap: %p - free\n", pv)); +#endif + RTHeapSimpleFree(g_aSupR3HardenedEarlyHeaps[iHeap].hHeap, pv); + if (g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations) + g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations--; + if ( !g_aSupR3HardenedEarlyHeaps[iHeap].cAllocations + && g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) + supR3HardenedEarlyCompact(); + return; + } + Assert(g_enmSupR3HardenedMainState >= SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED); + } + + HANDLE hHeap = g_hSupR3HardenedHeap; + Assert(hHeap != NULL); + RtlFreeHeap(hHeap, 0 /* dwFlags*/, pv); + } +} + + +/* + * Simplified version of RTMemWipeThoroughly that avoids dragging in the + * random number code. + */ + +RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF +{ + size_t cPasses = RT_MIN(cMinPasses, 6); + static const uint32_t s_aPatterns[] = { 0x00, 0xaa, 0x55, 0xff, 0xf0, 0x0f, 0xcc, 0x3c, 0xc3 }; + uint32_t iPattern = 0; + do + { + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + + memset(pv, s_aPatterns[iPattern], cb); + iPattern = (iPattern + 1) % RT_ELEMENTS(s_aPatterns); + ASMMemoryFence(); + } while (cPasses-- > 0); + + memset(pv, 0xff, cb); + ASMMemoryFence(); +} + + + +/* + * path-win.cpp + */ + +RTDECL(int) RTPathGetCurrent(char *pszPath, size_t cbPath) +{ + int rc; + if (g_enmSupR3HardenedMainState < SUPR3HARDENEDMAINSTATE_WIN_IMPORTS_RESOLVED) +/** @todo Rainy day: improve this by checking the process parameter block + * (needs to be normalized). */ + rc = RTStrCopy(pszPath, cbPath, "C:\\"); + else + { + /* + * GetCurrentDirectory may in some cases omit the drive letter, according + * to MSDN, thus the GetFullPathName call. + */ + RTUTF16 wszCurPath[RTPATH_MAX]; + if (GetCurrentDirectoryW(RTPATH_MAX, wszCurPath)) + { + RTUTF16 wszFullPath[RTPATH_MAX]; + if (GetFullPathNameW(wszCurPath, RTPATH_MAX, wszFullPath, NULL)) + rc = RTUtf16ToUtf8Ex(&wszFullPath[0], RTSTR_MAX, &pszPath, cbPath, NULL); + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + else + rc = RTErrConvertFromWin32(RtlGetLastWin32Error()); + } + return rc; +} + 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 . + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DEBUG_bird +# include +#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 [-brief]\n" + " Displays the version.\n" + "VBoxSupSvc [...]\n" + " Displays this help screen.\n" + "\n" + "VBoxSupSvc \n" + " Installs the service.\n" + "VBoxSupSvc \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 . + * + * 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 +#include + +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 . +; +; 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include + + +/** + * 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 . + * + * 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 +#include + +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) + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/Makefile.kmk b/src/VBox/HostDrivers/VBoxNetAdp/Makefile.kmk new file mode 100644 index 00000000..62c8baf4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/Makefile.kmk @@ -0,0 +1,228 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Network Adapter Driver (VBoxNetAdp). +# + +# +# 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 . +# +# 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 + +if1of ($(KBUILD_TARGET), solaris darwin freebsd) + # + # VBoxNetAdp - Virtual Network Adapter + # Note! On Solaris the name has to be <= 8 chars long. + # + ifdef VBOX_WITH_VBOXDRV + SYSMODS += VBoxNetAdp + VBoxNetAdp_TEMPLATE = VBoxR0Drv + VBoxNetAdp_INST = $(INST_VBOXNETADP)$(if $(eq $(KBUILD_TARGET),darwin),Contents/MacOS/) + VBoxNetAdp_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXNETADP)) + VBoxNetAdp_NAME.solaris = vboxnet + VBoxNetAdp_NAME.freebsd = vboxnetadp + VBoxNetAdp_DEPS.solaris += $(VBOX_SVN_REV_KMK) + VBoxNetAdp_DEFS = IN_RT_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) IN_SUP_STATIC + #VBoxNetAdp_LDFLAGS.darwin = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded + VBoxNetAdp_LDFLAGS.solaris += -N misc/gld -N drv/vboxdrv + VBoxNetAdp_INCS = \ + . + VBoxNetAdp_SOURCES.darwin = \ + darwin/VBoxNetAdp-darwin.cpp \ + VBoxNetAdp.c + VBoxNetAdp_SOURCES.solaris = \ + solaris/VBoxNetAdp-solaris.c + VBoxNetAdp_SOURCES.freebsd = \ + freebsd/VBoxNetAdp-freebsd.c \ + VBoxNetAdp.c + VBoxNetAdp_SOURCES = + #VBoxNetAdp_SOURCES = \ + # VBoxNetAdp.c + VBoxNetAdp_LIBS += \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + endif +endif + +# +# Darwin extras. +# +if "$(KBUILD_TARGET)" == "darwin" && defined(VBOX_WITH_VBOXDRV) + INSTALLS += VBoxNetAdp.kext + VBoxNetAdp.kext_INST = $(INST_VBOXNETADP)Contents/ + VBoxNetAdp.kext_SOURCES = $(VBoxNetAdp.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetAdp.kext_CLEAN = $(VBoxNetAdp.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetAdp.kext_BLDDIRS = $(VBoxNetAdp.kext_0_OUTDIR)/Contents/ + + $$(VBoxNetAdp.kext_0_OUTDIR)/Contents/Info.plist: $(PATH_SUB_CURRENT)/darwin/Info.plist $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetAdp,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \ + -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \ + -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \ + -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+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,VBoxNetAdp) + + INSTALLS.darwin += Scripts-darwin-adp + Scripts-darwin-adp_INST = $(INST_DIST) + Scripts-darwin-adp_EXEC_SOURCES = \ + darwin/loadnetadp.sh +endif # darwin && host-drivers + +ifeq ($(KBUILD_TARGET),win) + # + # VBoxNetAdp6.sys - The VirtualBox Adapter miniport driver. + # + SYSMODS.win += VBoxNetAdp6 + VBoxNetAdp6_TEMPLATE = VBoxR0Drv + if defined(VBOX_SIGNING_MODE) + VBoxNetAdp6_INSTTYPE.win = none + VBoxNetAdp6_DEBUG_INSTTYPE.win = both + endif + VBoxNetAdp6_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetAdp6_INCS := $(PATH_SUB_CURRENT) + VBoxNetAdp6_SDKS = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetAdp6_SOURCES = \ + win/VBoxNetAdp-win.cpp \ + win/VBoxNetAdp-win.rc + VBoxNetAdp6_DEFS += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetAdp6_DEFS += NDIS60_MINIPORT=1 NDIS60=1 + VBoxNetAdp6_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetAdp6_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetAdp6_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ndis.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetAdp6_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + + + INSTALLS.win += VBoxNetAdp6-inf + VBoxNetAdp6-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetAdp6-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.inf + VBoxNetAdp6-inf_CLEAN = $(VBoxNetAdp6-inf_SOURCES) + VBoxNetAdp6-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetAdp6Cat.dir + + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.inf: $(PATH_SUB_CURRENT)/win/VBoxNetAdp6.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetAdp6-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetAdp6-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.sys \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.cat \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.cat=>VBoxNetAdp6-PreW10.cat + + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.sys: $$(VBoxNetAdp6_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.cat: \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.sys \ + $(PATH_TARGET)/VBoxNetAdp6Cat.dir/VBoxNetAdp6.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetFlt-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # ifdef VBOX_SIGNING_MODE + +endif #ifeq ($(KBUILD_TARGET), win) + +ifeq ($(KBUILD_TARGET),linux) + # + # Install source files for compilation on Linux. + # files_vboxnetadp defines VBOX_VBOXNETADP_SOURCES. + # + INSTALLS += VBoxNetAdp-src + include $(PATH_SUB_CURRENT)/linux/files_vboxnetadp + VBoxNetAdp-src_INST = bin/src/vboxnetadp/ + VBoxNetAdp-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETADP_SOURCES)) \ + $(VBoxNetAdp-src_0_OUTDIR)/Makefile + VBoxNetAdp-src_CLEAN = \ + $(VBoxNetAdp-src_0_OUTDIR)/Makefile \ + $(PATH_TARGET)/VBoxNetAdp-src-1.dep + + # Scripts needed for building the kernel modules + includedep $(PATH_TARGET)/VBoxNetAdp-src-1.dep + $$(VBoxNetAdp-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/linux/Makefile \ + $$(if $$(eq $$(VBoxNetAdp/linux/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@ $< + else + $(QUIET)$(CP) -f $< $@ + endif + %$(QUIET2)$(APPEND) -t '$(PATH_TARGET)/VBoxNetAdp-src-1.dep' 'VBoxNetAdp/linux/Makefile_VBOX_HARDENED=$(VBOX_WITH_HARDENING)' + + # + # Build test for the linux host kernel modules. + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,VBoxNetAdp-src,vboxdrv-src,) +endif # linux + +ifeq ($(KBUILD_TARGET),freebsd) + # + # Install source files for compilation on FreeBSD. + # files_vboxnetadp defines VBOX_VBOXNETADP_SOURCES. + # + INSTALLS += VBoxNetAdp-src + include $(PATH_SUB_CURRENT)/freebsd/files_vboxnetadp + VBoxNetAdp-src_INST = bin/src/vboxnetadp/ + VBoxNetAdp-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETADP_SOURCES)) \ + $(VBoxNetAdp-src_0_OUTDIR)/Makefile + VBoxNetAdp-src_CLEAN = \ + $(VBoxNetAdp-src_0_OUTDIR)/Makefile + + $$(VBoxNetAdp-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/freebsd/Makefile \ + $$(if $$(eq $$(VBoxNetAdp/freebsd/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@ $< + else + $(QUIET)$(CP) -f $< $@ + endif + +endif # freebsd + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c b/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c new file mode 100644 index 00000000..ea3f7766 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c @@ -0,0 +1,238 @@ +/* $Id: VBoxNetAdp.c $ */ +/** @file + * VBoxNetAdp - Virtual Network Adapter Driver (Host), 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 . + * + * 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_netadp VBoxNetAdp - Network Adapter + * + * This is a kernel module that creates a virtual interface that can be attached + * to an internal network. + * + * In the big picture we're one of the three trunk interface on the internal + * network, the one named "TAP Interface": @image html Networking_Overview.gif + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_NET_ADP_DRV +#include "VBoxNetAdpInternal.h" + +#include +#include +#include + + +VBOXNETADP g_aAdapters[VBOXNETADP_MAX_INSTANCES]; +static uint8_t g_aUnits[VBOXNETADP_MAX_UNITS/8]; + + +DECLINLINE(int) vboxNetAdpGetUnitByName(const char *pcszName) +{ + uint32_t iUnit = RTStrToUInt32(pcszName + sizeof(VBOXNETADP_NAME) - 1); + bool fOld; + + if (iUnit >= VBOXNETADP_MAX_UNITS) + return -1; + + fOld = ASMAtomicBitTestAndSet(g_aUnits, iUnit); + return fOld ? -1 : (int)iUnit; +} + +DECLINLINE(int) vboxNetAdpGetNextAvailableUnit(void) +{ + bool fOld; + int iUnit; + /* There is absolutely no chance that all units are taken */ + do { + iUnit = ASMBitFirstClear(g_aUnits, VBOXNETADP_MAX_UNITS); + if (iUnit < 0) + break; + fOld = ASMAtomicBitTestAndSet(g_aUnits, iUnit); + } while (fOld); + + return iUnit; +} + +DECLINLINE(void) vboxNetAdpReleaseUnit(int iUnit) +{ + bool fSet = ASMAtomicBitTestAndClear(g_aUnits, iUnit); + NOREF(fSet); + Assert(fSet); +} + +/** + * Generate a suitable MAC address. + * + * @param pThis The instance. + * @param pMac Where to return the MAC address. + */ +DECLHIDDEN(void) vboxNetAdpComposeMACAddress(PVBOXNETADP pThis, PRTMAC pMac) +{ + /* Use a locally administered version of the OUI we use for the guest NICs. */ + pMac->au8[0] = 0x08 | 2; + pMac->au8[1] = 0x00; + pMac->au8[2] = 0x27; + + pMac->au8[3] = 0; /* pThis->iUnit >> 16; */ + pMac->au8[4] = 0; /* pThis->iUnit >> 8; */ + pMac->au8[5] = pThis->iUnit; +} + +int vboxNetAdpCreate(PVBOXNETADP *ppNew, const char *pcszName) +{ + int rc; + unsigned i; + for (i = 0; i < RT_ELEMENTS(g_aAdapters); i++) + { + PVBOXNETADP pThis = &g_aAdapters[i]; + + if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pThis->enmState, kVBoxNetAdpState_Transitional, kVBoxNetAdpState_Invalid)) + { + RTMAC Mac; + /* Found an empty slot -- use it. */ + Log(("vboxNetAdpCreate: found empty slot: %d\n", i)); + if (pcszName) + { + Log(("vboxNetAdpCreate: using name: %s\n", pcszName)); + pThis->iUnit = vboxNetAdpGetUnitByName(pcszName); + strncpy(pThis->szName, pcszName, sizeof(pThis->szName) - 1); + pThis->szName[sizeof(pThis->szName) - 1] = '\0'; + } + else + { + pThis->iUnit = vboxNetAdpGetNextAvailableUnit(); + pThis->szName[0] = '\0'; + } + if (pThis->iUnit < 0) + rc = VERR_INVALID_PARAMETER; + else + { + vboxNetAdpComposeMACAddress(pThis, &Mac); + rc = vboxNetAdpOsCreate(pThis, &Mac); + Log(("vboxNetAdpCreate: pThis=%p pThis->iUnit=%d, pThis->szName=%s\n", + pThis, pThis->iUnit, pThis->szName)); + } + if (RT_SUCCESS(rc)) + { + *ppNew = pThis; + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, kVBoxNetAdpState_Active); + Log2(("VBoxNetAdpCreate: Created %s\n", g_aAdapters[i].szName)); + } + else + { + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, kVBoxNetAdpState_Invalid); + Log(("vboxNetAdpCreate: vboxNetAdpOsCreate failed with '%Rrc'.\n", rc)); + } + for (i = 0; i < RT_ELEMENTS(g_aAdapters); i++) + Log2(("VBoxNetAdpCreate: Scanning entry: state=%d unit=%d name=%s\n", + g_aAdapters[i].enmState, g_aAdapters[i].iUnit, g_aAdapters[i].szName)); + return rc; + } + } + Log(("vboxNetAdpCreate: no empty slots!\n")); + + /* All slots in adapter array are busy. */ + return VERR_OUT_OF_RESOURCES; +} + +int vboxNetAdpDestroy(PVBOXNETADP pThis) +{ + int rc = VINF_SUCCESS; + + if (!ASMAtomicCmpXchgU32((uint32_t volatile *)&pThis->enmState, kVBoxNetAdpState_Transitional, kVBoxNetAdpState_Active)) + return VERR_INTNET_FLT_IF_BUSY; + + Assert(pThis->iUnit >= 0 && pThis->iUnit < VBOXNETADP_MAX_UNITS); + vboxNetAdpOsDestroy(pThis); + vboxNetAdpReleaseUnit(pThis->iUnit); + pThis->iUnit = -1; + pThis->szName[0] = '\0'; + + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, kVBoxNetAdpState_Invalid); + + return rc; +} + +int vboxNetAdpInit(void) +{ + unsigned i; + /* + * Init common members and call OS-specific init. + */ + memset(g_aUnits, 0, sizeof(g_aUnits)); + memset(g_aAdapters, 0, sizeof(g_aAdapters)); + LogFlow(("vboxnetadp: max host-only interfaces supported: %d (%d bytes)\n", + VBOXNETADP_MAX_INSTANCES, sizeof(g_aAdapters))); + for (i = 0; i < RT_ELEMENTS(g_aAdapters); i++) + { + g_aAdapters[i].enmState = kVBoxNetAdpState_Invalid; + g_aAdapters[i].iUnit = -1; + vboxNetAdpOsInit(&g_aAdapters[i]); + } + + return VINF_SUCCESS; +} + +/** + * Finds an adapter by its name. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pszName The name of the instance. + */ +PVBOXNETADP vboxNetAdpFindByName(const char *pszName) +{ + unsigned i; + + for (i = 0; i < RT_ELEMENTS(g_aAdapters); i++) + { + PVBOXNETADP pThis = &g_aAdapters[i]; + Log2(("VBoxNetAdp: Scanning entry: state=%d name=%s\n", pThis->enmState, pThis->szName)); + if ( strcmp(pThis->szName, pszName) == 0 + && ASMAtomicReadU32((uint32_t volatile *)&pThis->enmState) == kVBoxNetAdpState_Active) + return pThis; + } + return NULL; +} + +void vboxNetAdpShutdown(void) +{ + unsigned i; + + /* Remove virtual adapters */ + for (i = 0; i < RT_ELEMENTS(g_aAdapters); i++) + vboxNetAdpDestroy(&g_aAdapters[i]); +} diff --git a/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h b/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h new file mode 100644 index 00000000..d50fa039 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h @@ -0,0 +1,202 @@ +/* $Id: VBoxNetAdpInternal.h $ */ +/** @file + * VBoxNetAdp - Network Filter Driver (Host), 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 . + * + * 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_VBoxNetAdp_VBoxNetAdpInternal_h +#define VBOX_INCLUDED_SRC_VBoxNetAdp_VBoxNetAdpInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include +#include + + +RT_C_DECLS_BEGIN + +/** Pointer to the globals. */ +typedef struct VBOXNETADPGLOBALS *PVBOXNETADPGLOBALS; + +#define VBOXNETADP_MAX_INSTANCES 128 +#define VBOXNETADP_MAX_UNITS 128 +#define VBOXNETADP_NAME "vboxnet" +#define VBOXNETADP_MAX_NAME_LEN 32 +#define VBOXNETADP_MTU 1500 +#if defined(RT_OS_DARWIN) +# define VBOXNETADP_MAX_FAMILIES 4 +# define VBOXNETADP_DETACH_TIMEOUT 500 +#endif + +#define VBOXNETADP_CTL_DEV_NAME "vboxnetctl" +#define VBOXNETADP_CTL_ADD _IOWR('v', 1, VBOXNETADPREQ) +#define VBOXNETADP_CTL_REMOVE _IOW('v', 2, VBOXNETADPREQ) + +typedef struct VBoxNetAdpReq +{ + char szName[VBOXNETADP_MAX_NAME_LEN]; +} VBOXNETADPREQ; +typedef VBOXNETADPREQ *PVBOXNETADPREQ; + +/** + * Void entries mark vacant slots in adapter array. Valid entries are busy slots. + * As soon as slot is being modified its state changes to transitional. + * An entry in transitional state must only be accessed by the thread that + * put it to this state. + */ +/** + * To avoid races on adapter fields we stick to the following rules: + * - rewrite: Int net port calls are serialized + * - No modifications are allowed on busy adapters (deactivate first) + * Refuse to destroy adapter until it gets to available state + * - No transfers (thus getting busy) on inactive adapters + * - Init sequence: void->available->connected->active + 1) Create + 2) Connect + 3) Activate + * - Destruction sequence: active->connected->available->void + 1) Deactivate + 2) Disconnect + 3) Destroy +*/ + +enum VBoxNetAdpState +{ + kVBoxNetAdpState_Invalid, + kVBoxNetAdpState_Transitional, + kVBoxNetAdpState_Active, + kVBoxNetAdpState_32BitHack = 0x7FFFFFFF +}; +typedef enum VBoxNetAdpState VBOXNETADPSTATE; + +struct VBoxNetAdapter +{ + /** Denotes availability of this slot in adapter array. */ + VBOXNETADPSTATE enmState; + /** Corresponds to the digit at the end of device name. */ + int iUnit; + + union + { +#ifdef VBOXNETADP_OS_SPECFIC + struct + { +# if defined(RT_OS_DARWIN) + /** @name Darwin instance data. + * @{ */ + /** Event to signal detachment of interface. */ + RTSEMEVENT hEvtDetached; + /** Pointer to Darwin interface structure. */ + ifnet_t pIface; + /** MAC address. */ + RTMAC Mac; + /** @} */ +# elif defined(RT_OS_LINUX) + /** @name Darwin instance data. + * @{ */ + /** Pointer to Linux network device structure. */ + struct net_device *pNetDev; + /** @} */ +# elif defined(RT_OS_FREEBSD) + /** @name FreeBSD instance data. + * @{ */ + struct ifnet *ifp; + /** @} */ +# else +# error PORTME +# endif + } s; +#endif + /** Union alignment to a pointer. */ + void *pvAlign; + /** Padding. */ + uint8_t abPadding[64]; + } u; + /** The interface name. */ + char szName[VBOXNETADP_MAX_NAME_LEN]; +}; +typedef struct VBoxNetAdapter VBOXNETADP; +typedef VBOXNETADP *PVBOXNETADP; +/* Paranoia checks for alignment and padding. */ +AssertCompileMemberAlignment(VBOXNETADP, u, ARCH_BITS/8); +AssertCompileMemberAlignment(VBOXNETADP, szName, ARCH_BITS/8); +AssertCompileMembersSameSize(VBOXNETADP, u, VBOXNETADP, u.abPadding); + +DECLHIDDEN(int) vboxNetAdpInit(void); +DECLHIDDEN(void) vboxNetAdpShutdown(void); +DECLHIDDEN(int) vboxNetAdpCreate(PVBOXNETADP *ppNew, const char *pcszName); +DECLHIDDEN(int) vboxNetAdpDestroy(PVBOXNETADP pThis); +DECLHIDDEN(PVBOXNETADP) vboxNetAdpFindByName(const char *pszName); +DECLHIDDEN(void) vboxNetAdpComposeMACAddress(PVBOXNETADP pThis, PRTMAC pMac); + + +/** + * This is called to perform OS-specific structure initializations. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetAdpOsInit(PVBOXNETADP pThis); + +/** + * Counter part to vboxNetAdpOsCreate(). + * + * @param pThis The new instance. + * + * @remarks May own the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(void) vboxNetAdpOsDestroy(PVBOXNETADP pThis); + +/** + * This is called to attach to the actual host interface + * after linking the instance into the list. + * + * @return IPRT status code. + * @param pThis The new instance. + * @param pMac The MAC address to use for this instance. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetAdpOsCreate(PVBOXNETADP pThis, PCRTMAC pMac); + + + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetAdp_VBoxNetAdpInternal_h */ + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/darwin/Info.plist b/src/VBox/HostDrivers/VBoxNetAdp/darwin/Info.plist new file mode 100644 index 00000000..c0b20e7b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/darwin/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion English + CFBundleExecutable VBoxNetAdp + CFBundleIdentifier org.virtualbox.kext.VBoxNetAdp + CFBundleInfoDictionaryVersion 6.0 + CFBundleName VBoxNetAdp + CFBundlePackageType KEXT + CFBundleSignature ???? + CFBundleGetInfoString @VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2008-@VBOX_C_YEAR@ @VBOX_VENDOR@ + CFBundleVersion @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + CFBundleShortVersionString @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + IOKitPersonalities + + + OSBundleLibraries + + com.apple.kpi.bsd 8.8.1 + com.apple.kpi.mach 8.8.1 + com.apple.kpi.libkern 8.8.1 + org.virtualbox.kext.VBoxDrv @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + + + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/darwin/Makefile.kup b/src/VBox/HostDrivers/VBoxNetAdp/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetAdp/darwin/VBoxNetAdp-darwin.cpp b/src/VBox/HostDrivers/VBoxNetAdp/darwin/VBoxNetAdp-darwin.cpp new file mode 100644 index 00000000..ecf4d1f5 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/darwin/VBoxNetAdp-darwin.cpp @@ -0,0 +1,529 @@ +/* $Id: VBoxNetAdp-darwin.cpp $ */ +/** @file + * VBoxNetAdp - Virtual Network Adapter Driver (Host), 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 . + * + * 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_NET_ADP_DRV +#include "../../../Runtime/r0drv/darwin/the-darwin-kernel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../darwin/VBoxNetSend.h" + +#include +RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */ +#include +RT_C_DECLS_END + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +RT_C_DECLS_BEGIN +#include +RT_C_DECLS_END + +#define VBOXNETADP_OS_SPECFIC 1 +#include "../VBoxNetAdpInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The maximum number of SG segments. + * Used to prevent stack overflow and similar bad stuff. */ +#define VBOXNETADP_DARWIN_MAX_SEGS 32 +#define VBOXNETADP_DARWIN_MAX_FAMILIES 4 +#define VBOXNETADP_DARWIN_NAME "vboxnet" +#define VBOXNETADP_DARWIN_MTU 1500 +#define VBOXNETADP_DARWIN_DETACH_TIMEOUT 500 + +#define VBOXNETADP_FROM_IFACE(iface) ((PVBOXNETADP) ifnet_softc(iface)) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxNetAdpDarwinStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxNetAdpDarwinStop(struct kmod_info *pKModInfo, void *pvData); +RT_C_DECLS_END + +static int VBoxNetAdpDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int VBoxNetAdpDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess); +static int VBoxNetAdpDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess); + + +/********************************************************************************************************************************* +* 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(VBoxNetAdp, VBOX_VERSION_STRING, _start, _stop) +DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = VBoxNetAdpDarwinStart; +DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = VBoxNetAdpDarwinStop; +DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + +/** + * The (common) global data. + */ +static int g_nCtlDev = -1; /* Major dev number */ +static void *g_hCtlDev = 0; /* FS dev handle */ + +/** + * The character device switch table for the driver. + */ +static struct cdevsw g_ChDev = +{ + /*.d_open = */VBoxNetAdpDarwinOpen, + /*.d_close = */VBoxNetAdpDarwinClose, + /*.d_read = */eno_rdwrt, + /*.d_write = */eno_rdwrt, + /*.d_ioctl = */VBoxNetAdpDarwinIOCtl, + /*.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 +}; + + + +static void vboxNetAdpDarwinComposeUUID(PVBOXNETADP pThis, PRTUUID pUuid) +{ + /* Generate UUID from name and MAC address. */ + RTUuidClear(pUuid); + memcpy(pUuid->au8, "vboxnet", 7); + pUuid->Gen.u8ClockSeqHiAndReserved = (pUuid->Gen.u8ClockSeqHiAndReserved & 0x3f) | 0x80; + pUuid->Gen.u16TimeHiAndVersion = (pUuid->Gen.u16TimeHiAndVersion & 0x0fff) | 0x4000; + pUuid->Gen.u8ClockSeqLow = pThis->iUnit; + vboxNetAdpComposeMACAddress(pThis, (PRTMAC)pUuid->Gen.au8Node); +} + +static errno_t vboxNetAdpDarwinOutput(ifnet_t pIface, mbuf_t pMBuf) +{ + /* + * We are a dummy interface with all the real work done in + * VBoxNetFlt bridged networking filter. If anything makes it + * this far, it must be a broadcast or a packet for an unknown + * guest that intnet didn't know where to dispatch. In that case + * we must still do the BPF tap and stats. + */ + bpf_tap_out(pIface, DLT_EN10MB, pMBuf, NULL, 0); + ifnet_stat_increment_out(pIface, 1, mbuf_len(pMBuf), 0); + + mbuf_freem_list(pMBuf); + return 0; +} + +static void vboxNetAdpDarwinDetach(ifnet_t pIface) +{ + PVBOXNETADP pThis = VBOXNETADP_FROM_IFACE(pIface); + Assert(pThis); + Log2(("vboxNetAdpDarwinDetach: Signaling detach to vboxNetAdpUnregisterDevice.\n")); + /* Let vboxNetAdpDarwinUnregisterDevice know that the interface has been detached. */ + RTSemEventSignal(pThis->u.s.hEvtDetached); +} + +static errno_t vboxNetAdpDarwinDemux(ifnet_t pIface, mbuf_t pMBuf, + char *pFrameHeader, + protocol_family_t *pProtocolFamily) +{ + /* + * Anything we get here comes from VBoxNetFlt bridged networking + * filter where it has already been accounted for and fed to bpf. + */ + return ether_demux(pIface, pMBuf, pFrameHeader, pProtocolFamily); +} + + +static errno_t vboxNetAdpDarwinIfIOCtl(ifnet_t pIface, unsigned long uCmd, void *pvData) +{ + errno_t error = 0; + + if (pvData == NULL) + { + /* + * Common pattern in the kernel code is to make changes in the + * net layer and then notify the device driver by calling its + * ioctl function with NULL parameter, e.g.: + * + * ifnet_set_flags(interface, ...); + * ifnet_ioctl(interface, 0, SIOCSIFFLAGS, NULL); + * + * These are no-ops for us, so tell the caller we succeeded + * because some callers do check that return value. + */ + switch (uCmd) + { + case SIOCSIFFLAGS: + Log2(("VBoxNetAdp: %s%d: SIOCSIFFLAGS (null): flags = 0x%04hx\n", + ifnet_name(pIface), ifnet_unit(pIface), + (uint16_t)ifnet_flags(pIface))); + return 0; + + case SIOCADDMULTI: + case SIOCDELMULTI: + Log2(("VBoxNetAdp: %s%d: SIOC%sMULTI (null)\n", + ifnet_name(pIface), ifnet_unit(pIface), + uCmd == SIOCADDMULTI ? "ADD" : "DEL")); + return 0; + } + } + + Log2(("VBoxNetAdp: %s%d: %c%c '%c' %u len %u\n", + ifnet_name(pIface), ifnet_unit(pIface), + uCmd & IOC_OUT ? '<' : '-', + uCmd & IOC_IN ? '>' : '-', + IOCGROUP(uCmd), + uCmd & 0xff, + IOCPARM_LEN(uCmd))); + + error = ether_ioctl(pIface, uCmd, pvData); + return error; +} + + +int vboxNetAdpOsCreate(PVBOXNETADP pThis, PCRTMAC pMACAddress) +{ + int rc; + struct ifnet_init_params Params; + RTUUID uuid; + struct sockaddr_dl mac; + + pThis->u.s.hEvtDetached = NIL_RTSEMEVENT; + rc = RTSemEventCreate(&pThis->u.s.hEvtDetached); + if (RT_FAILURE(rc)) + { + printf("vboxNetAdpOsCreate: failed to create semaphore (rc=%d).\n", rc); + return rc; + } + + mac.sdl_len = sizeof(mac); + mac.sdl_family = AF_LINK; + mac.sdl_alen = ETHER_ADDR_LEN; + mac.sdl_nlen = 0; + mac.sdl_slen = 0; + memcpy(LLADDR(&mac), pMACAddress->au8, mac.sdl_alen); + + RTStrPrintf(pThis->szName, VBOXNETADP_MAX_NAME_LEN, "%s%d", VBOXNETADP_NAME, pThis->iUnit); + vboxNetAdpDarwinComposeUUID(pThis, &uuid); + Params.uniqueid = uuid.au8; + Params.uniqueid_len = sizeof(uuid); + Params.name = VBOXNETADP_NAME; + Params.unit = pThis->iUnit; + Params.family = IFNET_FAMILY_ETHERNET; + Params.type = IFT_ETHER; + Params.output = vboxNetAdpDarwinOutput; + Params.demux = vboxNetAdpDarwinDemux; + Params.add_proto = ether_add_proto; + Params.del_proto = ether_del_proto; + Params.check_multi = ether_check_multi; + Params.framer = ether_frameout; + Params.softc = pThis; + Params.ioctl = vboxNetAdpDarwinIfIOCtl; + Params.set_bpf_tap = NULL; + Params.detach = vboxNetAdpDarwinDetach; + Params.event = NULL; + Params.broadcast_addr = "\xFF\xFF\xFF\xFF\xFF\xFF"; + Params.broadcast_len = ETHER_ADDR_LEN; + + errno_t err = ifnet_allocate(&Params, &pThis->u.s.pIface); + if (!err) + { + err = ifnet_attach(pThis->u.s.pIface, &mac); + if (!err) + { + bpfattach(pThis->u.s.pIface, DLT_EN10MB, ETHER_HDR_LEN); + + err = ifnet_set_flags(pThis->u.s.pIface, IFF_RUNNING | IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST, 0xFFFF); + if (!err) + { + ifnet_set_mtu(pThis->u.s.pIface, VBOXNETADP_MTU); + VBoxNetSendDummy(pThis->u.s.pIface); + return VINF_SUCCESS; + } + else + Log(("vboxNetAdpDarwinRegisterDevice: Failed to set flags (err=%d).\n", err)); + ifnet_detach(pThis->u.s.pIface); + } + else + Log(("vboxNetAdpDarwinRegisterDevice: Failed to attach to interface (err=%d).\n", err)); + ifnet_release(pThis->u.s.pIface); + } + else + Log(("vboxNetAdpDarwinRegisterDevice: Failed to allocate interface (err=%d).\n", err)); + + RTSemEventDestroy(pThis->u.s.hEvtDetached); + pThis->u.s.hEvtDetached = NIL_RTSEMEVENT; + + return RTErrConvertFromErrno(err); +} + +void vboxNetAdpOsDestroy(PVBOXNETADP pThis) +{ + /* Bring down the interface */ + int rc = VINF_SUCCESS; + errno_t err; + + AssertPtr(pThis->u.s.pIface); + Assert(pThis->u.s.hEvtDetached != NIL_RTSEMEVENT); + + err = ifnet_set_flags(pThis->u.s.pIface, 0, IFF_UP | IFF_RUNNING); + if (err) + Log(("vboxNetAdpDarwinUnregisterDevice: Failed to bring down interface " + "(err=%d).\n", err)); + err = ifnet_detach(pThis->u.s.pIface); + if (err) + Log(("vboxNetAdpDarwinUnregisterDevice: Failed to detach interface " + "(err=%d).\n", err)); + Log2(("vboxNetAdpDarwinUnregisterDevice: Waiting for 'detached' event...\n")); + /* Wait until we get a signal from detach callback. */ + rc = RTSemEventWait(pThis->u.s.hEvtDetached, VBOXNETADP_DETACH_TIMEOUT); + if (rc == VERR_TIMEOUT) + LogRel(("VBoxAdpDrv: Failed to detach interface %s%d\n.", + VBOXNETADP_NAME, pThis->iUnit)); + err = ifnet_release(pThis->u.s.pIface); + if (err) + Log(("vboxNetAdpUnregisterDevice: Failed to release interface (err=%d).\n", err)); + + RTSemEventDestroy(pThis->u.s.hEvtDetached); + pThis->u.s.hEvtDetached = NIL_RTSEMEVENT; +} + +/** + * Device open. Called on open /dev/vboxnetctl + * + * @param Dev The device number. + * @param fFlags ???. + * @param fDevType ???. + * @param pProcess The process issuing this request. + */ +static int VBoxNetAdpDarwinOpen(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags, fDevType, pProcess); +#ifdef LOG_ENABLED + char szName[128]; + szName[0] = '\0'; + proc_name(proc_pid(pProcess), szName, sizeof(szName)); + Log(("VBoxNetAdpDarwinOpen: pid=%d '%s'\n", proc_pid(pProcess), szName)); +#endif + return 0; +} + +/** + * Close device. + */ +static int VBoxNetAdpDarwinClose(dev_t Dev, int fFlags, int fDevType, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags, fDevType, pProcess); + Log(("VBoxNetAdpDarwinClose: pid=%d\n", proc_pid(pProcess))); + 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 VBoxNetAdpDarwinIOCtl(dev_t Dev, u_long iCmd, caddr_t pData, int fFlags, struct proc *pProcess) +{ + RT_NOREF(Dev, fFlags, pProcess); + uint32_t cbReq = IOCPARM_LEN(iCmd); + PVBOXNETADPREQ pReq = (PVBOXNETADPREQ)pData; + int rc; + + Log(("VBoxNetAdpDarwinIOCtl: param len %#x; iCmd=%#lx\n", cbReq, iCmd)); + switch (IOCBASECMD(iCmd)) + { + case IOCBASECMD(VBOXNETADP_CTL_ADD): + { + if ( (IOC_DIRMASK & iCmd) != IOC_INOUT + || cbReq < sizeof(VBOXNETADPREQ)) + return EINVAL; + + PVBOXNETADP pNew; + Log(("VBoxNetAdpDarwinIOCtl: szName=%s\n", pReq->szName)); + rc = vboxNetAdpCreate(&pNew, + pReq->szName[0] && RTStrEnd(pReq->szName, RT_MIN(cbReq, sizeof(pReq->szName))) ? + pReq->szName : NULL); + if (RT_FAILURE(rc)) + return rc == VERR_OUT_OF_RESOURCES ? ENOMEM : EINVAL; + + Assert(strlen(pReq->szName) < sizeof(pReq->szName)); + strncpy(pReq->szName, pNew->szName, sizeof(pReq->szName) - 1); + pReq->szName[sizeof(pReq->szName) - 1] = '\0'; + Log(("VBoxNetAdpDarwinIOCtl: Added '%s'\n", pReq->szName)); + break; + } + + case IOCBASECMD(VBOXNETADP_CTL_REMOVE): + { + if (!RTStrEnd(pReq->szName, RT_MIN(cbReq, sizeof(pReq->szName)))) + return EINVAL; + + PVBOXNETADP pAdp = vboxNetAdpFindByName(pReq->szName); + if (!pAdp) + return EINVAL; + + rc = vboxNetAdpDestroy(pAdp); + if (RT_FAILURE(rc)) + return EINVAL; + Log(("VBoxNetAdpDarwinIOCtl: Removed %s\n", pReq->szName)); + break; + } + + default: + printf("VBoxNetAdpDarwinIOCtl: unknown command %lx.\n", IOCBASECMD(iCmd)); + return EINVAL; + } + + return 0; +} + +int vboxNetAdpOsInit(PVBOXNETADP pThis) +{ + /* + * Init the darwin specific members. + */ + pThis->u.s.pIface = NULL; + pThis->u.s.hEvtDetached = NIL_RTSEMEVENT; + + return VINF_SUCCESS; +} + +/** + * Start the kernel module. + */ +static kern_return_t VBoxNetAdpDarwinStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + int rc; + + /* + * Initialize IPRT and find our module tag id. + * (IPRT is shared with VBoxDrv, it creates the loggers.) + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetAdpDarwinStart\n")); + rc = vboxNetAdpInit(); + if (RT_SUCCESS(rc)) + { + g_nCtlDev = cdevsw_add(-1, &g_ChDev); + if (g_nCtlDev < 0) + { + LogRel(("VBoxAdp: failed to register control device.")); + rc = VERR_CANT_CREATE; + } + else + { + g_hCtlDev = devfs_make_node(makedev(g_nCtlDev, 0), DEVFS_CHAR, + UID_ROOT, GID_WHEEL, 0600, VBOXNETADP_CTL_DEV_NAME); + if (!g_hCtlDev) + { + LogRel(("VBoxAdp: failed to create FS node for control device.")); + rc = VERR_CANT_CREATE; + } + } + } + + if (RT_SUCCESS(rc)) + { + LogRel(("VBoxAdpDrv: version " VBOX_VERSION_STRING " r%d\n", VBOX_SVN_REV)); + return KMOD_RETURN_SUCCESS; + } + + LogRel(("VBoxAdpDrv: failed to initialize device extension (rc=%d)\n", rc)); + RTR0Term(); + } + else + printf("VBoxAdpDrv: failed to initialize IPRT (rc=%d)\n", rc); + + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxNetAdpDarwinStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + Log(("VBoxNetAdpDarwinStop\n")); + + vboxNetAdpShutdown(); + /* Remove control device */ + devfs_remove(g_hCtlDev); + cdevsw_remove(g_nCtlDev, &g_ChDev); + + RTR0Term(); + + return KMOD_RETURN_SUCCESS; +} diff --git a/src/VBox/HostDrivers/VBoxNetAdp/darwin/loadnetadp.sh b/src/VBox/HostDrivers/VBoxNetAdp/darwin/loadnetadp.sh new file mode 100755 index 00000000..b1e28d94 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/darwin/loadnetadp.sh @@ -0,0 +1,133 @@ +#!/bin/bash +## @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 . +# +# 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 +# + +SCRIPT_NAME="loadnetadp" +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` + +DRVNAME="VBoxNetAdp.kext" +BUNDLE="org.virtualbox.kext.VBoxNetAdp" + +DEP_DRVNAME="VBoxDrv.kext" +DEP_BUNDLE="org.virtualbox.kext.VBoxDrv" + + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DEP_DIR="$DIR/$DEP_DRVNAME" +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ ! -d "$DEP_DIR" ]; then + echo "Cannot find $DEP_DIR or it's not a directory... (dependency)" + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +trap "sudo chown -R `whoami` $DIR $DEP_DIR; exit 1" INT + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "${SCRIPT_NAME}.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 "${SCRIPT_NAME}.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 + +# 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" "$DEP_DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/${SCRIPT_NAME}.tmp + echo "${SCRIPT_NAME}.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" + + # load.sh puts it here. + DEP_DIR="/tmp/loaddrv.tmp/$DEP_DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" "$DEP_DIR" +fi + +sudo chmod -R o-rwx "$DIR" +sync +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextutil $OPTS -d "$DEP_DIR" "$DIR" +else + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextload $OPTS -d \"$DEP_DIR\" \"$DIR\")" +sudo kextload $OPTS -d "$DEP_DIR" "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" "$DEP_DIR" +kextstat | grep org.virtualbox.kext + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/freebsd/Makefile b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/Makefile new file mode 100644 index 00000000..013a223d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/Makefile @@ -0,0 +1,54 @@ +# $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 . +# +# 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 = vboxnetadp + +CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBOX_WITH_HARDENING -DVIMAGE + +.if (${MACHINE_ARCH} == "i386") + CFLAGS += -DRT_ARCH_X86 +.elif (${MACHINE_ARCH} == "amd64") + CFLAGS += -DRT_ARCH_AMD64 +.endif + +SRCS = \ + VBoxNetAdp-freebsd.c \ + VBoxNetAdp.c + +SRCS += device_if.h bus_if.h + +.include + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c new file mode 100644 index 00000000..db5c0b64 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c @@ -0,0 +1,339 @@ +/* $Id: VBoxNetAdp-freebsd.c $ */ +/** @file + * VBoxNetAdp - Virtual Network Adapter Driver (Host), FreeBSD Specific Code. + */ + +/*- + * Copyright (c) 2009 Fredrik Lindberg + * + * 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 * +*********************************************************************************************************************************/ +#include +#undef PVM +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_GROUP LOG_GROUP_NET_ADP_DRV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VBOXNETADP_OS_SPECFIC 1 +#include "../VBoxNetAdpInternal.h" + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800500 +# include +# include + +# define VBOXCURVNET_SET(arg) CURVNET_SET_QUIET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() VBOXCURVNET_SET(CRED_TO_VNET(curthread->td_ucred)) +# define VBOXCURVNET_RESTORE() CURVNET_RESTORE() + +#else /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +# define VBOXCURVNET_SET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() +# define VBOXCURVNET_RESTORE() + +#endif /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +static int VBoxNetAdpFreeBSDCtrlioctl(struct cdev *, u_long, caddr_t, int flags, + struct thread *); +static struct cdevsw vboxnetadp_cdevsw = +{ + .d_version = D_VERSION, + .d_ioctl = VBoxNetAdpFreeBSDCtrlioctl, + .d_read = (d_read_t *)nullop, + .d_write = (d_write_t *)nullop, + .d_name = VBOXNETADP_CTL_DEV_NAME, +}; + +static struct cdev *VBoxNetAdpFreeBSDcdev; + +static int VBoxNetAdpFreeBSDModuleEvent(struct module *, int, void *); +static moduledata_t g_VBoxNetAdpFreeBSDModule = { + "vboxnetadp", + VBoxNetAdpFreeBSDModuleEvent, + NULL +}; + +/** Declare the module as a pseudo device. */ +DECLARE_MODULE(vboxnetadp, g_VBoxNetAdpFreeBSDModule, SI_SUB_PSEUDO, SI_ORDER_ANY); +MODULE_VERSION(vboxnetadp, 1); +MODULE_DEPEND(vboxnetadp, vboxdrv, 1, 1, 1); +MODULE_DEPEND(vboxnetadp, vboxnetflt, 1, 1, 1); + +/** + * Module event handler + */ +static int +VBoxNetAdpFreeBSDModuleEvent(struct module *pMod, int enmEventType, void *pvArg) +{ + int rc = 0; + + Log(("VBoxNetAdpFreeBSDModuleEvent\n")); + + switch (enmEventType) + { + case MOD_LOAD: + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + Log(("RTR0Init failed %d\n", rc)); + return RTErrConvertToErrno(rc); + } + rc = vboxNetAdpInit(); + if (RT_FAILURE(rc)) + { + RTR0Term(); + Log(("vboxNetAdpInit failed %d\n", rc)); + return RTErrConvertToErrno(rc); + } + /* Create dev node */ + VBoxNetAdpFreeBSDcdev = make_dev(&vboxnetadp_cdevsw, 0, + UID_ROOT, GID_WHEEL, 0600, VBOXNETADP_CTL_DEV_NAME); + + break; + + case MOD_UNLOAD: + vboxNetAdpShutdown(); + destroy_dev(VBoxNetAdpFreeBSDcdev); + RTR0Term(); + break; + case MOD_SHUTDOWN: + case MOD_QUIESCE: + default: + return EOPNOTSUPP; + } + + if (RT_SUCCESS(rc)) + return 0; + return RTErrConvertToErrno(rc); +} + +/** + * Device I/O Control entry point. + */ +static int +VBoxNetAdpFreeBSDCtrlioctl(struct cdev *dev, u_long iCmd, caddr_t data, int flags, struct thread *td) +{ + PVBOXNETADP pAdp; + PVBOXNETADPREQ pReq = (PVBOXNETADPREQ)data; + struct ifnet *ifp; + int rc; + + switch (iCmd) + { + case VBOXNETADP_CTL_ADD: + if ( !(iCmd & IOC_INOUT) /* paranoia*/ + || IOCPARM_LEN(iCmd) < sizeof(*pReq)) + return EINVAL; + + rc = vboxNetAdpCreate(&pAdp, + pReq->szName[0] && RTStrEnd(pReq->szName, RT_MIN(IOCPARM_LEN(iCmd), sizeof(pReq->szName))) ? + pReq->szName : NULL); + if (RT_FAILURE(rc)) + return EINVAL; + + strncpy(pReq->szName, pAdp->szName, sizeof(pReq->szName) - 1); + pReq->szName[sizeof(pReq->szName) - 1] = '\0'; + break; + + case VBOXNETADP_CTL_REMOVE: + if (!RTStrEnd(pReq->szName, RT_MIN(sizeof(pReq->szName), IOCPARM_LEN(iCmd)))) + return EINVAL; + + pAdp = vboxNetAdpFindByName(pReq->szName); + if (!pAdp) + return EINVAL; + + rc = vboxNetAdpDestroy(pAdp); + if (RT_FAILURE(rc)) + return EINVAL; + + break; + + default: + return EINVAL; + } + return 0; +} + +/** + * Initialize device, just set the running flag. + */ +static void VBoxNetAdpFreeBSDNetinit(void *priv) +{ + PVBOXNETADP pThis = priv; + struct ifnet *ifp = pThis->u.s.ifp; + + ifp->if_drv_flags |= IFF_DRV_RUNNING; +} + +/** + * Transmit packets. + * netflt has already done everything for us so we just hand the + * packets to BPF and increment the packet stats. + */ +static void VBoxNetAdpFreeBSDNetstart(struct ifnet *ifp) +{ + PVBOXNETADP pThis = ifp->if_softc; + struct mbuf *m; + + if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) != IFF_DRV_RUNNING) + return; + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + while (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + { +#if __FreeBSD_version >= 1100036 + if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); +#else + ifp->if_opackets++; +#endif + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + BPF_MTAP(ifp, m); + m_freem(m); + } + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +} + +/** + * Interface ioctl handling + */ +static int VBoxNetAdpFreeBSDNetioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + int error = 0; + + switch (cmd) + { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) + { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + ifp->if_init(ifp->if_softc); + } + else + { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + } + break; + case SIOCGIFMEDIA: + { + struct ifmediareq *ifmr; + int count; + + ifmr = (struct ifmediareq *)data; + count = ifmr->ifm_count; + ifmr->ifm_count = 1; + ifmr->ifm_status = IFM_AVALID; + ifmr->ifm_active = IFM_ETHER; + ifmr->ifm_status |= IFM_ACTIVE; + ifmr->ifm_current = ifmr->ifm_active; + if (count >= 1) + { + int media = IFM_ETHER; + error = copyout(&media, ifmr->ifm_ulist, sizeof(int)); + } + break; + } + default: + return ether_ioctl(ifp, cmd, data); + } + return error; +} + +int vboxNetAdpOsInit(PVBOXNETADP pThis) +{ + pThis->u.s.ifp = NULL; + return VINF_SUCCESS;; +} + +int vboxNetAdpOsCreate(PVBOXNETADP pThis, PCRTMAC pMac) +{ + struct ifnet *ifp; + + VBOXCURVNET_SET_FROM_UCRED(); + ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) + return VERR_NO_MEMORY; + + if_initname(ifp, VBOXNETADP_NAME, pThis->iUnit); + ifp->if_softc = pThis; + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = VBoxNetAdpFreeBSDNetioctl; + ifp->if_start = VBoxNetAdpFreeBSDNetstart; + ifp->if_init = VBoxNetAdpFreeBSDNetinit; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + ether_ifattach(ifp, (void *)pMac); + ifp->if_baudrate = 0; + + strncpy(pThis->szName, ifp->if_xname, VBOXNETADP_MAX_NAME_LEN); + pThis->u.s.ifp = ifp; + VBOXCURVNET_RESTORE(); + return 0; +} + +void vboxNetAdpOsDestroy(PVBOXNETADP pThis) +{ + struct ifnet *ifp; + + ifp = pThis->u.s.ifp; + VBOXCURVNET_SET(ifp->if_vnet); + ether_ifdetach(ifp); + if_free(ifp); + VBOXCURVNET_RESTORE(); +} diff --git a/src/VBox/HostDrivers/VBoxNetAdp/freebsd/files_vboxnetadp b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/files_vboxnetadp new file mode 100755 index 00000000..d90ced7b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/freebsd/files_vboxnetadp @@ -0,0 +1,94 @@ +#!/bin/sh +# $Id: files_vboxnetadp $ +## @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 . +# +# 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 +# + + +VBOX_VBOXNETADP_SOURCES=" \ + ${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/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/heap.h=>include/iprt/heap.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/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/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/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-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/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.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}/src/VBox/HostDrivers/VBoxNetAdp/freebsd/VBoxNetAdp-freebsd.c=>VBoxNetAdp-freebsd.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c=>VBoxNetAdp.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h=>VBoxNetAdpInternal.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile b/src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile new file mode 100644 index 00000000..30aada92 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile @@ -0,0 +1,78 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox Linux Host Virtual Network Adapter 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 . +# +# 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 +VBOXNETADP_DIR := $(VBOX_MODULE_SRC_DIR) + +# Allow building directly from the subdirectory without assuming the toplevel +# makefile has done the copying. Not the default use case, but can be handy. +ifndef KBUILD_EXTRA_SYMBOLS +KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXNETADP_DIR)/../vboxdrv/Module.symvers) +endif + +VBOXMOD_NAME = vboxnetadp +VBOXMOD_OBJS = \ + linux/VBoxNetAdp-linux.o \ + VBoxNetAdp.o +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + math/gcc/divdi3.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 +VBOXMOD_INCL = \ + $(VBOXNETADP_DIR) \ + $(VBOXNETADP_DIR)include \ + $(VBOXNETADP_DIR)r0drv/linux +VBOXMOD_DEFS = \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + RT_WITH_VBOX \ + VBOX_WITH_HARDENING \ + VBOX_WITH_64_BITS_GUESTS # <-- must be consistent with Config.kmk! +VBOXMOD_CFLAGS = -include $(VBOXNETADP_DIR)include/VBox/SUPDrvMangling.h -fno-pie -Wno-declaration-after-statement + +include $(obj)/Makefile-footer.gmk + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile.kup b/src/VBox/HostDrivers/VBoxNetAdp/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetAdp/linux/VBoxNetAdp-linux.c b/src/VBox/HostDrivers/VBoxNetAdp/linux/VBoxNetAdp-linux.c new file mode 100644 index 00000000..0654e1d7 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/linux/VBoxNetAdp-linux.c @@ -0,0 +1,583 @@ +/* $Id: VBoxNetAdp-linux.c $ */ +/** @file + * VBoxNetAdp - Virtual Network Adapter Driver (Host), Linux Specific Code. + */ + +/* + * 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 . + * + * 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 "the-linux-kernel.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" +#include +#include +#include +#include + +#define LOG_GROUP LOG_GROUP_NET_ADP_DRV +#include +#include +#include +#include +#include +#include + +/* +#include +#include +#include +#include +#include +#include +*/ + +#define VBOXNETADP_OS_SPECFIC 1 +#include "../VBoxNetAdpInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXNETADP_LINUX_NAME "vboxnet%d" +#define VBOXNETADP_CTL_DEV_NAME "vboxnetctl" + +#define VBOXNETADP_FROM_IFACE(iface) ((PVBOXNETADP) ifnet_softc(iface)) + +/** Set netdev MAC address. */ +#if RTLNX_VER_MIN(5,17,0) +# define VBOX_DEV_ADDR_SET(dev, addr, len) dev_addr_mod(dev, 0, addr, len) +#else /* < 5.17.0 */ +# define VBOX_DEV_ADDR_SET(dev, addr, len) memcpy(dev->dev_addr, addr, len) +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxNetAdpLinuxInit(void); +static void __exit VBoxNetAdpLinuxUnload(void); + +static int VBoxNetAdpLinuxOpen(struct inode *pInode, struct file *pFilp); +static int VBoxNetAdpLinuxClose(struct inode *pInode, struct file *pFilp); +#if RTLNX_VER_MAX(2,6,36) +static int VBoxNetAdpLinuxIOCtl(struct inode *pInode, struct file *pFilp, + unsigned int uCmd, unsigned long ulArg); +#else /* >= 2,6,36 */ +static long VBoxNetAdpLinuxIOCtlUnlocked(struct file *pFilp, + unsigned int uCmd, unsigned long ulArg); +#endif /* >= 2,6,36 */ + +static void vboxNetAdpEthGetDrvinfo(struct net_device *dev, struct ethtool_drvinfo *info); +#if RTLNX_VER_MIN(4,20,0) +static int vboxNetAdpEthGetLinkSettings(struct net_device *pNetDev, struct ethtool_link_ksettings *pLinkSettings); +#else /* < 4,20,0 */ +static int vboxNetAdpEthGetSettings(struct net_device *dev, struct ethtool_cmd *cmd); +#endif /* < 4,20,0 */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +module_init(VBoxNetAdpLinuxInit); +module_exit(VBoxNetAdpLinuxUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " Network Adapter Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV) " (" RT_XSTR(INTNETTRUNKIFPORT_VERSION) ")"); +#endif + +/** + * The (common) global data. + */ +static struct file_operations gFileOpsVBoxNetAdp = +{ + owner: THIS_MODULE, + open: VBoxNetAdpLinuxOpen, + release: VBoxNetAdpLinuxClose, +#if RTLNX_VER_MAX(2,6,36) + ioctl: VBoxNetAdpLinuxIOCtl, +#else /* RTLNX_VER_MIN(2,6,36) */ + unlocked_ioctl: VBoxNetAdpLinuxIOCtlUnlocked, +#endif /* RTLNX_VER_MIN(2,6,36) */ +}; + +/** The miscdevice structure. */ +static struct miscdevice g_CtlDev = +{ + minor: MISC_DYNAMIC_MINOR, + name: VBOXNETADP_CTL_DEV_NAME, + fops: &gFileOpsVBoxNetAdp, +# if RTLNX_VER_MAX(2,6,18) + devfs_name: VBOXNETADP_CTL_DEV_NAME +# endif +}; + +# if RTLNX_VER_MIN(2,6,19) +static const struct ethtool_ops gEthToolOpsVBoxNetAdp = +# else +static struct ethtool_ops gEthToolOpsVBoxNetAdp = +# endif +{ + .get_drvinfo = vboxNetAdpEthGetDrvinfo, +# if RTLNX_VER_MIN(4,20,0) + .get_link_ksettings = vboxNetAdpEthGetLinkSettings, +# else + .get_settings = vboxNetAdpEthGetSettings, +# endif + .get_link = ethtool_op_get_link, +}; + + +struct VBoxNetAdpPriv +{ + struct net_device_stats Stats; +}; + +typedef struct VBoxNetAdpPriv VBOXNETADPPRIV; +typedef VBOXNETADPPRIV *PVBOXNETADPPRIV; + +static int vboxNetAdpLinuxOpen(struct net_device *pNetDev) +{ + netif_start_queue(pNetDev); + return 0; +} + +static int vboxNetAdpLinuxStop(struct net_device *pNetDev) +{ + netif_stop_queue(pNetDev); + return 0; +} + +static int vboxNetAdpLinuxXmit(struct sk_buff *pSkb, struct net_device *pNetDev) +{ + PVBOXNETADPPRIV pPriv = netdev_priv(pNetDev); + + /* Update the stats. */ + pPriv->Stats.tx_packets++; + pPriv->Stats.tx_bytes += pSkb->len; +#if RTLNX_VER_MAX(2,6,31) + /* Update transmission time stamp. */ + pNetDev->trans_start = jiffies; +#endif + /* Nothing else to do, just free the sk_buff. */ + dev_kfree_skb(pSkb); + return 0; +} + +static struct net_device_stats *vboxNetAdpLinuxGetStats(struct net_device *pNetDev) +{ + PVBOXNETADPPRIV pPriv = netdev_priv(pNetDev); + return &pPriv->Stats; +} + + +/* ethtool_ops::get_drvinfo */ +static void vboxNetAdpEthGetDrvinfo(struct net_device *pNetDev, struct ethtool_drvinfo *info) +{ + PVBOXNETADPPRIV pPriv = netdev_priv(pNetDev); + NOREF(pPriv); + + RTStrPrintf(info->driver, sizeof(info->driver), + "%s", VBOXNETADP_NAME); + + /* + * Would be nice to include VBOX_SVN_REV, but it's not available + * here. Use file's svn revision via svn keyword? + */ + RTStrPrintf(info->version, sizeof(info->version), + "%s", VBOX_VERSION_STRING); + + RTStrPrintf(info->fw_version, sizeof(info->fw_version), + "0x%08X", INTNETTRUNKIFPORT_VERSION); + + RTStrPrintf(info->bus_info, sizeof(info->driver), + "N/A"); +} + + +# if RTLNX_VER_MIN(4,20,0) +/* ethtool_ops::get_link_ksettings */ +static int vboxNetAdpEthGetLinkSettings(struct net_device *pNetDev, struct ethtool_link_ksettings *pLinkSettings) +{ + /* We just need to set field we care for, the rest is done by ethtool_get_link_ksettings() helper in ethtool. */ + ethtool_link_ksettings_zero_link_mode(pLinkSettings, supported); + ethtool_link_ksettings_zero_link_mode(pLinkSettings, advertising); + ethtool_link_ksettings_zero_link_mode(pLinkSettings, lp_advertising); + pLinkSettings->base.speed = SPEED_10; + pLinkSettings->base.duplex = DUPLEX_FULL; + pLinkSettings->base.port = PORT_TP; + pLinkSettings->base.phy_address = 0; + pLinkSettings->base.transceiver = XCVR_INTERNAL; + pLinkSettings->base.autoneg = AUTONEG_DISABLE; + return 0; +} +#else /* RTLNX_VER_MAX(4,20,0) */ +/* ethtool_ops::get_settings */ +static int vboxNetAdpEthGetSettings(struct net_device *pNetDev, struct ethtool_cmd *cmd) +{ + cmd->supported = 0; + cmd->advertising = 0; +#if RTLNX_VER_MIN(2,6,27) + ethtool_cmd_speed_set(cmd, SPEED_10); +#else + cmd->speed = SPEED_10; +#endif + cmd->duplex = DUPLEX_FULL; + cmd->port = PORT_TP; + cmd->phy_address = 0; + cmd->transceiver = XCVR_INTERNAL; + cmd->autoneg = AUTONEG_DISABLE; + cmd->maxtxpkt = 0; + cmd->maxrxpkt = 0; + return 0; +} +#endif /* RTLNX_VER_MAX(4,20,0) */ + + +#if RTLNX_VER_MIN(2,6,29) +static const struct net_device_ops vboxNetAdpNetdevOps = { + .ndo_open = vboxNetAdpLinuxOpen, + .ndo_stop = vboxNetAdpLinuxStop, + .ndo_start_xmit = vboxNetAdpLinuxXmit, + .ndo_get_stats = vboxNetAdpLinuxGetStats +}; +#endif + +static void vboxNetAdpNetDevInit(struct net_device *pNetDev) +{ + PVBOXNETADPPRIV pPriv; + + ether_setup(pNetDev); +#if RTLNX_VER_MIN(2,6,29) + pNetDev->netdev_ops = &vboxNetAdpNetdevOps; +#else /* RTLNX_VER_MAX(2,6,29) */ + pNetDev->open = vboxNetAdpLinuxOpen; + pNetDev->stop = vboxNetAdpLinuxStop; + pNetDev->hard_start_xmit = vboxNetAdpLinuxXmit; + pNetDev->get_stats = vboxNetAdpLinuxGetStats; +#endif /* RTLNX_VER_MAX(2,6,29) */ +#if RTLNX_VER_MIN(4,10,0) + pNetDev->max_mtu = 65536; + pNetDev->features = NETIF_F_TSO + | NETIF_F_TSO6 + | NETIF_F_TSO_ECN + | NETIF_F_SG + | NETIF_F_HW_CSUM; +#endif /* RTLNX_VER_MIN(4,10,0) */ + + pNetDev->ethtool_ops = &gEthToolOpsVBoxNetAdp; + + pPriv = netdev_priv(pNetDev); + memset(pPriv, 0, sizeof(*pPriv)); +} + + +int vboxNetAdpOsCreate(PVBOXNETADP pThis, PCRTMAC pMACAddress) +{ + int rc = VINF_SUCCESS; + struct net_device *pNetDev; + + /* No need for private data. */ + pNetDev = alloc_netdev(sizeof(VBOXNETADPPRIV), + pThis->szName[0] ? pThis->szName : VBOXNETADP_LINUX_NAME, +#if RTLNX_VER_MIN(3,17,0) + NET_NAME_UNKNOWN, +#endif + vboxNetAdpNetDevInit); + if (pNetDev) + { + int err; + + if (pNetDev->dev_addr) + { + VBOX_DEV_ADDR_SET(pNetDev, pMACAddress, ETH_ALEN); + Log2(("vboxNetAdpOsCreate: pNetDev->dev_addr = %.6Rhxd\n", pNetDev->dev_addr)); + + /* + * We treat presence of VBoxNetFlt filter as our "carrier", + * see vboxNetFltSetLinkState(). + * + * operstates.txt: "On device allocation, networking core + * sets the flags equivalent to netif_carrier_ok() and + * !netif_dormant()" - so turn carrier off here. + */ + netif_carrier_off(pNetDev); + + err = register_netdev(pNetDev); + if (!err) + { + strncpy(pThis->szName, pNetDev->name, sizeof(pThis->szName)); + pThis->szName[sizeof(pThis->szName) - 1] = '\0'; + pThis->u.s.pNetDev = pNetDev; + Log2(("vboxNetAdpOsCreate: pThis=%p pThis->szName = %p\n", pThis, pThis->szName)); + return VINF_SUCCESS; + } + } + else + { + LogRel(("VBoxNetAdp: failed to set MAC address (dev->dev_addr == NULL)\n")); + err = EFAULT; + } + free_netdev(pNetDev); + rc = RTErrConvertFromErrno(err); + } + return rc; +} + +void vboxNetAdpOsDestroy(PVBOXNETADP pThis) +{ + struct net_device *pNetDev = pThis->u.s.pNetDev; + AssertPtr(pThis->u.s.pNetDev); + + pThis->u.s.pNetDev = NULL; + unregister_netdev(pNetDev); + free_netdev(pNetDev); +} + +/** + * Device open. Called on open /dev/vboxnetctl + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int VBoxNetAdpLinuxOpen(struct inode *pInode, struct file *pFilp) +{ + Log(("VBoxNetAdpLinuxOpen: pid=%d/%d %s\n", RTProcSelf(), current->pid, current->comm)); + +#ifdef VBOX_WITH_HARDENING + /* + * Only root is allowed to access the device, enforce it! + */ + if (!capable(CAP_SYS_ADMIN)) + { + Log(("VBoxNetAdpLinuxOpen: admin privileges required!\n")); + return -EPERM; + } +#endif + + return 0; +} + + +/** + * Close device. + * + * @param pInode Pointer to inode info structure. + * @param pFilp Associated file pointer. + */ +static int VBoxNetAdpLinuxClose(struct inode *pInode, struct file *pFilp) +{ + Log(("VBoxNetAdpLinuxClose: pid=%d/%d %s\n", + RTProcSelf(), current->pid, current->comm)); + pFilp->private_data = NULL; + return 0; +} + +/** + * 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 RTLNX_VER_MAX(2,6,36) +static int VBoxNetAdpLinuxIOCtl(struct inode *pInode, struct file *pFilp, + unsigned int uCmd, unsigned long ulArg) +#else /* RTLNX_VER_MIN(2,6,36) */ +static long VBoxNetAdpLinuxIOCtlUnlocked(struct file *pFilp, + unsigned int uCmd, unsigned long ulArg) +#endif /* RTLNX_VER_MIN(2,6,36) */ +{ + VBOXNETADPREQ Req; + PVBOXNETADP pAdp; + int rc; + char *pszName = NULL; + + Log(("VBoxNetAdpLinuxIOCtl: param len %#x; uCmd=%#x; add=%#x\n", _IOC_SIZE(uCmd), uCmd, VBOXNETADP_CTL_ADD)); + if (RT_UNLIKELY(_IOC_SIZE(uCmd) != sizeof(Req))) /* paranoia */ + { + Log(("VBoxNetAdpLinuxIOCtl: bad ioctl sizeof(Req)=%#x _IOC_SIZE=%#x; uCmd=%#x.\n", sizeof(Req), _IOC_SIZE(uCmd), uCmd)); + return -EINVAL; + } + + switch (uCmd) + { + case VBOXNETADP_CTL_ADD: + Log(("VBoxNetAdpLinuxIOCtl: _IOC_DIR(uCmd)=%#x; IOC_OUT=%#x\n", _IOC_DIR(uCmd), IOC_OUT)); + if (RT_UNLIKELY(copy_from_user(&Req, (void *)ulArg, sizeof(Req)))) + { + Log(("VBoxNetAdpLinuxIOCtl: copy_from_user(,%#lx,) failed; uCmd=%#x.\n", ulArg, uCmd)); + return -EFAULT; + } + Log(("VBoxNetAdpLinuxIOCtl: Add %s\n", Req.szName)); + + if (Req.szName[0]) + { + pAdp = vboxNetAdpFindByName(Req.szName); + if (pAdp) + { + Log(("VBoxNetAdpLinuxIOCtl: '%s' already exists\n", Req.szName)); + return -EINVAL; + } + pszName = Req.szName; + } + rc = vboxNetAdpCreate(&pAdp, pszName); + if (RT_FAILURE(rc)) + { + Log(("VBoxNetAdpLinuxIOCtl: vboxNetAdpCreate -> %Rrc\n", rc)); + return -(rc == VERR_OUT_OF_RESOURCES ? ENOMEM : EINVAL); + } + + Assert(strlen(pAdp->szName) < sizeof(Req.szName)); + strncpy(Req.szName, pAdp->szName, sizeof(Req.szName) - 1); + Req.szName[sizeof(Req.szName) - 1] = '\0'; + + if (RT_UNLIKELY(copy_to_user((void *)ulArg, &Req, sizeof(Req)))) + { + /* this is really bad! */ + /** @todo remove the adapter again? */ + printk(KERN_ERR "VBoxNetAdpLinuxIOCtl: copy_to_user(%#lx,,%#zx); uCmd=%#x!\n", ulArg, sizeof(Req), uCmd); + return -EFAULT; + } + Log(("VBoxNetAdpLinuxIOCtl: Successfully added '%s'\n", Req.szName)); + break; + + case VBOXNETADP_CTL_REMOVE: + if (RT_UNLIKELY(copy_from_user(&Req, (void *)ulArg, sizeof(Req)))) + { + Log(("VBoxNetAdpLinuxIOCtl: copy_from_user(,%#lx,) failed; uCmd=%#x.\n", ulArg, uCmd)); + return -EFAULT; + } + Log(("VBoxNetAdpLinuxIOCtl: Remove %s\n", Req.szName)); + + pAdp = vboxNetAdpFindByName(Req.szName); + if (!pAdp) + { + Log(("VBoxNetAdpLinuxIOCtl: '%s' not found\n", Req.szName)); + return -EINVAL; + } + + rc = vboxNetAdpDestroy(pAdp); + if (RT_FAILURE(rc)) + { + Log(("VBoxNetAdpLinuxIOCtl: vboxNetAdpDestroy('%s') -> %Rrc\n", Req.szName, rc)); + return -EINVAL; + } + Log(("VBoxNetAdpLinuxIOCtl: Successfully removed '%s'\n", Req.szName)); + break; + + default: + printk(KERN_ERR "VBoxNetAdpLinuxIOCtl: unknown command %x.\n", uCmd); + return -EINVAL; + } + + return 0; +} + +int vboxNetAdpOsInit(PVBOXNETADP pThis) +{ + /* + * Init linux-specific members. + */ + pThis->u.s.pNetDev = NULL; + + return VINF_SUCCESS; +} + + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxNetAdpLinuxInit(void) +{ + int rc; + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetAdpLinuxInit\n")); + + rc = vboxNetAdpInit(); + if (RT_SUCCESS(rc)) + { + rc = misc_register(&g_CtlDev); + if (rc) + { + printk(KERN_ERR "VBoxNetAdp: Can't register " VBOXNETADP_CTL_DEV_NAME " device! rc=%d\n", rc); + return rc; + } + LogRel(("VBoxNetAdp: Successfully started.\n")); + return 0; + } + else + LogRel(("VBoxNetAdp: failed to register vboxnet0 device (rc=%d)\n", rc)); + } + else + LogRel(("VBoxNetAdp: failed to initialize IPRT (rc=%d)\n", rc)); + + return -RTErrConvertToErrno(rc); +} + + +/** + * Unload the module. + * + * @todo We have to prevent this if we're busy! + */ +static void __exit VBoxNetAdpLinuxUnload(void) +{ + Log(("VBoxNetAdpLinuxUnload\n")); + + /* + * Undo the work done during start (in reverse order). + */ + + vboxNetAdpShutdown(); + /* Remove control device */ + misc_deregister(&g_CtlDev); + + RTR0Term(); + + Log(("VBoxNetAdpLinuxUnload - done\n")); +} + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/linux/files_vboxnetadp b/src/VBox/HostDrivers/VBoxNetAdp/linux/files_vboxnetadp new file mode 100755 index 00000000..3f028677 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/linux/files_vboxnetadp @@ -0,0 +1,112 @@ +#!/bin/sh +# $Id: files_vboxnetadp $ +## @files +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# 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 . +# +# 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 +# + +VBOX_VBOXNETADP_SOURCES=" \ + ${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/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/heap.h=>include/iprt/heap.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/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/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/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.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}/src/VBox/HostDrivers/VBoxNetAdp/linux/VBoxNetAdp-linux.c=>linux/VBoxNetAdp-linux.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdp.c=>VBoxNetAdp.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h=>VBoxNetAdpInternal.h \ + ${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/math/gcc/divdi3.c=>math/gcc/divdi3.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/string/strformat.cpp=>common/string/strformat.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/strtonum.cpp=>common/string/strtonum.c \ + ${PATH_ROOT}/src/VBox/Runtime/include/internal/iprt.h=>include/internal/iprt.h \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/solaris/VBoxNetAdp-solaris.c b/src/VBox/HostDrivers/VBoxNetAdp/solaris/VBoxNetAdp-solaris.c new file mode 100644 index 00000000..44227dbd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/solaris/VBoxNetAdp-solaris.c @@ -0,0 +1,581 @@ +/* $Id: VBoxNetAdp-solaris.c $ */ +/** @file + * VBoxNetAdapter - Network Adapter Driver (Host), Solaris Specific Code. + */ + +/* + * 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 . + * + * 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_NET_ADP_DRV +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../VBoxNetAdpInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define DEVICE_NAME "vboxnet" +/** The module descriptions as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox NetAdp" +/** The maximum MTU size permittable, value taken from "Oracle Quad 10 Gb or Dual 40 + * Gb Ethernet Adapter User's Guide". */ +#define DEVICE_MAX_MTU_SIZE 9706 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int VBoxNetAdpSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int VBoxNetAdpSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +static int VBoxNetAdpSolarisQuiesceNotNeeded(dev_info_t *pDip); + +/** + * Streams: module info. + */ +static struct module_info g_VBoxNetAdpSolarisModInfo = +{ + 0x0dd, /* module id */ + DEVICE_NAME, + 0, /* min. packet size */ + INFPSZ, /* max. packet size */ + 0, /* hi-water mark */ + 0 /* lo-water mark */ +}; + +/** + * Streams: read queue hooks. + */ +static struct qinit g_VBoxNetAdpSolarisReadQ = +{ + NULL, /* read */ + gld_rsrv, + gld_open, + gld_close, + NULL, /* admin (reserved) */ + &g_VBoxNetAdpSolarisModInfo, + NULL /* module stats */ +}; + +/** + * Streams: write queue hooks. + */ +static struct qinit g_VBoxNetAdpSolarisWriteQ = +{ + gld_wput, + gld_wsrv, + NULL, /* open */ + NULL, /* close */ + NULL, /* admin (reserved) */ + &g_VBoxNetAdpSolarisModInfo, + NULL /* module stats */ +}; + +/** + * Streams: IO stream tab. + */ +static struct streamtab g_VBoxNetAdpSolarisStreamTab = +{ + &g_VBoxNetAdpSolarisReadQ, + &g_VBoxNetAdpSolarisWriteQ, + NULL, /* muxread init */ + NULL /* muxwrite init */ +}; + +/** + * cb_ops: driver char/block entry points + */ +static struct cb_ops g_VBoxNetAdpSolarisCbOps = +{ + nulldev, /* cb open */ + nulldev, /* cb close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* cb read */ + nodev, /* cb write */ + nodev, /* cb ioctl */ + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + &g_VBoxNetAdpSolarisStreamTab, + D_MP, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: driver entry/exit and other ops. + */ +static struct dev_ops g_VBoxNetAdpSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + gld_getinfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxNetAdpSolarisAttach, + VBoxNetAdpSolarisDetach, + nodev, /* reset */ + &g_VBoxNetAdpSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + VBoxNetAdpSolarisQuiesceNotNeeded +}; + +/** + * modldrv: export driver specifics to kernel + */ +static struct modldrv g_VBoxNetAdpSolarisDriver = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxNetAdpSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxNetAdpSolarisModLinkage = +{ + MODREV_1, /* loadable module system revision */ + { + &g_VBoxNetAdpSolarisDriver, /* adapter streams driver framework */ + NULL /* terminate array of linkage structures */ + } +}; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The default ethernet broadcast address */ +static uchar_t achBroadcastAddr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +/** + * vboxnetadp_state_t: per-instance data + */ +typedef struct vboxnetadp_state_t +{ + dev_info_t *pDip; /* device info. */ + RTMAC FactoryMac; /* default 'factory' MAC address */ + RTMAC CurrentMac; /* current MAC address */ +} vboxnetadp_state_t; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxNetAdpSolarisGenerateMac(PRTMAC pMac); +static int vboxNetAdpSolarisSetMacAddress(gld_mac_info_t *pMacInfo, unsigned char *pszMacAddr); +static int vboxNetAdpSolarisSend(gld_mac_info_t *pMacInfo, mblk_t *pMsg); +static int vboxNetAdpSolarisStub(gld_mac_info_t *pMacInfo); +static int vboxNetAdpSolarisSetPromisc(gld_mac_info_t *pMacInfo, int fPromisc); +static int vboxNetAdpSolarisSetMulticast(gld_mac_info_t *pMacInfo, unsigned char *pMulticastAddr, int fMulticast); +static int vboxNetAdpSolarisGetStats(gld_mac_info_t *pMacInfo, struct gld_stats *pStats); + + +/** + * Kernel entry points + */ +int _init(void) +{ + LogFunc((DEVICE_NAME ":_init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxNetAdpSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ":failed to disable autounloading!\n")); + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + rc = mod_install(&g_VBoxNetAdpSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ":mod_install failed. rc=%d\n", rc)); + RTR0Term(); + } + else + LogRel((DEVICE_NAME ":failed to initialize IPRT (rc=%d)\n", rc)); + + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + LogFunc((DEVICE_NAME ":_fini\n")); + + /* + * Undo the work done during start (in reverse order). + */ + int rc = mod_remove(&g_VBoxNetAdpSolarisModLinkage); + if (!rc) + RTR0Term(); + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ":_info\n")); + + int rc = mod_info(&g_VBoxNetAdpSolarisModLinkage, pModInfo); + + Log((DEVICE_NAME ":_info returns %d\n", rc)); + return rc; +} + + +/** + * 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). + * + * @returns corresponding solaris error code. + */ +static int VBoxNetAdpSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ":VBoxNetAdpSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + int rc = -1; + switch (enmCmd) + { + case DDI_ATTACH: + { + gld_mac_info_t *pMacInfo = gld_mac_alloc(pDip); + if (pMacInfo) + { + vboxnetadp_state_t *pState = RTMemAllocZ(sizeof(vboxnetadp_state_t)); + if (pState) + { + pState->pDip = pDip; + + /* + * Setup GLD MAC layer registration info. + */ + pMacInfo->gldm_reset = vboxNetAdpSolarisStub; + pMacInfo->gldm_start = vboxNetAdpSolarisStub; + pMacInfo->gldm_stop = vboxNetAdpSolarisStub; + pMacInfo->gldm_set_mac_addr = vboxNetAdpSolarisSetMacAddress; + pMacInfo->gldm_set_multicast = vboxNetAdpSolarisSetMulticast; + pMacInfo->gldm_set_promiscuous = vboxNetAdpSolarisSetPromisc; + pMacInfo->gldm_send = vboxNetAdpSolarisSend; + pMacInfo->gldm_intr = NULL; + pMacInfo->gldm_get_stats = vboxNetAdpSolarisGetStats; + pMacInfo->gldm_ioctl = NULL; + pMacInfo->gldm_ident = DEVICE_NAME; + pMacInfo->gldm_type = DL_ETHER; + pMacInfo->gldm_minpkt = 0; + pMacInfo->gldm_maxpkt = DEVICE_MAX_MTU_SIZE; + pMacInfo->gldm_capabilities = GLD_CAP_LINKSTATE; + AssertCompile(sizeof(RTMAC) == ETHERADDRL); + + pMacInfo->gldm_addrlen = ETHERADDRL; + pMacInfo->gldm_saplen = -2; + pMacInfo->gldm_broadcast_addr = achBroadcastAddr; + pMacInfo->gldm_ppa = ddi_get_instance(pState->pDip); + pMacInfo->gldm_devinfo = pState->pDip; + pMacInfo->gldm_private = (caddr_t)pState; + + /* + * We use a semi-random MAC addresses similar to a guest NIC's MAC address + * as the default factory address of the interface. + */ + rc = vboxNetAdpSolarisGenerateMac(&pState->FactoryMac); + if (RT_SUCCESS(rc)) + { + bcopy(&pState->FactoryMac, &pState->CurrentMac, sizeof(RTMAC)); + pMacInfo->gldm_vendor_addr = (unsigned char *)&pState->FactoryMac; + + /* + * Now try registering our GLD with the MAC layer. + * Registration can fail on some S10 versions when the MTU size is more than 1500. + * When we implement jumbo frames we should probably retry with MTU 1500 for S10. + */ + rc = gld_register(pDip, (char *)ddi_driver_name(pDip), pMacInfo); + if (rc == DDI_SUCCESS) + { + ddi_report_dev(pDip); + gld_linkstate(pMacInfo, GLD_LINKSTATE_UP); + return DDI_SUCCESS; + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisAttach failed to register GLD. rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisAttach failed to generate mac address.rc=%d\n")); + + RTMemFree(pState); + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisAttach failed to alloc state.\n")); + + gld_mac_free(pMacInfo); + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisAttach failed to alloc mac structure.\n")); + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_PM_RESUME: */ + default: + 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). + * + * @returns corresponding solaris error code. + */ +static int VBoxNetAdpSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ":VBoxNetAdpSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_DETACH: + { + /* + * Unregister and clean up. + */ + gld_mac_info_t *pMacInfo = ddi_get_driver_private(pDip); + if (pMacInfo) + { + vboxnetadp_state_t *pState = (vboxnetadp_state_t *)pMacInfo->gldm_private; + if (pState) + { + gld_linkstate(pMacInfo, GLD_LINKSTATE_DOWN); + int rc = gld_unregister(pMacInfo); + if (rc == DDI_SUCCESS) + { + gld_mac_free(pMacInfo); + RTMemFree(pState); + return DDI_SUCCESS; + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisDetach failed to unregister GLD from MAC layer.rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisDetach failed to get internal state.\n")); + } + else + LogRel((DEVICE_NAME ":VBoxNetAdpSolarisDetach failed to get driver private GLD data.\n")); + + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_SUSPEND: */ + /* case DDI_HOTPLUG_DETACH: */ + 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 VBoxNetAdpSolarisQuiesceNotNeeded(dev_info_t *pDip) +{ + return DDI_SUCCESS; +} + + +static int vboxNetAdpSolarisGenerateMac(PRTMAC pMac) +{ + pMac->au8[0] = 0x08; + pMac->au8[1] = 0x00; + pMac->au8[2] = 0x27; + RTRandBytes(&pMac->au8[3], 3); + Log((DEVICE_NAME ":VBoxNetAdpSolarisGenerateMac Generated %.*Rhxs\n", sizeof(RTMAC), &pMac)); + return VINF_SUCCESS; +} + + +static int vboxNetAdpSolarisSetMacAddress(gld_mac_info_t *pMacInfo, unsigned char *pszMacAddr) +{ + vboxnetadp_state_t *pState = (vboxnetadp_state_t *)pMacInfo->gldm_private; + if (pState) + { + bcopy(pszMacAddr, &pState->CurrentMac, sizeof(RTMAC)); + Log((DEVICE_NAME ":vboxNetAdpSolarisSetMacAddress updated MAC %.*Rhxs\n", sizeof(RTMAC), &pState->CurrentMac)); + return GLD_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetAdpSolarisSetMacAddress failed to get internal state.\n")); + return GLD_FAILURE; +} + + +static int vboxNetAdpSolarisSend(gld_mac_info_t *pMacInfo, mblk_t *pMsg) +{ + while (pMsg) + { + mblk_t *pMsgNext = pMsg->b_cont; + pMsg->b_cont = NULL; + freemsg(pMsg); + pMsg = pMsgNext; + } + return GLD_SUCCESS; +} + + +static int vboxNetAdpSolarisStub(gld_mac_info_t *pMacInfo) +{ + return GLD_SUCCESS; +} + + +static int vboxNetAdpSolarisSetMulticast(gld_mac_info_t *pMacInfo, unsigned char *pMulticastAddr, int fMulticast) +{ + return GLD_SUCCESS; +} + + +static int vboxNetAdpSolarisSetPromisc(gld_mac_info_t *pMacInfo, int fPromisc) +{ + /* Host requesting promiscuous intnet connection... */ + return GLD_SUCCESS; +} + + +static int vboxNetAdpSolarisGetStats(gld_mac_info_t *pMacInfo, struct gld_stats *pStats) +{ + /* + * For now fake up stats. Stats like duplex and speed are better set as they + * are used in utilities like dladm. Link state capabilities are critical + * as they are used by ipadm while trying to restore persistent interface configs. + */ + vboxnetadp_state_t *pState = (vboxnetadp_state_t *)pMacInfo->gldm_private; + if (pState) + { + pStats->glds_speed = 1000000000ULL; /* Bits/sec. */ + pStats->glds_media = GLDM_UNKNOWN; /* Media/Connector Type */ + pStats->glds_intr = 0; /* Interrupt count */ + pStats->glds_norcvbuf = 0; /* Recv. discards */ + pStats->glds_errxmt = 0; /* Xmit errors */ + pStats->glds_errrcv = 0; /* Recv. errors */ + pStats->glds_missed = 0; /* Pkt Drops on Recv. */ + pStats->glds_underflow = 0; /* Buffer underflows */ + pStats->glds_overflow = 0; /* Buffer overflows */ + + /* Ether */ + pStats->glds_frame = 0; /* Align errors */ + pStats->glds_crc = 0; /* CRC errors */ + pStats->glds_duplex = GLD_DUPLEX_FULL; /* Link duplex state */ + pStats->glds_nocarrier = 0; /* Carrier sense errors */ + pStats->glds_collisions = 0; /* Xmit Collisions */ + pStats->glds_excoll = 0; /* Frame discard due to excess collisions */ + pStats->glds_xmtlatecoll = 0; /* Late collisions */ + pStats->glds_defer = 0; /* Deferred Xmits */ + pStats->glds_dot3_first_coll = 0; /* Single collision frames */ + pStats->glds_dot3_multi_coll = 0; /* Multiple collision frames */ + pStats->glds_dot3_sqe_error = 0; /* SQE errors */ + pStats->glds_dot3_mac_xmt_error = 0; /* MAC Xmit errors */ + pStats->glds_dot3_mac_rcv_error = 0; /* Mac Recv. errors */ + pStats->glds_dot3_frame_too_long = 0; /* Frame too long errors */ + pStats->glds_short = 0; /* Runt frames */ + + pStats->glds_noxmtbuf = 0; /* Xmit Buf errors */ + pStats->glds_xmtretry = 0; /* Xmit retries */ + pStats->glds_multixmt = 0; /* Multicast Xmits */ + pStats->glds_multircv = 0; /* Multicast Recvs. */ + pStats->glds_brdcstxmt = 0; /* Broadcast Xmits*/ + pStats->glds_brdcstrcv = 0; /* Broadcast Recvs. */ + + return GLD_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetAdpSolarisGetStats failed to get internal state.\n")); + return GLD_FAILURE; +} + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/solaris/hostname.vboxnet0 b/src/VBox/HostDrivers/VBoxNetAdp/solaris/hostname.vboxnet0 new file mode 100644 index 00000000..30545bb9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/solaris/hostname.vboxnet0 @@ -0,0 +1 @@ +192.168.56.1 diff --git a/src/VBox/HostDrivers/VBoxNetAdp/solaris/vboxnet.conf b/src/VBox/HostDrivers/VBoxNetAdp/solaris/vboxnet.conf new file mode 100644 index 00000000..69aa69dd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/solaris/vboxnet.conf @@ -0,0 +1,43 @@ +# $Id: vboxnet.conf $ +## @file +# Solaris Host VBoxNet Configuration +# + +# +# 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 . +# +# 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). +# +name="vboxnet" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/win/Makefile.kup b/src/VBox/HostDrivers/VBoxNetAdp/win/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.cpp b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.cpp new file mode 100644 index 00000000..d89ba4d0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.cpp @@ -0,0 +1,1888 @@ +/* $Id: VBoxNetAdp-win.cpp $ */ +/** @file + * VBoxNetAdp-win.cpp - NDIS6 Host-only Networking Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define LOG_GROUP LOG_GROUP_NET_ADP_DRV + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "VBoxNetAdp-win.h" +#include "VBox/VBoxNetCmn-win.h" + +#define VBOXNETADP_MEM_TAG 'OHBV' + +/* + * By default the link speed reported to be 1Gbps. We may wish to lower + * it to 100Mbps to work around issues with multi-cast traffic on the host. + * See @bugref{6379}. + */ +#define VBOXNETADPWIN_LINK_SPEED 1000000000ULL + +#define LogError LogRel + +/* Forward declarations */ +MINIPORT_INITIALIZE vboxNetAdpWinInitializeEx; +MINIPORT_HALT vboxNetAdpWinHaltEx; +MINIPORT_UNLOAD vboxNetAdpWinUnload; +MINIPORT_PAUSE vboxNetAdpWinPause; +MINIPORT_RESTART vboxNetAdpWinRestart; +MINIPORT_OID_REQUEST vboxNetAdpWinOidRequest; +MINIPORT_SEND_NET_BUFFER_LISTS vboxNetAdpWinSendNetBufferLists; +MINIPORT_RETURN_NET_BUFFER_LISTS vboxNetAdpWinReturnNetBufferLists; +MINIPORT_CANCEL_SEND vboxNetAdpWinCancelSend; +MINIPORT_CHECK_FOR_HANG vboxNetAdpWinCheckForHangEx; +MINIPORT_RESET vboxNetAdpWinResetEx; +MINIPORT_DEVICE_PNP_EVENT_NOTIFY vboxNetAdpWinDevicePnPEventNotify; +MINIPORT_SHUTDOWN vboxNetAdpWinShutdownEx; +MINIPORT_CANCEL_OID_REQUEST vboxNetAdpWinCancelOidRequest; + + +/* Packet types by destination address; used in statistics. */ +typedef enum { + kVBoxNetAdpWinPacketType_Unicast, + kVBoxNetAdpWinPacketType_Multicast, + kVBoxNetAdpWinPacketType_Broadcast, + kVBoxNetAdpWinPacketType_ArraySize /* Must be the last one */ +} VBOXNETADPWIN_PACKET_TYPE; + + +/* Miniport states as defined by NDIS. */ +typedef enum { + kVBoxNetAdpWinState_Initializing, + kVBoxNetAdpWinState_Paused, + kVBoxNetAdpWinState_Restarting, + kVBoxNetAdpWinState_Running, + kVBoxNetAdpWinState_Pausing, + kVBoxNetAdpWinState_32BitHack = 0x7fffffff +} VBOXNETADPWIN_ADAPTER_STATE; + + +/* + * Valid state transitions are: + * 1) Disconnected -> Connecting : start the worker thread, attempting to init IDC; + * 2) Connecting -> Disconnected : failed to start IDC init worker thread; + * 3) Connecting -> Connected : IDC init successful, terminate the worker; + * 4) Connecting -> Stopping : IDC init incomplete, but the driver is being unloaded, terminate the worker; + * 5) Connected -> Stopping : IDC init was successful, no worker, the driver is being unloaded; + * + * Driver terminates in either in Disconnected or in Stopping state. + */ +typedef enum { + kVBoxNetAdpWinIdcState_Disconnected = 0, /* Initial state */ + kVBoxNetAdpWinIdcState_Connecting, /* Attemping to init IDC, worker thread running */ + kVBoxNetAdpWinIdcState_Connected, /* Successfully connected to IDC, worker thread terminated */ + kVBoxNetAdpWinIdcState_Stopping /* Terminating the worker thread and disconnecting IDC */ +} VBOXNETADPWIN_IDC_STATE; + +typedef struct _VBOXNETADPGLOBALS +{ + /** Miniport driver handle. */ + NDIS_HANDLE hMiniportDriver; + /** Power management capabilities, shared by all instances, do not change after init. */ + NDIS_PNP_CAPABILITIES PMCaps; + /** The INTNET trunk network interface factory. */ + INTNETTRUNKFACTORY TrunkFactory; + /** The SUPDRV component factory registration. */ + SUPDRVFACTORY SupDrvFactory; + /** The SUPDRV IDC handle (opaque struct). */ + SUPDRVIDCHANDLE SupDrvIDC; + /** IDC init thread handle. */ + HANDLE hInitIdcThread; + /** Lock protecting the following members. */ + NDIS_SPIN_LOCK Lock; + /** Lock-protected: the head of module list. */ + RTLISTANCHOR ListOfAdapters; + /** Lock-protected: The number of current factory references. */ + int32_t volatile cFactoryRefs; + /** Lock-protected: IDC initialization state. */ + volatile uint32_t enmIdcState; + /** Lock-protected: event signaled when trunk factory is not in use. */ + NDIS_EVENT EventUnloadAllowed; +} VBOXNETADPGLOBALS, *PVBOXNETADPGLOBALS; + +/* win-specific global data */ +VBOXNETADPGLOBALS g_VBoxNetAdpGlobals; + + +typedef struct _VBOXNETADP_ADAPTER { + /** Auxiliary member to link adapters into a list. */ + RTLISTNODE node; + /** Adapter handle for NDIS. */ + NDIS_HANDLE hAdapter; + /** Memory pool network buffers are allocated from. */ + NDIS_HANDLE hPool; + /** Our RJ-45 port. + * This is what the internal network plugs into. */ + INTNETTRUNKIFPORT MyPort; + /** The RJ-45 port on the INTNET "switch". + * This is what we're connected to. */ + PINTNETTRUNKSWPORT pSwitchPort; + /** Pointer to global data */ + PVBOXNETADPGLOBALS pGlobals; + /** Adapter state in NDIS, used for assertions only */ + VBOXNETADPWIN_ADAPTER_STATE volatile enmAdapterState; /// @todo do we need it really? + /** The trunk state. */ + INTNETTRUNKIFSTATE volatile enmTrunkState; + /** Number of pending operations, when it reaches zero we signal EventIdle. */ + int32_t volatile cBusy; + /** The event that is signaled when we go idle and that pfnWaitForIdle blocks on. */ + NDIS_EVENT EventIdle; + /** MAC address of adapter. */ + RTMAC MacAddr; + /** Statistics: bytes received from internal network. */ + uint64_t au64StatsInOctets[kVBoxNetAdpWinPacketType_ArraySize]; + /** Statistics: packets received from internal network. */ + uint64_t au64StatsInPackets[kVBoxNetAdpWinPacketType_ArraySize]; + /** Statistics: bytes sent to internal network. */ + uint64_t au64StatsOutOctets[kVBoxNetAdpWinPacketType_ArraySize]; + /** Statistics: packets sent to internal network. */ + uint64_t au64StatsOutPackets[kVBoxNetAdpWinPacketType_ArraySize]; + /** Adapter friendly name. */ + char szName[1]; +} VBOXNETADP_ADAPTER; +typedef VBOXNETADP_ADAPTER *PVBOXNETADP_ADAPTER; + + +/* Port */ + +#define IFPORT_2_VBOXNETADP_ADAPTER(pIfPort) \ + ( (PVBOXNETADP_ADAPTER)((uint8_t *)(pIfPort) - RT_UOFFSETOF(VBOXNETADP_ADAPTER, MyPort)) ) + +DECLINLINE(VBOXNETADPWIN_ADAPTER_STATE) vboxNetAdpWinGetState(PVBOXNETADP_ADAPTER pThis) +{ + return (VBOXNETADPWIN_ADAPTER_STATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmAdapterState); +} + +DECLINLINE(VBOXNETADPWIN_ADAPTER_STATE) vboxNetAdpWinSetState(PVBOXNETADP_ADAPTER pThis, VBOXNETADPWIN_ADAPTER_STATE enmNewState) +{ + return (VBOXNETADPWIN_ADAPTER_STATE)ASMAtomicXchgU32((uint32_t volatile *)&pThis->enmAdapterState, enmNewState); +} + +DECLINLINE(bool) vboxNetAdpWinSetState(PVBOXNETADP_ADAPTER pThis, VBOXNETADPWIN_ADAPTER_STATE enmNewState, + VBOXNETADPWIN_ADAPTER_STATE enmOldState) +{ + return ASMAtomicCmpXchgU32((uint32_t volatile *)&pThis->enmAdapterState, enmNewState, enmOldState); +} + +#ifdef DEBUG + +DECLHIDDEN(void) vboxNetAdpWinDumpPackets(const char *pszMsg, PNET_BUFFER_LIST pBufLists) +{ + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + Log6(("%s packet: cb=%d offset=%d", pszMsg, NET_BUFFER_DATA_LENGTH(pBuf), NET_BUFFER_DATA_OFFSET(pBuf))); + for (PMDL pMdl = NET_BUFFER_FIRST_MDL(pBuf); + pMdl != NULL; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + Log6((" MDL: cb=%d", MmGetMdlByteCount(pMdl))); + } + Log6(("\n")); + } + } +} + +DECLINLINE(const char *) vboxNetAdpWinEthTypeStr(uint16_t uType) +{ + switch (uType) + { + case RTNET_ETHERTYPE_IPV4: return "IP"; + case RTNET_ETHERTYPE_IPV6: return "IPv6"; + case RTNET_ETHERTYPE_ARP: return "ARP"; + } + return "unknown"; +} + +#define VBOXNETADP_PKTDMPSIZE 0x50 + +/** + * Dump a packet to debug log. + * + * @param cpPacket The packet. + * @param cb The size of the packet. + * @param cszText A string denoting direction of packet transfer. + */ +DECLINLINE(void) vboxNetAdpWinDumpPacket(PCINTNETSG pSG, const char *cszText) +{ + uint8_t bPacket[VBOXNETADP_PKTDMPSIZE]; + + uint32_t cb = pSG->cbTotal < VBOXNETADP_PKTDMPSIZE ? pSG->cbTotal : VBOXNETADP_PKTDMPSIZE; + IntNetSgReadEx(pSG, 0, cb, bPacket); + + AssertReturnVoid(cb >= 14); + + uint8_t *pHdr = bPacket; + uint8_t *pEnd = bPacket + cb; + AssertReturnVoid(pEnd - pHdr >= 14); + uint16_t uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+12)); + Log2(("NetADP: %s (%d bytes), %RTmac => %RTmac, EthType=%s(0x%x)\n", + cszText, pSG->cbTotal, pHdr+6, pHdr, vboxNetAdpWinEthTypeStr(uEthType), uEthType)); + pHdr += sizeof(RTNETETHERHDR); + if (uEthType == RTNET_ETHERTYPE_VLAN) + { + AssertReturnVoid(pEnd - pHdr >= 4); + uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+2)); + Log2((" + VLAN: id=%d EthType=%s(0x%x)\n", RT_N2H_U16(*(uint16_t*)(pHdr)) & 0xFFF, + vboxNetAdpWinEthTypeStr(uEthType), uEthType)); + pHdr += 2 * sizeof(uint16_t); + } + uint8_t uProto = 0xFF; + switch (uEthType) + { + case RTNET_ETHERTYPE_IPV6: + AssertReturnVoid(pEnd - pHdr >= 40); + uProto = pHdr[6]; + Log2((" + IPv6: %RTnaipv6 => %RTnaipv6\n", pHdr+8, pHdr+24)); + pHdr += 40; + break; + case RTNET_ETHERTYPE_IPV4: + AssertReturnVoid(pEnd - pHdr >= 20); + uProto = pHdr[9]; + Log2((" + IP: %RTnaipv4 => %RTnaipv4\n", *(uint32_t*)(pHdr+12), *(uint32_t*)(pHdr+16))); + pHdr += (pHdr[0] & 0xF) * 4; + break; + case RTNET_ETHERTYPE_ARP: + AssertReturnVoid(pEnd - pHdr >= 28); + AssertReturnVoid(RT_N2H_U16(*(uint16_t*)(pHdr+2)) == RTNET_ETHERTYPE_IPV4); + switch (RT_N2H_U16(*(uint16_t*)(pHdr+6))) + { + case 1: /* ARP request */ + Log2((" + ARP-REQ: who-has %RTnaipv4 tell %RTnaipv4\n", + *(uint32_t*)(pHdr+24), *(uint32_t*)(pHdr+14))); + break; + case 2: /* ARP reply */ + Log2((" + ARP-RPL: %RTnaipv4 is-at %RTmac\n", + *(uint32_t*)(pHdr+14), pHdr+8)); + break; + default: + Log2((" + ARP: unknown op %d\n", RT_N2H_U16(*(uint16_t*)(pHdr+6)))); + break; + } + break; + /* There is no default case as uProto is initialized with 0xFF */ + } + while (uProto != 0xFF) + { + switch (uProto) + { + case 0: /* IPv6 Hop-by-Hop option*/ + case 60: /* IPv6 Destination option*/ + case 43: /* IPv6 Routing option */ + case 44: /* IPv6 Fragment option */ + Log2((" + IPv6 option (%d): \n", uProto)); + uProto = pHdr[0]; + pHdr += pHdr[1] * 8 + 8; /* Skip to the next extension/protocol */ + break; + case 51: /* IPv6 IPsec AH */ + Log2((" + IPv6 IPsec AH: \n")); + uProto = pHdr[0]; + pHdr += (pHdr[1] + 2) * 4; /* Skip to the next extension/protocol */ + break; + case 50: /* IPv6 IPsec ESP */ + /* Cannot decode IPsec, fall through */ + Log2((" + IPv6 IPsec ESP: \n")); + uProto = 0xFF; + break; + case 59: /* No Next Header */ + Log2((" + IPv6 No Next Header\n")); + uProto = 0xFF; + break; + case 58: /* IPv6-ICMP */ + switch (pHdr[0]) + { + case 1: Log2((" + IPv6-ICMP: destination unreachable, code %d\n", pHdr[1])); break; + case 128: Log2((" + IPv6-ICMP: echo request\n")); break; + case 129: Log2((" + IPv6-ICMP: echo reply\n")); break; + default: Log2((" + IPv6-ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 1: /* ICMP */ + switch (pHdr[0]) + { + case 0: Log2((" + ICMP: echo reply\n")); break; + case 8: Log2((" + ICMP: echo request\n")); break; + case 3: Log2((" + ICMP: destination unreachable, code %d\n", pHdr[1])); break; + default: Log2((" + ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 6: /* TCP */ + Log2((" + TCP: src=%d dst=%d seq=%x ack=%x\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)), + RT_N2H_U32(*(uint32_t*)(pHdr+4)), RT_N2H_U32(*(uint32_t*)(pHdr+8)))); + uProto = 0xFF; + break; + case 17: /* UDP */ + Log2((" + UDP: src=%d dst=%d\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)))); + uProto = 0xFF; + break; + default: + Log2((" + Unknown: proto=0x%x\n", uProto)); + uProto = 0xFF; + break; + } + } + Log3(("%.*Rhxd\n", cb, bPacket)); +} + +#else /* !DEBUG */ +//# define vboxNetAdpWinDumpFilterTypes(uFlags) do { } while (0) +//# define vboxNetAdpWinDumpOffloadSettings(p) do { } while (0) +//# define vboxNetAdpWinDumpSetOffloadSettings(p) do { } while (0) +# define vboxNetAdpWinDumpPackets(m,l) do { } while (0) +# define vboxNetAdpWinDumpPacket(p,t) do { } while (0) +#endif /* !DEBUG */ + + +DECLHIDDEN(VBOXNETADPWIN_PACKET_TYPE) vboxNetAdpWinPacketType(PINTNETSG pSG) +{ + static const uint8_t g_abBcastAddr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + AssertReturn(pSG->cbTotal >= sizeof(g_abBcastAddr), kVBoxNetAdpWinPacketType_Unicast); + AssertReturn(pSG->cSegsUsed > 0, kVBoxNetAdpWinPacketType_Unicast); + AssertReturn(pSG->aSegs[0].cb >= sizeof(g_abBcastAddr), kVBoxNetAdpWinPacketType_Unicast); + if (!memcmp(pSG->aSegs[0].pv, g_abBcastAddr, sizeof(g_abBcastAddr))) + return kVBoxNetAdpWinPacketType_Broadcast; + if ((*(uint8_t*)pSG->aSegs[0].pv) & 1) + return kVBoxNetAdpWinPacketType_Multicast; + return kVBoxNetAdpWinPacketType_Unicast; +} + +DECLINLINE(void) vboxNetAdpWinUpdateStats(uint64_t *pPacketStats, uint64_t *pOctetStats, PINTNETSG pSG) +{ + VBOXNETADPWIN_PACKET_TYPE enmPktType = vboxNetAdpWinPacketType(pSG); + ASMAtomicIncU64(&pPacketStats[enmPktType]); + ASMAtomicAddU64(&pOctetStats[enmPktType], pSG->cbTotal); +} + +DECLINLINE(void) vboxNetAdpWinFreeMdlChain(PMDL pMdl) +{ + PMDL pMdlNext; + while (pMdl) + { + pMdlNext = pMdl->Next; + PUCHAR pDataBuf; + ULONG cb = 0; + NdisQueryMdl(pMdl, &pDataBuf, &cb, NormalPagePriority); + NdisFreeMdl(pMdl); + Log4(("vboxNetAdpWinFreeMdlChain: freed MDL 0x%p\n", pMdl)); + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetAdpWinFreeMdlChain: freed data buffer 0x%p\n", pDataBuf)); + pMdl = pMdlNext; + } +} + +DECLHIDDEN(PNET_BUFFER_LIST) vboxNetAdpWinSGtoNB(PVBOXNETADP_ADAPTER pThis, PINTNETSG pSG) +{ + AssertReturn(pSG->cSegsUsed >= 1, NULL); + LogFlow(("==>vboxNetAdpWinSGtoNB: segments=%d hPool=%p cb=%u\n", pSG->cSegsUsed, + pThis->hPool, pSG->cbTotal)); + AssertReturn(pThis->hPool, NULL); + + + PNET_BUFFER_LIST pBufList = NULL; + ULONG cbMdl = pSG->cbTotal; + ULONG uDataOffset = cbMdl - pSG->cbTotal; + PUCHAR pDataBuf = (PUCHAR)NdisAllocateMemoryWithTagPriority(pThis->hAdapter, cbMdl, + VBOXNETADP_MEM_TAG, NormalPoolPriority); + if (pDataBuf) + { + Log4(("vboxNetAdpWinSGtoNB: allocated data buffer (cb=%u) 0x%p\n", cbMdl, pDataBuf)); + PMDL pMdl = NdisAllocateMdl(pThis->hAdapter, pDataBuf, cbMdl); + if (!pMdl) + { + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetAdpWinSGtoNB: freed data buffer 0x%p\n", pDataBuf)); + LogError(("vboxNetAdpWinSGtoNB: failed to allocate an MDL (cb=%u)\n", cbMdl)); + LogFlow(("<==vboxNetAdpWinSGtoNB: return NULL\n")); + return NULL; + } + PUCHAR pDst = pDataBuf + uDataOffset; + for (int i = 0; i < pSG->cSegsUsed; i++) + { + NdisMoveMemory(pDst, pSG->aSegs[i].pv, pSG->aSegs[i].cb); + pDst += pSG->aSegs[i].cb; + } + pBufList = NdisAllocateNetBufferAndNetBufferList(pThis->hPool, + 0 /* ContextSize */, + 0 /* ContextBackFill */, + pMdl, + uDataOffset, + pSG->cbTotal); + if (pBufList) + { + Log4(("vboxNetAdpWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pThis->hAdapter; + /** @todo Do we need to initialize anything else? */ + } + else + { + LogError(("vboxNetAdpWinSGtoNB: failed to allocate an NBL+NB\n")); + vboxNetAdpWinFreeMdlChain(pMdl); + } + } + else + { + LogError(("vboxNetAdpWinSGtoNB: failed to allocate data buffer (size=%u)\n", cbMdl)); + } + + LogFlow(("<==vboxNetAdpWinSGtoNB: return %p\n", pBufList)); + return pBufList; +} + +DECLINLINE(void) vboxNetAdpWinDestroySG(PINTNETSG pSG) +{ + NdisFreeMemory(pSG, 0, 0); + Log4(("vboxNetAdpWinDestroySG: freed SG 0x%p\n", pSG)); +} + +/** + * Worker for vboxNetAdpWinNBtoSG() that gets the max segment count needed. + * @note vboxNetAdpWinNBtoSG may use fewer depending on cbPacket and offset! + * @note vboxNetLwfWinCalcSegments() is a copy of this code. + */ +DECLINLINE(ULONG) vboxNetAdpWinCalcSegments(PNET_BUFFER pNetBuf) +{ + ULONG cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); pMdl; pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + /* Skip empty MDLs (see @bugref{9233}) */ + if (MmGetMdlByteCount(pMdl)) + cSegs++; + } + return cSegs; +} + +/** + * @note vboxNetLwfWinNBtoSG() is a copy of this code. + */ +DECLHIDDEN(PINTNETSG) vboxNetAdpWinNBtoSG(PVBOXNETADP_ADAPTER pThis, PNET_BUFFER pNetBuf) +{ + ULONG cbPacket = NET_BUFFER_DATA_LENGTH(pNetBuf); + ULONG cSegs = vboxNetAdpWinCalcSegments(pNetBuf); + /* Allocate and initialize SG */ + PINTNETSG pSG = (PINTNETSG)NdisAllocateMemoryWithTagPriority(pThis->hAdapter, + RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]), + VBOXNETADP_MEM_TAG, + NormalPoolPriority); + AssertReturn(pSG, pSG); + Log4(("vboxNetAdpWinNBtoSG: allocated SG 0x%p\n", pSG)); + IntNetSgInitTempSegs(pSG, cbPacket /*cbTotal*/, cSegs, cSegs /*cSegsUsed*/); + + ULONG uOffset = NET_BUFFER_CURRENT_MDL_OFFSET(pNetBuf); + cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); + pMdl != NULL && cbPacket > 0; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + ULONG cbSrc = MmGetMdlByteCount(pMdl); + if (cbSrc == 0) + continue; /* Skip empty MDLs (see @bugref{9233}) */ + + PUCHAR pSrc = (PUCHAR)MmGetSystemAddressForMdlSafe(pMdl, LowPagePriority); + if (!pSrc) + { + vboxNetAdpWinDestroySG(pSG); + return NULL; + } + + /* Handle the offset in the current (which is the first for us) MDL */ + if (uOffset) + { + if (uOffset < cbSrc) + { + pSrc += uOffset; + cbSrc -= uOffset; + uOffset = 0; + } + else + { + /* This is an invalid MDL chain */ + vboxNetAdpWinDestroySG(pSG); + return NULL; + } + } + + /* Do not read the last MDL beyond packet's end */ + if (cbSrc > cbPacket) + cbSrc = cbPacket; + + Assert(cSegs < pSG->cSegsAlloc); + pSG->aSegs[cSegs].pv = pSrc; + pSG->aSegs[cSegs].cb = cbSrc; + pSG->aSegs[cSegs].Phys = NIL_RTHCPHYS; + cSegs++; + cbPacket -= cbSrc; + } + + Assert(cbPacket == 0); + Assert(cSegs <= pSG->cSegsUsed); + + /* Update actual segment count in case we used fewer than anticipated. */ + pSG->cSegsUsed = (uint16_t)cSegs; + + return pSG; +} + +DECLINLINE(bool) vboxNetAdpWinIsActive(PVBOXNETADP_ADAPTER pThis) +{ + if (vboxNetAdpWinGetState(pThis) != kVBoxNetAdpWinState_Running) + return false; + if (pThis->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE) + return false; + AssertPtrReturn(pThis->pSwitchPort, false); + return true; +} + +DECLHIDDEN(bool) vboxNetAdpWinForwardToIntNet(PVBOXNETADP_ADAPTER pThis, PNET_BUFFER_LIST pList, uint32_t fSrc) +{ + if (!vboxNetAdpWinIsActive(pThis)) + { + LogFlow(("vboxNetAdpWinForwardToIntNet: not active\n")); + return false; + } + AssertReturn(pThis->pSwitchPort, false); + AssertReturn(pThis->pSwitchPort->pfnRecv, false); + LogFlow(("==>vboxNetAdpWinForwardToIntNet\n")); + + if (ASMAtomicIncS32(&pThis->cBusy) == 1) + NdisResetEvent(&pThis->EventIdle); + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + PINTNETSG pSG = vboxNetAdpWinNBtoSG(pThis, pBuf); + if (pSG) + { + vboxNetAdpWinUpdateStats(pThis->au64StatsOutPackets, pThis->au64StatsOutOctets, pSG); + vboxNetAdpWinDumpPacket(pSG, (fSrc & INTNETTRUNKDIR_WIRE)?"intnet <-- wire":"intnet <-- host"); + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL, pSG, fSrc); + vboxNetAdpWinDestroySG(pSG); + } + } + if (ASMAtomicDecS32(&pThis->cBusy) == 0) + NdisSetEvent(&pThis->EventIdle); + + return true; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRetain + */ +static DECLCALLBACK(void) vboxNetAdpWinPortRetain(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + RT_NOREF1(pThis); + LogFlow(("vboxNetAdpWinPortRetain: pThis=%p, pIfPort=%p\n", pThis, pIfPort)); +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRelease + */ +static DECLCALLBACK(void) vboxNetAdpWinPortRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + RT_NOREF1(pThis); + LogFlow(("vboxNetAdpWinPortRelease: pThis=%p, pIfPort=%p\n", pThis, pIfPort)); +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectAndRelease + */ +static DECLCALLBACK(void) vboxNetAdpWinPortDisconnectAndRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + + LogFlow(("vboxNetAdpWinPortDisconnectAndRelease: pThis=%p, pIfPort=%p\n", pThis, pIfPort)); + /* + * Serious paranoia. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + AssertPtr(pThis->pGlobals); + Assert(pThis->szName[0]); + + AssertPtr(pThis->pSwitchPort); + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + + pThis->pSwitchPort = NULL; +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnSetState + */ +static DECLCALLBACK(INTNETTRUNKIFSTATE) vboxNetAdpWinPortSetState(PINTNETTRUNKIFPORT pIfPort, INTNETTRUNKIFSTATE enmState) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + INTNETTRUNKIFSTATE enmOldTrunkState; + + LogFlow(("vboxNetAdpWinPortSetState: pThis=%p, pIfPort=%p, enmState=%d\n", pThis, pIfPort, enmState)); + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pThis->pGlobals); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertPtrReturn(pThis->pSwitchPort, INTNETTRUNKIFSTATE_INVALID); + AssertReturn(enmState > INTNETTRUNKIFSTATE_INVALID && enmState < INTNETTRUNKIFSTATE_END, + INTNETTRUNKIFSTATE_INVALID); + + enmOldTrunkState = pThis->enmTrunkState; + if (enmOldTrunkState != enmState) + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmTrunkState, enmState); + + return enmOldTrunkState; +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnWaitForIdle + */ +static DECLCALLBACK(int) vboxNetAdpWinPortWaitForIdle(PINTNETTRUNKIFPORT pIfPort, uint32_t cMillies) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + int rc; + + LogFlow(("vboxNetAdpWinPortWaitForIdle: pThis=%p, pIfPort=%p, cMillies=%u\n", pThis, pIfPort, cMillies)); + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertPtrReturn(pThis->pSwitchPort, VERR_INVALID_STATE); + AssertReturn(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING, VERR_INVALID_STATE); + + rc = NdisWaitEvent(&pThis->EventIdle, cMillies) ? VINF_SUCCESS : VERR_TIMEOUT; + + return rc; +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnXmit + */ +static DECLCALLBACK(int) vboxNetAdpWinPortXmit(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + RT_NOREF1(fDst); + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + int rc = VINF_SUCCESS; + + LogFlow(("vboxNetAdpWinPortXmit: pThis=%p, pIfPort=%p, pvIfData=%p, pSG=%p, fDst=0x%x\n", pThis, pIfPort, pvIfData, pSG, fDst)); + RT_NOREF1(pvIfData); + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pSG); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertPtrReturn(pThis->pSwitchPort, VERR_INVALID_STATE); + + vboxNetAdpWinDumpPacket(pSG, "intnet --> host"); + + /* + * First of all, indicate we are busy. It is possible the trunk or the adapter + * will get paused or even disconnected, so we need to check the state after + * we have marked ourselves busy. + * Later, when NDIS returns all buffers, we will mark ourselves idle. + */ + if (ASMAtomicIncS32(&pThis->cBusy) == 1) + NdisResetEvent(&pThis->EventIdle); + + if (vboxNetAdpWinIsActive(pThis)) + { + PNET_BUFFER_LIST pBufList = vboxNetAdpWinSGtoNB(pThis, pSG); + if (pBufList) + { + NdisMIndicateReceiveNetBufferLists(pThis->hAdapter, pBufList, NDIS_DEFAULT_PORT_NUMBER, 1, 0); + vboxNetAdpWinUpdateStats(pThis->au64StatsInPackets, pThis->au64StatsInOctets, pSG); + } + } + + return rc; +} + +/** + * @copydoc INTNETTRUNKIFPORT::pfnNotifyMacAddress + */ +static DECLCALLBACK(void) vboxNetAdpWinPortNotifyMacAddress(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PCRTMAC pMac) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + + LogFlow(("vboxNetAdpWinPortNotifyMacAddress: pThis=%p, pIfPort=%p, pvIfData=%p, pMac=%p\n", pThis, pIfPort, pvIfData, pMac)); + RT_NOREF3(pThis, pvIfData, pMac); + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + /// @todo Do we really need to handle this? +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnConnectInterface + */ +static DECLCALLBACK(int) vboxNetAdpWinPortConnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIf, void **ppvIfData) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + int rc; + + LogFlow(("vboxNetAdpWinPortConnectInterface: pThis=%p, pIfPort=%p, pvIf=%p, ppvIfData=%p\n", pThis, pIfPort, pvIf, ppvIfData)); + RT_NOREF3(pThis, pvIf, ppvIfData); + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + rc = VINF_SUCCESS; + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectInterface + */ +static DECLCALLBACK(void) vboxNetAdpWinPortDisconnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIfData) +{ + PVBOXNETADP_ADAPTER pThis = IFPORT_2_VBOXNETADP_ADAPTER(pIfPort); + int rc; + + LogFlow(("vboxNetAdpWinPortDisconnectInterface: pThis=%p, pIfPort=%p, pvIfData=%p\n", pThis, pIfPort, pvIfData)); + RT_NOREF2(pThis, pvIfData); + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + rc = VINF_SUCCESS; + AssertRC(rc); +} + + + +/** + * Implements the SUPDRV component factor interface query method. + * + * @returns Pointer to an interface. NULL if not supported. + * + * @param pSupDrvFactory Pointer to the component factory registration structure. + * @param pSession The session - unused. + * @param pszInterfaceUuid The factory interface id. + */ +static DECLCALLBACK(void *) vboxNetAdpWinQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, + const char *pszInterfaceUuid) +{ + PVBOXNETADPGLOBALS pGlobals = (PVBOXNETADPGLOBALS)((uint8_t *)pSupDrvFactory - RT_UOFFSETOF(VBOXNETADPGLOBALS, SupDrvFactory)); + + /* + * Convert the UUID strings and compare them. + */ + RTUUID UuidReq; + int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); + if (RT_SUCCESS(rc)) + { + if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_UUID_STR)) + { + NdisAcquireSpinLock(&pGlobals->Lock); + if (pGlobals->enmIdcState == kVBoxNetAdpWinIdcState_Connected) + { + pGlobals->cFactoryRefs++; + NdisResetEvent(&pGlobals->EventUnloadAllowed); + } + NdisReleaseSpinLock(&pGlobals->Lock); + return &pGlobals->TrunkFactory; + } +#ifdef LOG_ENABLED + else + Log(("VBoxNetFlt: unknown factory interface query (%s)\n", pszInterfaceUuid)); +#endif + } + else + Log(("VBoxNetFlt: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); + + RT_NOREF1(pSession); + return NULL; +} + + +DECLHIDDEN(void) vboxNetAdpWinReportCapabilities(PVBOXNETADP_ADAPTER pThis) +{ + if (pThis->pSwitchPort) + { + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->MacAddr); + /* Promiscuous mode makes no sense for host-only adapters, does it? */ + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, + INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + } +} + +/** + * @copydoc INTNETTRUNKFACTORY::pfnCreateAndConnect + */ +static DECLCALLBACK(int) vboxNetAdpWinFactoryCreateAndConnect(PINTNETTRUNKFACTORY pIfFactory, const char *pszName, + PINTNETTRUNKSWPORT pSwitchPort, uint32_t fFlags, + PINTNETTRUNKIFPORT *ppIfPort) +{ + PVBOXNETADPGLOBALS pGlobals = (PVBOXNETADPGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETADPGLOBALS, TrunkFactory)); + + LogFlow(("==>vboxNetAdpWinFactoryCreateAndConnect: pszName=%p:{%s} fFlags=%#x\n", pszName, pszName, fFlags)); + Assert(pGlobals->cFactoryRefs > 0); + AssertMsgReturn(!(fFlags & ~(INTNETTRUNKFACTORY_FLAG_NO_PROMISC)), + ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + DbgPrint("vboxNetAdpWinFactoryCreateAndConnect: looking for %s...\n", pszName); + PVBOXNETADP_ADAPTER pAdapter = NULL; + NdisAcquireSpinLock(&pGlobals->Lock); + RTListForEach(&g_VBoxNetAdpGlobals.ListOfAdapters, pAdapter, VBOXNETADP_ADAPTER, node) + { + Log(("vboxNetAdpWinFactoryCreateAndConnect: evaluating adapter=%s\n", pAdapter->szName)); + DbgPrint("vboxNetAdpWinFactoryCreateAndConnect: evaluating %s...\n", pAdapter->szName); + if (!RTStrICmp(pszName, pAdapter->szName)) + { + pAdapter->pSwitchPort = pSwitchPort; + *ppIfPort = &pAdapter->MyPort; + NdisReleaseSpinLock(&g_VBoxNetAdpGlobals.Lock); /// @todo too early? adp should have been connected by the time we do this + Log(("vboxNetAdpWinFactoryCreateAndConnect: found matching adapter, name=%s\n", pszName)); + vboxNetAdpWinReportCapabilities(pAdapter); + /// @todo I guess there is no need in vboxNetAdpWinRegisterIpAddrNotifier(pThis); + LogFlow(("<==vboxNetAdpWinFactoryCreateAndConnect: return VINF_SUCCESS\n")); + return VINF_SUCCESS; + } + } + NdisReleaseSpinLock(&pGlobals->Lock); + /// @todo vboxNetAdpLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 6); + DbgPrint("vboxNetAdpWinFactoryCreateAndConnect: could not find %s\n", pszName); + LogFlow(("<==vboxNetAdpWinFactoryCreateAndConnect: return VERR_INTNET_FLT_IF_NOT_FOUND\n")); + return VERR_INTNET_FLT_IF_NOT_FOUND; +} + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnRelease + */ +static DECLCALLBACK(void) vboxNetAdpWinFactoryRelease(PINTNETTRUNKFACTORY pIfFactory) +{ + PVBOXNETADPGLOBALS pGlobals = (PVBOXNETADPGLOBALS)((uint8_t *)pIfFactory - RT_OFFSETOF(VBOXNETADPGLOBALS, TrunkFactory)); + + NdisAcquireSpinLock(&pGlobals->Lock); + int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); + if (cRefs == 0) + NdisSetEvent(&pGlobals->EventUnloadAllowed); + NdisReleaseSpinLock(&pGlobals->Lock); + Assert(cRefs >= 0); NOREF(cRefs); + LogFlow(("vboxNetAdpWinFactoryRelease: cRefs=%d (new)\n", cRefs)); +} + + + +/* IDC */ + +DECLINLINE(const char *) vboxNetAdpWinIdcStateToText(uint32_t enmState) +{ + switch (enmState) + { + case kVBoxNetAdpWinIdcState_Disconnected: return "Disconnected"; + case kVBoxNetAdpWinIdcState_Connecting: return "Connecting"; + case kVBoxNetAdpWinIdcState_Connected: return "Connected"; + case kVBoxNetAdpWinIdcState_Stopping: return "Stopping"; + } + return "Unknown"; +} + +static VOID vboxNetAdpWinInitIdcWorker(PVOID pvContext) +{ + int rc; + PVBOXNETADPGLOBALS pGlobals = (PVBOXNETADPGLOBALS)pvContext; + + /* + * Note that we break the rules here and access IDC state wihout acquiring + * the lock. This is ok because vboxNetAdpWinUnload will wait for this + * thread to terminate itself and we always use atomic access to IDC state. + * We check the state (while holding the lock) further when we have succeeded + * to connect. We cannot take the lock here and release it later as we will + * be holding it for too long. + */ + while (ASMAtomicReadU32(&pGlobals->enmIdcState) == kVBoxNetAdpWinIdcState_Connecting) + { + /* + * Establish a connection to SUPDRV and register our component factory. + */ + rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + if (RT_SUCCESS(rc)) + { + /* + * At this point we should take the lock to access IDC state as + * we technically may now race with factory methods. + */ + NdisAcquireSpinLock(&pGlobals->Lock); + bool fSuccess = ASMAtomicCmpXchgU32(&pGlobals->enmIdcState, + kVBoxNetAdpWinIdcState_Connected, + kVBoxNetAdpWinIdcState_Connecting); + NdisReleaseSpinLock(&pGlobals->Lock); + if (!fSuccess) + { + /* The state has been changed (the only valid transition is to "Stopping"), undo init */ + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + Log(("vboxNetAdpWinInitIdcWorker: state change (Connecting -> %s) while initializing IDC, closed IDC, rc=0x%x\n", + vboxNetAdpWinIdcStateToText(ASMAtomicReadU32(&pGlobals->enmIdcState)), rc)); + } + else + { + Log(("vboxNetAdpWinInitIdcWorker: IDC state change Connecting -> Connected\n")); + } + } + } + else + { + LARGE_INTEGER WaitIn100nsUnits; + WaitIn100nsUnits.QuadPart = -(LONGLONG)5000000; /* 0.5 sec */ + KeDelayExecutionThread(KernelMode, FALSE /* non-alertable */, &WaitIn100nsUnits); + } + } + PsTerminateSystemThread(STATUS_SUCCESS); +} + + +DECLHIDDEN(int) vboxNetAdpWinStartInitIdcThread(PVBOXNETADPGLOBALS pGlobals) +{ + int rc = VERR_INVALID_STATE; + + /* No locking needed yet */ + if (ASMAtomicCmpXchgU32(&pGlobals->enmIdcState, kVBoxNetAdpWinIdcState_Connecting, kVBoxNetAdpWinIdcState_Disconnected)) + { + Log(("vboxNetAdpWinStartInitIdcThread: IDC state change Diconnected -> Connecting\n")); + + NTSTATUS Status = PsCreateSystemThread(&g_VBoxNetAdpGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, + NULL, + NULL, + NULL, + vboxNetAdpWinInitIdcWorker, + &g_VBoxNetAdpGlobals); + Log(("vboxNetAdpWinStartInitIdcThread: create IDC initialization thread, status=0x%x\n", Status)); + if (Status != STATUS_SUCCESS) + { + LogError(("vboxNetAdpWinStartInitIdcThread: IDC initialization failed (system thread creation, status=0x%x)\n", Status)); + /* + * We failed to init IDC and there will be no second chance. + */ + Log(("vboxNetAdpWinStartInitIdcThread: IDC state change Connecting -> Diconnected\n")); + ASMAtomicWriteU32(&g_VBoxNetAdpGlobals.enmIdcState, kVBoxNetAdpWinIdcState_Disconnected); + } + rc = RTErrConvertFromNtStatus(Status); + } + return rc; +} + + + +/* === !!!! */ + + +NDIS_OID g_SupportedOids[] = +{ + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_INTERRUPT_MODERATION, + OID_GEN_LINK_PARAMETERS, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_RCV_OK, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_STATISTICS, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_VENDOR_ID, + OID_GEN_XMIT_OK, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_PNP_SET_POWER +}; + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinAllocAdapter(NDIS_HANDLE hAdapter, PVBOXNETADP_ADAPTER *ppAdapter, ULONG uIfIndex) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + PVBOXNETADP_ADAPTER pAdapter = NULL; + PVBOXNETADPGLOBALS pGlobals = &g_VBoxNetAdpGlobals; + + LogFlow(("==>vboxNetAdpWinAllocAdapter: adapter handle=%p\n", hAdapter)); + + /* Get the name */ + UNICODE_STRING strUnicodeName; + Status = NdisMQueryAdapterInstanceName(&strUnicodeName, hAdapter); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetAdpWinAllocAdapter: NdisMQueryAdapterInstanceName failed with 0x%x\n", Status)); + return Status; + } + + ANSI_STRING strAnsiName; + /* We use the miniport name to associate this filter module with the netflt instance */ + NTSTATUS rc = RtlUnicodeStringToAnsiString(&strAnsiName, + &strUnicodeName, + TRUE); + if (rc != STATUS_SUCCESS) + { + LogError(("vboxNetAdpWinAllocAdapter: RtlUnicodeStringToAnsiString(%ls) failed with 0x%x\n", + strUnicodeName, rc)); + //vboxNetAdpLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_FAILURE, 2); + NdisFreeMemory(strUnicodeName.Buffer, 0, 0); + return NDIS_STATUS_FAILURE; + } + NdisFreeMemory(strUnicodeName.Buffer, 0, 0); + DbgPrint("vboxNetAdpWinAllocAdapter: name=%Z\n", &strAnsiName); + + *ppAdapter = NULL; + + UINT cbAdapterWithNameExtra = sizeof(VBOXNETADP_ADAPTER) + strAnsiName.Length; + pAdapter = (PVBOXNETADP_ADAPTER)NdisAllocateMemoryWithTagPriority(pGlobals->hMiniportDriver, + cbAdapterWithNameExtra, + VBOXNETADPWIN_TAG, + NormalPoolPriority); + if (!pAdapter) + { + RtlFreeAnsiString(&strAnsiName); + Status = NDIS_STATUS_RESOURCES; + Log(("vboxNetAdpWinAllocAdapter: Out of memory while allocating adapter context (size=%d)\n", sizeof(VBOXNETADP_ADAPTER))); + } + else + { + NdisZeroMemory(pAdapter, cbAdapterWithNameExtra); + NdisMoveMemory(pAdapter->szName, strAnsiName.Buffer, strAnsiName.Length); + RtlFreeAnsiString(&strAnsiName); + + /* Allocate buffer pool */ + NET_BUFFER_LIST_POOL_PARAMETERS PoolParams; + NdisZeroMemory(&PoolParams, sizeof(PoolParams)); + PoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + PoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + PoolParams.Header.Size = sizeof(PoolParams); + PoolParams.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + PoolParams.fAllocateNetBuffer = TRUE; + PoolParams.ContextSize = 0; + PoolParams.PoolTag = VBOXNETADP_MEM_TAG; + pAdapter->hPool = NdisAllocateNetBufferListPool(hAdapter, &PoolParams); + if (!pAdapter->hPool) + { + LogError(("vboxNetAdpWinAllocAdapter: NdisAllocateNetBufferListPool failed\n")); + NdisFreeMemory(pAdapter, 0, 0); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetAdpWinAllocAdapter: allocated NBL+NB pool 0x%p\n", pAdapter->hPool)); + + pAdapter->hAdapter = hAdapter; + pAdapter->MyPort.u32Version = INTNETTRUNKIFPORT_VERSION; + pAdapter->MyPort.pfnRetain = vboxNetAdpWinPortRetain; + pAdapter->MyPort.pfnRelease = vboxNetAdpWinPortRelease; + pAdapter->MyPort.pfnDisconnectAndRelease = vboxNetAdpWinPortDisconnectAndRelease; + pAdapter->MyPort.pfnSetState = vboxNetAdpWinPortSetState; + pAdapter->MyPort.pfnWaitForIdle = vboxNetAdpWinPortWaitForIdle; + pAdapter->MyPort.pfnXmit = vboxNetAdpWinPortXmit; + pAdapter->MyPort.pfnNotifyMacAddress = vboxNetAdpWinPortNotifyMacAddress; + pAdapter->MyPort.pfnConnectInterface = vboxNetAdpWinPortConnectInterface; + pAdapter->MyPort.pfnDisconnectInterface = vboxNetAdpWinPortDisconnectInterface; + pAdapter->MyPort.u32VersionEnd = INTNETTRUNKIFPORT_VERSION; + pAdapter->pGlobals = pGlobals; + pAdapter->enmAdapterState = kVBoxNetAdpWinState_Initializing; + pAdapter->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; + pAdapter->cBusy = 0; + NdisInitializeEvent(&pAdapter->EventIdle); + NdisSetEvent(&pAdapter->EventIdle); /* We are idle initially */ + + /* Use a locally administered version of the OUI we use for the guest NICs. */ + pAdapter->MacAddr.au8[0] = 0x08 | 2; + pAdapter->MacAddr.au8[1] = 0x00; + pAdapter->MacAddr.au8[2] = 0x27; + + pAdapter->MacAddr.au8[3] = (uIfIndex >> 16) & 0xFF; + pAdapter->MacAddr.au8[4] = (uIfIndex >> 8) & 0xFF; + pAdapter->MacAddr.au8[5] = uIfIndex & 0xFF; + + NdisAcquireSpinLock(&pGlobals->Lock); + RTListPrepend(&pGlobals->ListOfAdapters, &pAdapter->node); + NdisReleaseSpinLock(&pGlobals->Lock); + + *ppAdapter = pAdapter; + } + LogFlow(("<==vboxNetAdpWinAllocAdapter: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(void) vboxNetAdpWinFreeAdapter(PVBOXNETADP_ADAPTER pAdapter) +{ + /* Remove from adapter chain */ + NdisAcquireSpinLock(&pAdapter->pGlobals->Lock); + RTListNodeRemove(&pAdapter->node); + NdisReleaseSpinLock(&pAdapter->pGlobals->Lock); + + NdisFreeMemory(pAdapter, 0, 0); +} + +DECLINLINE(NDIS_MEDIA_CONNECT_STATE) vboxNetAdpWinGetConnectState(PVBOXNETADP_ADAPTER pAdapter) +{ + RT_NOREF1(pAdapter); + return MediaConnectStateConnected; +} + + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinInitializeEx(IN NDIS_HANDLE NdisMiniportHandle, + IN NDIS_HANDLE MiniportDriverContext, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters) +{ + RT_NOREF1(MiniportDriverContext); + PVBOXNETADP_ADAPTER pAdapter = NULL; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + LogFlow(("==>vboxNetAdpWinInitializeEx: miniport=0x%x\n", NdisMiniportHandle)); + + do + { + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES RAttrs = {{0}}; + NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES GAttrs = {{0}}; + + Status = vboxNetAdpWinAllocAdapter(NdisMiniportHandle, &pAdapter, MiniportInitParameters->IfIndex); + if (Status != NDIS_STATUS_SUCCESS) + { + Log(("vboxNetAdpWinInitializeEx: Failed to allocate the adapter context with 0x%x\n", Status)); + break; + } + + RAttrs.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + RAttrs.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + RAttrs.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + RAttrs.MiniportAdapterContext = pAdapter; + RAttrs.AttributeFlags = VBOXNETADPWIN_ATTR_FLAGS; // NDIS_MINIPORT_ATTRIBUTES_NDIS_WDM + RAttrs.CheckForHangTimeInSeconds = VBOXNETADPWIN_HANG_CHECK_TIME; + RAttrs.InterfaceType = NdisInterfaceInternal; + + Status = NdisMSetMiniportAttributes(NdisMiniportHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&RAttrs); + if (Status != NDIS_STATUS_SUCCESS) + { + Log(("vboxNetAdpWinInitializeEx: NdisMSetMiniportAttributes(registration) failed with 0x%x\n", Status)); + break; + } + + /// @todo Registry? + + /// @todo WDM stack? + + /// @todo DPC? + + GAttrs.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + GAttrs.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + GAttrs.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + + GAttrs.MediaType = NdisMedium802_3; + GAttrs.PhysicalMediumType = NdisPhysicalMediumUnspecified; + GAttrs.MtuSize = 1500; /// @todo + GAttrs.MaxXmitLinkSpeed = VBOXNETADPWIN_LINK_SPEED; + GAttrs.XmitLinkSpeed = VBOXNETADPWIN_LINK_SPEED; + GAttrs.MaxRcvLinkSpeed = VBOXNETADPWIN_LINK_SPEED; + GAttrs.RcvLinkSpeed = VBOXNETADPWIN_LINK_SPEED; + GAttrs.MediaConnectState = vboxNetAdpWinGetConnectState(pAdapter); + GAttrs.MediaDuplexState = MediaDuplexStateFull; + GAttrs.LookaheadSize = 1500; /// @todo + GAttrs.MacOptions = VBOXNETADP_MAC_OPTIONS; + GAttrs.SupportedPacketFilters = VBOXNETADP_SUPPORTED_FILTERS; + GAttrs.MaxMulticastListSize = 32; /// @todo + + GAttrs.MacAddressLength = ETH_LENGTH_OF_ADDRESS; + Assert(GAttrs.MacAddressLength == sizeof(pAdapter->MacAddr)); + memcpy(GAttrs.PermanentMacAddress, pAdapter->MacAddr.au8, GAttrs.MacAddressLength); + memcpy(GAttrs.CurrentMacAddress, pAdapter->MacAddr.au8, GAttrs.MacAddressLength); + + GAttrs.RecvScaleCapabilities = NULL; + GAttrs.AccessType = NET_IF_ACCESS_BROADCAST; + GAttrs.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; + GAttrs.ConnectionType = NET_IF_CONNECTION_DEDICATED; + GAttrs.IfType = IF_TYPE_ETHERNET_CSMACD; + GAttrs.IfConnectorPresent = false; + GAttrs.SupportedStatistics = VBOXNETADPWIN_SUPPORTED_STATISTICS; + GAttrs.SupportedPauseFunctions = NdisPauseFunctionsUnsupported; + GAttrs.DataBackFillSize = 0; + GAttrs.ContextBackFillSize = 0; + GAttrs.SupportedOidList = g_SupportedOids; + GAttrs.SupportedOidListLength = sizeof(g_SupportedOids); + GAttrs.AutoNegotiationFlags = NDIS_LINK_STATE_DUPLEX_AUTO_NEGOTIATED; + GAttrs.PowerManagementCapabilities = &g_VBoxNetAdpGlobals.PMCaps; + + Status = NdisMSetMiniportAttributes(NdisMiniportHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&GAttrs); + if (Status != NDIS_STATUS_SUCCESS) + { + Log(("vboxNetAdpWinInitializeEx: NdisMSetMiniportAttributes(general) failed with 0x%x\n", Status)); + break; + } + + VBOXNETADPWIN_ADAPTER_STATE enmPrevState = vboxNetAdpWinSetState(pAdapter, kVBoxNetAdpWinState_Paused); + RT_NOREF1(enmPrevState); + Assert(enmPrevState == kVBoxNetAdpWinState_Initializing); + } while (false); + + if (Status != NDIS_STATUS_SUCCESS) + { + if (pAdapter) + vboxNetAdpWinFreeAdapter(pAdapter); + } + + LogFlow(("<==vboxNetAdpWinInitializeEx: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(VOID) vboxNetAdpWinHaltEx(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_HALT_ACTION HaltAction) +{ + RT_NOREF1(HaltAction); + PVBOXNETADP_ADAPTER pThis = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + LogFlow(("==>vboxNetAdpWinHaltEx\n")); + AssertPtr(pThis); + Assert(vboxNetAdpWinGetState(pThis) == kVBoxNetAdpWinState_Paused); + /* + * Check if the trunk is active which means the adapter gets disabled + * while it is used by VM(s) and we need to disconnect the trunk. + */ + if (pThis->pSwitchPort && pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE) + pThis->pSwitchPort->pfnDisconnect(pThis->pSwitchPort, &pThis->MyPort, NULL); + /* + * Since we are already in the paused state and we have disconnected + * the trunk, we can safely destroy this adapter. + */ + vboxNetAdpWinFreeAdapter(pThis); + LogFlow(("<==vboxNetAdpWinHaltEx\n")); +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinPause(IN NDIS_HANDLE MiniportAdapterContext, + IN PNDIS_MINIPORT_PAUSE_PARAMETERS MiniportPauseParameters) +{ + RT_NOREF1(MiniportPauseParameters); + PVBOXNETADP_ADAPTER pThis = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + LogFlow(("==>vboxNetAdpWinPause\n")); + VBOXNETADPWIN_ADAPTER_STATE enmPrevState = vboxNetAdpWinSetState(pThis, kVBoxNetAdpWinState_Pausing); + Assert(enmPrevState == kVBoxNetAdpWinState_Running); + if (!NdisWaitEvent(&pThis->EventIdle, 1000 /* ms */)) + { + LogError(("vboxNetAdpWinPause: timed out while pausing the adapter\n")); + /// @todo implement NDIS_STATUS_PENDING case? probably not. + } + enmPrevState = vboxNetAdpWinSetState(pThis, kVBoxNetAdpWinState_Paused); + Assert(enmPrevState == kVBoxNetAdpWinState_Pausing); + LogFlow(("<==vboxNetAdpWinPause: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinRestart(IN NDIS_HANDLE MiniportAdapterContext, + IN PNDIS_MINIPORT_RESTART_PARAMETERS MiniportRestartParameters) +{ + RT_NOREF1(MiniportRestartParameters); + PVBOXNETADP_ADAPTER pThis = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + LogFlow(("==>vboxNetAdpWinRestart\n")); + VBOXNETADPWIN_ADAPTER_STATE enmPrevState = vboxNetAdpWinSetState(pThis, kVBoxNetAdpWinState_Restarting); + Assert(enmPrevState == kVBoxNetAdpWinState_Paused); + /// @todo anything? + enmPrevState = vboxNetAdpWinSetState(pThis, kVBoxNetAdpWinState_Running); + Assert(enmPrevState == kVBoxNetAdpWinState_Restarting); + LogFlow(("<==vboxNetAdpWinRestart: status=0x%x\n", Status)); + return Status; +} + +DECLINLINE(uint64_t) vboxNetAdpWinStatsTotals(uint64_t *pStats) +{ + return pStats[kVBoxNetAdpWinPacketType_Unicast] + + pStats[kVBoxNetAdpWinPacketType_Multicast] + + pStats[kVBoxNetAdpWinPacketType_Broadcast]; +} + +DECLINLINE(PVOID) vboxNetAdpWinStatsU64(uint64_t *pTmp, ULONG *pcbTmp, uint64_t u64Stat) +{ + *pcbTmp = sizeof(*pTmp); + *pTmp = u64Stat; + return pTmp; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinOidRqQuery(PVBOXNETADP_ADAPTER pThis, + PNDIS_OID_REQUEST pRequest) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + struct _NDIS_OID_REQUEST::_REQUEST_DATA::_QUERY *pQuery = &pRequest->DATA.QUERY_INFORMATION; + + LogFlow(("==>vboxNetAdpWinOidRqQuery\n")); + + uint64_t u64Tmp = 0; + ULONG ulTmp = 0; + PVOID pInfo = &ulTmp; + ULONG cbInfo = sizeof(ulTmp); + + switch (pQuery->Oid) + { + case OID_GEN_INTERRUPT_MODERATION: + { + PNDIS_INTERRUPT_MODERATION_PARAMETERS pParams = + (PNDIS_INTERRUPT_MODERATION_PARAMETERS)pQuery->InformationBuffer; + cbInfo = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + if (cbInfo > pQuery->InformationBufferLength) + break; + pParams->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + pParams->Header.Revision = NDIS_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + pParams->Header.Size = NDIS_SIZEOF_INTERRUPT_MODERATION_PARAMETERS_REVISION_1; + pParams->Flags = 0; + pParams->InterruptModeration = NdisInterruptModerationNotSupported; + pInfo = NULL; /* Do not copy */ + break; + } + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + ulTmp = VBOXNETADP_MAX_FRAME_SIZE; + break; + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_TRANSMIT_BUFFER_SPACE: + /// @todo Make configurable + ulTmp = VBOXNETADP_MAX_FRAME_SIZE * 40; + break; + case OID_GEN_RCV_OK: + pInfo = vboxNetAdpWinStatsU64(&u64Tmp, &cbInfo, vboxNetAdpWinStatsTotals(pThis->au64StatsInPackets)); + break; + case OID_GEN_XMIT_OK: + pInfo = vboxNetAdpWinStatsU64(&u64Tmp, &cbInfo, vboxNetAdpWinStatsTotals(pThis->au64StatsOutPackets)); + break; + case OID_GEN_STATISTICS: + { + PNDIS_STATISTICS_INFO pStats = + (PNDIS_STATISTICS_INFO)pQuery->InformationBuffer; + cbInfo = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1; + if (cbInfo > pQuery->InformationBufferLength) + break; + pInfo = NULL; /* Do not copy */ + memset(pStats, 0, cbInfo); + pStats->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + pStats->Header.Revision = NDIS_STATISTICS_INFO_REVISION_1; + pStats->Header.Size = NDIS_SIZEOF_STATISTICS_INFO_REVISION_1; + pStats->SupportedStatistics = + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV + | NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV + | NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV + | NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV + | NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS + | NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR + | NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR + | NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS + | NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV + | NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV + | NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV + | NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT + | NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT; + + pStats->ifHCInOctets = vboxNetAdpWinStatsTotals(pThis->au64StatsInOctets); + pStats->ifHCInUcastPkts = ASMAtomicReadU64(&pThis->au64StatsInPackets[kVBoxNetAdpWinPacketType_Unicast]); + pStats->ifHCInMulticastPkts = ASMAtomicReadU64(&pThis->au64StatsInPackets[kVBoxNetAdpWinPacketType_Multicast]); + pStats->ifHCInBroadcastPkts = ASMAtomicReadU64(&pThis->au64StatsInPackets[kVBoxNetAdpWinPacketType_Broadcast]); + pStats->ifHCOutOctets = vboxNetAdpWinStatsTotals(pThis->au64StatsOutOctets);; + pStats->ifHCOutUcastPkts = ASMAtomicReadU64(&pThis->au64StatsOutPackets[kVBoxNetAdpWinPacketType_Unicast]); + pStats->ifHCOutMulticastPkts = ASMAtomicReadU64(&pThis->au64StatsOutPackets[kVBoxNetAdpWinPacketType_Multicast]); + pStats->ifHCOutBroadcastPkts = ASMAtomicReadU64(&pThis->au64StatsOutPackets[kVBoxNetAdpWinPacketType_Broadcast]); + pStats->ifHCInUcastOctets = ASMAtomicReadU64(&pThis->au64StatsInOctets[kVBoxNetAdpWinPacketType_Unicast]); + pStats->ifHCInMulticastOctets = ASMAtomicReadU64(&pThis->au64StatsInOctets[kVBoxNetAdpWinPacketType_Multicast]); + pStats->ifHCInBroadcastOctets = ASMAtomicReadU64(&pThis->au64StatsInOctets[kVBoxNetAdpWinPacketType_Broadcast]); + pStats->ifHCOutUcastOctets = ASMAtomicReadU64(&pThis->au64StatsOutOctets[kVBoxNetAdpWinPacketType_Unicast]); + pStats->ifHCOutMulticastOctets = ASMAtomicReadU64(&pThis->au64StatsOutOctets[kVBoxNetAdpWinPacketType_Multicast]); + pStats->ifHCOutBroadcastOctets = ASMAtomicReadU64(&pThis->au64StatsOutOctets[kVBoxNetAdpWinPacketType_Broadcast]); + break; + } + case OID_GEN_VENDOR_DESCRIPTION: + pInfo = VBOXNETADP_VENDOR_NAME; + cbInfo = sizeof(VBOXNETADP_VENDOR_NAME); + break; + case OID_GEN_VENDOR_DRIVER_VERSION: + ulTmp = (VBOXNETADP_VERSION_NDIS_MAJOR << 16) | VBOXNETADP_VERSION_NDIS_MINOR; + break; + case OID_GEN_VENDOR_ID: + ulTmp = VBOXNETADP_VENDOR_ID; + break; + case OID_802_3_PERMANENT_ADDRESS: + case OID_802_3_CURRENT_ADDRESS: + pInfo = &pThis->MacAddr; + cbInfo = sizeof(pThis->MacAddr); + break; + //case OID_802_3_MULTICAST_LIST: + case OID_802_3_MAXIMUM_LIST_SIZE: + ulTmp = VBOXNETADP_MCAST_LIST_SIZE; + break; + case OID_PNP_CAPABILITIES: + pInfo = &pThis->pGlobals->PMCaps; + cbInfo = sizeof(pThis->pGlobals->PMCaps); + break; + case OID_PNP_QUERY_POWER: + pInfo = NULL; /* Do not copy */ + cbInfo = 0; + break; + default: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + if (cbInfo > pQuery->InformationBufferLength) + { + pQuery->BytesNeeded = cbInfo; + Status = NDIS_STATUS_BUFFER_TOO_SHORT; + } + else + { + if (pInfo) + NdisMoveMemory(pQuery->InformationBuffer, pInfo, cbInfo); + pQuery->BytesWritten = cbInfo; + } + } + + LogFlow(("<==vboxNetAdpWinOidRqQuery: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinOidRqSet(PVBOXNETADP_ADAPTER pAdapter, + PNDIS_OID_REQUEST pRequest) +{ + RT_NOREF1(pAdapter); + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + struct _NDIS_OID_REQUEST::_REQUEST_DATA::_SET *pSet = &pRequest->DATA.SET_INFORMATION; + + LogFlow(("==>vboxNetAdpWinOidRqSet\n")); + + switch (pSet->Oid) + { + case OID_GEN_CURRENT_LOOKAHEAD: + if (pSet->InformationBufferLength != sizeof(ULONG)) + { + pSet->BytesNeeded = sizeof(ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + /// @todo For the time being we simply ignore lookahead settings. + pSet->BytesRead = sizeof(ULONG); + Status = NDIS_STATUS_SUCCESS; + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + if (pSet->InformationBufferLength != sizeof(ULONG)) + { + pSet->BytesNeeded = sizeof(ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + /// @todo For the time being we simply ignore packet filter settings. + pSet->BytesRead = pSet->InformationBufferLength; + Status = NDIS_STATUS_SUCCESS; + break; + + case OID_GEN_INTERRUPT_MODERATION: + pSet->BytesNeeded = 0; + pSet->BytesRead = 0; + Status = NDIS_STATUS_INVALID_DATA; + break; + + case OID_PNP_SET_POWER: + if (pSet->InformationBufferLength < sizeof(NDIS_DEVICE_POWER_STATE)) + { + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + pSet->BytesRead = sizeof(NDIS_DEVICE_POWER_STATE); + Status = NDIS_STATUS_SUCCESS; + break; + + default: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + LogFlow(("<==vboxNetAdpWinOidRqSet: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinOidRequest(IN NDIS_HANDLE MiniportAdapterContext, + IN PNDIS_OID_REQUEST NdisRequest) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + PVBOXNETADP_ADAPTER pAdapter = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + LogFlow(("==>vboxNetAdpWinOidRequest\n")); + vboxNetCmnWinDumpOidRequest(__FUNCTION__, NdisRequest); + + switch (NdisRequest->RequestType) + { +#if 0 + case NdisRequestMethod: + Status = vboxNetAdpWinOidRqMethod(pAdapter, NdisRequest); + break; +#endif + + case NdisRequestSetInformation: + Status = vboxNetAdpWinOidRqSet(pAdapter, NdisRequest); + break; + + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + Status = vboxNetAdpWinOidRqQuery(pAdapter, NdisRequest); + break; + + default: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + LogFlow(("<==vboxNetAdpWinOidRequest: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(VOID) vboxNetAdpWinSendNetBufferLists(IN NDIS_HANDLE MiniportAdapterContext, + IN PNET_BUFFER_LIST NetBufferLists, + IN NDIS_PORT_NUMBER PortNumber, + IN ULONG SendFlags) +{ + RT_NOREF1(PortNumber); + PVBOXNETADP_ADAPTER pAdapter = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + LogFlow(("==>vboxNetAdpWinSendNetBufferLists\n")); + PNET_BUFFER_LIST pNbl = NetBufferLists; + vboxNetAdpWinDumpPackets("vboxNetAdpWinSendNetBufferLists: got", pNbl); + + /* We alwast complete all send requests. */ + for (pNbl = NetBufferLists; pNbl; pNbl = NET_BUFFER_LIST_NEXT_NBL(pNbl)) + { + vboxNetAdpWinForwardToIntNet(pAdapter, pNbl, INTNETTRUNKDIR_HOST); + NET_BUFFER_LIST_STATUS(pNbl) = NDIS_STATUS_SUCCESS; + } + NdisMSendNetBufferListsComplete(pAdapter->hAdapter, NetBufferLists, + (SendFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL) ? + NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0); + LogFlow(("<==vboxNetAdpWinSendNetBufferLists\n")); +} + +DECLHIDDEN(VOID) vboxNetAdpWinReturnNetBufferLists(IN NDIS_HANDLE MiniportAdapterContext, + IN PNET_BUFFER_LIST NetBufferLists, + IN ULONG ReturnFlags) +{ + LogFlow(("==>vboxNetAdpWinReturnNetBufferLists\n")); + RT_NOREF1(ReturnFlags); + PVBOXNETADP_ADAPTER pThis = (PVBOXNETADP_ADAPTER)MiniportAdapterContext; + PNET_BUFFER_LIST pList = NetBufferLists; + while (pList) + { + Assert(pList->SourceHandle == pThis->hAdapter); + Assert(NET_BUFFER_LIST_FIRST_NB(pList)); + Assert(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + + PNET_BUFFER_LIST pNextList = NET_BUFFER_LIST_NEXT_NBL(pList); + + vboxNetAdpWinFreeMdlChain(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + NdisFreeNetBufferList(pList); + Log4(("vboxNetLwfWinReturnNetBufferLists: freed NBL+NB+MDL+Data 0x%p\n", pList)); + Assert(ASMAtomicReadS32(&pThis->cBusy) > 0); + if (ASMAtomicDecS32(&pThis->cBusy) == 0) + NdisSetEvent(&pThis->EventIdle); + + pList = pNextList; + } + LogFlow(("<==vboxNetAdpWinReturnNetBufferLists\n")); +} + +DECLHIDDEN(VOID) vboxNetAdpWinCancelSend(IN NDIS_HANDLE MiniportAdapterContext, + IN PVOID CancelId) +{ + RT_NOREF2(MiniportAdapterContext, CancelId); + LogFlow(("==>vboxNetAdpWinCancelSend\n")); + Log(("vboxNetAdpWinCancelSend: We should not be here!\n")); + LogFlow(("<==vboxNetAdpWinCancelSend\n")); +} + + +DECLHIDDEN(BOOLEAN) vboxNetAdpWinCheckForHangEx(IN NDIS_HANDLE MiniportAdapterContext) +{ + RT_NOREF1(MiniportAdapterContext); + LogFlow(("==>vboxNetAdpWinCheckForHangEx\n")); + LogFlow(("<==vboxNetAdpWinCheckForHangEx return false\n")); + return FALSE; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinResetEx(IN NDIS_HANDLE MiniportAdapterContext, + OUT PBOOLEAN AddressingReset) +{ + RT_NOREF2(MiniportAdapterContext, AddressingReset); + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + LogFlow(("==>vboxNetAdpWinResetEx\n")); + LogFlow(("<==vboxNetAdpWinResetEx: status=0x%x\n", Status)); + return Status; +} + +DECLHIDDEN(VOID) vboxNetAdpWinDevicePnPEventNotify(IN NDIS_HANDLE MiniportAdapterContext, + IN PNET_DEVICE_PNP_EVENT NetDevicePnPEvent) +{ + RT_NOREF2(MiniportAdapterContext, NetDevicePnPEvent); + LogFlow(("==>vboxNetAdpWinDevicePnPEventNotify\n")); + Log(("vboxNetAdpWinDevicePnPEventNotify: PnP event=%d\n", NetDevicePnPEvent->DevicePnPEvent)); + LogFlow(("<==vboxNetAdpWinDevicePnPEventNotify\n")); +} + + +DECLHIDDEN(VOID) vboxNetAdpWinShutdownEx(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_SHUTDOWN_ACTION ShutdownAction) +{ + RT_NOREF2(MiniportAdapterContext, ShutdownAction); + LogFlow(("==>vboxNetAdpWinShutdownEx\n")); + Log(("vboxNetAdpWinShutdownEx: action=%d\n", ShutdownAction)); + LogFlow(("<==vboxNetAdpWinShutdownEx\n")); +} + +DECLHIDDEN(VOID) vboxNetAdpWinCancelOidRequest(IN NDIS_HANDLE MiniportAdapterContext, + IN PVOID RequestId) +{ + RT_NOREF2(MiniportAdapterContext, RequestId); + LogFlow(("==>vboxNetAdpWinCancelOidRequest\n")); + Log(("vboxNetAdpWinCancelOidRequest: req id=%p\n", RequestId)); + LogFlow(("<==vboxNetAdpWinCancelOidRequest\n")); +} + + + +DECLHIDDEN(VOID) vboxNetAdpWinUnload(IN PDRIVER_OBJECT DriverObject) +{ + RT_NOREF1(DriverObject); + LogFlow(("==>vboxNetAdpWinUnload\n")); + PVBOXNETADPGLOBALS pGlobals = &g_VBoxNetAdpGlobals; + int rc; + NDIS_STATUS Status; + PKTHREAD pThread = NULL; + + /* We are about to disconnect IDC, let's make it clear so the factories will know */ + NdisAcquireSpinLock(&pGlobals->Lock); + uint32_t enmPrevState = ASMAtomicXchgU32(&g_VBoxNetAdpGlobals.enmIdcState, kVBoxNetAdpWinIdcState_Stopping); + NdisReleaseSpinLock(&pGlobals->Lock); + Log(("vboxNetAdpWinUnload: IDC state change %s -> Stopping\n", vboxNetAdpWinIdcStateToText(enmPrevState))); + + switch (enmPrevState) + { + case kVBoxNetAdpWinIdcState_Disconnected: + /* Have not even attempted to connect -- nothing to do. */ + break; + case kVBoxNetAdpWinIdcState_Stopping: + /* Impossible, but another thread is alreading doing StopIdc, bail out */ + LogError(("vboxNetAdpWinUnload: called in 'Stopping' state\n")); + break; + case kVBoxNetAdpWinIdcState_Connecting: + /* the worker thread is running, let's wait for it to stop */ + Status = ObReferenceObjectByHandle(g_VBoxNetAdpGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, NULL, KernelMode, + (PVOID*)&pThread, NULL); + if (Status == STATUS_SUCCESS) + { + KeWaitForSingleObject(pThread, Executive, KernelMode, FALSE, NULL); + ObDereferenceObject(pThread); + } + else + { + LogError(("vboxNetAdpWinStopIdc: ObReferenceObjectByHandle(%p) failed with 0x%x\n", + g_VBoxNetAdpGlobals.hInitIdcThread, Status)); + } + break; + case kVBoxNetAdpWinIdcState_Connected: + /* the worker succeeded in IDC init and terminated */ + /* Make sure nobody uses the trunk factory. Wait half a second if needed. */ + if (!NdisWaitEvent(&pGlobals->EventUnloadAllowed, 500)) + LogRel(("VBoxNetAdp: unloading driver while trunk factory is in use!\n")); + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + Log(("vboxNetAdpWinUnload: closed IDC, rc=0x%x\n", rc)); + break; + } + if (pGlobals->hMiniportDriver) + NdisMDeregisterMiniportDriver(pGlobals->hMiniportDriver); + NdisFreeSpinLock(&pGlobals->Lock); + LogFlow(("<==vboxNetAdpWinUnload\n")); + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + RTR0Term(); +} + + +/** + * register the miniport driver + */ +DECLHIDDEN(NDIS_STATUS) vboxNetAdpWinRegister(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr) +{ + NDIS_MINIPORT_DRIVER_CHARACTERISTICS MChars; + + NdisZeroMemory(&MChars, sizeof (MChars)); + + MChars.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS; + MChars.Header.Size = sizeof(NDIS_MINIPORT_DRIVER_CHARACTERISTICS); + MChars.Header.Revision = NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_1; + + MChars.MajorNdisVersion = VBOXNETADP_VERSION_NDIS_MAJOR; + MChars.MinorNdisVersion = VBOXNETADP_VERSION_NDIS_MINOR; + + MChars.MajorDriverVersion = VBOXNETADP_VERSION_MAJOR; + MChars.MinorDriverVersion = VBOXNETADP_VERSION_MINOR; + + MChars.InitializeHandlerEx = vboxNetAdpWinInitializeEx; + MChars.HaltHandlerEx = vboxNetAdpWinHaltEx; + MChars.UnloadHandler = vboxNetAdpWinUnload; + MChars.PauseHandler = vboxNetAdpWinPause; + MChars.RestartHandler = vboxNetAdpWinRestart; + MChars.OidRequestHandler = vboxNetAdpWinOidRequest; + MChars.SendNetBufferListsHandler = vboxNetAdpWinSendNetBufferLists; + MChars.ReturnNetBufferListsHandler = vboxNetAdpWinReturnNetBufferLists; + MChars.CancelSendHandler = vboxNetAdpWinCancelSend; + MChars.CheckForHangHandlerEx = vboxNetAdpWinCheckForHangEx; + MChars.ResetHandlerEx = vboxNetAdpWinResetEx; + MChars.DevicePnPEventNotifyHandler = vboxNetAdpWinDevicePnPEventNotify; + MChars.ShutdownHandlerEx = vboxNetAdpWinShutdownEx; + MChars.CancelOidRequestHandler = vboxNetAdpWinCancelOidRequest; + + NDIS_STATUS Status; + g_VBoxNetAdpGlobals.hMiniportDriver = NULL; + Log(("vboxNetAdpWinRegister: registering miniport driver...\n")); + Status = NdisMRegisterMiniportDriver(pDriverObject, + pRegistryPathStr, + (NDIS_HANDLE)&g_VBoxNetAdpGlobals, + &MChars, + &g_VBoxNetAdpGlobals.hMiniportDriver); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Log(("vboxNetAdpWinRegister: successfully registered miniport driver; registering device...\n")); + } + else + { + Log(("ERROR! vboxNetAdpWinRegister: failed to register miniport driver, status=0x%x", Status)); + } + return Status; +} + + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + + rc = RTR0Init(0); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + NdisZeroMemory(&g_VBoxNetAdpGlobals, sizeof (g_VBoxNetAdpGlobals)); + RTListInit(&g_VBoxNetAdpGlobals.ListOfAdapters); + NdisAllocateSpinLock(&g_VBoxNetAdpGlobals.Lock); + NdisInitializeEvent(&g_VBoxNetAdpGlobals.EventUnloadAllowed); + //g_VBoxNetAdpGlobals.PMCaps.WakeUpCapabilities.Flags = NDIS_DEVICE_WAKE_UP_ENABLE; + g_VBoxNetAdpGlobals.PMCaps.WakeUpCapabilities.MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + g_VBoxNetAdpGlobals.PMCaps.WakeUpCapabilities.MinPatternWakeUp = NdisDeviceStateUnspecified; + + /* Initialize SupDrv interface */ + g_VBoxNetAdpGlobals.SupDrvFactory.pfnQueryFactoryInterface = vboxNetAdpWinQueryFactoryInterface; + memcpy(g_VBoxNetAdpGlobals.SupDrvFactory.szName, "VBoxNetAdp", sizeof("VBoxNetAdp")); + /* Initialize trunk factory interface */ + g_VBoxNetAdpGlobals.TrunkFactory.pfnRelease = vboxNetAdpWinFactoryRelease; + g_VBoxNetAdpGlobals.TrunkFactory.pfnCreateAndConnect = vboxNetAdpWinFactoryCreateAndConnect; + + rc = vboxNetAdpWinStartInitIdcThread(&g_VBoxNetAdpGlobals); + if (RT_SUCCESS(rc)) + { + Status = vboxNetAdpWinRegister(pDriverObject, pRegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + Log(("NETADP: started successfully\n")); + return STATUS_SUCCESS; + } + } + else + Status = NDIS_STATUS_FAILURE; + NdisFreeSpinLock(&g_VBoxNetAdpGlobals.Lock); + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + RTR0Term(); + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} + diff --git a/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.h b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.h new file mode 100644 index 00000000..8d9d15a5 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.h @@ -0,0 +1,71 @@ +/* $Id: VBoxNetAdp-win.h $ */ +/** @file + * VBoxNetAdp-win.h - Host-only Miniport Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetAdp_win_VBoxNetAdp_win_h +#define VBOX_INCLUDED_SRC_VBoxNetAdp_win_VBoxNetAdp_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define VBOXNETADP_VERSION_NDIS_MAJOR 6 +#define VBOXNETADP_VERSION_NDIS_MINOR 0 + +#define VBOXNETADP_VERSION_MAJOR 1 +#define VBOXNETADP_VERSION_MINOR 0 + +#define VBOXNETADP_VENDOR_NAME "Oracle" +#define VBOXNETADP_VENDOR_ID 0xFFFFFF +#define VBOXNETADP_MCAST_LIST_SIZE 32 +#define VBOXNETADP_MAX_FRAME_SIZE 1518 // TODO: 14+4+1500 + +//#define VBOXNETADP_NAME_UNIQUE L"{7af6b074-048d-4444-bfce-1ecc8bc5cb76}" +#define VBOXNETADP_NAME_SERVICE L"VBoxNetAdp" + +#define VBOXNETADP_NAME_LINK L"\\DosDevices\\Global\\VBoxNetAdp" +#define VBOXNETADP_NAME_DEVICE L"\\Device\\VBoxNetAdp" + +#define VBOXNETADPWIN_TAG 'ANBV' + +#define VBOXNETADPWIN_ATTR_FLAGS NDIS_MINIPORT_ATTRIBUTES_NDIS_WDM | NDIS_MINIPORT_ATTRIBUTES_NO_HALT_ON_SUSPEND +#define VBOXNETADP_MAC_OPTIONS NDIS_MAC_OPTION_NO_LOOPBACK +#define VBOXNETADP_SUPPORTED_FILTERS (NDIS_PACKET_TYPE_DIRECTED | \ + NDIS_PACKET_TYPE_MULTICAST | \ + NDIS_PACKET_TYPE_BROADCAST | \ + NDIS_PACKET_TYPE_PROMISCUOUS | \ + NDIS_PACKET_TYPE_ALL_MULTICAST) +#define VBOXNETADPWIN_SUPPORTED_STATISTICS 0 //TODO! +#define VBOXNETADPWIN_HANG_CHECK_TIME 4 + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetAdp_win_VBoxNetAdp_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.rc b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.rc new file mode 100644 index 00000000..61cdb6e7 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp-win.rc @@ -0,0 +1,72 @@ +/* $Id: VBoxNetAdp-win.rc $ */ +/** @file + * VBoxNetAdp6 - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +#define DESCRIPTION_STR "VirtualBox NDIS 6.0 Host-Only Network Adapter Driver\0" +#define FILENAME_STR "VBoxNetAdp6" + +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_NETWORK +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", DESCRIPTION_STR + VALUE "InternalName", FILENAME_STR "\0" + VALUE "OriginalFilename", FILENAME_STR ".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/VBoxNetAdp/win/VBoxNetAdp6.inf b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp6.inf new file mode 100644 index 00000000..6c18bf6a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetAdp/win/VBoxNetAdp6.inf @@ -0,0 +1,101 @@ +; $Id: VBoxNetAdp6.inf $ +;; @file +; VBoxNetAdp6.inf - VirtualBox NDIS6 Miniport Driver inf file +; +; Note: We use the same component id as the old NetAdp implementation ? +; + +; +; Copyright (C) 2014-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature = "$Windows NT$" +;cat CatalogFile = VBoxNetAdp6.cat +Class = Net +ClassGUID = {4D36E972-E325-11CE-BFC1-08002BE10318} +Provider = %ORACLE% +;edit-DriverVer=10/23/2014,1.0.1.0 + + +[Manufacturer] +%ORACLE% = VBoxNetAdp6@COMMA-NT-ARCH@ + +[ControlFlags] + +[VBoxNetAdp6@DOT-NT-ARCH@] +%VBoxNetAdp6_Desc% = VBoxNetAdp6.ndi, sun_VBoxNetAdp + +[VBoxNetAdp6.ndi] +AddReg = VBoxNetAdp6.ndi.AddReg +Characteristics = 0x1 ; NCF_VIRTUAL +*IfType = 0x6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0x0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 +CopyFiles = VBoxNetAdp6.Files.Sys + +[VBoxNetAdp6.ndi.Services] +AddService = VBoxNetAdp, 2, VBoxNetAdp6.AddService + +[VBoxNetAdp6.AddService] +DisplayName = %VBoxNetAdp6Service_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetAdp6.sys +LoadOrderGroup = NDIS + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetAdp6.sys=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetAdp6.Files.Sys = 12 ; %windir%\System32\drivers + +[VBoxNetAdp6.Files.Sys] +VBoxNetAdp6.sys,,,2 + +[VBoxNetAdp6.ndi.AddReg] +HKR, , *NdisDeviceType, 0x00010001, 1 ; NDIS_DEVICE_TYPE_ENDPOINT +HKR, , BusNumber, 0, "0" +HKR, Ndi, Service, 0, VBoxNetAdp +HKR, Ndi, HelpText, , %VBoxNetAdp6_HELP% +HKR, Ndi\Interfaces, UpperRange, 0, ndis5 +HKR, Ndi\Interfaces, LowerRange, 0, ethernet + +[Strings] +ORACLE = "Oracle Corporation" +DiskDescription = "VirtualBox NDIS 6.0 Miniport Driver" +VBoxNetAdp6_Desc = "VirtualBox Host-Only Ethernet Adapter" +VBoxNetAdp6_HELP = "VirtualBox NDIS 6.0 Miniport Driver" +VBoxNetAdp6Service_Desc = "VirtualBox NDIS 6.0 Miniport Service" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk b/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk new file mode 100644 index 00000000..e2704459 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/Makefile.kmk @@ -0,0 +1,556 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Network Filter Driver (VBoxNetFlt). +# + +# +# 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 . +# +# 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 + + +if1of ($(KBUILD_TARGET), darwin win) # this ifeq must go, see @todo around elif + # + # VBoxNetFlt.sys - The mixed case driver. + # + ifdef VBOX_WITH_VBOXDRV + SYSMODS += VBoxNetFlt + endif + VBoxNetFlt_TEMPLATE = VBoxR0Drv + VBoxNetFlt_INST = $(INST_VBOXNETFLT)$(if $(eq $(KBUILD_TARGET),darwin),Contents/MacOS/) + VBoxNetFlt_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXNETFLT)) + if defined(VBOX_SIGNING_MODE) && "$(KBUILD_TARGET)" == "win" + VBoxNetFlt_INSTTYPE = none + VBoxNetFlt_DEBUG_INSTTYPE = both + endif + VBoxNetFlt_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetFlt_INCS = . + VBoxNetFlt_SOURCES.darwin = \ + darwin/VBoxNetFlt-darwin.cpp + VBoxNetFlt_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetFlt_SOURCES.win = \ + win/drv/VBoxNetFltP-win.cpp \ + win/drv/VBoxNetFltM-win.cpp \ + win/drv/VBoxNetFltRt-win.cpp \ + win/drv/VBoxNetFlt-win.rc + # with WINDDKWLH the WIN9X_COMPAT_SPINLOCK is needed to avoid inline declaration of KeInitializeSpinLock + # otherwise the linker would complain about dumplicate _KeInitializeSpinLock@4 definition + # in ntoskrnl.lib and our object files + VBoxNetFlt_DEFS.win += WIN9X_COMPAT_SPINLOCK=1 + VBoxNetFlt_DEFS.win += VBOXNETFLT_STATIC_CONFIG + VBoxNetFlt_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + VBoxNetFlt_DEFS.win += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetFlt_DEFS.win += NDIS50_MINIPORT=1 NDIS50=1 + ifdef VBOX_LOOPBACK_USEFLAGS + VBoxNetFlt_DEFS.win += VBOX_LOOPBACK_USEFLAGS + endif + VBoxNetFlt_SOURCES = VBoxNetFlt.c + #VBoxNetFlt_LDFLAGS.darwin = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded + VBoxNetFlt_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetFlt_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetFlt_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ndis.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/tdi.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetFlt_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + + darwin/VBoxNetFlt-darwin.cpp_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + + + # Darwin extras. + if "$(KBUILD_TARGET)" == "darwin" && defined(VBOX_WITH_VBOXDRV) + INSTALLS += VBoxNetFlt.kext + VBoxNetFlt.kext_INST = $(INST_VBOXNETFLT)Contents/ + VBoxNetFlt.kext_SOURCES = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetFlt.kext_CLEAN = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist + VBoxNetFlt.kext_BLDDIRS = $(VBoxNetFlt.kext_0_OUTDIR)/Contents/ + + $$(VBoxNetFlt.kext_0_OUTDIR)/Contents/Info.plist: $(PATH_SUB_CURRENT)/darwin/Info.plist $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's+@VBOX_VERSION_STRING@+$(VBOX_VERSION_STRING)+g' \ + -e 's+@VBOX_VERSION_MAJOR@+$(VBOX_VERSION_MAJOR)+g' \ + -e 's+@VBOX_VERSION_MINOR@+$(VBOX_VERSION_MINOR)+g' \ + -e 's+@VBOX_VERSION_BUILD@+$(VBOX_VERSION_BUILD)+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,VBoxNetFlt) + + INSTALLS.darwin += Scripts-darwin + Scripts-darwin_INST = $(INST_DIST) + Scripts-darwin_EXEC_SOURCES = \ + darwin/loadnetflt.sh + endif # darwin && host-drivers + + + ifeq ($(KBUILD_TARGET),win) + # + # Windows extras. + # + INSTALLS.win += VBoxNetFlt-inf + VBoxNetFlt-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetFlt-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf + VBoxNetFlt-inf_CLEAN = $(VBoxNetFlt-inf_SOURCES) + VBoxNetFlt-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetFltCat.dir + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetFlt.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetFltM.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetFlt-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetFlt-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.cat + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys: $$(VBoxNetFlt_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll: $$(VBoxNetFltNobj_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.cat: \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.sys \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltNobj.dll \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFlt.inf \ + $(PATH_TARGET)/VBoxNetFltCat.dir/VBoxNetFltM.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetFlt-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # signing + + + # + # VBoxNetLwf.sys - The light-weight filter driver for NDIS6. + # + SYSMODS += VBoxNetLwf + VBoxNetLwf_TEMPLATE = VBoxR0Drv + VBoxNetLwf_INST = $(INST_VBOXNETFLT) + if defined(VBOX_SIGNING_MODE) + VBoxNetLwf_INSTTYPE = none + VBoxNetLwf_DEBUG_INSTTYPE = both + endif + VBoxNetLwf_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetLwf_INCS = . + VBoxNetLwf_SOURCES = VBoxNetFlt.c VBoxNetFlt.rc + VBoxNetLwf_SDKS.win = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetLwf_SOURCES.win = \ + win/drv/VBoxNetLwf-win.cpp + # with WINDDKWLH the WIN9X_COMPAT_SPINLOCK is needed to avoid inline declaration of KeInitializeSpinLock + # otherwise the linker would complain about dumplicate _KeInitializeSpinLock@4 definition + # in ntoskrnl.lib and our object files + VBoxNetLwf_DEFS.win += WIN9X_COMPAT_SPINLOCK=1 NDISLWF=1 DBG=1 + # VBoxNetLwf_DEFS.win += VBOXNETFLT_STATIC_CONFIG + # VBoxNetLwf_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + # VBoxNetLwf_DEFS.win += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetLwf_DEFS.win += NDIS60=1 + # ifdef VBOX_LOOPBACK_USEFLAGS + # VBoxNetLwf_DEFS.win += VBOX_LOOPBACK_USEFLAGS + # endif + # VBoxNetLwf_SOURCES = VBoxNetLwf.c + VBoxNetLwf_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetLwf_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetLwf_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/ndis.lib \ + $(PATH_SDK_$(VBOX_WINDDK_WLH)_LIB)/netio.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetLwf_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + # + # VBoxNetLwf installation. + # + INSTALLS.win += VBoxNetLwf-inf + VBoxNetLwf-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetLwf-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf + VBoxNetLwf-inf_CLEAN = $(VBoxNetLwf-inf_SOURCES) + VBoxNetLwf-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetLwfCat.dir + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetLwf.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetLwf-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetLwf-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat=>VBoxNetLwf-PreW10.cat + + # $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwfNobj.dll \ + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys: $$(VBoxNetLwf_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + #$(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwfNobj.dll: $$(VBoxNetLwfNobj_1_TARGET) | $$(dir $$@) + # $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.cat: \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.sys \ + $(PATH_TARGET)/VBoxNetLwfCat.dir/VBoxNetLwf.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetLwf-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # signing + + + + # + # WinNetConfig - static library with host network interface config API (for the installer) + # + LIBRARIES.win += WinNetConfigSharedStatic + WinNetConfigSharedStatic_TEMPLATE = VBoxR3StaticDllNoAsan + WinNetConfigSharedStatic_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) + WinNetConfigSharedStatic_DEFS = _UNICODE UNICODE + WinNetConfigSharedStatic_SOURCES = \ + win/cfg/VBoxNetCfg.cpp \ + ../../Main/glue/string.cpp + + # Version for DLLs: + LIBRARIES.win += WinNetConfigDll + WinNetConfigDll_TEMPLATE = VBoxR3Dll + WinNetConfigDll_EXTENDS = WinNetConfigSharedStatic + + # Version for EXEs: + LIBRARIES.win += WinNetConfigExe + WinNetConfigExe_TEMPLATE = VBoxR3Exe + WinNetConfigExe_EXTENDS = WinNetConfigSharedStatic + + + # + # Template for NetFltInstall and friends. + # + TEMPLATE_VBoxNetFltR3 = Template for NetFltInstall, NetFltUninstall, NetAdpInstall, NetAdpUninstall, ++ + TEMPLATE_VBoxNetFltR3_EXTENDS = VBoxR3Exe + TEMPLATE_VBoxNetFltR3_SDKS = $(TEMPLATE_VBoxR3Exe_SDKS) ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBoxNtDll VBoxWinNewDevLib + TEMPLATE_VBoxNetFltR3_LIBS = $(TEMPLATE_VBoxR3Exe_LIBS) \ + $(WinNetConfigExe_1_TARGET) \ + $(PATH_STAGE_LIB)/VBoxDrvCfgExe$(VBOX_SUFF_LIB) \ + $(LIB_RUNTIME) \ + $(PATH_TOOL_$(VBOX_VCC_TOOL)_LIB)/comsupp.lib \ + $(PATH_SDK_$(VBOX_WINPSDK)_LIB)/WbemUuid.Lib + + # + # NetFltInstall + # + PROGRAMS.win += NetFltInstall + NetFltInstall_TEMPLATE = VBoxNetFltR3 + NetFltInstall_SOURCES = win/tools/VBoxNetFltInstall.cpp + + # + # NetFltUninstall + # + PROGRAMS.win += NetFltUninstall + NetFltUninstall_TEMPLATE = VBoxNetFltR3 + NetFltUninstall_SOURCES = win/tools/VBoxNetFltUninstall.cpp + + # + # NetAdpInstall + # + PROGRAMS.win += NetAdpInstall + NetAdpInstall_TEMPLATE = VBoxNetFltR3 + NetAdpInstall_SOURCES = win/tools/VBoxNetAdpInstall.cpp + + # + # NetAdpUninstall + # + PROGRAMS.win += NetAdpUninstall + NetAdpUninstall_TEMPLATE = VBoxNetFltR3 + NetAdpUninstall_SOURCES = win/tools/VBoxNetAdpUninstall.cpp + + # + # NetAdp6Install + # + PROGRAMS.win += NetAdp6Install + NetAdp6Install_TEMPLATE = VBoxNetFltR3 + NetAdp6Install_SOURCES = win/tools/VBoxNetAdpInstall.cpp + NetAdp6Install_DEFS = NDIS60=1 + + # + # NetAdp6Uninstall + # + PROGRAMS.win += NetAdp6Uninstall + NetAdp6Uninstall_TEMPLATE = VBoxNetFltR3 + NetAdp6Uninstall_SOURCES = win/tools/VBoxNetAdpUninstall.cpp + NetAdp6Uninstall_DEFS = NDIS60=1 + + # + # NetLwfInstall + # + PROGRAMS.win += NetLwfInstall + NetLwfInstall_TEMPLATE = VBoxNetFltR3 + NetLwfInstall_SOURCES = win/tools/VBoxNetLwfInstall.cpp + + # + # NetLwfUninstall + # + PROGRAMS.win += NetLwfUninstall + NetLwfUninstall_TEMPLATE = VBoxNetFltR3 + NetLwfUninstall_SOURCES = win/tools/VBoxNetLwfUninstall.cpp + + # + # VBoxNetFltNobj + # + DLLS.win += VBoxNetFltNobj + VBoxNetFltNobj_TEMPLATE = VBoxR3StaticDll + if defined(VBOX_SIGNING_MODE) + VBoxNetFltNobj_INSTTYPE = none + VBoxNetFltNobj_DEBUG_INSTTYPE = both + endif + VBoxNetFltNobj_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) + VBoxNetFltNobj_DEFS = WIN32 _ATL_STATIC_REGISTRY + VBoxNetFltNobj_INCS = \ + $(VBoxNetFltNobj_0_OUTDIR) + VBoxNetFltNobj_SOURCES = \ + win/nobj/VBoxNetFltNobj.cpp \ + win/nobj/VBoxNetFltNobj.def \ + win/nobj/VBoxNetFltNobj.rc + #VBoxNetFltNobj_INTERMEDIATES = + VBoxNetFltNobj_DEPS = \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h \ + $(VBoxNetFltNobj_0_OUTDIR)/dlldata.c \ + $(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.tlb + VBoxNetFltNobj_CLEAN = $(VBoxNetFltNobj_DEPS) + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h \ + + $$(VBoxNetFltNobj_0_OUTDIR)/dlldata.c \ + + $$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.tlb: \ + $(PATH_SUB_CURRENT)/win/nobj/VBoxNetFltNobjT.idl \ + | $$(dir $$@) + $(VBOX_MIDL_REDIRECT) $(VBOX_WIN_MIDL) /nologo \ + /out $(call VBOX_FN_MAKE_WIN_PATH,$(VBoxNetFltNobj_0_OUTDIR)) \ + /cpp_cmd $(VBOX_MIDL_CPP_CMD) \ + $(qforeachfile unq, incdir, $(SDK_$(VBOX_WINDDK)_INCS) $(SDK_$(VBOX_WINPSDK)_INCS),/I $(quote-sh $(incdir))) \ + $(call VBOX_FN_MAKE_WIN_PATH,$<) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT.h) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_i.c) + $(call def_VBoxMidlOutputDisableMscWarnings,$(VBoxNetFltNobj_0_OUTDIR)/VBoxNetFltNobjT_p.c) + + + # + # VBoxNetAdp.sys - The VirtualBox Adapter miniport driver. + # + SYSMODS.win += VBoxNetAdp + VBoxNetAdp_TEMPLATE = VBoxR0Drv + #VBoxNetAdp_INST = $(INST_VBOXNETADP) + if defined(VBOX_SIGNING_MODE) + VBoxNetAdp_INSTTYPE.win = none + VBoxNetAdp_DEBUG_INSTTYPE.win = both + endif + VBoxNetAdp_DEFS = IN_RT_R0 IN_SUP_STATIC + VBoxNetAdp_INCS := $(PATH_SUB_CURRENT) + VBoxNetAdp_SDKS = ReorderCompilerIncs $(VBOX_WINDDK_WLH) $(VBOX_WINPSDK_INCS) + VBoxNetAdp_SOURCES = \ + VBoxNetFlt.c \ + win/drv/VBoxNetFltM-win.cpp \ + win/drv/VBoxNetFltRt-win.cpp \ + win/drv/VBoxNetFlt-win.rc + VBoxNetAdp_DEFS += VBOXNETFLT_STATIC_CONFIG VBOXNETADP + VBoxNetAdp_DEFS.win += VBOXNETFLT_NO_PACKET_QUEUE + VBoxNetAdp_DEFS += NDIS_MINIPORT_DRIVER NDIS_WDM=1 BINARY_COMPATIBLE=0 + VBoxNetAdp_DEFS += NDIS50_MINIPORT=1 NDIS50=1 + VBoxNetAdp_LDFLAGS.win.x86 = -Entry:DriverEntry@8 + VBoxNetAdp_LDFLAGS.win.amd64 = -Entry:DriverEntry + VBoxNetAdp_LIBS.win = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ndis.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + VBoxNetAdp_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + + + INSTALLS.win += VBoxNetAdp-inf + VBoxNetAdp-inf_TEMPLATE = VBoxR0DrvInfCat + VBoxNetAdp-inf_SOURCES = \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf + VBoxNetAdp-inf_CLEAN = $(VBoxNetAdp-inf_SOURCES) + VBoxNetAdp-inf_BLDDIRS = $(PATH_TARGET)/VBoxNetAdpCat.dir + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf: $(PATH_SUB_CURRENT)/win/drv/VBoxNetAdp.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,VBoxNetAdp-inf,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + + ifdef VBOX_SIGNING_MODE + VBoxNetAdp-inf_SOURCES += \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.cat + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys: $$(VBoxNetAdp_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.cat: \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.sys \ + $(PATH_TARGET)/VBoxNetAdpCat.dir/VBoxNetAdp.inf + $(call MSG_TOOL,Inf2Cat,VBoxNetFlt-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + endif # ifdef VBOX_SIGNING_MODE + + endif #ifeq ($(KBUILD_TARGET), win) + +else if1of ($(KBUILD_TARGET), solaris freebsd) ## @todo merge this with the mixed case stuff. + # + # vboxnetflt(.ko/.o/) - The lower case driver. + # Note! On Solaris the name has to be <= 8 chars long. + # The DEBUG_HASH* stuff is for CONFIG_DYNAMIC_DEBUG-enabled kernels + # + ifdef VBOX_WITH_VBOXDRV + SYSMODS += vboxnetflt + vboxnetflt_TEMPLATE = VBoxR0Drv + vboxnetflt_NAME.solaris = vboxflt + vboxnetflt_DEFS = IN_RT_R0 + vboxnetflt_DEPS.solaris += $(VBOX_SVN_REV_KMK) + vboxnetflt_INCS := \ + $(PATH_SUB_CURRENT) + vboxnetflt_LDFLAGS.solaris += -N drv/vboxdrv -N misc/ctf + vboxnetflt_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + ## @todo vboxflt should resolves all the IPRT bits from vboxdrv. + #vboxnetflt_LIBS += \ + # $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) + vboxnetflt_SOURCES.solaris = solaris/VBoxNetFlt-solaris.c + vboxnetflt_SOURCES.freebsd = freebsd/VBoxNetFlt-freebsd.c + vboxnetflt_SOURCES = VBoxNetFlt.c + solaris/VBoxNetFlt-solaris.c_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + + ifdef VBOX_WITH_NETFLT_CROSSBOW + SYSMODS += vboxnetbow + vboxnetbow_TEMPLATE = VBoxR0Drv + vboxnetbow_NAME = vboxbow + vboxnetbow_DEFS = vboxnetflt_DEFS VBOX_WITH_NETFLT_CROSSBOW + vboxnetbow_INCS := $(PATH_SUB_CURRENT) + vboxnetbow_LDFLAGS += -N drv/vboxdrv -N drv/vnic -N misc/mac -N misc/dls + vboxnetbow_LIBS = \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + vboxnetbow_SOURCES.solaris = solaris/VBoxNetFltBow-solaris.c + vboxnetbow_SOURCES = VBoxNetFlt.c + solaris/VBoxNetFltBow-solaris.c_DEFS = VBOX_SVN_REV=$(VBOX_SVN_REV) + endif # VBOX_WITH_NETFLT_CROSSBOW + endif # VBOX_WITH_VBOXDRV +endif # to be removed. + + +ifeq ($(KBUILD_TARGET),linux) + # + # Install source files for compilation on Linux. + # files_vboxnetflt defines VBOX_VBOXNETFLT_SOURCES. + # + include $(PATH_SUB_CURRENT)/linux/files_vboxnetflt + INSTALLS += VBoxNetFlt-src VBoxNetFlt-sh + VBoxNetFlt-src_INST = bin/src/vboxnetflt/ + VBoxNetFlt-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETFLT_SOURCES)) \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + VBoxNetFlt-src_CLEAN = \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile \ + $(PATH_TARGET)/VBoxNetFlt-src-1.dep + + # Scripts needed for building the kernel module. + + includedep $(PATH_TARGET)/VBoxNetFlt-src-1.dep + $$(VBoxNetFlt-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/linux/Makefile \ + $$(if $$(eq $$(VBoxNetFlt/linux/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@ $< + else + $(QUIET)$(CP) -f $< $@ + endif + %$(QUIET2)$(RM) -f -- $(PATH_TARGET)/VBoxNetFlt-src-1.dep + %$(QUIET2)$(APPEND) '$(PATH_TARGET)/VBoxNetFlt-src-1.dep' 'VBoxNetFlt/linux/Makefile_VBOX_HARDENED=$(VBOX_WITH_HARDENING)' + + # + # Build test for the linux host kernel modules. + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,VBoxNetFlt-src,vboxdrv-src,) +endif # linux + +# Gross hack for FreeBSD 7, should figure this out properly +## @todo Properly generate opt_netgraph.h +ifeq ($(KBUILD_TARGET),freebsd) + vboxnetflt_DEPS.freebsd += $(PATH_OUT)/opt_netgraph.h + $(PATH_OUT)/opt_netgraph.h: + echo > $(PATH_OUT)/opt_netgraph.h + + # + # Install source files for compilation on FreeBSD. + # files_vboxnetflt defines VBOX_VBOXNETFLT_SOURCES. + # + include $(PATH_SUB_CURRENT)/freebsd/files_vboxnetflt + INSTALLS += VBoxNetFlt-src + VBoxNetFlt-src_INST = bin/src/vboxnetflt/ + VBoxNetFlt-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXNETFLT_SOURCES)) \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + VBoxNetFlt-src_CLEAN = \ + $(VBoxNetFlt-src_0_OUTDIR)/Makefile + + $$(VBoxNetFlt-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/freebsd/Makefile \ + $$(if $$(eq $$(VBoxNetFlt/freebsd/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + $(QUIET)$(CP) -f $< $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@.tmp $@ + ${QUIET}$(MV) -f $@.tmp $@ + endif + ifndef VBOX_WITH_NETFLT_VIMAGE + $(QUIET)$(SED) -e "s;-DVIMAGE;;g" --output $@.tmp $@ + ${QUIET}$(MV) -f $@.tmp $@ + endif +endif # freebsd + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c new file mode 100644 index 00000000..b80af59e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c @@ -0,0 +1,1591 @@ +/* $Id: VBoxNetFlt.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), 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 . + * + * 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_netflt VBoxNetFlt - Network Interface Filter + * + * This is a kernel module that attaches to a real interface on the host and + * filters and injects packets. + * + * In the big picture we're one of the three trunk interface on the internal + * network, the one named "NIC Filter Driver": @image html Networking_Overview.gif + * + * + * @section sec_netflt_locking Locking and Potential Races + * + * The main challenge here is to make sure the netfilter and internal network + * instances won't be destroyed while someone is calling into them. + * + * The main calls into or out of of the filter driver are: + * - Send. + * - Async send completion (not implemented yet) + * - Release by the internal network. + * - Receive. + * - Disappearance of the host networking interface. + * - Reappearance of the host networking interface. + * + * The latter two calls are can be caused by driver unloading/loading or the + * device being physical unplugged (e.g. a USB network device). Actually, the + * unload scenario must fervently be prevent as it will cause panics because the + * internal network will assume the trunk is around until it releases it. + * @todo Need to figure which host allow unloading and block/fix it. + * + * Currently the netfilter instance lives until the internal network releases + * it. So, it is the internal networks responsibility to make sure there are no + * active calls when it releases the trunk and destroys the network. The + * netfilter assists in this by providing INTNETTRUNKIFPORT::pfnSetState and + * INTNETTRUNKIFPORT::pfnWaitForIdle. The trunk state is used to enable/disable + * promiscuous mode on the hardware NIC (or similar activation) as well + * indicating that disconnect is imminent and no further calls shall be made + * into the internal network. After changing the state to disconnecting and + * prior to invoking INTNETTRUNKIFPORT::pfnDisconnectAndRelease, the internal + * network will use INTNETTRUNKIFPORT::pfnWaitForIdle to wait for any still + * active calls to complete. + * + * The netfilter employs a busy counter and an internal state in addition to the + * public trunk state. All these variables are protected using a spinlock. + * + * + * @section sec_netflt_msc Locking / Sequence Diagrams - OBSOLETE + * + * !OBSOLETE! - THIS WAS THE OLD APPROACH! + * + * This secion contains a few sequence diagrams describing the problematic + * transitions of a host interface filter instance. + * + * The thing that makes it all a bit problematic is that multiple events may + * happen at the same time, and that we have to be very careful to avoid + * deadlocks caused by mixing our locks with the ones in the host kernel. The + * main events are receive, send, async send completion, disappearance of the + * host networking interface and its reappearance. The latter two events are + * can be caused by driver unloading/loading or the device being physical + * unplugged (e.g. a USB network device). + * + * The strategy for dealing with these issues are: + * - Use a simple state machine. + * - Require the user (IntNet) to serialize all its calls to us, + * while at the same time not owning any lock used by any of the + * the callbacks we might call on receive and async send completion. + * - Make sure we're 100% idle before disconnecting, and have a + * disconnected status on both sides to fend off async calls. + * - Protect the host specific interface handle and the state variables + * using a spinlock. + * + * + * @subsection subsec_netflt_msc_dis_rel Disconnect from the network and release - OBSOLETE + * + * @msc + * VM, IntNet, NetFlt, Kernel, Wire; + * + * VM->IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * Kernel->Wire [label="pkt0 to wire", linecolor="green", textcolor="green"]; + * + * --- [label="Suspending the trunk interface"]; + * IntNet=>IntNet [label="Lock Network"]; + * + * Wire->Kernel [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt1 - racing us", linecolor="red", textcolor="red"]; + * NetFlt=>>IntNet [label="pkt1 recv - blocks", linecolor="red", textcolor="red"]; + * + * IntNet=>IntNet [label="Mark Trunk Suspended"]; + * IntNet=>IntNet [label="Unlock Network"]; + * + * IntNet=>NetFlt [label="pfnSetActive(false)"]; + * NetFlt=>NetFlt [label="Mark inactive (atomic)"]; + * IntNet<NetFlt [label="pfnWaitForIdle(forever)"]; + * + * IntNet=>>NetFlt [label="pkt1 to host", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt1 to host", linecolor="red", textcolor="red"]; + * + * Kernel<-Wire [label="pkt0 on wire", linecolor="green", textcolor="green"]; + * NetFlt<>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * NetFlt<-NetFlt [label="idle", linecolor="green", textcolor="green"]; + * + * IntNet<Kernel [label="pkt2", linecolor="red", textcolor="red"]; + * Kernel=>>NetFlt [label="pkt2", linecolor="red", textcolor="red"]; + * NetFlt=>>Kernel [label="pkt2 to host", linecolor="red", textcolor="red"]; + * + * VM->IntNet [label="pkt3", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> drop", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * + * --- [label="The trunk interface is idle now, disconnect it"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Unlink Trunk"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pfnDisconnectAndRelease"]; + * NetFlt=>Kernel [label="iflt_detach"]; + * NetFlt<<=Kernel [label="iff_detached"]; + * NetFlt>>Kernel [label="iff_detached"]; + * NetFlt<NetFlt [label="Release"]; + * IntNet<IntNet [label="pkt0", linecolor="green", textcolor="green"]; + * IntNet=>IntNet [label="Lock Network", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="green", textcolor="green" ]; + * IntNet=>IntNet [label="Unlock Network", linecolor="green", textcolor="green" ]; + * IntNet=>NetFlt [label="pkt0 to wire", linecolor="green", textcolor="green" ]; + * NetFlt=>Kernel [label="ifnet_reference w/ spinlock", linecolor="green", textcolor="green" ]; + * NetFlt<Kernel [label="pkt0 to wire (blocks)", linecolor="green", textcolor="green" ]; + * + * --- [label="The host interface is being disconnected"]; + * Kernel->NetFlt [label="iff_detached"]; + * NetFlt=>Kernel [label="ifnet_release w/ spinlock"]; + * NetFlt<NetFlt [label="fDisconnectedFromHost=true"]; + * NetFlt>>Kernel [label="iff_detached"]; + * + * NetFlt<NetFlt [label="Acquire spinlock", linecolor="green", textcolor="green"]; + * NetFlt=>Kernel [label="ifnet_release", linecolor="green", textcolor="green"]; + * NetFlt<NetFlt [label="pIf=NULL", linecolor="green", textcolor="green"]; + * NetFlt=>NetFlt [label="Release spinlock", linecolor="green", textcolor="green"]; + * IntNet<=NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet>>NetFlt [label="pfnSGRelease", linecolor="green", textcolor="green"]; + * IntNet<IntNet [label="pkt0"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt0 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * IntNet<IntNet [label="pkt1"]; + * IntNet=>IntNet [label="Lock Network"]; + * IntNet=>IntNet [label="Route packet -> wire"]; + * IntNet=>IntNet [label="Unlock Network"]; + * IntNet=>NetFlt [label="pkt1 to wire"]; + * NetFlt=>NetFlt [label="Read pIf(==NULL) w/ spinlock"]; + * NetFlt=>NetFlt [label="fRediscoveryPending=true w/ spinlock"]; + * NetFlt=>Kernel [label="ifnet_find_by_name"]; + * NetFlt<IntNet [label="pkt2", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Lock Network", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Route packet -> wire", linecolor="red", textcolor="red"]; + * IntNet=>IntNet [label="Unlock Network", linecolor="red", textcolor="red"]; + * IntNet=>NetFlt [label="pkt2 to wire", linecolor="red", textcolor="red"]; + * NetFlt=>NetFlt [label="!pIf || fRediscoveryPending (w/ spinlock)", linecolor="red", textcolor="red"]; + * IntNet<Kernel [label="iflt_attach"]; + * NetFlt<NetFlt [label="Acquire spinlock"]; + * NetFlt=>NetFlt [label="Set pIf and update flags"]; + * NetFlt=>NetFlt [label="Release spinlock"]; + * + * NetFlt=>Kernel [label="pkt1 to wire"]; + * Kernel->Wire [label="pkt1 to wire"]; + * NetFlt< +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define IFPORT_2_VBOXNETFLTINS(pIfPort) \ + ( (PVBOXNETFLTINS)((uint8_t *)(pIfPort) - RT_UOFFSETOF(VBOXNETFLTINS, MyPort)) ) + + +AssertCompileMemberSize(VBOXNETFLTINS, enmState, sizeof(uint32_t)); + +/** + * Sets the enmState member atomically. + * + * Used for all updates. + * + * @param pThis The instance. + * @param enmNewState The new value. + */ +DECLINLINE(void) vboxNetFltSetState(PVBOXNETFLTINS pThis, VBOXNETFTLINSSTATE enmNewState) +{ + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmState, enmNewState); +} + + +/** + * Gets the enmState member atomically. + * + * Used for all reads. + * + * @returns The enmState value. + * @param pThis The instance. + */ +DECLINLINE(VBOXNETFTLINSSTATE) vboxNetFltGetState(PVBOXNETFLTINS pThis) +{ + return (VBOXNETFTLINSSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmState); +} + + +/** + * Finds a instance by its name, the caller does the locking. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +static PVBOXNETFLTINS vboxNetFltFindInstanceLocked(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (!strcmp(pszName, pCur->szName)) + return pCur; + return NULL; +} + + +/** + * Finds a instance by its name, will request the mutex. + * + * No reference to the instance is retained, we're assuming the caller to + * already have one but just for some reason doesn't have the pointer to it. + * + * @returns Pointer to the instance by the given name. NULL if not found. + * @param pGlobals The globals. + * @param pszName The name of the instance. + */ +DECLHIDDEN(PVBOXNETFLTINS) vboxNetFltFindInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName) +{ + PVBOXNETFLTINS pRet; + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, NULL); + + pRet = vboxNetFltFindInstanceLocked(pGlobals, pszName); + + rc = RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return pRet; +} + + +/** + * Unlinks an instance from the chain. + * + * @param pGlobals The globals. + * @param pToUnlink The instance to unlink. + */ +static void vboxNetFltUnlinkLocked(PVBOXNETFLTGLOBALS pGlobals, PVBOXNETFLTINS pToUnlink) +{ + if (pGlobals->pInstanceHead == pToUnlink) + pGlobals->pInstanceHead = pToUnlink->pNext; + else + { + PVBOXNETFLTINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur; pCur = pCur->pNext) + if (pCur->pNext == pToUnlink) + { + pCur->pNext = pToUnlink->pNext; + break; + } + Assert(pCur); + } + pToUnlink->pNext = NULL; +} + + +/** + * Performs interface rediscovery if it was disconnected from the host. + * + * @returns true if successfully rediscovered and connected, false if not. + * @param pThis The instance. + */ +static bool vboxNetFltMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + uint64_t Now; + bool fRediscovered; + bool fDoIt; + + /* + * Don't do rediscovery if we're called with preemption disabled. + * + * Note! This may cause trouble if we're always called with preemption + * disabled and vboxNetFltOsMaybeRediscovered actually does some real + * work. For the time being though, only Darwin and FreeBSD depends + * on these call outs and neither supports sending with preemption + * disabled. + */ + if (!RTThreadPreemptIsEnabled(NIL_RTTHREAD)) + return false; + + /* + * Rediscovered already? Time to try again? + */ + Now = RTTimeNanoTS(); + RTSpinlockAcquire(pThis->hSpinlock); + + fRediscovered = !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); + fDoIt = !fRediscovered + && !ASMAtomicUoReadBool(&pThis->fRediscoveryPending) + && Now - ASMAtomicUoReadU64(&pThis->NanoTSLastRediscovery) > UINT64_C(5000000000); /* 5 sec */ + if (fDoIt) + ASMAtomicWriteBool(&pThis->fRediscoveryPending, true); + + RTSpinlockRelease(pThis->hSpinlock); + + /* + * Call the OS specific code to do the job. + * Update the state when the call returns, that is everything except for + * the fDisconnectedFromHost flag which the OS specific code shall set. + */ + if (fDoIt) + { + fRediscovered = vboxNetFltOsMaybeRediscovered(pThis); + + Assert(!fRediscovered || !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)); + + ASMAtomicUoWriteU64(&pThis->NanoTSLastRediscovery, RTTimeNanoTS()); + ASMAtomicWriteBool(&pThis->fRediscoveryPending, false); + + if (fRediscovered) + /** @todo this isn't 100% serialized. */ + vboxNetFltPortOsSetActive(pThis, pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE); + } + + return fRediscovered; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnXmit + */ +static DECLCALLBACK(int) vboxNetFltPortXmit(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc = VINF_SUCCESS; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pSG); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + + /* + * Do a busy retain and then make sure we're connected to the interface + * before invoking the OS specific code. + */ + if (RT_LIKELY(vboxNetFltTryRetainBusyActive(pThis))) + { + if ( !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost) + || vboxNetFltMaybeRediscovered(pThis)) + rc = vboxNetFltPortOsXmit(pThis, pvIfData, pSG, fDst); + vboxNetFltRelease(pThis, true /* fBusy */); + } + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnWaitForIdle + */ +static DECLCALLBACK(int) vboxNetFltPortWaitForIdle(PINTNETTRUNKIFPORT pIfPort, uint32_t cMillies) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, VERR_INVALID_STATE); + AssertReturn(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING, VERR_INVALID_STATE); + + /* + * Go to sleep on the semaphore after checking the busy count. + */ + vboxNetFltRetain(pThis, false /* fBusy */); + + rc = VINF_SUCCESS; + while (pThis->cBusy && RT_SUCCESS(rc)) + rc = RTSemEventWait(pThis->hEventIdle, cMillies); /** @todo make interruptible? */ + + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnSetState + */ +static DECLCALLBACK(INTNETTRUNKIFSTATE) vboxNetFltPortSetState(PINTNETTRUNKIFPORT pIfPort, INTNETTRUNKIFSTATE enmState) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + INTNETTRUNKIFSTATE enmOldTrunkState; + + /* + * Input validation. + */ + AssertPtr(pThis); + AssertPtr(pThis->pGlobals); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + AssertReturn(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected, INTNETTRUNKIFSTATE_INVALID); + AssertReturn(enmState > INTNETTRUNKIFSTATE_INVALID && enmState < INTNETTRUNKIFSTATE_END, + INTNETTRUNKIFSTATE_INVALID); + + /* + * Take the lock and change the state. + */ + RTSpinlockAcquire(pThis->hSpinlock); + enmOldTrunkState = pThis->enmTrunkState; + if (enmOldTrunkState != enmState) + ASMAtomicWriteU32((uint32_t volatile *)&pThis->enmTrunkState, enmState); + RTSpinlockRelease(pThis->hSpinlock); + + /* + * If the state change indicates that the trunk has become active or + * inactive, call the OS specific part so they can work the promiscuous + * settings and such. + * Note! The caller makes sure there are no concurrent pfnSetState calls. + */ + if ((enmOldTrunkState == INTNETTRUNKIFSTATE_ACTIVE) != (enmState == INTNETTRUNKIFSTATE_ACTIVE)) + vboxNetFltPortOsSetActive(pThis, (enmState == INTNETTRUNKIFSTATE_ACTIVE)); + + return enmOldTrunkState; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnNotifyMacAddress + */ +static DECLCALLBACK(void) vboxNetFltPortNotifyMacAddress(PINTNETTRUNKIFPORT pIfPort, void *pvIfData, PCRTMAC pMac) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + vboxNetFltPortOsNotifyMacAddress(pThis, pvIfData, pMac); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnConnectInterface + */ +static DECLCALLBACK(int) vboxNetFltPortConnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIf, void **ppvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsConnectInterface(pThis, pvIf, ppvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + + return rc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectInterface + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectInterface(PINTNETTRUNKIFPORT pIfPort, void *pvIfData) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + int rc; + + /* + * Input validation. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + + vboxNetFltRetain(pThis, false /* fBusy */); + rc = vboxNetFltPortOsDisconnectInterface(pThis, pvIfData); + vboxNetFltRelease(pThis, false /* fBusy */); + AssertRC(rc); /** @todo fix vboxNetFltPortOsDisconnectInterface. */ +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnDisconnectAndRelease + */ +static DECLCALLBACK(void) vboxNetFltPortDisconnectAndRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + + /* + * Serious paranoia. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Connected); + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); + + /* + * Disconnect and release it. + */ + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSpinlockRelease(pThis->hSpinlock); + + vboxNetFltOsDisconnectIt(pThis); + pThis->pSwitchPort = NULL; + +#ifdef VBOXNETFLT_STATIC_CONFIG + RTSpinlockAcquire(pThis->hSpinlock); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Unconnected); + RTSpinlockRelease(pThis->hSpinlock); +#endif + + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * Destroy a device that has been disconnected from the switch. + * + * @returns true if the instance is destroyed, false otherwise. + * @param pThis The instance to be destroyed. This is + * no longer valid when this function returns. + */ +static bool vboxNetFltDestroyInstance(PVBOXNETFLTINS pThis) +{ + PVBOXNETFLTGLOBALS pGlobals = pThis->pGlobals; + uint32_t cRefs = ASMAtomicUoReadU32((uint32_t volatile *)&pThis->cRefs); + int rc; + LogFlow(("vboxNetFltDestroyInstance: pThis=%p (%s)\n", pThis, pThis->szName)); + + /* + * Validate the state. + */ +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert( vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting + || vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Disconnecting); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_DISCONNECTING); + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cRefs); + Assert(!pThis->cBusy); + Assert(!pThis->pSwitchPort); + + /* + * Make sure the state is 'disconnecting' / 'destroying' and let the OS + * specific code do its part of the cleanup outside the mutex. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Disconnecting); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + vboxNetFltOsDeleteInstance(pThis); + + /* + * Unlink the instance and free up its resources. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); AssertRC(rc); + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Destroyed); + vboxNetFltUnlinkLocked(pGlobals, pThis); + RTSemFastMutexRelease(pGlobals->hFastMtx); + + RTSemEventDestroy(pThis->hEventIdle); + pThis->hEventIdle = NIL_RTSEMEVENT; + RTSpinlockDestroy(pThis->hSpinlock); + pThis->hSpinlock = NIL_RTSPINLOCK; + RTMemFree(pThis); + + NOREF(cRefs); + + return true; +} + + +/** + * Releases a reference to the specified instance. + * + * This method will destroy the instance when the count reaches 0. + * It will also take care of decrementing the counter and idle wakeup. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be decremented too. + */ +DECLHIDDEN(void) vboxNetFltRelease(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicDecU32(&pThis->cBusy); + if (!cRefs) + { + int rc = RTSemEventSignal(pThis->hEventIdle); + AssertRC(rc); + } + else + Assert(cRefs < UINT32_MAX / 2); + } + + /* + * The object reference counting. + */ + cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + vboxNetFltDestroyInstance(pThis); + else + Assert(cRefs < UINT32_MAX / 2); +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltPortRelease(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, false /* fBusy */); +} + + +/** + * @callback_method_impl{FNINTNETTRUNKIFPORTRELEASEBUSY} + */ +DECL_HIDDEN_CALLBACK(void) vboxNetFltPortReleaseBusy(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRelease(pThis, true /*fBusy*/); +} + + +/** + * Retains a reference to the specified instance and a busy reference too. + * + * @param pThis The instance. + * @param fBusy Whether the busy counter should be incremented as well. + */ +DECLHIDDEN(void) vboxNetFltRetain(PVBOXNETFLTINS pThis, bool fBusy) +{ + uint32_t cRefs; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Retain the object. + */ + cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1 && cRefs < UINT32_MAX / 2); + + /* + * Work the busy counter. + */ + if (fBusy) + { + cRefs = ASMAtomicIncU32(&pThis->cBusy); + Assert(cRefs > 0 && cRefs < UINT32_MAX / 2); + } + + NOREF(cRefs); +} + + +/** + * Tries to retain the device as busy if the trunk is active. + * + * This is used before calling pfnRecv or pfnPreRecv. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyActive(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * Tries to retain the device as busy if the trunk is not disconnecting. + * + * This is used before reporting stuff to the internal network. + * + * @returns true if we succeeded in retaining a busy reference to the active + * device. false if we failed. + * @param pThis The instance. + */ +DECLHIDDEN(bool) vboxNetFltTryRetainBusyNotDisconnected(PVBOXNETFLTINS pThis) +{ + uint32_t cRefs; + bool fRc; + + /* + * Paranoid Android. + */ + AssertPtr(pThis); + Assert(pThis->MyPort.u32Version == INTNETTRUNKIFPORT_VERSION); + Assert(pThis->MyPort.u32VersionEnd == INTNETTRUNKIFPORT_VERSION); + Assert( vboxNetFltGetState(pThis) > kVBoxNetFltInsState_Invalid + && vboxNetFltGetState(pThis) < kVBoxNetFltInsState_Destroyed); + AssertPtr(pThis->pGlobals); + Assert(pThis->hEventIdle != NIL_RTSEMEVENT); + Assert(pThis->hSpinlock != NIL_RTSPINLOCK); + Assert(pThis->szName[0]); + + /* + * Do the retaining and checking behind the spinlock. + */ + RTSpinlockAcquire(pThis->hSpinlock); + fRc = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE + || pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE; + if (fRc) + { + cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + + cRefs = ASMAtomicIncU32(&pThis->cBusy); + AssertMsg(cRefs >= 1 && cRefs < UINT32_MAX / 2, ("%d\n", cRefs)); NOREF(cRefs); + } + RTSpinlockRelease(pThis->hSpinlock); + + return fRc; +} + + +/** + * @copydoc INTNETTRUNKIFPORT::pfnRetain + */ +static DECLCALLBACK(void) vboxNetFltPortRetain(PINTNETTRUNKIFPORT pIfPort) +{ + PVBOXNETFLTINS pThis = IFPORT_2_VBOXNETFLTINS(pIfPort); + vboxNetFltRetain(pThis, false /* fBusy */); +} + + +/** + * Connects the instance to the specified switch port. + * + * Called while owning the lock. We're ASSUMING that the internal + * networking code is already owning an recursive mutex, so, there + * will be no deadlocks when vboxNetFltOsConnectIt calls back into + * it for setting preferences. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pSwitchPort The port on the internal network 'switch'. + * @param ppIfPort Where to return our port interface. + */ +static int vboxNetFltConnectIt(PVBOXNETFLTINS pThis, PINTNETTRUNKSWPORT pSwitchPort, PINTNETTRUNKIFPORT *ppIfPort) +{ + int rc; + + /* + * Validate state. + */ + Assert(!pThis->fRediscoveryPending); + Assert(!pThis->cBusy); +#ifdef VBOXNETFLT_STATIC_CONFIG + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Unconnected); +#else + Assert(vboxNetFltGetState(pThis) == kVBoxNetFltInsState_Initializing); +#endif + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + + /* + * Do the job. + * Note that we're calling the os stuff while owning the semaphore here. + */ + pThis->pSwitchPort = pSwitchPort; + rc = vboxNetFltOsConnectIt(pThis); + if (RT_SUCCESS(rc)) + { + vboxNetFltSetState(pThis, kVBoxNetFltInsState_Connected); + *ppIfPort = &pThis->MyPort; + } + else + pThis->pSwitchPort = NULL; + + Assert(pThis->enmTrunkState == INTNETTRUNKIFSTATE_INACTIVE); + return rc; +} + + +/** + * Creates a new instance. + * + * The new instance will be in the suspended state in a dynamic config and in + * the inactive in a static one. + * + * Called without owning the lock, but will request is several times. + * + * @returns VBox status code. + * @param pGlobals The globals. + * @param pszName The instance name. + * @param pSwitchPort The port on the switch that we're connected with (dynamic only). + * @param fNoPromisc Do not attempt going into promiscuous mode. + * @param pvContext Context argument for vboxNetFltOsInitInstance. + * @param ppIfPort Where to store the pointer to our port interface (dynamic only). + */ +static int vboxNetFltNewInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PINTNETTRUNKSWPORT pSwitchPort, + bool fNoPromisc, void *pvContext, PINTNETTRUNKIFPORT *ppIfPort) +{ + /* + * Allocate and initialize a new instance before requesting the mutex. + * Note! That in a static config we'll initialize the trunk state to + * disconnecting and flip it in vboxNetFltFactoryCreateAndConnect + * later on. This better reflext the state and it works better with + * assertions in the destruction path. + */ + int rc; + size_t const cchName = strlen(pszName); + PVBOXNETFLTINS pNew = (PVBOXNETFLTINS)RTMemAllocZVar(RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1])); + if (!pNew) + return VERR_INTNET_FLT_IF_FAILED; + AssertMsg(((uintptr_t)pNew & 7) == 0, ("%p LB %#x\n", pNew, RT_UOFFSETOF_DYN(VBOXNETFLTINS, szName[cchName + 1]))); + pNew->pNext = NULL; + pNew->MyPort.u32Version = INTNETTRUNKIFPORT_VERSION; + pNew->MyPort.pfnRetain = vboxNetFltPortRetain; + pNew->MyPort.pfnRelease = vboxNetFltPortRelease; + pNew->MyPort.pfnDisconnectAndRelease= vboxNetFltPortDisconnectAndRelease; + pNew->MyPort.pfnSetState = vboxNetFltPortSetState; + pNew->MyPort.pfnWaitForIdle = vboxNetFltPortWaitForIdle; + pNew->MyPort.pfnXmit = vboxNetFltPortXmit; + pNew->MyPort.pfnNotifyMacAddress = vboxNetFltPortNotifyMacAddress; + pNew->MyPort.pfnConnectInterface = vboxNetFltPortConnectInterface; + pNew->MyPort.pfnDisconnectInterface = vboxNetFltPortDisconnectInterface; + pNew->MyPort.u32VersionEnd = INTNETTRUNKIFPORT_VERSION; + pNew->pSwitchPort = pSwitchPort; + pNew->pGlobals = pGlobals; + pNew->hSpinlock = NIL_RTSPINLOCK; + pNew->enmState = kVBoxNetFltInsState_Initializing; +#ifdef VBOXNETFLT_STATIC_CONFIG + pNew->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; +#else + pNew->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; +#endif + pNew->fDisconnectedFromHost = false; + pNew->fRediscoveryPending = false; + pNew->fDisablePromiscuous = fNoPromisc; + pNew->NanoTSLastRediscovery = INT64_MAX; + pNew->cRefs = 1; + pNew->cBusy = 0; + pNew->hEventIdle = NIL_RTSEMEVENT; + RT_BCOPY_UNFORTIFIED(pNew->szName, pszName, cchName + 1); + + rc = RTSpinlockCreate(&pNew->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxNetFltNewInstance"); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pNew->hEventIdle); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltOsPreInitInstance(pNew); + if (RT_SUCCESS(rc)) + { + /* + * Insert the instance into the chain, checking for + * duplicates first of course (race). + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + if (!vboxNetFltFindInstanceLocked(pGlobals, pszName)) + { + pNew->pNext = pGlobals->pInstanceHead; + pGlobals->pInstanceHead = pNew; + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Call the OS specific initialization code. + */ + rc = vboxNetFltOsInitInstance(pNew, pvContext); + RTSemFastMutexRequest(pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* + * Static instances are unconnected at birth. + */ + Assert(!pSwitchPort); + pNew->enmState = kVBoxNetFltInsState_Unconnected; + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppIfPort = &pNew->MyPort; + return rc; + +#else /* !VBOXNETFLT_STATIC_CONFIG */ + /* + * Connect it as well, the OS specific bits has to be done outside + * the lock as they may call back to into intnet. + */ + rc = vboxNetFltConnectIt(pNew, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + Assert(*ppIfPort == &pNew->MyPort); + return rc; + } + + /* Bail out (failed). */ + vboxNetFltOsDeleteInstance(pNew); +#endif /* !VBOXNETFLT_STATIC_CONFIG */ + } + vboxNetFltUnlinkLocked(pGlobals, pNew); + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); + } + } + RTSemEventDestroy(pNew->hEventIdle); + } + RTSpinlockDestroy(pNew->hSpinlock); + } + + RTMemFree(pNew); + return rc; +} + + +#ifdef VBOXNETFLT_STATIC_CONFIG +/** + * Searches for the NetFlt instance by its name and creates the new one if not found. + * + * @returns VBox status code. + * @retval VINF_SUCCESS and *ppInstance if a new instance was created. + * @retval VINF_ALREADY_INITIALIZED and *ppInstance if an instance already exists. + * + * @param pGlobal Pointer to the globals. + * @param pszName The instance name. + * @param ppInstance Where to return the instance pointer on success. + * @param pvContext Context which needs to be passed along to vboxNetFltOsInitInstance. + */ +DECLHIDDEN(int) vboxNetFltSearchCreateInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PVBOXNETFLTINS *ppInstance, void *pvContext) +{ + PINTNETTRUNKIFPORT pIfPort; + PVBOXNETFLTINS pCur; + VBOXNETFTLINSSTATE enmState; + int rc; + + *ppInstance = NULL; + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* + * Look for an existing instance in the list. + * + * There might be an existing one in the list if the driver was unbound + * while it was connected to an internal network. We're running into + * a destruction race that is a bit similar to the one in + * vboxNetFltFactoryCreateAndConnect, only the roles are reversed + * and we're not in a position to back down. Instead of backing down + * we'll delay a bit giving the other thread time to complete the + * destructor. + */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + while (pCur) + { + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + enmState = vboxNetFltGetState(pCur); + switch (enmState) + { + case kVBoxNetFltInsState_Unconnected: + case kVBoxNetFltInsState_Connected: + case kVBoxNetFltInsState_Disconnecting: + if (pCur->fDisconnectedFromHost) + { + /* Wait for it to exit the transitional disconnecting + state. It might otherwise be running the risk of + upsetting the OS specific code... */ + /** @todo This reconnect stuff should be serialized correctly for static + * devices. Shouldn't it? In the dynamic case we're using the INTNET + * outbound thunk lock, but that doesn't quite cut it here, or does + * it? We could either transition to initializing or make a callback + * while owning the mutex here... */ + if (enmState == kVBoxNetFltInsState_Disconnecting) + { + do + { + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + RTSemFastMutexRequest(pGlobals->hFastMtx); + enmState = vboxNetFltGetState(pCur); + } + while (enmState == kVBoxNetFltInsState_Disconnecting); + AssertMsg(enmState == kVBoxNetFltInsState_Unconnected, ("%d\n", enmState)); + Assert(pCur->fDisconnectedFromHost); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + *ppInstance = pCur; + return VINF_ALREADY_INITIALIZED; + } + /* fall thru */ + + default: + { + bool fDfH = pCur->fDisconnectedFromHost; + RTSemFastMutexRelease(pGlobals->hFastMtx); + vboxNetFltRelease(pCur, false /* fBusy */); + LogRel(("VBoxNetFlt: Huh? An instance of '%s' already exists! [pCur=%p cRefs=%d fDfH=%RTbool enmState=%d]\n", + pszName, pCur, cRefs - 1, fDfH, enmState)); + *ppInstance = NULL; + return VERR_INTNET_FLT_IF_BUSY; + } + } + } + + /* Zero references, it's being destroyed. Delay a bit so the destructor + can finish its work and try again. (vboxNetFltNewInstance will fail + with duplicate name if we don't.) */ +# ifdef RT_STRICT + Assert(cRefs == 1); + enmState = vboxNetFltGetState(pCur); + AssertMsg( enmState == kVBoxNetFltInsState_Unconnected + || enmState == kVBoxNetFltInsState_Disconnecting + || enmState == kVBoxNetFltInsState_Destroyed, ("%d\n", enmState)); +# endif + ASMAtomicDecU32(&pCur->cRefs); + RTSemFastMutexRelease(pGlobals->hFastMtx); + RTThreadSleep(2); /* (2ms) */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + + /* try again */ + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + + /* + * Try create a new instance. + * (fNoPromisc is overridden in the vboxNetFltFactoryCreateAndConnect path, so pass true here.) + */ + rc = vboxNetFltNewInstance(pGlobals, pszName, NULL, true /* fNoPromisc */, pvContext, &pIfPort); + if (RT_SUCCESS(rc)) + *ppInstance = IFPORT_2_VBOXNETFLTINS(pIfPort); + else + *ppInstance = NULL; + + return rc; +} +#endif /* VBOXNETFLT_STATIC_CONFIG */ + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnCreateAndConnect + */ +static DECLCALLBACK(int) vboxNetFltFactoryCreateAndConnect(PINTNETTRUNKFACTORY pIfFactory, const char *pszName, + PINTNETTRUNKSWPORT pSwitchPort, uint32_t fFlags, + PINTNETTRUNKIFPORT *ppIfPort) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + PVBOXNETFLTINS pCur; + int rc; + + LogFlow(("vboxNetFltFactoryCreateAndConnect: pszName=%p:{%s} fFlags=%#x\n", pszName, pszName, fFlags)); + Assert(pGlobals->cFactoryRefs > 0); + AssertMsgReturn(!(fFlags & ~(INTNETTRUNKFACTORY_FLAG_NO_PROMISC)), + ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + + /* + * Static: Find instance, check if busy, connect if not. + * Dynamic: Check for duplicate / busy interface instance. + */ + rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRCReturn(rc, rc); + +//#if defined(VBOXNETADP) && defined(RT_OS_WINDOWS) +// /* temporary hack to pick up the first adapter */ +// pCur = pGlobals->pInstanceHead; /** @todo Don't for get to remove this temporary hack... :-) */ +//#else + pCur = vboxNetFltFindInstanceLocked(pGlobals, pszName); +//#endif + if (pCur) + { +#ifdef VBOXNETFLT_STATIC_CONFIG + /* Try grab a reference. If the count had already reached zero we're racing the + destructor code and must back down. */ + uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); + if (cRefs > 1) + { + if (vboxNetFltGetState(pCur) == kVBoxNetFltInsState_Unconnected) + { + pCur->enmTrunkState = INTNETTRUNKIFSTATE_INACTIVE; /** @todo protect me? */ + pCur->fDisablePromiscuous = !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC); + rc = vboxNetFltConnectIt(pCur, pSwitchPort, ppIfPort); + if (RT_SUCCESS(rc)) + pCur = NULL; /* Don't release it, reference given to the caller. */ + else + pCur->enmTrunkState = INTNETTRUNKIFSTATE_DISCONNECTING; + } + else + rc = VERR_INTNET_FLT_IF_BUSY; + } + else + { + Assert(cRefs == 1); + ASMAtomicDecU32(&pCur->cRefs); + pCur = NULL; /* nothing to release */ + rc = VERR_INTNET_FLT_IF_NOT_FOUND; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + if (pCur) + vboxNetFltRelease(pCur, false /* fBusy */); +#else + rc = VERR_INTNET_FLT_IF_BUSY; + RTSemFastMutexRelease(pGlobals->hFastMtx); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; + } + + RTSemFastMutexRelease(pGlobals->hFastMtx); + +#ifdef VBOXNETFLT_STATIC_CONFIG + rc = VERR_INTNET_FLT_IF_NOT_FOUND; +#else + /* + * Dynamically create a new instance. + */ + rc = vboxNetFltNewInstance(pGlobals, + pszName, + pSwitchPort, + !!(fFlags & INTNETTRUNKFACTORY_FLAG_NO_PROMISC), + NULL, + ppIfPort); +#endif + LogFlow(("vboxNetFltFactoryCreateAndConnect: returns %Rrc\n", rc)); + return rc; +} + + +/** + * @copydoc INTNETTRUNKFACTORY::pfnRelease + */ +static DECLCALLBACK(void) vboxNetFltFactoryRelease(PINTNETTRUNKFACTORY pIfFactory) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pIfFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, TrunkFactory)); + + int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); + Assert(cRefs >= 0); NOREF(cRefs); + LogFlow(("vboxNetFltFactoryRelease: cRefs=%d (new)\n", cRefs)); +} + + +/** + * Implements the SUPDRV component factor interface query method. + * + * @returns Pointer to an interface. NULL if not supported. + * + * @param pSupDrvFactory Pointer to the component factory registration structure. + * @param pSession The session - unused. + * @param pszInterfaceUuid The factory interface id. + */ +static DECLCALLBACK(void *) vboxNetFltQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, + const char *pszInterfaceUuid) +{ + PVBOXNETFLTGLOBALS pGlobals = (PVBOXNETFLTGLOBALS)((uint8_t *)pSupDrvFactory - RT_UOFFSETOF(VBOXNETFLTGLOBALS, SupDrvFactory)); + + /* + * Convert the UUID strings and compare them. + */ + RTUUID UuidReq; + int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); + if (RT_SUCCESS(rc)) + { + if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_UUID_STR)) + { + ASMAtomicIncS32(&pGlobals->cFactoryRefs); + return &pGlobals->TrunkFactory; + } +#ifdef LOG_ENABLED + /* log legacy queries */ + /* else if (!RTUuidCompareStr(&UuidReq, INTNETTRUNKFACTORY_V1_UUID_STR)) + Log(("VBoxNetFlt: V1 factory query\n")); + */ + else + Log(("VBoxNetFlt: unknown factory interface query (%s)\n", pszInterfaceUuid)); +#endif + } + else + Log(("VBoxNetFlt: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); + + RT_NOREF1(pSession); + return NULL; +} + + +/** + * Checks whether the VBoxNetFlt wossname can be unloaded. + * + * This will return false if someone is currently using the module. + * + * @returns true if it's relatively safe to unload it, otherwise false. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(bool) vboxNetFltCanUnload(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + bool fRc = !pGlobals->pInstanceHead + && pGlobals->cFactoryRefs <= 0; + RTSemFastMutexRelease(pGlobals->hFastMtx); + AssertRC(rc); + return fRc; +} + + +/** + * Try to close the IDC connection to SUPDRV if established. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + * + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + + Assert(pGlobals->hFastMtx != NIL_RTSEMFASTMUTEX); + + /* + * Check before trying to deregister the factory. + */ + if (!vboxNetFltCanUnload(pGlobals)) + return VERR_WRONG_ORDER; + + if (!pGlobals->fIDCOpen) + rc = VINF_SUCCESS; + else + { + /* + * Disconnect from SUPDRV and check that nobody raced us, + * reconnect if that should happen. + */ + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + if (!vboxNetFltCanUnload(pGlobals)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + return VERR_WRONG_ORDER; + } + + SUPR0IdcClose(&pGlobals->SupDrvIDC); + pGlobals->fIDCOpen = false; + } + + return rc; +} + + +/** + * Establishes the IDC connection to SUPDRV and registers our component factory. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc; + Assert(!pGlobals->fIDCOpen); + + /* + * Establish a connection to SUPDRV and register our component factory. + */ + rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + if (RT_SUCCESS(rc)) + { + pGlobals->fIDCOpen = true; + Log(("VBoxNetFlt: pSession=%p\n", SUPR0IdcGetSession(&pGlobals->SupDrvIDC))); + return rc; + } + + /* bail out. */ + LogRel(("VBoxNetFlt: Failed to register component factory, rc=%Rrc\n", rc)); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + } + + return rc; +} + + +/** + * Deletes the globals. + * + * This must be called after the IDC connection has been closed, + * see vboxNetFltTryDeleteIdc(). + * + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltTryDeleteIdcAndGlobals() + */ +DECLHIDDEN(void) vboxNetFltDeleteGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + Assert(!pGlobals->fIDCOpen); + + /* + * Release resources. + */ + RTSemFastMutexDestroy(pGlobals->hFastMtx); + pGlobals->hFastMtx = NIL_RTSEMFASTMUTEX; +} + + +/** + * Initializes the globals. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + * @sa vboxNetFltInitGlobalsAndIdc(). + */ +DECLHIDDEN(int) vboxNetFltInitGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = RTSemFastMutexCreate(&pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + pGlobals->pInstanceHead = NULL; + + pGlobals->TrunkFactory.pfnRelease = vboxNetFltFactoryRelease; + pGlobals->TrunkFactory.pfnCreateAndConnect = vboxNetFltFactoryCreateAndConnect; +#if defined(RT_OS_WINDOWS) && defined(VBOXNETADP) + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetAdp", sizeof("VBoxNetAdp")); +#else + memcpy(pGlobals->SupDrvFactory.szName, "VBoxNetFlt", sizeof("VBoxNetFlt")); +#endif + pGlobals->SupDrvFactory.pfnQueryFactoryInterface = vboxNetFltQueryFactoryInterface; + pGlobals->fIDCOpen = false; + + return rc; + } + + return rc; +} + + +/** + * Called by the native part when the OS wants the driver to unload. + * + * @returns VINF_SUCCESS on success, VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltTryDeleteIdcAndGlobals(PVBOXNETFLTGLOBALS pGlobals) +{ + int rc = vboxNetFltTryDeleteIdc(pGlobals); + if (RT_SUCCESS(rc)) + vboxNetFltDeleteGlobals(pGlobals); + return rc; +} + + +/** + * Called by the native driver/kext module initialization routine. + * + * It will initialize the common parts of the globals, assuming the caller + * has already taken care of the OS specific bits, and establish the IDC + * connection to SUPDRV. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + */ +DECLHIDDEN(int) vboxNetFltInitGlobalsAndIdc(PVBOXNETFLTGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = vboxNetFltInitGlobals(pGlobals); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltInitIdc(pGlobals); + if (RT_SUCCESS(rc)) + return rc; + + /* bail out. */ + vboxNetFltDeleteGlobals(pGlobals); + } + + return rc; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc new file mode 100644 index 00000000..0205307a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.rc @@ -0,0 +1,69 @@ +/* $Id: VBoxNetFlt.rc $ */ +/** @file + * VBoxNetFlt - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +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_NETWORK +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "VirtualBox NDIS 6.0 Lightweight Filter Driver\0" + VALUE "InternalName", "VBoxNetLwf\0" + VALUE "OriginalFilename", "VBoxNetLwf.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/VBoxNetFlt/VBoxNetFltInternal.h b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h new file mode 100644 index 00000000..1cff0981 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h @@ -0,0 +1,499 @@ +/* $Id: VBoxNetFltInternal.h $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), 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 . + * + * 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_VBoxNetFlt_VBoxNetFltInternal_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_VBoxNetFltInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include +#include + + +RT_C_DECLS_BEGIN + +/** Pointer to the globals. */ +typedef struct VBOXNETFLTGLOBALS *PVBOXNETFLTGLOBALS; + + +/** + * The state of a filter driver instance. + * + * The state machine differs a bit between the platforms because of + * the way we hook into the stack. On some hosts we can dynamically + * attach when required (on CreateInstance) and on others we will + * have to connect when the network stack is bound up. These modes + * are called static and dynamic config and governed at compile time + * by the VBOXNETFLT_STATIC_CONFIG define. + * + * See sec_netflt_msc for more details on locking and synchronization. + */ +typedef enum VBOXNETFTLINSSTATE +{ + /** The usual invalid state. */ + kVBoxNetFltInsState_Invalid = 0, + /** Initialization. + * We've reserved the interface name but need to attach to the actual + * network interface outside the lock to avoid deadlocks. + * In the dynamic case this happens during a Create(Instance) call. + * In the static case it happens during driver initialization. */ + kVBoxNetFltInsState_Initializing, +#ifdef VBOXNETFLT_STATIC_CONFIG + /** Unconnected, not hooked up to a switch (static only). + * The filter driver instance has been instantiated and hooked up, + * waiting to be connected to an internal network. */ + kVBoxNetFltInsState_Unconnected, +#endif + /** Connected to an internal network. */ + kVBoxNetFltInsState_Connected, + /** Disconnecting from the internal network and possibly the host network interface. + * Partly for reasons of deadlock avoidance again. */ + kVBoxNetFltInsState_Disconnecting, + /** The instance has been disconnected from both the host and the internal network. */ + kVBoxNetFltInsState_Destroyed, + + /** The habitual 32-bit enum hack. */ + kVBoxNetFltInsState_32BitHack = 0x7fffffff +} VBOXNETFTLINSSTATE; + + +/** + * The per-instance data of the VBox filter driver. + * + * This is data associated with a network interface / NIC / wossname which + * the filter driver has been or may be attached to. When possible it is + * attached dynamically, but this may not be possible on all OSes so we have + * to be flexible about things. + * + * A network interface / NIC / wossname can only have one filter driver + * instance attached to it. So, attempts at connecting an internal network + * to an interface that's already in use (connected to another internal network) + * will result in a VERR_SHARING_VIOLATION. + * + * Only one internal network can connect to a filter driver instance. + */ +typedef struct VBOXNETFLTINS +{ + /** Pointer to the next interface in the list. (VBOXNETFLTGLOBAL::pInstanceHead) */ + struct VBOXNETFLTINS *pNext; + /** Our RJ-45 port. + * This is what the internal network plugs into. */ + INTNETTRUNKIFPORT MyPort; + /** The RJ-45 port on the INTNET "switch". + * This is what we're connected to. */ + PINTNETTRUNKSWPORT pSwitchPort; + /** Pointer to the globals. */ + PVBOXNETFLTGLOBALS pGlobals; + + /** The spinlock protecting the state variables and host interface handle. */ + RTSPINLOCK hSpinlock; + /** The current interface state. */ + VBOXNETFTLINSSTATE volatile enmState; + /** The trunk state. */ + INTNETTRUNKIFSTATE volatile enmTrunkState; + bool volatile fActive; + /** Disconnected from the host network interface. */ + bool volatile fDisconnectedFromHost; + /** Rediscovery is pending. + * cBusy will never reach zero during rediscovery, so which + * takes care of serializing rediscovery and disconnecting. */ + bool volatile fRediscoveryPending; + /** Whether we should not attempt to set promiscuous mode at all. */ + bool fDisablePromiscuous; +#if (ARCH_BITS == 32) && defined(__GNUC__) +#if 0 + uint32_t u32Padding; /**< Alignment padding, will assert in ASMAtomicUoWriteU64 otherwise. */ +#endif +#endif + /** The timestamp of the last rediscovery. */ + uint64_t volatile NanoTSLastRediscovery; + /** Reference count. */ + uint32_t volatile cRefs; + /** The busy count. + * This counts the number of current callers and pending packet. */ + uint32_t volatile cBusy; + /** The event that is signaled when we go idle and that pfnWaitForIdle blocks on. */ + RTSEMEVENT hEventIdle; + + /** @todo move MacAddr out of this structure! */ + union + { +#ifdef VBOXNETFLT_OS_SPECFIC + struct + { +# if defined(RT_OS_DARWIN) + /** @name Darwin instance data. + * @{ */ + /** Pointer to the darwin network interface we're attached to. + * This is treated as highly volatile and should only be read and retained + * while owning hSpinlock. Releasing references to this should not be done + * while owning it though as we might end up destroying it in some paths. */ + ifnet_t volatile pIfNet; + /** The interface filter handle. + * Same access rules as with pIfNet. */ + interface_filter_t volatile pIfFilter; + /** Whether we've need to set promiscuous mode when the interface comes up. */ + bool volatile fNeedSetPromiscuous; + /** Whether we've successfully put the interface into to promiscuous mode. + * This is for dealing with the ENETDOWN case. */ + bool volatile fSetPromiscuous; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** PF_SYSTEM socket to listen for events (XXX: globals?) */ + socket_t pSysSock; + /** @} */ +# elif defined(RT_OS_LINUX) + /** @name Linux instance data + * @{ */ + /** Pointer to the device. */ + struct net_device * volatile pDev; + /** MTU of host's interface. */ + uint32_t cbMtu; + /** Whether we've successfully put the interface into to promiscuous mode. + * This is for dealing with the ENETDOWN case. */ + bool volatile fPromiscuousSet; + /** Whether device exists and physically attached. */ + bool volatile fRegistered; + /** Whether our packet handler is installed. */ + bool volatile fPacketHandler; + /** The MAC address of the interface. */ + RTMAC MacAddr; + struct notifier_block Notifier; /* netdevice */ + struct notifier_block NotifierIPv4; + struct notifier_block NotifierIPv6; + struct packet_type PacketType; +# ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + struct sk_buff_head XmitQueue; + struct work_struct XmitTask; +# endif + /** @} */ +# elif defined(RT_OS_SOLARIS) + /** @name Solaris instance data. + * @{ */ +# ifdef VBOX_WITH_NETFLT_CROSSBOW + /** Whether the underlying interface is a VNIC or not. */ + bool fIsVNIC; + /** Whether the underlying interface is a VNIC template or not. */ + bool fIsVNICTemplate; + /** Handle to list of created VNICs. */ + list_t hVNICs; + /** The MAC address of the host interface. */ + RTMAC MacAddr; + /** Handle of this interface (lower MAC). */ + mac_handle_t hInterface; + /** Handle to link state notifier. */ + mac_notify_handle_t hNotify; +# else + /** Pointer to the bound IPv4 stream. */ + struct vboxnetflt_stream_t * volatile pIp4Stream; + /** Pointer to the bound IPv6 stream. */ + struct vboxnetflt_stream_t * volatile pIp6Stream; + /** Pointer to the bound ARP stream. */ + struct vboxnetflt_stream_t * volatile pArpStream; + /** Pointer to the unbound promiscuous stream. */ + struct vboxnetflt_promisc_stream_t * volatile pPromiscStream; + /** Whether we are attaching to IPv6 stream dynamically now. */ + bool volatile fAttaching; + /** Whether this is a VLAN interface or not. */ + bool volatile fVLAN; + /** Layered device handle to the interface. */ + ldi_handle_t hIface; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** Mutex protection used for loopback. */ + kmutex_t hMtx; + /** Mutex protection used for dynamic IPv6 attaches. */ + RTSEMFASTMUTEX hPollMtx; +# endif + /** @} */ +# elif defined(RT_OS_FREEBSD) + /** @name FreeBSD instance data. + * @{ */ + /** Interface handle */ + struct ifnet *ifp; + /** Netgraph node handle */ + node_p node; + /** Input hook */ + hook_p input; + /** Output hook */ + hook_p output; + /** Original interface flags */ + unsigned int flags; + /** Input queue */ + struct ifqueue inq; + /** Output queue */ + struct ifqueue outq; + /** Input task */ + struct task tskin; + /** Output task */ + struct task tskout; + /** The MAC address of the interface. */ + RTMAC MacAddr; + /** @} */ +# elif defined(RT_OS_WINDOWS) + /** @name Windows instance data. + * @{ */ + /** Filter driver device context. */ + VBOXNETFLTWIN WinIf; + + volatile uint32_t cModeNetFltRefs; + volatile uint32_t cModePassThruRefs; +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + /** Packet worker thread info */ + PACKET_QUEUE_WORKER PacketQueueWorker; +#endif + /** The MAC address of the interface. Caching MAC for performance reasons. */ + RTMAC MacAddr; + /** mutex used to synchronize WinIf init/deinit */ + RTSEMMUTEX hWinIfMutex; + /** @} */ +# else +# error "PORTME" +# endif + } s; +#endif + /** Padding. */ +#if defined(RT_OS_WINDOWS) +# if defined(VBOX_NETFLT_ONDEMAND_BIND) + uint8_t abPadding[192]; +# elif defined(VBOXNETADP) + uint8_t abPadding[256]; +# else + uint8_t abPadding[1024]; +# endif +#elif defined(RT_OS_LINUX) + uint8_t abPadding[320]; +#elif defined(RT_OS_FREEBSD) + uint8_t abPadding[320]; +#else + uint8_t abPadding[128]; +#endif + } u; + + /** The interface name. */ + char szName[1]; +} VBOXNETFLTINS; +/** Pointer to the instance data of a host network filter driver. */ +typedef struct VBOXNETFLTINS *PVBOXNETFLTINS; + +AssertCompileMemberAlignment(VBOXNETFLTINS, NanoTSLastRediscovery, 8); +#ifdef VBOXNETFLT_OS_SPECFIC +AssertCompile(RT_SIZEOFMEMB(VBOXNETFLTINS, u.s) <= RT_SIZEOFMEMB(VBOXNETFLTINS, u.abPadding)); +#endif + + +/** + * The global data of the VBox filter driver. + * + * This contains the bit required for communicating with support driver, VBoxDrv + * (start out as SupDrv). + */ +typedef struct VBOXNETFLTGLOBALS +{ + /** Mutex protecting the list of instances and state changes. */ + RTSEMFASTMUTEX hFastMtx; + /** Pointer to a list of instance data. */ + PVBOXNETFLTINS pInstanceHead; + + /** The INTNET trunk network interface factory. */ + INTNETTRUNKFACTORY TrunkFactory; + /** The SUPDRV component factory registration. */ + SUPDRVFACTORY SupDrvFactory; + /** The number of current factory references. */ + int32_t volatile cFactoryRefs; + /** Whether the IDC connection is open or not. + * This is only for cleaning up correctly after the separate IDC init on Windows. */ + bool fIDCOpen; + /** The SUPDRV IDC handle (opaque struct). */ + SUPDRVIDCHANDLE SupDrvIDC; +} VBOXNETFLTGLOBALS; + + +DECLHIDDEN(int) vboxNetFltInitGlobalsAndIdc(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltInitGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltInitIdc(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltTryDeleteIdcAndGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(void) vboxNetFltDeleteGlobals(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(int) vboxNetFltTryDeleteIdc(PVBOXNETFLTGLOBALS pGlobals); + +DECLHIDDEN(bool) vboxNetFltCanUnload(PVBOXNETFLTGLOBALS pGlobals); +DECLHIDDEN(PVBOXNETFLTINS) vboxNetFltFindInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName); + +DECL_HIDDEN_CALLBACK(void) vboxNetFltPortReleaseBusy(PINTNETTRUNKIFPORT pIfPort); +DECLHIDDEN(void) vboxNetFltRetain(PVBOXNETFLTINS pThis, bool fBusy); +DECLHIDDEN(bool) vboxNetFltTryRetainBusyActive(PVBOXNETFLTINS pThis); +DECLHIDDEN(bool) vboxNetFltTryRetainBusyNotDisconnected(PVBOXNETFLTINS pThis); +DECLHIDDEN(void) vboxNetFltRelease(PVBOXNETFLTINS pThis, bool fBusy); + +#ifdef VBOXNETFLT_STATIC_CONFIG +DECLHIDDEN(int) vboxNetFltSearchCreateInstance(PVBOXNETFLTGLOBALS pGlobals, const char *pszName, PVBOXNETFLTINS *ppInstance, void * pContext); +#endif + + + +/** @name The OS specific interface. + * @{ */ +/** + * Try rediscover the host interface. + * + * This is called periodically from the transmit path if we're marked as + * disconnected from the host. There is no chance of a race here. + * + * @returns true if the interface was successfully rediscovered and reattach, + * otherwise false. + * @param pThis The new instance. + */ +DECLHIDDEN(bool) vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis); + +/** + * Transmits a frame. + * + * @return IPRT status code. + * @param pThis The new instance. + * @param pvIfData Pointer to the host-private interface data. + * @param pSG The (scatter/)gather list. + * @param fDst The destination mask. At least one bit will be set. + * + * @remarks Owns the out-bound trunk port semaphore. + */ +DECLHIDDEN(int) vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst); + +/** + * This is called when activating or suspending the instance. + * + * Use this method to enable and disable promiscuous mode on + * the interface to prevent unnecessary interrupt load. + * + * It is only called when the state changes. + * + * @param pThis The instance. + * @param fActive Whether to active (@c true) or deactive. + * + * @remarks Owns the lock for the out-bound trunk port. + */ +DECLHIDDEN(void) vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive); + +/** + * This is called when a network interface has obtained a new MAC address. + * + * @param pThis The instance. + * @param pvIfData Pointer to the private interface data. + * @param pMac Pointer to the new MAC address. + */ +DECLHIDDEN(void) vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac); + +/** + * This is called when an interface is connected to the network. + * + * @return IPRT status code. + * @param pThis The instance. + * @param pvIf Pointer to the interface. + * @param ppvIfData Where to store the private interface data. + */ +DECLHIDDEN(int) vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData); + +/** + * This is called when a VM host disconnects from the network. + * + * @param pThis The instance. + * @param pvIfData Pointer to the private interface data. + */ +DECLHIDDEN(int) vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData); + +/** + * This is called to when disconnecting from a network. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks May own the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(int) vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis); + +/** + * This is called to when connecting to a network. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks Owns the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(int) vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis); + +/** + * Counter part to vboxNetFltOsInitInstance(). + * + * @param pThis The new instance. + * + * @remarks May own the semaphores for the global list, the network lock and the out-bound trunk port. + */ +DECLHIDDEN(void) vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis); + +/** + * This is called to attach to the actual host interface + * after linking the instance into the list. + * + * The MAC address as well promiscuousness and GSO capabilities should be + * reported by this function. + * + * @return IPRT status code. + * @param pThis The new instance. + * @param pvContext The user supplied context in the static config only. + * NULL in the dynamic config. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext); + +/** + * This is called to perform structure initializations. + * + * @return IPRT status code. + * @param pThis The new instance. + * + * @remarks Owns no locks. + */ +DECLHIDDEN(int) vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis); +/** @} */ + + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_VBoxNetFltInternal_h */ + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist new file mode 100644 index 00000000..20338308 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion English + CFBundleExecutable VBoxNetFlt + CFBundleIdentifier org.virtualbox.kext.VBoxNetFlt + CFBundleInfoDictionaryVersion 6.0 + CFBundleName VBoxNetFlt + CFBundlePackageType KEXT + CFBundleSignature ???? + CFBundleGetInfoString @VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@ + CFBundleVersion @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + CFBundleShortVersionString @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + IOKitPersonalities + + + OSBundleLibraries + + com.apple.kpi.bsd 8.8.1 + com.apple.kpi.mach 8.8.1 + com.apple.kpi.libkern 8.8.1 + com.apple.kpi.unsupported 8.8.1 + com.apple.kpi.iokit 8.8.1 + org.virtualbox.kext.VBoxDrv @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + + OSBundleLibraries_x86_64 + + com.apple.kpi.bsd 10.0.0d3 + com.apple.kpi.mach 10.0.0d3 + com.apple.kpi.libkern 10.0.0d3 + com.apple.kpi.unsupported 10.0.0d3 + com.apple.kpi.iokit 10.0.0d3 + org.virtualbox.kext.VBoxDrv @VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@ + + + + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp b/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp new file mode 100644 index 00000000..3a14a8c5 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/VBoxNetFlt-darwin.cpp @@ -0,0 +1,1754 @@ +/* $Id: VBoxNetFlt-darwin.cpp $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), 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 . + * + * 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_NET_FLT_DRV +#include "../../../Runtime/r0drv/darwin/the-darwin-kernel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../darwin/VBoxNetSend.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 /* The 10.15 SDK has a slightly butchered API deprecation attempt. */ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmacro-redefined" /* Each header redefines __NKE_API_DEPRECATED. */ +# pragma clang diagnostic ignored "-Wmissing-declarations" /* Misplaced __NKE_API_DEPRECATED; in kpi_mbuf.h. */ +# include +# include +# include +# include +# pragma clang diagnostic pop +#else /* < 10.15*/ +# include +# include +RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */ +# include +# include +RT_C_DECLS_END +#endif /* < 10.15*/ + + +#include +#include +RT_C_DECLS_BEGIN +#include +RT_C_DECLS_END +#include +#include +#include + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The maximum number of SG segments. + * Used to prevent stack overflow and similar bad stuff. */ +#define VBOXNETFLT_DARWIN_MAX_SEGS 32 + +#if 0 +/** For testing extremely segmented frames. */ +#define VBOXNETFLT_DARWIN_TEST_SEG_SIZE 14 +#endif + +/* XXX: hidden undef #ifdef __APPLE__ */ +#define VBOX_IN_LOOPBACK(addr) (((addr) & IN_CLASSA_NET) == 0x7f000000) +#define VBOX_IN_LINKLOCAL(addr) (((addr) & IN_CLASSB_NET) == 0xa9fe0000) + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxNetFltDarwinStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxNetFltDarwinStop(struct kmod_info *pKModInfo, void *pvData); + +static void vboxNetFltDarwinSysSockUpcall(socket_t pSysSock, void *pvData, int fWait); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The mbuf tag data. + * + * We have to associate the ethernet header with each packet we're sending + * because things like icmp will inherit the tag it self so the tag along + * isn't sufficient to identify our mbufs. For the icmp scenario the ethernet + * header naturally changes before the packet is send pack, so let check it. + */ +typedef struct VBOXNETFLTTAG +{ + /** The ethernet header of the outgoing frame. */ + RTNETETHERHDR EthHdr; +} VBOXNETFLTTAG; +/** Pointer to a VBoxNetFlt mbuf tag. */ +typedef VBOXNETFLTTAG *PVBOXNETFLTTAG; +/** Pointer to a const VBoxNetFlt mbuf tag. */ +typedef VBOXNETFLTTAG const *PCVBOXNETFLTTAG; + + +/********************************************************************************************************************************* +* 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(VBoxNetFlt, VBOX_VERSION_STRING, _start, _stop) +DECL_HIDDEN_DATA(kmod_start_func_t *) _realmain = VBoxNetFltDarwinStart; +DECL_HIDDEN_DATA(kmod_stop_func_t *) _antimain = VBoxNetFltDarwinStop; +DECL_HIDDEN_DATA(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +/** The unique tag id for this module. + * This is basically a unique string hash that lives on until reboot. + * It is used for tagging mbufs. */ +static mbuf_tag_id_t g_idTag; + +/** The offset of the struct ifnet::if_pcount variable. + * @remarks Initial value is valid for Lion and earlier. We adjust it on attach + * for later releases. */ +static unsigned g_offIfNetPCount = sizeof(void *) * (1 /*if_softc*/ + 1 /*if_name*/ + 2 /*if_link*/ + 2 /*if_addrhead*/ + 1 /*if_check_multi*/) + + sizeof(u_long) /*if_refcnt*/; +/** Macro for accessing ifnet::if_pcount. */ +#define VBOX_GET_PCOUNT(pIfNet) ( *(int *)((uintptr_t)pIfNet + g_offIfNetPCount) ) +/** The size of area of ifnet structure we try to locate if_pcount in. */ +#define VBOXNETFLT_DARWIN_IFNET_SIZE 256 +/** Indicates whether g_offIfNetPCount has been adjusted already (no point in + * doing it more than once). */ +static bool g_fNetPCountFound = false; + + +/** + * Change the promiscuous setting and try spot the changed in @a pIfNet. + * + * @returns Offset of potential p_count field. + * @param pIfNet The interface we're attaching to. + * @param iPromisc Whether to enable (1) or disable (0) promiscuous mode. + * + * @note This implementation relies on if_pcount to be aligned on sizeof(int). + */ +static unsigned vboxNetFltDarwinSetAndDiff(ifnet_t pIfNet, int iPromisc) +{ + int aiSavedState[VBOXNETFLT_DARWIN_IFNET_SIZE / sizeof(int)]; + memcpy(aiSavedState, pIfNet, sizeof(aiSavedState)); + + ifnet_set_promiscuous(pIfNet, iPromisc); + + int const iDiff = iPromisc ? 1 : -1; + + /* + * We assume that ifnet structure will never have less members in front of if_pcount + * than it used to have in Lion. If this turns out to be false assumption we will + * have to start from zero offset. + */ + for (unsigned i = g_offIfNetPCount / sizeof(int); i < RT_ELEMENTS(aiSavedState); i++) + if (((int*)pIfNet)[i] - aiSavedState[i] == iDiff) + return i * sizeof(int); + + return 0; +} + + +/** + * Detect and adjust the offset of ifnet::if_pcount. + * + * @param pIfNet The interface we're attaching to. + */ +static void vboxNetFltDarwinDetectPCountOffset(ifnet_t pIfNet) +{ + if (g_fNetPCountFound) + return; + + /* + * It would be nice to use locking at this point, but it is not available via KPI. + * This is why we try several times. At each attempt we modify if_pcount four times + * to rule out false detections. + */ + unsigned offTry1, offTry2, offTry3, offTry4; + for (int iAttempt = 0; iAttempt < 3; iAttempt++) + { + offTry1 = vboxNetFltDarwinSetAndDiff(pIfNet, 1); + offTry2 = vboxNetFltDarwinSetAndDiff(pIfNet, 1); + offTry3 = vboxNetFltDarwinSetAndDiff(pIfNet, 0); + offTry4 = vboxNetFltDarwinSetAndDiff(pIfNet, 0); + if (offTry1 == offTry2 && offTry2 == offTry3 && offTry3 == offTry4) + { + if (g_offIfNetPCount != offTry1) + { + Log(("VBoxNetFltDarwinDetectPCountOffset: Adjusted if_pcount offset to %x from %x.\n", offTry1, g_offIfNetPCount)); + g_offIfNetPCount = offTry1; + g_fNetPCountFound = true; + } + break; + } + } + + if (g_offIfNetPCount != offTry1) + LogRel(("VBoxNetFlt: Failed to detect promiscuous count, all traffic may reach wire (%x != %x).\n", g_offIfNetPCount, offTry1)); +} + + +/** + * Start the kernel module. + */ +static kern_return_t VBoxNetFltDarwinStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + + /* + * Initialize IPRT and find our module tag id. + * (IPRT is shared with VBoxDrv, it creates the loggers.) + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetFltDarwinStart\n")); + errno_t err = mbuf_tag_id_find("org.VirtualBox.kext.VBoxFltDrv", &g_idTag); + if (!err) + { + /* + * Initialize the globals and connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + LogRel(("VBoxFltDrv: version " VBOX_VERSION_STRING " r%d\n", VBOX_SVN_REV)); + return KMOD_RETURN_SUCCESS; + } + + LogRel(("VBoxFltDrv: failed to initialize device extension (rc=%d)\n", rc)); + } + else + LogRel(("VBoxFltDrv: mbuf_tag_id_find failed, err=%d\n", err)); + RTR0Term(); + } + else + printf("VBoxFltDrv: failed to initialize IPRT (rc=%d)\n", rc); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxNetFltDarwinStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + Log(("VBoxNetFltDarwinStop\n")); + + /* + * Refuse to unload if anyone is currently using the filter driver. + * This is important as I/O kit / xnu will to be able to do usage + * tracking for us! + */ + int rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + if (RT_FAILURE(rc)) + { + Log(("VBoxNetFltDarwinStop - failed, busy.\n")); + return KMOD_RETURN_FAILURE; + } + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTR0Term(); + + return KMOD_RETURN_SUCCESS; +} + + +/** + * Reads and retains the host interface handle. + * + * @returns The handle, NULL if detached. + * @param pThis + */ +DECLINLINE(ifnet_t) vboxNetFltDarwinRetainIfNet(PVBOXNETFLTINS pThis) +{ + ifnet_t pIfNet = NULL; + + /* + * Be careful here to avoid problems racing the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + if (!ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + if (pIfNet) + ifnet_reference(pIfNet); + } + RTSpinlockRelease(pThis->hSpinlock); + + return pIfNet; +} + + +/** + * Release the host interface handle previously retained + * by vboxNetFltDarwinRetainIfNet. + * + * @param pThis The instance. + * @param pIfNet The vboxNetFltDarwinRetainIfNet return value, NULL is fine. + */ +DECLINLINE(void) vboxNetFltDarwinReleaseIfNet(PVBOXNETFLTINS pThis, ifnet_t pIfNet) +{ + NOREF(pThis); + if (pIfNet) + ifnet_release(pIfNet); +} + + +/** + * Checks whether this is an mbuf created by vboxNetFltDarwinMBufFromSG, + * i.e. a buffer which we're pushing and should be ignored by the filter callbacks. + * + * @returns true / false accordingly. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The frame pointer, optional. + */ +DECLINLINE(bool) vboxNetFltDarwinMBufIsOur(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame) +{ + NOREF(pThis); + + /* + * Lookup the tag set by vboxNetFltDarwinMBufFromSG. + */ + PCVBOXNETFLTTAG pTagData; + size_t cbTagData; + errno_t err = mbuf_tag_find(pMBuf, g_idTag, 0 /* type */, &cbTagData, (void **)&pTagData); + if (err) + return false; + AssertReturn(cbTagData == sizeof(*pTagData), false); + + /* + * Dig out the ethernet header from the mbuf. + */ + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pvFrame; + if (!pEthHdr) + pEthHdr = (PCRTNETETHERHDR)mbuf_pkthdr_header(pMBuf); + if (!pEthHdr) + pEthHdr = (PCRTNETETHERHDR)mbuf_data(pMBuf); + /* ASSUMING that there is enough data to work on! */ + if ( pEthHdr->DstMac.au8[0] != pTagData->EthHdr.DstMac.au8[0] + || pEthHdr->DstMac.au8[1] != pTagData->EthHdr.DstMac.au8[1] + || pEthHdr->DstMac.au8[2] != pTagData->EthHdr.DstMac.au8[2] + || pEthHdr->DstMac.au8[3] != pTagData->EthHdr.DstMac.au8[3] + || pEthHdr->DstMac.au8[4] != pTagData->EthHdr.DstMac.au8[4] + || pEthHdr->DstMac.au8[5] != pTagData->EthHdr.DstMac.au8[5] + || pEthHdr->SrcMac.au8[0] != pTagData->EthHdr.SrcMac.au8[0] + || pEthHdr->SrcMac.au8[1] != pTagData->EthHdr.SrcMac.au8[1] + || pEthHdr->SrcMac.au8[2] != pTagData->EthHdr.SrcMac.au8[2] + || pEthHdr->SrcMac.au8[3] != pTagData->EthHdr.SrcMac.au8[3] + || pEthHdr->SrcMac.au8[4] != pTagData->EthHdr.SrcMac.au8[4] + || pEthHdr->SrcMac.au8[5] != pTagData->EthHdr.SrcMac.au8[5] + || pEthHdr->EtherType != pTagData->EthHdr.EtherType) + { + Log3(("tagged, but the ethernet header has changed\n")); + return false; + } + + return true; +} + + +/** + * Internal worker that create a darwin mbuf for a (scatter/)gather list. + * + * @returns Pointer to the mbuf. + * @param pThis The instance. + * @param pSG The (scatter/)gather list. + */ +static mbuf_t vboxNetFltDarwinMBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG) +{ + /// @todo future? mbuf_how_t How = preemption enabled ? MBUF_DONTWAIT : MBUF_WAITOK; + mbuf_how_t How = MBUF_WAITOK; + + /* + * We need some way of getting back to our instance data when + * the mbuf is freed, so use pvUserData for this. + * -- this is not relevant anylonger! -- + */ + Assert(!pSG->pvUserData || pSG->pvUserData == pThis); + Assert(!pSG->pvUserData2); + pSG->pvUserData = pThis; + + /* + * Allocate a packet and copy over the data. + * + * Using mbuf_attachcluster() here would've been nice but there are two + * issues with it: (1) it's 10.5.x only, and (2) the documentation indicates + * that it's not supposed to be used for really external buffers. The 2nd + * point might be argued against considering that the only m_clattach user + * is mallocs memory for the ext mbuf and not doing what's stated in the docs. + * However, it's hard to tell if these m_clattach buffers actually makes it + * to the NICs or not, and even if they did, the NIC would need the physical + * addresses for the pages they contain and might end up copying the data + * to a new mbuf anyway. + * + * So, in the end it's better to just do it the simple way that will work + * 100%, even if it involves some extra work (alloc + copy) we really wished + * to avoid. + * + * Note. We can't make use of the physical addresses on darwin because the + * way the mbuf / cluster stuff works (see mbuf_data_to_physical and + * mcl_to_paddr). + */ + mbuf_t pPkt = NULL; + errno_t err = mbuf_allocpacket(How, pSG->cbTotal, NULL, &pPkt); + if (!err) + { + /* Skip zero sized memory buffers (paranoia). */ + mbuf_t pCur = pPkt; + while (pCur && !mbuf_maxlen(pCur)) + pCur = mbuf_next(pCur); + Assert(pCur); + + /* Set the required packet header attributes. */ + mbuf_pkthdr_setlen(pPkt, pSG->cbTotal); + mbuf_pkthdr_setheader(pPkt, mbuf_data(pCur)); + + /* Special case the single buffer copy. */ + if ( mbuf_next(pCur) + && mbuf_maxlen(pCur) >= pSG->cbTotal) + { + mbuf_setlen(pCur, pSG->cbTotal); + IntNetSgRead(pSG, mbuf_data(pCur)); + } + else + { + /* Multi buffer copying. */ + size_t cbLeft = pSG->cbTotal; + size_t offSrc = 0; + while (cbLeft > 0 && pCur) + { + size_t cb = mbuf_maxlen(pCur); + if (cb > cbLeft) + cb = cbLeft; + mbuf_setlen(pCur, cb); + IntNetSgReadEx(pSG, offSrc, cb, mbuf_data(pCur)); + + /* advance */ + offSrc += cb; + cbLeft -= cb; + pCur = mbuf_next(pCur); + } + Assert(cbLeft == 0); + } + if (!err) + { + /* + * Tag the packet and return successfully. + */ + PVBOXNETFLTTAG pTagData; + err = mbuf_tag_allocate(pPkt, g_idTag, 0 /* type */, sizeof(VBOXNETFLTTAG) /* tag len */, How, (void **)&pTagData); + if (!err) + { + Assert(pSG->aSegs[0].cb >= sizeof(pTagData->EthHdr)); + memcpy(&pTagData->EthHdr, pSG->aSegs[0].pv, sizeof(pTagData->EthHdr)); + return pPkt; + } + + /* bailout: */ + AssertMsg(err == ENOMEM || err == EWOULDBLOCK, ("err=%d\n", err)); + } + + mbuf_freem(pPkt); + } + else + AssertMsg(err == ENOMEM || err == EWOULDBLOCK, ("err=%d\n", err)); + pSG->pvUserData = NULL; + + return NULL; +} + + +/** + * Calculates the number of segments required to represent the mbuf. + * + * @returns Number of segments. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The frame pointer, optional. + */ +DECLINLINE(unsigned) vboxNetFltDarwinMBufCalcSGSegs(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame) +{ + NOREF(pThis); + + /* + * Count the buffers in the chain. + */ + unsigned cSegs = 0; + for (mbuf_t pCur = pMBuf; pCur; pCur = mbuf_next(pCur)) + if (mbuf_len(pCur)) + cSegs++; + else if ( !cSegs + && pvFrame + && (uintptr_t)pvFrame - (uintptr_t)mbuf_datastart(pMBuf) < mbuf_maxlen(pMBuf)) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add one buffer if the total is less than the ethernet minimum 60 bytes. + * This may allocate a segment too much if the ethernet header is separated, + * but that shouldn't harm us much. + */ + if (mbuf_pkthdr_len(pMBuf) < 60) + cSegs++; +#endif + +#ifdef VBOXNETFLT_DARWIN_TEST_SEG_SIZE + /* maximize the number of segments. */ + cSegs = RT_MAX(VBOXNETFLT_DARWIN_MAX_SEGS - 1, cSegs); +#endif + + return cSegs ? cSegs : 1; +} + + +/** + * Initializes a SG list from an mbuf. + * + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pSG The SG. + * @param pvFrame The frame pointer, optional. + * @param cSegs The number of segments allocated for the SG. + * This should match the number in the mbuf exactly! + * @param fSrc The source of the frame. + */ +DECLINLINE(void) vboxNetFltDarwinMBufToSG(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame, PINTNETSG pSG, unsigned cSegs, + uint32_t fSrc) +{ + RT_NOREF(pThis, fSrc); + + /* + * Walk the chain and convert the buffers to segments. Works INTNETSG::cbTotal. + */ + unsigned iSeg = 0; + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + for (mbuf_t pCur = pMBuf; pCur; pCur = mbuf_next(pCur)) + { + size_t cbSeg = mbuf_len(pCur); + if (cbSeg) + { + void *pvSeg = mbuf_data(pCur); + + /* deal with pvFrame */ + if (!iSeg && pvFrame && pvFrame != pvSeg) + { + void *pvStart = mbuf_datastart(pMBuf); + uintptr_t offSeg = (uintptr_t)pvSeg - (uintptr_t)pvStart; + uintptr_t offSegEnd = offSeg + cbSeg; + Assert(pvStart && pvSeg && offSeg < mbuf_maxlen(pMBuf) && offSegEnd <= mbuf_maxlen(pMBuf)); NOREF(offSegEnd); + uintptr_t offFrame = (uintptr_t)pvFrame - (uintptr_t)pvStart; + if (RT_LIKELY(offFrame < offSeg)) + { + pvSeg = pvFrame; + cbSeg += offSeg - offFrame; + } + else + AssertMsgFailed(("pvFrame=%p pvStart=%p pvSeg=%p offSeg=%p cbSeg=%#zx offSegEnd=%p offFrame=%p maxlen=%#zx\n", + pvFrame, pvStart, pvSeg, offSeg, cbSeg, offSegEnd, offFrame, mbuf_maxlen(pMBuf))); + pvFrame = NULL; + } + + AssertBreak(iSeg < cSegs); + pSG->cbTotal += cbSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].pv = pvSeg; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + iSeg++; + } + /* The pvFrame might be in a now empty buffer. */ + else if ( !iSeg + && pvFrame + && (uintptr_t)pvFrame - (uintptr_t)mbuf_datastart(pMBuf) < mbuf_maxlen(pMBuf)) + { + cbSeg = (uintptr_t)mbuf_datastart(pMBuf) + mbuf_maxlen(pMBuf) - (uintptr_t)pvFrame; + pSG->cbTotal += cbSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].pv = pvFrame; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + iSeg++; + pvFrame = NULL; + } + } + + Assert(iSeg && iSeg <= cSegs); + pSG->cSegsUsed = iSeg; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add a trailer if the frame is too small. + * + * Since we're getting to the packet before it is framed, it has not + * yet been padded. The current solution is to add a segment pointing + * to a buffer containing all zeros and pray that works for all frames... + */ + if (pSG->cbTotal < 60 && (fSrc == INTNETTRUNKDIR_HOST)) + { + AssertReturnVoid(iSeg < cSegs); + + static uint8_t const s_abZero[128] = {0}; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->aSegs[iSeg].pv = (void *)&s_abZero[0]; + pSG->aSegs[iSeg].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + pSG->cSegsUsed++; + } +#endif + +#ifdef VBOXNETFLT_DARWIN_TEST_SEG_SIZE + /* + * Redistribute the segments. + */ + if (pSG->cSegsUsed < pSG->cSegsAlloc) + { + /* copy the segments to the end. */ + int iSrc = pSG->cSegsUsed; + int iDst = pSG->cSegsAlloc; + while (iSrc > 0) + { + iDst--; + iSrc--; + pSG->aSegs[iDst] = pSG->aSegs[iSrc]; + } + + /* create small segments from the start. */ + pSG->cSegsUsed = pSG->cSegsAlloc; + iSrc = iDst; + iDst = 0; + while ( iDst < iSrc + && iDst < pSG->cSegsAlloc) + { + pSG->aSegs[iDst].Phys = NIL_RTHCPHYS; + pSG->aSegs[iDst].pv = pSG->aSegs[iSrc].pv; + pSG->aSegs[iDst].cb = RT_MIN(pSG->aSegs[iSrc].cb, VBOXNETFLT_DARWIN_TEST_SEG_SIZE); + if (pSG->aSegs[iDst].cb != pSG->aSegs[iSrc].cb) + { + pSG->aSegs[iSrc].cb -= pSG->aSegs[iDst].cb; + pSG->aSegs[iSrc].pv = (uint8_t *)pSG->aSegs[iSrc].pv + pSG->aSegs[iDst].cb; + } + else if (++iSrc >= pSG->cSegsAlloc) + { + pSG->cSegsUsed = iDst + 1; + break; + } + iDst++; + } + } +#endif + + AssertMsg(!pvFrame, ("pvFrame=%p pMBuf=%p iSeg=%d\n", pvFrame, pMBuf, iSeg)); +} + + +/** + * Helper for determining whether the host wants the interface to be + * promiscuous. + */ +static bool vboxNetFltDarwinIsPromiscuous(PVBOXNETFLTINS pThis) +{ + bool fRc = false; + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + /* gather the data */ + uint16_t fIf = ifnet_flags(pIfNet); + unsigned cPromisc = VBOX_GET_PCOUNT(pIfNet); + bool fSetPromiscuous = ASMAtomicUoReadBool(&pThis->u.s.fSetPromiscuous); + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + + /* calc the return. */ + fRc = (fIf & IFF_PROMISC) + && cPromisc > fSetPromiscuous; + } + return fRc; +} + + + +/** + * + * @see iff_detached_func in the darwin kpi. + */ +static void vboxNetFltDarwinIffDetached(void *pvThis, ifnet_t pIfNet) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + uint64_t NanoTS = RTTimeSystemNanoTS(); + LogFlow(("vboxNetFltDarwinIffDetached: pThis=%p NanoTS=%RU64 (%d)\n", + pThis, NanoTS, RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : -1)); + + Assert(!pThis->fDisconnectedFromHost); + Assert(!pThis->fRediscoveryPending); + + /* + * If we've put it into promiscuous mode, undo that now. If we don't + * the if_pcount will go all wrong when it's replugged. + */ + if (ASMAtomicXchgBool(&pThis->u.s.fSetPromiscuous, false)) + ifnet_set_promiscuous(pIfNet, 0); + + /* + * We carefully take the spinlock and increase the interface reference + * behind it in order to avoid problematic races with the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + int cPromisc = RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : - 1; + + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfNet); + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfFilter); + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + pThis->u.s.fSetPromiscuous = false; + ASMAtomicUoWriteU64(&pThis->NanoTSLastRediscovery, NanoTS); + ASMAtomicUoWriteBool(&pThis->fRediscoveryPending, false); + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + + RTSpinlockRelease(pThis->hSpinlock); + + if (pIfNet) + ifnet_release(pIfNet); + LogRel(("VBoxNetFlt: was detached from '%s' (%d)\n", pThis->szName, cPromisc)); +} + + +/** + * + * @see iff_ioctl_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffIoCtl(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, u_long uCmd, void *pvArg) +{ + RT_NOREF(pIfNet); + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + LogFlow(("vboxNetFltDarwinIffIoCtl: pThis=%p uCmd=%lx\n", pThis, uCmd)); + + /* + * Update fOtherPromiscuous. + */ + /** @todo we'll have to find the offset of if_pcount to get this right! */ + //if (uCmd == SIOCSIFFLAGS) + //{ + // + //} + + /* + * We didn't handle it, continue processing. + */ + NOREF(pThis); + NOREF(eProtocol); + NOREF(uCmd); + NOREF(pvArg); + return EOPNOTSUPP; +} + + +/** + * + * @see iff_event_func in the darwin kpi. + */ +static void vboxNetFltDarwinIffEvent(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, const struct kev_msg *pEvMsg) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvThis; + LogFlow(("vboxNetFltDarwinIffEvent: pThis=%p\n", pThis)); + + NOREF(pThis); + NOREF(pIfNet); + NOREF(eProtocol); + NOREF(pEvMsg); + + /* + * Watch out for the interface going online / offline. + */ + if ( RT_VALID_PTR(pThis) + && RT_VALID_PTR(pEvMsg) + && pEvMsg->vendor_code == KEV_VENDOR_APPLE + && pEvMsg->kev_class == KEV_NETWORK_CLASS + && pEvMsg->kev_subclass == KEV_DL_SUBCLASS) + { + if (pThis->u.s.pIfNet == pIfNet) + { + if (pEvMsg->event_code == KEV_DL_LINK_ON) + { + if (ASMAtomicUoReadBool(&pThis->u.s.fNeedSetPromiscuous)) + { + /* failed to bring it online. */ + errno_t err = ifnet_set_promiscuous(pIfNet, 1); + if (!err) + { + ASMAtomicWriteBool(&pThis->u.s.fSetPromiscuous, true); + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + Log(("vboxNetFltDarwinIffEvent: enabled promiscuous mode on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); + } + else + Log(("vboxNetFltDarwinIffEvent: ifnet_set_promiscuous failed on %s, err=%d (%d)\n", pThis->szName, err, VBOX_GET_PCOUNT(pIfNet))); + } + else if ( ASMAtomicUoReadBool(&pThis->u.s.fSetPromiscuous) + && !(ifnet_flags(pIfNet) & IFF_PROMISC)) + { + /* Try fix the inconsistency. */ + errno_t err = ifnet_set_flags(pIfNet, IFF_PROMISC, IFF_PROMISC); + if (!err) + err = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err && (ifnet_flags(pIfNet) & IFF_PROMISC)) + Log(("vboxNetFltDarwinIffEvent: fixed IFF_PROMISC on %s (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); + else + Log(("vboxNetFltDarwinIffEvent: failed to fix IFF_PROMISC on %s, err=%d flags=%#x (%d)\n", + pThis->szName, err, ifnet_flags(pIfNet), VBOX_GET_PCOUNT(pIfNet))); + } + else + Log(("vboxNetFltDarwinIffEvent: online, '%s'. flags=%#x (%d)\n", pThis->szName, ifnet_flags(pIfNet), VBOX_GET_PCOUNT(pIfNet))); + } + else if (pEvMsg->event_code == KEV_DL_LINK_OFF) + Log(("vboxNetFltDarwinIffEvent: %s goes down (%d)\n", pThis->szName, VBOX_GET_PCOUNT(pIfNet))); +/** @todo KEV_DL_LINK_ADDRESS_CHANGED -> pfnReportMacAddress */ +/** @todo KEV_DL_SIFFLAGS -> pfnReportPromiscuousMode */ + } + else + Log(("vboxNetFltDarwinIffEvent: pThis->u.s.pIfNet=%p pIfNet=%p (%d)\n", pThis->u.s.pIfNet, pIfNet, RT_VALID_PTR(pIfNet) ? VBOX_GET_PCOUNT(pIfNet) : -1)); + } + else if (RT_VALID_PTR(pEvMsg)) + Log(("vboxNetFltDarwinIffEvent: vendor_code=%#x kev_class=%#x kev_subclass=%#x event_code=%#x\n", + pEvMsg->vendor_code, pEvMsg->kev_class, pEvMsg->kev_subclass, pEvMsg->event_code)); +} + + +/** + * Internal worker for vboxNetFltDarwinIffInput and vboxNetFltDarwinIffOutput, + * + * @returns 0 or EJUSTRETURN. + * @param pThis The instance. + * @param pMBuf The mbuf. + * @param pvFrame The start of the frame, optional. + * @param fSrc Where the packet (allegedly) comes from, one INTNETTRUNKDIR_* value. + * @param eProtocol The protocol. + */ +static errno_t vboxNetFltDarwinIffInputOutputWorker(PVBOXNETFLTINS pThis, mbuf_t pMBuf, void *pvFrame, + uint32_t fSrc, protocol_family_t eProtocol) +{ + /* + * Drop it immediately? + */ + Log2(("vboxNetFltDarwinIffInputOutputWorker: pThis=%p pMBuf=%p pvFrame=%p fSrc=%#x cbPkt=%x\n", + pThis, pMBuf, pvFrame, fSrc, pMBuf ? mbuf_pkthdr_len(pMBuf) : -1)); + if (!pMBuf) + return 0; +#if 0 /* debugging lost icmp packets */ + if (mbuf_pkthdr_len(pMBuf) > 0x300) + { + uint8_t *pb = (uint8_t *)(pvFrame ? pvFrame : mbuf_data(pMBuf)); + Log3(("D=%.6Rhxs S=%.6Rhxs T=%04x IFF\n", pb, pb + 6, RT_BE2H_U16(*(uint16_t *)(pb + 12)))); + } +#endif + if (vboxNetFltDarwinMBufIsOur(pThis, pMBuf, pvFrame)) + return 0; + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (!vboxNetFltTryRetainBusyActive(pThis)) + return 0; + + /* + * Finalize out-bound packets since the stack puts off finalizing + * TCP/IP checksums as long as possible. + * ASSUMES this only applies to outbound IP packets. + */ + if (fSrc == INTNETTRUNKDIR_HOST) + { + Assert(!pvFrame); + mbuf_outbound_finalize(pMBuf, eProtocol, sizeof(RTNETETHERHDR)); + } + + /* + * Create a (scatter/)gather list for the mbuf and feed it to the internal network. + */ + bool fDropIt = false; + unsigned cSegs = vboxNetFltDarwinMBufCalcSGSegs(pThis, pMBuf, pvFrame); + if (cSegs < VBOXNETFLT_DARWIN_MAX_SEGS) + { + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltDarwinMBufToSG(pThis, pMBuf, pvFrame, pSG, cSegs, fSrc); + + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + if (fDropIt) + { + /* + * If the interface is in promiscuous mode we should let + * all inbound packets (this one was for a bridged guest) + * reach the driver as it passes them to tap callbacks in + * order for BPF to work properly. + */ + if ( fSrc == INTNETTRUNKDIR_WIRE + && vboxNetFltDarwinIsPromiscuous(pThis)) + { + fDropIt = false; + } + + /* + * A packet from the host to a guest. As we won't pass it + * to the drvier/wire we need to feed it to bpf ourselves. + * + * XXX: TODO: bpf should be done before; use pfnPreRecv? + */ + if (fSrc == INTNETTRUNKDIR_HOST) + { + bpf_tap_out(pThis->u.s.pIfNet, DLT_EN10MB, pMBuf, NULL, 0); + ifnet_stat_increment_out(pThis->u.s.pIfNet, 1, mbuf_len(pMBuf), 0); + } + } + } + + vboxNetFltRelease(pThis, true /* fBusy */); + + if (fDropIt) + { + mbuf_freem(pMBuf); + return EJUSTRETURN; + } + return 0; +} + + +/** + * From the host. + * + * @see iff_output_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffOutput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf) +{ + /** @todo there was some note about the ethernet header here or something like that... */ + + NOREF(eProtocol); + NOREF(pIfNet); + return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, NULL, INTNETTRUNKDIR_HOST, eProtocol); +} + + +/** + * From the wire. + * + * @see iff_input_func in the darwin kpi. + */ +static errno_t vboxNetFltDarwinIffInput(void *pvThis, ifnet_t pIfNet, protocol_family_t eProtocol, mbuf_t *ppMBuf, char **ppchFrame) +{ + RT_NOREF(eProtocol, pIfNet); + return vboxNetFltDarwinIffInputOutputWorker((PVBOXNETFLTINS)pvThis, *ppMBuf, *ppchFrame, INTNETTRUNKDIR_WIRE, eProtocol); +} + + +/** A worker thread for vboxNetFltSendDummy(). */ +static DECLCALLBACK(int) vboxNetFltSendDummyWorker(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + Assert(pvUser); + ifnet_t pIfNet = (ifnet_t)pvUser; + return VBoxNetSendDummy(pIfNet); +} + + +/** + * Prevent GUI icon freeze issue when VirtualBoxVM process terminates. + * + * This function is a workaround for stuck-in-dock issue. The idea here is to + * send a dummy packet to an interface from the context of a kernel thread. + * Therefore, an XNU's receive thread (which is created as a result if we are + * the first who is communicating with the interface) will be associated with + * the kernel thread instead of VirtualBoxVM process. + * + * @param pIfNet Interface to be used to send data. + */ +static void vboxNetFltSendDummy(ifnet_t pIfNet) +{ + RTTHREAD hThread; + int rc = RTThreadCreate(&hThread, vboxNetFltSendDummyWorker, (void *)pIfNet, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "DummyThread"); + if (RT_SUCCESS(rc)) + { + RTThreadWait(hThread, RT_INDEFINITE_WAIT, NULL); + LogFlow(("vboxNetFltSendDummy: a dummy packet has been successfully sent in order to prevent stuck-in-dock issue\n")); + } + else + LogFlow(("vboxNetFltSendDummy: unable to send dummy packet in order to prevent stuck-in-dock issue\n")); +} + + +/** + * Internal worker for vboxNetFltOsInitInstance and vboxNetFltOsMaybeRediscovered. + * + * @returns VBox status code. + * @param pThis The instance. + * @param fRediscovery If set we're doing a rediscovery attempt, so, don't + * flood the release log. + */ +static int vboxNetFltDarwinAttachToInterface(PVBOXNETFLTINS pThis, bool fRediscovery) +{ + LogFlow(("vboxNetFltDarwinAttachToInterface: pThis=%p (%s)\n", pThis, pThis->szName)); + IPRT_DARWIN_SAVE_EFL_AC(); + + /* + * Locate the interface first. + * + * The pIfNet member is updated before iflt_attach is called and used + * to deal with the hypothetical case where someone rips out the + * interface immediately after our iflt_attach call. + */ + ifnet_t pIfNet = NULL; + errno_t err = ifnet_find_by_name(pThis->szName, &pIfNet); + if (err) + { + Assert(err == ENXIO); + if (!fRediscovery) + LogRel(("VBoxFltDrv: failed to find ifnet '%s' (err=%d)\n", pThis->szName, err)); + else + Log(("VBoxFltDrv: failed to find ifnet '%s' (err=%d)\n", pThis->szName, err)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_NOT_FOUND; + } + + AssertCompileMemberAlignment(VBOXNETFLTINS, u.s.pIfNet, ARCH_BITS / 8); + AssertMsg(!((uintptr_t)&pThis->u.s.pIfNet & (ARCH_BITS / 8 - 1)), ("pThis=%p\n", pThis)); + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWritePtr(&pThis->u.s.pIfNet, pIfNet); + RTSpinlockRelease(pThis->hSpinlock); + + /* Adjust g_offIfNetPCount as it varies for different versions of xnu. */ + vboxNetFltDarwinDetectPCountOffset(pIfNet); + + /* Prevent stuck-in-dock issue by associating interface receive thread with kernel thread. */ + vboxNetFltSendDummy(pIfNet); + + /* + * Get the mac address while we still have a valid ifnet reference. + */ + err = ifnet_lladdr_copy_bytes(pIfNet, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); + if (!err) + { + /* + * Try attach the filter. + */ + struct iff_filter RegRec; + RegRec.iff_cookie = pThis; + RegRec.iff_name = "VBoxNetFlt"; + RegRec.iff_protocol = 0; + RegRec.iff_input = vboxNetFltDarwinIffInput; + RegRec.iff_output = vboxNetFltDarwinIffOutput; + RegRec.iff_event = vboxNetFltDarwinIffEvent; + RegRec.iff_ioctl = vboxNetFltDarwinIffIoCtl; + RegRec.iff_detached = vboxNetFltDarwinIffDetached; + interface_filter_t pIfFilter = NULL; + err = iflt_attach(pIfNet, &RegRec, &pIfFilter); + Assert(err || pIfFilter); + + RTSpinlockAcquire(pThis->hSpinlock); + pIfNet = ASMAtomicUoReadPtrT(&pThis->u.s.pIfNet, ifnet_t); + if (pIfNet && !err) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + ASMAtomicUoWritePtr(&pThis->u.s.pIfFilter, pIfFilter); + pIfNet = NULL; /* don't dereference it */ + } + RTSpinlockRelease(pThis->hSpinlock); + + /* Report capabilities. */ + if ( !pIfNet + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); +#if 0 + /* + * XXX: Don't tell SrvIntNetR0 if the interface is + * promiscuous, because there's no code yet to update that + * information and we don't want it stuck, spamming all + * traffic to the host. + */ + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltDarwinIsPromiscuous(pThis)); +#endif + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + } + + /* Release the interface on failure. */ + if (pIfNet) + ifnet_release(pIfNet); + + int rc = RTErrConvertFromErrno(err); + if (RT_SUCCESS(rc)) + LogRel(("VBoxFltDrv: attached to '%s' / %RTmac\n", pThis->szName, &pThis->u.s.MacAddr)); + else + LogRel(("VBoxFltDrv: failed to attach to ifnet '%s' (err=%d)\n", pThis->szName, err)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + vboxNetFltDarwinAttachToInterface(pThis, true /* fRediscovery */); + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + NOREF(pvIfData); + + int rc = VINF_SUCCESS; + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + /* + * Create a mbuf for the gather list and push it onto the wire. + * BPF tap and stats will be taken care of by the driver. + */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + mbuf_t pMBuf = vboxNetFltDarwinMBufFromSG(pThis, pSG); + if (pMBuf) + { + errno_t err = ifnet_output_raw(pIfNet, PF_LINK, pMBuf); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + /* + * Create a mbuf for the gather list and push it onto the host stack. + * BPF tap and stats are on us. + */ + if (fDst & INTNETTRUNKDIR_HOST) + { + mbuf_t pMBuf = vboxNetFltDarwinMBufFromSG(pThis, pSG); + if (pMBuf) + { + void *pvEthHdr = mbuf_data(pMBuf); + unsigned const cbEthHdr = 14; + struct ifnet_stat_increment_param stats; + + RT_ZERO(stats); + stats.packets_in = 1; + stats.bytes_in = mbuf_len(pMBuf); /* full ethernet frame */ + + mbuf_pkthdr_setrcvif(pMBuf, pIfNet); + mbuf_pkthdr_setheader(pMBuf, pvEthHdr); /* link-layer header */ + mbuf_adj(pMBuf, cbEthHdr); /* move to payload */ + +#if 0 /* XXX: disabled since we don't request promiscuous from intnet */ + /* + * TODO: Since intnet knows whether it forwarded us + * this packet because it's for us or because we are + * promiscuous, it can perhaps set a flag for us in + * INTNETSG::fFlags so that we don't have to re-check + * it here. + */ + PCRTNETETHERHDR pcEthHdr = (PCRTNETETHERHDR)pvEthHdr; + if ( (pcEthHdr->DstMac.au8[0] & 1) == 0 /* unicast? */ + && memcmp(&pcEthHdr->DstMac, &pThis->u.s.MacAddr, sizeof(RTMAC)) != 0) + { + mbuf_setflags_mask(pMBuf, MBUF_PROMISC, MBUF_PROMISC); + } +#endif + + bpf_tap_in(pIfNet, DLT_EN10MB, pMBuf, pvEthHdr, cbEthHdr); + errno_t err = ifnet_input(pIfNet, pMBuf, &stats); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + } + + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + ifnet_t pIfNet = vboxNetFltDarwinRetainIfNet(pThis); + if (pIfNet) + { + if (pThis->fDisablePromiscuous) + { + /* + * Promiscuous mode should not be used (wireless), we just need to + * make sure the interface is up. + */ + if (fActive) + { + u_int16_t fIf = ifnet_flags(pIfNet); + if ((fIf & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + { + ifnet_set_flags(pIfNet, IFF_UP, IFF_UP); + ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + } + } + } + else + { + /* + * This api is a bit weird, the best reference is the code. + * + * Also, we have a bit or race conditions wrt the maintenance of + * host the interface promiscuity for vboxNetFltPortOsIsPromiscuous. + */ + unsigned const cPromiscBefore = VBOX_GET_PCOUNT(pIfNet); + u_int16_t fIf; + if (fActive) + { + Assert(!pThis->u.s.fSetPromiscuous); + errno_t err = ENETDOWN; + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, true); + + /* + * Try bring the interface up and running if it's down. + */ + fIf = ifnet_flags(pIfNet); + if ((fIf & (IFF_UP | IFF_RUNNING)) != (IFF_UP | IFF_RUNNING)) + { + err = ifnet_set_flags(pIfNet, IFF_UP, IFF_UP); + errno_t err2 = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err) + err = err2; + fIf = ifnet_flags(pIfNet); + } + + /* + * Is it already up? If it isn't, leave it to the link event or + * we'll upset if_pcount (as stated above, ifnet_set_promiscuous is weird). + */ + if ((fIf & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) + { + err = ifnet_set_promiscuous(pIfNet, 1); + pThis->u.s.fSetPromiscuous = err == 0; + if (!err) + { + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + + /* check if it actually worked, this stuff is not always behaving well. */ + if (!(ifnet_flags(pIfNet) & IFF_PROMISC)) + { + err = ifnet_set_flags(pIfNet, IFF_PROMISC, IFF_PROMISC); + if (!err) + err = ifnet_ioctl(pIfNet, 0, SIOCSIFFLAGS, NULL); + if (!err) + Log(("vboxNetFlt: fixed IFF_PROMISC on %s (%d->%d)\n", pThis->szName, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + else + Log(("VBoxNetFlt: failed to fix IFF_PROMISC on %s, err=%d (%d->%d)\n", + pThis->szName, err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + } + else + Log(("VBoxNetFlt: ifnet_set_promiscuous -> err=%d grr! (%d->%d)\n", err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + else if (!err) + Log(("VBoxNetFlt: Waiting for the link to come up... (%d->%d)\n", cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + if (err) + LogRel(("VBoxNetFlt: Failed to put '%s' into promiscuous mode, err=%d (%d->%d)\n", pThis->szName, err, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + else + { + ASMAtomicWriteBool(&pThis->u.s.fNeedSetPromiscuous, false); + if (pThis->u.s.fSetPromiscuous) + { + errno_t err = ifnet_set_promiscuous(pIfNet, 0); + AssertMsg(!err, ("%d\n", err)); NOREF(err); + } + pThis->u.s.fSetPromiscuous = false; + + fIf = ifnet_flags(pIfNet); + Log(("VBoxNetFlt: fIf=%#x; %d->%d\n", fIf, cPromiscBefore, VBOX_GET_PCOUNT(pIfNet))); + } + } + + vboxNetFltDarwinReleaseIfNet(pThis, pIfNet); + } + IPRT_DARWIN_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + /* Nothing to do here. */ + RT_NOREF(pThis); + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + /* Nothing to do here. */ + RT_NOREF(pThis); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + IPRT_DARWIN_SAVE_EFL_AC(); + + /* + * Carefully obtain the interface filter reference and detach it. + */ + RTSpinlockAcquire(pThis->hSpinlock); + interface_filter_t pIfFilter = ASMAtomicUoReadPtrT(&pThis->u.s.pIfFilter, interface_filter_t); + if (pIfFilter) + ASMAtomicUoWriteNullPtr(&pThis->u.s.pIfFilter); + RTSpinlockRelease(pThis->hSpinlock); + + if (pIfFilter) + iflt_detach(pIfFilter); + + if (pThis->u.s.pSysSock != NULL) + { + RT_GCC_NO_WARN_DEPRECATED_BEGIN + + sock_close(pThis->u.s.pSysSock); + pThis->u.s.pSysSock = NULL; + + RT_GCC_NO_WARN_DEPRECATED_END + } + + IPRT_DARWIN_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + NOREF(pvContext); + + int rc = vboxNetFltDarwinAttachToInterface(pThis, false /* fRediscovery */); + if (RT_FAILURE(rc)) + return rc; + + if (pThis->pSwitchPort->pfnNotifyHostAddress == NULL) + return rc; + + /* + * XXX: uwe + * + * Learn host's IP addresses and set up notifications for changes. + * To avoid racing, set up notifications first. + * + * XXX: This should probably be global, since the only thing + * specific to ifnet here is its IPv6 link-local address. + */ + IPRT_DARWIN_SAVE_EFL_AC(); + errno_t error; + + /** @todo Figure out how to replace the socket stuff we use to detect + * addresses here as 10.5 deprecates it. */ + RT_GCC_NO_WARN_DEPRECATED_BEGIN + + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + /** @todo reorg code to not have numerous returns with duplicate code... */ + + error = sock_socket(PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT, + vboxNetFltDarwinSysSockUpcall, pThis, + &pThis->u.s.pSysSock); + if (error != 0) + { + LogRel(("sock_socket(SYSPROTO_EVENT): error %d\n", error)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + int nbio = 1; + error = sock_ioctl(pThis->u.s.pSysSock, FIONBIO, &nbio); + if (error != 0) + { + LogRel(("FIONBIO: error %d\n", error)); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + if (!sock_isnonblocking(pThis->u.s.pSysSock)) + { + LogRel(("FIONBIO ok, but socket is blocking?!\n")); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + struct kev_request req; + req.vendor_code = KEV_VENDOR_APPLE; + req.kev_class = KEV_NETWORK_CLASS; + req.kev_subclass = KEV_ANY_SUBCLASS; /* need both INET and INET6, so have to request all */ + + error = sock_ioctl(pThis->u.s.pSysSock, SIOCSKEVFILT, &req); + if (error != 0) + { + LogRel(("SIOCSKEVFILT: error %d\n", error)); + sock_close(pThis->u.s.pSysSock); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + RT_GCC_NO_WARN_DEPRECATED_END + + ifnet_t pIfNet = pThis->u.s.pIfNet; /* already retained */ + + ifaddr_t *pIfAddrList; + error = ifnet_get_address_list(/* all interfaces*/ NULL, &pIfAddrList); + if (error != 0) + { + LogRel(("ifnet_get_address_list: error %d\n", error)); + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; + } + + for (ifaddr_t *pIfAddr = pIfAddrList; *pIfAddr != NULL; ++pIfAddr) + { + ifaddr_t ifa = *pIfAddr; + sa_family_t family = ifaddr_address_family(ifa); + struct sockaddr_storage ss; + + error = ifaddr_address(ifa, (struct sockaddr *)&ss, sizeof(ss)); + if (error != 0) + { + LogRel(("getting address family %d: error %d\n", family, error)); + continue; + } + + if (family == AF_INET) + { + struct sockaddr_in *sin = (struct sockaddr_in *)&ss; + u_int32_t u32Addr = ntohl(sin->sin_addr.s_addr); + + if (VBOX_IN_LOOPBACK(u32Addr)) + continue; + + if (ifaddr_ifnet(ifa) != pIfNet && VBOX_IN_LINKLOCAL(u32Addr)) + continue; + + Log(("> inet %RTnaipv4\n", sin->sin_addr.s_addr)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv4, &sin->sin_addr); + } + else if (family == AF_INET6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss; + + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + continue; + + /* link-local from other interfaces are out of scope */ + if (ifaddr_ifnet(ifa) != pIfNet && IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + continue; + + Log(("> inet6 %RTnaipv6\n", &sin6->sin6_addr)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv6, &sin6->sin6_addr); + } + } + + ifnet_free_address_list(pIfAddrList); + + /* + * Now that we've got current addresses, check for events that + * might have happened while we were working. + */ + vboxNetFltDarwinSysSockUpcall(pThis->u.s.pSysSock, pThis, MBUF_DONTWAIT); + + IPRT_DARWIN_RESTORE_EFL_AC(); + return rc; +} + + +static void vboxNetFltDarwinSysSockUpcall(socket_t pSysSock, void *pvData, int fWait) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; + errno_t error; + + NOREF(fWait); + + if (RT_UNLIKELY(pSysSock != pThis->u.s.pSysSock)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %p != %p?\n", pSysSock, pThis->u.s.pSysSock)); + return; + } + + ifnet_t pIfNet = pThis->u.s.pIfNet; /* XXX: retain? */ + ifnet_family_t if_family = ifnet_family(pIfNet); + u_int32_t if_unit = ifnet_unit(pIfNet); + + for (;;) + { + mbuf_t m; + size_t len = sizeof(struct kern_event_msg) - sizeof(u_int32_t) + sizeof(struct kev_in6_data); + + RT_GCC_NO_WARN_DEPRECATED_BEGIN + error = sock_receivembuf(pSysSock, NULL, &m, 0, &len); + RT_GCC_NO_WARN_DEPRECATED_END + if (error != 0) + { + if (error == EWOULDBLOCK) + { + Log(("vboxNetFltDarwinSysSockUpcall: EWOULDBLOCK - we are done\n")); + error = 0; + } + else + Log(("sock_receivembuf: error %d\n", error)); + break; + } + + if (len < sizeof(struct kern_event_msg) - sizeof(u_int32_t)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short\n", (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kern_event_msg *msg = (struct kern_event_msg *)mbuf_data(m); + if (msg->kev_subclass == KEV_INET_SUBCLASS) + { + if (len - (sizeof(struct kern_event_msg) - sizeof(u_int32_t)) < sizeof(struct kev_in_data)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short for KEV_INET_SUBCLASS\n", (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kev_in_data *iev = (struct kev_in_data *)msg->event_data; + struct net_event_data *link = &iev->link_data; + PCRTNETADDRU pAddr = (PCRTNETADDRU)&iev->ia_addr; + u_int32_t u32Addr = ntohl(pAddr->IPv4.u); + + if (VBOX_IN_LOOPBACK(u32Addr)) + { + mbuf_freem(m); + continue; + } + + if ( (link->if_family != if_family || link->if_unit != if_unit) + && VBOX_IN_LINKLOCAL(u32Addr)) + { + mbuf_freem(m); + continue; + } + + switch (msg->event_code) + { + case KEV_INET_NEW_ADDR: + Log(("KEV_INET_NEW_ADDR %.*s%d: %RTnaipv4\n", IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, true /*fAdded*/, kIntNetAddrType_IPv4, pAddr); + break; + + case KEV_INET_ADDR_DELETED: + Log(("KEV_INET_ADDR_DELETED %.*s%d: %RTnaipv4\n", IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, false /*fAdded*/, kIntNetAddrType_IPv4, pAddr); + break; + + default: + Log(("KEV INET event %u %.*s%d: addr %RTnaipv4\n", + msg->event_code, IFNAMSIZ, link->if_name, link->if_unit, pAddr->IPv4.u)); + break; + } + } + else if (msg->kev_subclass == KEV_INET6_SUBCLASS) + { + if (len - (sizeof(struct kern_event_msg) - sizeof(u_int32_t)) < sizeof(struct kev_in6_data)) + { + Log(("vboxNetFltDarwinSysSockUpcall: %u bytes is too short for KEV_INET6_SUBCLASS\n", + (unsigned int)len)); + mbuf_freem(m); + return; + } + + struct kev_in6_data *iev6 = (struct kev_in6_data *)msg->event_data; + struct net_event_data *link = &iev6->link_data; + PCRTNETADDRU pAddr = (PCRTNETADDRU)&iev6->ia_addr.sin6_addr; + + if (IN6_IS_ADDR_LOOPBACK(&iev6->ia_addr.sin6_addr)) + { + mbuf_freem(m); + continue; + } + + if ( (link->if_family != if_family || link->if_unit != if_unit) + && IN6_IS_ADDR_LINKLOCAL(&iev6->ia_addr.sin6_addr)) + { + mbuf_freem(m); + continue; + } + + switch (msg->event_code) + { + case KEV_INET6_NEW_USER_ADDR: + Log(("KEV_INET6_NEW_USER_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + case KEV_INET6_NEW_LL_ADDR: + Log(("KEV_INET6_NEW_LL_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + case KEV_INET6_NEW_RTADV_ADDR: + Log(("KEV_INET6_NEW_RTADV_ADDR %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + goto kev_inet6_new; + + kev_inet6_new: + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, true /*fAdded*/, kIntNetAddrType_IPv6, pAddr); + break; + + case KEV_INET6_ADDR_DELETED: + Log(("KEV_INET6_ADDR_DELETED %.*s%d: %RTnaipv6\n", + IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, false /*fAdded*/, kIntNetAddrType_IPv6, pAddr); + break; + + default: + Log(("KEV INET6 event %u %.*s%d: addr %RTnaipv6\n", + msg->event_code, IFNAMSIZ, link->if_name, link->if_unit, pAddr)); + break; + } + } + else + Log(("vboxNetFltDarwinSysSockUpcall: subclass %u ignored\n", (unsigned)msg->kev_subclass)); + + mbuf_freem(m); + } +} + + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + /* + * Init the darwin specific members. + */ + pThis->u.s.pIfNet = NULL; + pThis->u.s.pIfFilter = NULL; + pThis->u.s.fSetPromiscuous = false; + pThis->u.s.fNeedSetPromiscuous = false; + //pThis->u.s.MacAddr = {0}; + pThis->u.s.pSysSock = NULL; + + return VINF_SUCCESS; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + NOREF(pThis); NOREF(pvIfData); NOREF(pMac); +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIf); NOREF(ppvIfData); + return VINF_SUCCESS; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIfData); + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh b/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh new file mode 100755 index 00000000..0fede55c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/darwin/loadnetflt.sh @@ -0,0 +1,133 @@ +#!/bin/bash +## @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 . +# +# 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 +# + +SCRIPT_NAME="loadnetflt" +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` + +DRVNAME="VBoxNetFlt.kext" +BUNDLE="org.virtualbox.kext.VBoxNetFlt" + +DEP_DRVNAME="VBoxDrv.kext" +DEP_BUNDLE="org.virtualbox.kext.VBoxDrv" + + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DEP_DIR="$DIR/$DEP_DRVNAME" +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ ! -d "$DEP_DIR" ]; then + echo "Cannot find $DEP_DIR or it's not a directory... (dependency)" + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +trap "sudo chown -R `whoami` $DIR $DEP_DIR; exit 1" INT + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "${SCRIPT_NAME}.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 "${SCRIPT_NAME}.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 + +# 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" "$DEP_DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/${SCRIPT_NAME}.tmp + echo "${SCRIPT_NAME}.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" + + # load.sh puts it here. + DEP_DIR="/tmp/loaddrv.tmp/$DEP_DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" "$DEP_DIR" +fi + +sudo chmod -R o-rwx "$DIR" +sync +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextutil $OPTS -d "$DEP_DIR" "$DIR" +else + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextload $OPTS -d \"$DEP_DIR\" \"$DIR\")" +sudo kextload $OPTS -d "$DEP_DIR" "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" "$DEP_DIR" +kextstat | grep org.virtualbox.kext + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile new file mode 100644 index 00000000..596f80a0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/Makefile @@ -0,0 +1,57 @@ +# $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 . +# +# 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 = vboxnetflt + +CFLAGS += -DRT_OS_FREEBSD -DIN_RING0 -DIN_RT_R0 -DIN_SUP_R0 -DVBOX -DRT_WITH_VBOX -Iinclude -I. -Ir0drv -w -DVBOX_WITH_HARDENING -DVIMAGE + +.if (${MACHINE_ARCH} == "i386") + CFLAGS += -DRT_ARCH_X86 +.elif (${MACHINE_ARCH} == "amd64") + CFLAGS += -DRT_ARCH_AMD64 +.endif + +SRCS = \ + VBoxNetFlt-freebsd.c \ + VBoxNetFlt.c \ + SUPR0IdcClient-freebsd.c \ + SUPR0IdcClient.c \ + SUPR0IdcClientComponent.c + +SRCS += device_if.h bus_if.h opt_netgraph.h + +.include + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c new file mode 100644 index 00000000..b8b4212e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c @@ -0,0 +1,817 @@ +/* $Id: VBoxNetFlt-freebsd.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), FreeBSD Specific Code. + */ + +/* + * Copyright (c) 2009 Fredrik Lindberg + * + * 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 * +*********************************************************************************************************************************/ +#include +#undef PVM +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + +static int vboxnetflt_modevent(struct module *, int, void *); +static ng_constructor_t ng_vboxnetflt_constructor; +static ng_rcvmsg_t ng_vboxnetflt_rcvmsg; +static ng_shutdown_t ng_vboxnetflt_shutdown; +static ng_newhook_t ng_vboxnetflt_newhook; +static ng_rcvdata_t ng_vboxnetflt_rcvdata; +static ng_disconnect_t ng_vboxnetflt_disconnect; +static int ng_vboxnetflt_mod_event(module_t mod, int event, void *data); + +/** Netgraph node type */ +#define NG_VBOXNETFLT_NODE_TYPE "vboxnetflt" +/** Netgraph message cookie */ +#define NGM_VBOXNETFLT_COOKIE 0x56424f58 + +/** Input netgraph hook name */ +#define NG_VBOXNETFLT_HOOK_IN "input" +/** Output netgraph hook name */ +#define NG_VBOXNETFLT_HOOK_OUT "output" + +/** mbuf tag identifier */ +#define MTAG_VBOX 0x56424f58 +/** mbuf packet tag */ +#define PACKET_TAG_VBOX 128 + +#if defined(__FreeBSD_version) && __FreeBSD_version >= 800500 +# include +# include + +# define VBOXCURVNET_SET(arg) CURVNET_SET_QUIET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() VBOXCURVNET_SET(CRED_TO_VNET(curthread->td_ucred)) +# define VBOXCURVNET_RESTORE() CURVNET_RESTORE() + +#else /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +# define VBOXCURVNET_SET(arg) +# define VBOXCURVNET_SET_FROM_UCRED() +# define VBOXCURVNET_RESTORE() + +#endif /* !defined(__FreeBSD_version) || __FreeBSD_version < 800500 */ + +/* + * Netgraph command list, we don't support any + * additional commands. + */ +static const struct ng_cmdlist ng_vboxnetflt_cmdlist[] = +{ + { 0 } +}; + +/* + * Netgraph type definition + */ +static struct ng_type ng_vboxnetflt_typestruct = +{ + .version = NG_ABI_VERSION, + .name = NG_VBOXNETFLT_NODE_TYPE, + .mod_event = vboxnetflt_modevent, + .constructor= ng_vboxnetflt_constructor, + .rcvmsg = ng_vboxnetflt_rcvmsg, + .shutdown = ng_vboxnetflt_shutdown, + .newhook = ng_vboxnetflt_newhook, + .rcvdata = ng_vboxnetflt_rcvdata, + .disconnect = ng_vboxnetflt_disconnect, + .cmdlist = ng_vboxnetflt_cmdlist, +}; +NETGRAPH_INIT(vboxnetflt, &ng_vboxnetflt_typestruct); + +/* + * Use vboxnetflt because the kernel module is named vboxnetflt and vboxnetadp + * depends on this when loading dependencies. + * NETGRAP_INIT will prefix the given name with ng_ so MODULE_DEPEND needs the + * prefixed name. + */ +MODULE_VERSION(vboxnetflt, 1); +MODULE_DEPEND(ng_vboxnetflt, vboxdrv, 1, 1, 1); + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +/** + * Module event handler, called from netgraph subsystem. + */ +static int vboxnetflt_modevent(struct module *pMod, int enmEventType, void *pvArg) +{ + int rc; + + Log(("VBoxNetFltFreeBSDModuleEvent\n")); + + switch (enmEventType) + { + case MOD_LOAD: + rc = RTR0Init(0); + if (RT_FAILURE(rc)) + { + printf("RTR0Init failed %d\n", rc); + return RTErrConvertToErrno(rc); + } + + memset(&g_VBoxNetFltGlobals, 0, sizeof(VBOXNETFLTGLOBALS)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_FAILURE(rc)) + { + printf("vboxNetFltInitGlobalsAndIdc failed %d\n", rc); + return RTErrConvertToErrno(rc); + } + /* No MODULE_VERSION in ng_ether so we can't MODULE_DEPEND it */ + kern_kldload(curthread, "ng_ether", NULL); + break; + + case MOD_UNLOAD: + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + memset(&g_VBoxNetFltGlobals, 0, sizeof(VBOXNETFLTGLOBALS)); + RTR0Term(); + break; + + case MOD_SHUTDOWN: + case MOD_QUIESCE: + default: + return EOPNOTSUPP; + } + + if (RT_SUCCESS(rc)) + return 0; + return RTErrConvertToErrno(rc); +} + +/* + * Convert from mbufs to vbox scatter-gather data structure + */ +static void vboxNetFltFreeBSDMBufToSG(PVBOXNETFLTINS pThis, struct mbuf *m, PINTNETSG pSG, + unsigned int cSegs, unsigned int segOffset) +{ + static uint8_t const s_abZero[128] = {0}; + unsigned int i; + struct mbuf *m0; + + IntNetSgInitTempSegs(pSG, m_length(m, NULL), cSegs, 0 /*cSegsUsed*/); + + for (m0 = m, i = segOffset; m0; m0 = m0->m_next) + { + if (m0->m_len == 0) + continue; + + pSG->aSegs[i].cb = m0->m_len; + pSG->aSegs[i].pv = mtod(m0, uint8_t *); + pSG->aSegs[i].Phys = NIL_RTHCPHYS; + i++; + } + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (pSG->cbTotal < 60) + { + pSG->aSegs[i].Phys = NIL_RTHCPHYS; + pSG->aSegs[i].pv = (void *)&s_abZero[0]; + pSG->aSegs[i].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + i++; + } +#endif + + pSG->cSegsUsed = i; +} + +/* + * Convert to mbufs from vbox scatter-gather data structure + */ +static struct mbuf * vboxNetFltFreeBSDSGMBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG) +{ + struct mbuf *m; + int error; + unsigned int i; + + if (pSG->cbTotal == 0) + return (NULL); + + m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR); + if (m == NULL) + return (NULL); + + m->m_pkthdr.len = m->m_len = 0; + m->m_pkthdr.rcvif = NULL; + + for (i = 0; i < pSG->cSegsUsed; i++) + { + error = m_append(m, pSG->aSegs[i].cb, pSG->aSegs[i].pv); + if (error == 0) + { + m_freem(m); + return (NULL); + } + } + return (m); +} + + +static int ng_vboxnetflt_constructor(node_p node) +{ + /* Nothing to do */ + return (EINVAL); +} + +/* + * Setup netgraph hooks + */ +static int ng_vboxnetflt_newhook(node_p node, hook_p hook, const char *name) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + + if (strcmp(name, NG_VBOXNETFLT_HOOK_IN) == 0) + { +#if __FreeBSD_version >= 800000 + NG_HOOK_SET_TO_INBOUND(hook); +#endif + pThis->u.s.input = hook; + } + else if (strcmp(name, NG_VBOXNETFLT_HOOK_OUT) == 0) + { + pThis->u.s.output = hook; + } + else + return (EINVAL); + + NG_HOOK_HI_STACK(hook); + return (0); +} + +/** + * Netgraph message processing for node specific messages. + * We don't accept any special messages so this is not used. + */ +static int ng_vboxnetflt_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + struct ng_mesg *msg; + int error = 0; + + NGI_GET_MSG(item, msg); + if (msg->header.typecookie != NGM_VBOXNETFLT_COOKIE) + return (EINVAL); + + switch (msg->header.cmd) + { + default: + error = EINVAL; + } + return (error); +} + +/** + * Handle data on netgraph hooks. + * Frames processing is deferred to a taskqueue because this might + * be called with non-sleepable locks held and code paths inside + * the virtual switch might sleep. + */ +static int ng_vboxnetflt_rcvdata(hook_p hook, item_p item) +{ + const node_p node = NG_HOOK_NODE(hook); + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + struct ifnet *ifp = pThis->u.s.ifp; + struct mbuf *m; + struct m_tag *mtag; + bool fActive; + + VBOXCURVNET_SET(ifp->if_vnet); + fActive = vboxNetFltTryRetainBusyActive(pThis); + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* Locate tag to see if processing should be skipped for this frame */ + mtag = m_tag_locate(m, MTAG_VBOX, PACKET_TAG_VBOX, NULL); + if (mtag != NULL) + { + m_tag_unlink(m, mtag); + m_tag_free(mtag); + } + + /* + * Handle incoming hook. This is connected to the + * input path of the interface, thus handling incoming frames. + */ + if (pThis->u.s.input == hook) + { + if (mtag != NULL || !fActive) + { + ether_demux(ifp, m); + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return (0); + } + mtx_lock_spin(&pThis->u.s.inq.ifq_mtx); + _IF_ENQUEUE(&pThis->u.s.inq, m); + mtx_unlock_spin(&pThis->u.s.inq.ifq_mtx); +#if __FreeBSD_version > 1100100 + taskqueue_enqueue(taskqueue_fast, &pThis->u.s.tskin); +#else + taskqueue_enqueue_fast(taskqueue_fast, &pThis->u.s.tskin); +#endif + } + /* + * Handle mbufs on the outgoing hook, frames going to the interface + */ + else if (pThis->u.s.output == hook) + { + if (mtag != NULL || !fActive) + { + int rc = ether_output_frame(ifp, m); + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return rc; + } + mtx_lock_spin(&pThis->u.s.outq.ifq_mtx); + _IF_ENQUEUE(&pThis->u.s.outq, m); + mtx_unlock_spin(&pThis->u.s.outq.ifq_mtx); +#if __FreeBSD_version > 1100100 + taskqueue_enqueue(taskqueue_fast, &pThis->u.s.tskout); +#else + taskqueue_enqueue_fast(taskqueue_fast, &pThis->u.s.tskout); +#endif + } + else + { + m_freem(m); + } + + if (fActive) + vboxNetFltRelease(pThis, true /*fBusy*/); + VBOXCURVNET_RESTORE(); + return (0); +} + +static int ng_vboxnetflt_shutdown(node_p node) +{ + PVBOXNETFLTINS pThis = NG_NODE_PRIVATE(node); + bool fActive; + + /* Prevent node shutdown if we're active */ + if (pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE) + return (EBUSY); + NG_NODE_UNREF(node); + return (0); +} + +static int ng_vboxnetflt_disconnect(hook_p hook) +{ + return (0); +} + +/** + * Input processing task, handles incoming frames + */ +static void vboxNetFltFreeBSDinput(void *arg, int pending) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)arg; + struct mbuf *m, *m0; + struct ifnet *ifp = pThis->u.s.ifp; + unsigned int cSegs = 0; + bool fDropIt = false, fActive; + PINTNETSG pSG; + + VBOXCURVNET_SET(ifp->if_vnet); + vboxNetFltRetain(pThis, true /* fBusy */); + for (;;) + { + mtx_lock_spin(&pThis->u.s.inq.ifq_mtx); + _IF_DEQUEUE(&pThis->u.s.inq, m); + mtx_unlock_spin(&pThis->u.s.inq.ifq_mtx); + if (m == NULL) + break; + + for (m0 = m; m0 != NULL; m0 = m0->m_next) + if (m0->m_len > 0) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (m_length(m, NULL) < 60) + cSegs++; +#endif + + /* Create a copy and deliver to the virtual switch */ + pSG = RTMemTmpAlloc(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltFreeBSDMBufToSG(pThis, m, pSG, cSegs, 0); + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_WIRE); + RTMemTmpFree(pSG); + if (fDropIt) + m_freem(m); + else + ether_demux(ifp, m); + } + vboxNetFltRelease(pThis, true /* fBusy */); + VBOXCURVNET_RESTORE(); +} + +/** + * Output processing task, handles outgoing frames + */ +static void vboxNetFltFreeBSDoutput(void *arg, int pending) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)arg; + struct mbuf *m, *m0; + struct ifnet *ifp = pThis->u.s.ifp; + unsigned int cSegs = 0; + bool fDropIt = false, fActive; + PINTNETSG pSG; + + VBOXCURVNET_SET(ifp->if_vnet); + vboxNetFltRetain(pThis, true /* fBusy */); + for (;;) + { + mtx_lock_spin(&pThis->u.s.outq.ifq_mtx); + _IF_DEQUEUE(&pThis->u.s.outq, m); + mtx_unlock_spin(&pThis->u.s.outq.ifq_mtx); + if (m == NULL) + break; + + for (m0 = m; m0 != NULL; m0 = m0->m_next) + if (m0->m_len > 0) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (m_length(m, NULL) < 60) + cSegs++; +#endif + /* Create a copy and deliver to the virtual switch */ + pSG = RTMemTmpAlloc(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + vboxNetFltFreeBSDMBufToSG(pThis, m, pSG, cSegs, 0); + fDropIt = pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_HOST); + RTMemTmpFree(pSG); + + if (fDropIt) + m_freem(m); + else + ether_output_frame(ifp, m); + } + vboxNetFltRelease(pThis, true /* fBusy */); + VBOXCURVNET_RESTORE(); +} + +/** + * Called to deliver a frame to either the host, the wire or both. + */ +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + NOREF(pvIfData); + + void (*input_f)(struct ifnet *, struct mbuf *); + struct ifnet *ifp; + struct mbuf *m; + struct m_tag *mtag; + bool fActive; + int error; + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + + if (fDst & INTNETTRUNKDIR_WIRE) + { + m = vboxNetFltFreeBSDSGMBufFromSG(pThis, pSG); + if (m == NULL) + return VERR_NO_MEMORY; + m = m_pullup(m, ETHER_HDR_LEN); + if (m == NULL) + return VERR_NO_MEMORY; + + m->m_flags |= M_PKTHDR; + ether_output_frame(ifp, m); + } + + if (fDst & INTNETTRUNKDIR_HOST) + { + m = vboxNetFltFreeBSDSGMBufFromSG(pThis, pSG); + if (m == NULL) + return VERR_NO_MEMORY; + m = m_pullup(m, ETHER_HDR_LEN); + if (m == NULL) + return VERR_NO_MEMORY; + /* + * Delivering packets to the host will be captured by the + * input hook. Tag the packet with a mbuf tag so that we + * can skip re-delivery of the packet to the guest during + * input hook processing. + */ + mtag = m_tag_alloc(MTAG_VBOX, PACKET_TAG_VBOX, 0, M_NOWAIT); + if (mtag == NULL) + { + m_freem(m); + return VERR_NO_MEMORY; + } + + m_tag_init(m); + m_tag_prepend(m, mtag); + m->m_flags |= M_PKTHDR; + m->m_pkthdr.rcvif = ifp; + ifp->if_input(ifp, m); + } + VBOXCURVNET_RESTORE(); + return VINF_SUCCESS; +} + +static bool vboxNetFltFreeBsdIsPromiscuous(PVBOXNETFLTINS pThis) +{ + /** @todo This isn't taking into account that we put the interface in + * promiscuous mode. */ + return (pThis->u.s.flags & IFF_PROMISC) ? true : false; +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + char nam[NG_NODESIZ]; + struct ifnet *ifp; + node_p node; + + VBOXCURVNET_SET_FROM_UCRED(); + NOREF(pvContext); + ifp = ifunit(pThis->szName); + if (ifp == NULL) + return VERR_INTNET_FLT_IF_NOT_FOUND; + + /* Create a new netgraph node for this instance */ + if (ng_make_node_common(&ng_vboxnetflt_typestruct, &node) != 0) + return VERR_INTERNAL_ERROR; + + RTSpinlockAcquire(pThis->hSpinlock); + + ASMAtomicUoWritePtr(&pThis->u.s.ifp, ifp); + pThis->u.s.node = node; + bcopy(IF_LLADDR(ifp), &pThis->u.s.MacAddr, ETHER_ADDR_LEN); + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + + /* Initialize deferred input queue */ + bzero(&pThis->u.s.inq, sizeof(struct ifqueue)); + mtx_init(&pThis->u.s.inq.ifq_mtx, "vboxnetflt inq", NULL, MTX_SPIN); + TASK_INIT(&pThis->u.s.tskin, 0, vboxNetFltFreeBSDinput, pThis); + + /* Initialize deferred output queue */ + bzero(&pThis->u.s.outq, sizeof(struct ifqueue)); + mtx_init(&pThis->u.s.outq.ifq_mtx, "vboxnetflt outq", NULL, MTX_SPIN); + TASK_INIT(&pThis->u.s.tskout, 0, vboxNetFltFreeBSDoutput, pThis); + + RTSpinlockRelease(pThis->hSpinlock); + + NG_NODE_SET_PRIVATE(node, pThis); + + /* Attempt to name it vboxnetflt_ */ + snprintf(nam, NG_NODESIZ, "vboxnetflt_%s", pThis->szName); + ng_name_node(node, nam); + + /* Report MAC address, promiscuous mode and GSO capabilities. */ + /** @todo keep these reports up to date, either by polling for changes or + * intercept some control flow if possible. */ + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltFreeBsdIsPromiscuous(pThis)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + VBOXCURVNET_RESTORE(); + + return VINF_SUCCESS; +} + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + struct ifnet *ifp, *ifp0; + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + /* + * Attempt to check if the interface is still there and re-initialize if + * something has changed. + */ + ifp0 = ifunit(pThis->szName); + if (ifp != ifp0) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, true); + ng_rmnode_self(pThis->u.s.node); + pThis->u.s.node = NULL; + } + VBOXCURVNET_RESTORE(); + + if (ifp0 != NULL) + { + vboxNetFltOsDeleteInstance(pThis); + vboxNetFltOsInitInstance(pThis, NULL); + } + + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + + taskqueue_drain(taskqueue_fast, &pThis->u.s.tskin); + taskqueue_drain(taskqueue_fast, &pThis->u.s.tskout); + + mtx_destroy(&pThis->u.s.inq.ifq_mtx); + mtx_destroy(&pThis->u.s.outq.ifq_mtx); + + VBOXCURVNET_SET_FROM_UCRED(); + if (pThis->u.s.node != NULL) + ng_rmnode_self(pThis->u.s.node); + VBOXCURVNET_RESTORE(); + pThis->u.s.node = NULL; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + + pThis->u.s.ifp = NULL; + pThis->u.s.flags = 0; + pThis->u.s.node = NULL; + return VINF_SUCCESS; +} + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + struct ifnet *ifp; + struct ifreq ifreq; + int error; + node_p node; + struct ng_mesg *msg; + struct ngm_connect *con; + struct ngm_rmhook *rm; + char path[NG_PATHSIZ]; + + Log(("%s: fActive:%d\n", __func__, fActive)); + + ifp = ASMAtomicUoReadPtrT(&pThis->u.s.ifp, struct ifnet *); + VBOXCURVNET_SET(ifp->if_vnet); + node = ASMAtomicUoReadPtrT(&pThis->u.s.node, node_p); + + memset(&ifreq, 0, sizeof(struct ifreq)); + /* Activate interface */ + if (fActive) + { + pThis->u.s.flags = ifp->if_flags; + ifpromisc(ifp, 1); + + /* ng_ether nodes are named after the interface name */ + snprintf(path, sizeof(path), "%s:", ifp->if_xname); + + /* + * Send a netgraph connect message to the ng_ether node + * assigned to the bridged interface. Connecting + * the hooks 'lower' (ng_ether) to out 'input'. + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_CONNECT, + sizeof(struct ngm_connect), M_NOWAIT); + if (msg == NULL) + return; + con = (struct ngm_connect *)msg->data; + snprintf(con->path, NG_PATHSIZ, "vboxnetflt_%s:", ifp->if_xname); + strlcpy(con->ourhook, "lower", NG_HOOKSIZ); + strlcpy(con->peerhook, "input", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + + /* + * Do the same for the hooks 'upper' (ng_ether) and our + * 'output' hook. + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_CONNECT, + sizeof(struct ngm_connect), M_NOWAIT); + if (msg == NULL) + return; + con = (struct ngm_connect *)msg->data; + snprintf(con->path, NG_PATHSIZ, "vboxnetflt_%s:", + ifp->if_xname); + strlcpy(con->ourhook, "upper", sizeof(con->ourhook)); + strlcpy(con->peerhook, "output", sizeof(con->peerhook)); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + } + else + { + /* De-activate interface */ + pThis->u.s.flags = 0; + ifpromisc(ifp, 0); + + /* Disconnect msgs are addressed to ourself */ + snprintf(path, sizeof(path), "vboxnetflt_%s:", ifp->if_xname); + + /* + * Send a netgraph message to disconnect our 'input' hook + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_RMHOOK, + sizeof(struct ngm_rmhook), M_NOWAIT); + if (msg == NULL) + return; + rm = (struct ngm_rmhook *)msg->data; + strlcpy(rm->ourhook, "input", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + + /* + * Send a netgraph message to disconnect our 'output' hook + */ + NG_MKMESSAGE(msg, NGM_GENERIC_COOKIE, NGM_RMHOOK, + sizeof(struct ngm_rmhook), M_NOWAIT); + if (msg == NULL) + return; + rm = (struct ngm_rmhook *)msg->data; + strlcpy(rm->ourhook, "output", NG_HOOKSIZ); + NG_SEND_MSG_PATH(error, node, msg, path, 0); + } + VBOXCURVNET_RESTORE(); +} + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + return VINF_SUCCESS; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + return VINF_SUCCESS; +} + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + NOREF(pThis); NOREF(pvIfData); NOREF(pMac); +} + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIf); NOREF(ppvIfData); + return VINF_SUCCESS; +} + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIfData); + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt new file mode 100755 index 00000000..602b72e4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/freebsd/files_vboxnetflt @@ -0,0 +1,99 @@ +#!/bin/sh +# $Id: files_vboxnetflt $ +## @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 . +# +# 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 +# + +VBOX_VBOXNETFLT_SOURCES=" \ + ${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/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/heap.h=>include/iprt/heap.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/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/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/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-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/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/intnetinline.h=>include/VBox/intnetinline.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.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}/src/VBox/HostDrivers/VBoxNetFlt/freebsd/VBoxNetFlt-freebsd.c=>VBoxNetFlt-freebsd.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c=>VBoxNetFlt.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h=>VBoxNetFltInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClient.c=>SUPR0IdcClient.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c=>SUPR0IdcClientComponent.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h=>SUPR0IdcClientInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/freebsd/SUPR0IdcClient-freebsd.c=>SUPR0IdcClient-freebsd.c \ + ${PATH_ROOT}/src/VBox/Runtime/r0drv/freebsd/the-freebsd-kernel.h=>r0drv/freebsd/the-freebsd-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile new file mode 100644 index 00000000..9c5d5b80 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile @@ -0,0 +1,81 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox Linux Host Network Filter 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 . +# +# 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 +VBOXNETFLT_DIR := $(VBOX_MODULE_SRC_DIR) + +# Allow building directly from the subdirectory without assuming the toplevel +# makefile has done the copying. Not the default use case, but can be handy. +ifndef KBUILD_EXTRA_SYMBOLS +KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXNETFLT_DIR)/../vboxdrv/Module.symvers) +endif + +VBOXMOD_NAME = vboxnetflt +VBOXMOD_OBJS = \ + linux/VBoxNetFlt-linux.o \ + VBoxNetFlt.o \ + SUPR0IdcClient.o \ + SUPR0IdcClientComponent.o \ + linux/SUPR0IdcClient-linux.o +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + math/gcc/divdi3.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 +VBOXMOD_INCL = \ + $(VBOXNETFLT_DIR) \ + $(VBOXNETFLT_DIR)include \ + $(VBOXNETFLT_DIR)r0drv/linux +VBOXMOD_DEFS = \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + RT_WITH_VBOX \ + VBOX_WITH_HARDENING \ + VBOX_WITH_64_BITS_GUESTS # <-- must be consistent with Config.kmk! +VBOXMOD_CFLAGS = -include $(VBOXNETFLT_DIR)include/VBox/SUPDrvMangling.h -fno-pie -Wno-declaration-after-statement + +include $(obj)/Makefile-footer.gmk + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c b/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c new file mode 100644 index 00000000..eaf07e8c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c @@ -0,0 +1,2609 @@ +/* $Id: VBoxNetFlt-linux.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), Linux 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 . + * + * 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_NET_FLT_DRV +#define VBOXNETFLT_LINUX_NO_XMIT_QUEUE +#include "the-linux-kernel.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" +#if RTLNX_VER_MIN(2,6,24) +# include +#endif +#if RTLNX_VER_MIN(6,4,10) || RTLNX_RHEL_RANGE(9,4, 9,99) +# include +#endif +#include +#if RTLNX_VER_MAX(2,6,29) || RTLNX_VER_MIN(5,11,0) +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#if RTLNX_VER_MIN(4,5,0) +# include +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + +typedef struct VBOXNETFLTNOTIFIER { + struct notifier_block Notifier; + PVBOXNETFLTINS pThis; +} VBOXNETFLTNOTIFIER; +typedef struct VBOXNETFLTNOTIFIER *PVBOXNETFLTNOTIFIER; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_FLT_NB_TO_INST(pNB) RT_FROM_MEMBER(pNB, VBOXNETFLTINS, u.s.Notifier) +#define VBOX_FLT_PT_TO_INST(pPT) RT_FROM_MEMBER(pPT, VBOXNETFLTINS, u.s.PacketType) +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE +# define VBOX_FLT_XT_TO_INST(pXT) RT_FROM_MEMBER(pXT, VBOXNETFLTINS, u.s.XmitTask) +#endif + +#if RTLNX_VER_MIN(3,11,0) +# define VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr) netdev_notifier_info_to_dev(ptr) +#else +# define VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr) ((struct net_device *)ptr) +#endif + +#if RTLNX_VER_MIN(3,5,0) +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(skb_frag_page(frag)) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr) +#else +# if RTLNX_VER_MIN(3,2,0) +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(skb_frag_page(frag), KM_SKB_DATA_SOFTIRQ) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr, KM_SKB_DATA_SOFTIRQ) +# else +# define VBOX_SKB_KMAP_FRAG(frag) kmap_atomic(frag->page, KM_SKB_DATA_SOFTIRQ) +# define VBOX_SKB_KUNMAP_FRAG(vaddr) kunmap_atomic(vaddr, KM_SKB_DATA_SOFTIRQ) +# endif +#endif + +#if RTLNX_VER_MIN(2,6,34) +# define VBOX_NETDEV_NAME(dev) netdev_name(dev) +#else +# define VBOX_NETDEV_NAME(dev) ((dev)->reg_state != NETREG_REGISTERED ? "(unregistered net_device)" : (dev)->name) +#endif + +#if RTLNX_VER_MIN(2,6,25) +# define VBOX_IPV4_IS_LOOPBACK(addr) ipv4_is_loopback(addr) +# define VBOX_IPV4_IS_LINKLOCAL_169(addr) ipv4_is_linklocal_169(addr) +#else +# define VBOX_IPV4_IS_LOOPBACK(addr) ((addr & htonl(IN_CLASSA_NET)) == htonl(0x7f000000)) +# define VBOX_IPV4_IS_LINKLOCAL_169(addr) ((addr & htonl(IN_CLASSB_NET)) == htonl(0xa9fe0000)) +#endif + +#if RTLNX_VER_MIN(2,6,22) +# define VBOX_SKB_RESET_NETWORK_HDR(skb) skb_reset_network_header(skb) +# define VBOX_SKB_RESET_MAC_HDR(skb) skb_reset_mac_header(skb) +# define VBOX_SKB_CSUM_OFFSET(skb) skb->csum_offset +#else +# define VBOX_SKB_RESET_NETWORK_HDR(skb) skb->nh.raw = skb->data +# define VBOX_SKB_RESET_MAC_HDR(skb) skb->mac.raw = skb->data +# define VBOX_SKB_CSUM_OFFSET(skb) skb->csum +#endif + +#if RTLNX_VER_MIN(2,6,19) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(skb) +#else +# define CHECKSUM_PARTIAL CHECKSUM_HW +# if RTLNX_VER_MIN(2,6,10) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(skb, 0) +# else +# if RTLNX_VER_MIN(2,6,7) +# define VBOX_SKB_CHECKSUM_HELP(skb) skb_checksum_help(&skb, 0) +# else +# define VBOX_SKB_CHECKSUM_HELP(skb) (!skb_checksum_help(skb)) +# endif +/* Versions prior 2.6.10 use stats for both bstats and qstats */ +# define bstats stats +# define qstats stats +# endif +#endif + +#if RTLNX_VER_MIN(3,20,0) || RTLNX_RHEL_RANGE(7,2, 8,0) || RTLNX_RHEL_RANGE(6,8, 7,0) +# define VBOX_HAVE_SKB_VLAN +#endif + +#ifdef VBOX_HAVE_SKB_VLAN +# define vlan_tx_tag_get(skb) skb_vlan_tag_get(skb) +# define vlan_tx_tag_present(skb) skb_vlan_tag_present(skb) +#endif + +#ifndef NET_IP_ALIGN +# define NET_IP_ALIGN 2 +#endif + +#if 1 +/** Create scatter / gather segments for fragments. When not used, we will + * linearize the socket buffer before creating the internal networking SG. */ +# define VBOXNETFLT_SG_SUPPORT 1 +#endif + +#if RTLNX_VER_MIN(2,6,18) + +/** Indicates that the linux kernel may send us GSO frames. */ +# define VBOXNETFLT_WITH_GSO 1 + +/** This enables or disables the transmitting of GSO frame from the internal + * network and to the host. */ +# define VBOXNETFLT_WITH_GSO_XMIT_HOST 1 + +# if 0 /** @todo This is currently disable because it causes performance loss of 5-10%. */ +/** This enables or disables the transmitting of GSO frame from the internal + * network and to the wire. */ +# define VBOXNETFLT_WITH_GSO_XMIT_WIRE 1 +# endif + +/** This enables or disables the forwarding/flooding of GSO frame from the host + * to the internal network. */ +# define VBOXNETFLT_WITH_GSO_RECV 1 + +#endif /* RTLNX_VER_MIN(2,6,18) */ + +#if RTLNX_VER_MIN(2,6,29) +/** This enables or disables handling of GSO frames coming from the wire (GRO). */ +# define VBOXNETFLT_WITH_GRO 1 +#endif + +/* + * GRO support was backported to RHEL 5.4 + */ +#if RTLNX_RHEL_MAJ_PREREQ(5, 4) +# define VBOXNETFLT_WITH_GRO 1 +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxNetFltLinuxInit(void); +static void __exit VBoxNetFltLinuxUnload(void); +static void vboxNetFltLinuxForwardToIntNet(PVBOXNETFLTINS pThis, struct sk_buff *pBuf); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; + +module_init(VBoxNetFltLinuxInit); +module_exit(VBoxNetFltLinuxUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " Network Filter Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV) " (" RT_XSTR(INTNETTRUNKIFPORT_VERSION) ")"); +#endif + + +#if RTLNX_VER_MAX(2,6,12) && defined(LOG_ENABLED) +unsigned dev_get_flags(const struct net_device *dev) +{ + unsigned flags; + + flags = (dev->flags & ~(IFF_PROMISC | + IFF_ALLMULTI | + IFF_RUNNING)) | + (dev->gflags & (IFF_PROMISC | + IFF_ALLMULTI)); + + if (netif_running(dev) && netif_carrier_ok(dev)) + flags |= IFF_RUNNING; + + return flags; +} +#endif /* RTLNX_VER_MAX(2,6,12) */ + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxNetFltLinuxInit(void) +{ + int rc; + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + Log(("VBoxNetFltLinuxInit\n")); + + /* + * Initialize the globals and connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + LogRel(("VBoxNetFlt: Successfully started.\n")); + return 0; + } + + LogRel(("VBoxNetFlt: failed to initialize device extension (rc=%d)\n", rc)); + RTR0Term(); + } + else + LogRel(("VBoxNetFlt: failed to initialize IPRT (rc=%d)\n", rc)); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + return -RTErrConvertToErrno(rc); +} + + +/** + * Unload the module. + * + * @todo We have to prevent this if we're busy! + */ +static void __exit VBoxNetFltLinuxUnload(void) +{ + int rc; + Log(("VBoxNetFltLinuxUnload\n")); + Assert(vboxNetFltCanUnload(&g_VBoxNetFltGlobals)); + + /* + * Undo the work done during start (in reverse order). + */ + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltGlobals); + AssertRC(rc); NOREF(rc); + + RTR0Term(); + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + Log(("VBoxNetFltLinuxUnload - done\n")); +} + + +/** + * We filter traffic from the host to the internal network + * before it reaches the NIC driver. + * + * The current code uses a very ugly hack overriding hard_start_xmit + * callback in the device structure, but it has been shown to give us a + * performance boost of 60-100% though. Eventually we have to find some + * less hacky way of getting this job done. + */ +#define VBOXNETFLT_WITH_HOST2WIRE_FILTER + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + +# if RTLNX_VER_MAX(2,6,29) + +typedef struct ethtool_ops OVR_OPSTYPE; +# define OVR_OPS ethtool_ops +# define OVR_XMIT pfnStartXmit + +# else /* RTLNX_VER_MIN(2,6,29) */ + +typedef struct net_device_ops OVR_OPSTYPE; +# define OVR_OPS netdev_ops +# define OVR_XMIT pOrgOps->ndo_start_xmit + +# endif /* RTLNX_VER_MIN(2,6,29) */ + +/** + * The overridden net_device_ops of the device we're attached to. + * + * As there is no net_device_ops structure in pre-2.6.29 kernels we override + * ethtool_ops instead along with hard_start_xmit callback in net_device + * structure. + * + * This is a very dirty hack that was created to explore how much we can improve + * the host to guest transfers by not CC'ing the NIC. It turns out to be + * the only way to filter outgoing packets for devices without TX queue. + */ +typedef struct VBoxNetDeviceOpsOverride +{ + /** Our overridden ops. */ + OVR_OPSTYPE Ops; + /** Magic word. */ + uint32_t u32Magic; + /** Pointer to the original ops. */ + OVR_OPSTYPE const *pOrgOps; +# if RTLNX_VER_MAX(2,6,29) + /** Pointer to the original hard_start_xmit function. */ + int (*pfnStartXmit)(struct sk_buff *pSkb, struct net_device *pDev); +# endif /* RTLNX_VER_MAX(2,6,29) */ + /** Pointer to the net filter instance. */ + PVBOXNETFLTINS pVBoxNetFlt; + /** The number of filtered packages. */ + uint64_t cFiltered; + /** The total number of packets */ + uint64_t cTotal; +} VBOXNETDEVICEOPSOVERRIDE, *PVBOXNETDEVICEOPSOVERRIDE; +/** VBOXNETDEVICEOPSOVERRIDE::u32Magic value. */ +#define VBOXNETDEVICEOPSOVERRIDE_MAGIC UINT32_C(0x00c0ffee) + +/** + * ndo_start_xmit wrapper that drops packets that shouldn't go to the wire + * because they belong on the internal network. + * + * @returns NETDEV_TX_XXX. + * @param pSkb The socket buffer to transmit. + * @param pDev The net device. + */ +static int vboxNetFltLinuxStartXmitFilter(struct sk_buff *pSkb, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride = (PVBOXNETDEVICEOPSOVERRIDE)pDev->OVR_OPS; + uint8_t abHdrBuf[sizeof(RTNETETHERHDR) + sizeof(uint32_t) + RTNETIPV4_MIN_LEN]; + PCRTNETETHERHDR pEtherHdr; + PINTNETTRUNKSWPORT pSwitchPort; + uint32_t cbHdrs; + + + /* + * Validate the override structure. + * + * Note! We're racing vboxNetFltLinuxUnhookDev here. If this was supposed + * to be production quality code, we would have to be much more + * careful here and avoid the race. + */ + if ( !RT_VALID_PTR(pOverride) + || pOverride->u32Magic != VBOXNETDEVICEOPSOVERRIDE_MAGIC +# if RTLNX_VER_MIN(2,6,29) + || !RT_VALID_PTR(pOverride->pOrgOps) +# endif + ) + { + printk("vboxNetFltLinuxStartXmitFilter: bad override %p\n", pOverride); + dev_kfree_skb(pSkb); + return NETDEV_TX_OK; + } + pOverride->cTotal++; + + /* + * Do the filtering base on the default OUI of our virtual NICs + * + * Note! In a real solution, we would ask the switch whether the + * destination MAC is 100% to be on the internal network and then + * drop it. + */ + cbHdrs = skb_headlen(pSkb); + cbHdrs = RT_MIN(cbHdrs, sizeof(abHdrBuf)); + pEtherHdr = (PCRTNETETHERHDR)skb_header_pointer(pSkb, 0, cbHdrs, &abHdrBuf[0]); + if ( pEtherHdr + && RT_VALID_PTR(pOverride->pVBoxNetFlt) + && (pSwitchPort = pOverride->pVBoxNetFlt->pSwitchPort) != NULL + && RT_VALID_PTR(pSwitchPort) + && cbHdrs >= 6) + { + INTNETSWDECISION enmDecision; + + /** @todo consider reference counting, etc. */ + enmDecision = pSwitchPort->pfnPreRecv(pSwitchPort, pEtherHdr, cbHdrs, INTNETTRUNKDIR_HOST); + if (enmDecision == INTNETSWDECISION_INTNET) + { + dev_kfree_skb(pSkb); + pOverride->cFiltered++; + return NETDEV_TX_OK; + } + } + + return pOverride->OVR_XMIT(pSkb, pDev); +} + +/** + * Hooks the device ndo_start_xmit operation of the device. + * + * @param pThis The net filter instance. + * @param pDev The net device. + */ +static void vboxNetFltLinuxHookDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride; + + /* Cancel override if ethtool_ops is missing (host-only case, @bugref{5712}) */ + if (!RT_VALID_PTR(pDev->OVR_OPS)) + return; + pOverride = RTMemAlloc(sizeof(*pOverride)); + if (!pOverride) + return; + pOverride->pOrgOps = pDev->OVR_OPS; + pOverride->Ops = *pDev->OVR_OPS; +# if RTLNX_VER_MAX(2,6,29) + pOverride->pfnStartXmit = pDev->hard_start_xmit; +# else /* RTLNX_VER_MIN(2,6,29) */ + pOverride->Ops.ndo_start_xmit = vboxNetFltLinuxStartXmitFilter; +# endif /* RTLNX_VER_MIN(2,6,29) */ + pOverride->u32Magic = VBOXNETDEVICEOPSOVERRIDE_MAGIC; + pOverride->cTotal = 0; + pOverride->cFiltered = 0; + pOverride->pVBoxNetFlt = pThis; + + RTSpinlockAcquire(pThis->hSpinlock); /* (this isn't necessary, but so what) */ + ASMAtomicWritePtr((void * volatile *)&pDev->OVR_OPS, pOverride); +# if RTLNX_VER_MAX(2,6,29) + ASMAtomicXchgPtr((void * volatile *)&pDev->hard_start_xmit, vboxNetFltLinuxStartXmitFilter); +# endif /* RTLNX_VER_MAX(2,6,29) */ + RTSpinlockRelease(pThis->hSpinlock); +} + +/** + * Undos what vboxNetFltLinuxHookDev did. + * + * @param pThis The net filter instance. + * @param pDev The net device. Can be NULL, in which case + * we'll try retrieve it from @a pThis. + */ +static void vboxNetFltLinuxUnhookDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + PVBOXNETDEVICEOPSOVERRIDE pOverride; + + RTSpinlockAcquire(pThis->hSpinlock); + if (!pDev) + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (RT_VALID_PTR(pDev)) + { + pOverride = (PVBOXNETDEVICEOPSOVERRIDE)pDev->OVR_OPS; + if ( RT_VALID_PTR(pOverride) + && pOverride->u32Magic == VBOXNETDEVICEOPSOVERRIDE_MAGIC + && RT_VALID_PTR(pOverride->pOrgOps) + ) + { +# if RTLNX_VER_MAX(2,6,29) + ASMAtomicWritePtr((void * volatile *)&pDev->hard_start_xmit, pOverride->pfnStartXmit); +# endif /* RTLNX_VER_MAX(2,6,29) */ + ASMAtomicWritePtr((void const * volatile *)&pDev->OVR_OPS, pOverride->pOrgOps); + ASMAtomicWriteU32(&pOverride->u32Magic, 0); + } + else + pOverride = NULL; + } + else + pOverride = NULL; + RTSpinlockRelease(pThis->hSpinlock); + + if (pOverride) + { + printk("vboxnetflt: %llu out of %llu packets were not sent (directed to host)\n", pOverride->cFiltered, pOverride->cTotal); + RTMemFree(pOverride); + } +} + +#endif /* VBOXNETFLT_WITH_HOST2WIRE_FILTER */ + + +/** + * Reads and retains the host interface handle. + * + * @returns The handle, NULL if detached. + * @param pThis + */ +DECLINLINE(struct net_device *) vboxNetFltLinuxRetainNetDev(PVBOXNETFLTINS pThis) +{ +#if 0 + struct net_device *pDev = NULL; + + Log(("vboxNetFltLinuxRetainNetDev\n")); + /* + * Be careful here to avoid problems racing the detached callback. + */ + RTSpinlockAcquire(pThis->hSpinlock); + if (!ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + pDev = (struct net_device *)ASMAtomicUoReadPtr((void * volatile *)&pThis->u.s.pDev); + if (pDev) + { + dev_hold(pDev); + Log(("vboxNetFltLinuxRetainNetDev: Device %p(%s) retained. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + } + } + RTSpinlockRelease(pThis->hSpinlock); + + Log(("vboxNetFltLinuxRetainNetDev - done\n")); + return pDev; +#else + return ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); +#endif +} + + +/** + * Release the host interface handle previously retained + * by vboxNetFltLinuxRetainNetDev. + * + * @param pThis The instance. + * @param pDev The vboxNetFltLinuxRetainNetDev + * return value, NULL is fine. + */ +DECLINLINE(void) vboxNetFltLinuxReleaseNetDev(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ +#if 0 + Log(("vboxNetFltLinuxReleaseNetDev\n")); + NOREF(pThis); + if (pDev) + { + dev_put(pDev); + Log(("vboxNetFltLinuxReleaseNetDev: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + } + Log(("vboxNetFltLinuxReleaseNetDev - done\n")); +#endif +} + +#define VBOXNETFLT_CB_TAG(skb) (0xA1C90000 | (skb->dev->ifindex & 0xFFFF)) +#define VBOXNETFLT_SKB_TAG(skb) (*(uint32_t*)&((skb)->cb[sizeof((skb)->cb)-sizeof(uint32_t)])) + +/** + * Checks whether this is an mbuf created by vboxNetFltLinuxMBufFromSG, + * i.e. a buffer which we're pushing and should be ignored by the filter callbacks. + * + * @returns true / false accordingly. + * @param pBuf The sk_buff. + */ +DECLINLINE(bool) vboxNetFltLinuxSkBufIsOur(struct sk_buff *pBuf) +{ + return VBOXNETFLT_SKB_TAG(pBuf) == VBOXNETFLT_CB_TAG(pBuf); +} + + +/** + * Checks whether this SG list contains a GSO packet. + * + * @returns true / false accordingly. + * @param pSG The (scatter/)gather list. + */ +DECLINLINE(bool) vboxNetFltLinuxIsGso(PINTNETSG pSG) +{ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + return !((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type == PDMNETWORKGSOTYPE_INVALID); +#else /* !VBOXNETFLT_WITH_GSO_XMIT_WIRE && !VBOXNETFLT_WITH_GSO_XMIT_HOST */ + return false; +#endif /* !VBOXNETFLT_WITH_GSO_XMIT_WIRE && !VBOXNETFLT_WITH_GSO_XMIT_HOST */ +} + + +/** + * Find out the frame size (of a single segment in case of GSO frames). + * + * @returns the frame size. + * @param pSG The (scatter/)gather list. + */ +DECLINLINE(uint32_t) vboxNetFltLinuxFrameSize(PINTNETSG pSG) +{ + uint16_t u16Type = 0; + uint32_t cbVlanTag = 0; + if (pSG->aSegs[0].cb >= sizeof(RTNETETHERHDR)) + u16Type = RT_BE2H_U16(((PCRTNETETHERHDR)pSG->aSegs[0].pv)->EtherType); + else if (pSG->cbTotal >= sizeof(RTNETETHERHDR)) + { + uint32_t off = RT_UOFFSETOF(RTNETETHERHDR, EtherType); + uint32_t i; + for (i = 0; i < pSG->cSegsUsed; ++i) + { + if (off <= pSG->aSegs[i].cb) + { + if (off + sizeof(uint16_t) <= pSG->aSegs[i].cb) + u16Type = RT_BE2H_U16(*(uint16_t *)((uintptr_t)pSG->aSegs[i].pv + off)); + else if (i + 1 < pSG->cSegsUsed) + u16Type = RT_BE2H_U16( ((uint16_t)( ((uint8_t *)pSG->aSegs[i].pv)[off] ) << 8) + + *(uint8_t *)pSG->aSegs[i + 1].pv); /* ASSUMES no empty segments! */ + /* else: frame is too short. */ + break; + } + off -= pSG->aSegs[i].cb; + } + } + if (u16Type == RTNET_ETHERTYPE_VLAN) + cbVlanTag = 4; + return (vboxNetFltLinuxIsGso(pSG) ? (uint32_t)pSG->GsoCtx.cbMaxSeg + pSG->GsoCtx.cbHdrsTotal : pSG->cbTotal) - cbVlanTag; +} + + +/** + * Internal worker that create a linux sk_buff for a + * (scatter/)gather list. + * + * @returns Pointer to the sk_buff. + * @param pThis The instance. + * @param pSG The (scatter/)gather list. + * @param fDstWire Set if the destination is the wire. + */ +static struct sk_buff *vboxNetFltLinuxSkBufFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, bool fDstWire) +{ + struct sk_buff *pPkt; + struct net_device *pDev; +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + unsigned fGsoType = 0; +#endif + + if (pSG->cbTotal == 0) + { + LogRel(("VBoxNetFlt: Dropped empty packet coming from internal network.\n")); + return NULL; + } + Log5(("VBoxNetFlt: Packet to %s of %d bytes (frame=%d).\n", fDstWire?"wire":"host", pSG->cbTotal, vboxNetFltLinuxFrameSize(pSG))); + if (fDstWire && (vboxNetFltLinuxFrameSize(pSG) > ASMAtomicReadU32(&pThis->u.s.cbMtu) + 14)) + { + static bool s_fOnce = true; + if (s_fOnce) + { + s_fOnce = false; + printk("VBoxNetFlt: Dropped over-sized packet (%d bytes) coming from internal network.\n", vboxNetFltLinuxFrameSize(pSG)); + } + return NULL; + } + + /** @todo We should use fragments mapping the SG buffers with large packets. + * 256 bytes seems to be the a threshold used a lot for this. It + * requires some nasty work on the intnet side though... */ + /* + * Allocate a packet and copy over the data. + */ + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + pPkt = dev_alloc_skb(pSG->cbTotal + NET_IP_ALIGN); + if (RT_UNLIKELY(!pPkt)) + { + Log(("vboxNetFltLinuxSkBufFromSG: Failed to allocate sk_buff(%u).\n", pSG->cbTotal)); + pSG->pvUserData = NULL; + return NULL; + } + pPkt->dev = pDev; + pPkt->ip_summed = CHECKSUM_NONE; + + /* Align IP header on 16-byte boundary: 2 + 14 (ethernet hdr size). */ + skb_reserve(pPkt, NET_IP_ALIGN); + + /* Copy the segments. */ + skb_put(pPkt, pSG->cbTotal); + IntNetSgRead(pSG, pPkt->data); + +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + /* + * Setup GSO if used by this packet. + */ + switch ((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type) + { + default: + AssertMsgFailed(("%u (%s)\n", pSG->GsoCtx.u8Type, PDMNetGsoTypeName((PDMNETWORKGSOTYPE)pSG->GsoCtx.u8Type) )); + RT_FALL_THRU(); + case PDMNETWORKGSOTYPE_INVALID: + fGsoType = 0; + break; + case PDMNETWORKGSOTYPE_IPV4_TCP: + fGsoType = SKB_GSO_TCPV4; + break; + case PDMNETWORKGSOTYPE_IPV6_TCP: + fGsoType = SKB_GSO_TCPV6; + break; + } + if (fGsoType) + { + struct skb_shared_info *pShInfo = skb_shinfo(pPkt); + + pShInfo->gso_type = fGsoType | SKB_GSO_DODGY; + pShInfo->gso_size = pSG->GsoCtx.cbMaxSeg; + pShInfo->gso_segs = PDMNetGsoCalcSegmentCount(&pSG->GsoCtx, pSG->cbTotal); + + /* + * We need to set checksum fields even if the packet goes to the host + * directly as it may be immediately forwarded by IP layer @bugref{5020}. + */ + Assert(skb_headlen(pPkt) >= pSG->GsoCtx.cbHdrsTotal); + pPkt->ip_summed = CHECKSUM_PARTIAL; +# if RTLNX_VER_MIN(2,6,22) + pPkt->csum_start = skb_headroom(pPkt) + pSG->GsoCtx.offHdr2; + if (fGsoType & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) + pPkt->csum_offset = RT_UOFFSETOF(RTNETTCP, th_sum); + else + pPkt->csum_offset = RT_UOFFSETOF(RTNETUDP, uh_sum); +# else + pPkt->h.raw = pPkt->data + pSG->GsoCtx.offHdr2; + if (fGsoType & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6)) + pPkt->csum = RT_UOFFSETOF(RTNETTCP, th_sum); + else + pPkt->csum = RT_UOFFSETOF(RTNETUDP, uh_sum); +# endif + if (!fDstWire) + PDMNetGsoPrepForDirectUse(&pSG->GsoCtx, pPkt->data, pSG->cbTotal, PDMNETCSUMTYPE_PSEUDO); + } +#endif /* VBOXNETFLT_WITH_GSO_XMIT_WIRE || VBOXNETFLT_WITH_GSO_XMIT_HOST */ + + /* + * Finish up the socket buffer. + */ + pPkt->protocol = eth_type_trans(pPkt, pDev); + if (fDstWire) + { + VBOX_SKB_RESET_NETWORK_HDR(pPkt); + + /* Restore ethernet header back. */ + skb_push(pPkt, ETH_HLEN); /** @todo VLAN: +4 if VLAN? */ + VBOX_SKB_RESET_MAC_HDR(pPkt); + } + VBOXNETFLT_SKB_TAG(pPkt) = VBOXNETFLT_CB_TAG(pPkt); + + return pPkt; +} + + +/** + * Return the offset where to start checksum computation from. + * + * @returns the offset relative to pBuf->data. + * @param pBuf The socket buffer. + */ +DECLINLINE(unsigned) vboxNetFltLinuxGetChecksumStartOffset(struct sk_buff *pBuf) +{ +#if RTLNX_VER_MIN(2,6,38) + return skb_checksum_start_offset(pBuf); +#elif RTLNX_VER_MIN(2,6,22) + return pBuf->csum_start - skb_headroom(pBuf); +#else + unsigned char *pTransportHdr = pBuf->h.raw; +# if RTLNX_VER_MAX(2,6,19) + /* + * Try to work around the problem with CentOS 4.7 and 5.2 (2.6.9 + * and 2.6.18 kernels), they pass wrong 'h' pointer down. We take IP + * header length from the header itself and reconstruct 'h' pointer + * to TCP (or whatever) header. + */ + if (pBuf->h.raw == pBuf->nh.raw && pBuf->protocol == htons(ETH_P_IP)) + pTransportHdr = pBuf->nh.raw + pBuf->nh.iph->ihl * 4; +# endif + return pTransportHdr - pBuf->data; +#endif +} + + +/** + * Initializes a SG list from an sk_buff. + * + * @param pThis The instance. + * @param pBuf The sk_buff. + * @param pSG The SG. + * @param cbExtra The number of bytes of extra space allocated immediately after the SG. + * @param cSegs The number of segments allocated for the SG. + * This should match the number in the mbuf exactly! + * @param fSrc The source of the frame. + * @param pGsoCtx Pointer to the GSO context if it's a GSO + * internal network frame. NULL if regular frame. + */ +static void vboxNetFltLinuxSkBufToSG(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, PINTNETSG pSG, + unsigned cbExtra, unsigned cSegs, uint32_t fSrc, PCPDMNETWORKGSO pGsoCtx) +{ + int i; + NOREF(pThis); + +#ifndef VBOXNETFLT_SG_SUPPORT + Assert(!skb_shinfo(pBuf)->frag_list); +#else /* VBOXNETFLT_SG_SUPPORT */ + uint8_t *pExtra = (uint8_t *)&pSG->aSegs[cSegs]; + unsigned cbConsumed = 0; + unsigned cbProduced = 0; + +# if RTLNX_VER_MIN(2,6,27) + /* Restore VLAN tag stripped by host hardware */ + if (vlan_tx_tag_present(pBuf)) + { + uint8_t *pMac = pBuf->data; + struct vlan_ethhdr *pVHdr = (struct vlan_ethhdr *)pExtra; + Assert(ETH_ALEN * 2 + VLAN_HLEN <= cbExtra); + memmove(pVHdr, pMac, ETH_ALEN * 2); + /* Consume whole Ethernet header: 2 addresses + EtherType (see @bugref{8599}) */ + cbConsumed += ETH_ALEN * 2 + sizeof(uint16_t); + pVHdr->h_vlan_proto = RT_H2N_U16(ETH_P_8021Q); + pVHdr->h_vlan_TCI = RT_H2N_U16(vlan_tx_tag_get(pBuf)); + pVHdr->h_vlan_encapsulated_proto = *(uint16_t*)(pMac + ETH_ALEN * 2); + cbProduced += VLAN_ETH_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { + unsigned uCsumStartOffset = vboxNetFltLinuxGetChecksumStartOffset(pBuf); + unsigned uCsumStoreOffset = uCsumStartOffset + VBOX_SKB_CSUM_OFFSET(pBuf) - cbConsumed; + Log3(("cbConsumed=%u cbProduced=%u uCsumStartOffset=%u uCsumStoreOffset=%u\n", + cbConsumed, cbProduced, uCsumStartOffset, uCsumStoreOffset)); + Assert(cbProduced + uCsumStoreOffset + sizeof(uint16_t) <= cbExtra); + /* + * We assume that the checksum is stored at the very end of the transport header + * so we will have all headers in a single fragment. If our assumption is wrong + * we may see suboptimal performance. + */ + memmove(pExtra + cbProduced, + pBuf->data + cbConsumed, + uCsumStoreOffset); + unsigned uChecksum = skb_checksum(pBuf, uCsumStartOffset, pBuf->len - uCsumStartOffset, 0); + *(uint16_t*)(pExtra + cbProduced + uCsumStoreOffset) = csum_fold(uChecksum); + cbProduced += uCsumStoreOffset + sizeof(uint16_t); + cbConsumed += uCsumStoreOffset + sizeof(uint16_t); + } +#endif /* VBOXNETFLT_SG_SUPPORT */ + + if (!pGsoCtx) + IntNetSgInitTempSegs(pSG, pBuf->len + cbProduced - cbConsumed, cSegs, 0 /*cSegsUsed*/); + else + IntNetSgInitTempSegsGso(pSG, pBuf->len + cbProduced - cbConsumed, cSegs, 0 /*cSegsUsed*/, pGsoCtx); + + int iSeg = 0; +#ifdef VBOXNETFLT_SG_SUPPORT + if (cbProduced) + { + pSG->aSegs[iSeg].cb = cbProduced; + pSG->aSegs[iSeg].pv = pExtra; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + } + pSG->aSegs[iSeg].cb = skb_headlen(pBuf) - cbConsumed; + pSG->aSegs[iSeg].pv = pBuf->data + cbConsumed; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6((" kmap_atomic:")); +# endif /* LOG_ENABLED */ + for (i = 0; i < skb_shinfo(pBuf)->nr_frags; i++) + { + skb_frag_t *pFrag = &skb_shinfo(pBuf)->frags[i]; +# if RTLNX_VER_MIN(5,4,0) || RTLNX_SUSE_MAJ_PREREQ(15, 2) + pSG->aSegs[iSeg].cb = pFrag->bv_len; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->bv_offset; +# else /* < KERNEL_VERSION(5, 4, 0) */ + pSG->aSegs[iSeg].cb = pFrag->size; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->page_offset; +# endif /* >= KERNEL_VERSION(5, 4, 0) */ + Log6((" %p", pSG->aSegs[iSeg].pv)); + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + } + struct sk_buff *pFragBuf; + for (pFragBuf = skb_shinfo(pBuf)->frag_list; pFragBuf; pFragBuf = pFragBuf->next) + { + pSG->aSegs[iSeg].cb = skb_headlen(pFragBuf); + pSG->aSegs[iSeg].pv = pFragBuf->data; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + for (i = 0; i < skb_shinfo(pFragBuf)->nr_frags; i++) + { + skb_frag_t *pFrag = &skb_shinfo(pFragBuf)->frags[i]; +# if RTLNX_VER_MIN(5,4,0) || RTLNX_SUSE_MAJ_PREREQ(15, 2) + pSG->aSegs[iSeg].cb = pFrag->bv_len; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->bv_offset; +# else /* < KERNEL_VERSION(5, 4, 0) */ + pSG->aSegs[iSeg].cb = pFrag->size; + pSG->aSegs[iSeg].pv = VBOX_SKB_KMAP_FRAG(pFrag) + pFrag->page_offset; +# endif /* >= KERNEL_VERSION(5, 4, 0) */ + Log6((" %p", pSG->aSegs[iSeg].pv)); + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; + Assert(iSeg <= pSG->cSegsAlloc); + } + } +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("\n")); +# endif /* LOG_ENABLED */ +#else + pSG->aSegs[iSeg].cb = pBuf->len; + pSG->aSegs[iSeg].pv = pBuf->data; + pSG->aSegs[iSeg++].Phys = NIL_RTHCPHYS; +#endif + + pSG->cSegsUsed = iSeg; + +#if 0 + if (cbProduced) + { + LogRel(("vboxNetFltLinuxSkBufToSG: original packet dump:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); + LogRel(("vboxNetFltLinuxSkBufToSG: cbConsumed=%u cbProduced=%u cbExtra=%u\n", cbConsumed, cbProduced, cbExtra)); + uint32_t offset = 0; + for (i = 0; i < pSG->cSegsUsed; ++i) + { + LogRel(("vboxNetFltLinuxSkBufToSG: seg#%d (%d bytes, starting at 0x%x):\n%.*Rhxd\n", + i, pSG->aSegs[i].cb, offset, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); + offset += pSG->aSegs[i].cb; + } + } +#endif + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* + * Add a trailer if the frame is too small. + * + * Since we're getting to the packet before it is framed, it has not + * yet been padded. The current solution is to add a segment pointing + * to a buffer containing all zeros and pray that works for all frames... + */ + if (pSG->cbTotal < 60 && (fSrc & INTNETTRUNKDIR_HOST)) + { + Assert(pBuf->data_len == 0); /* Packets with fragments are never small! */ + static uint8_t const s_abZero[128] = {0}; + + AssertReturnVoid(iSeg < cSegs); + + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->aSegs[iSeg].pv = (void *)&s_abZero[0]; + pSG->aSegs[iSeg++].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + pSG->cSegsUsed++; + Assert(iSeg <= pSG->cSegsAlloc) + } +#endif + + Log6(("vboxNetFltLinuxSkBufToSG: allocated=%d, segments=%d frags=%d next=%p frag_list=%p pkt_type=%x fSrc=%x\n", + pSG->cSegsAlloc, pSG->cSegsUsed, skb_shinfo(pBuf)->nr_frags, pBuf->next, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type, fSrc)); + for (i = 0; i < pSG->cSegsUsed; i++) + Log6(("vboxNetFltLinuxSkBufToSG: #%d: cb=%d pv=%p\n", + i, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); +} + +/** + * Packet handler; not really documented - figure it out yourself. + * + * @returns 0 or EJUSTRETURN - this is probably copy & pastry and thus wrong. + */ +#if RTLNX_VER_MIN(2,6,14) +static int vboxNetFltLinuxPacketHandler(struct sk_buff *pBuf, + struct net_device *pSkbDev, + struct packet_type *pPacketType, + struct net_device *pOrigDev) +#else +static int vboxNetFltLinuxPacketHandler(struct sk_buff *pBuf, + struct net_device *pSkbDev, + struct packet_type *pPacketType) +#endif +{ + PVBOXNETFLTINS pThis; + struct net_device *pDev; + LogFlow(("vboxNetFltLinuxPacketHandler: pBuf=%p pSkbDev=%p pPacketType=%p\n", + pBuf, pSkbDev, pPacketType)); +#if RTLNX_VER_MIN(2,6,18) + Log3(("vboxNetFltLinuxPacketHandler: skb len=%u data_len=%u truesize=%u next=%p nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# if RTLNX_VER_MIN(2,6,22) + Log6(("vboxNetFltLinuxPacketHandler: packet dump follows:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); +# endif +#else + Log3(("vboxNetFltLinuxPacketHandler: skb len=%u data_len=%u truesize=%u next=%p nr_frags=%u tso_size=%u tso_seqs=%u frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->tso_size, skb_shinfo(pBuf)->tso_segs, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +#endif + /* + * Drop it immediately? + */ + if (!pBuf) + return 0; + + if (pBuf->pkt_type == PACKET_LOOPBACK) + { + /* + * We are not interested in loopbacked packets as they will always have + * another copy going to the wire. + */ + Log2(("vboxNetFltLinuxPacketHandler: dropped loopback packet (cb=%u)\n", pBuf->len)); + dev_kfree_skb(pBuf); /* We must 'consume' all packets we get (@bugref{6539})! */ + return 0; + } + + pThis = VBOX_FLT_PT_TO_INST(pPacketType); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev != pSkbDev) + { + Log(("vboxNetFltLinuxPacketHandler: Devices do not match, pThis may be wrong! pThis=%p\n", pThis)); + kfree_skb(pBuf); /* This is a failure, so we use kfree_skb instead of dev_kfree_skb. */ + return 0; + } + + Log6(("vboxNetFltLinuxPacketHandler: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + if (vboxNetFltLinuxSkBufIsOur(pBuf)) + { + Log2(("vboxNetFltLinuxPacketHandler: got our own sk_buff, drop it.\n")); + dev_kfree_skb(pBuf); + return 0; + } + +#ifndef VBOXNETFLT_SG_SUPPORT + { + /* + * Get rid of fragmented packets, they cause too much trouble. + */ + unsigned int uMacLen = pBuf->mac_len; + struct sk_buff *pCopy = skb_copy(pBuf, GFP_ATOMIC); + dev_kfree_skb(pBuf); + if (!pCopy) + { + LogRel(("VBoxNetFlt: Failed to allocate packet buffer, dropping the packet.\n")); + return 0; + } + pBuf = pCopy; + /* Somehow skb_copy ignores mac_len */ + pBuf->mac_len = uMacLen; +# if RTLNX_VER_MIN(2,6,27) + /* Restore VLAN tag stripped by host hardware */ + if (vlan_tx_tag_present(pBuf) && skb_headroom(pBuf) >= VLAN_ETH_HLEN) + { + uint8_t *pMac = (uint8_t*)skb_mac_header(pBuf); + struct vlan_ethhdr *pVHdr = (struct vlan_ethhdr *)(pMac - VLAN_HLEN); + memmove(pVHdr, pMac, ETH_ALEN * 2); + pVHdr->h_vlan_proto = RT_H2N_U16(ETH_P_8021Q); + pVHdr->h_vlan_TCI = RT_H2N_U16(vlan_tx_tag_get(pBuf)); + pBuf->mac_header -= VLAN_HLEN; + pBuf->mac_len += VLAN_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + +# if RTLNX_VER_MIN(2,6,18) + Log3(("vboxNetFltLinuxPacketHandler: skb copy len=%u data_len=%u truesize=%u next=%p nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# if RTLNX_VER_MIN(2,6,22) + Log6(("vboxNetFltLinuxPacketHandler: packet dump follows:\n%.*Rhxd\n", pBuf->len-pBuf->data_len, skb_mac_header(pBuf))); +# endif /* RTLNX_VER_MIN(2,6,22) */ +# else /* RTLNX_VER_MAX(2,6,18) */ + Log3(("vboxNetFltLinuxPacketHandler: skb copy len=%u data_len=%u truesize=%u next=%p nr_frags=%u tso_size=%u tso_seqs=%u frag_list=%p pkt_type=%x\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->tso_size, skb_shinfo(pBuf)->tso_segs, skb_shinfo(pBuf)->frag_list, pBuf->pkt_type)); +# endif /* RTLNX_VER_MAX(2,6,18) */ + } +#endif /* !VBOXNETFLT_SG_SUPPORT */ + +#ifdef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + /* Forward it to the internal network. */ + vboxNetFltLinuxForwardToIntNet(pThis, pBuf); +#else /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + /* Add the packet to transmit queue and schedule the bottom half. */ + skb_queue_tail(&pThis->u.s.XmitQueue, pBuf); + schedule_work(&pThis->u.s.XmitTask); + Log6(("vboxNetFltLinuxPacketHandler: scheduled work %p for sk_buff %p\n", + &pThis->u.s.XmitTask, pBuf)); +#endif /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + + /* It does not really matter what we return, it is ignored by the kernel. */ + return 0; +} + +/** + * Calculate the number of INTNETSEG segments the socket buffer will need. + * + * @returns Segment count. + * @param pBuf The socket buffer. + * @param pcbTemp Where to store the number of bytes of the part + * of the socket buffer that will be copied to + * a temporary storage. + */ +DECLINLINE(unsigned) vboxNetFltLinuxCalcSGSegments(struct sk_buff *pBuf, unsigned *pcbTemp) +{ + *pcbTemp = 0; +#ifdef VBOXNETFLT_SG_SUPPORT + unsigned cSegs = 1 + skb_shinfo(pBuf)->nr_frags; + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { + *pcbTemp = vboxNetFltLinuxGetChecksumStartOffset(pBuf) + VBOX_SKB_CSUM_OFFSET(pBuf) + sizeof(uint16_t); + } +# if RTLNX_VER_MIN(2,6,27) + if (vlan_tx_tag_present(pBuf)) + { + if (*pcbTemp) + *pcbTemp += VLAN_HLEN; + else + *pcbTemp = VLAN_ETH_HLEN; + } +# endif /* RTLNX_VER_MIN(2,6,27) */ + if (*pcbTemp) + ++cSegs; + struct sk_buff *pFrag; + for (pFrag = skb_shinfo(pBuf)->frag_list; pFrag; pFrag = pFrag->next) + { + Log6(("vboxNetFltLinuxCalcSGSegments: frag=%p len=%d data_len=%d frags=%d frag_list=%p next=%p\n", + pFrag, pFrag->len, pFrag->data_len, skb_shinfo(pFrag)->nr_frags, skb_shinfo(pFrag)->frag_list, pFrag->next)); + cSegs += 1 + skb_shinfo(pFrag)->nr_frags; + } +#else + unsigned cSegs = 1; +#endif +#ifdef PADD_RUNT_FRAMES_FROM_HOST + /* vboxNetFltLinuxSkBufToSG adds a padding segment if it's a runt. */ + if (pBuf->len < 60) + cSegs++; +#endif + return cSegs; +} + + +/** + * Destroy the intnet scatter / gather buffer created by + * vboxNetFltLinuxSkBufToSG. + * + * @param pSG The (scatter/)gather list. + * @param pBuf The original socket buffer that was used to create + * the scatter/gather list. + */ +static void vboxNetFltLinuxDestroySG(PINTNETSG pSG, struct sk_buff *pBuf) +{ +#ifdef VBOXNETFLT_SG_SUPPORT + int i, iSeg = 1; /* Skip non-paged part of SKB */ + /* Check if the extra buffer behind SG structure was used for modified packet header */ + if (pBuf->data != pSG->aSegs[0].pv) + ++iSeg; /* Skip it as well */ +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("kunmap_atomic:")); +# endif /* LOG_ENABLED */ + /* iSeg now points to the first mapped fragment if there are any */ + for (i = 0; i < skb_shinfo(pBuf)->nr_frags; i++) + { + Log6((" %p", pSG->aSegs[iSeg].pv)); + VBOX_SKB_KUNMAP_FRAG(pSG->aSegs[iSeg++].pv); + } + struct sk_buff *pFragBuf; + for (pFragBuf = skb_shinfo(pBuf)->frag_list; pFragBuf; pFragBuf = pFragBuf->next) + { + ++iSeg; /* Non-fragment (unmapped) portion of chained SKB */ + for (i = 0; i < skb_shinfo(pFragBuf)->nr_frags; i++) + { + Log6((" %p", pSG->aSegs[iSeg].pv)); + VBOX_SKB_KUNMAP_FRAG(pSG->aSegs[iSeg++].pv); + } + } +# ifdef LOG_ENABLED + if (pBuf->data_len) + Log6(("\n")); +# endif /* LOG_ENABLED */ +#endif + NOREF(pSG); +} + +#ifdef LOG_ENABLED +/** + * Logging helper. + */ +static void vboxNetFltDumpPacket(PINTNETSG pSG, bool fEgress, const char *pszWhere, int iIncrement) +{ + int i, offSeg; + uint8_t *pInt, *pExt; + static int iPacketNo = 1; + iPacketNo += iIncrement; + if (fEgress) + { + pExt = pSG->aSegs[0].pv; + pInt = pExt + 6; + } + else + { + pInt = pSG->aSegs[0].pv; + pExt = pInt + 6; + } + Log(("VBoxNetFlt: (int)%02x:%02x:%02x:%02x:%02x:%02x" + " %s (%s)%02x:%02x:%02x:%02x:%02x:%02x (%u bytes) packet #%u\n", + pInt[0], pInt[1], pInt[2], pInt[3], pInt[4], pInt[5], + fEgress ? "-->" : "<--", pszWhere, + pExt[0], pExt[1], pExt[2], pExt[3], pExt[4], pExt[5], + pSG->cbTotal, iPacketNo)); + if (pSG->cSegsUsed == 1) + { + Log4(("%.*Rhxd\n", pSG->aSegs[0].cb, pSG->aSegs[0].pv)); + } + else + { + for (i = 0, offSeg = 0; i < pSG->cSegsUsed; i++) + { + Log4(("-- segment %d at 0x%x (%d bytes)\n --\n%.*Rhxd\n", + i, offSeg, pSG->aSegs[i].cb, pSG->aSegs[i].cb, pSG->aSegs[i].pv)); + offSeg += pSG->aSegs[i].cb; + } + } +} +#else +# define vboxNetFltDumpPacket(a, b, c, d) do {} while (0) +#endif + +#ifdef VBOXNETFLT_WITH_GSO_RECV + +/** + * Worker for vboxNetFltLinuxForwardToIntNet that checks if we can forwards a + * GSO socket buffer without having to segment it. + * + * @returns true on success, false if needs segmenting. + * @param pThis The net filter instance. + * @param pSkb The GSO socket buffer. + * @param fSrc The source. + * @param pGsoCtx Where to return the GSO context on success. + */ +static bool vboxNetFltLinuxCanForwardAsGso(PVBOXNETFLTINS pThis, struct sk_buff *pSkb, uint32_t fSrc, + PPDMNETWORKGSO pGsoCtx) +{ + PDMNETWORKGSOTYPE enmGsoType; + uint16_t uEtherType; + unsigned int cbTransport; + unsigned int offTransport; + unsigned int cbTransportHdr; + unsigned uProtocol; + union + { + RTNETIPV4 IPv4; + RTNETIPV6 IPv6; + RTNETTCP Tcp; + uint8_t ab[40]; + uint16_t au16[40/2]; + uint32_t au32[40/4]; + } Buf; + + /* + * Check the GSO properties of the socket buffer and make sure it fits. + */ + /** @todo Figure out how to handle SKB_GSO_TCP_ECN! */ + if (RT_UNLIKELY( skb_shinfo(pSkb)->gso_type & ~(SKB_GSO_DODGY | SKB_GSO_TCPV6 | SKB_GSO_TCPV4) )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: gso_type=%#x\n", skb_shinfo(pSkb)->gso_type)); + return false; + } + if (RT_UNLIKELY( skb_shinfo(pSkb)->gso_size < 1 + || pSkb->len > VBOX_MAX_GSO_SIZE )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: gso_size=%#x skb_len=%#x (max=%#x)\n", skb_shinfo(pSkb)->gso_size, pSkb->len, VBOX_MAX_GSO_SIZE)); + return false; + } + + /* + * Switch on the ethertype. + */ + uEtherType = pSkb->protocol; + if ( uEtherType == RT_H2N_U16_C(RTNET_ETHERTYPE_VLAN) + && pSkb->mac_len == sizeof(RTNETETHERHDR) + sizeof(uint32_t)) + { + uint16_t const *puEtherType = skb_header_pointer(pSkb, sizeof(RTNETETHERHDR) + sizeof(uint16_t), sizeof(uint16_t), &Buf); + if (puEtherType) + uEtherType = *puEtherType; + } + switch (uEtherType) + { + case RT_H2N_U16_C(RTNET_ETHERTYPE_IPV4): + { + unsigned int cbHdr; + PCRTNETIPV4 pIPv4 = (PCRTNETIPV4)skb_header_pointer(pSkb, pSkb->mac_len, sizeof(Buf.IPv4), &Buf); + if (RT_UNLIKELY(!pIPv4)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access IPv4 hdr\n")); + return false; + } + + cbHdr = pIPv4->ip_hl * 4; + cbTransport = RT_N2H_U16(pIPv4->ip_len); + if (RT_UNLIKELY( cbHdr < RTNETIPV4_MIN_LEN + || cbHdr > cbTransport )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: invalid IPv4 lengths: ip_hl=%u ip_len=%u\n", pIPv4->ip_hl, RT_N2H_U16(pIPv4->ip_len))); + return false; + } + cbTransport -= cbHdr; + offTransport = pSkb->mac_len + cbHdr; + uProtocol = pIPv4->ip_p; + if (uProtocol == RTNETIPV4_PROT_TCP) + enmGsoType = PDMNETWORKGSOTYPE_IPV4_TCP; + else if (uProtocol == RTNETIPV4_PROT_UDP) + enmGsoType = PDMNETWORKGSOTYPE_IPV4_UDP; + else /** @todo IPv6: 4to6 tunneling */ + enmGsoType = PDMNETWORKGSOTYPE_INVALID; + break; + } + + case RT_H2N_U16_C(RTNET_ETHERTYPE_IPV6): + { + PCRTNETIPV6 pIPv6 = (PCRTNETIPV6)skb_header_pointer(pSkb, pSkb->mac_len, sizeof(Buf.IPv6), &Buf); + if (RT_UNLIKELY(!pIPv6)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access IPv6 hdr\n")); + return false; + } + + cbTransport = RT_N2H_U16(pIPv6->ip6_plen); + offTransport = pSkb->mac_len + sizeof(RTNETIPV6); + uProtocol = pIPv6->ip6_nxt; + /** @todo IPv6: Dig our way out of the other headers. */ + if (uProtocol == RTNETIPV4_PROT_TCP) + enmGsoType = PDMNETWORKGSOTYPE_IPV6_TCP; + else if (uProtocol == RTNETIPV4_PROT_UDP) + enmGsoType = PDMNETWORKGSOTYPE_IPV6_UDP; + else + enmGsoType = PDMNETWORKGSOTYPE_INVALID; + break; + } + + default: + Log5(("vboxNetFltLinuxCanForwardAsGso: uEtherType=%#x\n", RT_H2N_U16(uEtherType))); + return false; + } + + if (enmGsoType == PDMNETWORKGSOTYPE_INVALID) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: Unsupported protocol %d\n", uProtocol)); + return false; + } + + if (RT_UNLIKELY( offTransport + cbTransport <= offTransport + || offTransport + cbTransport > pSkb->len + || cbTransport < (uProtocol == RTNETIPV4_PROT_TCP ? RTNETTCP_MIN_LEN : RTNETUDP_MIN_LEN)) ) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: Bad transport length; off=%#x + cb=%#x => %#x; skb_len=%#x (%s)\n", + offTransport, cbTransport, offTransport + cbTransport, pSkb->len, PDMNetGsoTypeName(enmGsoType) )); + return false; + } + + /* + * Check the TCP/UDP bits. + */ + if (uProtocol == RTNETIPV4_PROT_TCP) + { + PCRTNETTCP pTcp = (PCRTNETTCP)skb_header_pointer(pSkb, offTransport, sizeof(Buf.Tcp), &Buf); + if (RT_UNLIKELY(!pTcp)) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: failed to access TCP hdr\n")); + return false; + } + + cbTransportHdr = pTcp->th_off * 4; + pGsoCtx->cbHdrsSeg = offTransport + cbTransportHdr; + if (RT_UNLIKELY( cbTransportHdr < RTNETTCP_MIN_LEN + || cbTransportHdr > cbTransport + || offTransport + cbTransportHdr >= UINT8_MAX + || offTransport + cbTransportHdr >= pSkb->len )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: No space for TCP header; off=%#x cb=%#x skb_len=%#x\n", offTransport, cbTransportHdr, pSkb->len)); + return false; + } + + } + else + { + Assert(uProtocol == RTNETIPV4_PROT_UDP); + cbTransportHdr = sizeof(RTNETUDP); + pGsoCtx->cbHdrsSeg = offTransport; /* Exclude UDP header */ + if (RT_UNLIKELY( offTransport + cbTransportHdr >= UINT8_MAX + || offTransport + cbTransportHdr >= pSkb->len )) + { + Log5(("vboxNetFltLinuxCanForwardAsGso: No space for UDP header; off=%#x skb_len=%#x\n", offTransport, pSkb->len)); + return false; + } + } + + /* + * We're good, init the GSO context. + */ + pGsoCtx->u8Type = enmGsoType; + pGsoCtx->cbHdrsTotal = offTransport + cbTransportHdr; + pGsoCtx->cbMaxSeg = skb_shinfo(pSkb)->gso_size; + pGsoCtx->offHdr1 = pSkb->mac_len; + pGsoCtx->offHdr2 = offTransport; + pGsoCtx->u8Unused = 0; + + return true; +} + +/** + * Forward the socket buffer as a GSO internal network frame. + * + * @returns IPRT status code. + * @param pThis The net filter instance. + * @param pSkb The GSO socket buffer. + * @param fSrc The source. + * @param pGsoCtx Where to return the GSO context on success. + */ +static int vboxNetFltLinuxForwardAsGso(PVBOXNETFLTINS pThis, struct sk_buff *pSkb, uint32_t fSrc, PCPDMNETWORKGSO pGsoCtx) +{ + int rc; + unsigned cbExtra; + unsigned cSegs = vboxNetFltLinuxCalcSGSegments(pSkb, &cbExtra); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]) + cbExtra); + if (RT_LIKELY(pSG)) + { + vboxNetFltLinuxSkBufToSG(pThis, pSkb, pSG, cbExtra, cSegs, fSrc, pGsoCtx); + + vboxNetFltDumpPacket(pSG, false, (fSrc & INTNETTRUNKDIR_HOST) ? "host" : "wire", 1); + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + + vboxNetFltLinuxDestroySG(pSG, pSkb); + rc = VINF_SUCCESS; + } + else + { + Log(("VBoxNetFlt: Dropping the sk_buff (failure case).\n")); + rc = VERR_NO_MEMORY; + } + return rc; +} + +#endif /* VBOXNETFLT_WITH_GSO_RECV */ + +/** + * Worker for vboxNetFltLinuxForwardToIntNet. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY. + * @param pThis The net filter instance. + * @param pBuf The socket buffer. + * @param fSrc The source. + */ +static int vboxNetFltLinuxForwardSegment(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, uint32_t fSrc) +{ + int rc; + unsigned cbExtra; + unsigned cSegs = vboxNetFltLinuxCalcSGSegments(pBuf, &cbExtra); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]) + cbExtra); + if (RT_LIKELY(pSG)) + { + vboxNetFltLinuxSkBufToSG(pThis, pBuf, pSG, cbExtra, cSegs, fSrc, NULL /*pGsoCtx*/); + + vboxNetFltDumpPacket(pSG, false, (fSrc & INTNETTRUNKDIR_HOST) ? "host" : "wire", 1); + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + + vboxNetFltLinuxDestroySG(pSG, pBuf); + rc = VINF_SUCCESS; + } + else + { + Log(("VBoxNetFlt: Failed to allocate SG buffer.\n")); + rc = VERR_NO_MEMORY; + } + return rc; +} + + +/** + * I won't disclose what I do, figure it out yourself, including pThis referencing. + * + * @param pThis The net filter instance. + * @param pBuf The socket buffer. + * @param fSrc Where the packet comes from. + */ +static void vboxNetFltLinuxForwardToIntNetInner(PVBOXNETFLTINS pThis, struct sk_buff *pBuf, uint32_t fSrc) +{ +#ifdef VBOXNETFLT_WITH_GSO + if (skb_is_gso(pBuf)) + { + PDMNETWORKGSO GsoCtx; + Log6(("vboxNetFltLinuxForwardToIntNetInner: skb len=%u data_len=%u truesize=%u next=%p" + " nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x ip_summed=%d\n", + pBuf->len, pBuf->data_len, pBuf->truesize, pBuf->next, + skb_shinfo(pBuf)->nr_frags, skb_shinfo(pBuf)->gso_size, + skb_shinfo(pBuf)->gso_segs, skb_shinfo(pBuf)->gso_type, + skb_shinfo(pBuf)->frag_list, pBuf->pkt_type, pBuf->ip_summed)); + + if (RT_LIKELY(fSrc & INTNETTRUNKDIR_HOST)) + { + /* + * skb_gso_segment does the following. Do we need to do it as well? + */ +# if RTLNX_VER_MIN(2,6,22) + skb_reset_mac_header(pBuf); + pBuf->mac_len = pBuf->network_header - pBuf->mac_header; +# else + pBuf->mac.raw = pBuf->data; + pBuf->mac_len = pBuf->nh.raw - pBuf->data; +# endif + } + +# ifdef VBOXNETFLT_WITH_GSO_RECV + if ( (skb_shinfo(pBuf)->gso_type & (SKB_GSO_TCPV6 | SKB_GSO_TCPV4)) + && vboxNetFltLinuxCanForwardAsGso(pThis, pBuf, fSrc, &GsoCtx) ) + vboxNetFltLinuxForwardAsGso(pThis, pBuf, fSrc, &GsoCtx); + else +# endif /* VBOXNETFLT_WITH_GSO_RECV */ + { + /* Need to segment the packet */ + struct sk_buff *pNext; + struct sk_buff *pSegment = skb_gso_segment(pBuf, 0 /*supported features*/); + if (IS_ERR(pSegment)) + { + LogRel(("VBoxNetFlt: Failed to segment a packet (%d).\n", PTR_ERR(pSegment))); + return; + } + + for (; pSegment; pSegment = pNext) + { + Log6(("vboxNetFltLinuxForwardToIntNetInner: segment len=%u data_len=%u truesize=%u next=%p" + " nr_frags=%u gso_size=%u gso_seqs=%u gso_type=%x frag_list=%p pkt_type=%x\n", + pSegment->len, pSegment->data_len, pSegment->truesize, pSegment->next, + skb_shinfo(pSegment)->nr_frags, skb_shinfo(pSegment)->gso_size, + skb_shinfo(pSegment)->gso_segs, skb_shinfo(pSegment)->gso_type, + skb_shinfo(pSegment)->frag_list, pSegment->pkt_type)); + pNext = pSegment->next; + pSegment->next = 0; + vboxNetFltLinuxForwardSegment(pThis, pSegment, fSrc); + dev_kfree_skb(pSegment); + } + } + } + else +#endif /* VBOXNETFLT_WITH_GSO */ + { + Log6(("vboxNetFltLinuxForwardToIntNetInner: ptk_type=%d ip_summed=%d len=%d" + " data_len=%d headroom=%d hdr_len=%d csum_offset=%d\n", + pBuf->pkt_type, pBuf->ip_summed, pBuf->len, pBuf->data_len, skb_headroom(pBuf), + skb_headlen(pBuf), vboxNetFltLinuxGetChecksumStartOffset(pBuf))); +#ifndef VBOXNETFLT_SG_SUPPORT + if (pBuf->ip_summed == CHECKSUM_PARTIAL && pBuf->pkt_type == PACKET_OUTGOING) + { +# if RTLNX_VER_MIN(2,6,19) + int rc = VBOX_SKB_CHECKSUM_HELP(pBuf); +# else + /* + * Try to work around the problem with CentOS 4.7 and 5.2 (2.6.9 + * and 2.6.18 kernels), they pass wrong 'h' pointer down. We take IP + * header length from the header itself and reconstruct 'h' pointer + * to TCP (or whatever) header. + */ + unsigned char *tmp = pBuf->h.raw; + if (pBuf->h.raw == pBuf->nh.raw && pBuf->protocol == htons(ETH_P_IP)) + pBuf->h.raw = pBuf->nh.raw + pBuf->nh.iph->ihl * 4; + int rc = VBOX_SKB_CHECKSUM_HELP(pBuf); + /* Restore the original (wrong) pointer. */ + pBuf->h.raw = tmp; +# endif + if (rc) + { + LogRel(("VBoxNetFlt: Failed to compute checksum, dropping the packet.\n")); + return; + } + } +#endif /* !VBOXNETFLT_SG_SUPPORT */ + vboxNetFltLinuxForwardSegment(pThis, pBuf, fSrc); + } +} + + +/** + * Temporarily adjust pBuf->data so it always points to the Ethernet header, + * then forward it to the internal network. + * + * @param pThis The net filter instance. + * @param pBuf The socket buffer. This is consumed by this function. + */ +static void vboxNetFltLinuxForwardToIntNet(PVBOXNETFLTINS pThis, struct sk_buff *pBuf) +{ + uint32_t fSrc = pBuf->pkt_type == PACKET_OUTGOING ? INTNETTRUNKDIR_HOST : INTNETTRUNKDIR_WIRE; + + if (RT_UNLIKELY(fSrc & INTNETTRUNKDIR_WIRE)) + { + /* + * The packet came from the wire and the driver has already consumed + * mac header. We need to restore it back. Moreover, after we are + * through with this skb we need to restore its original state! + */ + skb_push(pBuf, pBuf->mac_len); + Log5(("vboxNetFltLinuxForwardToIntNet: mac_len=%d data=%p mac_header=%p network_header=%p\n", + pBuf->mac_len, pBuf->data, skb_mac_header(pBuf), skb_network_header(pBuf))); + } + + vboxNetFltLinuxForwardToIntNetInner(pThis, pBuf, fSrc); + + /* + * Restore the original state of skb as there are other handlers this skb + * will be provided to. + */ + if (RT_UNLIKELY(fSrc & INTNETTRUNKDIR_WIRE)) + skb_pull(pBuf, pBuf->mac_len); + + dev_kfree_skb(pBuf); +} + + +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE +/** + * Work queue handler that forwards the socket buffers queued by + * vboxNetFltLinuxPacketHandler to the internal network. + * + * @param pWork The work queue. + */ +# if RTLNX_VER_MIN(2,6,20) +static void vboxNetFltLinuxXmitTask(struct work_struct *pWork) +# else +static void vboxNetFltLinuxXmitTask(void *pWork) +# endif +{ + PVBOXNETFLTINS pThis = VBOX_FLT_XT_TO_INST(pWork); + struct sk_buff *pBuf; + + Log6(("vboxNetFltLinuxXmitTask: Got work %p.\n", pWork)); + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (vboxNetFltTryRetainBusyActive(pThis)) + { + while ((pBuf = skb_dequeue(&pThis->u.s.XmitQueue)) != NULL) + vboxNetFltLinuxForwardToIntNet(pThis, pBuf); + + vboxNetFltRelease(pThis, true /* fBusy */); + } + else + { + /** @todo Shouldn't we just drop the packets here? There is little point in + * making them accumulate when the VM is paused and it'll only waste + * kernel memory anyway... Hmm. maybe wait a short while (2-5 secs) + * before start draining the packets (goes for the intnet ring buf + * too)? */ + } +} +#endif /* !VBOXNETFLT_LINUX_NO_XMIT_QUEUE */ + +/** + * Reports the GSO capabilities of the hardware NIC. + * + * @param pThis The net filter instance. The caller hold a + * reference to this. + */ +static void vboxNetFltLinuxReportNicGsoCapabilities(PVBOXNETFLTINS pThis) +{ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_WIRE) || defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + struct net_device *pDev; + unsigned int fFeatures; + + RTSpinlockAcquire(pThis->hSpinlock); + + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev) + fFeatures = pDev->features; + else + fFeatures = 0; + + RTSpinlockRelease(pThis->hSpinlock); + + if (pThis->pSwitchPort) + { + /* Set/update the GSO capabilities of the NIC. */ + uint32_t fGsoCapabilites = 0; + if (fFeatures & NETIF_F_TSO) + fGsoCapabilites |= RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP); + if (fFeatures & NETIF_F_TSO6) + fGsoCapabilites |= RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP); + Log3(("vboxNetFltLinuxReportNicGsoCapabilities: reporting wire %s%s\n", + (fGsoCapabilites & RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP)) ? "tso " : "", + (fGsoCapabilites & RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP)) ? "tso6 " : "")); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, fGsoCapabilites, INTNETTRUNKDIR_WIRE); + } + + vboxNetFltRelease(pThis, true /*fBusy*/); + } +#endif /* VBOXNETFLT_WITH_GSO_XMIT_WIRE || VBOXNETFLT_WITH_GSO_XMIT_HOST */ +} + +/** + * Helper that determines whether the host (ignoreing us) is operating the + * interface in promiscuous mode or not. + */ +static bool vboxNetFltLinuxPromiscuous(PVBOXNETFLTINS pThis) +{ + bool fRc = false; + struct net_device * pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + fRc = !!(pDev->promiscuity - (ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet) & 1)); + LogFlow(("vboxNetFltPortOsIsPromiscuous: returns %d, pDev->promiscuity=%d, fPromiscuousSet=%d\n", + fRc, pDev->promiscuity, pThis->u.s.fPromiscuousSet)); + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + return fRc; +} + +/** + * Does this device needs link state change signaled? + * Currently we need it for our own VBoxNetAdp and TAP. + */ +static bool vboxNetFltNeedsLinkState(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + if (pDev->ethtool_ops && pDev->ethtool_ops->get_drvinfo) + { + struct ethtool_drvinfo Info; + + memset(&Info, 0, sizeof(Info)); + Info.cmd = ETHTOOL_GDRVINFO; + pDev->ethtool_ops->get_drvinfo(pDev, &Info); + Log3(("%s: driver=%.*s version=%.*s bus_info=%.*s\n", + __FUNCTION__, + sizeof(Info.driver), Info.driver, + sizeof(Info.version), Info.version, + sizeof(Info.bus_info), Info.bus_info)); + + if (!strncmp(Info.driver, "vboxnet", sizeof(Info.driver))) + return true; + +#if RTLNX_VER_MIN(2,6,36) /* TAP started doing carrier */ + return !strncmp(Info.driver, "tun", 4) + && !strncmp(Info.bus_info, "tap", 4); +#endif + } + + return false; +} + +#if RTLNX_VER_MAX(2,6,18) +DECLINLINE(void) netif_tx_lock_bh(struct net_device *pDev) +{ + spin_lock_bh(&pDev->xmit_lock); +} + +DECLINLINE(void) netif_tx_unlock_bh(struct net_device *pDev) +{ + spin_unlock_bh(&pDev->xmit_lock); +} +#endif + +/** + * Some devices need link state change when filter attaches/detaches + * since the filter is their link in a sense. + */ +static void vboxNetFltSetLinkState(PVBOXNETFLTINS pThis, struct net_device *pDev, bool fLinkUp) +{ + if (vboxNetFltNeedsLinkState(pThis, pDev)) + { + Log3(("%s: bringing device link %s\n", + __FUNCTION__, fLinkUp ? "up" : "down")); + netif_tx_lock_bh(pDev); + if (fLinkUp) + netif_carrier_on(pDev); + else + netif_carrier_off(pDev); + netif_tx_unlock_bh(pDev); + } +} + +/** + * Internal worker for vboxNetFltLinuxNotifierCallback. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pDev The device to attach to. + */ +static int vboxNetFltLinuxAttachToInterface(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + LogFlow(("vboxNetFltLinuxAttachToInterface: pThis=%p (%s)\n", pThis, pThis->szName)); + + /* + * Retain and store the device. + */ + dev_hold(pDev); + + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWritePtr(&pThis->u.s.pDev, pDev); + RTSpinlockRelease(pThis->hSpinlock); + + Log(("vboxNetFltLinuxAttachToInterface: Device %p(%s) retained. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + Log(("vboxNetFltLinuxAttachToInterface: Got pDev=%p pThis=%p pThis->u.s.pDev=%p\n", + pDev, pThis, ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *))); + + /* Get the mac address while we still have a valid net_device reference. */ + memcpy(&pThis->u.s.MacAddr, pDev->dev_addr, sizeof(pThis->u.s.MacAddr)); + /* Initialize MTU */ + pThis->u.s.cbMtu = pDev->mtu; + + /* + * Install a packet filter for this device with a protocol wildcard (ETH_P_ALL). + */ + pThis->u.s.PacketType.type = __constant_htons(ETH_P_ALL); + pThis->u.s.PacketType.dev = pDev; + pThis->u.s.PacketType.func = vboxNetFltLinuxPacketHandler; + dev_add_pack(&pThis->u.s.PacketType); + ASMAtomicUoWriteBool(&pThis->u.s.fPacketHandler, true); + Log(("vboxNetFltLinuxAttachToInterface: this=%p: Packet handler installed.\n", pThis)); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxHookDev(pThis, pDev); +#endif + + /* + * Are we the "carrier" for this device (e.g. vboxnet or tap)? + */ + vboxNetFltSetLinkState(pThis, pDev, true); + + /* + * Set indicators that require the spinlock. Be abit paranoid about racing + * the device notification handle. + */ + RTSpinlockAcquire(pThis->hSpinlock); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + if (pDev) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + ASMAtomicUoWriteBool(&pThis->u.s.fRegistered, true); + pDev = NULL; /* don't dereference it */ + } + RTSpinlockRelease(pThis->hSpinlock); + + /* + * Report GSO capabilities + */ + Assert(pThis->pSwitchPort); + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, vboxNetFltLinuxPromiscuous(pThis)); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + + LogRel(("VBoxNetFlt: attached to '%s' / %RTmac\n", pThis->szName, &pThis->u.s.MacAddr)); + return VINF_SUCCESS; +} + + +static int vboxNetFltLinuxUnregisterDevice(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + bool fRegistered; + Assert(!pThis->fDisconnectedFromHost); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxUnhookDev(pThis, pDev); +#endif + + if (ASMAtomicCmpXchgBool(&pThis->u.s.fPacketHandler, false, true)) + { + dev_remove_pack(&pThis->u.s.PacketType); + Log(("vboxNetFltLinuxUnregisterDevice: this=%p: packet handler removed.\n", pThis)); + } + + RTSpinlockAcquire(pThis->hSpinlock); + fRegistered = ASMAtomicXchgBool(&pThis->u.s.fRegistered, false); + if (fRegistered) + { + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + ASMAtomicUoWriteNullPtr(&pThis->u.s.pDev); + } + RTSpinlockRelease(pThis->hSpinlock); + + if (fRegistered) + { +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_purge(&pThis->u.s.XmitQueue); +#endif + Log(("vboxNetFltLinuxUnregisterDevice: this=%p: xmit queue purged.\n", pThis)); + Log(("vboxNetFltLinuxUnregisterDevice: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + dev_put(pDev); + } + + return NOTIFY_OK; +} + +static int vboxNetFltLinuxDeviceIsUp(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + /* Check if we are not suspended and promiscuous mode has not been set. */ + if ( pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE + && !ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet)) + { + /* Note that there is no need for locking as the kernel got hold of the lock already. */ + dev_set_promiscuity(pDev, 1); + ASMAtomicWriteBool(&pThis->u.s.fPromiscuousSet, true); + Log(("vboxNetFltLinuxDeviceIsUp: enabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + Log(("vboxNetFltLinuxDeviceIsUp: no need to enable promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + return NOTIFY_OK; +} + +static int vboxNetFltLinuxDeviceGoingDown(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + /* Undo promiscuous mode if we has set it. */ + if (ASMAtomicUoReadBool(&pThis->u.s.fPromiscuousSet)) + { + /* Note that there is no need for locking as the kernel got hold of the lock already. */ + dev_set_promiscuity(pDev, -1); + ASMAtomicWriteBool(&pThis->u.s.fPromiscuousSet, false); + Log(("vboxNetFltLinuxDeviceGoingDown: disabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + Log(("vboxNetFltLinuxDeviceGoingDown: no need to disable promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + return NOTIFY_OK; +} + +/** + * Callback for listening to MTU change event. + * + * We need to track changes of host's inteface MTU to discard over-sized frames + * coming from the internal network as they may hang the TX queue of host's + * adapter. + * + * @returns NOTIFY_OK + * @param pThis The netfilter instance. + * @param pDev Pointer to device structure of host's interface. + */ +static int vboxNetFltLinuxDeviceMtuChange(PVBOXNETFLTINS pThis, struct net_device *pDev) +{ + ASMAtomicWriteU32(&pThis->u.s.cbMtu, pDev->mtu); + Log(("vboxNetFltLinuxDeviceMtuChange: set MTU for %s to %d\n", pThis->szName, pDev->mtu)); + return NOTIFY_OK; +} + +#ifdef LOG_ENABLED +/** Stringify the NETDEV_XXX constants. */ +static const char *vboxNetFltLinuxGetNetDevEventName(unsigned long ulEventType) +{ + const char *pszEvent = "NETDEV_"; + switch (ulEventType) + { + case NETDEV_REGISTER: pszEvent = "NETDEV_REGISTER"; break; + case NETDEV_UNREGISTER: pszEvent = "NETDEV_UNREGISTER"; break; + case NETDEV_UP: pszEvent = "NETDEV_UP"; break; + case NETDEV_DOWN: pszEvent = "NETDEV_DOWN"; break; + case NETDEV_REBOOT: pszEvent = "NETDEV_REBOOT"; break; + case NETDEV_CHANGENAME: pszEvent = "NETDEV_CHANGENAME"; break; + case NETDEV_CHANGE: pszEvent = "NETDEV_CHANGE"; break; + case NETDEV_CHANGEMTU: pszEvent = "NETDEV_CHANGEMTU"; break; + case NETDEV_CHANGEADDR: pszEvent = "NETDEV_CHANGEADDR"; break; + case NETDEV_GOING_DOWN: pszEvent = "NETDEV_GOING_DOWN"; break; +# ifdef NETDEV_FEAT_CHANGE + case NETDEV_FEAT_CHANGE: pszEvent = "NETDEV_FEAT_CHANGE"; break; +# endif + } + return pszEvent; +} +#endif /* LOG_ENABLED */ + +/** + * Callback for listening to netdevice events. + * + * This works the rediscovery, clean up on unregistration, promiscuity on + * up/down, and GSO feature changes from ethtool. + * + * @returns NOTIFY_OK + * @param self Pointer to our notifier registration block. + * @param ulEventType The event. + * @param ptr Event specific, but it is usually the device it + * relates to. + */ +static int vboxNetFltLinuxNotifierCallback(struct notifier_block *self, unsigned long ulEventType, void *ptr) + +{ + PVBOXNETFLTINS pThis = VBOX_FLT_NB_TO_INST(self); + struct net_device *pMyDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + struct net_device *pDev = VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr); + int rc = NOTIFY_OK; + + Log(("VBoxNetFlt: got event %s(0x%lx) on %s, pDev=%p pThis=%p pThis->u.s.pDev=%p\n", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, pDev->name, pDev, pThis, pMyDev)); + + if (ulEventType == NETDEV_REGISTER) + { +#if RTLNX_VER_MIN(2,6,24) /* cgroups/namespaces introduced */ +# if RTLNX_VER_MIN(2,6,26) +# define VBOX_DEV_NET(dev) dev_net(dev) +# define VBOX_NET_EQ(n1, n2) net_eq((n1), (n2)) +# else +# define VBOX_DEV_NET(dev) ((dev)->nd_net) +# define VBOX_NET_EQ(n1, n2) ((n1) == (n2)) +# endif + struct net *pMyNet = current->nsproxy->net_ns; + struct net *pDevNet = VBOX_DEV_NET(pDev); + + if (VBOX_NET_EQ(pDevNet, pMyNet)) +#endif /* namespaces */ + { + if (strcmp(pDev->name, pThis->szName) == 0) + { + vboxNetFltLinuxAttachToInterface(pThis, pDev); + } + } + } + else + { + if (pDev == pMyDev) + { + switch (ulEventType) + { + case NETDEV_UNREGISTER: + rc = vboxNetFltLinuxUnregisterDevice(pThis, pDev); + break; + case NETDEV_UP: + rc = vboxNetFltLinuxDeviceIsUp(pThis, pDev); + break; + case NETDEV_GOING_DOWN: + rc = vboxNetFltLinuxDeviceGoingDown(pThis, pDev); + break; + case NETDEV_CHANGEMTU: + rc = vboxNetFltLinuxDeviceMtuChange(pThis, pDev); + break; + case NETDEV_CHANGENAME: + break; +#ifdef NETDEV_FEAT_CHANGE + case NETDEV_FEAT_CHANGE: + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + break; +#endif + } + } + } + + return rc; +} + +/* + * Initial enumeration of netdevs. Called with NETDEV_REGISTER by + * register_netdevice_notifier() under rtnl lock. + */ +static int vboxNetFltLinuxEnumeratorCallback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = ((PVBOXNETFLTNOTIFIER)self)->pThis; + struct net_device *dev = VBOX_NETDEV_NOTIFIER_INFO_TO_DEV(ptr); + struct in_device *in_dev; + struct inet6_dev *in6_dev; + + if (ulEventType != NETDEV_REGISTER) + return NOTIFY_OK; + + if (RT_UNLIKELY(pThis->pSwitchPort->pfnNotifyHostAddress == NULL)) + return NOTIFY_OK; + + /* + * IPv4 + */ +#if RTLNX_VER_MIN(2,6,14) + in_dev = __in_dev_get_rtnl(dev); +#else + in_dev = __in_dev_get(dev); +#endif + if (in_dev != NULL) + { + struct in_ifaddr *ifa; + + for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { + if (VBOX_IPV4_IS_LOOPBACK(ifa->ifa_address)) + return NOTIFY_OK; + + if ( dev != pThis->u.s.pDev + && VBOX_IPV4_IS_LINKLOCAL_169(ifa->ifa_address)) + continue; + + Log(("%s: %s: IPv4 addr %RTnaipv4 mask %RTnaipv4\n", + __FUNCTION__, VBOX_NETDEV_NAME(dev), + ifa->ifa_address, ifa->ifa_mask)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv4, &ifa->ifa_address); + } + } + + /* + * IPv6 + */ + in6_dev = __in6_dev_get(dev); + if (in6_dev != NULL) + { + struct inet6_ifaddr *ifa; + + read_lock_bh(&in6_dev->lock); +#if RTLNX_VER_MIN(2,6,35) + list_for_each_entry(ifa, &in6_dev->addr_list, if_list) +#else + for (ifa = in6_dev->addr_list; ifa != NULL; ifa = ifa->if_next) +#endif + { + if ( dev != pThis->u.s.pDev + && ipv6_addr_type(&ifa->addr) & (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK)) + continue; + + Log(("%s: %s: IPv6 addr %RTnaipv6/%u\n", + __FUNCTION__, VBOX_NETDEV_NAME(dev), + &ifa->addr, (unsigned)ifa->prefix_len)); + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, + /* :fAdded */ true, kIntNetAddrType_IPv6, &ifa->addr); + } + read_unlock_bh(&in6_dev->lock); + } + + return NOTIFY_OK; +} + + +static int vboxNetFltLinuxNotifierIPv4Callback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = RT_FROM_MEMBER(self, VBOXNETFLTINS, u.s.NotifierIPv4); + struct net_device *pDev, *pEventDev; + struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; + bool fMyDev; + int rc = NOTIFY_OK; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + pEventDev = ifa->ifa_dev->dev; + fMyDev = (pDev == pEventDev); + Log(("VBoxNetFlt: %s: IPv4 event %s(0x%lx) %s: addr %RTnaipv4 mask %RTnaipv4\n", + pDev ? VBOX_NETDEV_NAME(pDev) : "", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, + pEventDev ? VBOX_NETDEV_NAME(pEventDev) : "", + ifa->ifa_address, ifa->ifa_mask)); + + if (pDev != NULL) + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + + if (VBOX_IPV4_IS_LOOPBACK(ifa->ifa_address)) + return NOTIFY_OK; + + if ( !fMyDev + && VBOX_IPV4_IS_LINKLOCAL_169(ifa->ifa_address)) + return NOTIFY_OK; + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + bool fAdded; + if (ulEventType == NETDEV_UP) + fAdded = true; + else if (ulEventType == NETDEV_DOWN) + fAdded = false; + else + return NOTIFY_OK; + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, + kIntNetAddrType_IPv4, &ifa->ifa_local); + } + + return rc; +} + + +static int vboxNetFltLinuxNotifierIPv6Callback(struct notifier_block *self, unsigned long ulEventType, void *ptr) +{ + PVBOXNETFLTINS pThis = RT_FROM_MEMBER(self, VBOXNETFLTINS, u.s.NotifierIPv6); + struct net_device *pDev, *pEventDev; + struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr; + bool fMyDev; + int rc = NOTIFY_OK; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + pEventDev = ifa->idev->dev; + fMyDev = (pDev == pEventDev); + Log(("VBoxNetFlt: %s: IPv6 event %s(0x%lx) %s: %RTnaipv6\n", + pDev ? VBOX_NETDEV_NAME(pDev) : "", + vboxNetFltLinuxGetNetDevEventName(ulEventType), ulEventType, + pEventDev ? VBOX_NETDEV_NAME(pEventDev) : "", + &ifa->addr)); + + if (pDev != NULL) + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + + if ( !fMyDev + && ipv6_addr_type(&ifa->addr) & (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_LOOPBACK)) + return NOTIFY_OK; + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + bool fAdded; + if (ulEventType == NETDEV_UP) + fAdded = true; + else if (ulEventType == NETDEV_DOWN) + fAdded = false; + else + return NOTIFY_OK; + + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, + kIntNetAddrType_IPv6, &ifa->addr); + } + + return rc; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + struct net_device * pDev; + int err; + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + NOREF(pvIfData); + + LogFlow(("vboxNetFltPortOsXmit: pThis=%p (%s)\n", pThis, pThis->szName)); + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + /* + * Create a sk_buff for the gather list and push it onto the wire. + */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + struct sk_buff *pBuf = vboxNetFltLinuxSkBufFromSG(pThis, pSG, true); + if (pBuf) + { + vboxNetFltDumpPacket(pSG, true, "wire", 1); + Log6(("vboxNetFltPortOsXmit: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + Log6(("vboxNetFltPortOsXmit: dev_queue_xmit(%p)\n", pBuf)); + err = dev_queue_xmit(pBuf); + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + /* + * Create a sk_buff for the gather list and push it onto the host stack. + */ + if (fDst & INTNETTRUNKDIR_HOST) + { + struct sk_buff *pBuf = vboxNetFltLinuxSkBufFromSG(pThis, pSG, false); + if (pBuf) + { + vboxNetFltDumpPacket(pSG, true, "host", (fDst & INTNETTRUNKDIR_WIRE) ? 0 : 1); + Log6(("vboxNetFltPortOsXmit: pBuf->cb dump:\n%.*Rhxd\n", sizeof(pBuf->cb), pBuf->cb)); + Log6(("vboxNetFltPortOsXmit: netif_rx_ni(%p)\n", pBuf)); +#if RTLNX_VER_MIN(5,18,0) || RTLNX_RHEL_MIN(9,1) + local_bh_disable(); + err = netif_rx(pBuf); + local_bh_enable(); +#else + err = netif_rx_ni(pBuf); +#endif + if (err) + rc = RTErrConvertFromErrno(err); + } + else + rc = VERR_NO_MEMORY; + } + + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + struct net_device *pDev; + IPRT_LINUX_SAVE_EFL_AC(); + + LogFlow(("vboxNetFltPortOsSetActive: pThis=%p (%s), fActive=%RTbool, fDisablePromiscuous=%RTbool\n", + pThis, pThis->szName, fActive, pThis->fDisablePromiscuous)); + + if (pThis->fDisablePromiscuous) + return; + + pDev = vboxNetFltLinuxRetainNetDev(pThis); + if (pDev) + { + /* + * This api is a bit weird, the best reference is the code. + * + * Also, we have a bit or race conditions wrt the maintenance of + * host the interface promiscuity for vboxNetFltPortOsIsPromiscuous. + */ +#ifdef LOG_ENABLED + u_int16_t fIf; + unsigned const cPromiscBefore = pDev->promiscuity; +#endif + if (fActive) + { + Assert(!pThis->u.s.fPromiscuousSet); + + rtnl_lock(); + dev_set_promiscuity(pDev, 1); + rtnl_unlock(); + pThis->u.s.fPromiscuousSet = true; + Log(("vboxNetFltPortOsSetActive: enabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + else + { + if (pThis->u.s.fPromiscuousSet) + { + rtnl_lock(); + dev_set_promiscuity(pDev, -1); + rtnl_unlock(); + Log(("vboxNetFltPortOsSetActive: disabled promiscuous mode on %s (%d)\n", pThis->szName, pDev->promiscuity)); + } + pThis->u.s.fPromiscuousSet = false; + +#ifdef LOG_ENABLED + fIf = dev_get_flags(pDev); + Log(("VBoxNetFlt: fIf=%#x; %d->%d\n", fIf, cPromiscBefore, pDev->promiscuity)); +#endif + } + + vboxNetFltLinuxReleaseNetDev(pThis, pDev); + } + IPRT_LINUX_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + /* + * Remove packet handler when we get disconnected from internal switch as + * we don't want the handler to forward packets to disconnected switch. + */ + if (ASMAtomicCmpXchgBool(&pThis->u.s.fPacketHandler, false, true)) + { + IPRT_LINUX_SAVE_EFL_AC(); + dev_remove_pack(&pThis->u.s.PacketType); + Log(("vboxNetFltOsDisconnectIt: this=%p: Packet handler removed.\n", pThis)); + IPRT_LINUX_RESTORE_EFL_AC(); + } + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Report the GSO capabilities of the host and device (if connected). + * Note! No need to mark ourselves busy here. + */ + /** @todo duplicate work here now? Attach */ +#if defined(VBOXNETFLT_WITH_GSO_XMIT_HOST) + Log3(("vboxNetFltOsConnectIt: reporting host tso tso6\n")); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, + 0 + | RT_BIT_32(PDMNETWORKGSOTYPE_IPV4_TCP) + | RT_BIT_32(PDMNETWORKGSOTYPE_IPV6_TCP) + , INTNETTRUNKDIR_HOST); + +#endif + vboxNetFltLinuxReportNicGsoCapabilities(pThis); + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + struct net_device *pDev; + bool fRegistered; + IPRT_LINUX_SAVE_EFL_AC(); + +#ifdef VBOXNETFLT_WITH_HOST2WIRE_FILTER + vboxNetFltLinuxUnhookDev(pThis, NULL); +#endif + + /** @todo This code may race vboxNetFltLinuxUnregisterDevice (very very + * unlikely, but none the less). Since it doesn't actually update the + * state (just reads it), it is likely to panic in some interesting + * ways. */ + + RTSpinlockAcquire(pThis->hSpinlock); + pDev = ASMAtomicUoReadPtrT(&pThis->u.s.pDev, struct net_device *); + fRegistered = ASMAtomicXchgBool(&pThis->u.s.fRegistered, false); + RTSpinlockRelease(pThis->hSpinlock); + + if (fRegistered) + { + vboxNetFltSetLinkState(pThis, pDev, false); + +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_purge(&pThis->u.s.XmitQueue); +#endif + Log(("vboxNetFltOsDeleteInstance: this=%p: xmit queue purged.\n", pThis)); + Log(("vboxNetFltOsDeleteInstance: Device %p(%s) released. ref=%d\n", + pDev, pDev->name, +#if RTLNX_VER_MIN(2,6,37) + netdev_refcnt_read(pDev) +#else + atomic_read(&pDev->refcnt) +#endif + )); + dev_put(pDev); + } + + unregister_inet6addr_notifier(&pThis->u.s.NotifierIPv6); + unregister_inetaddr_notifier(&pThis->u.s.NotifierIPv4); + + Log(("vboxNetFltOsDeleteInstance: this=%p: Notifier removed.\n", pThis)); + unregister_netdevice_notifier(&pThis->u.s.Notifier); + module_put(THIS_MODULE); + + IPRT_LINUX_RESTORE_EFL_AC(); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + int err; + IPRT_LINUX_SAVE_EFL_AC(); + NOREF(pvContext); + + pThis->u.s.Notifier.notifier_call = vboxNetFltLinuxNotifierCallback; + err = register_netdevice_notifier(&pThis->u.s.Notifier); + if (err) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_FAILED; + } + if (!pThis->u.s.fRegistered) + { + unregister_netdevice_notifier(&pThis->u.s.Notifier); + LogRel(("VBoxNetFlt: failed to find %s.\n", pThis->szName)); + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_NOT_FOUND; + } + + Log(("vboxNetFltOsInitInstance: this=%p: Notifier installed.\n", pThis)); + if ( pThis->fDisconnectedFromHost + || !try_module_get(THIS_MODULE)) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INTNET_FLT_IF_FAILED; + } + + if (pThis->pSwitchPort->pfnNotifyHostAddress) + { + VBOXNETFLTNOTIFIER Enumerator; + + /* + * register_inetaddr_notifier() and register_inet6addr_notifier() + * do not call the callback for existing devices. Enumerating + * all network devices explicitly is a bit of an ifdef mess, + * so co-opt register_netdevice_notifier() to do that for us. + */ + RT_ZERO(Enumerator); + Enumerator.Notifier.notifier_call = vboxNetFltLinuxEnumeratorCallback; + Enumerator.pThis = pThis; + + err = register_netdevice_notifier(&Enumerator.Notifier); + if (err) + { + LogRel(("%s: failed to enumerate network devices: error %d\n", __FUNCTION__, err)); + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; + } + + unregister_netdevice_notifier(&Enumerator.Notifier); + + pThis->u.s.NotifierIPv4.notifier_call = vboxNetFltLinuxNotifierIPv4Callback; + err = register_inetaddr_notifier(&pThis->u.s.NotifierIPv4); + if (err) + LogRel(("%s: failed to register IPv4 notifier: error %d\n", __FUNCTION__, err)); + + pThis->u.s.NotifierIPv6.notifier_call = vboxNetFltLinuxNotifierIPv6Callback; + err = register_inet6addr_notifier(&pThis->u.s.NotifierIPv6); + if (err) + LogRel(("%s: failed to register IPv6 notifier: error %d\n", __FUNCTION__, err)); + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Init the linux specific members. + */ + ASMAtomicUoWriteNullPtr(&pThis->u.s.pDev); + pThis->u.s.fRegistered = false; + pThis->u.s.fPromiscuousSet = false; + pThis->u.s.fPacketHandler = false; + memset(&pThis->u.s.PacketType, 0, sizeof(pThis->u.s.PacketType)); +#ifndef VBOXNETFLT_LINUX_NO_XMIT_QUEUE + skb_queue_head_init(&pThis->u.s.XmitQueue); +# if RTLNX_VER_MIN(2,6,20) + INIT_WORK(&pThis->u.s.XmitTask, vboxNetFltLinuxXmitTask); +# else + INIT_WORK(&pThis->u.s.XmitTask, vboxNetFltLinuxXmitTask, &pThis->u.s.XmitTask); +# endif +#endif + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + NOREF(pThis); NOREF(pvIfData); NOREF(pMac); +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIf); NOREF(pvIfData); + return VINF_SUCCESS; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIfData); + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt b/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt new file mode 100755 index 00000000..f2f8f1f4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/linux/files_vboxnetflt @@ -0,0 +1,113 @@ +#!/bin/sh +# $Id: files_vboxnetflt $ +## @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 . +# +# 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 +# + +VBOX_VBOXNETFLT_SOURCES=" \ + ${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/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/heap.h=>include/iprt/heap.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/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/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-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/intnet.h=>include/VBox/intnet.h \ + ${PATH_ROOT}/include/VBox/intnetinline.h=>include/VBox/intnetinline.h \ + ${PATH_ROOT}/include/VBox/vmm/pdmnetinline.h=>include/VBox/vmm/pdmnetinline.h \ + ${PATH_ROOT}/include/VBox/param.h=>include/VBox/param.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.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}/src/VBox/HostDrivers/VBoxNetFlt/linux/VBoxNetFlt-linux.c=>linux/VBoxNetFlt-linux.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFlt.c=>VBoxNetFlt.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxNetFlt/VBoxNetFltInternal.h=>VBoxNetFltInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClient.c=>SUPR0IdcClient.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c=>SUPR0IdcClientComponent.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h=>SUPR0IdcClientInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c=>linux/SUPR0IdcClient-linux.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/math/gcc/divdi3.c=>math/gcc/divdi3.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/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c new file mode 100644 index 00000000..f455ec3c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFlt-solaris.c @@ -0,0 +1,4042 @@ +/* $Id: VBoxNetFlt-solaris.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), 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 . + * + * 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_NET_FLT_DRV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define VBOXNETFLT_SOLARIS_IPV6_POLLING +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Workaround for very strange define in sys/user.h +// #define u (curproc->p_user) /* user is now part of proc structure */ +#ifdef u +#undef u +#endif + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxflt" +/** The module descriptions as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox NetDrv" +#define DEVICE_DESC_MOD "VirtualBox NetMod" + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING +/** Driver properties */ +# define VBOXNETFLT_IP6POLLINTERVAL "ipv6-pollinterval" +#endif + +/** Maximum loopback packet queue size per interface */ +#define VBOXNETFLT_LOOPBACK_SIZE 32 + +/** VLAN tag masking, should probably be in IPRT? */ +#define VLAN_ID(vlan) (((vlan) >> 0) & 0x0fffu) +#define VLAN_CFI(vlan) (((vlan) >> 12) & 0x0001u) +#define VLAN_PRI(vlan) (((vlan) >> 13) & 0x0007u) +#define VLAN_TAG(pri,cfi,vid) (((pri) << 13) | ((cfi) << 12) | ((vid) << 0)) + +typedef struct VLANHEADER +{ + uint16_t Type; + uint16_t Data; +} VLANHEADER; +typedef struct VLANHEADER *PVLANHEADER; + + +/********************************************************************************************************************************* +* Global Functions * +*********************************************************************************************************************************/ +/** + * Stream Driver hooks. + */ +static int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppvResult); +static int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +static int VBoxNetFltSolarisQuiesceNotNeeded(dev_info_t *pDip); + +/** + * Stream Module hooks. + */ +static int VBoxNetFltSolarisModOpen(queue_t *pQueue, dev_t *pDev, int fFile, int fStream, cred_t *pCred); +static int VBoxNetFltSolarisModClose(queue_t *pQueue, int fFile, cred_t *pCred); +static int VBoxNetFltSolarisModReadPut(queue_t *pQueue, mblk_t *pMsg); +static int VBoxNetFltSolarisModWritePut(queue_t *pQueue, mblk_t *pMsg); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Streams: module info. + */ +static struct module_info g_VBoxNetFltSolarisModInfo = +{ + 0xbad, /* module id */ + DEVICE_NAME, + 0, /* min. packet size */ + INFPSZ, /* max. packet size */ + 0, /* hi-water mark */ + 0 /* lo-water mark */ +}; + +/** + * Streams: read queue hooks. + */ +static struct qinit g_VBoxNetFltSolarisReadQ = +{ + VBoxNetFltSolarisModReadPut, + NULL, /* service */ + VBoxNetFltSolarisModOpen, + VBoxNetFltSolarisModClose, + NULL, /* admin (reserved) */ + &g_VBoxNetFltSolarisModInfo, + NULL /* module stats */ +}; + +/** + * Streams: write queue hooks. + */ +static struct qinit g_VBoxNetFltSolarisWriteQ = +{ + VBoxNetFltSolarisModWritePut, + NULL, /* service */ + NULL, /* open */ + NULL, /* close */ + NULL, /* admin (reserved) */ + &g_VBoxNetFltSolarisModInfo, + NULL /* module stats */ +}; + +/** + * Streams: IO stream tab. + */ +static struct streamtab g_VBoxNetFltSolarisStreamTab = +{ + &g_VBoxNetFltSolarisReadQ, + &g_VBoxNetFltSolarisWriteQ, + NULL, /* muxread init */ + NULL /* muxwrite init */ +}; + +/** + * cb_ops: driver char/block entry points + */ +static struct cb_ops g_VBoxNetFltSolarisCbOps = +{ + nulldev, /* cb open */ + nulldev, /* cb close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* cb read */ + nodev, /* cb write */ + nodev, /* cb ioctl */ + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + &g_VBoxNetFltSolarisStreamTab, + D_NEW | D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: driver entry/exit and other ops. + */ +static struct dev_ops g_VBoxNetFltSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxNetFltSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxNetFltSolarisAttach, + VBoxNetFltSolarisDetach, + nodev, /* reset */ + &g_VBoxNetFltSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + VBoxNetFltSolarisQuiesceNotNeeded +}; + +/** + * modldrv: export driver specifics to kernel + */ +static struct modldrv g_VBoxNetFltSolarisDriver = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxNetFltSolarisDevOps +}; + +/** + * fmodsw: streams module ops + */ +static struct fmodsw g_VBoxNetFltSolarisModOps = +{ + DEVICE_NAME, + &g_VBoxNetFltSolarisStreamTab, + D_NEW | D_MP | D_MTQPAIR | D_MTOUTPERIM | D_MTOCEXCL +}; + +/** + * modlstrmod: streams module specifics to kernel + */ +static struct modlstrmod g_VBoxNetFltSolarisModule = +{ + &mod_strmodops, /* extern from kernel */ + DEVICE_DESC_MOD " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxNetFltSolarisModOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxNetFltSolarisModLinkage = +{ + MODREV_1, /* loadable module system revision */ + { + &g_VBoxNetFltSolarisDriver, /* streams driver framework */ + &g_VBoxNetFltSolarisModule, /* streams module framework */ + NULL /* terminate array of linkage structures */ + } +}; + +struct vboxnetflt_state_t; + +/** + * vboxnetflt_dladdr_t: DL SAP address format + */ +typedef struct vboxnetflt_dladdr_t +{ + ether_addr_t Mac; + uint16_t SAP; +} vboxnetflt_dladdr_t; + +#define VBOXNETFLT_DLADDRL sizeof(vboxnetflt_dladdr_t) + +/** + * which stream is this? + */ +typedef enum VBOXNETFLTSTREAMTYPE +{ + kUndefined = 0, + kIp4Stream = 0x1b, + kIp6Stream = 0xcc, + kArpStream = 0xab, + kPromiscStream = 0xdf +} VBOXNETFLTSTREAMTYPE; + +/** + * loopback packet identifier + */ +typedef struct VBOXNETFLTPACKETID +{ + struct VBOXNETFLTPACKETID *pNext; + uint16_t cbPacket; + uint16_t Checksum; + RTMAC SrcMac; + RTMAC DstMac; +} VBOXNETFLTPACKETID; +typedef struct VBOXNETFLTPACKETID *PVBOXNETFLTPACKETID; + +/** + * vboxnetflt_stream_t: per-stream data (multiple streams per interface) + */ +typedef struct vboxnetflt_stream_t +{ + int DevMinor; /* minor device no. (for clone) */ + queue_t *pReadQueue; /* read side queue */ + struct vboxnetflt_stream_t *pNext; /* next stream in list */ + PVBOXNETFLTINS volatile pThis; /* the backend instance */ + VBOXNETFLTSTREAMTYPE Type; /* the type of the stream */ +} vboxnetflt_stream_t; + +/** + * vboxnetflt_promisc_stream_t: per-interface dedicated stream data + */ +typedef struct vboxnetflt_promisc_stream_t +{ + vboxnetflt_stream_t Stream; /* dedicated/promiscuous stream */ + bool fPromisc; /* cached promiscuous value */ + bool fRawMode; /* whether raw mode request was successful */ + uint32_t ModeReqId; /* track MIOCTLs for swallowing our fake request acknowledgements */ +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + PRTTIMER pIp6Timer; /* ipv6 stream poll timer for dynamic ipv6 stream attachment */ +#endif + size_t cLoopback; /* loopback queue size list */ + timeout_id_t volatile TimeoutId; /* timeout id of promisc. req */ + PVBOXNETFLTPACKETID pHead; /* loopback packet identifier head */ + PVBOXNETFLTPACKETID pTail; /* loopback packet identifier tail */ +} vboxnetflt_promisc_stream_t; + +typedef struct vboxnetflt_promisc_params_t +{ + PVBOXNETFLTINS pThis; /* the backend instance */ + bool fPromiscOn; /* whether promiscuous req. on or off */ +} vboxnetflt_promisc_params_t; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxNetFltSolarisSetRawMode(vboxnetflt_promisc_stream_t *pPromiscStream); +/* static int vboxNetFltSolarisSetFastMode(queue_t *pQueue); */ + +static int vboxNetFltSolarisPhysAddrReq(queue_t *pQueue); +static void vboxNetFltSolarisCachePhysAddr(PVBOXNETFLTINS pThis, mblk_t *pPhysAddrAckMsg); +static int vboxNetFltSolarisBindReq(queue_t *pQueue, int SAP); +static int vboxNetFltSolarisNotifyReq(queue_t *pQueue); + +/* static int vboxNetFltSolarisUnitDataToRaw(PVBOXNETFLTINS pThis, mblk_t *pMsg, mblk_t **ppRawMsg); */ +static int vboxNetFltSolarisRawToUnitData(mblk_t *pMsg, mblk_t **ppDlpiMsg); + +static inline void vboxNetFltSolarisInitPacketId(PVBOXNETFLTPACKETID pTag, mblk_t *pMsg); +static int vboxNetFltSolarisQueueLoopback(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg); +static bool vboxNetFltSolarisIsOurMBlk(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg); + +static mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst); +static unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg); +static int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc); +static int vboxNetFltSolarisRecv(PVBOXNETFLTINS pThis, vboxnetflt_stream_t *pStream, queue_t *pQueue, mblk_t *pMsg); +/* static mblk_t *vboxNetFltSolarisFixChecksums(mblk_t *pMsg); */ +/* static void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg); */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global device info handle. */ +static dev_info_t *g_pVBoxNetFltSolarisDip = NULL; + +/** The (common) global data. */ +static VBOXNETFLTGLOBALS g_VBoxNetFltSolarisGlobals; + +/** The list of all opened streams. */ +vboxnetflt_stream_t *g_VBoxNetFltSolarisStreams = NULL; + +/** Global mutex protecting open/close. */ +static RTSEMFASTMUTEX g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; + +/** Global credentials using during open/close. */ +static cred_t *g_pVBoxNetFltSolarisCred = NULL; + +/** + * g_VBoxNetFltInstance is the current PVBOXNETFLTINS to be associated with the stream being created + * in ModOpen. This is just shared global data between the dynamic attach and the ModOpen procedure. + */ +PVBOXNETFLTINS volatile g_VBoxNetFltSolarisInstance = NULL; + +/** Goes along with the instance to determine type of stream being opened/created. */ +VBOXNETFLTSTREAMTYPE volatile g_VBoxNetFltSolarisStreamType = kUndefined; + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING +/** Global IPv6 polling interval */ +static int g_VBoxNetFltSolarisPollInterval = -1; +#endif + +static int s_off_vnode = -1; +#define VNODE_FOR_FILE_T(filetpointer) (*(struct vnode **)((char *)(filetpointer) + s_off_vnode)) + + +static int +vboxNetFltSolarisCtfGetMemberOffset(ctf_file_t *pCtfFile, const char *pszStruct, const char *pszMember, int *pOffset) +{ + AssertReturn(pCtfFile, VERR_INVALID_PARAMETER); + AssertReturn(pszStruct, VERR_INVALID_PARAMETER); + AssertReturn(pszMember, VERR_INVALID_PARAMETER); + AssertReturn(pOffset, VERR_INVALID_PARAMETER); + + ctf_id_t TypeId = ctf_lookup_by_name(pCtfFile, pszStruct); + if (TypeId != CTF_ERR) + { + ctf_membinfo_t MemberInfo; + bzero(&MemberInfo, sizeof(MemberInfo)); + if (ctf_member_info(pCtfFile, TypeId, pszMember, &MemberInfo) != CTF_ERR) + { + *pOffset = (MemberInfo.ctm_offset >> 3); + LogRel((DEVICE_NAME ":%s::%s at %d\n", pszStruct, pszMember, *pOffset)); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":ctf_member_info failed for struct %s member %s\n", pszStruct, pszMember)); + } + else + LogRel((DEVICE_NAME ":ctf_lookup_by_name failed for struct %s\n", pszStruct)); + + return VERR_NOT_FOUND; +} + + +static int +vboxNetFltSolarisProbeCtf(void) +{ + /* + * CTF probing for fluid f_vnode member in file_t. + */ + int rc = VERR_INTERNAL_ERROR; + modctl_t *pModCtl = mod_hold_by_name("genunix"); + if (pModCtl) + { + int err; + mutex_enter(&mod_lock); + ctf_file_t *pCtfFile = ctf_modopen(pModCtl->mod_mp, &err); + mutex_exit(&mod_lock); + if (pCtfFile) + { + rc = vboxNetFltSolarisCtfGetMemberOffset(pCtfFile, "file_t", "f_vnode", &s_off_vnode); + ctf_close(pCtfFile); + } + else + LogRel((DEVICE_NAME ":ctf_modopen failed. err=%d\n", err)); + + mod_release_mod(pModCtl); + } + else + LogRel((DEVICE_NAME ":mod_hold_by_name failed.\n")); + + return rc; +} + + +/** + * Kernel entry points + */ +int _init(void) +{ + LogFunc((DEVICE_NAME ":_init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxNetFltSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ":failed to disable autounloading!\n")); + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltSolarisProbeCtf(); + if (RT_SUCCESS(rc)) + { + /* + * Initialize Solaris specific globals here. + */ + g_VBoxNetFltSolarisStreams = NULL; + g_VBoxNetFltSolarisInstance = NULL; + g_pVBoxNetFltSolarisCred = crdup(kcred); + if (RT_LIKELY(g_pVBoxNetFltSolarisCred)) + { + rc = RTSemFastMutexCreate(&g_VBoxNetFltSolarisMtx); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the globals and connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltSolarisGlobals); + if (RT_SUCCESS(rc)) + { + rc = mod_install(&g_VBoxNetFltSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ":mod_install failed. rc=%d\n", rc)); + vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); + } + else + LogRel((DEVICE_NAME ":failed to initialize globals.\n")); + + RTSemFastMutexDestroy(g_VBoxNetFltSolarisMtx); + g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; + } + } + else + { + LogRel((DEVICE_NAME ":failed to allocate credentials.\n")); + rc = VERR_NO_MEMORY; + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisProbeCtf failed. rc=%d\n", rc)); + + RTR0Term(); + } + else + LogRel((DEVICE_NAME ":failed to initialize IPRT (rc=%d)\n", rc)); + + memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + LogFunc((DEVICE_NAME ":_fini\n")); + + /* + * Undo the work done during start (in reverse order). + */ + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ":_fini - busy!\n")); + return EBUSY; + } + + rc = mod_remove(&g_VBoxNetFltSolarisModLinkage); + if (!rc) + { + if (g_pVBoxNetFltSolarisCred) + { + crfree(g_pVBoxNetFltSolarisCred); + g_pVBoxNetFltSolarisCred = NULL; + } + + if (g_VBoxNetFltSolarisMtx != NIL_RTSEMFASTMUTEX) + { + RTSemFastMutexDestroy(g_VBoxNetFltSolarisMtx); + g_VBoxNetFltSolarisMtx = NIL_RTSEMFASTMUTEX; + } + + RTR0Term(); + } + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ":_info\n")); + + int rc = mod_info(&g_VBoxNetFltSolarisModLinkage, pModInfo); + + Log((DEVICE_NAME ":_info returns %d\n", rc)); + return rc; +} + + +/** + * 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). + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_ATTACH: + { + int rc = ddi_create_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, CLONE_DEV); + if (rc == DDI_SUCCESS) + { + g_pVBoxNetFltSolarisDip = pDip; +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + /* + * Get the user prop. for polling interval. + */ + int Interval = ddi_getprop(DDI_DEV_T_ANY, pDip, DDI_PROP_DONTPASS, VBOXNETFLT_IP6POLLINTERVAL, -1 /* default */); + if (Interval == -1) + Log((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: no poll interval property specified. Skipping Ipv6 polling.\n")); + else if (Interval < 1 || Interval > 120) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Invalid polling interval %d. Expected between 1 and 120 secs.\n", + Interval)); + Interval = -1; + } + + g_VBoxNetFltSolarisPollInterval = Interval; +#endif + ddi_report_dev(pDip); + return DDI_SUCCESS; + } + else + LogRel((DEVICE_NAME ":VBoxNetFltSolarisAttach failed to create minor node. rc%d\n", rc)); + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_PM_RESUME: */ + default: + 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). + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_DETACH: + { + ddi_remove_minor_node(pDip, NULL); + return DDI_SUCCESS; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_PM_SUSPEND: */ + /* case DDI_HOT_PLUG_DETACH: */ + 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 VBoxNetFltSolarisQuiesceNotNeeded(dev_info_t *pDip) +{ + return DDI_SUCCESS; +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisGetInfo pDip=%p enmCmd=%d pArg=%p instance=%d\n", pDip, enmCmd, + getminor((dev_t)pvArg))); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + *ppvResult = g_pVBoxNetFltSolarisDip; + return *ppvResult ? DDI_SUCCESS : DDI_FAILURE; + } + + case DDI_INFO_DEVT2INSTANCE: + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; + return DDI_SUCCESS; + } + } + + return DDI_FAILURE; +} + + +/** + * Stream module open entry point, initializes the queue and allows streams processing. + * + * @param pQueue Pointer to the read queue (cannot be NULL). + * @param pDev Pointer to the dev_t associated with the driver at the end of the stream. + * @param fOpenMode Open mode (always 0 for streams driver, thus ignored). + * @param fStreamMode Stream open mode. + * @param pCred Pointer to user credentials. + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisModOpen(queue_t *pQueue, dev_t *pDev, int fOpenMode, int fStreamMode, cred_t *pCred) +{ + Assert(pQueue); + + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModOpen pQueue=%p pDev=%p fOpenMode=%d fStreamMode=%d\n", pQueue, pDev, + fOpenMode, fStreamMode)); + + /* + * Already open? + */ + if (pQueue->q_ptr) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen invalid open.\n")); + return ENOENT; + } + + /* + * Check that the request was initiated by our code. + * + * This ASSUMES that crdup() will return a copy with a unique address and + * not do any kind of clever pooling. This check will when combined with + * g_VBoxNetFltSolarisMtx prevent races and that the instance gets + * associated with the wrong streams. + */ + if (pCred != g_pVBoxNetFltSolarisCred) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen invalid credentials.\n")); + return EACCES; + } + + /* + * Check for the VirtualBox instance. + */ + PVBOXNETFLTINS pThis = g_VBoxNetFltSolarisInstance; + if (!pThis) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to get VirtualBox instance.\n")); + return ENOENT; + } + + /* + * Check VirtualBox stream type. + */ + if ( g_VBoxNetFltSolarisStreamType != kPromiscStream + && g_VBoxNetFltSolarisStreamType != kArpStream + && g_VBoxNetFltSolarisStreamType != kIp6Stream + && g_VBoxNetFltSolarisStreamType != kIp4Stream) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed due to undefined VirtualBox open mode. Type=%d\n", + g_VBoxNetFltSolarisStreamType)); + return ENOENT; + } + + /* + * Get minor number. For clone opens provide a new dev_t. + */ + minor_t DevMinor = 0; + vboxnetflt_stream_t *pStream = NULL; + vboxnetflt_stream_t **ppPrevStream = &g_VBoxNetFltSolarisStreams; + if (fStreamMode == CLONEOPEN) + { + for (; (pStream = *ppPrevStream) != NULL; ppPrevStream = &pStream->pNext) + { + if (DevMinor < pStream->DevMinor) + break; + DevMinor++; + } + *pDev = makedevice(getmajor(*pDev), DevMinor); + } + else + DevMinor = getminor(*pDev); + + if (g_VBoxNetFltSolarisStreamType == kPromiscStream) + { + vboxnetflt_promisc_stream_t *pPromiscStream = RTMemAlloc(sizeof(vboxnetflt_promisc_stream_t)); + if (RT_UNLIKELY(!pPromiscStream)) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to allocate promiscuous stream data.\n")); + return ENOMEM; + } + + pPromiscStream->fPromisc = false; + pPromiscStream->fRawMode = false; + pPromiscStream->ModeReqId = 0; + pPromiscStream->pHead = NULL; + pPromiscStream->pTail = NULL; + pPromiscStream->cLoopback = 0; + pPromiscStream->TimeoutId = 0; +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + pPromiscStream->pIp6Timer = NULL; +#endif + pStream = (vboxnetflt_stream_t *)pPromiscStream; + } + else + { + /* + * Allocate & initialize per-stream data. Hook it into the (read and write) queue's module specific data. + */ + pStream = RTMemAlloc(sizeof(vboxnetflt_stream_t)); + if (RT_UNLIKELY(!pStream)) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen failed to allocate stream data.\n")); + return ENOMEM; + } + } + pStream->DevMinor = DevMinor; + pStream->pReadQueue = pQueue; + + /* + * Pick up the current global VBOXNETFLTINS instance as + * the one that we will associate this stream with. + */ + ASMAtomicUoWritePtr(&pStream->pThis, pThis); + pStream->Type = g_VBoxNetFltSolarisStreamType; + switch (pStream->Type) + { + case kIp4Stream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pIp4Stream, pStream); break; + case kIp6Stream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pIp6Stream, pStream); break; + case kArpStream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pArpStream, pStream); break; + case kPromiscStream: ASMAtomicUoWritePtr((void**)&pThis->u.s.pPromiscStream, pStream); break; + default: /* Heh. */ + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModOpen huh!? Invalid stream type %d\n", pStream->Type)); + RTMemFree(pStream); + return EINVAL; + } + } + + pQueue->q_ptr = pStream; + WR(pQueue)->q_ptr = pStream; + + /* + * Link it to the list of streams. + */ + pStream->pNext = *ppPrevStream; + *ppPrevStream = pStream; + + /* + * Increment IntNet reference count for this stream. + */ + vboxNetFltRetain(pThis, false /* fBusy */); + + qprocson(pQueue); + + /* + * Don't hold the spinlocks across putnext calls as it could + * (and does mostly) re-enter the put procedure on the same thread. + */ + if (pStream->Type == kPromiscStream) + { + vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; + + /* + * Bind to SAP 0 (DL_ETHER). + * Note: We don't support DL_TPR (token passing ring) SAP as that is unnecessary asynchronous + * work to get DL_INFO_REQ acknowledgements and determine SAP based on the Mac Type etc. + * Besides TPR doesn't really exist anymore practically as far as I know. + */ + int rc = vboxNetFltSolarisBindReq(pStream->pReadQueue, 0 /* SAP */); + if (RT_LIKELY(RT_SUCCESS(rc))) + { + /* + * Request the physical address (we cache the acknowledgement). + */ + rc = vboxNetFltSolarisPhysAddrReq(pStream->pReadQueue); + if (RT_LIKELY(RT_SUCCESS(rc))) + { + /* + * Ask for DLPI link notifications, don't bother check for errors here. + */ + vboxNetFltSolarisNotifyReq(pStream->pReadQueue); + + /* + * Enable raw mode. + */ + rc = vboxNetFltSolarisSetRawMode(pPromiscStream); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetRawMode failed rc=%Rrc.\n", rc)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetRawMode failed rc=%Rrc.\n", rc)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisBindReq failed rc=%Rrc.\n", rc)); + } + + NOREF(fOpenMode); + + Log((DEVICE_NAME ":VBoxNetFltSolarisModOpen returns 0, DevMinor=%d pQueue=%p\n", DevMinor, pStream->pReadQueue)); + + return 0; +} + + +/** + * Stream module close entry point, undoes the work done on open and closes the stream. + * + * @param pQueue Pointer to the read queue (cannot be NULL). + * @param fOpenMode Open mode (always 0 for streams driver, thus ignored). + * @param pCred Pointer to user credentials. + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisModClose(queue_t *pQueue, int fOpenMode, cred_t *pCred) +{ + Assert(pQueue); + + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModClose pQueue=%p fOpenMode=%d\n", pQueue, fOpenMode)); + + vboxnetflt_stream_t *pStream = NULL; + vboxnetflt_stream_t **ppPrevStream = NULL; + + /* + * Get instance data. + */ + pStream = (vboxnetflt_stream_t *)pQueue->q_ptr; + if (RT_UNLIKELY(!pStream)) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModClose failed to get stream.\n")); + return ENXIO; + } + + if (pStream->Type == kPromiscStream) + { + /* + * If there are any timeout scheduled, we need to make sure they are cancelled. + */ + vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; + timeout_id_t TimeoutId = ASMAtomicReadPtr(&pPromiscStream->TimeoutId); + if (TimeoutId) + { + quntimeout(WR(pPromiscStream->Stream.pReadQueue), TimeoutId); + ASMAtomicWritePtr(&pPromiscStream->TimeoutId, NULL); + } + + flushq(pQueue, FLUSHALL); + flushq(WR(pQueue), FLUSHALL); + } + + qprocsoff(pQueue); + + if (pStream->Type == kPromiscStream) + { + vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; + + mutex_enter(&pStream->pThis->u.s.hMtx); + + /* + * Free-up loopback buffers. + */ + PVBOXNETFLTPACKETID pCur = pPromiscStream->pHead; + while (pCur) + { + PVBOXNETFLTPACKETID pNext = pCur->pNext; + RTMemFree(pCur); + pCur = pNext; + } + pPromiscStream->pHead = NULL; + pPromiscStream->pTail = NULL; + pPromiscStream->cLoopback = 0; + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + /* + * Sheer paranoia. + */ + if (pPromiscStream->pIp6Timer != NULL) + { + RTTimerStop(pPromiscStream->pIp6Timer); + RTTimerDestroy(pPromiscStream->pIp6Timer); + ASMAtomicUoWriteNullPtr(&pPromiscStream->pIp6Timer); + } +#endif + + mutex_exit(&pStream->pThis->u.s.hMtx); + } + + /* + * Unlink it from the list of streams. + */ + for (ppPrevStream = &g_VBoxNetFltSolarisStreams; (pStream = *ppPrevStream) != NULL; ppPrevStream = &pStream->pNext) + if (pStream == (vboxnetflt_stream_t *)pQueue->q_ptr) + break; + *ppPrevStream = pStream->pNext; + + /* + * Delete the stream. + */ + switch (pStream->Type) + { + case kIp4Stream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pIp4Stream); break; + case kIp6Stream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pIp6Stream); break; + case kArpStream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pArpStream); break; + case kPromiscStream: ASMAtomicUoWriteNullPtr(&pStream->pThis->u.s.pPromiscStream); break; + default: /* Heh. */ + { + AssertRelease(pStream->Type); + break; + } + } + + /* + * Decrement IntNet reference count for this stream. + */ + vboxNetFltRelease(pStream->pThis, false /* fBusy */); + + RTMemFree(pStream); + pQueue->q_ptr = NULL; + WR(pQueue)->q_ptr = NULL; + + NOREF(fOpenMode); + NOREF(pCred); + + return 0; +} + + +/** + * Read side put procedure for processing messages in the read queue. + * All streams, bound and unbound share this read procedure. + * + * @param pQueue Pointer to the read queue. + * @param pMsg Pointer to the message. + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisModReadPut(queue_t *pQueue, mblk_t *pMsg) +{ + if (!pMsg) + return 0; + + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModReadPut pQueue=%p pMsg=%p\n", pQueue, pMsg)); + + bool fSendUpstream = true; + vboxnetflt_stream_t *pStream = pQueue->q_ptr; + PVBOXNETFLTINS pThis = NULL; + + /* + * In the unlikely case where VirtualBox crashed and this filter + * is somehow still in the host stream we must try not to panic the host. + */ + if ( pStream + && pStream->Type == kPromiscStream) + { + fSendUpstream = false; + pThis = ASMAtomicUoReadPtrT(&pStream->pThis, PVBOXNETFLTINS); + if (RT_LIKELY(pThis)) + { + /* + * Retain the instance if we're filtering regardless of we are active or not + * The reason being even when we are inactive we reference the instance (e.g + * the promiscuous OFF acknowledgement case). + */ + RTSpinlockAcquire(pThis->hSpinlock); + const bool fActive = pThis->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE; + vboxNetFltRetain(pThis, true /* fBusy */); + RTSpinlockRelease(pThis->hSpinlock); + + vboxnetflt_promisc_stream_t *pPromiscStream = (vboxnetflt_promisc_stream_t *)pStream; + + switch (DB_TYPE(pMsg)) + { + case M_DATA: + { + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut M_DATA\n")); + + if ( fActive + && pPromiscStream->fRawMode) + { + vboxNetFltSolarisRecv(pThis, pStream, pQueue, pMsg); + } + break; + } + + case M_PROTO: + case M_PCPROTO: + { + union DL_primitives *pPrim = (union DL_primitives *)pMsg->b_rptr; + t_uscalar_t Prim = pPrim->dl_primitive; + + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO %d\n", Prim)); + switch (Prim) + { + case DL_NOTIFY_IND: + { + if (MBLKL(pMsg) < DL_NOTIFY_IND_SIZE) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Invalid notification size; expected>=%d" + " got=%d\n", DL_NOTIFY_IND_SIZE, MBLKL(pMsg))); + break; + } + + dl_notify_ind_t *pNotifyInd = (dl_notify_ind_t *)pMsg->b_rptr; + switch (pNotifyInd->dl_notification) + { + case DL_NOTE_PHYS_ADDR: + { + if (pNotifyInd->dl_data != DL_CURR_PHYS_ADDR) + break; + + size_t cOffset = pNotifyInd->dl_addr_offset; + size_t cbAddr = pNotifyInd->dl_addr_length; + + if (!cOffset || !cbAddr) + { + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_PHYS_ADDR." + "Invalid offset/addr.\n")); + fSendUpstream = false; + break; + } + + bcopy(pMsg->b_rptr + cOffset, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_PHYS_ADDR. New Mac=%.*Rhxs\n", + sizeof(pThis->u.s.MacAddr), &pThis->u.s.MacAddr)); + break; + } + + case DL_NOTE_LINK_UP: + { + if (ASMAtomicXchgBool(&pThis->fDisconnectedFromHost, false)) + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_LINK_UP.\n")); + break; + } + + case DL_NOTE_LINK_DOWN: + { + if (!ASMAtomicXchgBool(&pThis->fDisconnectedFromHost, true)) + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_NOTE_LINK_DOWN.\n")); + break; + } + } + break; + } + + case DL_BIND_ACK: + { + /* + * Swallow our bind request acknowledgement. + */ + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: DL_BIND_ACK. Bound to requested SAP!\n")); + break; + } + + case DL_PHYS_ADDR_ACK: + { + /* + * Swallow our physical address request acknowledgement. + */ + vboxNetFltSolarisCachePhysAddr(pThis, pMsg); + break; + } + + case DL_OK_ACK: + { + /* + * Swallow our fake promiscuous request acknowledgement. + */ + dl_ok_ack_t *pOkAck = (dl_ok_ack_t *)pMsg->b_rptr; + if (pOkAck->dl_correct_primitive == DL_PROMISCON_REQ) + { + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO: DL_OK_ACK: fPromisc is ON.\n")); + pPromiscStream->fPromisc = true; + } + else if (pOkAck->dl_correct_primitive == DL_PROMISCOFF_REQ) + { + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_PCPROTO: DL_OK_ACK: fPromisc is OFF.\n")); + pPromiscStream->fPromisc = false; + } + break; + } + } + break; + } + + case M_IOCACK: + { + /* + * Swallow our fake raw/fast path mode request acknowledgement. + */ + struct iocblk *pIOC = (struct iocblk *)pMsg->b_rptr; + if (pIOC->ioc_id == pPromiscStream->ModeReqId) + { + pPromiscStream->fRawMode = true; + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Mode acknowledgement. RawMode is %s\n", + pPromiscStream->fRawMode ? "ON" : "OFF")); + } + break; + } + + case M_IOCNAK: + { + /* + * Swallow our fake raw/fast path mode request not acknowledged. + */ + struct iocblk *pIOC = (struct iocblk *)pMsg->b_rptr; + if (pIOC->ioc_id == pPromiscStream->ModeReqId) + { + pPromiscStream->fRawMode = false; + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: WARNING! Mode not acknowledged. RawMode is %s\n", + pPromiscStream->fRawMode ? "ON" : "OFF")); + } + break; + } + + case M_FLUSH: + { + /* + * We must support flushing queues. + */ + Log((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: M_FLUSH\n")); + if (*pMsg->b_rptr & FLUSHR) + flushq(pQueue, FLUSHALL); + break; + } + } + + vboxNetFltRelease(pThis, true /* fBusy */); + } + else + LogRel((DEVICE_NAME ":VBoxNetFltSolarisModReadPut: Could not find VirtualBox instance!!\n")); + } + + if (fSendUpstream) + { + /* + * Don't queue up things here, can cause bad things to happen when the system + * is under heavy loads and we need to jam across high priority messages which + * if it's not done properly will end up in an infinite loop. + */ + putnext(pQueue, pMsg); + } + else + { + /* + * We need to free up the message if we don't pass it through. + */ + freemsg(pMsg); + } + + return 0; +} + + +/** + * Write side put procedure for processing messages in the write queue. + * All streams, bound and unbound share this write procedure. + * + * @param pQueue Pointer to the write queue. + * @param pMsg Pointer to the message. + * + * @returns corresponding solaris error code. + */ +static int VBoxNetFltSolarisModWritePut(queue_t *pQueue, mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":VBoxNetFltSolarisModWritePut pQueue=%p pMsg=%p\n", pQueue, pMsg)); + + putnext(pQueue, pMsg); + return 0; +} + + +/** + * Put the stream in raw mode. + * + * @returns VBox status code. + * @param pPromiscStream Pointer to the read queue. + */ +static int vboxNetFltSolarisSetRawMode(vboxnetflt_promisc_stream_t *pPromiscStream) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetRawMode pPromiscStream=%p\n", pPromiscStream)); + + mblk_t *pRawMsg = NULL; + pRawMsg = mkiocb(DLIOCRAW); + if (RT_UNLIKELY(!pRawMsg)) + return VERR_NO_MEMORY; + + queue_t *pQueue = pPromiscStream->Stream.pReadQueue; + if (!pQueue) + return VERR_INVALID_POINTER; + + struct iocblk *pIOC = (struct iocblk *)pRawMsg->b_rptr; + pPromiscStream->ModeReqId = pIOC->ioc_id; + pIOC->ioc_count = 0; + + qreply(pQueue, pRawMsg); + return VINF_SUCCESS; +} + + +#if 0 +/** + * Put the stream back in fast path mode. + * + * @returns VBox status code. + * @param pQueue Pointer to the read queue. + */ +static int vboxNetFltSolarisSetFastMode(queue_t *pQueue) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetFastMode pQueue=%p\n", pQueue)); + + mblk_t *pFastMsg = mkiocb(DL_IOC_HDR_INFO); + if (RT_UNLIKELY(!pFastMsg)) + return VERR_NO_MEMORY; + + vboxnetflt_stream_t *pStream = pQueue->q_ptr; + struct iocblk *pIOC = (struct iocblk *)pFastMsg->b_rptr; + pStream->ModeReqId = pIOC->ioc_id; + + size_t cbReq = sizeof(dl_unitdata_req_t) + sizeof(vboxnetflt_dladdr_t); + mblk_t *pDataReqMsg = allocb(cbReq, BPRI_MED); + if (RT_UNLIKELY(!pDataReqMsg)) + return VERR_NO_MEMORY; + + DB_TYPE(pDataReqMsg) = M_PROTO; + dl_unitdata_req_t *pDataReq = (dl_unitdata_req_t *)pDataReqMsg->b_rptr; + pDataReq->dl_primitive = DL_UNITDATA_REQ; + pDataReq->dl_dest_addr_length = sizeof(vboxnetflt_dladdr_t); + pDataReq->dl_dest_addr_offset = sizeof(dl_unitdata_req_t); + pDataReq->dl_priority.dl_min = 0; + pDataReq->dl_priority.dl_max = 0; + + bzero(pDataReqMsg->b_rptr + sizeof(dl_unitdata_req_t), sizeof(vboxnetflt_dladdr_t)); + pDataReqMsg->b_wptr = pDataReqMsg->b_rptr + cbReq; + + /* + * Link the data format request message into the header ioctl message. + */ + pFastMsg->b_cont = pDataReqMsg; + pIOC->ioc_count = msgdsize(pDataReqMsg); + + qreply(pQueue, pFastMsg); + return VINF_SUCCESS; +} +#endif + + +/** + * Callback function for qwriter to send promiscuous request messages + * downstream. + * + * @param pQueue Pointer to the write queue. + * @param fPromisc Whether to send promiscuous ON or OFF requests. + * + * @returns VBox status code. + */ +static int vboxNetFltSolarisPromiscReq(queue_t *pQueue, bool fPromisc) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisPromiscReq pQueue=%p fPromisc=%d\n", pQueue, fPromisc)); + + t_uscalar_t Cmd; + size_t cbReq = 0; + if (fPromisc) + { + Cmd = DL_PROMISCON_REQ; + cbReq = DL_PROMISCON_REQ_SIZE; + } + else + { + Cmd = DL_PROMISCOFF_REQ; + cbReq = DL_PROMISCOFF_REQ_SIZE; + } + + mblk_t *pPromiscPhysMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); + if (RT_UNLIKELY(!pPromiscPhysMsg)) + return VERR_NO_MEMORY; + + mblk_t *pPromiscSapMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); + if (RT_UNLIKELY(!pPromiscSapMsg)) + { + freemsg(pPromiscPhysMsg); + return VERR_NO_MEMORY; + } + + if (fPromisc) + { + ((dl_promiscon_req_t *)pPromiscPhysMsg->b_rptr)->dl_level = DL_PROMISC_PHYS; + ((dl_promiscon_req_t *)pPromiscSapMsg->b_rptr)->dl_level = DL_PROMISC_SAP; + } + else + { + ((dl_promiscoff_req_t *)pPromiscPhysMsg->b_rptr)->dl_level = DL_PROMISC_PHYS; + ((dl_promiscoff_req_t *)pPromiscSapMsg->b_rptr)->dl_level = DL_PROMISC_SAP; + } + + putnext(pQueue, pPromiscPhysMsg); + putnext(pQueue, pPromiscSapMsg); + + return VINF_SUCCESS; +} + + +/** + * Callback wrapper for qwriter() to safely send promiscuous requests. This is + * called at the outer perimeter with exclusive lock held. + * + * @param pQueue Pointer to the write queue. + * @param pMsg A one byte message indicates a Promisc ON, otherwise + * a promiscuous OFF request. See + * vboxNetFltSolarisPromiscReqWrap(). + */ +static void vboxNetFltSolarisPromiscReqWrapExcl(queue_t *pQueue, mblk_t *pMsg) +{ + /* + * Paranoia. + */ + AssertReturnVoid(pQueue); + if (RT_UNLIKELY(!pMsg)) + LogRel((DEVICE_NAME ":VBoxNetFltSolarisPromiscReqWrapExcl pQueue=%p missing message!\n", pQueue)); + + bool fPromisc = (MBLKL(pMsg) == 1); + freemsg(pMsg); + pMsg = NULL; + int rc = vboxNetFltSolarisPromiscReq(pQueue, fPromisc); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ":VBoxNetFltSolarisPromiscReqWrapExcl vboxNetFltSolarisPromiscReq failed. rc=%d\n", rc)); +} + + +/** + * Callback wrapper for qtimeout() to safely send promiscuous requests. This is + * called at the inner perimeter with shared lock. + * + * @param pvData Pointer to vboxnetflt_promisc_params_t. See + * vboxNetFltPortOsSetActive(). + */ +static void vboxNetFltSolarisPromiscReqWrap(void *pvData) +{ + vboxnetflt_promisc_params_t *pParams = pvData; + if (RT_LIKELY(pParams)) + { + PVBOXNETFLTINS pThis = pParams->pThis; + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, + vboxnetflt_promisc_stream_t *); + if ( pPromiscStream + && pPromiscStream->Stream.pReadQueue) + { + /* + * Use size of message to indicate to qwriter callback whether it must send + * promiscuous On or Off messages. This is ugly but easier and more efficient than + * scheduling two separate qwriter callbacks with prepared messages to putnext. + */ + size_t cbMsg = pParams->fPromiscOn ? 1 : 2; + mblk_t *pMsg = allocb(cbMsg, BPRI_HI); + if (RT_UNLIKELY(!pMsg)) + { + LogRel((DEVICE_NAME ":Failed to alloc message of %u bytes\n", cbMsg)); + return; + } + + /* + * Move the data pointer so we can use MBLKL, as MBLKSIZE gets the db_lim which is + * always aligned. + */ + pMsg->b_wptr += cbMsg; + + /* + * Upgrade inner perimeter lock to exclusive outer perimeter lock and + * then call putnext while we are at the outer perimeter. + */ + qwriter(WR(pPromiscStream->Stream.pReadQueue), pMsg, vboxNetFltSolarisPromiscReqWrapExcl, PERIM_OUTER); + ASMAtomicWritePtr(&pPromiscStream->TimeoutId, NULL); + } + RTMemFree(pParams); + } +} + + +/** + * Send a fake physical address request downstream. + * + * @returns VBox status code. + * @param pQueue Pointer to the read queue. + */ +static int vboxNetFltSolarisPhysAddrReq(queue_t *pQueue) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisPhysAddrReq pQueue=%p\n", pQueue)); + + t_uscalar_t Cmd = DL_PHYS_ADDR_REQ; + size_t cbReq = DL_PHYS_ADDR_REQ_SIZE; + mblk_t *pPhysAddrMsg = mexchange(NULL, NULL, cbReq, M_PROTO, Cmd); + if (RT_UNLIKELY(!pPhysAddrMsg)) + return VERR_NO_MEMORY; + + dl_phys_addr_req_t *pPhysAddrReq = (dl_phys_addr_req_t *)pPhysAddrMsg->b_rptr; + pPhysAddrReq->dl_addr_type = DL_CURR_PHYS_ADDR; + + qreply(pQueue, pPhysAddrMsg); + return VINF_SUCCESS; +} + + +/** + * Cache the MAC address into the VirtualBox instance given a physical + * address acknowledgement message. + * + * @param pThis The instance. + * @param pMsg Pointer to the physical address acknowledgement message. + */ +static void vboxNetFltSolarisCachePhysAddr(PVBOXNETFLTINS pThis, mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr pThis=%p pMsg=%p\n", pThis, pMsg)); + + AssertCompile(sizeof(RTMAC) == ETHERADDRL); + dl_phys_addr_ack_t *pPhysAddrAck = (dl_phys_addr_ack_t *)pMsg->b_rptr; + if (pPhysAddrAck->dl_addr_length == sizeof(pThis->u.s.MacAddr)) + { + bcopy(pMsg->b_rptr + pPhysAddrAck->dl_addr_offset, &pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); + + Log((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr: DL_PHYS_ADDR_ACK: Mac=%.*Rhxs\n", + sizeof(pThis->u.s.MacAddr), &pThis->u.s.MacAddr)); + + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + if (pThis->pSwitchPort) + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCachePhysAddr: Invalid address size. expected=%d got=%d\n", ETHERADDRL, + pPhysAddrAck->dl_addr_length)); + } +} + + +/** + * Prepare DLPI bind request to a SAP. + * + * @returns VBox status code. + * @param pQueue Pointer to the read queue. + * @param SAP The SAP to bind the stream to. + */ +static int vboxNetFltSolarisBindReq(queue_t *pQueue, int SAP) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisBindReq SAP=%d\n", SAP)); + + mblk_t *pBindMsg = mexchange(NULL, NULL, DL_BIND_REQ_SIZE, M_PROTO, DL_BIND_REQ); + if (RT_UNLIKELY(!pBindMsg)) + return VERR_NO_MEMORY; + + dl_bind_req_t *pBindReq = (dl_bind_req_t *)pBindMsg->b_rptr; + pBindReq->dl_sap = SAP; + pBindReq->dl_max_conind = 0; + pBindReq->dl_conn_mgmt = 0; + pBindReq->dl_xidtest_flg = 0; + pBindReq->dl_service_mode = DL_CLDLS; + + qreply(pQueue, pBindMsg); + return VINF_SUCCESS; +} + + +/** + * Prepare DLPI notifications request. + * + * @returns VBox status code. + * @param pQueue Pointer to the read queue. + */ +static int vboxNetFltSolarisNotifyReq(queue_t *pQueue) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisNotifyReq\n")); + + mblk_t *pNotifyMsg = mexchange(NULL, NULL, DL_NOTIFY_REQ_SIZE, M_PROTO, DL_NOTIFY_REQ); + if (RT_UNLIKELY(!pNotifyMsg)) + return VERR_NO_MEMORY; + + dl_notify_req_t *pNotifyReq = (dl_notify_req_t *)pNotifyMsg->b_rptr; + pNotifyReq->dl_notifications = DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN | DL_NOTE_PHYS_ADDR; + + qreply(pQueue, pNotifyMsg); + return VINF_SUCCESS; +} + + +/** + * Opens the required device and returns the vnode_t associated with it. + * We require this for the funny attach/detach routine. + * + * @returns VBox status code. + * @param pszDev The device path. + * @param ppVNode Where to store the vnode_t pointer associated with the opened device. + * @param ppVNodeHeld Where to store the vnode_t required during closing of the device. + * @param ppUser Open handle required while closing the device. + */ +static int vboxNetFltSolarisOpenDev(char *pszDev, vnode_t **ppVNode, vnode_t **ppVNodeHeld, TIUSER **ppUser) +{ + int rc; + vnode_t *pVNodeHeld = NULL; + rc = lookupname(pszDev, UIO_SYSSPACE, FOLLOW, NULLVPP, &pVNodeHeld); + if ( !rc + && pVNodeHeld) + { + TIUSER *pUser; + rc = t_kopen((file_t *)NULL, pVNodeHeld->v_rdev, FREAD | FWRITE, &pUser, kcred); + if (!rc) + { + if ( pUser + && pUser->fp + && VNODE_FOR_FILE_T(pUser->fp)) + { + *ppVNode = VNODE_FOR_FILE_T(pUser->fp); + *ppVNodeHeld = pVNodeHeld; + *ppUser = pUser; + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev failed. pUser=%p fp=%p f_vnode=%p\n", pUser, + pUser ? pUser->fp : NULL, pUser && pUser->fp ? VNODE_FOR_FILE_T(pUser->fp) : NULL)); + } + + if (pUser) + t_kclose(pUser, 0); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev t_kopen failed. rc=%d\n", rc)); + + VN_RELE(pVNodeHeld); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenDev lookupname failed. rc=%d pVNodeHeld=%p\n", rc, pVNodeHeld)); + + return VERR_PATH_NOT_FOUND; +} + + +/** + * Close the device opened using vboxNetFltSolarisOpenDev. + * + * @param pVNodeHeld Pointer to the held vnode of the device. + * @param pUser Pointer to the file handle. + */ +static void vboxNetFltSolarisCloseDev(vnode_t *pVNodeHeld, TIUSER *pUser) +{ + t_kclose(pUser, 0); + VN_RELE(pVNodeHeld); +} + + +/** + * Set the DLPI style-2 PPA via an attach request, Synchronous. + * Waits for request acknowledgement and verifies the result. + * + * @returns VBox status code. + * @param hDevice Layered device handle. + * @param PPA Physical Point of Attachment (PPA) number. + */ +static int vboxNetFltSolarisAttachReq(ldi_handle_t hDevice, int PPA) +{ + int rc; + mblk_t *pAttachMsg = mexchange(NULL, NULL, DL_ATTACH_REQ_SIZE, M_PROTO, DL_ATTACH_REQ); + if (RT_UNLIKELY(!pAttachMsg)) + return VERR_NO_MEMORY; + + dl_attach_req_t *pAttachReq = (dl_attach_req_t *)pAttachMsg->b_rptr; + pAttachReq->dl_ppa = PPA; + + rc = ldi_putmsg(hDevice, pAttachMsg); + if (!rc) + { + rc = ldi_getmsg(hDevice, &pAttachMsg, NULL); + if (!rc) + { + /* + * Verify if the attach succeeded. + */ + size_t cbMsg = MBLKL(pAttachMsg); + if (cbMsg >= sizeof(t_uscalar_t)) + { + union DL_primitives *pPrim = (union DL_primitives *)pAttachMsg->b_rptr; + t_uscalar_t AckPrim = pPrim->dl_primitive; + + if ( AckPrim == DL_OK_ACK /* Success! */ + && cbMsg == DL_OK_ACK_SIZE) + { + rc = VINF_SUCCESS; + } + else if ( AckPrim == DL_ERROR_ACK /* Error Ack. */ + && cbMsg == DL_ERROR_ACK_SIZE) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but unsupported op.\n")); + rc = VERR_NOT_SUPPORTED; + } + else /* Garbled reply */ + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but invalid op." + " expected %d recvd %d\n", DL_OK_ACK, AckPrim)); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg succeeded, but invalid size %d expected %d\n", cbMsg, + DL_OK_ACK_SIZE)); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_getmsg failed. rc=%d\n", rc)); + rc = VERR_INVALID_FUNCTION; + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachReq ldi_putmsg failed. rc=%d\n", rc)); + rc = VERR_UNRESOLVED_ERROR; + } + + freemsg(pAttachMsg); + return rc; +} + + +/** + * Get the logical interface flags from the stream. + * + * @returns VBox status code. + * @param hDevice Layered device handle. + * @param pInterface Pointer to the interface. + */ +static int vboxNetFltSolarisGetIfFlags(ldi_handle_t hDevice, struct lifreq *pInterface) +{ + struct strioctl IOCReq; + int rc; + int ret; + IOCReq.ic_cmd = SIOCGLIFFLAGS; + IOCReq.ic_timout = 40; + IOCReq.ic_len = sizeof(struct lifreq); + IOCReq.ic_dp = (caddr_t)pInterface; + rc = ldi_ioctl(hDevice, I_STR, (intptr_t)&IOCReq, FKIOCTL, kcred, &ret); + if (!rc) + return VINF_SUCCESS; + + return RTErrConvertFromErrno(rc); +} + + +/** + * Sets the multiplexor ID from the interface. + * + * @returns VBox status code. + * @param pVNode Pointer to the device vnode. + * @param pInterface Pointer to the interface. + */ +static int vboxNetFltSolarisSetMuxId(vnode_t *pVNode, struct lifreq *pInterface) +{ + struct strioctl IOCReq; + int rc; + int ret; + IOCReq.ic_cmd = SIOCSLIFMUXID; + IOCReq.ic_timout = 40; + IOCReq.ic_len = sizeof(struct lifreq); + IOCReq.ic_dp = (caddr_t)pInterface; + + rc = strioctl(pVNode, I_STR, (intptr_t)&IOCReq, 0, K_TO_K, kcred, &ret); + if (!rc) + return VINF_SUCCESS; + + return RTErrConvertFromErrno(rc); +} + + +/** + * Get the multiplexor file descriptor of the lower stream. + * + * @returns VBox status code. + * @param pVNode Pointer to the device vnode. + * @param MuxId The multiplexor ID. + * @param pFd Where to store the lower stream file descriptor. + */ +static int vboxNetFltSolarisMuxIdToFd(vnode_t *pVNode, int MuxId, int *pFd) +{ + int ret; + + *pFd = -1; /* silence compiler warnings from -Wmaybe-uninitialized */ + int rc = strioctl(pVNode, _I_MUXID2FD, (intptr_t)MuxId, 0, K_TO_K, kcred, &ret); + if (!rc) + { + *pFd = ret; + return VINF_SUCCESS; + } + + return RTErrConvertFromErrno(rc); +} + + +/** + * Relinks the lower and the upper IPv4 stream. + * + * @returns VBox status code. + * @param pVNode Pointer to the device vnode. + * @param pInterface Pointer to the interface. + * @param IpMuxFd The IP multiplexor ID. + * @param ArpMuxFd The ARP multiplexor ID. + */ +static int vboxNetFltSolarisRelinkIp4(vnode_t *pVNode, struct lifreq *pInterface, int IpMuxFd, int ArpMuxFd) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: pVNode=%p pInterface=%p IpMuxFd=%d ArpMuxFd=%d\n", pVNode, + pInterface, IpMuxFd, ArpMuxFd)); + + int NewIpMuxId; + int NewArpMuxId; + int rc = strioctl(pVNode, I_PLINK, (intptr_t)IpMuxFd, 0, K_TO_K, kcred, &NewIpMuxId); + int rc2 = strioctl(pVNode, I_PLINK, (intptr_t)ArpMuxFd, 0, K_TO_K, kcred, &NewArpMuxId); + if ( !rc + && !rc2) + { + pInterface->lifr_ip_muxid = NewIpMuxId; + pInterface->lifr_arp_muxid = NewArpMuxId; + rc = vboxNetFltSolarisSetMuxId(pVNode, pInterface); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: failed to set new Mux Id.\n")); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp4: failed to link.\n")); + + return VERR_GENERAL_FAILURE; +} + + +/** + * Relinks the lower and the upper IPv6 stream. + * + * @returns VBox status code. + * @param pVNode Pointer to the device vnode. + * @param pInterface Pointer to the interface. + * @param Ip6MuxFd The IPv6 multiplexor ID. + */ +static int vboxNetFltSolarisRelinkIp6(vnode_t *pVNode, struct lifreq *pInterface, int Ip6MuxFd) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: pVNode=%p pInterface=%p Ip6MuxFd=%d\n", pVNode, pInterface, Ip6MuxFd)); + + int NewIp6MuxId; + int rc = strioctl(pVNode, I_PLINK, (intptr_t)Ip6MuxFd, 0, K_TO_K, kcred, &NewIp6MuxId); + if (!rc) + { + pInterface->lifr_ip_muxid = NewIp6MuxId; + rc = vboxNetFltSolarisSetMuxId(pVNode, pInterface); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: failed to set new Mux Id.\n")); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisRelinkIp6: failed to link.\n")); + + return VERR_GENERAL_FAILURE; +} + + +/** + * Dynamically find the position on the host stack where to attach/detach ourselves. + * + * @returns VBox status code. + * @param fAttach Is this an attach or detach. + * @param pVNode Pointer to the lower stream vnode. + * @param pModPos Where to store the module position. + */ +static int vboxNetFltSolarisDetermineModPos(bool fAttach, vnode_t *pVNode, int *pModPos) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: fAttach=%d pVNode=%p pModPos=%p\n", fAttach, pVNode, pModPos)); + + int cMod; + int rc = strioctl(pVNode, I_LIST, (intptr_t)NULL, 0, K_TO_K, kcred, &cMod); + if (!rc) + { + if (cMod < 1) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: too few modules on host interface. cMod=%d\n")); + return VERR_OUT_OF_RANGE; + } + + /* + * While attaching we make sure we are at the bottom most of the stack, excepting + * the host driver. + */ + Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: cMod=%d\n", cMod)); + if (fAttach) + { + *pModPos = cMod - 1; + return VINF_SUCCESS; + } + + /* + * Detaching is a bit more complicated; since user could have altered the stack positions + * we take the safe approach by finding our position. + */ + struct str_list StrList; + StrList.sl_nmods = cMod; + StrList.sl_modlist = RTMemAllocZ(cMod * sizeof(struct str_list)); + if (RT_UNLIKELY(!StrList.sl_modlist)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to alloc memory for StrList.\n")); + return VERR_NO_MEMORY; + } + + /* + * Get the list of all modules on the stack. + */ + int ret; + rc = strioctl(pVNode, I_LIST, (intptr_t)&StrList, 0, K_TO_K, kcred, &ret); + if (!rc) + { + /* + * Find our filter. + */ + for (int i = 0; i < StrList.sl_nmods; i++) + { + if (!strcmp(DEVICE_NAME, StrList.sl_modlist[i].l_name)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: Success! Found %s at %d.\n", DEVICE_NAME, i)); + *pModPos = i; + RTMemFree(StrList.sl_modlist); + return VINF_SUCCESS; + } + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to find %s in the host stack.\n", DEVICE_NAME)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to get module information. rc=%d\n")); + + RTMemFree(StrList.sl_modlist); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisDetermineModPos: failed to get list of modules on host interface. rc=%d\n", rc)); + return VERR_GENERAL_FAILURE; +} + + +/** + * Opens up the DLPI style 2 link that requires explicit PPA attach + * phase. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pDevId Where to store the opened LDI device id. + */ +static int vboxNetFltSolarisOpenStyle2(PVBOXNETFLTINS pThis, ldi_ident_t *pDevId) +{ + /* + * Strip out PPA from the device name, eg: "ce3". + */ + char *pszDev = RTStrDup(pThis->szName); + if (!pszDev) + return VERR_NO_MEMORY; + + char *pszEnd = strchr(pszDev, '\0'); + while (--pszEnd > pszDev) + if (!RT_C_IS_DIGIT(*pszEnd)) + break; + pszEnd++; + + int rc = VERR_GENERAL_FAILURE; + long PPA = -1; + if ( pszEnd + && ddi_strtol(pszEnd, NULL, 10, &PPA) == 0) + { + *pszEnd = '\0'; + char szDev[128]; + RTStrPrintf(szDev, sizeof(szDev), "/dev/%s", pszDev); + + /* + * Try open the device as DPLI style 2. + */ + rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, *pDevId); + if (!rc) + { + /* + * Attach the PPA explictly. + */ + rc = vboxNetFltSolarisAttachReq(pThis->u.s.hIface, (int)PPA); + if (RT_SUCCESS(rc)) + { + RTStrFree(pszDev); + return rc; + } + + ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); + pThis->u.s.hIface = NULL; + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 dl_attach failed. rc=%d szDev=%s PPA=%d rc=%d\n", rc, szDev, PPA)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 Failed to open. rc=%d szDev=%s PPA=%d\n", rc, szDev, PPA)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStyle2 Failed to construct PPA. pszDev=%s pszEnd=%s.\n", pszDev, pszEnd)); + + RTStrFree(pszDev); + return VERR_INTNET_FLT_IF_FAILED; +} + + +/** + * Opens up dedicated stream on top of the interface. + * As a side-effect, the stream gets opened during + * the I_PUSH phase. + * + * @param pThis The instance. + */ +static int vboxNetFltSolarisOpenStream(PVBOXNETFLTINS pThis) +{ + ldi_ident_t DevId; + DevId = ldi_ident_from_anon(); + int ret; + + /* + * Figure out if this is a VLAN interface or not based on the interface name. + * Only works for the VLAN PPA-hack based names. See @bugref{4854} for details. + */ + char *pszEnd = strchr(pThis->szName, '\0'); + while (--pszEnd > pThis->szName) + if (!RT_C_IS_DIGIT(*pszEnd)) + break; + pszEnd++; + uint32_t PPA = RTStrToUInt32(pszEnd); + if (PPA > 1000) + { + pThis->u.s.fVLAN = true; + LogRel((DEVICE_NAME ": %s detected as VLAN interface with VID=%u.\n", pThis->szName, PPA / 1000U)); + } + + /* + * Try style-1 open first. + */ + char szDev[128]; + RTStrPrintf(szDev, sizeof(szDev), "/dev/net/%s", pThis->szName); + int rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, DevId); + if ( rc + && rc == ENODEV) /* ENODEV is returned when resolvepath fails, not ENOENT */ + { + /* + * Fallback to non-ClearView style-1 open. + */ + RTStrPrintf(szDev, sizeof(szDev), "/dev/%s", pThis->szName); + rc = ldi_open_by_name(szDev, FREAD | FWRITE, kcred, &pThis->u.s.hIface, DevId); + } + + if (rc) + { + /* + * Try DLPI style 2. + */ + rc = vboxNetFltSolarisOpenStyle2(pThis, &DevId); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream vboxNetFltSolarisOpenStyle2 failed. rc=%d\n", rc)); + else + rc = 0; + } + + ldi_ident_release(DevId); + if (rc) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to open '%s' rc=%d pszName='%s'\n", szDev, rc, pThis->szName)); + return VERR_INTNET_FLT_IF_FAILED; + } + + rc = ldi_ioctl(pThis->u.s.hIface, I_FIND, (intptr_t)DEVICE_NAME, FKIOCTL, kcred, &ret); + if (!rc) + { + if (!ret) + { + if (RT_LIKELY(g_pVBoxNetFltSolarisCred)) /* Paranoia */ + { + rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); + AssertRCReturn(rc, rc); + + g_VBoxNetFltSolarisInstance = pThis; + g_VBoxNetFltSolarisStreamType = kPromiscStream; + + rc = ldi_ioctl(pThis->u.s.hIface, I_PUSH, (intptr_t)DEVICE_NAME, FKIOCTL, g_pVBoxNetFltSolarisCred, &ret); + + g_VBoxNetFltSolarisInstance = NULL; + g_VBoxNetFltSolarisStreamType = kUndefined; + + RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream huh!? Missing credentials.\n")); + rc = VERR_INVALID_POINTER; + } + + if (!rc) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to push filter onto host interface '%s'\n", pThis->szName)); + } + else + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisOpenStream Failed to search for filter in interface '%s'.\n", pThis->szName)); + + ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); + pThis->u.s.hIface = NULL; + + return VERR_INTNET_FLT_IF_FAILED; +} + + +/** + * Closes the interface, thereby closing the dedicated stream. + * + * @param pThis The instance. + */ +static void vboxNetFltSolarisCloseStream(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisCloseStream pThis=%p\n")); + + if (pThis->u.s.hIface) + { + ldi_close(pThis->u.s.hIface, FREAD | FWRITE, kcred); + pThis->u.s.hIface = NULL; + } +} + + +/** + * Dynamically attach under IPv4 and ARP streams on the host stack. + * + * @returns VBox status code. + * @param pThis The instance. + * @param fAttach Is this an attach or detach. + */ +static int vboxNetFltSolarisAttachIp4(PVBOXNETFLTINS pThis, bool fAttach) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachIp4 pThis=%p fAttach=%d\n", pThis, fAttach)); + + /* + * Statutory Warning: Hackish code ahead. + */ + char *pszModName = DEVICE_NAME; + + struct lifreq Ip4Interface; + bzero(&Ip4Interface, sizeof(Ip4Interface)); + Ip4Interface.lifr_addr.ss_family = AF_INET; + strncpy(Ip4Interface.lifr_name, pThis->szName, sizeof(Ip4Interface.lifr_name)); + + struct strmodconf StrMod; + StrMod.mod_name = pszModName; + StrMod.pos = -1; /* this is filled in later. */ + + struct strmodconf ArpStrMod; + bcopy(&StrMod, &ArpStrMod, sizeof(StrMod)); + + int rc; + int rc2; + int ret; + ldi_ident_t DeviceIdent = ldi_ident_from_anon(); + ldi_handle_t Ip4DevHandle; + ldi_handle_t ArpDevHandle; + + /* + * Open the IP and ARP streams as layered devices. + */ + rc = ldi_open_by_name(IP_DEV_NAME, FREAD | FWRITE, kcred, &Ip4DevHandle, DeviceIdent); + if (rc) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open the IP stream on '%s'.\n", pThis->szName)); + ldi_ident_release(DeviceIdent); + return VERR_INTNET_FLT_IF_FAILED; + } + + rc = ldi_open_by_name("/dev/arp", FREAD | FWRITE, kcred, &ArpDevHandle, DeviceIdent); + if (rc) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open the ARP stream on '%s'.\n", pThis->szName)); + ldi_ident_release(DeviceIdent); + ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); + return VERR_INTNET_FLT_IF_FAILED; + } + + ldi_ident_release(DeviceIdent); + + /* + * Obtain the interface flags from IPv4. + */ + rc = vboxNetFltSolarisGetIfFlags(Ip4DevHandle, &Ip4Interface); + if (RT_SUCCESS(rc)) + { + /* + * Open the UDP stream. We sort of cheat here and obtain the vnode so that we can perform + * things that are not possible from the layered interface. + */ + vnode_t *pUdp4VNode = NULL; + vnode_t *pUdp4VNodeHeld = NULL; + TIUSER *pUdp4User = NULL; + rc = vboxNetFltSolarisOpenDev(UDP_DEV_NAME, &pUdp4VNode, &pUdp4VNodeHeld, &pUdp4User); + if (RT_SUCCESS(rc)) + { + /* + * Get the multiplexor IDs. + */ + rc = ldi_ioctl(Ip4DevHandle, SIOCGLIFMUXID, (intptr_t)&Ip4Interface, FKIOCTL, kcred, &ret); + if (!rc) + { + /* + * Get the multiplex file descriptor to the lower streams. Generally this is lost + * once a module is I_PLINK, we need to reobtain it for inserting/removing ourselves from the stack. + */ + int Ip4MuxFd; + int ArpMuxFd; + rc = vboxNetFltSolarisMuxIdToFd(pUdp4VNode, Ip4Interface.lifr_ip_muxid, &Ip4MuxFd); + rc2 = vboxNetFltSolarisMuxIdToFd(pUdp4VNode, Ip4Interface.lifr_arp_muxid, &ArpMuxFd); + if ( RT_SUCCESS(rc) + && RT_SUCCESS(rc2)) + { + /* + * We need to I_PUNLINK on these multiplexor IDs before we can start + * operating on the lower stream as insertions are direct operations on the lower stream. + */ + rc = strioctl(pUdp4VNode, I_PUNLINK, (intptr_t)Ip4Interface.lifr_ip_muxid, 0, K_TO_K, kcred, &ret); + rc2 = strioctl(pUdp4VNode, I_PUNLINK, (intptr_t)Ip4Interface.lifr_arp_muxid, 0, K_TO_K, kcred, &ret); + if ( !rc + && !rc2) + { + /* + * Obtain the vnode from the useless userland file descriptor. + */ + file_t *pIpFile = getf(Ip4MuxFd); + file_t *pArpFile = getf(ArpMuxFd); + if ( pIpFile + && pArpFile + && VNODE_FOR_FILE_T(pArpFile) + && VNODE_FOR_FILE_T(pIpFile)) + { + vnode_t *pIp4VNode = VNODE_FOR_FILE_T(pIpFile); + vnode_t *pArpVNode = VNODE_FOR_FILE_T(pArpFile); + + /* + * Find the position on the host stack for attaching/detaching ourselves. + */ + rc = vboxNetFltSolarisDetermineModPos(fAttach, pIp4VNode, &StrMod.pos); + rc2 = vboxNetFltSolarisDetermineModPos(fAttach, pArpVNode, &ArpStrMod.pos); + if ( RT_SUCCESS(rc) + && RT_SUCCESS(rc2)) + { + /* + * Inject/Eject from the host IP stack. + */ + + /* + * Set global data which will be grabbed by ModOpen. + * There is a known (though very unlikely) race here because + * of the inability to pass user data while inserting. + */ + rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); + AssertRCReturn(rc, rc); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = pThis; + g_VBoxNetFltSolarisStreamType = kIp4Stream; + } + + rc = strioctl(pIp4VNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, + g_pVBoxNetFltSolarisCred, &ret); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = NULL; + g_VBoxNetFltSolarisStreamType = kUndefined; + } + + RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); + + if (!rc) + { + /* + * Inject/Eject from the host ARP stack. + */ + rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); + AssertRCReturn(rc, rc); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = pThis; + g_VBoxNetFltSolarisStreamType = kArpStream; + } + + rc = strioctl(pArpVNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&ArpStrMod, 0, K_TO_K, + g_pVBoxNetFltSolarisCred, &ret); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = NULL; + g_VBoxNetFltSolarisStreamType = kUndefined; + } + + RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); + + if (!rc) + { + /* + * Our job's not yet over; we need to relink the upper and lower streams + * otherwise we've pretty much screwed up the host interface. + */ + rc = vboxNetFltSolarisRelinkIp4(pUdp4VNode, &Ip4Interface, Ip4MuxFd, ArpMuxFd); + if (RT_SUCCESS(rc)) + { + /* + * Close the devices ONLY during the return from function case; otherwise + * we end up close twice which is an instant kernel panic. + */ + vboxNetFltSolarisCloseDev(pUdp4VNodeHeld, pUdp4User); + ldi_close(ArpDevHandle, FREAD | FWRITE, kcred); + ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); + releasef(Ip4MuxFd); + releasef(ArpMuxFd); + + Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Success! %s %s@(IPv4:%d Arp:%d) " + "%s interface %s\n", fAttach ? "Injected" : "Ejected", StrMod.mod_name, + StrMod.pos, ArpStrMod.pos, fAttach ? "to" : "from", pThis->szName)); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Relinking failed. Mode=%s rc=%d.\n", + fAttach ? "inject" : "eject", rc)); + } + + /* + * Try failing gracefully during attach. + */ + if (fAttach) + strioctl(pArpVNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to %s the ARP stack. rc=%d\n", + fAttach ? "inject into" : "eject from", rc)); + } + + if (fAttach) + strioctl(pIp4VNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); + + vboxNetFltSolarisRelinkIp4(pUdp4VNode, &Ip4Interface, Ip4MuxFd, ArpMuxFd); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to %s the IP stack. rc=%d\n", + fAttach ? "inject into" : "eject from", rc)); + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to find position. rc=%d rc2=%d\n", rc, + rc2)); + } + + releasef(Ip4MuxFd); + releasef(ArpMuxFd); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get vnode from MuxFd.\n")); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to unlink upper stream rc=%d rc2=%d.\n", rc, + rc2)); + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get MuxFd from MuxId. rc=%d rc2=%d\n", rc, rc2)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to get Mux Ids. rc=%d\n", rc)); + vboxNetFltSolarisCloseDev(pUdp4VNodeHeld, pUdp4User); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: failed to open UDP. rc=%d\n", rc)); + + rc = VERR_INTNET_FLT_IF_FAILED; + } + else + { + /* + * This would happen for interfaces that are not plumbed. + */ + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp4: Warning: seems '%s' is unplumbed.\n", pThis->szName)); + rc = VINF_SUCCESS; + } + + ldi_close(ArpDevHandle, FREAD | FWRITE, kcred); + ldi_close(Ip4DevHandle, FREAD | FWRITE, kcred); + + return rc; +} + + +/** + * Dynamically attach under IPv6 on the host stack. + * + * @returns VBox status code. + * @param pThis The instance. + * @param fAttach Is this an attach or detach. + */ +static int vboxNetFltSolarisAttachIp6(PVBOXNETFLTINS pThis, bool fAttach) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachIp6 pThis=%p fAttach=%d\n", pThis, fAttach)); + + /* + * Statutory Warning: Hackish code ahead. + */ + char *pszModName = DEVICE_NAME; + + struct lifreq Ip6Interface; + bzero(&Ip6Interface, sizeof(Ip6Interface)); + Ip6Interface.lifr_addr.ss_family = AF_INET6; + strncpy(Ip6Interface.lifr_name, pThis->szName, sizeof(Ip6Interface.lifr_name)); + + struct strmodconf StrMod; + StrMod.mod_name = pszModName; + StrMod.pos = -1; /* this is filled in later. */ + + int rc; + int ret; + ldi_ident_t DeviceIdent = ldi_ident_from_anon(); + ldi_handle_t Ip6DevHandle; + + /* + * Open the IPv6 stream as a layered devices. + */ + rc = ldi_open_by_name(IP6_DEV_NAME, FREAD | FWRITE, kcred, &Ip6DevHandle, DeviceIdent); + ldi_ident_release(DeviceIdent); + if (rc) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to open the IPv6 stream on '%s'.\n", pThis->szName)); + return VERR_INTNET_FLT_IF_FAILED; + } + + /* + * Obtain the interface flags from IPv6. + */ + rc = vboxNetFltSolarisGetIfFlags(Ip6DevHandle, &Ip6Interface); + if (RT_SUCCESS(rc)) + { + /* + * Open the UDP stream. We sort of cheat here and obtain the vnode so that we can perform + * things that are not possible from the layered interface. + */ + vnode_t *pUdp6VNode = NULL; + vnode_t *pUdp6VNodeHeld = NULL; + TIUSER *pUdp6User = NULL; + rc = vboxNetFltSolarisOpenDev(UDP6_DEV_NAME, &pUdp6VNode, &pUdp6VNodeHeld, &pUdp6User); + if (RT_SUCCESS(rc)) + { + /* + * Get the multiplexor IDs. + */ + rc = ldi_ioctl(Ip6DevHandle, SIOCGLIFMUXID, (intptr_t)&Ip6Interface, FKIOCTL, kcred, &ret); + if (!rc) + { + /* + * Get the multiplex file descriptor to the lower streams. Generally this is lost + * once a module is I_PLINK, we need to reobtain it for inserting/removing ourselves from the stack. + */ + int Ip6MuxFd; + rc = vboxNetFltSolarisMuxIdToFd(pUdp6VNode, Ip6Interface.lifr_ip_muxid, &Ip6MuxFd); + if (RT_SUCCESS(rc)) + { + /* + * We need to I_PUNLINK on these multiplexor IDs before we can start + * operating on the lower stream as insertions are direct operations on the lower stream. + */ + rc = strioctl(pUdp6VNode, I_PUNLINK, (intptr_t)Ip6Interface.lifr_ip_muxid, 0, K_TO_K, kcred, &ret); + if (!rc) + { + /* + * Obtain the vnode from the useless userland file descriptor. + */ + file_t *pIpFile = getf(Ip6MuxFd); + if ( pIpFile + && VNODE_FOR_FILE_T(pIpFile)) + { + vnode_t *pIp6VNode = VNODE_FOR_FILE_T(pIpFile); + + /* + * Find the position on the host stack for attaching/detaching ourselves. + */ + rc = vboxNetFltSolarisDetermineModPos(fAttach, pIp6VNode, &StrMod.pos); + if (RT_SUCCESS(rc)) + { + /* + * Set global data which will be grabbed by ModOpen. + * There is a known (though very unlikely) race here because + * of the inability to pass user data while inserting. + */ + rc = RTSemFastMutexRequest(g_VBoxNetFltSolarisMtx); + AssertRCReturn(rc, rc); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = pThis; + g_VBoxNetFltSolarisStreamType = kIp6Stream; + } + + /* + * Inject/Eject from the host IPv6 stack. + */ + rc = strioctl(pIp6VNode, fAttach ? _I_INSERT : _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, + g_pVBoxNetFltSolarisCred, &ret); + + if (fAttach) + { + g_VBoxNetFltSolarisInstance = NULL; + g_VBoxNetFltSolarisStreamType = kUndefined; + } + + RTSemFastMutexRelease(g_VBoxNetFltSolarisMtx); + + if (!rc) + { + /* + * Our job's not yet over; we need to relink the upper and lower streams + * otherwise we've pretty much screwed up the host interface. + */ + rc = vboxNetFltSolarisRelinkIp6(pUdp6VNode, &Ip6Interface, Ip6MuxFd); + if (RT_SUCCESS(rc)) + { + /* + * Close the devices ONLY during the return from function case; otherwise + * we end up close twice which is an instant kernel panic. + */ + vboxNetFltSolarisCloseDev(pUdp6VNodeHeld, pUdp6User); + ldi_close(Ip6DevHandle, FREAD | FWRITE, kcred); + releasef(Ip6MuxFd); + + Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: Success! %s %s@(IPv6:%d) " + "%s interface %s\n", fAttach ? "Injected" : "Ejected", StrMod.mod_name, + StrMod.pos, fAttach ? "to" : "from", pThis->szName)); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: Relinking failed. Mode=%s rc=%d.\n", + fAttach ? "inject" : "eject", rc)); + } + + if (fAttach) + strioctl(pIp6VNode, _I_REMOVE, (intptr_t)&StrMod, 0, K_TO_K, kcred, &ret); + + vboxNetFltSolarisRelinkIp6(pUdp6VNode, &Ip6Interface, Ip6MuxFd); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to %s the IP stack. rc=%d\n", + fAttach ? "inject into" : "eject from", rc)); + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to find position. rc=%d\n", rc)); + + releasef(Ip6MuxFd); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get vnode from MuxFd.\n")); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to unlink upper stream rc=%d.\n", rc)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get MuxFd from MuxId. rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get Mux Ids. rc=%d\n", rc)); + + vboxNetFltSolarisCloseDev(pUdp6VNodeHeld, pUdp6User); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to open UDP. rc=%d\n", rc)); + + rc = VERR_INTNET_FLT_IF_FAILED; + } + else + { + Log((DEVICE_NAME ":vboxNetFltSolarisAttachIp6: failed to get IPv6 flags.\n", pThis->szName)); + rc = VERR_INTNET_FLT_IF_NOT_FOUND; + } + + ldi_close(Ip6DevHandle, FREAD | FWRITE, kcred); + + return rc; +} + + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING +/** + * Ipv6 dynamic attachment timer callback to attach to the Ipv6 stream if needed. + * + * @param pTimer Pointer to the timer. + * @param pvData Opaque pointer to the instance. + * @param iTick Timer tick (unused). + */ +static void vboxNetFltSolarispIp6Timer(PRTTIMER pTimer, void *pvData, uint64_t iTick) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarispIp6Timer pTimer=%p pvData=%p\n", pTimer, pvData)); + + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; + if ( RT_LIKELY(pThis) + && RT_LIKELY(pTimer)) + { + vboxnetflt_stream_t *pIp6Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp6Stream, vboxnetflt_stream_t *); + bool fIp6Attaching = ASMAtomicUoReadBool(&pThis->u.s.fAttaching); + if ( !pIp6Stream + && !fIp6Attaching) + { + int rc = RTSemFastMutexRequest(pThis->u.s.hPollMtx); + if (RT_SUCCESS(rc)) + { + ASMAtomicUoWriteBool(&pThis->u.s.fAttaching, true); + + vboxNetFltSolarisAttachIp6(pThis, true /* fAttach */); + + ASMAtomicUoWriteBool(&pThis->u.s.fAttaching, false); + RTSemFastMutexRelease(pThis->u.s.hPollMtx); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarispIp6Timer failed to obtain mutex. rc=%Rrc\n", rc)); + } + } + + NOREF(iTick); +} + + +/** + * Setups up a kernel timer based on the driver property for attaching to IPv6 stream + * whenever the stream gets plumbed for the interface. + * + * @returns VBox status code. + * @param pThis The instance. + */ +static int vboxNetFltSolarisSetupIp6Polling(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling pThis=%p\n", pThis)); + + int rc = VERR_GENERAL_FAILURE; + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); + if (RT_LIKELY(pPromiscStream)) + { + if (RT_LIKELY(pPromiscStream->pIp6Timer == NULL)) + { + /* + * Validate IPv6 polling interval. + */ + int Interval = g_VBoxNetFltSolarisPollInterval; + if (Interval < 1 || Interval > 120) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Invalid polling interval %d. Expected between" + " 1 and 120 secs.\n", Interval)); + return VERR_INVALID_PARAMETER; + } + + /* + * Setup kernel poll timer. + */ + rc = RTTimerCreateEx(&pPromiscStream->pIp6Timer, Interval * (uint64_t)1000000000, RTTIMER_FLAGS_CPU_ANY, + vboxNetFltSolarispIp6Timer, (void *)pThis); + if (RT_SUCCESS(rc)) + { + rc = RTTimerStart(pPromiscStream->pIp6Timer, 10 * (uint64_t)1000000000 /* 10 seconds to blastoff */); + Log((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Ipv6 %d second timer begins firing in 10 seconds.\n", + Interval)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Failed to create timer. rc=%d\n", rc)); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetupIp6Polling: Polling already started.\n")); + rc = VINF_SUCCESS; + } + } + return rc; +} +#endif + +/** + * Wrapper for detaching ourselves from the interface. + * + * @returns VBox status code. + * @param pThis The instance. + * @remarks Owns the globals mutex, so re-requesting it anytime during this phase + * would panic the system (e.g. in vboxNetFltSolarisFindInstance). + */ +static int vboxNetFltSolarisDetachFromInterface(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisDetachFromInterface pThis=%p\n", pThis)); + + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + vboxNetFltSolarisCloseStream(pThis); + int rc = VINF_SUCCESS; + if (pThis->u.s.pIp4Stream) + rc = vboxNetFltSolarisAttachIp4(pThis, false /* fAttach */); + if (pThis->u.s.pIp6Stream) + rc = vboxNetFltSolarisAttachIp6(pThis, false /* fAttach */); + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); + if ( pPromiscStream + && pPromiscStream->pIp6Timer == NULL) + { + RTTimerStop(pPromiscStream->pIp6Timer); + RTTimerDestroy(pPromiscStream->pIp6Timer); + ASMAtomicUoWriteNullPtr(&pPromiscStream->pIp6Timer); + } +#endif + + return rc; +} + + +/** + * Wrapper for attaching ourselves to the interface. + * + * @returns VBox status code. + * @param pThis The instance. + */ +static int vboxNetFltSolarisAttachToInterface(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface pThis=%p\n", pThis)); + + /* + * Since this is asynchronous streams injection, let the attach succeed before we can start + * processing the stream. + */ + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, true); + int rc = vboxNetFltSolarisOpenStream(pThis); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltSolarisAttachIp4(pThis, true /* fAttach */); + if (RT_SUCCESS(rc)) + { + /* + * Ipv6 attaching is optional and can fail. We don't bother to bring down the whole + * attach process just if Ipv6 interface is unavailable. + */ + int rc2 = vboxNetFltSolarisAttachIp6(pThis, true /* fAttach */); + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + /* + * If Ip6 interface is not plumbed and an Ip6 polling interval is specified, we need + * to begin polling to attach on the Ip6 interface whenever it comes up. + */ + if ( rc2 == VERR_INTNET_FLT_IF_NOT_FOUND + && g_VBoxNetFltSolarisPollInterval != -1) + { + int rc3 = vboxNetFltSolarisSetupIp6Polling(pThis); + if (RT_FAILURE(rc3)) + { + /* + * If we failed to setup Ip6 polling, warn in the release log and continue. + */ + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface IPv6 polling inactive. rc=%Rrc\n", rc3)); + } + } +#endif + + /* + * Report promiscuousness and capabilities. + */ + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + /** @todo There is no easy way of obtaining the global host side promiscuous + * counter. Currently we just return false. */ + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, false); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } + + /* + * Ipv4 is successful, and maybe Ipv6, we're ready for transfers. + */ + ASMAtomicWriteBool(&pThis->fDisconnectedFromHost, false); + + return VINF_SUCCESS; + } + + vboxNetFltSolarisCloseStream(pThis); + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface vboxNetFltSolarisOpenStream failed rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Create a solaris message block from the SG list. + * + * @returns Solaris message block. + * @param pThis The instance. + * @param pSG Pointer to the scatter-gather list. + * @param fDst The destination mask, INTNETTRUNKDIR_XXX. Ignored. + */ +static mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG pThis=%p pSG=%p\n", pThis, pSG)); + + mblk_t *pMsg = allocb(pSG->cbTotal, BPRI_MED); + if (RT_UNLIKELY(!pMsg)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG failed to alloc %d bytes for mblk_t.\n", pSG->cbTotal)); + return NULL; + } + + /* + * Single buffer copy. Maybe later explore the + * need/possibility for using a mblk_t chain rather. + */ + for (unsigned i = 0; i < pSG->cSegsUsed; i++) + { + if (pSG->aSegs[i].pv) + { + bcopy(pSG->aSegs[i].pv, pMsg->b_wptr, pSG->aSegs[i].cb); + pMsg->b_wptr += pSG->aSegs[i].cb; + } + } + DB_TYPE(pMsg) = M_DATA; + return pMsg; +} + + +/** + * Calculate the number of segments required for this message block. + * + * @returns Number of segments. + * @param pThis The instance + * @param pMsg Pointer to the data message. + */ +static unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg) +{ + unsigned cSegs = 0; + for (mblk_t *pCur = pMsg; pCur; pCur = pCur->b_cont) + if (MBLKL(pCur)) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (msgdsize(pMsg) < 60) + cSegs++; +#endif + + NOREF(pThis); + return RT_MAX(cSegs, 1); +} + + +/** + * Initializes an SG list from the given message block. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pMsg Pointer to the data message. + The caller must ensure it's not a control message block. + * @param pSG Pointer to the SG. + * @param cSegs Number of segments in the SG. + * This should match the number in the message block exactly! + * @param fSrc The source of the message. + */ +static int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pThis=%p pMsg=%p pSG=%p cSegs=%d\n", pThis, pMsg, pSG, cSegs)); + + /* + * Convert the message block to segments. Work INTNETSG::cbTotal. + */ + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + mblk_t *pCur = pMsg; + unsigned iSeg = 0; + while (pCur) + { + size_t cbSeg = MBLKL(pCur); + if (cbSeg) + { + void *pvSeg = pCur->b_rptr; + pSG->aSegs[iSeg].pv = pvSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->cbTotal += cbSeg; + iSeg++; + } + pCur = pCur->b_cont; + } + pSG->cSegsUsed = iSeg; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (pSG->cbTotal < 60 && (fSrc & INTNETTRUNKDIR_HOST)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pulling up to length.\n")); + + static uint8_t const s_abZero[128] = {0}; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->aSegs[iSeg].pv = (void *)&s_abZero[0]; + pSG->aSegs[iSeg].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + pSG->cSegsUsed++; + Assert(iSeg + 1 < cSegs); + } +#endif + + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG iSeg=%d pSG->cbTotal=%d msgdsize=%d\n", iSeg, pSG->cbTotal, msgdsize(pMsg))); + return VINF_SUCCESS; +} + + +/** + * Converts raw mode M_DATA messages to M_PROTO DL_UNITDATA_IND format. + * + * @returns VBox status code. + * @param pMsg Pointer to the raw message. + * @param ppDlpiMsg Where to store the M_PROTO message. + * + * @remarks The original raw message would be no longer valid and will be + * linked as part of the new DLPI message. Callers must take care + * not to use the raw message if this routine is successful. + */ +static int vboxNetFltSolarisRawToUnitData(mblk_t *pMsg, mblk_t **ppDlpiMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisRawToUnitData pMsg=%p\n", pMsg)); + + if (DB_TYPE(pMsg) != M_DATA) + return VERR_NO_MEMORY; + + size_t cbMsg = sizeof(dl_unitdata_ind_t) + 2 * sizeof(vboxnetflt_dladdr_t); + mblk_t *pDlpiMsg = allocb(cbMsg, BPRI_MED); + if (RT_UNLIKELY(!pDlpiMsg)) + return VERR_NO_MEMORY; + + DB_TYPE(pDlpiMsg) = M_PROTO; + dl_unitdata_ind_t *pDlpiData = (dl_unitdata_ind_t *)pDlpiMsg->b_rptr; + pDlpiData->dl_primitive = DL_UNITDATA_IND; + pDlpiData->dl_dest_addr_length = VBOXNETFLT_DLADDRL; + pDlpiData->dl_dest_addr_offset = sizeof(dl_unitdata_ind_t); + pDlpiData->dl_src_addr_length = VBOXNETFLT_DLADDRL; + pDlpiData->dl_src_addr_offset = VBOXNETFLT_DLADDRL + sizeof(dl_unitdata_ind_t); + + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; + + vboxnetflt_dladdr_t *pDlAddr = (vboxnetflt_dladdr_t *)(pDlpiMsg->b_rptr + pDlpiData->dl_dest_addr_offset); + pDlAddr->SAP = RT_BE2H_U16(pEthHdr->EtherType); + bcopy(&pEthHdr->DstMac, &pDlAddr->Mac, sizeof(RTMAC)); + + pDlAddr = (vboxnetflt_dladdr_t *)(pDlpiMsg->b_rptr + pDlpiData->dl_src_addr_offset); + pDlAddr->SAP = RT_BE2H_U16(pEthHdr->EtherType); + bcopy(&pEthHdr->SrcMac, &pDlAddr->Mac, sizeof(RTMAC)); + + pDlpiMsg->b_wptr = pDlpiMsg->b_rptr + cbMsg; + + /* Make the message point to the protocol header */ + pMsg->b_rptr += sizeof(RTNETETHERHDR); + + pDlpiMsg->b_cont = pMsg; + *ppDlpiMsg = pDlpiMsg; + return VINF_SUCCESS; +} + +#if 0 +/** + * Converts DLPI M_PROTO messages to the raw mode M_DATA format. + * + * @returns VBox status code. + * @param pMsg Pointer to the M_PROTO message. + * @param ppRawMsg Where to store the converted message. + * + * @remarks If successful, the original pMsg is no longer valid, it will be deleted. + * Callers must take care not to continue to use pMsg after a successful + * call to this conversion routine. + */ +static int vboxNetFltSolarisUnitDataToRaw(PVBOXNETFLTINS pThis, mblk_t *pMsg, mblk_t **ppRawMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw pMsg=%p\n", pMsg)); + + if ( !pMsg->b_cont + || DB_TYPE(pMsg) != M_PROTO) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw invalid input message.\n")); + return VERR_NET_PROTOCOL_ERROR; + } + + /* + * Upstream consumers send/receive packets in the fast path mode. + * We of course need to convert them into raw ethernet frames. + */ + RTNETETHERHDR EthHdr; + union DL_primitives *pPrim = (union DL_primitives *)pMsg->b_rptr; + switch (pPrim->dl_primitive) + { + case DL_UNITDATA_IND: + { + /* + * Receive side. + */ + dl_unitdata_ind_t *pDlpiMsg = (dl_unitdata_ind_t *)pMsg->b_rptr; + bcopy(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset, &EthHdr.DstMac, sizeof(EthHdr.DstMac)); + bcopy(pMsg->b_rptr + pDlpiMsg->dl_src_addr_offset, &EthHdr.SrcMac, sizeof(EthHdr.SrcMac)); + + vboxnetflt_dladdr_t *pDLSapAddr = (vboxnetflt_dladdr_t *)(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset); + EthHdr.EtherType = RT_H2BE_U16(pDLSapAddr->SAP); + + break; + } + + case DL_UNITDATA_REQ: + { + /* + * Send side. + */ + dl_unitdata_req_t *pDlpiMsg = (dl_unitdata_req_t *)pMsg->b_rptr; + + bcopy(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset, &EthHdr.DstMac, sizeof(EthHdr.DstMac)); + bcopy(&pThis->u.s.MacAddr, &EthHdr.SrcMac, sizeof(EthHdr.SrcMac)); + + vboxnetflt_dladdr_t *pDLSapAddr = (vboxnetflt_dladdr_t *)(pMsg->b_rptr + pDlpiMsg->dl_dest_addr_offset); + EthHdr.EtherType = RT_H2BE_U16(pDLSapAddr->SAP); + + break; + } + + default: + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisUnitDataToRaw Unknown M_PROTO. This shouldn't be happening!!")); + return VERR_NET_PROTOCOL_ERROR; + } + } + + /* + * Let us just link it as a mblk_t chain rather than re-copy the entire message. + * The vboxNetFltSolarisMBlkToSG function will handle chained mblk_t's. + */ + size_t cbLen = sizeof(EthHdr); + mblk_t *pEtherMsg = allocb(cbLen, BPRI_MED); + if (RT_UNLIKELY(!pEtherMsg)) + return VERR_NO_MEMORY; + + DB_TYPE(pEtherMsg) = M_DATA; + bcopy(&EthHdr, pEtherMsg->b_wptr, sizeof(EthHdr)); + pEtherMsg->b_wptr += cbLen; + + pEtherMsg->b_cont = pMsg->b_cont; + + /* + * Change the chained blocks to type M_DATA. + */ + for (mblk_t *pTmp = pEtherMsg->b_cont; pTmp; pTmp = pTmp->b_cont) + DB_TYPE(pTmp) = M_DATA; + + pMsg->b_cont = NULL; + freemsg(pMsg); + + *ppRawMsg = pEtherMsg; + return VINF_SUCCESS; +} +#endif + +/** + * Initializes a packet identifier. + * + * @param pTag Pointer to the packed identifier. + * @param pMsg Pointer to the message to be identified. + * + * @remarks Warning!!! This function assumes 'pMsg' is an unchained message. + */ +static inline void vboxNetFltSolarisInitPacketId(PVBOXNETFLTPACKETID pTag, mblk_t *pMsg) +{ + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; + size_t cbMsg = MBLKL(pMsg); + + pTag->cbPacket = cbMsg; + pTag->Checksum = RTCrc32(pMsg->b_rptr, cbMsg); + bcopy(&pEthHdr->SrcMac, &pTag->SrcMac, sizeof(RTMAC)); + bcopy(&pEthHdr->DstMac, &pTag->DstMac, sizeof(RTMAC)); +} + + +/** + * Queues a packet for loopback elimination. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pPromiscStream Pointer to the promiscuous stream. + * @param pMsg Pointer to the message. + */ +static int vboxNetFltSolarisQueueLoopback(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg) +{ + Assert(pThis); + Assert(pMsg); + Assert(DB_TYPE(pMsg) == M_DATA); + Assert(pPromiscStream); + + LogFunc((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback pThis=%p pPromiscStream=%p pMsg=%p\n", pThis, pPromiscStream, pMsg)); + + if (RT_UNLIKELY(pMsg->b_cont)) + { + /* + * We don't currently make chained messages in on Xmit + * so this only needs to be supported when we do that. + */ + return VERR_NOT_SUPPORTED; + } + + size_t cbMsg = MBLKL(pMsg); + if (RT_UNLIKELY(cbMsg < sizeof(RTNETETHERHDR))) + return VERR_NET_MSG_SIZE; + + int rc = VINF_SUCCESS; + mutex_enter(&pThis->u.s.hMtx); + + PVBOXNETFLTPACKETID pCur = NULL; + if (pPromiscStream->cLoopback < VBOXNETFLT_LOOPBACK_SIZE + || ( pPromiscStream->pHead + && pPromiscStream->pHead->cbPacket == 0)) + { + do + { + if (!pPromiscStream->pHead) + { + pCur = RTMemAlloc(sizeof(VBOXNETFLTPACKETID)); + if (RT_UNLIKELY(!pCur)) + { + rc = VERR_NO_MEMORY; + break; + } + + vboxNetFltSolarisInitPacketId(pCur, pMsg); + + pCur->pNext = NULL; + pPromiscStream->pHead = pCur; + pPromiscStream->pTail = pCur; + pPromiscStream->cLoopback++; + + Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback initialized head. checksum=%u.\n", + pPromiscStream->pHead->Checksum)); + break; + } + else if ( pPromiscStream->pHead + && pPromiscStream->pHead->cbPacket == 0) + { + pCur = pPromiscStream->pHead; + vboxNetFltSolarisInitPacketId(pCur, pMsg); + + Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback re-used head checksum=%u cLoopback=%d.\n", + pCur->Checksum, pPromiscStream->cLoopback)); + break; + } + else + { + pCur = RTMemAlloc(sizeof(VBOXNETFLTPACKETID)); + if (RT_UNLIKELY(!pCur)) + { + rc = VERR_NO_MEMORY; + break; + } + + vboxNetFltSolarisInitPacketId(pCur, pMsg); + + pCur->pNext = pPromiscStream->pHead; + pPromiscStream->pHead = pCur; + pPromiscStream->cLoopback++; + + Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback added head checksum=%u cLoopback=%d.\n", pCur->Checksum, + pPromiscStream->cLoopback)); + break; + } + } while (0); + } + else + { + /* + * Maximum loopback queue size reached. Re-use tail as head. + */ + Assert(pPromiscStream->pHead); + Assert(pPromiscStream->pTail); + + /* + * Find tail's previous item. + */ + PVBOXNETFLTPACKETID pPrev = NULL; + pCur = pPromiscStream->pHead; + + /** @todo consider if this is worth switching to a double linked list... */ + while (pCur != pPromiscStream->pTail) + { + pPrev = pCur; + pCur = pCur->pNext; + } + + pPromiscStream->pTail = pPrev; + pPromiscStream->pTail->pNext = NULL; + pCur->pNext = pPromiscStream->pHead; + pPromiscStream->pHead = pCur; + + vboxNetFltSolarisInitPacketId(pCur, pMsg); + Log((DEVICE_NAME ":vboxNetFltSolarisQueueLoopback recycled tail!! checksum=%u cLoopback=%d\n", pCur->Checksum, + pPromiscStream->cLoopback)); + } + + mutex_exit(&pThis->u.s.hMtx); + + return rc; +} + + +/** + * Checks if the packet is enqueued for loopback as our own packet. + * + * @returns If it's our packet, returns true after dequeuing it, otherwise false. + * @param pThis The instance. + * @param pPromiscStream Pointer to the promiscuous stream. + * @param pMsg Pointer to the message. + */ +static bool vboxNetFltSolarisIsOurMBlk(PVBOXNETFLTINS pThis, vboxnetflt_promisc_stream_t *pPromiscStream, mblk_t *pMsg) +{ + Assert(pThis); + Assert(pPromiscStream); + Assert(pMsg); + Assert(DB_TYPE(pMsg) == M_DATA); + + LogFunc((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk pThis=%p pMsg=%p\n", pThis, pMsg)); + + if (pMsg->b_cont) + { + /** Handle this when Xmit makes chained messages */ + return false; + } + + size_t cbMsg = MBLKL(pMsg); + if (cbMsg < sizeof(RTNETETHERHDR)) + return false; + + mutex_enter(&pThis->u.s.hMtx); + + PVBOXNETFLTPACKETID pPrev = NULL; + PVBOXNETFLTPACKETID pCur = pPromiscStream->pHead; + bool fIsOurPacket = false; + while (pCur) + { + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; + if ( pCur->cbPacket != cbMsg + || pCur->SrcMac.au8[0] != pEthHdr->SrcMac.au8[0] + || pCur->SrcMac.au8[1] != pEthHdr->SrcMac.au8[1] + || pCur->SrcMac.au8[2] != pEthHdr->SrcMac.au8[2] + || pCur->SrcMac.au8[3] != pEthHdr->SrcMac.au8[3] + || pCur->SrcMac.au8[4] != pEthHdr->SrcMac.au8[4] + || pCur->SrcMac.au8[5] != pEthHdr->SrcMac.au8[5] + || pCur->DstMac.au8[0] != pEthHdr->DstMac.au8[0] + || pCur->DstMac.au8[1] != pEthHdr->DstMac.au8[1] + || pCur->DstMac.au8[2] != pEthHdr->DstMac.au8[2] + || pCur->DstMac.au8[3] != pEthHdr->DstMac.au8[3] + || pCur->DstMac.au8[4] != pEthHdr->DstMac.au8[4] + || pCur->DstMac.au8[5] != pEthHdr->DstMac.au8[5]) + { + pPrev = pCur; + pCur = pCur->pNext; + continue; + } + + uint16_t Checksum = RTCrc32(pMsg->b_rptr, cbMsg); + if (pCur->Checksum != Checksum) + { + pPrev = pCur; + pCur = pCur->pNext; + continue; + } + + /* + * Yes, it really is our own packet, mark it as handled + * and move it as a "free slot" to the head and return success. + */ + pCur->cbPacket = 0; + if (pPrev) + { + if (!pCur->pNext) + pPromiscStream->pTail = pPrev; + + pPrev->pNext = pCur->pNext; + pCur->pNext = pPromiscStream->pHead; + pPromiscStream->pHead = pCur; + } + fIsOurPacket = true; + + Log((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk found packet %p Checksum=%u cLoopback=%d\n", pMsg, Checksum, + pPromiscStream->cLoopback)); + break; + } + + Log((DEVICE_NAME ":vboxNetFltSolarisIsOurMBlk returns %d.\n", fIsOurPacket)); + mutex_exit(&pThis->u.s.hMtx); + return fIsOurPacket; +} + + +/** + * Helper. + */ +DECLINLINE(bool) vboxNetFltPortSolarisIsHostMac(PVBOXNETFLTINS pThis, PCRTMAC pMac) +{ + /* + * MAC address change acknowledgements are intercepted on the read side + * hence theoretically we are always update to date with any changes. + */ + return pThis->u.s.MacAddr.au16[0] == pMac->au16[0] + && pThis->u.s.MacAddr.au16[1] == pMac->au16[1] + && pThis->u.s.MacAddr.au16[2] == pMac->au16[2]; +} + + +/** + * Worker for routing messages from the wire or from the host. + * + * @returns VBox status code. + * @param pThis The instance. + * @param pStream Pointer to the stream. + * @param pQueue Pointer to the read queue. + * @param pMsg Pointer to the message. + */ +static int vboxNetFltSolarisRecv(PVBOXNETFLTINS pThis, vboxnetflt_stream_t *pStream, queue_t *pQueue, mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisRecv pThis=%p pMsg=%p\n", pThis, pMsg)); + + AssertCompile(sizeof(struct ether_header) == sizeof(RTNETETHERHDR)); + Assert(pStream->Type == kPromiscStream); + + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, vboxnetflt_promisc_stream_t *); + if (RT_UNLIKELY(!pPromiscStream)) + { + LogRel((DEVICE_NAME ":Promiscuous stream missing!! Failing to receive packet.\n")); + return VERR_INVALID_POINTER; + } + + /* + * Paranoia... + */ + if (RT_UNLIKELY(MBLKL(pMsg) < sizeof(RTNETETHERHDR))) + { + size_t cbMsg = msgdsize(pMsg); + if (cbMsg < sizeof(RTNETETHERHDR)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv %s: packet too small. Dropping packet.\n", pThis->szName)); + return VINF_SUCCESS; + } + + mblk_t *pFullMsg = msgpullup(pMsg, -1 /* all data blocks */); + if (pFullMsg) + { + freemsg(pMsg); + pMsg = pFullMsg; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv msgpullup failed.\n")); + return VERR_NO_MEMORY; + } + } + + /* + * Don't loopback packets we transmit to the wire. + */ + if (vboxNetFltSolarisIsOurMBlk(pThis, pPromiscStream, pMsg)) + { + Log((DEVICE_NAME ":Avoiding packet loopback.\n")); + return VINF_SUCCESS; + } + + /* + * Figure out the source of the packet based on the source Mac address. + */ + uint32_t fSrc = INTNETTRUNKDIR_WIRE; + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; + if (vboxNetFltPortSolarisIsHostMac(pThis, &pEthHdr->SrcMac)) + fSrc = INTNETTRUNKDIR_HOST; + + /* + * Afaik; we no longer need to worry about incorrect checksums because we now use + * a dedicated stream and don't intercept packets under IP/ARP which might be doing + * checksum offloading. + */ +#if 0 + if (fSrc & INTNETTRUNKDIR_HOST) + { + mblk_t *pCorrectedMsg = vboxNetFltSolarisFixChecksums(pMsg); + if (pCorrectedMsg) + pMsg = pCorrectedMsg; + } + vboxNetFltSolarisAnalyzeMBlk(pMsg); +#endif + + /* + * Solaris raw mode streams for priority-tagged VLAN does not strip the VLAN tag. + * It zero's the VLAN-Id but keeps the tag intact as part of the Ethernet header. + * We need to manually strip these tags out or the guests might get confused. + */ + bool fCopied = false; + bool fTagged = false; + if ( pThis->u.s.fVLAN + && pPromiscStream->fRawMode) + { + if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_VLAN)) + { + if (msgdsize(pMsg) > sizeof(RTNETETHERHDR) + sizeof(VLANHEADER)) + { + if (pMsg->b_cont) + { + mblk_t *pFullMsg = msgpullup(pMsg, -1 /* all data blocks */); + if (pFullMsg) + { + /* Original pMsg will be freed by the caller */ + pMsg = pFullMsg; + fCopied = true; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv msgpullup failed.\n")); + return VERR_NO_MEMORY; + } + } + + PVLANHEADER pVlanHdr = (PVLANHEADER)(pMsg->b_rptr + sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType)); + Log((DEVICE_NAME ":Recv VLAN Pcp=%u Cfi=%u Id=%u\n", VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)), + VLAN_CFI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)))); + if ( VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)) > 0 + && VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)) == 0) + { + /* + * Create new Ethernet header with stripped VLAN tag. + */ + size_t cbEthPrefix = sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType); + mblk_t *pStrippedMsg = allocb(cbEthPrefix, BPRI_MED); + if (RT_LIKELY(pStrippedMsg)) + { + fTagged = true; + + /* + * Copy ethernet header excluding the ethertype. + */ + bcopy(pMsg->b_rptr, pStrippedMsg->b_wptr, cbEthPrefix); + pStrippedMsg->b_wptr += cbEthPrefix; + + /* + * Link the rest of the message (ethertype + data, skipping VLAN header). + */ + pMsg->b_rptr += cbEthPrefix + sizeof(VLANHEADER); + pStrippedMsg->b_cont = pMsg; + pMsg = pStrippedMsg; + Log((DEVICE_NAME ":Stripped VLAN tag.\n")); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisRecv insufficient memory for creating VLAN stripped packet" + " cbMsg=%u.\n", cbEthPrefix)); + if (fCopied) + freemsg(pMsg); + return VERR_NO_MEMORY; + } + } + } + } + } + + /* + * Route all received packets into the internal network. + */ + unsigned cSegs = vboxNetFltSolarisMBlkCalcSGSegs(pThis, pMsg); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + int rc = vboxNetFltSolarisMBlkToSG(pThis, pMsg, pSG, cSegs, fSrc); + if (RT_SUCCESS(rc)) + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL /* pvIf */, pSG, fSrc); + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG failed. rc=%d\n", rc)); + + /* + * If we've allocated the prefix before the VLAN tag in a new message, free that. + */ + if (fTagged) + { + mblk_t *pTagMsg = pMsg->b_cont; + pMsg->b_cont = NULL; /* b_cont could be the message from the caller or a copy we made (fCopied) */ + freemsg(pMsg); + pMsg = pTagMsg; + } + + /* + * If we made an extra copy for VLAN stripping, we need to free that ourselves. + */ + if (fCopied) + freemsg(pMsg); + + return VINF_SUCCESS; +} + +#if 0 +/** + * Finalize the message to be fed into the internal network. + * Verifies and tries to fix checksums for TCP, UDP and IP. + * + * @returns Corrected message or NULL if no change was required. + * @param pMsg Pointer to the message block. + * This must not be DLPI linked messages, must be M_DATA. + * + * @remarks If this function returns a checksum adjusted message, the + * passed in input message has been freed and should not be + * referenced anymore by the caller. + */ +static mblk_t *vboxNetFltSolarisFixChecksums(mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisFixChecksums pMsg=%p\n")); + + Assert(DB_TYPE(pMsg) == M_DATA); + + if (MBLKL(pMsg) < sizeof(RTNETETHERHDR)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisFixChecksums Packet shorter than ethernet header size!\n")); + return NULL; + } + + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; + if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV4)) + { + /* + * Check if we have a complete packet or being fed a chain. + */ + size_t cbIpPacket = 0; + mblk_t *pFullMsg = NULL; + if (pMsg->b_cont) + { + Log((DEVICE_NAME ":Chained mblk_t.\n")); + + /* + * Handle chain by making a packet copy to verify if the IP checksum is correct. + * Contributions to calculating IP checksums from a chained message block with + * odd/non-pulled up sizes are welcome. + */ + size_t cbFullMsg = msgdsize(pMsg); + mblk_t *pFullMsg = allocb(cbFullMsg, BPRI_MED); + Log((DEVICE_NAME ":msgdsize returns %d\n", cbFullMsg)); + if (RT_UNLIKELY(!pFullMsg)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisFixChecksums failed to alloc new message of %d bytes.\n", cbFullMsg)); + return NULL; + } + + for (mblk_t *pTmp = pMsg; pTmp; pTmp = pTmp->b_cont) + { + if (DB_TYPE(pTmp) == M_DATA) + { + bcopy(pTmp->b_rptr, pFullMsg->b_wptr, MBLKL(pTmp)); + pFullMsg->b_wptr += MBLKL(pTmp); + } + } + + DB_TYPE(pFullMsg) = M_DATA; + pEthHdr = (PRTNETETHERHDR)pFullMsg->b_rptr; + cbIpPacket = MBLKL(pFullMsg) - sizeof(RTNETETHERHDR); + } + else + cbIpPacket = MBLKL(pMsg) - sizeof(RTNETETHERHDR); + + /* + * Check if the IP checksum is valid. + */ + uint8_t *pbProtocol = (uint8_t *)(pEthHdr + 1); + PRTNETIPV4 pIpHdr = (PRTNETIPV4)pbProtocol; + size_t cbPayload = cbIpPacket - (pIpHdr->ip_hl << 2); + bool fChecksumAdjusted = false; + if (RTNetIPv4IsHdrValid(pIpHdr, cbPayload, cbPayload)) + { + pbProtocol += (pIpHdr->ip_hl << 2); + + /* + * Fix up TCP/UDP and IP checksums if they're incomplete/invalid. + */ + if (pIpHdr->ip_p == RTNETIPV4_PROT_TCP) + { + PRTNETTCP pTcpHdr = (PRTNETTCP)pbProtocol; + uint16_t TcpChecksum = RTNetIPv4TCPChecksum(pIpHdr, pTcpHdr, NULL); + if (pTcpHdr->th_sum != TcpChecksum) + { + pTcpHdr->th_sum = TcpChecksum; + fChecksumAdjusted = true; + Log((DEVICE_NAME ":fixed TCP checksum.\n")); + } + } + else if (pIpHdr->ip_p == RTNETIPV4_PROT_UDP) + { + PRTNETUDP pUdpHdr = (PRTNETUDP)pbProtocol; + uint16_t UdpChecksum = RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1); + + if (pUdpHdr->uh_sum != UdpChecksum) + { + pUdpHdr->uh_sum = UdpChecksum; + fChecksumAdjusted = true; + Log((DEVICE_NAME ":Fixed UDP checksum.")); + } + } + } + + if (fChecksumAdjusted) + { + /* + * If we made a copy and the checksum is corrected on the copy, + * free the original, return the checksum fixed copy. + */ + if (pFullMsg) + { + freemsg(pMsg); + return pFullMsg; + } + + return pMsg; + } + + /* + * If we made a copy and the checksum is NOT corrected, free the copy, + * and return NULL. + */ + if (pFullMsg) + freemsg(pFullMsg); + + return NULL; + } + + return NULL; +} + + +/** + * Simple packet dump, used for internal debugging. + * + * @param pMsg Pointer to the message to analyze and dump. + */ +static void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisAnalyzeMBlk pMsg=%p\n", pMsg)); + + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; + uint8_t *pb = pMsg->b_rptr; + if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV4)) + { + PRTNETIPV4 pIpHdr = (PRTNETIPV4)(pEthHdr + 1); + size_t cbLen = MBLKL(pMsg) - sizeof(*pEthHdr); + if (!pMsg->b_cont) + { + if (pIpHdr->ip_p == RTNETIPV4_PROT_ICMP) + LogRel((DEVICE_NAME ":ICMP D=%.6Rhxs S=%.6Rhxs T=%04x\n", pb, pb + 6, RT_BE2H_U16(*(uint16_t *)(pb + 12)))); + else if (pIpHdr->ip_p == RTNETIPV4_PROT_TCP) + LogRel((DEVICE_NAME ":TCP D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); + else if (pIpHdr->ip_p == RTNETIPV4_PROT_UDP) + { + PCRTNETUDP pUdpHdr = (PCRTNETUDP)((uint32_t *)pIpHdr + pIpHdr->ip_hl); + if ( RT_BE2H_U16(pUdpHdr->uh_sport) == 67 + && RT_BE2H_U16(pUdpHdr->uh_dport) == 68) + { + LogRel((DEVICE_NAME ":UDP bootp ack D=%.6Rhxs S=%.6Rhxs UDP_CheckSum=%04x Computex=%04x\n", pb, pb + 6, + RT_BE2H_U16(pUdpHdr->uh_sum), RT_BE2H_U16(RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1)))); + } + } + } + else + { + Log((DEVICE_NAME ":Chained IP packet. Skipping validity check.\n")); + } + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_VLAN)) + { + PVLANHEADER pVlanHdr = (PVLANHEADER)(pMsg->b_rptr + sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType)); + LogRel((DEVICE_NAME ":VLAN Pcp=%u Cfi=%u Id=%u\n", VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)), + VLAN_CFI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)))); + LogRel((DEVICE_NAME "%.*Rhxd\n", sizeof(VLANHEADER), pVlanHdr)); + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_ARP)) + { + PRTNETARPHDR pArpHdr = (PRTNETARPHDR)(pEthHdr + 1); + LogRel((DEVICE_NAME ":ARP Op=%d\n", pArpHdr->ar_oper)); + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV6)) + { + LogRel((DEVICE_NAME ":IPv6 D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_1) + || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_2) + || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_3)) + { + LogRel((DEVICE_NAME ":IPX packet.\n")); + } + else + { + LogRel((DEVICE_NAME ":Unknown EtherType=%x D=%.6Rhxs S=%.6Rhxs\n", RT_H2BE_U16(pEthHdr->EtherType), &pEthHdr->DstMac, + &pEthHdr->SrcMac)); + /* Log((DEVICE_NAME ":%.*Rhxd\n", MBLKL(pMsg), pMsg->b_rptr)); */ + } +} +#endif + + +/* -=-=-=-=-=- Common Hooks -=-=-=-=-=- */ + + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + LogFunc((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d\n", pThis, fActive)); + + /* + * Enable/disable promiscuous mode. + */ + vboxnetflt_promisc_params_t *pData = RTMemAllocZ(sizeof(vboxnetflt_promisc_params_t)); + if (RT_LIKELY(pData)) + { + /* + * See @bugref{5262} as to why we need to do all this qtimeout/qwriter tricks. + */ + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, + vboxnetflt_promisc_stream_t *); + if ( pPromiscStream + && pPromiscStream->Stream.pReadQueue) + { + pData->pThis = pThis; + pData->fPromiscOn = fActive; + if (ASMAtomicReadPtr(&pPromiscStream->TimeoutId)) + quntimeout(WR(pPromiscStream->Stream.pReadQueue), pPromiscStream->TimeoutId); + timeout_id_t TimeoutId = qtimeout(WR(pPromiscStream->Stream.pReadQueue), vboxNetFltSolarisPromiscReqWrap, + pData, 1 /* ticks */); + ASMAtomicWritePtr(&pPromiscStream->TimeoutId, TimeoutId); + return; /* pData will be freed by vboxNetFltSolarisPromiscReqWrap() */ + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d missing stream!\n", pThis, fActive)); + RTMemFree(pData); + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsSetActive out of memory!\n")); +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltOsDisconnectIt pThis=%p\n", pThis)); + + vboxNetFltSolarisDetachFromInterface(pThis); + + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + /* Nothing to do here. */ + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + LogFunc((DEVICE_NAME ":vboxNetFltOsDeleteInstance pThis=%p\n", pThis)); + + mutex_destroy(&pThis->u.s.hMtx); + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + if (pThis->u.s.hPollMtx != NIL_RTSEMFASTMUTEX) + { + RTSemFastMutexDestroy(pThis->u.s.hPollMtx); + pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; + } +#endif + +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + LogFunc((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p\n")); + + /* + * Mutex used for loopback lockouts. + */ + int rc = VINF_SUCCESS; + mutex_init(&pThis->u.s.hMtx, NULL /* name */, MUTEX_DRIVER, NULL /* cookie */); +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + rc = RTSemFastMutexCreate(&pThis->u.s.hPollMtx); + if (RT_SUCCESS(rc)) + { +#endif + rc = vboxNetFltSolarisAttachToInterface(pThis); + if (RT_SUCCESS(rc)) + return rc; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisAttachToInterface failed. rc=%Rrc\n", rc)); + +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + RTSemFastMutexDestroy(pThis->u.s.hPollMtx); + pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; + } + else + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to create poll mutex. rc=%Rrc\n", rc)); +#endif + + mutex_destroy(&pThis->u.s.hMtx); + + NOREF(pvContext); + return rc; +} + + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + /* + * Init. the solaris specific data. + */ + pThis->u.s.hIface = NULL; + pThis->u.s.pIp4Stream = NULL; + pThis->u.s.pIp6Stream = NULL; + pThis->u.s.pArpStream = NULL; + pThis->u.s.pPromiscStream = NULL; + pThis->u.s.fAttaching = false; + pThis->u.s.fVLAN = false; +#ifdef VBOXNETFLT_SOLARIS_IPV6_POLLING + pThis->u.s.hPollMtx = NIL_RTSEMFASTMUTEX; +#endif + bzero(&pThis->u.s.MacAddr, sizeof(pThis->u.s.MacAddr)); + return VINF_SUCCESS; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + /* + * We don't support interface rediscovery on Solaris hosts because the + * filter is very tightly bound to the stream. + */ + return false; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + NOREF(pThis); NOREF(pvIfData); NOREF(pMac); +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIf); NOREF(ppvIfData); + return VINF_SUCCESS; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + NOREF(pThis); NOREF(pvIfData); + return VINF_SUCCESS; +} + + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + NOREF(pvIfData); + LogFunc((DEVICE_NAME ":vboxNetFltPortOsXmit pThis=%p pSG=%p fDst=%d\n", pThis, pSG, fDst)); + + int rc = VINF_SUCCESS; + if (fDst & INTNETTRUNKDIR_WIRE) + { + vboxnetflt_promisc_stream_t *pPromiscStream = ASMAtomicUoReadPtrT(&pThis->u.s.pPromiscStream, + vboxnetflt_promisc_stream_t *); + if (RT_LIKELY(pPromiscStream)) + { + mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); + if (RT_LIKELY(pMsg)) + { + Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_WIRE\n")); + + vboxNetFltSolarisQueueLoopback(pThis, pPromiscStream, pMsg); + putnext(WR(pPromiscStream->Stream.pReadQueue), pMsg); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit vboxNetFltSolarisMBlkFromSG failed.\n")); + return VERR_NO_MEMORY; + } + } + } + + if (fDst & INTNETTRUNKDIR_HOST) + { + /* + * For unplumbed interfaces we would not be bound to IP or ARP. + * We either bind to both or neither; so atomic reading one should be sufficient. + */ + vboxnetflt_stream_t *pIp4Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp4Stream, vboxnetflt_stream_t *); + if (!pIp4Stream) + return rc; + + /* + * Create a message block and send it up the host stack (upstream). + */ + mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); + if (RT_LIKELY(pMsg)) + { + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; + + /* + * Send message up ARP stream. + */ + if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_ARP)) + { + Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST ARP\n")); + + vboxnetflt_stream_t *pArpStream = ASMAtomicUoReadPtrT(&pThis->u.s.pArpStream, vboxnetflt_stream_t *); + if (pArpStream) + { + /* + * Construct a DL_UNITDATA_IND style message for ARP as it doesn't understand fast path. + */ + mblk_t *pDlpiMsg; + rc = vboxNetFltSolarisRawToUnitData(pMsg, &pDlpiMsg); + if (RT_SUCCESS(rc)) + { + pMsg = pDlpiMsg; + + queue_t *pArpReadQueue = pArpStream->pReadQueue; + putnext(pArpReadQueue, pMsg); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisRawToUnitData failed!\n")); + freemsg(pMsg); + rc = VERR_NO_MEMORY; + } + } + else + freemsg(pMsg); /* Should really never happen... */ + } + else + { + vboxnetflt_stream_t *pIp6Stream = ASMAtomicUoReadPtrT(&pThis->u.s.pIp6Stream, vboxnetflt_stream_t *); + if ( pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV6) + && pIp6Stream) + { + /* + * Send messages up IPv6 stream. + */ + Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST IPv6\n")); + + pMsg->b_rptr += sizeof(RTNETETHERHDR); + queue_t *pIp6ReadQueue = pIp6Stream->pReadQueue; + putnext(pIp6ReadQueue, pMsg); + } + else + { + /* + * Send messages up IPv4 stream. + */ + Log((DEVICE_NAME ":vboxNetFltPortOsXmit INTNETTRUNKDIR_HOST IPv4\n")); + + pMsg->b_rptr += sizeof(RTNETETHERHDR); + queue_t *pIp4ReadQueue = pIp4Stream->pReadQueue; + putnext(pIp4ReadQueue, pMsg); + } + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG failed.\n")); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c new file mode 100644 index 00000000..4a717253 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/VBoxNetFltBow-solaris.c @@ -0,0 +1,1702 @@ +/* $Id: VBoxNetFltBow-solaris.c $ */ +/** @file + * VBoxNetFlt - Network Filter Driver (Host), 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 . + * + * 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_NET_FLT_DRV +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#define VBOXNETFLT_OS_SPECFIC 1 +#include "../VBoxNetFltInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxbow" +/** The module descriptions as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox NetBow" +/** The dynamically created VNIC name (hardcoded in NetIf-solaris.cpp). + * @todo move this define into a common header. */ +#define VBOXBOW_VNIC_NAME "vboxvnic" +/** The VirtualBox VNIC template name (hardcoded in NetIf-solaris.cpp). + * @todo move this define into a common header. */ +#define VBOXBOW_VNIC_TEMPLATE_NAME "vboxvnic_template" +/** Debugging switch for using symbols in kmdb */ +# define LOCAL static +/** VBOXNETFLTVNIC::u32Magic */ +# define VBOXNETFLTVNIC_MAGIC 0x0ddfaced + +/** VLAN tag masking, should probably be in IPRT? */ +#define VLAN_ID(vlan) (((vlan) >> 0) & 0x0fffu) +#define VLAN_CFI(vlan) (((vlan) >> 12) & 0x0001u) +#define VLAN_PRI(vlan) (((vlan) >> 13) & 0x0007u) +#define VLAN_TAG(pri,cfi,vid) (((pri) << 13) | ((cfi) << 12) | ((vid) << 0)) + +typedef struct VLANHEADER +{ + uint16_t Type; + uint16_t Data; +} VLANHEADER; +typedef struct VLANHEADER *PVLANHEADER; + +/* Private: from sys/vlan.h */ +#ifndef VLAN_ID_NONE +# define VLAN_ID_NONE 0 +#endif + +/* Private: from sys/param.h */ +#ifndef MAXLINKNAMESPECIFIER +# define MAXLINKNAMESPECIFIER 96 /* MAXLINKNAMELEN + ZONENAME_MAX */ +#endif + +/* Private: from sys/mac_client_priv.h, mac client function prototypes. */ +extern uint16_t mac_client_vid(mac_client_handle_t hClient); +extern void mac_client_get_resources(mac_client_handle_t hClient, mac_resource_props_t *pResources); +extern int mac_client_set_resources(mac_client_handle_t hClient, mac_resource_props_t *pResources); + + +/********************************************************************************************************************************* +* Kernel Entry Hooks * +*********************************************************************************************************************************/ +LOCAL int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +LOCAL int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +LOCAL int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppvResult); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxNetFltSolarisCbOps = +{ + nulldev, /* c open */ + nulldev, /* c close */ + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + nodev, /* c read */ + nodev, /* c write*/ + nodev, /* c ioctl*/ + 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 */ + nodev, /* c aread */ + nodev /* c awrite */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_VBoxNetFltSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxNetFltSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxNetFltSolarisAttach, + VBoxNetFltSolarisDetach, + nodev, /* reset */ + &g_VBoxNetFltSolarisCbOps, + NULL, /* bus ops */ + nodev, /* power */ + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxNetFltSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxNetFltSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxNetFltSolarisModLinkage = +{ + MODREV_1, + { + &g_VBoxNetFltSolarisModule, + NULL, + } +}; + +/* + * VBOXNETFLTVNICTEMPLATE: VNIC template information. + */ +typedef struct VBOXNETFLTVNICTEMPLATE +{ + /** The name of link on which the VNIC template is created on. */ + char szLinkName[MAXNAMELEN]; + /** The VLAN Id (can be VLAN_ID_NONE). */ + uint16_t uVLANId; + /** Resources (bandwidth, CPU bindings, flow priority etc.) */ + mac_resource_props_t Resources; +} VBOXNETFLTVNICTEMPLATE; +typedef struct VBOXNETFLTVNICTEMPLATE *PVBOXNETFLTVNICTEMPLATE; + +/** + * VBOXNETFLTVNIC: Per-VNIC instance data. + */ +typedef struct VBOXNETFLTVNIC +{ + /** Magic number (VBOXNETFLTVNIC_MAGIC). */ + uint32_t u32Magic; + /** Whether we created the VNIC or not. */ + bool fCreated; + /** Pointer to the VNIC template if any. */ + PVBOXNETFLTVNICTEMPLATE pVNICTemplate; + /** Pointer to the VirtualBox interface instance. */ + void *pvIf; + /** The MAC handle. */ + mac_handle_t hInterface; + /** The VNIC link ID. */ + datalink_id_t hLinkId; + /** The MAC client handle */ + mac_client_handle_t hClient; + /** The unicast address handle. */ + mac_unicast_handle_t hUnicast; + /** The promiscuous handle. */ + mac_promisc_handle_t hPromisc; + /* The VNIC name. */ + char szName[MAXLINKNAMESPECIFIER]; + /** Handle to the next VNIC in the list. */ + list_node_t hNode; +} VBOXNETFLTVNIC; +typedef struct VBOXNETFLTVNIC *PVBOXNETFLTVNIC; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global Device handle we only support one instance. */ +static dev_info_t *g_pVBoxNetFltSolarisDip = NULL; +/** The (common) global data. */ +static VBOXNETFLTGLOBALS g_VBoxNetFltSolarisGlobals; +/** Global next-free VNIC Id (never decrements). */ +static volatile uint64_t g_VBoxNetFltSolarisVNICId; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +LOCAL mblk_t *vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst); +LOCAL unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg); +LOCAL int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc); +LOCAL void vboxNetFltSolarisRecv(void *pvData, mac_resource_handle_t hResource, mblk_t *pMsg, boolean_t fLoopback); +//LOCAL void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg); +LOCAL int vboxNetFltSolarisReportInfo(PVBOXNETFLTINS pThis, mac_handle_t hInterface, bool fIsVNIC); +LOCAL int vboxNetFltSolarisInitVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC); +LOCAL int vboxNetFltSolarisInitVNICTemplate(PVBOXNETFLTINS pThis, PVBOXNETFLTVNICTEMPLATE pVNICTemplate); +LOCAL PVBOXNETFLTVNIC vboxNetFltSolarisAllocVNIC(void); +LOCAL void vboxNetFltSolarisFreeVNIC(PVBOXNETFLTVNIC pVNIC); +LOCAL void vboxNetFltSolarisDestroyVNIC(PVBOXNETFLTVNIC pVNIC); +LOCAL int vboxNetFltSolarisCreateVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC *ppVNIC); +DECLINLINE(int) vboxNetFltSolarisGetLinkId(const char *pszMacName, datalink_id_t *pLinkId); + +/** + * Kernel entry points + */ +int _init(void) +{ + Log((DEVICE_NAME ":_init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxNetFltSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + cmn_err(CE_NOTE, ":failed to disable autounloading!\n"); + + /* + * Initialize IPRT. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the globals and connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); + rc = vboxNetFltInitGlobalsAndIdc(&g_VBoxNetFltSolarisGlobals); + if (RT_SUCCESS(rc)) + { + rc = mod_install(&g_VBoxNetFltSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ":mod_install failed. rc=%d\n", rc)); + vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); + } + else + LogRel((DEVICE_NAME ":failed to initialize globals.\n")); + + RTR0Term(); + } + else + cmn_err(CE_NOTE, "failed to initialize IPRT (rc=%d)\n", rc); + + memset(&g_VBoxNetFltSolarisGlobals, 0, sizeof(g_VBoxNetFltSolarisGlobals)); + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + Log((DEVICE_NAME ":_fini\n")); + + /* + * Undo the work done during start (in reverse order). + */ + rc = vboxNetFltTryDeleteIdcAndGlobals(&g_VBoxNetFltSolarisGlobals); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ":_fini - busy! rc=%d\n", rc)); + return EBUSY; + } + + rc = mod_remove(&g_VBoxNetFltSolarisModLinkage); + if (!rc) + RTR0Term(); + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + /* _info() can be called before _init() so RTR0Init() might not be called at this point. */ + int rc = mod_info(&g_VBoxNetFltSolarisModLinkage, pModInfo); + return rc; +} + + +/** + * 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). + * + * @returns corresponding solaris error code. + */ +LOCAL int VBoxNetFltSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + Log((DEVICE_NAME ":VBoxNetFltSolarisAttach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_ATTACH: + { + g_pVBoxNetFltSolarisDip = pDip; + return DDI_SUCCESS; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_PM_RESUME: */ + default: + 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). + * + * @returns corresponding solaris error code. + */ +LOCAL int VBoxNetFltSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + Log((DEVICE_NAME ":VBoxNetFltSolarisDetach pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + switch (enmCmd) + { + case DDI_DETACH: + { + return DDI_SUCCESS; + } + + case DDI_RESUME: + { + /* Nothing to do here... */ + return DDI_SUCCESS; + } + + /* case DDI_PM_SUSPEND: */ + /* case DDI_HOT_PLUG_DETACH: */ + default: + return DDI_FAILURE; + } +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @returns corresponding solaris error code. + */ +LOCAL int VBoxNetFltSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + Log((DEVICE_NAME ":VBoxNetFltSolarisGetInfo pDip=%p enmCmd=%d pArg=%p instance=%d\n", pDip, enmCmd, getminor((dev_t)pvArg))); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + *ppvResult = g_pVBoxNetFltSolarisDip; + return *ppvResult ? DDI_SUCCESS : DDI_FAILURE; + } + + case DDI_INFO_DEVT2INSTANCE: + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; + break; + } + } + + return DDI_FAILURE; +} + + +/** + * Create a solaris message block from the SG list. + * + * @param pThis The instance. + * @param pSG Pointer to the scatter-gather list. + * @param fDst INTNETTRUNKDIR_XXX. + * + * @returns Solaris message block. + */ +DECLINLINE(mblk_t *) vboxNetFltSolarisMBlkFromSG(PVBOXNETFLTINS pThis, PINTNETSG pSG, uint32_t fDst) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG pThis=%p pSG=%p\n", pThis, pSG)); + + mblk_t *pMsg = allocb(pSG->cbTotal, BPRI_HI); + if (RT_UNLIKELY(!pMsg)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkFromSG failed to alloc %d bytes for mblk_t.\n", pSG->cbTotal)); + return NULL; + } + + /* + * Single buffer copy. Maybe later explore the + * need/possibility for using a mblk_t chain rather. + */ + for (unsigned i = 0; i < pSG->cSegsUsed; i++) + { + if (pSG->aSegs[i].pv) + { + bcopy(pSG->aSegs[i].pv, pMsg->b_wptr, pSG->aSegs[i].cb); + pMsg->b_wptr += pSG->aSegs[i].cb; + } + } + return pMsg; +} + + +/** + * Calculate the number of segments required for this message block. + * + * @param pThis The instance + * @param pMsg Pointer to the data message. + * + * @returns Number of segments. + */ +LOCAL unsigned vboxNetFltSolarisMBlkCalcSGSegs(PVBOXNETFLTINS pThis, mblk_t *pMsg) +{ + unsigned cSegs = 0; + for (mblk_t *pCur = pMsg; pCur; pCur = pCur->b_cont) + if (MBLKL(pCur)) + cSegs++; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (msgdsize(pMsg) < 60) + cSegs++; +#endif + + NOREF(pThis); + return RT_MAX(cSegs, 1); +} + + +/** + * Initializes an SG list from the given message block. + * + * @param pThis The instance. + * @param pMsg Pointer to the data message. + The caller must ensure it's not a control message block. + * @param pSG Pointer to the SG. + * @param cSegs Number of segments in the SG. + * This should match the number in the message block exactly! + * @param fSrc The source of the message. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisMBlkToSG(PVBOXNETFLTINS pThis, mblk_t *pMsg, PINTNETSG pSG, unsigned cSegs, uint32_t fSrc) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pThis=%p pMsg=%p pSG=%p cSegs=%d\n", pThis, pMsg, pSG, cSegs)); + + /* + * Convert the message block to segments. Works cbTotal and sets cSegsUsed. + */ + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + mblk_t *pCur = pMsg; + unsigned iSeg = 0; + while (pCur) + { + size_t cbSeg = MBLKL(pCur); + if (cbSeg) + { + void *pvSeg = pCur->b_rptr; + pSG->aSegs[iSeg].pv = pvSeg; + pSG->aSegs[iSeg].cb = cbSeg; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->cbTotal += cbSeg; + iSeg++; + } + pCur = pCur->b_cont; + } + pSG->cSegsUsed = iSeg; + +#ifdef PADD_RUNT_FRAMES_FROM_HOST + if (pSG->cbTotal < 60 && (fSrc & INTNETTRUNKDIR_HOST)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG pulling up to length.\n")); + + static uint8_t const s_abZero[128] = {0}; + pSG->aSegs[iSeg].Phys = NIL_RTHCPHYS; + pSG->aSegs[iSeg].pv = (void *)&s_abZero[0]; + pSG->aSegs[iSeg].cb = 60 - pSG->cbTotal; + pSG->cbTotal = 60; + pSG->cSegsUsed++; + Assert(iSeg + 1 < cSegs); + } +#endif + + Log((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG iSeg=%d pSG->cbTotal=%d msgdsize=%d\n", iSeg, pSG->cbTotal, msgdsize(pMsg))); + return VINF_SUCCESS; +} + + +#if 0 +/** + * Simple packet dump, used for internal debugging. + * + * @param pMsg Pointer to the message to analyze and dump. + */ +LOCAL void vboxNetFltSolarisAnalyzeMBlk(mblk_t *pMsg) +{ + LogFunc((DEVICE_NAME ":vboxNetFltSolarisAnalyzeMBlk pMsg=%p\n", pMsg)); + + PCRTNETETHERHDR pEthHdr = (PCRTNETETHERHDR)pMsg->b_rptr; + uint8_t *pb = pMsg->b_rptr; + if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV4)) + { + PRTNETIPV4 pIpHdr = (PRTNETIPV4)(pEthHdr + 1); + if (!pMsg->b_cont) + { + if (pIpHdr->ip_p == RTNETIPV4_PROT_ICMP) + LogRel((DEVICE_NAME ":ICMP D=%.6Rhxs S=%.6Rhxs T=%04x\n", pb, pb + 6, RT_BE2H_U16(*(uint16_t *)(pb + 12)))); + else if (pIpHdr->ip_p == RTNETIPV4_PROT_TCP) + LogRel((DEVICE_NAME ":TCP D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); + else if (pIpHdr->ip_p == RTNETIPV4_PROT_UDP) + { + PCRTNETUDP pUdpHdr = (PCRTNETUDP)((uint32_t *)pIpHdr + pIpHdr->ip_hl); + if ( RT_BE2H_U16(pUdpHdr->uh_sport) == 67 + && RT_BE2H_U16(pUdpHdr->uh_dport) == 68) + { + LogRel((DEVICE_NAME ":UDP bootp ack D=%.6Rhxs S=%.6Rhxs UDP_CheckSum=%04x Computex=%04x\n", pb, pb + 6, + RT_BE2H_U16(pUdpHdr->uh_sum), RT_BE2H_U16(RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pUdpHdr + 1)))); + } + } + } + else + { + Log((DEVICE_NAME ":Chained IP packet. Skipping validity check.\n")); + } + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_VLAN)) + { + PVLANHEADER pVlanHdr = (PVLANHEADER)(pMsg->b_rptr + sizeof(RTNETETHERHDR) - sizeof(pEthHdr->EtherType)); + LogRel((DEVICE_NAME ":VLAN Pcp=%u Cfi=%u Id=%u\n", VLAN_PRI(RT_BE2H_U16(pVlanHdr->Data)), + VLAN_CFI(RT_BE2H_U16(pVlanHdr->Data)), VLAN_ID(RT_BE2H_U16(pVlanHdr->Data)))); + LogRel((DEVICE_NAME "%.*Rhxd\n", sizeof(VLANHEADER), pVlanHdr)); + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_ARP)) + { + PRTNETARPHDR pArpHdr = (PRTNETARPHDR)(pEthHdr + 1); + LogRel((DEVICE_NAME ":ARP Op=%d\n", pArpHdr->ar_oper)); + } + else if (pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPV6)) + { + LogRel((DEVICE_NAME ":IPv6 D=%.6Rhxs S=%.6Rhxs\n", pb, pb + 6)); + } + else if ( pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_1) + || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_2) + || pEthHdr->EtherType == RT_H2BE_U16(RTNET_ETHERTYPE_IPX_3)) + { + LogRel((DEVICE_NAME ":IPX packet.\n")); + } + else + { + LogRel((DEVICE_NAME ":Unknown EtherType=%x D=%.6Rhxs S=%.6Rhxs\n", RT_H2BE_U16(pEthHdr->EtherType), &pEthHdr->DstMac, + &pEthHdr->SrcMac)); + /* Log((DEVICE_NAME ":%.*Rhxd\n", MBLKL(pMsg), pMsg->b_rptr)); */ + } +} +#endif + + +/** + * Helper. + */ +DECLINLINE(bool) vboxNetFltPortSolarisIsHostMac(PVBOXNETFLTINS pThis, PCRTMAC pMac) +{ + return pThis->u.s.MacAddr.au16[0] == pMac->au16[0] + && pThis->u.s.MacAddr.au16[1] == pMac->au16[1] + && pThis->u.s.MacAddr.au16[2] == pMac->au16[2]; +} + + +/** + * Receive (rx) entry point. + * + * @param pvData Private data. + * @param hResource The resource handle. + * @param pMsg The packet. + * @param fLoopback Whether this is a loopback packet or not. + */ +LOCAL void vboxNetFltSolarisRecv(void *pvData, mac_resource_handle_t hResource, mblk_t *pMsg, boolean_t fLoopback) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisRecv pvData=%p pMsg=%p fLoopback=%d cbData=%d\n", pvData, pMsg, fLoopback, + pMsg ? MBLKL(pMsg) : 0)); + + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvData; + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pMsg); + + /* + * Active? Retain the instance and increment the busy counter. + */ + if (!vboxNetFltTryRetainBusyActive(pThis)) + { + freemsgchain(pMsg); + return; + } + + uint32_t fSrc = INTNETTRUNKDIR_WIRE; + PRTNETETHERHDR pEthHdr = (PRTNETETHERHDR)pMsg->b_rptr; + if ( MBLKL(pMsg) >= sizeof(RTNETETHERHDR) + && vboxNetFltPortSolarisIsHostMac(pThis, &pEthHdr->SrcMac)) + fSrc = INTNETTRUNKDIR_HOST; + + /* + * Route all received packets into the internal network. + */ + uint16_t cFailed = 0; + for (mblk_t *pCurMsg = pMsg; pCurMsg != NULL; pCurMsg = pCurMsg->b_next) + { + unsigned cSegs = vboxNetFltSolarisMBlkCalcSGSegs(pThis, pCurMsg); + PINTNETSG pSG = (PINTNETSG)alloca(RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + int rc = vboxNetFltSolarisMBlkToSG(pThis, pMsg, pSG, cSegs, fSrc); + if (RT_SUCCESS(rc)) + pThis->pSwitchPort->pfnRecv(pThis->pSwitchPort, NULL, pSG, fSrc); + else + cFailed++; + } + vboxNetFltRelease(pThis, true /* fBusy */); + + if (RT_UNLIKELY(cFailed)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisMBlkToSG failed for %u packets.\n", cFailed)); + + freemsgchain(pMsg); + + NOREF(hResource); +} + + +#if 0 +/** + * MAC layer link notification hook. + * + * @param pvArg Opaque pointer to the instance. + * @param Type Notification Type. + * + * @remarks This hook will be invoked for various changes to the underlying + * interface even when VMs aren't running so don't do any funky stuff + * here. + */ +LOCAL void vboxNetFltSolarisLinkNotify(void *pvArg, mac_notify_type_t Type) +{ + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify pvArg=%p Type=%d\n", pvArg, Type)); + + PVBOXNETFLTINS pThis = pvArg; + AssertPtrReturnVoid(pThis); + AssertPtrReturnVoid(pThis->u.s.hInterface); + + switch (Type) + { + case MAC_NOTE_LINK: + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify link state change\n")); + link_state_t hLinkState = mac_stat_get(pThis->u.s.hInterface, MAC_STAT_LINK_STATE); + bool fDisconnectedFromHost = hLinkState == LINK_STATE_UP ? false : true; + if (fDisconnectedFromHost != ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost)) + { + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, fDisconnectedFromHost); + LogRel((DEVICE_NAME ":vboxNetFltSolarisLinkNotify link state change: new state=%s\n", + fDisconnectedFromHost ? "DOWN" : "UP")); + } + break; + } + + default: + return; + } +} +#endif + + +/** + * Report capabilities and MAC address to IntNet after obtaining the MAC address + * of the underlying interface for a VNIC or the current interface if it's a + * physical/ether-stub interface. + * + * @param pThis The instance. + * @param hInterface The Interface handle. + * @param fIsVNIC Whether this interface handle corresponds to a VNIC + * or not. + * + * @remarks Retains the instance while doing it's job. + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisReportInfo(PVBOXNETFLTINS pThis, mac_handle_t hInterface, bool fIsVNIC) +{ + mac_handle_t hLowerMac = NULL; + if (!fIsVNIC) + hLowerMac = hInterface; + else + { + hLowerMac = mac_get_lower_mac_handle(hInterface); + if (RT_UNLIKELY(!hLowerMac)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo failed to get lower MAC handle for '%s'\n", pThis->szName)); + return VERR_INVALID_HANDLE; + } + } + + pThis->u.s.hInterface = hLowerMac; + +#if 0 + /* + * Try setup link notification hooks, this might fail if mac_no_notification() + * doesn't support it. We won't bother using the private function since link notification + * isn't critical for us and ignore failures. + */ + pThis->u.s.hNotify = mac_notify_add(hLowerMac, vboxNetFltSolarisLinkNotify, pThis); + if (!pThis->u.s.hNotify) + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo Warning! Failed to setup link notification hook.\n")); +#endif + + mac_unicast_primary_get(hLowerMac, (uint8_t *)pThis->u.s.MacAddr.au8); + if (vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + Assert(pThis->pSwitchPort); + Log((DEVICE_NAME ":vboxNetFltSolarisReportInfo phys mac %.6Rhxs\n", &pThis->u.s.MacAddr)); + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, false); /** @todo Promisc */ + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisReportInfo failed to retain interface. pThis=%p\n", pThis)); + + return VERR_INTNET_FLT_IF_BUSY; +} + + +/** + * Initialize a VNIC, optionally from a template. + * + * @param pThis The instance. + * @param pVNIC Pointer to the VNIC. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisInitVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + /* + * Some paranoia. + */ + AssertReturn(pThis, VERR_INVALID_PARAMETER); + AssertReturn(pVNIC, VERR_INVALID_PARAMETER); + AssertReturn(pVNIC->hInterface, VERR_INVALID_POINTER); + AssertReturn(pVNIC->hLinkId != DATALINK_INVALID_LINKID, VERR_INVALID_HANDLE); + AssertReturn(!pVNIC->hClient, VERR_INVALID_POINTER); + + int rc = mac_client_open(pVNIC->hInterface, &pVNIC->hClient, + NULL, /* name of this client */ + MAC_OPEN_FLAGS_USE_DATALINK_NAME | /* client name same as underlying NIC */ + MAC_OPEN_FLAGS_MULTI_PRIMARY /* allow multiple primary unicasts */ + ); + if (RT_LIKELY(!rc)) + { + if (pVNIC->pVNICTemplate) + rc = mac_client_set_resources(pVNIC->hClient, &pVNIC->pVNICTemplate->Resources); + + if (RT_LIKELY(!rc)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisInitVNIC succesfully initialized VNIC.\n")); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNIC mac_client_set_resources failed. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_VNIC_INIT_FAILED; + } + + mac_client_close(pVNIC->hClient, 0 /* flags */); + pVNIC->hClient = NULL; + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNIC failed to open mac client for '%s' rc=%d\n", pThis->szName, rc)); + + return VERR_INTNET_FLT_VNIC_OPEN_FAILED; +} + + + +/** + * Get the underlying link name for a VNIC (template). + * + * @return VBox status code. + * @param hVNICMacHandle The handle to the VNIC. + * @param pszLowerLinkName Where to store the lower-mac linkname, must be + * at least MAXLINKNAMELEN in size. + */ +LOCAL int vboxNetFltSolarisGetLowerLinkName(mac_handle_t hVNICMacHandle, char *pszLowerLinkName) +{ + Assert(mac_is_vnic(hVNICMacHandle)); + mac_handle_t hPhysLinkHandle = mac_get_lower_mac_handle(hVNICMacHandle); + if (RT_LIKELY(hPhysLinkHandle)) + { + datalink_id_t PhysLinkId; + const char *pszMacName = mac_name(hPhysLinkHandle); + int rc = vboxNetFltSolarisGetLinkId(pszMacName, &PhysLinkId); + if (RT_SUCCESS(rc)) + { + rc = dls_mgmt_get_linkinfo(PhysLinkId, pszLowerLinkName, NULL /*class*/, NULL /*media*/, NULL /*flags*/); + if (RT_LIKELY(!rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get link info. pszMacName=%s pszLowerLinkName=%s\n", + pszMacName, pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_INFO_NOT_FOUND; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get link id. pszMacName=%s pszLowerLinkName=%s\n", + pszMacName, pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_ID_NOT_FOUND; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLowerLinkName failed to get lower-mac. pszLowerLinkName=%s\n", pszLowerLinkName)); + return VERR_INTNET_FLT_LOWER_LINK_OPEN_FAILED; +} + + +/** + * Initializes the VNIC template. This involves opening the template VNIC to + * retreive info. like the VLAN Id, underlying MAC address etc. + * + * @param pThis The VM connection instance. + * @param pVNICTemplate Pointer to a VNIC template to initialize. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisInitVNICTemplate(PVBOXNETFLTINS pThis, PVBOXNETFLTVNICTEMPLATE pVNICTemplate) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate pThis=%p pVNICTemplate=%p\n", pThis, pVNICTemplate)); + + AssertReturn(pVNICTemplate, VERR_INVALID_PARAMETER); + AssertReturn(pThis->u.s.fIsVNICTemplate == true, VERR_INVALID_STATE); + + /* + * Get the VNIC template's datalink ID. + */ + datalink_id_t VNICLinkId; + int rc = vboxNetFltSolarisGetLinkId(pThis->szName, &VNICLinkId); + if (RT_SUCCESS(rc)) + { + /* + * Open the VNIC to obtain a MAC handle so as to retreive the VLAN ID. + */ + mac_handle_t hInterface; + rc = mac_open_by_linkid(VNICLinkId, &hInterface); + if (!rc) + { + /* + * Get the underlying linkname. + */ + AssertCompile(sizeof(pVNICTemplate->szLinkName) >= MAXLINKNAMELEN); + rc = vboxNetFltSolarisGetLowerLinkName(hInterface, pVNICTemplate->szLinkName); + if (RT_SUCCESS(rc)) + { + /* + * Now open the VNIC template to retrieve the VLAN Id & resources. + */ + mac_client_handle_t hClient; + rc = mac_client_open(hInterface, &hClient, + NULL, /* name of this client */ + MAC_OPEN_FLAGS_USE_DATALINK_NAME | /* client name same as underlying NIC */ + MAC_OPEN_FLAGS_MULTI_PRIMARY /* allow multiple primary unicasts */ + ); + if (RT_LIKELY(!rc)) + { + pVNICTemplate->uVLANId = mac_client_vid(hClient); + mac_client_get_resources(hClient, &pVNICTemplate->Resources); + mac_client_close(hClient, 0 /* fFlags */); + mac_close(hInterface); + + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate successfully init. VNIC template. szLinkName=%s " + "VLAN Id=%u\n", pVNICTemplate->szLinkName, pVNICTemplate->uVLANId)); + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to open VNIC template. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to get lower linkname for VNIC template '%s'.\n", + pThis->szName)); + + mac_close(hInterface); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to open by link ID. rc=%d\n", rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + } + else + LogRel((DEVICE_NAME ":vboxNetFltSolarisInitVNICTemplate failed to get VNIC template link Id. rc=%d\n", rc)); + + return rc; +} + + +/** + * Allocate a VNIC structure. + * + * @returns An allocated VNIC structure or NULL in case of errors. + */ +LOCAL PVBOXNETFLTVNIC vboxNetFltSolarisAllocVNIC(void) +{ + PVBOXNETFLTVNIC pVNIC = RTMemAllocZ(sizeof(VBOXNETFLTVNIC)); + if (RT_UNLIKELY(!pVNIC)) + return NULL; + + pVNIC->u32Magic = VBOXNETFLTVNIC_MAGIC; + pVNIC->fCreated = false; + pVNIC->pVNICTemplate = NULL; + pVNIC->pvIf = NULL; + pVNIC->hInterface = NULL; + pVNIC->hLinkId = DATALINK_INVALID_LINKID; + pVNIC->hClient = NULL; + pVNIC->hUnicast = NULL; + pVNIC->hPromisc = NULL; + RT_ZERO(pVNIC->szName); + list_link_init(&pVNIC->hNode); + return pVNIC; +} + + +/** + * Frees an allocated VNIC. + * + * @param pVNIC Pointer to the VNIC. + */ +DECLINLINE(void) vboxNetFltSolarisFreeVNIC(PVBOXNETFLTVNIC pVNIC) +{ + RTMemFree(pVNIC); +} + + +/** + * Destroy a created VNIC if it was created by us, or just + * de-initializes the VNIC freeing up resources handles. + * + * @param pVNIC Pointer to the VNIC. + */ +LOCAL void vboxNetFltSolarisDestroyVNIC(PVBOXNETFLTVNIC pVNIC) +{ + AssertPtrReturnVoid(pVNIC); + AssertMsgReturnVoid(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, ("pVNIC=%p u32Magic=%#x\n", pVNIC, pVNIC->u32Magic)); + if (pVNIC) + { + if (pVNIC->hClient) + { +#if 0 + if (pVNIC->hUnicast) + { + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; + } +#endif + + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } + + mac_rx_clear(pVNIC->hClient); + + mac_client_close(pVNIC->hClient, 0 /* fFlags */); + pVNIC->hClient = NULL; + } + + if (pVNIC->hInterface) + { + mac_close(pVNIC->hInterface); + pVNIC->hInterface = NULL; + } + + if (pVNIC->fCreated) + { + vnic_delete(pVNIC->hLinkId, 0 /* Flags */); + pVNIC->hLinkId = DATALINK_INVALID_LINKID; + pVNIC->fCreated = false; + } + + if (pVNIC->pVNICTemplate) + { + RTMemFree(pVNIC->pVNICTemplate); + pVNIC->pVNICTemplate = NULL; + } + } +} + + +/** + * Create a non-persistent VNIC over the given interface. + * + * @param pThis The VM connection instance. + * @param ppVNIC Where to store the created VNIC. + * + * @returns VBox status code. + */ +LOCAL int vboxNetFltSolarisCreateVNIC(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC *ppVNIC) +{ + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC pThis=%p\n", pThis)); + + AssertReturn(pThis, VERR_INVALID_POINTER); + AssertReturn(ppVNIC, VERR_INVALID_POINTER); + + int rc = VERR_INVALID_STATE; + PVBOXNETFLTVNIC pVNIC = vboxNetFltSolarisAllocVNIC(); + if (RT_UNLIKELY(!pVNIC)) + return VERR_NO_MEMORY; + + /* + * Set a random MAC address for now. It will be changed to the VM interface's + * MAC address later, see vboxNetFltPortOsNotifyMacAddress(). + */ + RTMAC GuestMac; + GuestMac.au8[0] = 0x08; + GuestMac.au8[1] = 0x00; + GuestMac.au8[2] = 0x27; + RTRandBytes(&GuestMac.au8[3], 3); + + AssertCompile(sizeof(RTMAC) <= MAXMACADDRLEN); + + const char *pszLinkName = pThis->szName; + uint16_t uVLANId = VLAN_ID_NONE; + vnic_mac_addr_type_t AddrType = VNIC_MAC_ADDR_TYPE_FIXED; + vnic_ioc_diag_t Diag = VNIC_IOC_DIAG_NONE; + int MacSlot = 0; + int MacLen = sizeof(GuestMac); + uint32_t fFlags = 0; + + if (pThis->u.s.fIsVNICTemplate) + { + pVNIC->pVNICTemplate = RTMemAllocZ(sizeof(VBOXNETFLTVNICTEMPLATE)); + if (RT_UNLIKELY(!pVNIC->pVNICTemplate)) + { + vboxNetFltSolarisFreeVNIC(pVNIC); + return VERR_NO_MEMORY; + } + + /* + * Initialize the VNIC template. + */ + rc = vboxNetFltSolarisInitVNICTemplate(pThis, pVNIC->pVNICTemplate); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC failed to initialize VNIC from VNIC template. rc=%Rrc\n", rc)); + vboxNetFltSolarisFreeVNIC(pVNIC); + return rc; + } + + pszLinkName = pVNIC->pVNICTemplate->szLinkName; + uVLANId = pVNIC->pVNICTemplate->uVLANId; +#if 0 + /* + * Required only if we're creating a VLAN interface & not a VNIC with a VLAN Id. + */ + if (uVLANId != VLAN_ID_NONE) + fFlags |= MAC_VLAN; +#endif + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC pThis=%p VLAN Id=%u\n", pThis, uVLANId)); + } + + /* + * Make sure the dynamic VNIC we're creating doesn't already exists, if so pick a new instance. + * This is to avoid conflicts with users manually creating VNICs whose name starts with VBOXBOW_VNIC_NAME. + */ + do + { + AssertCompile(sizeof(pVNIC->szName) > sizeof(VBOXBOW_VNIC_NAME "18446744073709551615" /* UINT64_MAX */)); + RTStrPrintf(pVNIC->szName, sizeof(pVNIC->szName), "%s%RU64", VBOXBOW_VNIC_NAME, g_VBoxNetFltSolarisVNICId); + mac_handle_t hTmpMacHandle; + rc = mac_open_by_linkname(pVNIC->szName, &hTmpMacHandle); + if (rc) + break; + mac_close(hTmpMacHandle); + ASMAtomicIncU64(&g_VBoxNetFltSolarisVNICId); + } while (1); + + /* + * Create the VNIC under 'pszLinkName', which can be the one from the VNIC template or can + * be a physical interface. + */ + rc = vnic_create(pVNIC->szName, pszLinkName, &AddrType, &MacLen, GuestMac.au8, &MacSlot, 0 /* Mac-Prefix Length */, uVLANId, + fFlags, &pVNIC->hLinkId, &Diag, NULL /* Reserved */); + if (!rc) + { + pVNIC->fCreated = true; + ASMAtomicIncU64(&g_VBoxNetFltSolarisVNICId); + + /* + * Now try opening the created VNIC. + */ + rc = mac_open_by_linkid(pVNIC->hLinkId, &pVNIC->hInterface); + if (!rc) + { + /* + * Initialize the VNIC from the physical interface or the VNIC template. + */ + rc = vboxNetFltSolarisInitVNIC(pThis, pVNIC); + if (RT_SUCCESS(rc)) + { + Log((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC created VNIC '%s' over '%s' with random mac %.6Rhxs\n", + pVNIC->szName, pszLinkName, &GuestMac)); + *ppVNIC = pVNIC; + return VINF_SUCCESS; + } + + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC vboxNetFltSolarisInitVNIC failed. rc=%d\n", rc)); + mac_close(pVNIC->hInterface); + pVNIC->hInterface = NULL; + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC logrel failed to open VNIC '%s' over '%s'. rc=%d\n", pVNIC->szName, + pThis->szName, rc)); + rc = VERR_INTNET_FLT_VNIC_LINK_ID_NOT_FOUND; + } + + vboxNetFltSolarisDestroyVNIC(pVNIC); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltSolarisCreateVNIC failed to create VNIC '%s' over '%s' rc=%d Diag=%d\n", pVNIC->szName, + pszLinkName, rc, Diag)); + rc = VERR_INTNET_FLT_VNIC_CREATE_FAILED; + } + + vboxNetFltSolarisFreeVNIC(pVNIC); + + return rc; +} + + +/** + * Wrapper for getting the datalink ID given the MAC name. + * + * @param pszMacName The MAC name. + * @param pLinkId Where to store the datalink ID. + * + * @returns VBox status code. + */ +DECLINLINE(int) vboxNetFltSolarisGetLinkId(const char *pszMacName, datalink_id_t *pLinkId) +{ + /* + * dls_mgmt_get_linkid() requires to be in a state to answer upcalls. We should always use this + * first before resorting to other means to retrieve the MAC name. + */ + int rc = dls_mgmt_get_linkid(pszMacName, pLinkId); + if (rc) + rc = dls_devnet_macname2linkid(pszMacName, pLinkId); + + if (RT_LIKELY(!rc)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ":vboxNetFltSolarisGetLinkId failed for '%s'. rc=%d\n", pszMacName, rc)); + return RTErrConvertFromErrno(rc); +} + + +/** + * Set the promiscuous mode RX hook. + * + * @param pThis The VM connection instance. + * @param pVNIC Pointer to the VNIC. + * + * @returns VBox status code. + */ +DECLINLINE(int) vboxNetFltSolarisSetPromisc(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + int rc = VINF_SUCCESS; + if (!pVNIC->hPromisc) + { + rc = mac_promisc_add(pVNIC->hClient, MAC_CLIENT_PROMISC_FILTERED, vboxNetFltSolarisRecv, pThis, &pVNIC->hPromisc, + MAC_PROMISC_FLAGS_NO_TX_LOOP | MAC_PROMISC_FLAGS_VLAN_TAG_STRIP | MAC_PROMISC_FLAGS_NO_PHYS); + if (RT_UNLIKELY(rc)) + LogRel((DEVICE_NAME ":vboxNetFltSolarisSetPromisc failed. rc=%d\n", rc)); + rc = RTErrConvertFromErrno(rc); + } + return rc; +} + + +/** + * Clear the promiscuous mode RX hook. + * + * @param pThis The VM connection instance. + * @param pVNIC Pointer to the VNIC. + */ +DECLINLINE(void) vboxNetFltSolarisRemovePromisc(PVBOXNETFLTINS pThis, PVBOXNETFLTVNIC pVNIC) +{ + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } +} + + +/* -=-=-=-=-=- Common Hooks -=-=-=-=-=- */ + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsSetActive pThis=%p fActive=%d\n", pThis, fActive)); + + /* + * Reactivate/quiesce the interface. + */ + PVBOXNETFLTVNIC pVNIC = list_head(&pThis->u.s.hVNICs); + if (fActive) + { + for (; pVNIC != NULL; pVNIC = list_next(&pThis->u.s.hVNICs, pVNIC)) + if (pVNIC->hClient) + { +#if 0 + mac_rx_set(pVNIC->hClient, vboxNetFltSolarisRecv, pThis); +#endif + vboxNetFltSolarisSetPromisc(pThis, pVNIC); + } + } + else + { + for (; pVNIC != NULL; pVNIC = list_next(&pThis->u.s.hVNICs, pVNIC)) + if (pVNIC->hClient) + { +#if 0 + mac_rx_clear(pVNIC->hClient); +#endif + vboxNetFltSolarisRemovePromisc(pThis, pVNIC); + } + } +} + + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsDisconnectIt pThis=%p\n", pThis)); + return VINF_SUCCESS; +} + + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsConnectIt pThis=%p\n", pThis)); + return VINF_SUCCESS; +} + + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + Log((DEVICE_NAME ":vboxNetFltOsDeleteInstance pThis=%p\n", pThis)); + + if (pThis->u.s.hNotify) + mac_notify_remove(pThis->u.s.hNotify, B_TRUE /* Wait */); + + /* + * Destroy all managed VNICs. If a VNIC was passed to us, there + * will be only 1 item in the list, otherwise as many interfaces + * that were somehow not destroyed using DisconnectInterface() will be + * present. + */ + PVBOXNETFLTVNIC pVNIC = NULL; + while ((pVNIC = list_remove_head(&pThis->u.s.hVNICs)) != NULL) + { + vboxNetFltSolarisDestroyVNIC(pVNIC); + vboxNetFltSolarisFreeVNIC(pVNIC); + } + + list_destroy(&pThis->u.s.hVNICs); +} + + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p pvContext=%p\n", pThis, pvContext)); + + /* + * Figure out if the interface is a VNIC or a physical/etherstub/whatever NIC, then + * do the actual VNIC creation if necessary in vboxNetFltPortOsConnectInterface(). + */ + mac_handle_t hInterface; + int rc = mac_open_by_linkname(pThis->szName, &hInterface); + if (RT_LIKELY(!rc)) + { + rc = mac_is_vnic(hInterface); + if (!rc) + { + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p physical interface '%s' detected.\n", pThis, pThis->szName)); + pThis->u.s.fIsVNIC = false; + } + else + { + pThis->u.s.fIsVNIC = true; + if (RTStrNCmp(pThis->szName, VBOXBOW_VNIC_TEMPLATE_NAME, sizeof(VBOXBOW_VNIC_TEMPLATE_NAME) - 1) == 0) + { + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p VNIC template '%s' detected.\n", pThis, pThis->szName)); + pThis->u.s.fIsVNICTemplate = true; + } + } + + if ( pThis->u.s.fIsVNIC + && !pThis->u.s.fIsVNICTemplate) + Log((DEVICE_NAME ":vboxNetFltOsInitInstance pThis=%p VNIC '%s' detected.\n", pThis, pThis->szName)); + + /* + * Report info. (host MAC address, promiscuous, GSO capabilities etc.) to IntNet. + */ + rc = vboxNetFltSolarisReportInfo(pThis, hInterface, pThis->u.s.fIsVNIC); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to report info. rc=%d\n", rc)); + + mac_close(hInterface); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to open link '%s'! rc=%d\n", pThis->szName, rc)); + rc = VERR_INTNET_FLT_IF_FAILED; + } + + return rc; +} + + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + /* + * Init. the solaris specific data. + */ + pThis->u.s.fIsVNIC = false; + pThis->u.s.fIsVNICTemplate = false; + list_create(&pThis->u.s.hVNICs, sizeof(VBOXNETFLTVNIC), offsetof(VBOXNETFLTVNIC, hNode)); + pThis->u.s.hNotify = NULL; + RT_ZERO(pThis->u.s.MacAddr); + return VINF_SUCCESS; +} + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + /* + * @todo Think about this. + */ + return false; +} + + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + /* + * Validate parameters. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + AssertPtrReturn(pVNIC, VERR_INVALID_POINTER); + AssertMsgReturn(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid magic=%#x (expected %#x)\n", pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC), + VERR_INVALID_MAGIC); + + /* + * Xmit the packet down the appropriate VNIC interface. + */ + int rc = VINF_SUCCESS; + mblk_t *pMsg = vboxNetFltSolarisMBlkFromSG(pThis, pSG, fDst); + if (RT_LIKELY(pMsg)) + { + Log((DEVICE_NAME ":vboxNetFltPortOsXmit pThis=%p cbData=%d\n", pThis, MBLKL(pMsg))); + + mac_tx_cookie_t pXmitCookie = mac_tx(pVNIC->hClient, pMsg, 0 /* Hint */, MAC_DROP_ON_NO_DESC, NULL /* return message */); + if (RT_LIKELY(!pXmitCookie)) + return VINF_SUCCESS; + + pMsg = NULL; + rc = VERR_NET_IO_ERROR; + LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit Xmit failed pVNIC=%p.\n", pVNIC)); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsXmit no memory for allocating Xmit packet.\n")); + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + Log((DEVICE_NAME ":vboxNetFltPortOSNotifyMacAddress pszIf=%s pszVNIC=%s MAC=%.6Rhxs\n", pThis->szName, + ((PVBOXNETFLTVNIC)pvIfData)->szName, pMac)); + + /* + * Validate parameters. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + AssertPtrReturnVoid(pVNIC); + AssertMsgReturnVoid(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid pVNIC=%p magic=%#x (expected %#x)\n", pvIfData, pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC)); + AssertMsgReturnVoid(pVNIC->hLinkId != DATALINK_INVALID_LINKID, + ("Invalid hLinkId pVNIC=%p magic=%#x\n", pVNIC, pVNIC->u32Magic)); + + /* + * Set the MAC address of the VNIC to the one used by the VM interface. + */ + uchar_t au8GuestMac[MAXMACADDRLEN]; + bcopy(pMac->au8, au8GuestMac, sizeof(RTMAC)); + + vnic_mac_addr_type_t AddrType = VNIC_MAC_ADDR_TYPE_FIXED; + vnic_ioc_diag_t Diag = VNIC_IOC_DIAG_NONE; + int MacSlot = 0; + int MacLen = sizeof(RTMAC); + + int rc = vnic_modify_addr(pVNIC->hLinkId, &AddrType, &MacLen, au8GuestMac, &MacSlot, 0 /* Mac-Prefix Length */, &Diag); + if (RT_LIKELY(!rc)) + { + /* + * Remove existing unicast address, promisc. and the RX hook. + */ +#if 0 + if (pVNIC->hUnicast) + { + mac_rx_clear(pVNIC->hClient); + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; + } +#endif + + if (pVNIC->hPromisc) + { + mac_promisc_remove(pVNIC->hPromisc); + pVNIC->hPromisc = NULL; + } + + mac_diag_t MacDiag = MAC_DIAG_NONE; + /* uint16_t uVLANId = pVNIC->pVNICTemplate ? pVNIC->pVNICTemplate->uVLANId : 0; */ +#if 0 + rc = mac_unicast_add(pVNIC->hClient, NULL, MAC_UNICAST_PRIMARY, &pVNIC->hUnicast, 0 /* VLAN Id */, &MacDiag); +#endif + if (RT_LIKELY(!rc)) + { + rc = vboxNetFltSolarisSetPromisc(pThis, pVNIC); +#if 0 + if (RT_SUCCESS(rc)) + { + /* + * Set the RX receive function. + * This shouldn't be necessary as vboxNetFltPortOsSetActive() will be invoked after this, but in the future, + * if the guest NIC changes MAC address this may not be followed by a vboxNetFltPortOsSetActive() call, + * so set it here anyway. + */ + mac_rx_set(pVNIC->hClient, vboxNetFltSolarisRecv, pThis); + Log((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress successfully added unicast address %.6Rhxs\n", pMac)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed to set promiscuous mode. rc=%d\n", rc)); + mac_unicast_remove(pVNIC->hClient, pVNIC->hUnicast); + pVNIC->hUnicast = NULL; +#endif + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed to add primary unicast address. rc=%d Diag=%d\n", rc, + MacDiag)); + } + } + else + { + /* + * They really ought to use EEXIST, but I'm afraid this error comes from the VNIC device driver directly. + * Sequence: vnic_modify_addr()->mac_unicast_primary_set()->mac_update_macaddr() which uses a function pointer + * to the MAC driver (calls mac_vnic_unicast_set() in our case). Documented here if the error code should change we know + * where to look. + */ + if (rc == ENOTSUP) + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress: failed! a VNIC with mac %.6Rhxs probably already exists.", + pMac, rc)); + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress: This NIC cannot establish connection. szName=%s szVNIC=%s\n", + pThis->szName, pVNIC->szName)); + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsNotifyMacAddress failed! mac %.6Rhxs rc=%d Diag=%d\n", pMac, rc, Diag)); + } +} + + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsConnectInterface pThis=%p pvIf=%p\n", pThis, pvIf)); + + int rc = VINF_SUCCESS; + + /* + * If the underlying interface is a physical interface or a VNIC template, we need to create + * a VNIC per guest NIC. + */ + if ( !pThis->u.s.fIsVNIC + || pThis->u.s.fIsVNICTemplate) + { + PVBOXNETFLTVNIC pVNIC = NULL; + rc = vboxNetFltSolarisCreateVNIC(pThis, &pVNIC); + if (RT_SUCCESS(rc)) + { + /* + * VM Interface<->VNIC association so that we can Xmit/Recv on the right ones. + */ + pVNIC->pvIf = pvIf; + *ppvIfData = pVNIC; + + /* + * Add the created VNIC to the list of VNICs we manage. + */ + list_insert_tail(&pThis->u.s.hVNICs, pVNIC); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to create VNIC rc=%d\n", rc)); + } + else + { + /* + * This is a VNIC passed to us, use it directly. + */ + PVBOXNETFLTVNIC pVNIC = vboxNetFltSolarisAllocVNIC(); + if (RT_LIKELY(pVNIC)) + { + pVNIC->fCreated = false; + + rc = mac_open_by_linkname(pThis->szName, &pVNIC->hInterface); + if (!rc) + { + /* + * Obtain the data link ID for this VNIC, it's needed for modifying the MAC address among other things. + */ + rc = vboxNetFltSolarisGetLinkId(pThis->szName, &pVNIC->hLinkId); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the VNIC and add it to the list of managed VNICs. + */ + RTStrPrintf(pVNIC->szName, sizeof(pVNIC->szName), "%s", pThis->szName); + rc = vboxNetFltSolarisInitVNIC(pThis, pVNIC); + if (!rc) + { + pVNIC->pvIf = pvIf; + *ppvIfData = pVNIC; + list_insert_head(&pThis->u.s.hVNICs, pVNIC); + return VINF_SUCCESS; + } + else + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to initialize VNIC. rc=%d\n", rc)); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to get link id for '%s'. rc=%d\n", + pThis->szName, rc)); + } + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltPortOsConnectInterface failed to open VNIC '%s'. rc=%d\n", pThis->szName, rc)); + rc = VERR_OPEN_FAILED; + } + + vboxNetFltSolarisFreeVNIC(pVNIC); + } + else + { + LogRel((DEVICE_NAME ":vboxNetFltOsInitInstance failed to allocate VNIC private data.\n")); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + Log((DEVICE_NAME ":vboxNetFltPortOsDisconnectInterface pThis=%p\n", pThis)); + + /* + * It is possible we get called when vboxNetFltPortOsConnectInterface() didn't succeed + * in which case pvIfData will be NULL. See intnetR0NetworkCreateIf() pfnConnectInterface call + * through reference counting in SUPR0ObjRelease() for the "pIf" object. + */ + PVBOXNETFLTVNIC pVNIC = pvIfData; + if (RT_LIKELY(pVNIC)) + { + AssertMsgReturn(pVNIC->u32Magic == VBOXNETFLTVNIC_MAGIC, + ("Invalid magic=%#x (expected %#x)\n", pVNIC->u32Magic, VBOXNETFLTVNIC_MAGIC), VERR_INVALID_POINTER); + + /* + * If the underlying interface is a physical interface or a VNIC template, we need to delete the created VNIC. + */ + if ( !pThis->u.s.fIsVNIC + || pThis->u.s.fIsVNICTemplate) + { + /* + * Remove the VNIC from the list, destroy and free it. + */ + list_remove(&pThis->u.s.hVNICs, pVNIC); + Log((DEVICE_NAME ":vboxNetFltPortOsDisconnectInterface destroying pVNIC=%p\n", pVNIC)); + vboxNetFltSolarisDestroyVNIC(pVNIC); + vboxNetFltSolarisFreeVNIC(pVNIC); + } + } + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf new file mode 100644 index 00000000..8b4f716d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxbow.conf @@ -0,0 +1,43 @@ +# $Id: vboxbow.conf $ +## @file +# Solaris Host VBoxBow (Crossbow) Configuration +# + +# +# 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 . +# +# 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). +# +name="vboxbow" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf new file mode 100644 index 00000000..dce8b795 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/solaris/vboxflt.conf @@ -0,0 +1,56 @@ +# $Id: vboxflt.conf $ +## @file +# Solaris Host VBoxFlt Configuration +# + +# +# 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 . +# +# 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). +# +name="vboxflt" parent="pseudo" instance=0; + +# If the interface being used does not have IPv6 plumbed and you want +# vboxflt to automatically attach to it by checking for Ipv6 stream every +# "ipv6-pollinterval" seconds. +# +# By default this is disabled meaning if the interface does not have Ipv6 +# plumbed when the virtual machine starts, guest<->host Ipv6 will not work +# though guest<->remote Ipv6 would work. +# +# Enable if you have a dynamically plumbing/unplumbing Ipv6 interface for +# which you want vboxflt to adjust accordingly, otherwise keep this disabled. +# Enabling this option will have some minor performance penalty. +#ipv6-pollinterval=3; + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp new file mode 100644 index 00000000..a686196f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/cfg/VBoxNetCfg.cpp @@ -0,0 +1,3770 @@ +/* $Id: VBoxNetCfg.cpp $ */ +/** @file + * VBoxNetCfg.cpp - Network Configuration API. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define _WIN32_DCOM + +#include "VBox/VBoxNetCfg-win.h" +#include "VBox/VBoxDrvCfg-win.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifndef Assert /** @todo r=bird: where would this be defined? */ +//# ifdef DEBUG +//# define Assert(_expr) assert(_expr) +//# else +//# define Assert(_expr) do{ }while (0) +//# endif +# define Assert _ASSERT +# define AssertMsg(expr, msg) do{}while (0) +#endif + +#define NonStandardLog DoLogging +#define NonStandardLogFlow(x) DoLogging x + +#define SetErrBreak(strAndArgs) \ + if (1) { \ + hrc = E_FAIL; \ + NonStandardLog strAndArgs; \ + bstrError.printfNoThrow strAndArgs; \ + break; \ + } else do {} while (0) + + +#define VBOXNETCFGWIN_NETADP_ID_SZ "sun_VBoxNetAdp" +#define VBOXNETCFGWIN_NETADP_ID_WSZ RT_CONCAT(L,VBOXNETCFGWIN_NETADP_ID_SZ) +#define DRIVERHWID VBOXNETCFGWIN_NETADP_ID_WSZ + +/* We assume the following name matches the device description in vboxnetadp6.inf */ +#define HOSTONLY_ADAPTER_NAME_SZ "VirtualBox Host-Only Ethernet Adapter" +#define HOSTONLY_ADAPTER_NAME_WSZ RT_CONCAT(L,HOSTONLY_ADAPTER_NAME_SZ) + +#define VBOX_CONNECTION_NAME_SZ "VirtualBox Host-Only Network" +#define VBOX_CONNECTION_NAME_WSZ RT_CONCAT(L,VBOX_CONNECTION_NAME_SZ) + +#define VBOXNETCFGWIN_NETLWF_ID L"oracle_VBoxNetLwf" + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static PFNVBOXNETCFGLOGGER volatile g_pfnLogger = NULL; + +/* + * Wrappers for HelpAPI functions + */ +typedef void FNINITIALIZEIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNINITIALIZEIPINTERFACEENTRY *PFNINITIALIZEIPINTERFACEENTRY; + +typedef NETIOAPI_API FNGETIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNGETIPINTERFACEENTRY *PFNGETIPINTERFACEENTRY; + +typedef NETIOAPI_API FNSETIPINTERFACEENTRY( _Inout_ PMIB_IPINTERFACE_ROW row); +typedef FNSETIPINTERFACEENTRY *PFNSETIPINTERFACEENTRY; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static PFNINITIALIZEIPINTERFACEENTRY g_pfnInitializeIpInterfaceEntry = NULL; +static PFNGETIPINTERFACEENTRY g_pfnGetIpInterfaceEntry = NULL; +static PFNSETIPINTERFACEENTRY g_pfnSetIpInterfaceEntry = NULL; + +static void DoLogging(const char *pszString, ...); + +/* + * Forward declaration for using vboxNetCfgWinSetupMetric() + */ +static HRESULT vboxNetCfgWinSetupMetric(IN NET_LUID *pLuid); +static HRESULT vboxNetCfgWinGetInterfaceLUID(IN HKEY hKey, OUT NET_LUID *pLUID); + + + +static HRESULT vboxNetCfgWinINetCfgLock(IN INetCfg *pNetCfg, + IN LPCWSTR pszwClientDescription, + IN DWORD cmsTimeout, + OUT LPWSTR *ppszwClientDescription) +{ + INetCfgLock *pLock; + HRESULT hr = pNetCfg->QueryInterface(IID_INetCfgLock, (PVOID *)&pLock); + if (FAILED(hr)) + { + NonStandardLogFlow(("QueryInterface failed: %Rhrc\n", hr)); + return hr; + } + + hr = pLock->AcquireWriteLock(cmsTimeout, pszwClientDescription, ppszwClientDescription); + if (hr == S_FALSE) + NonStandardLogFlow(("Write lock busy\n")); + else if (FAILED(hr)) + NonStandardLogFlow(("AcquireWriteLock failed: %Rhrc\n", hr)); + + pLock->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinINetCfgUnlock(IN INetCfg *pNetCfg) +{ + INetCfgLock *pLock; + HRESULT hr = pNetCfg->QueryInterface(IID_INetCfgLock, (PVOID *)&pLock); + if (FAILED(hr)) + { + NonStandardLogFlow(("QueryInterface failed: %Rhrc\n", hr)); + return hr; + } + + hr = pLock->ReleaseWriteLock(); + if (FAILED(hr)) + NonStandardLogFlow(("ReleaseWriteLock failed: %Rhrc\n", hr)); + + pLock->Release(); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinQueryINetCfg(OUT INetCfg **ppNetCfg, + IN BOOL fGetWriteLock, + IN LPCWSTR pszwClientDescription, + IN DWORD cmsTimeout, + OUT LPWSTR *ppszwClientDescription) +{ + INetCfg *pNetCfg = NULL; + HRESULT hr = CoCreateInstance(CLSID_CNetCfg, NULL, CLSCTX_INPROC_SERVER, IID_INetCfg, (PVOID *)&pNetCfg); + if (FAILED(hr)) + { + NonStandardLogFlow(("CoCreateInstance failed: %Rhrc\n", hr)); + return hr; + } + + if (fGetWriteLock) + { + hr = vboxNetCfgWinINetCfgLock(pNetCfg, pszwClientDescription, cmsTimeout, ppszwClientDescription); + if (hr == S_FALSE) + { + NonStandardLogFlow(("Write lock is busy\n", hr)); + hr = NETCFG_E_NO_WRITE_LOCK; + } + } + + if (SUCCEEDED(hr)) + { + hr = pNetCfg->Initialize(NULL); + if (SUCCEEDED(hr)) + { + *ppNetCfg = pNetCfg; + return S_OK; + } + NonStandardLogFlow(("Initialize failed: %Rhrc\n", hr)); + } + + pNetCfg->Release(); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinReleaseINetCfg(IN INetCfg *pNetCfg, IN BOOL fHasWriteLock) +{ + if (!pNetCfg) /* If network config has been released already, just bail out. */ + { + NonStandardLogFlow(("Warning: No network config given but write lock is set to TRUE\n")); + return S_OK; + } + + HRESULT hr = pNetCfg->Uninitialize(); + if (FAILED(hr)) + { + NonStandardLogFlow(("Uninitialize failed: %Rhrc\n", hr)); + /* Try to release the write lock below. */ + } + + if (fHasWriteLock) + { + HRESULT hr2 = vboxNetCfgWinINetCfgUnlock(pNetCfg); + if (FAILED(hr2)) + NonStandardLogFlow(("vboxNetCfgWinINetCfgUnlock failed: %Rhrc\n", hr2)); + if (SUCCEEDED(hr)) + hr = hr2; + } + + pNetCfg->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinGetComponentByGuidEnum(IEnumNetCfgComponent *pEnumNcc, + IN const GUID *pGuid, + OUT INetCfgComponent **ppNcc) +{ + HRESULT hr = pEnumNcc->Reset(); + if (FAILED(hr)) + { + NonStandardLogFlow(("Reset failed: %Rhrc\n", hr)); + return hr; + } + + INetCfgComponent *pNcc = NULL; + while ((hr = pEnumNcc->Next(1, &pNcc, NULL)) == S_OK) + { + ULONG uComponentStatus = 0; + hr = pNcc->GetDeviceStatus(&uComponentStatus); + if (SUCCEEDED(hr)) + { + if (uComponentStatus == 0) + { + GUID NccGuid; + hr = pNcc->GetInstanceGuid(&NccGuid); + + if (SUCCEEDED(hr)) + { + if (NccGuid == *pGuid) + { + /* found the needed device */ + *ppNcc = pNcc; + break; + } + } + else + NonStandardLogFlow(("GetInstanceGuid failed: %Rhrc\n", hr)); + } + } + + pNcc->Release(); + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGetComponentByGuid(IN INetCfg *pNc, + IN const GUID *pguidClass, + IN const GUID * pComponentGuid, + OUT INetCfgComponent **ppncc) +{ + IEnumNetCfgComponent *pEnumNcc = NULL; + HRESULT hr = pNc->EnumComponents(pguidClass, &pEnumNcc); + if (SUCCEEDED(hr)) + { + hr = vboxNetCfgWinGetComponentByGuidEnum(pEnumNcc, pComponentGuid, ppncc); + if (hr == S_FALSE) + NonStandardLogFlow(("Component not found\n")); + else if (FAILED(hr)) + NonStandardLogFlow(("vboxNetCfgWinGetComponentByGuidEnum failed: %Rhrc\n", hr)); + pEnumNcc->Release(); + } + else + NonStandardLogFlow(("EnumComponents failed: %Rhrc\n", hr)); + return hr; +} + +static HRESULT vboxNetCfgWinQueryInstaller(IN INetCfg *pNetCfg, IN const GUID *pguidClass, INetCfgClassSetup **ppSetup) +{ + HRESULT hr = pNetCfg->QueryNetCfgClass(pguidClass, IID_INetCfgClassSetup, (void **)ppSetup); + if (FAILED(hr)) + NonStandardLogFlow(("QueryNetCfgClass failed: %Rhrc\n", hr)); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinInstallComponent(IN INetCfg *pNetCfg, IN LPCWSTR pszwComponentId, + IN const GUID *pguidClass, OUT INetCfgComponent **ppComponent) +{ + INetCfgClassSetup *pSetup; + HRESULT hr = vboxNetCfgWinQueryInstaller(pNetCfg, pguidClass, &pSetup); + if (FAILED(hr)) + { + NonStandardLogFlow(("vboxNetCfgWinQueryInstaller failed: %Rhrc\n", hr)); + return hr; + } + + OBO_TOKEN Token; + RT_ZERO(Token); + Token.Type = OBO_USER; + + INetCfgComponent *pTempComponent = NULL; + hr = pSetup->Install(pszwComponentId, &Token, + 0, /* IN DWORD dwSetupFlags */ + 0, /* IN DWORD dwUpgradeFromBuildNo */ + NULL, /* IN LPCWSTR pszwAnswerFile */ + NULL, /* IN LPCWSTR pszwAnswerSections */ + &pTempComponent); + if (SUCCEEDED(hr)) + { + if (pTempComponent != NULL) + { + /* + * Set default metric value of interface to fix multicast issue + * See @bugref{6379} for details. + */ + HKEY hKey = (HKEY)INVALID_HANDLE_VALUE; + HRESULT hrc2 = pTempComponent->OpenParamKey(&hKey); + + /* Set default metric value for host-only interface only */ + if ( SUCCEEDED(hrc2) + && hKey != (HKEY)INVALID_HANDLE_VALUE + /* Original was weird: && wcsnicmp(pszwComponentId, VBOXNETCFGWIN_NETADP_ID_WSZ, 256) == 0) */ + && RTUtf16ICmpAscii(pszwComponentId, VBOXNETCFGWIN_NETADP_ID_SZ) == 0) + { + NET_LUID luid; + hrc2 = vboxNetCfgWinGetInterfaceLUID(hKey, &luid); + + /* Close the key as soon as possible. See @bugref{7973}. */ + RegCloseKey(hKey); + hKey = (HKEY)INVALID_HANDLE_VALUE; + + if (FAILED(hrc2)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent Warning! vboxNetCfgWinGetInterfaceLUID failed, default metric for new interface will not be set: %Rhrc\n", hrc2)); + } + else + { + hrc2 = vboxNetCfgWinSetupMetric(&luid); + if (FAILED(hrc2)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent Warning! vboxNetCfgWinSetupMetric failed, default metric for new interface will not be set: %Rhrc\n", hrc2)); + } + } + } + if (hKey != (HKEY)INVALID_HANDLE_VALUE) + RegCloseKey(hKey); + if (ppComponent != NULL) + *ppComponent = pTempComponent; + else + pTempComponent->Release(); + } + + /* ignore the apply failure */ + HRESULT hrc3 = pNetCfg->Apply(); + Assert(hrc3 == S_OK); + if (hrc3 != S_OK) + NonStandardLogFlow(("Apply failed: %Rhrc\n", hrc3)); + } + else + NonStandardLogFlow(("Install failed: %Rhrc\n", hr)); + + pSetup->Release(); + return hr; +} + +static HRESULT vboxNetCfgWinInstallInfAndComponent(IN INetCfg *pNetCfg, IN LPCWSTR pszwComponentId, IN const GUID *pguidClass, + IN LPCWSTR const *apwszInfPaths, IN UINT cInfPaths, + OUT INetCfgComponent **ppComponent) +{ + NonStandardLogFlow(("Installing %u INF files ...\n", cInfPaths)); + + HRESULT hr = S_OK; + UINT cFilesProcessed = 0; + for (; cFilesProcessed < cInfPaths; cFilesProcessed++) + { + NonStandardLogFlow(("Installing INF file \"%ls\" ...\n", apwszInfPaths[cFilesProcessed])); + hr = VBoxDrvCfgInfInstall(apwszInfPaths[cFilesProcessed]); + if (FAILED(hr)) + { + NonStandardLogFlow(("VBoxNetCfgWinInfInstall failed: %Rhrc\n", hr)); + break; + } + } + + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinInstallComponent(pNetCfg, pszwComponentId, pguidClass, ppComponent); + if (FAILED(hr)) + NonStandardLogFlow(("VBoxNetCfgWinInstallComponent failed: %Rhrc\n", hr)); + } + + if (FAILED(hr)) + { + NonStandardLogFlow(("Installation failed, rolling back installation set ...\n")); + + do + { + HRESULT hr2 = VBoxDrvCfgInfUninstall(apwszInfPaths[cFilesProcessed], 0); + if (FAILED(hr2)) + NonStandardLogFlow(("VBoxDrvCfgInfUninstall failed: %Rhrc\n", hr2)); + /* Keep going. */ + if (!cFilesProcessed) + break; + } while (cFilesProcessed--); + + NonStandardLogFlow(("Rollback complete\n")); + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinUninstallComponent(IN INetCfg *pNetCfg, IN INetCfgComponent *pComponent) +{ + GUID GuidClass; + HRESULT hr = pComponent->GetClassGuid(&GuidClass); + if (FAILED(hr)) + { + NonStandardLogFlow(("GetClassGuid failed: %Rhrc\n", hr)); + return hr; + } + + INetCfgClassSetup *pSetup = NULL; + hr = vboxNetCfgWinQueryInstaller(pNetCfg, &GuidClass, &pSetup); + if (FAILED(hr)) + { + NonStandardLogFlow(("vboxNetCfgWinQueryInstaller failed: %Rhrc\n", hr)); + return hr; + } + + OBO_TOKEN Token; + RT_ZERO(Token); + Token.Type = OBO_USER; + + hr = pSetup->DeInstall(pComponent, &Token, NULL /* OUT LPWSTR *pmszwRefs */); + if (SUCCEEDED(hr)) + { + hr = pNetCfg->Apply(); + if (FAILED(hr)) + NonStandardLogFlow(("Apply failed: %Rhrc\n", hr)); + } + else + NonStandardLogFlow(("DeInstall failed: %Rhrc\n", hr)); + + if (pSetup) + pSetup->Release(); + return hr; +} + +typedef BOOL (*PFN_VBOXNETCFGWIN_NETCFGENUM_CALLBACK_T)(IN INetCfg *pNetCfg, IN INetCfgComponent *pNetCfgComponent, + PVOID pvContext); + +static HRESULT vboxNetCfgWinEnumNetCfgComponents(IN INetCfg *pNetCfg, + IN const GUID *pguidClass, + PFN_VBOXNETCFGWIN_NETCFGENUM_CALLBACK_T pfnCallback, + PVOID pContext) +{ + IEnumNetCfgComponent *pEnumComponent = NULL; + HRESULT hr = pNetCfg->EnumComponents(pguidClass, &pEnumComponent); + if (SUCCEEDED(hr)) + { + INetCfgComponent *pNetCfgComponent; + hr = pEnumComponent->Reset(); + for (;;) + { + hr = pEnumComponent->Next(1, &pNetCfgComponent, NULL); + if (hr == S_OK) + { +// ULONG uComponentStatus; +// hr = pNcc->GetDeviceStatus(&uComponentStatus); +// if (SUCCEEDED(hr)) + BOOL fResult = FALSE; + if (pNetCfgComponent) + { + fResult = pfnCallback(pNetCfg, pNetCfgComponent, pContext); + pNetCfgComponent->Release(); + } + + if (!fResult) + break; + } + else + { + if (hr == S_FALSE) + hr = S_OK; /* no more components */ + else + NonStandardLogFlow(("Next failed: %Rhrc\n", hr)); + break; + } + } + pEnumComponent->Release(); + } + return hr; +} + +/** PFNVBOXNETCFGWINNETENUMCALLBACK */ +static BOOL vboxNetCfgWinRemoveAllNetDevicesOfIdCallback(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pvContext) +{ + RT_NOREF1(pvContext); + + SP_REMOVEDEVICE_PARAMS rmdParams; + RT_ZERO(rmdParams); + rmdParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + rmdParams.ClassInstallHeader.InstallFunction = DIF_REMOVE; + rmdParams.Scope = DI_REMOVEDEVICE_GLOBAL; + + if (SetupDiSetClassInstallParams(hDevInfo,pDev, + &rmdParams.ClassInstallHeader, sizeof(rmdParams))) + { + if (SetupDiSetSelectedDevice(hDevInfo, pDev)) + { +#ifndef VBOXNETCFG_DELAYEDRENAME + /* Figure out NetCfgInstanceId. */ + HKEY hKey = SetupDiOpenDevRegKey(hDevInfo, + pDev, + DICS_FLAG_GLOBAL, + 0, + DIREG_DRV, + KEY_READ); + if (hKey == INVALID_HANDLE_VALUE) + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiOpenDevRegKey failed with error %u\n", + GetLastError())); + else + { + WCHAR wszCfgGuidString[50] = { L'' }; + DWORD cbSize = sizeof(wszCfgGuidString) - sizeof(WCHAR); /* make sure we get a terminated string back */ + DWORD dwValueType = 0; + LSTATUS lrc = RegQueryValueExW(hKey, L"NetCfgInstanceId", NULL, &dwValueType, (LPBYTE)wszCfgGuidString, &cbSize); + if (lrc == ERROR_SUCCESS) + { + /** @todo r=bird: original didn't check the type here, just assumed it was a + * valid zero terminated string. (zero term handled by -sizeof(WHCAR) above now). */ + if (dwValueType == REG_SZ || dwValueType == REG_EXPAND_SZ || dwValueType == REG_EXPAND_SZ) + { + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Processing device ID \"%ls\"\n", + wszCfgGuidString)); + + /* Figure out device name. */ + WCHAR wszDevName[256 + 1] = {0}; + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, pDev, SPDRP_FRIENDLYNAME, NULL, (PBYTE)wszDevName, + sizeof(wszDevName) - sizeof(WCHAR) /* yes, in bytes */, NULL)) + { + /* + * Rename the connection before removing the device. This will + * hopefully prevent an error when we will be attempting + * to rename a newly created connection (see @bugref{6740}). + */ + WCHAR wszNewName[RT_ELEMENTS(wszDevName) + 128 /* ensure sufficient buffer */]; + HRESULT hr = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszNewName, + RT_ELEMENTS(wszNewName) - 10 /*removed++*/, + NULL); + RTUtf16CatAscii(wszNewName, sizeof(wszNewName), " removed"); + if (SUCCEEDED(hr)) + hr = VBoxNetCfgWinRenameConnection(wszCfgGuidString, wszNewName); + //NonStandardLogFlow(("VBoxNetCfgWinRenameConnection(%S,%S) => 0x%x\n", wszCfgGuidString, TempName, hr_tmp)); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Failed to get friendly name for device \"%ls\"\n", + wszCfgGuidString)); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Friendly name for \"%S\" isn't a string: %d\n", + wszCfgGuidString, dwValueType + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: Querying instance ID failed with %u (%#x)\n", + lrc, lrc)); + + RegCloseKey(hKey); + } +#endif /* VBOXNETCFG_DELAYEDRENAME */ + + if (SetupDiCallClassInstaller(DIF_REMOVE, hDevInfo, pDev)) + { + SP_DEVINSTALL_PARAMS_W DevParams = { sizeof(DevParams) }; + if (SetupDiGetDeviceInstallParams(hDevInfo, pDev, &DevParams)) + { + if ( (DevParams.Flags & DI_NEEDRESTART) + || (DevParams.Flags & DI_NEEDREBOOT)) + NonStandardLog(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: A reboot is required\n")); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiGetDeviceInstallParams failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiCallClassInstaller failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiSetSelectedDevice failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinRemoveAllNetDevicesOfIdCallback: SetupDiSetClassInstallParams failed with %u\n", + GetLastError())); + + /* Continue enumeration. */ + return TRUE; +} + +typedef struct VBOXNECTFGWINPROPCHANGE +{ + VBOXNECTFGWINPROPCHANGE_TYPE_T enmPcType; + HRESULT hr; +} VBOXNECTFGWINPROPCHANGE, *PVBOXNECTFGWINPROPCHANGE; + +static BOOL vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pContext) +{ + PVBOXNECTFGWINPROPCHANGE pPc = (PVBOXNECTFGWINPROPCHANGE)pContext; + + SP_PROPCHANGE_PARAMS PcParams; + RT_ZERO(PcParams); + PcParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER); + PcParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; + PcParams.Scope = DICS_FLAG_GLOBAL; + + switch (pPc->enmPcType) + { + case VBOXNECTFGWINPROPCHANGE_TYPE_DISABLE: + PcParams.StateChange = DICS_DISABLE; + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Change type (DICS_DISABLE): %d\n", pPc->enmPcType)); + break; + case VBOXNECTFGWINPROPCHANGE_TYPE_ENABLE: + PcParams.StateChange = DICS_ENABLE; + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Change type (DICS_ENABLE): %d\n", pPc->enmPcType)); + break; + default: + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: Unexpected prop change type: %d\n", pPc->enmPcType)); + pPc->hr = E_INVALIDARG; + return FALSE; + } + + if (SetupDiSetClassInstallParamsW(hDevInfo, pDev, &PcParams.ClassInstallHeader, sizeof(PcParams))) + { + if (SetupDiSetSelectedDevice(hDevInfo, pDev)) + { + if (SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, pDev)) + { + SP_DEVINSTALL_PARAMS_W DevParams = { sizeof(DevParams) }; + if (SetupDiGetDeviceInstallParamsW(hDevInfo, pDev, &DevParams)) + { + if ( (DevParams.Flags & DI_NEEDRESTART) + || (DevParams.Flags & DI_NEEDREBOOT)) + NonStandardLog(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: A reboot is required\n")); + } + else + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: SetupDiGetDeviceInstallParams failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback: SetupDiCallClassInstaller failed with %u\n", + GetLastError())); + } + else + NonStandardLogFlow(("SetupDiSetSelectedDevice failed with %u\n", GetLastError())); + } + else + NonStandardLogFlow(("SetupDiSetClassInstallParams failed with %u\n", GetLastError())); + + /* Continue enumeration. */ + return TRUE; +} + +typedef BOOL (*PFNVBOXNETCFGWINNETENUMCALLBACK)(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDev, PVOID pContext); + +static HRESULT vboxNetCfgWinEnumNetDevices(LPCWSTR pwszPnPId, PFNVBOXNETCFGWINNETENUMCALLBACK pfnCallback, PVOID pvContext) +{ + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Searching for: %ls\n", pwszPnPId)); + + HRESULT hr; + HDEVINFO hDevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, + NULL, /* IN PCTSTR Enumerator, OPTIONAL */ + NULL, /* IN HWND hwndParent, OPTIONAL */ + DIGCF_PRESENT, /* IN DWORD Flags,*/ + NULL, /* IN HDEVINFO DeviceInfoSet, OPTIONAL */ + NULL, /* IN PCTSTR MachineName, OPTIONAL */ + NULL /* IN PVOID Reserved */); + if (hDevInfo != INVALID_HANDLE_VALUE) + { + size_t const cwcPnPId = RTUtf16Len(pwszPnPId); + DWORD winEr = NO_ERROR; + DWORD dwDevId = 0; + DWORD cbBuffer = 0; + PBYTE pbBuffer = NULL; + for (;;) + { + SP_DEVINFO_DATA Dev; + memset(&Dev, 0, sizeof(SP_DEVINFO_DATA)); + Dev.cbSize = sizeof(SP_DEVINFO_DATA); + + if (!SetupDiEnumDeviceInfo(hDevInfo, dwDevId, &Dev)) + { + winEr = GetLastError(); + if (winEr == ERROR_NO_MORE_ITEMS) + winEr = NO_ERROR; + break; + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Enumerating device %u ... \n", dwDevId)); + dwDevId++; + + DWORD cbRequired = 0; + SetLastError(0); + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &Dev, + SPDRP_HARDWAREID, /* IN DWORD Property */ + NULL, /* OUT PDWORD PropertyRegDataType OPTIONAL */ + pbBuffer, /* OUT PBYTE PropertyBuffer */ + cbBuffer, /* IN DWORD PropertyBufferSize */ + &cbRequired /* OUT PDWORD RequiredSize OPTIONAL */)) + { + winEr = GetLastError(); + if (winEr != ERROR_INSUFFICIENT_BUFFER) + { + if (winEr == ERROR_INVALID_DATA) + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (1) failed with ERROR_INVALID_DATA - ignoring, skipping to next device\n")); + continue; + } + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (1) failed with %u\n", winEr)); + break; + } + winEr = NO_ERROR; + + cbBuffer = RT_ALIGN_32(cbRequired, 64); + void *pvNew = RTMemRealloc(pbBuffer, cbBuffer); + if (pvNew) + pbBuffer = (PBYTE)pvNew; + else + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Out of memory allocating %u bytes\n", cbBuffer)); + winEr = ERROR_OUTOFMEMORY; + break; + } + + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &Dev, + SPDRP_HARDWAREID, /* IN DWORD Property */ + NULL, /* OUT PDWORD PropertyRegDataType, OPTIONAL */ + pbBuffer, /* OUT PBYTE PropertyBuffer */ + cbBuffer, /* IN DWORD PropertyBufferSize */ + &cbRequired /* OUT PDWORD RequiredSize OPTIONAL */)) + { + winEr = GetLastError(); + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetDeviceRegistryPropertyW (2) failed with %u\n", + winEr)); + break; + } + } + + PWSTR pwszCurId = (PWSTR)pbBuffer; + size_t cwcCurId = RTUtf16Len(pwszCurId); + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Device %u: %ls\n", dwDevId, pwszCurId)); + + if (cwcCurId >= cwcPnPId) + { + NonStandardLogFlow(("!RTUtf16NICmp(pwszCurId = (%ls), pwszPnPId = (%ls), cwcPnPId = (%d))\n", pwszCurId, pwszPnPId, cwcPnPId)); + + pwszCurId += cwcCurId - cwcPnPId; + if (!RTUtf16NICmp(pwszCurId, pwszPnPId, cwcPnPId)) + { + if (!pfnCallback(hDevInfo, &Dev, pvContext)) + break; + } + } + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Found %u devices total\n", dwDevId)); + + if (pbBuffer) + RTMemFree(pbBuffer); + + hr = HRESULT_FROM_WIN32(winEr); + + SetupDiDestroyDeviceInfoList(hDevInfo); + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: SetupDiGetClassDevsExW failed with %u\n", winEr)); + hr = HRESULT_FROM_WIN32(winEr); + } + + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices: Ended with hr (0x%x)\n", hr)); + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRemoveAllNetDevicesOfId(IN LPCWSTR pwszPnPId) +{ + return vboxNetCfgWinEnumNetDevices(pwszPnPId, vboxNetCfgWinRemoveAllNetDevicesOfIdCallback, NULL); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinPropChangeAllNetDevicesOfId(IN LPCWSTR pwszPnPId, VBOXNECTFGWINPROPCHANGE_TYPE_T enmPcType) +{ + VBOXNECTFGWINPROPCHANGE Pc; + Pc.enmPcType = enmPcType; + Pc.hr = S_OK; + NonStandardLogFlow(("Calling VBoxNetCfgWinEnumNetDevices with pwszPnPId (= %ls) and vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback\n", pwszPnPId)); + + HRESULT hr = vboxNetCfgWinEnumNetDevices(pwszPnPId, vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback, &Pc); + if (!SUCCEEDED(hr)) + { + NonStandardLogFlow(("VBoxNetCfgWinEnumNetDevices failed 0x%x\n", hr)); + return hr; + } + + if (!SUCCEEDED(Pc.hr)) + { + NonStandardLogFlow(("vboxNetCfgWinPropChangeAllNetDevicesOfIdCallback failed 0x%x\n", Pc.hr)); + return Pc.hr; + } + + return S_OK; +} + + + +/********************************************************************************************************************************* +* Logging * +*********************************************************************************************************************************/ + +static void DoLogging(const char *pszString, ...) +{ + PFNVBOXNETCFGLOGGER pfnLogger = g_pfnLogger; + if (pfnLogger) + { + char szBuffer[4096]; + va_list va; + va_start(va, pszString); + RTStrPrintfV(szBuffer, RT_ELEMENTS(szBuffer), pszString, va); + va_end(va); + + pfnLogger(szBuffer); + } +} + +VBOXNETCFGWIN_DECL(void) VBoxNetCfgWinSetLogging(IN PFNVBOXNETCFGLOGGER pfnLogger) +{ + g_pfnLogger = pfnLogger; +} + + + +/********************************************************************************************************************************* +* IP configuration API * +*********************************************************************************************************************************/ +/* network settings config */ +#if 1 /** @todo r=bird: Can't we replace this with VBox/com/ptr.h? */ +/** + * Strong referencing operators. Used as a second argument to ComPtr<>/ComObjPtr<>. + */ +template +class ComStrongRef +{ +protected: + + static void addref(C *p) { p->AddRef(); } + static void release(C *p) { p->Release(); } +}; + + +/** + * Base template for smart COM pointers. Not intended to be used directly. + */ +template class RefOps = ComStrongRef> +class ComPtrBase : protected RefOps +{ +public: + + /* special template to disable AddRef()/Release() */ + template + class NoAddRefRelease : public I + { + public: + virtual ~NoAddRefRelease() { /* Make VC++ 19.2 happy. */ } + private: +#ifndef VBOX_WITH_XPCOM + STDMETHOD_(ULONG, AddRef)() = 0; + STDMETHOD_(ULONG, Release)() = 0; +#else + NS_IMETHOD_(nsrefcnt) AddRef(void) = 0; + NS_IMETHOD_(nsrefcnt) Release(void) = 0; +#endif + }; + +protected: + + ComPtrBase() : p(NULL) {} + ComPtrBase(const ComPtrBase &that) : p(that.p) { addref(); } + ComPtrBase(C *that_p) : p(that_p) { addref(); } + + ~ComPtrBase() { release(); } + + ComPtrBase &operator=(const ComPtrBase &that) + { + safe_assign(that.p); + return *this; + } + + ComPtrBase &operator=(C *that_p) + { + safe_assign(that_p); + return *this; + } + +public: + + void setNull() + { + release(); + p = NULL; + } + + bool isNull() const + { + return (p == NULL); + } + + bool operator!() const { return isNull(); } + + bool operator<(C* that_p) const { return p < that_p; } + bool operator==(C* that_p) const { return p == that_p; } + + template + bool equalsTo(I *aThat) const + { + return ComPtrEquals(p, aThat); + } + + template + bool equalsTo(const ComPtrBase &oc) const + { + return equalsTo((OC *) oc); + } + + /** Intended to pass instances as in parameters to interface methods */ + operator C *() const { return p; } + + /** + * Dereferences the instance (redirects the -> operator to the managed + * pointer). + */ + NoAddRefRelease *operator->() const + { + AssertMsg (p, ("Managed pointer must not be null\n")); + return (NoAddRefRelease *) p; + } + + template + HRESULT queryInterfaceTo(I **pp) const + { + if (pp) + { + if (p) + return p->QueryInterface(COM_IIDOF(I), (void **)pp); + *pp = NULL; + return S_OK; + } + return E_INVALIDARG; + } + + /** Intended to pass instances as out parameters to interface methods */ + C **asOutParam() + { + setNull(); + return &p; + } + +private: + + void addref() + { + if (p) + RefOps::addref(p); + } + + void release() + { + if (p) + RefOps::release(p); + } + + void safe_assign(C *that_p) + { + /* be aware of self-assignment */ + if (that_p) + RefOps::addref(that_p); + release(); + p = that_p; + } + + C *p; +}; + +/** + * Smart COM pointer wrapper that automatically manages refcounting of + * interface pointers. + * + * @param I COM interface class + */ +template class RefOps = ComStrongRef> +class ComPtr : public ComPtrBase +{ + typedef ComPtrBase Base; + +public: + + ComPtr() : Base() {} + ComPtr(const ComPtr &that) : Base(that) {} + ComPtr&operator= (const ComPtr &that) + { + Base::operator=(that); + return *this; + } + + template + ComPtr(OI *that_p) : Base () { operator=(that_p); } + + /* specialization for I */ + ComPtr(I *that_p) : Base (that_p) {} + + template + ComPtr(const ComPtr &oc) : Base() { operator=((OC *) oc); } + + template + ComPtr &operator=(OI *that_p) + { + if (that_p) + that_p->QueryInterface(COM_IIDOF(I), (void **)Base::asOutParam()); + else + Base::setNull(); + return *this; + } + + /* specialization for I */ + ComPtr &operator=(I *that_p) + { + Base::operator=(that_p); + return *this; + } + + template + ComPtr &operator=(const ComPtr &oc) + { + return operator=((OC *) oc); + } +}; +#endif + +static HRESULT netIfWinFindAdapterClassById(IWbemServices *pSvc, const GUID *pGuid, IWbemClassObject **pAdapterConfig) +{ + HRESULT hr; + + WCHAR wszGuid[50]; + int cwcGuid = StringFromGUID2(*pGuid, wszGuid, RT_ELEMENTS(wszGuid)); + if (cwcGuid) + { + com::BstrFmt bstrQuery("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE SettingID = \"%ls\"", wszGuid); + IEnumWbemClassObject* pEnumerator = NULL; + hr = pSvc->ExecQuery(com::Bstr("WQL").raw(), bstrQuery.raw(), WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, &pEnumerator); + if (SUCCEEDED(hr)) + { + if (pEnumerator) + { + IWbemClassObject *pclsObj = NULL; + ULONG uReturn = 0; + hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn); + NonStandardLogFlow(("netIfWinFindAdapterClassById: IEnumWbemClassObject::Next -> hr=0x%x pclsObj=%p uReturn=%u 42=%u\n", + hr, (void *)pclsObj, uReturn, 42)); + if (SUCCEEDED(hr)) + { + if (uReturn && pclsObj != NULL) + { + *pAdapterConfig = pclsObj; + pEnumerator->Release(); + NonStandardLogFlow(("netIfWinFindAdapterClassById: S_OK and %p\n", *pAdapterConfig)); + return S_OK; + } + hr = E_FAIL; + } + pEnumerator->Release(); + } + else + { + NonStandardLogFlow(("ExecQuery returned no enumerator\n")); + hr = E_FAIL; + } + } + else + NonStandardLogFlow(("ExecQuery failed (0x%x)\n", hr)); + } + else + { + DWORD winEr = GetLastError(); + hr = HRESULT_FROM_WIN32(winEr); + if (SUCCEEDED(hr)) + hr = E_FAIL; + NonStandardLogFlow(("StringFromGUID2 failed winEr=%u, hr=0x%x\n", winEr, hr)); + } + + NonStandardLogFlow(("netIfWinFindAdapterClassById: 0x%x and %p\n", hr, *pAdapterConfig)); + return hr; +} + +static HRESULT netIfWinIsHostOnly(IWbemClassObject *pAdapterConfig, BOOL *pfIsHostOnly) +{ + VARIANT vtServiceName; + VariantInit(&vtServiceName); + + HRESULT hr = pAdapterConfig->Get(L"ServiceName", 0 /*lFlags*/, &vtServiceName, NULL /*pvtType*/, NULL /*plFlavor*/); + if (SUCCEEDED(hr)) + { + *pfIsHostOnly = RTUtf16CmpAscii(vtServiceName.bstrVal, "VBoxNetAdp") == 0; + + VariantClear(&vtServiceName); + } + + return hr; +} + +static HRESULT netIfWinGetIpSettings(IWbemClassObject * pAdapterConfig, ULONG *pIpv4, ULONG *pMaskv4) +{ + *pIpv4 = 0; + *pMaskv4 = 0; + + VARIANT vtIp; + VariantInit(&vtIp); + HRESULT hr = pAdapterConfig->Get(L"IPAddress", 0, &vtIp, 0, 0); + if (SUCCEEDED(hr)) + { + if (vtIp.vt == (VT_ARRAY | VT_BSTR)) + { + VARIANT vtMask; + VariantInit(&vtMask); + hr = pAdapterConfig->Get(L"IPSubnet", 0, &vtMask, 0, 0); + if (SUCCEEDED(hr)) + { + if (vtMask.vt == (VT_ARRAY | VT_BSTR)) + { + SAFEARRAY *pIpArray = vtIp.parray; + SAFEARRAY *pMaskArray = vtMask.parray; + if (pIpArray && pMaskArray) + { + BSTR pBstrCurIp; + BSTR pBstrCurMask; + for (LONG i = 0; + SafeArrayGetElement(pIpArray, &i, (PVOID)&pBstrCurIp) == S_OK + && SafeArrayGetElement(pMaskArray, &i, (PVOID)&pBstrCurMask) == S_OK; + i++) + { + com::Utf8Str strIp(pBstrCurIp); + ULONG Ipv4 = inet_addr(strIp.c_str()); + if (Ipv4 != INADDR_NONE) + { + *pIpv4 = Ipv4; + + com::Utf8Str strMask(pBstrCurMask); + *pMaskv4 = inet_addr(strMask.c_str()); + break; + } + } + } + } + VariantClear(&vtMask); + } + } + VariantClear(&vtIp); + } + + return hr; +} + +#if 0 /* unused */ + +static HRESULT netIfWinHasIpSettings(IWbemClassObject * pAdapterConfig, SAFEARRAY * pCheckIp, SAFEARRAY * pCheckMask, bool *pFound) +{ + VARIANT vtIp; + HRESULT hr; + VariantInit(&vtIp); + + *pFound = false; + + hr = pAdapterConfig->Get(L"IPAddress", 0, &vtIp, 0, 0); + if (SUCCEEDED(hr)) + { + VARIANT vtMask; + VariantInit(&vtMask); + hr = pAdapterConfig->Get(L"IPSubnet", 0, &vtMask, 0, 0); + if (SUCCEEDED(hr)) + { + SAFEARRAY * pIpArray = vtIp.parray; + SAFEARRAY * pMaskArray = vtMask.parray; + if (pIpArray && pMaskArray) + { + BSTR pIp, pMask; + for (LONG k = 0; + SafeArrayGetElement(pCheckIp, &k, (PVOID)&pIp) == S_OK + && SafeArrayGetElement(pCheckMask, &k, (PVOID)&pMask) == S_OK; + k++) + { + BSTR pCurIp; + BSTR pCurMask; + for (LONG i = 0; + SafeArrayGetElement(pIpArray, &i, (PVOID)&pCurIp) == S_OK + && SafeArrayGetElement(pMaskArray, &i, (PVOID)&pCurMask) == S_OK; + i++) + { + if (!wcsicmp(pCurIp, pIp)) + { + if (!wcsicmp(pCurMask, pMask)) + *pFound = true; + break; + } + } + } + } + + + VariantClear(&vtMask); + } + + VariantClear(&vtIp); + } + + return hr; +} + +static HRESULT netIfWinWaitIpSettings(IWbemServices *pSvc, const GUID * pGuid, SAFEARRAY * pCheckIp, SAFEARRAY * pCheckMask, ULONG sec2Wait, bool *pFound) +{ + /* on Vista we need to wait for the address to get applied */ + /* wait for the address to appear in the list */ + HRESULT hr = S_OK; + ULONG i; + *pFound = false; + ComPtr pAdapterConfig; + for (i = 0; + (hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam())) == S_OK + && (hr = netIfWinHasIpSettings(pAdapterConfig, pCheckIp, pCheckMask, pFound)) == S_OK + && !(*pFound) + && i < sec2Wait/6; + i++) + { + Sleep(6000); + } + + return hr; +} + +#endif /* unused */ + +static HRESULT netIfWinCreateIWbemServices(IWbemServices **ppSvc) +{ + IWbemLocator *pLoc = NULL; + HRESULT hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc); + if (SUCCEEDED(hr)) + { + IWbemServices *pSvc = NULL; + hr = pLoc->ConnectServer(com::Bstr(L"ROOT\\CIMV2").raw(), /* [in] const BSTR strNetworkResource */ + NULL, /* [in] const BSTR strUser */ + NULL, /* [in] const BSTR strPassword */ + 0, /* [in] const BSTR strLocale */ + NULL, /* [in] LONG lSecurityFlags */ + 0, /* [in] const BSTR strAuthority */ + 0, /* [in] IWbemContext* pCtx */ + &pSvc /* [out] IWbemServices** ppNamespace */); + if (SUCCEEDED(hr)) + { + hr = CoSetProxyBlanket(pSvc, /* IUnknown * pProxy */ + RPC_C_AUTHN_WINNT, /* DWORD dwAuthnSvc */ + RPC_C_AUTHZ_NONE, /* DWORD dwAuthzSvc */ + NULL, /* WCHAR * pServerPrincName */ + RPC_C_AUTHN_LEVEL_CALL, /* DWORD dwAuthnLevel */ + RPC_C_IMP_LEVEL_IMPERSONATE, /* DWORD dwImpLevel */ + NULL, /* RPC_AUTH_IDENTITY_HANDLE pAuthInfo */ + EOAC_NONE /* DWORD dwCapabilities */ + ); + if (SUCCEEDED(hr)) + { + *ppSvc = pSvc; + /* do not need it any more */ + pLoc->Release(); + return hr; + } + + NonStandardLogFlow(("CoSetProxyBlanket failed: %Rhrc\n", hr)); + pSvc->Release(); + } + else + NonStandardLogFlow(("ConnectServer failed: %Rhrc\n", hr)); + pLoc->Release(); + } + else + NonStandardLogFlow(("CoCreateInstance failed: %Rhrc\n", hr)); + return hr; +} + +static HRESULT netIfWinAdapterConfigPath(IWbemClassObject *pObj, com::Bstr *pRet) +{ + VARIANT index; + VariantInit(&index); + HRESULT hr = pObj->Get(L"Index", 0, &index, 0, 0); + if (SUCCEEDED(hr)) + hr = pRet->printfNoThrow("Win32_NetworkAdapterConfiguration.Index='%u'", index.uintVal); + else + { + pRet->setNull(); + NonStandardLogFlow(("Get failed: %Rhrc\n", hr)); + } + return hr; +} + +static HRESULT netIfExecMethod(IWbemServices * pSvc, IWbemClassObject *pClass, com::Bstr const &rObjPath, + const char *pszMethodName, LPWSTR *papwszArgNames, LPVARIANT *pArgs, UINT cArgs, + IWbemClassObject **ppOutParams) +{ + *ppOutParams = NULL; + com::Bstr bstrMethodName; + HRESULT hr = bstrMethodName.assignEx(pszMethodName); + if (SUCCEEDED(hr)) + { + ComPtr pInParamsDefinition; + ComPtr pClassInstance; + if (cArgs) + { + hr = pClass->GetMethod(bstrMethodName.raw(), 0, pInParamsDefinition.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + hr = pInParamsDefinition->SpawnInstance(0, pClassInstance.asOutParam()); + if (SUCCEEDED(hr)) + { + for (UINT i = 0; i < cArgs; i++) + { + hr = pClassInstance->Put(papwszArgNames[i], 0, pArgs[i], 0); + if (FAILED(hr)) + break; + } + } + } + } + + if (SUCCEEDED(hr)) + { + IWbemClassObject *pOutParams = NULL; + hr = pSvc->ExecMethod(rObjPath.raw(), bstrMethodName.raw(), 0, NULL, pClassInstance, &pOutParams, NULL); + if (SUCCEEDED(hr)) + *ppOutParams = pOutParams; + } + } + + return hr; +} + +static HRESULT netIfWinCreateIpArray(SAFEARRAY **ppArray, in_addr const *paIps, UINT cIps) +{ + HRESULT hr = S_OK; + SAFEARRAY *pIpArray = SafeArrayCreateVector(VT_BSTR, 0, cIps); + if (pIpArray) + { + for (UINT i = 0; i < cIps; i++) + { + com::Bstr bstrVal; + hr = bstrVal.printfNoThrow("%RTnaipv4", paIps[i].s_addr); + if (SUCCEEDED(hr)) + { + Assert(bstrVal.equals(inet_ntoa(paIps[i]))); + + BSTR pRawVal; + hr = bstrVal.detachToEx(&pRawVal); + if (SUCCEEDED(hr)) + { + LONG aIndex[1] = { (LONG)i }; + hr = SafeArrayPutElement(pIpArray, aIndex, pRawVal); + if (SUCCEEDED(hr)) + continue; + SysFreeString(pRawVal); + } + } + break; + } + + if (SUCCEEDED(hr)) + *ppArray = pIpArray; + else + SafeArrayDestroy(pIpArray); + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + return hr; +} + +#if 0 /* unused */ +static HRESULT netIfWinCreateIpArrayV4V6(SAFEARRAY **ppArray, BSTR Ip) +{ + HRESULT hr; + SAFEARRAY *pIpArray = SafeArrayCreateVector(VT_BSTR, 0, 1); + if (pIpArray) + { + BSTR val = com::Bstr(Ip, false).copy(); + long aIndex[1]; + aIndex[0] = 0; + hr = SafeArrayPutElement(pIpArray, aIndex, val); + if (FAILED(hr)) + { + SysFreeString(val); + SafeArrayDestroy(pIpArray); + } + + if (SUCCEEDED(hr)) + { + *ppArray = pIpArray; + } + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + + return hr; +} +#endif + + +static HRESULT netIfWinCreateIpArrayVariantV4(VARIANT *pIpAddresses, in_addr const *paIps, UINT cIps) +{ + VariantInit(pIpAddresses); + pIpAddresses->vt = VT_ARRAY | VT_BSTR; + + SAFEARRAY *pIpArray; + HRESULT hr = netIfWinCreateIpArray(&pIpArray, paIps, cIps); + if (SUCCEEDED(hr)) + pIpAddresses->parray = pIpArray; + return hr; +} + +#if 0 /* unused */ +static HRESULT netIfWinCreateIpArrayVariantV4V6(VARIANT * pIpAddresses, BSTR Ip) +{ + HRESULT hr; + VariantInit(pIpAddresses); + pIpAddresses->vt = VT_ARRAY | VT_BSTR; + SAFEARRAY *pIpArray; + hr = netIfWinCreateIpArrayV4V6(&pIpArray, Ip); + if (SUCCEEDED(hr)) + { + pIpAddresses->parray = pIpArray; + } + return hr; +} +#endif + +static HRESULT netIfWinEnableStatic(IWbemServices *pSvc, const GUID *pGuid, com::Bstr &rObjPath, VARIANT *pIp, VARIANT *pMask) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + LPWSTR argNames[] = {L"IPAddress", L"SubnetMask"}; + LPVARIANT args[] = { pIp, pMask }; + + ComPtr pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath.raw(), "EnableStatic", argNames, args, + 2, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + { + hr = S_OK; + //bool bFound; + //HRESULT tmpHr = netIfWinWaitIpSettings(pSvc, pGuid, pIp->parray, pMask->parray, 180, &bFound); + NOREF(pGuid); + break; + } + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + } + } + return hr; +} + + +static HRESULT netIfWinEnableStaticV4(IWbemServices *pSvc, const GUID *pGuid, com::Bstr &rObjPath, + in_addr const *paIps, in_addr const *paMasks, UINT cIpAndMasks) +{ + VARIANT ipAddresses; + HRESULT hr = netIfWinCreateIpArrayVariantV4(&ipAddresses, paIps, cIpAndMasks); + if (SUCCEEDED(hr)) + { + VARIANT ipMasks; + hr = netIfWinCreateIpArrayVariantV4(&ipMasks, paMasks, cIpAndMasks); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStatic(pSvc, pGuid, rObjPath, &ipAddresses, &ipMasks); + VariantClear(&ipMasks); + } + VariantClear(&ipAddresses); + } + return hr; +} + +#if 0 /* unused */ + +static HRESULT netIfWinEnableStaticV4V6(IWbemServices * pSvc, const GUID * pGuid, BSTR ObjPath, BSTR Ip, BSTR Mask) +{ + VARIANT ipAddresses; + HRESULT hr = netIfWinCreateIpArrayVariantV4V6(&ipAddresses, Ip); + if (SUCCEEDED(hr)) + { + VARIANT ipMasks; + hr = netIfWinCreateIpArrayVariantV4V6(&ipMasks, Mask); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStatic(pSvc, pGuid, ObjPath, &ipAddresses, &ipMasks); + VariantClear(&ipMasks); + } + VariantClear(&ipAddresses); + } + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGateways(IWbemServices * pSvc, BSTR ObjPath, VARIANT * pGw) +{ + ComPtr pClass; + BSTR ClassName = SysAllocString(L"Win32_NetworkAdapterConfiguration"); + HRESULT hr; + if (ClassName) + { + hr = pSvc->GetObject(ClassName, 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + LPWSTR argNames[] = {L"DefaultIPGateway"}; + LPVARIANT args[] = {pGw}; + ComPtr pOutParams; + + hr = netIfExecMethod(pSvc, pClass, ObjPath, com::Bstr(L"SetGateways"), argNames, args, 1, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + hr = pOutParams->Get(com::Bstr(L"ReturnValue"), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { +// Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + hr = S_OK; + break; + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + SysFreeString(ClassName); + } + else + hr = HRESULT_FROM_WIN32(GetLastError()); + + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGatewaysV4(IWbemServices * pSvc, BSTR ObjPath, in_addr* aGw, UINT cGw) +{ + VARIANT gwais; + HRESULT hr = netIfWinCreateIpArrayVariantV4(&gwais, aGw, cGw); + if (SUCCEEDED(hr)) + { + netIfWinSetGateways(pSvc, ObjPath, &gwais); + VariantClear(&gwais); + } + return hr; +} + +/* win API allows to set gw metrics as well, we are not setting them */ +static HRESULT netIfWinSetGatewaysV4V6(IWbemServices * pSvc, BSTR ObjPath, BSTR Gw) +{ + VARIANT vGw; + HRESULT hr = netIfWinCreateIpArrayVariantV4V6(&vGw, Gw); + if (SUCCEEDED(hr)) + { + netIfWinSetGateways(pSvc, ObjPath, &vGw); + VariantClear(&vGw); + } + return hr; +} + +#endif /* unused */ + +static HRESULT netIfWinEnableDHCP(IWbemServices * pSvc, const com::Bstr &rObjPath) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + ComPtr pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath, "EnableDHCP", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + switch (winEr) + { + case 0: + hr = S_OK; + break; + default: + hr = HRESULT_FROM_WIN32( winEr ); + break; + } + } + } + } + } + } + return hr; +} + +static HRESULT netIfWinDhcpRediscover(IWbemServices * pSvc, const com::Bstr &rObjPath) +{ + com::Bstr bstrClassName; + HRESULT hr = bstrClassName.assignEx("Win32_NetworkAdapterConfiguration"); + if (SUCCEEDED(hr)) + { + ComPtr pClass; + hr = pSvc->GetObject(bstrClassName.raw(), 0, NULL, pClass.asOutParam(), NULL); + if (SUCCEEDED(hr)) + { + ComPtr pOutParams; + hr = netIfExecMethod(pSvc, pClass, rObjPath, "ReleaseDHCPLease", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + com::Bstr bstrReturnValue; + hr = bstrReturnValue.assignEx("ReturnValue"); + if (SUCCEEDED(hr)) + { + VARIANT varReturnValue; + VariantInit(&varReturnValue); + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + int winEr = varReturnValue.uintVal; + if (winEr == 0) + { + hr = netIfExecMethod(pSvc, pClass, rObjPath, "RenewDHCPLease", NULL, NULL, 0, pOutParams.asOutParam()); + if (SUCCEEDED(hr)) + { + hr = pOutParams->Get(bstrReturnValue.raw(), 0, &varReturnValue, NULL, 0); + Assert(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) + { + //Assert(varReturnValue.vt == VT_UINT); + winEr = varReturnValue.uintVal; + if (winEr == 0) + hr = S_OK; + else + hr = HRESULT_FROM_WIN32( winEr ); + } + } + } + else + hr = HRESULT_FROM_WIN32( winEr ); + } + } + } + } + } + + return hr; +} + +static HRESULT vboxNetCfgWinIsDhcpEnabled(IWbemClassObject *pAdapterConfig, BOOL *pfEnabled) +{ + VARIANT vtEnabled; + VariantInit(&vtEnabled); + HRESULT hr = pAdapterConfig->Get(L"DHCPEnabled", 0, &vtEnabled, 0, 0); + if (SUCCEEDED(hr)) + *pfEnabled = vtEnabled.boolVal; + else + *pfEnabled = FALSE; + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGetAdapterSettings(IN const GUID *pGuid, OUT PADAPTER_SETTINGS pSettings) +{ + ComPtr pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + hr = vboxNetCfgWinIsDhcpEnabled(pAdapterConfig, &pSettings->bDhcp); + if (SUCCEEDED(hr)) + hr = netIfWinGetIpSettings(pAdapterConfig, &pSettings->ip, &pSettings->mask); + } + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinIsDhcpEnabled(const GUID * pGuid, BOOL *pEnabled) +{ + ComPtr pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + VARIANT vtEnabled; + hr = pAdapterConfig->Get(L"DHCPEnabled", 0, &vtEnabled, 0, 0); + if (SUCCEEDED(hr)) + *pEnabled = vtEnabled.boolVal; + } + } + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinEnableStaticIpConfig(IN const GUID *pGuid, IN ULONG ip, IN ULONG mask) +{ + NonStandardLogFlow(("VBoxNetCfgWinEnableStaticIpConfig: ip=0x%x mask=0x%x\n", ip, mask)); + ComPtr pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + in_addr aIp[1]; + in_addr aMask[1]; + aIp[0].S_un.S_addr = ip; + aMask[0].S_un.S_addr = mask; + + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStaticV4(pSvc, pGuid, bstrObjPath, aIp, aMask, ip != 0 ? 1 : 0); + if (SUCCEEDED(hr)) + { +#if 0 + in_addr aGw[1]; + aGw[0].S_un.S_addr = gw; + hr = netIfWinSetGatewaysV4(pSvc, bstrObjPath, aGw, 1); + if (SUCCEEDED(hr)) +#endif + { + } + } + } + } + else + { + hr = E_FAIL; + } + } + } + } + + NonStandardLogFlow(("VBoxNetCfgWinEnableStaticIpConfig: returns %Rhrc\n", hr)); + return hr; +} + +#if 0 +static HRESULT netIfEnableStaticIpConfigV6(const GUID *pGuid, IN_BSTR aIPV6Address, IN_BSTR aIPV6Mask, IN_BSTR aIPV6DefaultGateway) +{ + HRESULT hr; + ComPtr pSvc; + hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BSTR ObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &ObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableStaticV4V6(pSvc, pAdapterConfig, ObjPath, aIPV6Address, aIPV6Mask); + if (SUCCEEDED(hr)) + { + if (aIPV6DefaultGateway) + { + hr = netIfWinSetGatewaysV4V6(pSvc, ObjPath, aIPV6DefaultGateway); + } + if (SUCCEEDED(hr)) + { +// hr = netIfWinUpdateConfig(pIf); + } + } + SysFreeString(ObjPath); + } + } + } + + return SUCCEEDED(hr) ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +static HRESULT netIfEnableStaticIpConfigV6(const GUID *pGuid, IN_BSTR aIPV6Address, ULONG aIPV6MaskPrefixLength) +{ + RTNETADDRIPV6 Mask; + int rc = RTNetPrefixToMaskIPv6(aIPV6MaskPrefixLength, &Mask); + if (RT_SUCCESS(rc)) + { + Bstr maskStr = composeIPv6Address(&Mask); + rc = netIfEnableStaticIpConfigV6(pGuid, aIPV6Address, maskStr, NULL); + } + return rc; +} +#endif + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinEnableDynamicIpConfig(IN const GUID *pGuid) +{ + ComPtr pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinEnableDHCP(pSvc, bstrObjPath); + if (SUCCEEDED(hr)) + { + //hr = netIfWinUpdateConfig(pIf); + } + } + } + else + hr = E_FAIL; + } + } + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinDhcpRediscover(IN const GUID *pGuid) +{ + ComPtr pSvc; + HRESULT hr = netIfWinCreateIWbemServices(pSvc.asOutParam()); + if (SUCCEEDED(hr)) + { + ComPtr pAdapterConfig; + hr = netIfWinFindAdapterClassById(pSvc, pGuid, pAdapterConfig.asOutParam()); + if (SUCCEEDED(hr)) + { + BOOL fIsHostOnly; + hr = netIfWinIsHostOnly(pAdapterConfig, &fIsHostOnly); + if (SUCCEEDED(hr)) + { + if (fIsHostOnly) + { + com::Bstr bstrObjPath; + hr = netIfWinAdapterConfigPath(pAdapterConfig, &bstrObjPath); + if (SUCCEEDED(hr)) + { + hr = netIfWinDhcpRediscover(pSvc, bstrObjPath); + if (SUCCEEDED(hr)) + { + //hr = netIfWinUpdateConfig(pIf); + } + } + } + else + hr = E_FAIL; + } + } + } + + + return hr; +} + +static const char *vboxNetCfgWinAddrToStr(char *pszBuf, size_t cbBuf, LPSOCKADDR pAddr) +{ + switch (pAddr->sa_family) + { + case AF_INET: + RTStrPrintf(pszBuf, cbBuf, "%d.%d.%d.%d", + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b1, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b2, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b3, + ((PSOCKADDR_IN)pAddr)->sin_addr.S_un.S_un_b.s_b4); + break; + case AF_INET6: + RTStrPrintf(pszBuf, cbBuf, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[0], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[1], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[2], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[3], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[4], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[5], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[6], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[7], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[8], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[9], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[10], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[11], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[12], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[13], + ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[14], ((PSOCKADDR_IN6)pAddr)->sin6_addr.s6_addr[15]); + break; + default: + RTStrCopy(pszBuf, cbBuf, "unknown"); + break; + } + return pszBuf; +} + +typedef bool (*PFNVBOXNETCFG_IPSETTINGS_CALLBACK) (ULONG ip, ULONG mask, PVOID pContext); + +static void vboxNetCfgWinEnumIpConfig(PIP_ADAPTER_ADDRESSES pAddresses, PFNVBOXNETCFG_IPSETTINGS_CALLBACK pfnCallback, PVOID pContext) +{ + PIP_ADAPTER_ADDRESSES pAdapter; + for (pAdapter = pAddresses; pAdapter; pAdapter = pAdapter->Next) + { + NonStandardLogFlow(("+- Enumerating adapter '%ls' %s\n", pAdapter->FriendlyName, pAdapter->AdapterName)); + for (PIP_ADAPTER_PREFIX pPrefix = pAdapter->FirstPrefix; pPrefix; pPrefix = pPrefix->Next) + { + char szBuf[80]; + const char *pcszAddress = vboxNetCfgWinAddrToStr(szBuf, sizeof(szBuf), pPrefix->Address.lpSockaddr); + + /* We are concerned with IPv4 only, ignore the rest. */ + if (pPrefix->Address.lpSockaddr->sa_family != AF_INET) + { + NonStandardLogFlow(("| +- %s %d: not IPv4, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + /* Ignore invalid prefixes as well as host addresses. */ + if (pPrefix->PrefixLength < 1 || pPrefix->PrefixLength > 31) + { + NonStandardLogFlow(("| +- %s %d: host or broadcast, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + /* Ignore multicast and beyond. */ + ULONG ip = ((struct sockaddr_in *)pPrefix->Address.lpSockaddr)->sin_addr.s_addr; + if ((ip & 0xF0) > 224) + { + NonStandardLogFlow(("| +- %s %d: multicast, ignoring\n", pcszAddress, pPrefix->PrefixLength)); + continue; + } + + ULONG mask = htonl((~(((ULONG)~0) >> pPrefix->PrefixLength))); + bool fContinue = pfnCallback(ip, mask, pContext); + if (!fContinue) + { + NonStandardLogFlow(("| +- %s %d: CONFLICT!\n", pcszAddress, pPrefix->PrefixLength)); + return; + } + + NonStandardLogFlow(("| +- %s %d: no conflict, moving on\n", pcszAddress, pPrefix->PrefixLength)); + } + } +} + +typedef struct _IPPROBE_CONTEXT +{ + ULONG Prefix; + bool fConflict; +}IPPROBE_CONTEXT, *PIPPROBE_CONTEXT; + +#define IPPROBE_INIT(a_pContext, a_addr) \ + do { (a_pContext)->fConflict = false; (a_pContext)->Prefix = (a_addr); } while (0) + +#define IPPROBE_INIT_STR(a_pContext, a_straddr) \ + IPROBE_INIT(a_pContext, inet_addr(_straddr)) + +static bool vboxNetCfgWinIpProbeCallback (ULONG ip, ULONG mask, PVOID pContext) +{ + PIPPROBE_CONTEXT pProbe = (PIPPROBE_CONTEXT)pContext; + + if ((ip & mask) == (pProbe->Prefix & mask)) + { + pProbe->fConflict = true; + return false; + } + + return true; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGenHostOnlyNetworkNetworkIp(OUT PULONG pNetIp, OUT PULONG pNetMask) +{ + HRESULT hr = S_OK; + + *pNetIp = 0; + *pNetMask = 0; + + /* + * MSDN recommends to pre-allocate a 15KB buffer. + */ + ULONG cbBuf = 15 * _1K; + PIP_ADAPTER_ADDRESSES paAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAllocZ(cbBuf); + if (!paAddresses) + return E_OUTOFMEMORY; + DWORD dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, paAddresses, &cbBuf); + if (dwRc == ERROR_BUFFER_OVERFLOW) + { + /* Impressive! More than 10 adapters! Get more memory and try again. */ + RTMemFree(paAddresses); + paAddresses = (PIP_ADAPTER_ADDRESSES)RTMemAllocZ(cbBuf); + if (!paAddresses) + return E_OUTOFMEMORY; + dwRc = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, paAddresses, &cbBuf); + } + if (dwRc == NO_ERROR) + { + const ULONG ip192168 = inet_addr("192.168.0.0"); + for (int i = 0; i < 384; i++) + { +#if 0 + ULONG ipProbe = rand()*255 / RAND_MAX; +#else + uint32_t ipProbe = RTRandU32Ex(0, 255); +#endif + ipProbe = ip192168 | (ipProbe << 16); + NonStandardLogFlow(("probing %RTnaipv4\n", ipProbe)); + + IPPROBE_CONTEXT Context; + IPPROBE_INIT(&Context, ipProbe); + vboxNetCfgWinEnumIpConfig(paAddresses, vboxNetCfgWinIpProbeCallback, &Context); + if (!Context.fConflict) + { + NonStandardLogFlow(("found unused net %RTnaipv4\n", ipProbe)); + *pNetIp = ipProbe; + *pNetMask = inet_addr("255.255.255.0"); + break; + } + } + if (*pNetIp == 0) + dwRc = ERROR_DHCP_ADDRESS_CONFLICT; + } + else + NonStandardLogFlow(("GetAdaptersAddresses err (%u)\n", dwRc)); + + RTMemFree(paAddresses); + + if (dwRc != NO_ERROR) + hr = HRESULT_FROM_WIN32(dwRc); + return hr; +} + +/* + * convenience functions to perform netflt/adp manipulations + */ +#define VBOXNETCFGWIN_NETFLT_ID L"sun_VBoxNetFlt" +#define VBOXNETCFGWIN_NETFLT_MP_ID L"sun_VBoxNetFltmp" + +static HRESULT vboxNetCfgWinNetFltUninstall(IN INetCfg *pNc, DWORD InfRmFlags) +{ + INetCfgComponent *pNcc = NULL; + HRESULT hr = pNc->FindComponent(VBOXNETCFGWIN_NETFLT_ID, &pNcc); + if (hr == S_OK) + { + NonStandardLog("NetFlt is installed currently, uninstalling ...\n"); + + hr = VBoxNetCfgWinUninstallComponent(pNc, pNcc); + NonStandardLogFlow(("NetFlt component uninstallation ended with hr (%Rhrc)\n", hr)); + + pNcc->Release(); + } + else if (hr == S_FALSE) + NonStandardLog("NetFlt is not installed currently\n"); + else + NonStandardLogFlow(("FindComponent failed: %Rhrc\n", hr)); + + VBoxDrvCfgInfUninstallAllF(L"NetService", VBOXNETCFGWIN_NETFLT_ID, InfRmFlags); + VBoxDrvCfgInfUninstallAllF(L"Net", VBOXNETCFGWIN_NETFLT_MP_ID, InfRmFlags); + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetFltUninstall(IN INetCfg *pNc) +{ + return vboxNetCfgWinNetFltUninstall(pNc, 0); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetFltInstall(IN INetCfg *pNc, IN LPCWSTR const *pwszInfFullPath, IN UINT cInfFullPaths) +{ + HRESULT hr = vboxNetCfgWinNetFltUninstall(pNc, SUOI_FORCEDELETE); + if (SUCCEEDED(hr)) + { + NonStandardLog("NetFlt will be installed ...\n"); + hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETFLT_ID, + &GUID_DEVCLASS_NETSERVICE, + pwszInfFullPath, + cInfFullPaths, + NULL); + } + return hr; +} + +static HRESULT vboxNetCfgWinNetAdpUninstall(IN INetCfg *pNc, LPCWSTR pwszId, DWORD InfRmFlags) +{ + NOREF(pNc); + NonStandardLog("Finding NetAdp driver package and trying to uninstall it ...\n"); + + VBoxDrvCfgInfUninstallAllF(L"Net", pwszId, InfRmFlags); + NonStandardLog("NetAdp is not installed currently\n"); + return S_OK; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetAdpUninstall(IN INetCfg *pNc, IN LPCWSTR pwszId) +{ + return vboxNetCfgWinNetAdpUninstall(pNc, pwszId, SUOI_FORCEDELETE); +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetAdpInstall(IN INetCfg *pNc, + IN LPCWSTR const pwszInfFullPath) +{ + NonStandardLog("NetAdp will be installed ...\n"); + HRESULT hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETADP_ID_WSZ, + &GUID_DEVCLASS_NET, + &pwszInfFullPath, + 1, + NULL); + return hr; +} + + +static HRESULT vboxNetCfgWinNetLwfUninstall(IN INetCfg *pNc, DWORD InfRmFlags) +{ + INetCfgComponent * pNcc = NULL; + HRESULT hr = pNc->FindComponent(VBOXNETCFGWIN_NETLWF_ID, &pNcc); + if (hr == S_OK) + { + NonStandardLog("NetLwf is installed currently, uninstalling ...\n"); + + hr = VBoxNetCfgWinUninstallComponent(pNc, pNcc); + + pNcc->Release(); + } + else if (hr == S_FALSE) + { + NonStandardLog("NetLwf is not installed currently\n"); + hr = S_OK; + } + else + { + NonStandardLogFlow(("FindComponent failed: %Rhrc\n", hr)); + hr = S_OK; + } + + VBoxDrvCfgInfUninstallAllF(L"NetService", VBOXNETCFGWIN_NETLWF_ID, InfRmFlags); + + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetLwfUninstall(IN INetCfg *pNc) +{ + return vboxNetCfgWinNetLwfUninstall(pNc, 0); +} + +static void VBoxNetCfgWinFilterLimitWorkaround(void) +{ + /* + * Need to check if the system has a limit of installed filter drivers. If it + * has, bump the limit to 14, which the maximum value supported by Windows 7. + * Note that we only touch the limit if it is set to the default value (8). + * See @bugref{7899}. + */ + /** @todo r=bird: This code was mixing HRESULT and LSTATUS, checking the return + * codes using SUCCEEDED(lrc) instead of lrc == ERROR_SUCCESS. So, it might + * have misbehaved on bogus registry content, but worked fine on sane values. */ + HKEY hKeyNet = NULL; + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Network", 0, + KEY_QUERY_VALUE | KEY_SET_VALUE, &hKeyNet); + if (lrc == ERROR_SUCCESS) + { + DWORD dwMaxNumFilters = 0; + DWORD cbMaxNumFilters = sizeof(dwMaxNumFilters); + lrc = RegQueryValueExW(hKeyNet, L"MaxNumFilters", NULL, NULL, (LPBYTE)&dwMaxNumFilters, &cbMaxNumFilters); + if (lrc == ERROR_SUCCESS && cbMaxNumFilters == sizeof(dwMaxNumFilters) && dwMaxNumFilters == 8) + { + dwMaxNumFilters = 14; + lrc = RegSetValueExW(hKeyNet, L"MaxNumFilters", 0, REG_DWORD, (LPBYTE)&dwMaxNumFilters, sizeof(dwMaxNumFilters)); + if (lrc == ERROR_SUCCESS) + NonStandardLog("Adjusted the installed filter limit to 14...\n"); + else + NonStandardLog("Failed to set MaxNumFilters, error code %d\n", lrc); + } + RegCloseKey(hKeyNet); + } + else + NonStandardLog("Failed to open network key, error code %d\n", lrc); + +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinNetLwfInstall(IN INetCfg *pNc, IN LPCWSTR const pwszInfFullPath) +{ + HRESULT hr = vboxNetCfgWinNetLwfUninstall(pNc, SUOI_FORCEDELETE); + if (SUCCEEDED(hr)) + { + VBoxNetCfgWinFilterLimitWorkaround(); + NonStandardLog("NetLwf will be installed ...\n"); + hr = vboxNetCfgWinInstallInfAndComponent(pNc, VBOXNETCFGWIN_NETLWF_ID, + &GUID_DEVCLASS_NETSERVICE, + &pwszInfFullPath, + 1, + NULL); + } + return hr; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinGenHostonlyConnectionName(IN PCWSTR pwszDevName, OUT WCHAR *pwszBuf, + IN ULONG cwcBuf, OUT PULONG pcwcNeeded) +{ + /* Look for a suffix that we need to preserve. */ + size_t const cwcDevName = RTUtf16Len(pwszDevName); + size_t offSuffix = cwcDevName; + while (offSuffix > 0 && pwszDevName[offSuffix - 1] != '#') + offSuffix--; + size_t const cwcSuffix = pwszDevName[offSuffix] != '#' ? 0 : cwcDevName - offSuffix; + + /* Calculate required buffer size: */ + size_t cwcNeeded = sizeof(VBOX_CONNECTION_NAME_WSZ) / sizeof(wchar_t) /* includes terminator */ + + !!cwcSuffix /*space*/ + cwcSuffix; + if (pcwcNeeded) + *pcwcNeeded = (ULONG)cwcNeeded; + + if (cwcNeeded <= cwcBuf) + { + memcpy(pwszBuf, VBOX_CONNECTION_NAME_WSZ, sizeof(VBOX_CONNECTION_NAME_WSZ)); + if (cwcSuffix > 0) + { + size_t offDst = sizeof(VBOX_CONNECTION_NAME_WSZ) / sizeof(wchar_t) - 1; + pwszBuf[offDst++] = ' '; + memcpy(&pwszBuf[offDst], &pwszDevName[offSuffix], cwcSuffix * sizeof(wchar_t)); + pwszBuf[offDst + cwcSuffix] = '\0'; + } + return S_OK; + } + return E_FAIL; +} + +static BOOL vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority(IN INetCfg *pNc, IN INetCfgComponent *pNcc, PVOID pContext) +{ + GUID * const pGuid = (GUID*)pContext; + RT_NOREF1(pNc); + + /* Get component's binding. */ + INetCfgComponentBindings *pNetCfgBindings = NULL; + HRESULT hr = pNcc->QueryInterface(IID_INetCfgComponentBindings, (PVOID *)&pNetCfgBindings); + if (SUCCEEDED(hr)) + { + /* Get binding path enumerator reference. */ + IEnumNetCfgBindingPath *pEnumNetCfgBindPath = NULL; + hr = pNetCfgBindings->EnumBindingPaths(EBP_BELOW, &pEnumNetCfgBindPath); + if (SUCCEEDED(hr)) + { + bool fFoundIface = false; + hr = pEnumNetCfgBindPath->Reset(); + do + { + INetCfgBindingPath *pNetCfgBindPath = NULL; + hr = pEnumNetCfgBindPath->Next(1, &pNetCfgBindPath, NULL); + if (hr == S_OK) + { + IEnumNetCfgBindingInterface *pEnumNetCfgBindIface; + hr = pNetCfgBindPath->EnumBindingInterfaces(&pEnumNetCfgBindIface); + if (hr == S_OK) + { + pEnumNetCfgBindIface->Reset(); + do + { + INetCfgBindingInterface *pNetCfgBindIfce; + hr = pEnumNetCfgBindIface->Next(1, &pNetCfgBindIfce, NULL); + if (hr == S_OK) + { + INetCfgComponent *pNetCfgCompo; + hr = pNetCfgBindIfce->GetLowerComponent(&pNetCfgCompo); + if (hr == S_OK) + { + ULONG uComponentStatus; + hr = pNetCfgCompo->GetDeviceStatus(&uComponentStatus); + if (hr == S_OK) + { + GUID guid; + hr = pNetCfgCompo->GetInstanceGuid(&guid); + if ( hr == S_OK + && guid == *pGuid) + { + hr = pNetCfgBindings->MoveAfter(pNetCfgBindPath, NULL); + if (FAILED(hr)) + NonStandardLogFlow(("Unable to move interface: %Rhrc\n", hr)); + fFoundIface = true; + + /* + * Enable binding paths for host-only adapters bound to bridged filter + * (see @bugref{8140}). + */ + HRESULT hr2; + LPWSTR pwszHwId = NULL; + if ((hr2 = pNcc->GetId(&pwszHwId)) != S_OK) + NonStandardLogFlow(("Failed to get HW ID: %Rhrc\n", hr2)); + else + { + /** @todo r=bird: Original was: + * _wcsnicmp(pwszHwId, VBOXNETCFGWIN_NETLWF_ID, sizeof(VBOXNETCFGWIN_NETLWF_ID)/2) + * which is the same as _wcsicmp. Not sure if this was accidental, but it's not the + * only one in this code area (VBoxNetFltNobj.cpp had some too IIRC). */ + if (RTUtf16ICmp(pwszHwId, VBOXNETCFGWIN_NETLWF_ID) != 0) + NonStandardLogFlow(("Ignoring component %ls\n", pwszHwId)); + else if ((hr2 = pNetCfgBindPath->IsEnabled()) != S_FALSE) + NonStandardLogFlow(("Already enabled binding path: %Rhrc\n", hr2)); + else if ((hr2 = pNetCfgBindPath->Enable(TRUE)) != S_OK) + NonStandardLogFlow(("Failed to enable binding path: %Rhrc\n", hr2)); + else + NonStandardLogFlow(("Enabled binding path\n")); + CoTaskMemFree(pwszHwId); + } + } + } + pNetCfgCompo->Release(); + } + else + NonStandardLogFlow(("GetLowerComponent failed: %Rhrc\n", hr)); + pNetCfgBindIfce->Release(); + } + else + { + if (hr == S_FALSE) /* No more binding interfaces? */ + hr = S_OK; + else + NonStandardLogFlow(("Next binding interface failed: %Rhrc\n", hr)); + break; + } + } while (!fFoundIface); + pEnumNetCfgBindIface->Release(); + } + else + NonStandardLogFlow(("EnumBindingInterfaces failed: %Rhrc\n", hr)); + pNetCfgBindPath->Release(); + } + else + { + if (hr == S_FALSE) /* No more binding paths? */ + hr = S_OK; + else + NonStandardLogFlow(("Next bind path failed: %Rhrc\n", hr)); + break; + } + } while (!fFoundIface); + pEnumNetCfgBindPath->Release(); + } + else + NonStandardLogFlow(("EnumBindingPaths failed: %Rhrc\n", hr)); + pNetCfgBindings->Release(); + } + else + NonStandardLogFlow(("QueryInterface for IID_INetCfgComponentBindings failed: %Rhrc\n", hr)); + return TRUE; +} + +/** Callback for SetupDiSetDeviceInstallParams used by + * vboxNetCfgWinCreateHostOnlyNetworkInterface */ +static UINT WINAPI vboxNetCfgWinPspFileCallback(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2) +{ + switch (Notification) + { + case SPFILENOTIFY_TARGETNEWER: + case SPFILENOTIFY_TARGETEXISTS: + return TRUE; + } + return SetupDefaultQueueCallbackW(Context, Notification, Param1, Param2); +} + + + + +/* The original source of the VBoxNetAdp adapter creation/destruction code has the following copyright: */ +/* + Copyright 2004 by the Massachusetts Institute of Technology + + All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation, and that the name of the Massachusetts + Institute of Technology (M.I.T.) not be used in advertising or publicity + pertaining to distribution of the software without specific, written + prior permission. + + M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + SOFTWARE. +*/ + + +/** + * Use the IShellFolder API to rename the connection. + */ +static HRESULT rename_shellfolder(PCWSTR pwszGuid, PCWSTR pwszNewName) +{ + /* Build the display name in the form "::{GUID}". Do this first in case it overflows. */ + WCHAR wszAdapterGuid[MAX_PATH + 2] = {0}; + ssize_t cwc = RTUtf16Printf(wszAdapterGuid, RT_ELEMENTS(wszAdapterGuid), "::%ls", pwszGuid); + if (cwc < 0) + return E_INVALIDARG; + + /* This is the GUID for the network connections folder. It is constant. + * {7007ACC7-3202-11D1-AAD2-00805FC1270E} */ + const GUID MY_CLSID_NetworkConnections = { + 0x7007ACC7, 0x3202, 0x11D1, { + 0xAA, 0xD2, 0x00, 0x80, 0x5F, 0xC1, 0x27, 0x0E + } + }; + + /* Create an instance of the network connections folder. */ + IShellFolder *pShellFolder = NULL; + HRESULT hr = CoCreateInstance(MY_CLSID_NetworkConnections, NULL, CLSCTX_INPROC_SERVER, IID_IShellFolder, + reinterpret_cast(&pShellFolder)); + if (SUCCEEDED(hr)) + { + /* Parse the display name. */ + LPITEMIDLIST pidl = NULL; + hr = pShellFolder->ParseDisplayName(NULL, NULL, wszAdapterGuid, NULL, &pidl, NULL); + if (SUCCEEDED(hr)) + hr = pShellFolder->SetNameOf(NULL, pidl, pwszNewName, SHGDN_NORMAL, &pidl); + CoTaskMemFree(pidl); + pShellFolder->Release(); + } + return hr; +} + +/** + * Loads a system DLL. + * + * @returns Module handle or NULL + * @param pwszName The DLL name. + */ +static HMODULE loadSystemDll(const wchar_t *pwszName) +{ + WCHAR wszPath[MAX_PATH]; + UINT cwcPath = GetSystemDirectoryW(wszPath, RT_ELEMENTS(wszPath)); + size_t cwcName = RTUtf16Len(pwszName) + 1; + if (cwcPath + 1 + cwcName > RT_ELEMENTS(wszPath)) + return NULL; + + wszPath[cwcPath++] = '\\'; + memcpy(&wszPath[cwcPath], pwszName, cwcName * sizeof(wszPath[0])); + return LoadLibraryW(wszPath); +} + +static bool vboxNetCfgWinDetectStaleConnection(PCWSTR pwszName) +{ + HKEY hKeyAdapters = NULL; + LSTATUS lrc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}", + 0 /*ulOptions*/, KEY_ALL_ACCESS, &hKeyAdapters); + if (lrc != ERROR_SUCCESS) + return false; + + bool fFailureImminent = false; + for (DWORD i = 0; !fFailureImminent; ++i) + { + WCHAR wszAdapterSubKeyName[MAX_PATH]; + DWORD cwcAdapterSubKeyName = MAX_PATH; + lrc = RegEnumKeyEx(hKeyAdapters, i, wszAdapterSubKeyName, &cwcAdapterSubKeyName, NULL, NULL, NULL, NULL); + if (lrc != ERROR_SUCCESS) + break; + + HKEY hKeyAdapter = NULL; + lrc = RegOpenKeyEx(hKeyAdapters, wszAdapterSubKeyName, 0, KEY_ALL_ACCESS, &hKeyAdapter); + if (lrc == ERROR_SUCCESS) + { + HKEY hKeyConnection = NULL; + lrc = RegOpenKeyEx(hKeyAdapter, L"Connection", 0, KEY_ALL_ACCESS, &hKeyConnection); + if (lrc == ERROR_SUCCESS) + { + WCHAR wszCurName[MAX_PATH + 1]; + DWORD cbCurName = sizeof(wszCurName) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + lrc = RegQueryValueEx(hKeyConnection, L"Name", NULL, NULL, (LPBYTE)wszCurName, &cbCurName); + if ( lrc == ERROR_SUCCESS + /** @todo r=bird: The original code didn't do any value type checks, thus allowing all SZ types. */ + && (dwType == REG_SZ || dwType == REG_EXPAND_SZ || dwType == REG_MULTI_SZ)) + { + wszCurName[MAX_PATH] = '\0'; /* returned values doesn't necessarily need to be terminated */ + + if (RTUtf16ICmp(pwszName, pwszName) == 0) + fFailureImminent = true; + } + RegCloseKey(hKeyConnection); + } + RegCloseKey(hKeyAdapter); + } + } + RegCloseKey(hKeyAdapters); + + return fFailureImminent; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRenameConnection(LPWSTR pwszGuid, PCWSTR NewName) +{ + /* + * Before attempting to rename the connection, check if there is a stale + * connection with the same name. We must return ok, so the rest of + * configuration process proceeds normally. + */ + if (vboxNetCfgWinDetectStaleConnection(NewName)) + return S_OK; + + /* First try the IShellFolder interface, which was unimplemented + * for the network connections folder before XP. */ + HRESULT hrc = rename_shellfolder(pwszGuid, NewName); + if (hrc == E_NOTIMPL) + { +/** @todo that code doesn't seem to work! */ + /* The IShellFolder interface is not implemented on this platform. + * Try the (undocumented) HrRenameConnection API in the netshell + * library. */ + CLSID clsid; + hrc = CLSIDFromString((LPOLESTR)pwszGuid, &clsid); + if (FAILED(hrc)) + return E_FAIL; + + HINSTANCE hNetShell = loadSystemDll(L"netshell.dll"); + if (hNetShell == NULL) + return E_FAIL; + + typedef HRESULT (WINAPI *PFNHRRENAMECONNECTION)(const GUID *, PCWSTR); + PFNHRRENAMECONNECTION pfnRenameConnection = (PFNHRRENAMECONNECTION)GetProcAddress(hNetShell, "HrRenameConnection"); + if (pfnRenameConnection != NULL) + hrc = pfnRenameConnection(&clsid, NewName); + else + hrc = E_FAIL; + + FreeLibrary(hNetShell); + } + if (FAILED(hrc)) + return hrc; + return S_OK; +} + + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRemoveHostOnlyNetworkInterface(IN const GUID *pGUID, OUT BSTR *pBstrErrMsg) +{ + HRESULT hrc = S_OK; + com::Bstr bstrError; + + do /* break non-loop */ + { + WCHAR wszPnPInstanceId[512] = {0}; + + /* + * We have to find the device instance ID through a registry search + */ + HKEY hkeyNetwork = NULL; + HKEY hkeyConnection = NULL; + do /* another non-loop for breaking out of */ + { + WCHAR wszGuid[50]; + int cwcGuid = StringFromGUID2(*pGUID, wszGuid, RT_ELEMENTS(wszGuid)); + if (!cwcGuid) + SetErrBreak(("Failed to create a Guid string")); + + WCHAR wszRegLocation[128 + RT_ELEMENTS(wszGuid)]; + RTUtf16Printf(wszRegLocation, RT_ELEMENTS(wszRegLocation), + "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\%ls", wszGuid); + + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszRegLocation, 0, KEY_READ, &hkeyNetwork); + if (lrc != ERROR_SUCCESS || !hkeyNetwork) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u [1]", wszRegLocation, lrc)); + + lrc = RegOpenKeyExW(hkeyNetwork, L"Connection", 0, KEY_READ, &hkeyConnection); + if (lrc != ERROR_SUCCESS || !hkeyConnection) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u [2]", wszRegLocation, lrc)); + + DWORD cbValue = sizeof(wszPnPInstanceId) - sizeof(WCHAR); + DWORD dwType = ~0U; + lrc = RegQueryValueExW(hkeyConnection, L"PnPInstanceID", NULL, &dwType, (LPBYTE)wszPnPInstanceId, &cbValue); + if (lrc != ERROR_SUCCESS || dwType != REG_SZ) + SetErrBreak(("Host interface network is not found in registry (%S): lrc=%u, dwType=%u [3]", + wszRegLocation, lrc, dwType)); + } while (0); + + if (hkeyConnection) + RegCloseKey(hkeyConnection); + if (hkeyNetwork) + RegCloseKey(hkeyNetwork); + if (FAILED(hrc)) + break; + + /* + * Now we are going to enumerate all network devices and + * wait until we encounter the right device instance ID + */ + HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE; + do /* break-only, not-a-loop */ + { + BOOL ok; + + /* initialize the structure size */ + SP_DEVINFO_DATA DeviceInfoData = { sizeof(DeviceInfoData) }; + + /* copy the net class GUID */ + GUID netGuid; + memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET)); + + /* return a device info set contains all installed devices of the Net class */ + hDeviceInfo = SetupDiGetClassDevs(&netGuid, NULL, NULL, DIGCF_PRESENT); + if (hDeviceInfo == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiGetClassDevs failed (0x%08X)", GetLastError())); + + /* Enumerate the driver info list. */ + bool fFound = false; + for (DWORD index = 0; !fFound; index++) + { + if (!SetupDiEnumDeviceInfo(hDeviceInfo, index, &DeviceInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + /* try to get the hardware ID registry property */ + DWORD cbValue = 0; + if (SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, + &DeviceInfoData, + SPDRP_HARDWAREID, + NULL, + NULL, + 0, + &cbValue)) + continue; /* Something is wrong. This shouldn't have worked with a NULL buffer! */ + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + continue; + + WCHAR *pwszzDeviceHwId = (WCHAR *)RTMemAllocZ(cbValue + sizeof(WCHAR) * 2); + if (!pwszzDeviceHwId) + break; + if (SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, + &DeviceInfoData, + SPDRP_HARDWAREID, + NULL, + (PBYTE)pwszzDeviceHwId, + cbValue, + &cbValue)) + { + /* search the string list. */ + for (WCHAR *pwszCurHwId = pwszzDeviceHwId; + (uintptr_t)pwszCurHwId - (uintptr_t)pwszzDeviceHwId < cbValue && *pwszCurHwId != L'\0'; + pwszCurHwId += RTUtf16Len(pwszCurHwId) + 1) + if (RTUtf16ICmp(DRIVERHWID, pwszCurHwId) == 0) + { + /* get the device instance ID */ + WCHAR wszDevId[MAX_DEVICE_ID_LEN]; + if (CM_Get_Device_IDW(DeviceInfoData.DevInst, wszDevId, MAX_DEVICE_ID_LEN, 0) == CR_SUCCESS) + { + /* compare to what we determined before */ + if (RTUtf16Cmp(wszDevId, wszPnPInstanceId) == 0) + { + fFound = true; + break; + } + } + } + } + RTMemFree(pwszzDeviceHwId); + } + + if (!fFound) + SetErrBreak(("Host Interface Network driver not found (0x%08X)", GetLastError())); + + ok = SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData); + if (!ok) + SetErrBreak(("SetupDiSetSelectedDevice failed (0x%08X)", GetLastError())); + + ok = SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData); + if (!ok) + SetErrBreak(("SetupDiCallClassInstaller (DIF_REMOVE) failed (0x%08X)", GetLastError())); + } while (0); + + /* clean up the device info set */ + if (hDeviceInfo != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList (hDeviceInfo); + } while (0); + + if (pBstrErrMsg) + { + *pBstrErrMsg = NULL; + if (bstrError.isNotEmpty()) + bstrError.detachToEx(pBstrErrMsg); + } + return hrc; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinUpdateHostOnlyNetworkInterface(LPCWSTR pcsxwInf, BOOL *pfRebootRequired, LPCWSTR pcsxwId) +{ + return VBoxDrvCfgDrvUpdate(pcsxwId, pcsxwInf, pfRebootRequired); +} + +static const char *vboxNetCfgWinGetStateText(DWORD dwState) +{ + switch (dwState) + { + case SERVICE_STOPPED: return "is not running"; + case SERVICE_STOP_PENDING: return "is stopping"; + case SERVICE_CONTINUE_PENDING: return "continue is pending"; + case SERVICE_PAUSE_PENDING: return "pause is pending"; + case SERVICE_PAUSED: return "is paused"; + case SERVICE_RUNNING: return "is running"; + case SERVICE_START_PENDING: return "is starting"; + } + return "state is invalid"; +} + +static DWORD vboxNetCfgWinGetNetSetupState(SC_HANDLE hService) +{ + SERVICE_STATUS status; + status.dwCurrentState = SERVICE_RUNNING; + if (hService) { + if (QueryServiceStatus(hService, &status)) + NonStandardLogFlow(("NetSetupSvc %s\n", vboxNetCfgWinGetStateText(status.dwCurrentState))); + else + NonStandardLogFlow(("QueryServiceStatus failed (0x%x)\n", GetLastError())); + } + return status.dwCurrentState; +} + +DECLINLINE(bool) vboxNetCfgWinIsNetSetupRunning(SC_HANDLE hService) +{ + return vboxNetCfgWinGetNetSetupState(hService) == SERVICE_RUNNING; +} + +DECLINLINE(bool) vboxNetCfgWinIsNetSetupStopped(SC_HANDLE hService) +{ + return vboxNetCfgWinGetNetSetupState(hService) == SERVICE_STOPPED; +} + +typedef struct +{ + BSTR bstrName; + GUID *pGuid; + HRESULT hr; +} RENAMING_CONTEXT; + +static BOOL vboxNetCfgWinRenameHostOnlyNetworkInterface(IN INetCfg *pNc, IN INetCfgComponent *pNcc, PVOID pContext) +{ + RT_NOREF1(pNc); + RENAMING_CONTEXT *pParams = (RENAMING_CONTEXT *)pContext; + + GUID guid; + pParams->hr = pNcc->GetInstanceGuid(&guid); + if ( pParams->hr == S_OK && guid == *pParams->pGuid) + { + /* Located our component, rename it */ + pParams->hr = pNcc->SetDisplayName(pParams->bstrName); + return FALSE; + } + return TRUE; +} + +/** + * Enumerate all host-only adapters collecting their names into a set, then + * come up with the next available name by taking the first unoccupied index. + */ +static HRESULT vboxNetCfgWinNextAvailableDevName(com::Bstr *pbstrName) +{ + SP_DEVINFO_DATA DeviceInfoData = { sizeof(SP_DEVINFO_DATA) }; + HDEVINFO hDeviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT); + if (hDeviceInfoSet == INVALID_HANDLE_VALUE) + return HRESULT_FROM_WIN32(GetLastError()); + + typedef struct VBOXDEVNAMEENTRY + { + RTLISTNODE ListEntry; + WCHAR wszDevName[64]; + WCHAR wcZeroParanoia; + } VBOXDEVNAMEENTRY; + +#if 0 + /* + * Build a list of names starting with HOSTONLY_ADAPTER_NAME_WSZ belonging to our device. + */ + RTLISTANCHOR Head; /* VBOXDEVNAMEENTRY */ + RTListInit(&Head); + HRESULT hrc = S_OK; +#else + /* + * Build a bitmap of in-use index values of devices starting with HOSTONLY_ADAPTER_NAME_WSZ. + * Reserving 0 for one w/o a suffix, and marking 1 as unusable. + */ + uint64_t bmIndexes[_32K / 64]; /* 4KB - 32767 device should be sufficient. */ + RT_ZERO(bmIndexes); + ASMBitSet(&bmIndexes, 1); +#endif + for (DWORD i = 0; SetupDiEnumDeviceInfo(hDeviceInfoSet, i, &DeviceInfoData); ++i) + { + /* Should be more than enough for both our device id and our device name, we do not care about the rest */ + VBOXDEVNAMEENTRY Entry = { { 0, 0 }, L"", 0 }; /* (initialize it to avoid the wrath of asan) */ + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_HARDWAREID, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL)) + continue; + + /* Ignore everything except our host-only adapters */ + if (RTUtf16ICmp(Entry.wszDevName, DRIVERHWID) == 0) + { + if ( SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_FRIENDLYNAME, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL) + || SetupDiGetDeviceRegistryPropertyW(hDeviceInfoSet, &DeviceInfoData, SPDRP_DEVICEDESC, + NULL, (PBYTE)Entry.wszDevName, sizeof(Entry.wszDevName), NULL)) + { + /* We can ignore any host-only adapter with a non-standard name. */ + if (RTUtf16NICmp(Entry.wszDevName, HOSTONLY_ADAPTER_NAME_WSZ, + RT_ELEMENTS(HOSTONLY_ADAPTER_NAME_WSZ) - 1) == 0) + { +#if 0 + VBOXDEVNAMEENTRY *pEntry = (VBOXDEVNAMEENTRY *)RTMemDup(&Entry, sizeof(Entry)); + if (pEntry) + RTListAppend(&Head, &pEntry->ListEntry); + else + { + hrc = E_OUTOFMEMORY; + break; + } +#else + WCHAR const *pwc = &Entry.wszDevName[RT_ELEMENTS(HOSTONLY_ADAPTER_NAME_WSZ) - 1]; + + /* skip leading space */ + WCHAR wc = *pwc; + while (wc == L' ' || wc == L'\t' || wc == L'\n' || wc == L'\r') + wc = *++pwc; + + /* If end of string, use index 0. */ + if (wc == L'\0') + ASMBitSet(bmIndexes, 0); + + /* Hash and digit? */ + else if (wc == L'#') + { + wc = *++pwc; + while (wc == L' ' || wc == L'\t' || wc == L'\n' || wc == L'\r') /* just in case */ + wc = *++pwc; + if (wc >= L'0' && wc <= L'9') + { + /* Convert what we can to a number and mark it as allocated in the bitmap. */ + uint64_t uIndex = wc - L'0'; + while ((wc = *++pwc) >= L'0' && wc <= L'9') + uIndex = uIndex * 10 + wc - L'0'; + if (uIndex < sizeof(bmIndexes) * 8 && uIndex > 0) + ASMBitSet(bmIndexes, (int32_t)uIndex); + } + } +#endif + } + } + } + } +#if 0 + if (SUCCEEDED(hrc)) + { + /* + * First try a name w/o an index, then try index #2 and up. + * + * Note! We have to use ASCII/UTF-8 strings here as Bstr will confuse WCHAR + * with BSTR/OLECHAR strings and use SysAllocString to duplicate it . + */ + char szName[sizeof(HOSTONLY_ADAPTER_NAME_SZ " #4294967296") + 32] = HOSTONLY_ADAPTER_NAME_SZ; + size_t const cchBase = sizeof(HOSTONLY_ADAPTER_NAME_SZ) - 1; + for (DWORD idx = 2;; idx++) + { + bool fFound = false; + VBOXDEVNAMEENTRY *pCur; + RTListForEach(&Head, pCur, VBOXDEVNAMEENTRY, ListEntry) + { + fFound = RTUtf16ICmpAscii(pCur->wszDevName, szName) == 0; + if (fFound) + { + hrc = pbstrName->assignEx(szName); + break; + } + } + if (fFound) + break; + RTStrPrintf(&szName[cchBase], sizeof(szName) - cchBase, " #%u", idx); + } + } + + VBOXDEVNAMEENTRY *pFirst; + while ((pFirst = RTListRemoveFirst(&Head, VBOXDEVNAMEENTRY, ListEntry)) != NULL) + RTMemFree(pFirst); + +#else + /* + * Find an unused index value and format the corresponding name. + */ + HRESULT hrc; + int32_t iBit = ASMBitFirstClear(bmIndexes, sizeof(bmIndexes) * 8); + if (iBit >= 0) + { + if (iBit == 0) + hrc = pbstrName->assignEx(HOSTONLY_ADAPTER_NAME_SZ); /* Not _WSZ! */ + else + hrc = pbstrName->printfNoThrow(HOSTONLY_ADAPTER_NAME_SZ " #%u", iBit); + } + else + { + NonStandardLogFlow(("vboxNetCfgWinNextAvailableDevName: no unused index in the first 32K!\n")); + hrc = E_FAIL; + } +#endif + + if (hDeviceInfoSet) + SetupDiDestroyDeviceInfoList(hDeviceInfoSet); + return hrc; +} + +static HRESULT vboxNetCfgWinCreateHostOnlyNetworkInterface(IN LPCWSTR pwszInfPath, IN bool fIsInfPathFile, + IN BSTR pBstrDesiredName, + OUT GUID *pGuid, OUT BSTR *pBstrName, OUT BSTR *pBstrErrMsg) +{ + com::Bstr bstrError; + + /* Determine the interface name. We make a copy of the input here for + renaming reasons, see futher down. */ + com::Bstr bstrNewInterfaceName; + HRESULT hrc; + if (SysStringLen(pBstrDesiredName) != 0) + hrc = bstrNewInterfaceName.assignEx(pBstrDesiredName); + else + { + hrc = vboxNetCfgWinNextAvailableDevName(&bstrNewInterfaceName); + if (FAILED(hrc)) + NonStandardLogFlow(("vboxNetCfgWinNextAvailableDevName failed with 0x%x\n", hrc)); + } + if (FAILED(hrc)) + return hrc; + + WCHAR wszCfgGuidString[50] = {0}; + WCHAR wszDevName[256 + 1] = {0}; + SP_DEVINFO_DATA DeviceInfoData = { sizeof(DeviceInfoData) }; + HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE; + PVOID pQueueCallbackContext = NULL; + BOOL fRegistered = FALSE; + BOOL destroyList = FALSE; + HKEY hkey = (HKEY)INVALID_HANDLE_VALUE; + LSTATUS lrcRet = ERROR_SUCCESS; /* the */ + + do /* non-loop, for breaking. */ + { + /* copy the net class GUID */ + GUID netGuid; + memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET)); + + /* Create an empty device info set associated with the net class GUID: */ + hDeviceInfo = SetupDiCreateDeviceInfoList(&netGuid, NULL); + if (hDeviceInfo == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiCreateDeviceInfoList failed (%Rwc)", GetLastError())); + + /* Translate the GUID to a class name: */ + WCHAR wszClassName[MAX_PATH]; + if (!SetupDiClassNameFromGuid(&netGuid, wszClassName, MAX_PATH, NULL)) + SetErrBreak(("SetupDiClassNameFromGuid failed (%Rwc)", GetLastError())); + + /* Create a device info element and add the new device instance key to registry: */ + if (!SetupDiCreateDeviceInfo(hDeviceInfo, wszClassName, &netGuid, NULL, NULL, DICD_GENERATE_ID, &DeviceInfoData)) + SetErrBreak(("SetupDiCreateDeviceInfo failed (%Rwc)", GetLastError())); + + /* Select the newly created device info to be the currently selected member: */ + if (!SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiSetSelectedDevice failed (%Rwc)", GetLastError())); + + SP_DEVINSTALL_PARAMS DeviceInstallParams; + if (pwszInfPath) + { + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + memset(DeviceInstallParams.DriverPath, 0, sizeof(DeviceInstallParams.DriverPath)); + size_t pathLenght = wcslen(pwszInfPath) + 1/* null terminator */; + if (pathLenght < sizeof(DeviceInstallParams.DriverPath)/sizeof(DeviceInstallParams.DriverPath[0])) + { + memcpy(DeviceInstallParams.DriverPath, pwszInfPath, pathLenght*sizeof(DeviceInstallParams.DriverPath[0])); + + if (fIsInfPathFile) + DeviceInstallParams.Flags |= DI_ENUMSINGLEINF; + + if (!SetupDiSetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + NonStandardLogFlow(("SetupDiSetDeviceInstallParams failed (%Rwc)\n", GetLastError())); + break; + } + } + else + { + NonStandardLogFlow(("SetupDiSetDeviceInstallParams faileed: INF path is too long\n")); + break; + } + } + else + NonStandardLogFlow(("SetupDiGetDeviceInstallParams failed (%Rwc)\n", GetLastError())); + } + + /* build a list of class drivers */ + if (!SetupDiBuildDriverInfoList(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER)) + SetErrBreak(("SetupDiBuildDriverInfoList failed (%Rwc)", GetLastError())); + + destroyList = TRUE; + + /* + * Enumerate the driver info list. + */ + /* For our purposes, 2k buffer is more than enough to obtain the + hardware ID of the VBoxNetAdp driver. */ /** @todo r=bird: The buffer isn't 2KB, it's 8KB, but whatever. */ + DWORD detailBuf[2048]; + SP_DRVINFO_DATA DriverInfoData = { sizeof(DriverInfoData) }; + bool fFound = false; + for (DWORD index = 0; !fFound; index++) + { + /* If the function fails with last error set to ERROR_NO_MORE_ITEMS, + then we have reached the end of the list. Otherwise there was + something wrong with this particular driver. */ + if (!SetupDiEnumDriverInfo(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER, index, &DriverInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + /* if we successfully find the hardware ID and it turns out to + * be the one for the loopback driver, then we are done. */ + PSP_DRVINFO_DETAIL_DATA_W pDriverInfoDetail = (PSP_DRVINFO_DETAIL_DATA_W)detailBuf; + pDriverInfoDetail->cbSize = sizeof(SP_DRVINFO_DETAIL_DATA); + DWORD cbValue = 0; + if (SetupDiGetDriverInfoDetailW(hDeviceInfo, + &DeviceInfoData, + &DriverInfoData, + pDriverInfoDetail, + sizeof(detailBuf) - sizeof(detailBuf[0]), + &cbValue)) + { + /* Sure that the HardwareID string list is properly zero terminated (paranoia). */ + detailBuf[RT_ELEMENTS(detailBuf) - 1] = 0; AssertCompile(sizeof(detailBuf[0]) == sizeof(WCHAR) * 2); + + /* pDriverInfoDetail->HardwareID is a MULTISZ string. Go through the whole + list and see if there is a match somewhere: */ + for (WCHAR *pwszCurHwId = pDriverInfoDetail->HardwareID; + (uintptr_t)pwszCurHwId - (uintptr_t)pDriverInfoDetail < cbValue && *pwszCurHwId != L'\0'; + pwszCurHwId += RTUtf16Len(pwszCurHwId) + 1) + if (RTUtf16ICmp(DRIVERHWID, pwszCurHwId) == 0) + { + fFound = true; + break; + } + } + } + + if (!fFound) + SetErrBreak(("Could not find Host Interface Networking driver! Please reinstall")); + + /* set the loopback driver to be the currently selected */ + if (!SetupDiSetSelectedDriver(hDeviceInfo, &DeviceInfoData, &DriverInfoData)) + SetErrBreak(("SetupDiSetSelectedDriver failed (%u)", GetLastError())); + + /* register the phantom device to prepare for install */ + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller failed (%u)", GetLastError())); + + /* registered, but remove if errors occur in the following code */ + fRegistered = TRUE; + + /* ask the installer if we can install the device */ + if (!SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, hDeviceInfo, &DeviceInfoData)) + { + if (GetLastError() != ERROR_DI_DO_DEFAULT) + SetErrBreak(("SetupDiCallClassInstaller (DIF_ALLOW_INSTALL) failed (%u)", GetLastError())); + /* that's fine */ + } + + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + pQueueCallbackContext = SetupInitDefaultQueueCallback(NULL); + if (pQueueCallbackContext) + { + DeviceInstallParams.InstallMsgHandlerContext = pQueueCallbackContext; + DeviceInstallParams.InstallMsgHandler = (PSP_FILE_CALLBACK)vboxNetCfgWinPspFileCallback; + if (!SetupDiSetDeviceInstallParamsW(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupDiSetDeviceInstallParamsW failed, winEr (%d)\n", winEr)); + Assert(0); + } + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupInitDefaultQueueCallback failed, winEr (%d)\n", winEr)); + } + } + else + { + DWORD winEr = GetLastError(); + NonStandardLogFlow(("SetupDiGetDeviceInstallParams failed, winEr (%d)\n", winEr)); + } + + /* install the files first */ + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLDEVICEFILES) failed (%Rwc)", GetLastError())); + + /* get the device install parameters and disable filecopy */ + DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS); + if (SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + { + DeviceInstallParams.Flags |= DI_NOFILECOPY; + if (!SetupDiSetDeviceInstallParamsW(hDeviceInfo, &DeviceInfoData, &DeviceInstallParams)) + SetErrBreak(("SetupDiSetDeviceInstallParamsW failed (%Rwc)", GetLastError())); + } + /** @todo r=bird: Why isn't SetupDiGetDeviceInstallParams failure fatal here? */ + + /* + * Register any device-specific co-installers for this device, + */ + if (!SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_REGISTER_COINSTALLERS) failed (%Rwc)", GetLastError())); + + /* + * install any installer-specified interfaces. + * and then do the real install + */ + if (!SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLINTERFACES) failed (%Rwc)", GetLastError())); + + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, hDeviceInfo, &DeviceInfoData)) + SetErrBreak(("SetupDiCallClassInstaller (DIF_INSTALLDEVICE) failed (%Rwc)", GetLastError())); + + /* + * Query the instance ID; on Windows 10, the registry key may take a short + * while to appear. Microsoft recommends waiting for up to 5 seconds, but + * we want to be on the safe side, so let's wait for 20 seconds. Waiting + * longer is harmful as network setup service will shut down after a period + * of inactivity. + */ + for (int retries = 0; retries < 2 * 20; ++retries) + { + Sleep(500); /* half second */ + + /* Figure out NetCfgInstanceId */ + hkey = SetupDiOpenDevRegKey(hDeviceInfo, &DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_READ); + if (hkey == INVALID_HANDLE_VALUE) + break; + + DWORD cbSize = sizeof(wszCfgGuidString); + DWORD dwValueType = 0; + lrcRet = RegQueryValueExW(hkey, L"NetCfgInstanceId", NULL, &dwValueType, (LPBYTE)wszCfgGuidString, &cbSize); + /* As long as the return code is FILE_NOT_FOUND, sleep and retry. */ + if (lrcRet != ERROR_FILE_NOT_FOUND) + break; + + RegCloseKey(hkey); + hkey = (HKEY)INVALID_HANDLE_VALUE; + } + + if (lrcRet == ERROR_FILE_NOT_FOUND) + { + hrc = E_ABORT; + break; + } + + /* + * We need to check 'hkey' after we check 'lrcRet' to distinguish the case + * of failed SetupDiOpenDevRegKey from the case when we timed out. + */ + if (hkey == INVALID_HANDLE_VALUE) + SetErrBreak(("SetupDiOpenDevRegKey failed (%Rwc)", GetLastError())); + + if (lrcRet != ERROR_SUCCESS) + SetErrBreak(("Querying NetCfgInstanceId failed (%Rwc)", lrcRet)); + + NET_LUID luid; + HRESULT hrcSMRes = vboxNetCfgWinGetInterfaceLUID(hkey, &luid); + + /* Close the key as soon as possible. See @bugref{7973}. */ + RegCloseKey(hkey); + hkey = (HKEY)INVALID_HANDLE_VALUE; + + if (FAILED(hrcSMRes)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("vboxNetCfgWinCreateHostOnlyNetworkInterface: Warning! " + "vboxNetCfgWinGetInterfaceLUID failed, default metric for new interface will not be set: %Rhrc\n", + hrcSMRes)); + } + else + { + /* + * Set default metric value of interface to fix multicast issue + * See @bugref{6379} for details. + */ + hrcSMRes = vboxNetCfgWinSetupMetric(&luid); + if (FAILED(hrcSMRes)) + { + /* + * The setting of Metric is not very important functionality, + * So we will not break installation process due to this error. + */ + NonStandardLogFlow(("vboxNetCfgWinCreateHostOnlyNetworkInterface: Warning! " + "vboxNetCfgWinSetupMetric failed, default metric for new interface will not be set: %Rhrc\n", + hrcSMRes)); + } + } + + /* + * We need to query the device name after we have succeeded in querying its + * instance ID to avoid similar waiting-and-retrying loop (see @bugref{7973}). + */ + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &DeviceInfoData, + SPDRP_FRIENDLYNAME , /* IN DWORD Property,*/ + NULL, /*OUT PDWORD PropertyRegDataType, OPTIONAL*/ + (PBYTE)wszDevName, /*OUT PBYTE PropertyBuffer,*/ + sizeof(wszDevName) - sizeof(WCHAR), /* IN DWORD PropertyBufferSize,*/ + NULL /*OUT PDWORD RequiredSize OPTIONAL*/ )) + { + DWORD dwErr = GetLastError(); + if (dwErr != ERROR_INVALID_DATA) + SetErrBreak(("SetupDiGetDeviceRegistryProperty failed (%Rwc)", dwErr)); + + RT_ZERO(wszDevName); + if (!SetupDiGetDeviceRegistryPropertyW(hDeviceInfo, &DeviceInfoData, + SPDRP_DEVICEDESC, /* IN DWORD Property,*/ + NULL, /*OUT PDWORD PropertyRegDataType, OPTIONAL*/ + (PBYTE)wszDevName, /*OUT PBYTE PropertyBuffer,*/ + sizeof(wszDevName) - sizeof(WCHAR), /* IN DWORD PropertyBufferSize,*/ + NULL /*OUT PDWORD RequiredSize OPTIONAL*/ )) + SetErrBreak(("SetupDiGetDeviceRegistryProperty failed (%Rwc)", GetLastError())); + } + + /* No need to rename the device if the names match. */ + if (RTUtf16Cmp(bstrNewInterfaceName.raw(), wszDevName) == 0) + bstrNewInterfaceName.setNull(); + +#ifdef VBOXNETCFG_DELAYEDRENAME + /* Re-use wszDevName for device instance id retrieval. */ + DWORD cwcReturned = 0; + RT_ZERO(wszDevName); + if (!SetupDiGetDeviceInstanceIdW(hDeviceInfo, &DeviceInfoData, wszDevName, RT_ELEMENTS(wszDevName) - 1, &cwcReturned)) + SetErrBreak(("SetupDiGetDeviceInstanceId failed (%Rwc)", GetLastError())); +#endif /* VBOXNETCFG_DELAYEDRENAME */ + } while (0); + + /* + * Cleanup. + */ + if (hkey != INVALID_HANDLE_VALUE) + RegCloseKey(hkey); + + if (pQueueCallbackContext) + SetupTermDefaultQueueCallback(pQueueCallbackContext); + + if (hDeviceInfo != INVALID_HANDLE_VALUE) + { + /* an error has occurred, but the device is registered, we must remove it */ + if (lrcRet != ERROR_SUCCESS && fRegistered) + SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData); + + SetupDiDeleteDeviceInfo(hDeviceInfo, &DeviceInfoData); + + /* destroy the driver info list */ +#if 0 + /* I've remove this, as I was consistently getting crashes in + * SetupDiDestroyDeviceInfoList otherwise during MSI installation + * (W10 build 19044, VBox r153431 + nocrt changes). + * + * Some details from windbg: + * + * (175e8.1596c): Access violation - code c0000005 (first chance) + * First chance exceptions are reported before any exception handling. + * This exception may be expected and handled. + * SETUPAPI!DereferenceClassDriverList+0x4e: + * 00007ffa`83e2a42a 834008ff add dword ptr [rax+8],0FFFFFFFFh ds:00000000`00000008=???????? + * 0:006> k + * # Child-SP RetAddr Call Site + * 00 0000007e`ccd7c070 00007ffa`83e2a287 SETUPAPI!DereferenceClassDriverList+0x4e + * 01 0000007e`ccd7c0a0 00007ffa`56b96bd3 SETUPAPI!SetupDiDestroyDriverInfoList+0x117 + * 02 0000007e`ccd7c0f0 00007ffa`56b982a3 MSIF170!vboxNetCfgWinCreateHostOnlyNetworkInterface+0xb23 [E:\vbox\svn\trunk\src\VBox\HostDrivers\VBoxNetFlt\win\cfg\VBoxNetCfg.cpp @ 3378] + * 03 0000007e`ccd7ef10 00007ffa`56b92cb8 MSIF170!VBoxNetCfgWinCreateHostOnlyNetworkInterface+0x53 [E:\vbox\svn\trunk\src\VBox\HostDrivers\VBoxNetFlt\win\cfg\VBoxNetCfg.cpp @ 3479] + * 04 0000007e`ccd7efc0 00007ffa`610f59d3 MSIF170!_createHostOnlyInterface+0x218 [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1453] + * 05 0000007e`ccd7f260 00007ffa`610d80ac msi!CallCustomDllEntrypoint+0x2b + * 06 0000007e`ccd7f2d0 00007ffa`84567034 msi!CMsiCustomAction::CustomActionThread+0x34c + * 07 0000007e`ccd7f8c0 00007ffa`849a2651 KERNEL32!BaseThreadInitThunk+0x14 + * 08 0000007e`ccd7f8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21 + * 0:006> r + * rax=0000000000000000 rbx=0000025f4f03af90 rcx=00007ffa83f23b40 + * rdx=0000025f4f03af90 rsi=0000025f5108be50 rdi=0000025f4f066f10 + * rip=00007ffa83e2a42a rsp=0000007eccd7c070 rbp=00007ffa83f23000 + * r8=0000007eccd7c0a8 r9=0000007eccd7c1f0 r10=0000000000000000 + * r11=0000007eccd7c020 r12=0000007eccd7c3d0 r13=00007ffa83f23000 + * r14=0000000000000000 r15=0000025f4f03af90 + * iopl=0 nv up ei pl nz na po nc + * cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206 + * SETUPAPI!DereferenceClassDriverList+0x4e: + * 00007ffa`83e2a42a 834008ff add dword ptr [rax+8],0FFFFFFFFh ds:00000000`00000008=???????? + * + */ + if (destroyList) + SetupDiDestroyDriverInfoList(hDeviceInfo, &DeviceInfoData, SPDIT_CLASSDRIVER); +#else + RT_NOREF(destroyList); +#endif + + /* clean up the device info set */ + SetupDiDestroyDeviceInfoList(hDeviceInfo); + } + + /* + * Return the network connection GUID on success. + */ + if (SUCCEEDED(hrc)) + { + /** @todo r=bird: Some please explain this mess. It's not just returning a + * GUID here, it's a bit more than that. */ + RENAMING_CONTEXT context; + context.hr = E_FAIL; + + if (pGuid) + { + hrc = CLSIDFromString(wszCfgGuidString, (LPCLSID)pGuid); + if (FAILED(hrc)) + NonStandardLogFlow(("CLSIDFromString failed, hrc (0x%x)\n", hrc)); + } + + INetCfg *pNetCfg = NULL; + LPWSTR pwszApp = NULL; + HRESULT hrc2 = VBoxNetCfgWinQueryINetCfg(&pNetCfg, TRUE, L"VirtualBox Host-Only Creation", + 30 * 1000, /* on Vista we often get 6to4svc.dll holding the lock, wait for 30 sec. */ + /** @todo special handling for 6to4svc.dll ???, i.e. several retrieves */ + &pwszApp); + if (hrc2 == S_OK) + { + if (bstrNewInterfaceName.isNotEmpty()) + { + /* The assigned name does not match the desired one, rename the device */ + context.bstrName = bstrNewInterfaceName.raw(); + context.pGuid = pGuid; + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NET, + vboxNetCfgWinRenameHostOnlyNetworkInterface, &context); + } + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETSERVICE, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETTRANS, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = vboxNetCfgWinEnumNetCfgComponents(pNetCfg, &GUID_DEVCLASS_NETCLIENT, + vboxNetCfgWinAdjustHostOnlyNetworkInterfacePriority, pGuid); + if (SUCCEEDED(hrc2)) + hrc2 = pNetCfg->Apply(); + else + NonStandardLogFlow(("Enumeration failed, hrc2=%Rhrc\n", hrc2)); + + VBoxNetCfgWinReleaseINetCfg(pNetCfg, TRUE); + } + else if (hrc2 == NETCFG_E_NO_WRITE_LOCK && pwszApp) + { + NonStandardLogFlow(("Application '%ls' is holding the lock, failed\n", pwszApp)); + CoTaskMemFree(pwszApp); + pwszApp = NULL; + } + else + NonStandardLogFlow(("VBoxNetCfgWinQueryINetCfg failed, hrc2=%Rhrc\n", hrc2)); + +#ifndef VBOXNETCFG_DELAYEDRENAME + /* If the device has been successfully renamed, replace the name now. */ + if (SUCCEEDED(hrc2) && SUCCEEDED(context.hr)) + RTUtf16Copy(wszDevName, RT_ELEMENTS(wszDevName), pBstrDesiredName); + + WCHAR wszConnectionName[128]; + hrc2 = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszConnectionName, RT_ELEMENTS(wszConnectionName), NULL); + if (SUCCEEDED(hrc2)) + hrc2 = VBoxNetCfgWinRenameConnection(wszCfgGuidString, wszConnectionName); +#endif + + /* + * Now, return the network connection GUID/name. + */ + if (pBstrName) + { + *pBstrName = SysAllocString((const OLECHAR *)wszDevName); + if (!*pBstrName) + { + NonStandardLogFlow(("SysAllocString failed\n")); + hrc = E_OUTOFMEMORY; + } + } + } + + if (pBstrErrMsg) + { + *pBstrErrMsg = NULL; + if (bstrError.isNotEmpty()) + bstrError.detachToEx(pBstrErrMsg); + } + return hrc; +} + +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinCreateHostOnlyNetworkInterface(IN LPCWSTR pwszInfPath, IN bool fIsInfPathFile, + IN BSTR pwszDesiredName, OUT GUID *pGuid, + OUT BSTR *pBstrName, OUT BSTR *pBstrErrMsg) +{ + HRESULT hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + if (hrc == E_ABORT) + { + NonStandardLogFlow(("Timed out while waiting for NetCfgInstanceId, try again immediately...\n")); + + /* + * This is the first time we fail to obtain NetCfgInstanceId, let us + * retry it once. It is needed to handle the situation when network + * setup fails to recognize the arrival of our device node while it + * is busy removing another host-only interface, and it gets stuck + * with no matching network interface created for our device node. + * See @bugref{7973} for details. + */ + hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + if (hrc == E_ABORT) + { + NonStandardLogFlow(("Timed out again while waiting for NetCfgInstanceId, try again after a while...\n")); + + /* + * This is the second time we fail to obtain NetCfgInstanceId, let us + * retry it once more. This time we wait to network setup service + * to go down before retrying. Hopefully it will resolve all error + * conditions. See @bugref{7973} for details. + */ + SC_HANDLE hSCM = OpenSCManagerW(NULL, NULL, GENERIC_READ); + if (hSCM) + { + SC_HANDLE hService = OpenServiceW(hSCM, L"NetSetupSvc", GENERIC_READ); + if (hService) + { + for (int retries = 0; retries < 60 && !vboxNetCfgWinIsNetSetupStopped(hService); ++retries) + Sleep(1000); + CloseServiceHandle(hService); + hrc = vboxNetCfgWinCreateHostOnlyNetworkInterface(pwszInfPath, fIsInfPathFile, pwszDesiredName, + pGuid, pBstrName, pBstrErrMsg); + } + else + NonStandardLogFlow(("OpenService failed (%Rwc)\n", GetLastError())); + CloseServiceHandle(hSCM); + } + else + NonStandardLogFlow(("OpenSCManager failed (%Rwc)", GetLastError())); + + /* Give up and report the error. */ + if (hrc == E_ABORT) + { + if (pBstrErrMsg) + { + com::Bstr bstrError; + bstrError.printfNoThrow("Querying NetCfgInstanceId failed (ERROR_FILE_NOT_FOUND)"); + bstrError.detachToEx(pBstrErrMsg); + } + hrc = E_FAIL; + } + } + } + return hrc; +} + +static HRESULT vboxNetCfgWinGetLoopbackMetric(OUT DWORD *Metric) +{ + Assert(g_pfnInitializeIpInterfaceEntry != NULL); + Assert(g_pfnGetIpInterfaceEntry != NULL); + + MIB_IPINTERFACE_ROW row; + RT_ZERO(row); /* paranoia */ + g_pfnInitializeIpInterfaceEntry(&row); + + row.Family = AF_INET; + row.InterfaceLuid.Info.IfType = IF_TYPE_SOFTWARE_LOOPBACK; + + NETIO_STATUS dwErr = g_pfnGetIpInterfaceEntry(&row); + if (dwErr == NO_ERROR) + { + *Metric = row.Metric; + return S_OK; + } + return HRESULT_FROM_WIN32(dwErr); +} + +static HRESULT vboxNetCfgWinSetInterfaceMetric(IN NET_LUID *pInterfaceLuid, IN DWORD metric) +{ + Assert(g_pfnInitializeIpInterfaceEntry != NULL); + Assert(g_pfnSetIpInterfaceEntry != NULL); + + MIB_IPINTERFACE_ROW newRow; + RT_ZERO(newRow); /* paranoia */ + g_pfnInitializeIpInterfaceEntry(&newRow); + + // identificate the interface to change + newRow.InterfaceLuid = *pInterfaceLuid; + newRow.Family = AF_INET; + + // changed settings + newRow.UseAutomaticMetric = false; + newRow.Metric = metric; + + // change settings + NETIO_STATUS dwErr = g_pfnSetIpInterfaceEntry(&newRow); + if (dwErr == NO_ERROR) + return S_OK; + return HRESULT_FROM_WIN32(dwErr); +} + +static HRESULT vboxNetCfgWinSetupMetric(IN NET_LUID* pLuid) +{ + HRESULT hrc = E_FAIL; + HMODULE hmod = loadSystemDll(L"Iphlpapi.dll"); + if (hmod) + { + g_pfnInitializeIpInterfaceEntry = (PFNINITIALIZEIPINTERFACEENTRY)GetProcAddress(hmod, "InitializeIpInterfaceEntry"); + g_pfnGetIpInterfaceEntry = (PFNGETIPINTERFACEENTRY) GetProcAddress(hmod, "GetIpInterfaceEntry"); + g_pfnSetIpInterfaceEntry = (PFNSETIPINTERFACEENTRY) GetProcAddress(hmod, "SetIpInterfaceEntry"); + Assert(g_pfnInitializeIpInterfaceEntry); + Assert(g_pfnGetIpInterfaceEntry); + Assert(g_pfnSetIpInterfaceEntry); + + if ( g_pfnInitializeIpInterfaceEntry + && g_pfnGetIpInterfaceEntry + && g_pfnSetIpInterfaceEntry) + { + DWORD loopbackMetric = 0; + hrc = vboxNetCfgWinGetLoopbackMetric(&loopbackMetric); + if (SUCCEEDED(hrc)) + hrc = vboxNetCfgWinSetInterfaceMetric(pLuid, loopbackMetric - 1); + } + + g_pfnInitializeIpInterfaceEntry = NULL; + g_pfnSetIpInterfaceEntry = NULL; + g_pfnGetIpInterfaceEntry = NULL; + + FreeLibrary(hmod); + } + return hrc; +} + +static HRESULT vboxNetCfgWinGetInterfaceLUID(IN HKEY hKey, OUT NET_LUID *pLUID) +{ + if (pLUID == NULL) + return E_INVALIDARG; + + DWORD dwLuidIndex = 0; + DWORD cbSize = sizeof(dwLuidIndex); + DWORD dwValueType = REG_DWORD; /** @todo r=bird: This is output only. No checked after the call. So, only for debugging? */ + LSTATUS lrc = RegQueryValueExW(hKey, L"NetLuidIndex", NULL, &dwValueType, (LPBYTE)&dwLuidIndex, &cbSize); + if (lrc == ERROR_SUCCESS) + { + DWORD dwIfType = 0; + cbSize = sizeof(dwIfType); + dwValueType = REG_DWORD; /** @todo r=bird: This is output only. No checked after the call. So, only for debugging? */ + lrc = RegQueryValueExW(hKey, L"*IfType", NULL, &dwValueType, (LPBYTE)&dwIfType, &cbSize); + if (lrc == ERROR_SUCCESS) + { + RT_ZERO(*pLUID); + pLUID->Info.IfType = dwIfType; + pLUID->Info.NetLuidIndex = dwLuidIndex; + return S_OK; + } + } + + RT_ZERO(*pLUID); + return HRESULT_FROM_WIN32(lrc); +} + + +#ifdef VBOXNETCFG_DELAYEDRENAME +VBOXNETCFGWIN_DECL(HRESULT) VBoxNetCfgWinRenameHostOnlyConnection(IN const GUID *pGuid, IN LPCWSTR pwszId, OUT BSTR *pDevName) +{ + if (pDevName) + *pDevName = NULL; + + HRESULT hr = S_OK; /** @todo r=bird: ODD return status for SetupDiCreateDeviceInfoList failures! */ + HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, NULL); + if (hDevInfo != INVALID_HANDLE_VALUE) + { + SP_DEVINFO_DATA DevInfoData = { sizeof(SP_DEVINFO_DATA) }; + if (SetupDiOpenDeviceInfo(hDevInfo, pwszId, NULL, 0, &DevInfoData)) + { + WCHAR wszDevName[256 + 1] = {0}; + DWORD err = ERROR_SUCCESS; + if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, SPDRP_FRIENDLYNAME, NULL /*PropertyRegDataType*/, + (PBYTE)wszDevName, sizeof(wszDevName) - sizeof(WCHAR) /*yes, bytes*/, + NULL /*RequiredSize*/)) + { + err = GetLastError(); + if (err == ERROR_INVALID_DATA) + { + RT_ZERO(wszDevName); + if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &DevInfoData, SPDRP_DEVICEDESC, NULL /*PropertyRegDataType*/, + (PBYTE)wszDevName, sizeof(wszDevName) - sizeof(WCHAR) /*yes, bytes*/, + NULL /*RequiredSize*/)) + err = ERROR_SUCCESS; + else + err = GetLastError(); + } + } + if (err == ERROR_SUCCESS) + { + WCHAR wszConnectionNewName[128] = {0}; + hr = VBoxNetCfgWinGenHostonlyConnectionName(wszDevName, wszConnectionNewName, + RT_ELEMENTS(wszConnectionNewName), NULL); + if (SUCCEEDED(hr)) + { + WCHAR wszGuid[50]; + int cbWGuid = StringFromGUID2(*pGuid, wszGuid, RT_ELEMENTS(wszGuid)); + if (cbWGuid) + { + hr = VBoxNetCfgWinRenameConnection(wszGuid, wszConnectionNewName); + if (FAILED(hr)) + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: VBoxNetCfgWinRenameConnection failed (0x%x)\n", hr)); + } + else + { + err = GetLastError(); + hr = HRESULT_FROM_WIN32(err); + if (SUCCEEDED(hr)) + hr = E_FAIL; + NonStandardLogFlow(("StringFromGUID2 failed err=%u, hr=0x%x\n", err, hr)); + } + } + else + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: VBoxNetCfgWinGenHostonlyConnectionName failed (0x%x)\n", hr)); + if (SUCCEEDED(hr) && pDevName) + { + *pDevName = SysAllocString((const OLECHAR *)wszDevName); + if (!*pDevName) + { + NonStandardLogFlow(("SysAllocString failed\n")); + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + } + } + else + { + hr = HRESULT_FROM_WIN32(err); + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: SetupDiGetDeviceRegistryPropertyW failed (0x%x)\n", err)); + } + } + else + { + DWORD err = GetLastError(); + hr = HRESULT_FROM_WIN32(err); + NonStandardLogFlow(("VBoxNetCfgWinRenameHostOnlyConnection: SetupDiOpenDeviceInfo failed (0x%x)\n", err)); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + + return hr; +} +#endif /* VBOXNETCFG_DELAYEDRENAME */ + +#undef SetErrBreak + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf new file mode 100644 index 00000000..9e13a198 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetAdp.inf @@ -0,0 +1,95 @@ +; $Id: VBoxNetAdp.inf $ +;; @file +; VBoxNetAdp.inf - VirtualBox Host-Only Driver inf file +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +signature = "$Windows NT$" +;cat CatalogFile = VBoxNetAdp.cat +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %ORACLE% +; DriverPackageType=Network +; DriverPackageDisplayName=%VBoxNetAdp_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + +[ControlFlags] +;ExcludeFromSelect = sun_VBoxNetAdp + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetAdp.sys=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetAdp.Files.Sys = 12 ; %windir%\System32\drivers + +[Manufacturer] +%ORACLE% = VBoxNetAdp@COMMA-NT-ARCH@ + +[VBoxNetAdp@DOT-NT-ARCH@] +%VBoxNetAdp_Desc% = VBoxNetAdp.ndi, sun_VBoxNetAdp + +[VBoxNetAdp.ndi] +Characteristics = 0x1 ; NCF_VIRTUAL +CopyFiles = VBoxNetAdp.Files.Sys +AddReg = VBoxNetAdp.AddReg + +[VBoxNetAdp.Files.Sys] +VBoxNetAdp.sys,,,2 + +[VBoxNetAdp.ndi.Services] +AddService = VBoxNetAdp,0x2, VBoxNetAdp.AddService + +[VBoxNetAdp.AddService] +DisplayName = %VBoxNetAdp_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetAdp.sys +LoadOrderGroup = NDIS + +[VBoxNetAdp.AddReg] +HKR, , *NdisDeviceType, 0x00010001, 1 ; NDIS_DEVICE_TYPE_ENDPOINT +HKR, Ndi, Service, 0, "VBoxNetAdp" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + +[Strings] +ORACLE = "Oracle Corporation" +VBoxNetAdp_Desc = "VirtualBox Host-Only Ethernet Adapter" +DiskDescription = "VirtualBox Host-Only Ethernet Adapter" + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc new file mode 100644 index 00000000..979fb6dd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt-win.rc @@ -0,0 +1,77 @@ +/* $Id: VBoxNetFlt-win.rc $ */ +/** @file + * VBoxNetFlt - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +#ifndef VBOXNETADP +# define DESCRIPTION_STR "VirtualBox Bridged Networking Driver\0" +# define FILENAME_STR "VBoxNetFlt" +#else +# define DESCRIPTION_STR "VirtualBox Host-Only Network Adapter Driver\0" +# define FILENAME_STR "VBoxNetAdp" +#endif + +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_NETWORK +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileDescription", DESCRIPTION_STR + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "InternalName", FILENAME_STR "\0" + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "OriginalFilename", FILENAME_STR ".sys\0" + 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/VBoxNetFlt/win/drv/VBoxNetFlt.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf new file mode 100644 index 00000000..22425793 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFlt.inf @@ -0,0 +1,117 @@ +; $Id: VBoxNetFlt.inf $ +;; @file +; VBoxNetFlt.inf - VirtualBox Bridged Networking Driver inf file Protocol edge +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature = "$Windows NT$" +;cat CatalogFile = VBoxNetFlt.cat +Class = NetService +ClassGUID = {4D36E974-E325-11CE-BFC1-08002BE10318} +Provider = %ORACLE% +;DriverPackageType=Network +;DriverPackageDisplayName=%VBoxNetFlt_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + + +[Manufacturer] +%ORACLE% = VBoxNetFlt@COMMA-NT-ARCH@ + +[ControlFlags] + +[VBoxNetFlt@DOT-NT-ARCH@] +%VBoxNetFlt_Desc% = VBoxNetFlt.ndi, sun_VBoxNetFlt + +[VBoxNetFlt.ndi] +AddReg = VBoxNetFlt.ndi.AddReg, VBoxNetFlt.AddReg +Characteristics = 0x4410 ; NCF_FILTER | NCF_NDIS_PROTOCOL +CopyFiles = VBoxNetFlt.Files.DLL, VBoxNetFlt.Files.Sys +CopyInf = VBoxNetFltM.inf + +[VBoxNetFlt.ndi.Remove] +DelFiles = VBoxNetFlt.Files.DLL, VBoxNetFlt.Files.Sys + +[VBoxNetFlt.ndi.Services] +AddService = VBoxNetFlt,, VBoxNetFlt.AddService + +[VBoxNetFlt.AddService] +DisplayName = %VBoxNetFltService_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetFlt.sys +LoadOrderGroup = PNP_TDI +AddReg = VBoxNetFlt.AddService.AddReg + + +[VBoxNetFlt.AddService.AddReg] + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetFlt.sys=1 +VBoxNetFltNobj.dll=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetFlt.Files.DLL = 11 ; %windir%\System32 +VBoxNetFlt.Files.Sys = 12 ; %windir%\System32\drivers + +[VBoxNetFlt.Files.Sys] +VBoxNetFlt.sys,,,2 + +[VBoxNetFlt.Files.DLL] +VBoxNetFltNobj.dll,,,2 + +[VBoxNetFlt.ndi.AddReg] +HKR, Ndi, HelpText, , %VBoxNetFlt_HELP% +HKR, Ndi, ClsID, 0, {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} +HKR, Ndi, ComponentDll, , VBoxNetFltNobj.dll +HKR, Ndi, FilterClass, , failover +HKR, Ndi, FilterDeviceInfId, , sun_VBoxNetFltmp +HKR, Ndi, Service, , VBoxNetFlt +HKR, Ndi\Interfaces, UpperRange, , noupper +HKR, Ndi\Interfaces, LowerRange, , nolower +HKR, Ndi\Interfaces, FilterMediaTypes, , "ethernet, nolower" + +[VBoxNetFlt.AddReg] +HKR, Parameters, Param1, 0, 4 + +[Strings] +ORACLE = "Oracle Corporation" +DiskDescription = "VirtualBox Bridged Networking Driver" +VBoxNetFlt_Desc = "VirtualBox Bridged Networking Driver" +VBoxNetFlt_HELP = "VirtualBox Bridged Networking Driver" +VBoxNetFltService_Desc = "VirtualBox Bridged Networking Service" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h new file mode 100644 index 00000000..520a7d7c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltCmn-win.h @@ -0,0 +1,533 @@ +/* $Id: VBoxNetFltCmn-win.h $ */ +/** @file + * VBoxNetFltCmn-win.h - Bridged Networking Driver, Windows Specific Code. + * Common header with configuration defines and global defs + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV + +/* debugging flags */ +#ifdef DEBUG +//# define DEBUG_NETFLT_PACKETS +# ifndef DEBUG_misha +# define RT_NO_STRICT +# endif +/* # define DEBUG_NETFLT_LOOPBACK */ +/* receive logic has several branches */ +/* the DEBUG_NETFLT_RECV* macros used to debug the ProtocolReceive callback + * which is typically not used in case the underlying miniport indicates the packets with NdisMIndicateReceivePacket + * the best way to debug the ProtocolReceive (which in turn has several branches) is to enable the DEBUG_NETFLT_RECV + * one by one in the below order, i.e. + * first DEBUG_NETFLT_RECV + * then DEBUG_NETFLT_RECV + DEBUG_NETFLT_RECV_NOPACKET */ +//# define DEBUG_NETFLT_RECV +//# define DEBUG_NETFLT_RECV_NOPACKET +//# define DEBUG_NETFLT_RECV_TRANSFERDATA +/* use ExAllocatePoolWithTag instead of NdisAllocateMemoryWithTag */ +// #define DEBUG_NETFLT_USE_EXALLOC +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define VBOXNETFLT_OS_SPECFIC 1 + +/** version + * NOTE: we are NOT using NDIS 5.1 features now */ +#ifdef NDIS51_MINIPORT +# define VBOXNETFLT_VERSION_MP_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_MP_NDIS_MINOR 1 +#else +# define VBOXNETFLT_VERSION_MP_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_MP_NDIS_MINOR 0 +#endif + +#ifndef VBOXNETADP +#ifdef NDIS51 +# define VBOXNETFLT_VERSION_PT_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_PT_NDIS_MINOR 1 /* todo: use 0 here as well ? */ +#else +# define VBOXNETFLT_VERSION_PT_NDIS_MAJOR 5 +# define VBOXNETFLT_VERSION_PT_NDIS_MINOR 0 +#endif + +# define VBOXNETFLT_NAME_PROTOCOL L"VBoxNetFlt" +/** device to be used to prevent the driver unload & ioctl interface (if necessary in the future) */ +# define VBOXNETFLT_NAME_LINK L"\\DosDevices\\Global\\VBoxNetFlt" +# define VBOXNETFLT_NAME_DEVICE L"\\Device\\VBoxNetFlt" +#else +# define VBOXNETFLT_NAME_LINK L"\\DosDevices\\Global\\VBoxNetAdp" +# define VBOXNETFLT_NAME_DEVICE L"\\Device\\VBoxNetAdp" +#endif + +typedef struct VBOXNETFLTINS *PVBOXNETFLTINS; + +/** configuration */ + +/** Ndis Packet pool settings + * these are applied to both receive and send packet pools */ +/* number of packets for normal used */ +#define VBOXNETFLT_PACKET_POOL_SIZE_NORMAL 0x000000FF +/* number of additional overflow packets */ +#define VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW 0x0000FF00 + +/** packet queue size used when the driver is working in the "active" mode */ +#define VBOXNETFLT_PACKET_INFO_POOL_SIZE 0x0000FFFF + +#ifndef VBOXNETADP +/** memory tag used for memory allocations + * (VBNF stands for VBox NetFlt) */ +# define VBOXNETFLT_MEM_TAG 'FNBV' +#else +/** memory tag used for memory allocations + * (VBNA stands for VBox NetAdp) */ +# define VBOXNETFLT_MEM_TAG 'ANBV' +#endif + +/** receive and transmit Ndis buffer pool size */ +#define VBOXNETFLT_BUFFER_POOL_SIZE_TX 128 +#define VBOXNETFLT_BUFFER_POOL_SIZE_RX 128 + +#define VBOXNETFLT_PACKET_ETHEADER_SIZE 14 +#define VBOXNETFLT_PACKET_HEADER_MATCH_SIZE 24 +#define VBOXNETFLT_PACKET_QUEUE_SG_SEGS_ALLOC 32 + + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) +# define VBOXNETFLT_PACKETMATCH_LENGTH (VBOXNETFLT_PACKET_ETHEADER_SIZE + 2) +#endif + +#ifdef VBOXNETADP +#define VBOXNETADP_HEADER_SIZE 14 +#define VBOXNETADP_MAX_DATA_SIZE 1500 +#define VBOXNETADP_MAX_PACKET_SIZE (VBOXNETADP_HEADER_SIZE + VBOXNETADP_MAX_DATA_SIZE) +#define VBOXNETADP_MIN_PACKET_SIZE 60 +/* link speed 100Mbps (measured in 100 bps) */ +#define VBOXNETADP_LINK_SPEED 1000000 +#define VBOXNETADP_MAX_LOOKAHEAD_SIZE VBOXNETADP_MAX_DATA_SIZE +#define VBOXNETADP_VENDOR_ID 0x080027 +#define VBOXNETADP_VENDOR_DRIVER_VERSION 0x00010000 +#define VBOXNETADP_VENDOR_DESC "Sun" +#define VBOXNETADP_MAX_MCAST_LIST 32 +#define VBOXNETADP_ETH_ADDRESS_LENGTH 6 + +//#define VBOXNETADP_REPORT_DISCONNECTED +#endif +/* type defs */ + +/** Flag specifying that the type of enqueued packet + * if set the info contains the PINTNETSG packet + * if clear the packet info contains the PNDIS_PACKET packet + * Typically the packet queue we are maintaining contains PNDIS_PACKETs only, + * however in case the underlying miniport indicates a packet with the NDIS_STATUS_RESOURCES status + * we MUST return the packet back to the miniport immediately + * this is why we are creating the INTNETSG, copying the ndis packet info there and enqueueing it */ +#define VBOXNETFLT_PACKET_SG 0x00000001 + +/** the flag specifying that the packet source + * if set the packet comes from the host (upperlying protocol) + * if clear the packet comes from the wire (underlying miniport) */ +#define VBOXNETFLT_PACKET_SRC_HOST 0x00000002 + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/** flag specifying the packet was originated by our driver + * i.e. we could use it on our needs and should not return it + * we are enqueueing "our" packets on ProtocolReceive call-back when + * Ndis does not give us a receive packet (the driver below us has called NdisM..IndicateReceive) + * this is supported for Ndis Packet only */ +#define VBOXNETFLT_PACKET_MINE 0x00000004 + +/** flag passed to vboxNetFltWinQuEnqueuePacket specifying that the packet should be copied + * this is supported for Ndis Packet only */ +#define VBOXNETFLT_PACKET_COPY 0x00000008 +#endif + +/** packet queue element containing the packet info */ +typedef struct VBOXNETFLT_PACKET_INFO +{ + /** list entry used for enqueueing the info */ + LIST_ENTRY ListEntry; + /** pointer to the pool containing this packet info */ + struct VBOXNETFLT_PACKET_INFO_POOL *pPool; + /** flags describing the referenced packet. Contains PACKET_xxx flags (i.e. PACKET_SG, PACKET_SRC_HOST) */ + uint32_t fFlags; + /** pointer to the packet this info represents */ + PVOID pPacket; +} VBOXNETFLT_PACKET_INFO, *PVBOXNETFLT_PACKET_INFO; + +/* paranoid check to make sure the elements in the packet info array are properly aligned */ +AssertCompile((sizeof(VBOXNETFLT_PACKET_INFO) & (sizeof(PVOID) - 1)) == 0); + +/** represents the packet queue */ +typedef LIST_ENTRY PVBOXNETFLT_ACKET_QUEUE, *PVBOXNETFLT_PACKET_QUEUE; + +/* + * we are using non-interlocked versions of LIST_ENTRY-related operations macros and synchronize + * access to the queue and its elements by acquiring/releasing a spinlock using Ndis[Acquire,Release]Spinlock + * + * we are NOT using interlocked versions of insert/remove head/tail list functions because we need to iterate though + * the queue elements as well as remove elements from the midle of the queue + * + * * @todo: it seems that we can switch to using interlocked versions of list-entry functions + * since we have removed all functionality (mentioned above, i.e. queue elements iteration, etc.) that might prevent us from doing this + */ +typedef struct VBOXNETFLT_INTERLOCKED_PACKET_QUEUE +{ + /** queue */ + PVBOXNETFLT_ACKET_QUEUE Queue; + /** queue lock */ + NDIS_SPIN_LOCK Lock; +} VBOXNETFLT_INTERLOCKED_PACKET_QUEUE, *PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE; + +typedef struct VBOXNETFLT_SINGLE_LIST +{ + /** queue */ + SINGLE_LIST_ENTRY Head; + /** pointer to the list tail. used to enqueue elements to the tail of the list */ + PSINGLE_LIST_ENTRY pTail; +} VBOXNETFLT_SINGLE_LIST, *PVBOXNETFLT_SINGLE_LIST; + +typedef struct VBOXNETFLT_INTERLOCKED_SINGLE_LIST +{ + /** queue */ + VBOXNETFLT_SINGLE_LIST List; + /** queue lock */ + NDIS_SPIN_LOCK Lock; +} VBOXNETFLT_INTERLOCKED_SINGLE_LIST, *PVBOXNETFLT_INTERLOCKED_SINGLE_LIST; + +/** packet info pool contains free packet info elements to be used for the packet queue + * we are using the pool mechanism to allocate packet queue elements + * the pool mechanism is pretty simple now, we are allocating a bunch of memory + * for maintaining VBOXNETFLT_PACKET_INFO_POOL_SIZE queue elements and just returning null when the pool is exhausted + * This mechanism seems to be enough for now since we are using VBOXNETFLT_PACKET_INFO_POOL_SIZE = 0xffff which is + * the maximum size of packets the ndis packet pool supports */ +typedef struct VBOXNETFLT_PACKET_INFO_POOL +{ + /** free packet info queue */ + VBOXNETFLT_INTERLOCKED_PACKET_QUEUE Queue; + /** memory bugger used by the pool */ + PVOID pBuffer; +} VBOXNETFLT_PACKET_INFO_POOL, *PVBOXNETFLT_PACKET_INFO_POOL; + +typedef enum VBOXNETDEVOPSTATE +{ + kVBoxNetDevOpState_InvalidValue = 0, + kVBoxNetDevOpState_Initializing, + kVBoxNetDevOpState_Initialized, + kVBoxNetDevOpState_Deinitializing, + kVBoxNetDevOpState_Deinitialized, + +} VBOXNETDEVOPSTATE; + +typedef enum VBOXNETFLT_WINIFSTATE +{ + /** The usual invalid state. */ + kVBoxWinIfState_Invalid = 0, + /** Initialization. */ + kVBoxWinIfState_Connecting, + /** Connected fuly functional state */ + kVBoxWinIfState_Connected, + /** Disconnecting */ + kVBoxWinIfState_Disconnecting, + /** Disconnected */ + kVBoxWinIfState_Disconnected, +} VBOXNETFLT_WINIFSTATE; + +/** structure used to maintain the state and reference count of the miniport and protocol */ +typedef struct VBOXNETFLT_WINIF_DEVICE +{ + /** initialize state */ + VBOXNETDEVOPSTATE OpState; + /** ndis power state */ + NDIS_DEVICE_POWER_STATE PowerState; + /** reference count */ + uint32_t cReferences; +} VBOXNETFLT_WINIF_DEVICE, *PVBOXNETFLT_WINIF_DEVICE; + +#define VBOXNDISREQUEST_INPROGRESS 1 +#define VBOXNDISREQUEST_QUEUED 2 + +typedef struct VBOXNETFLTWIN_STATE +{ + union + { + struct + { + UINT fRequestInfo : 2; + UINT fInterfaceClosing : 1; + UINT fStandBy : 1; + UINT fProcessingPacketFilter : 1; + UINT fPPFNetFlt : 1; + UINT fUpperProtSetFilterInitialized : 1; + UINT Reserved : 25; + }; + UINT Value; + }; +} VBOXNETFLTWIN_STATE, *PVBOXNETFLTWIN_STATE; + +DECLINLINE(VBOXNETFLTWIN_STATE) vboxNetFltWinAtomicUoReadWinState(VBOXNETFLTWIN_STATE State) +{ + UINT fValue = ASMAtomicUoReadU32((volatile uint32_t *)&State.Value); + return *((PVBOXNETFLTWIN_STATE)((void*)&fValue)); +} + +/* miniport layer globals */ +typedef struct VBOXNETFLTGLOBALS_MP +{ + /** our miniport handle */ + NDIS_HANDLE hMiniport; + /** ddis wrapper handle */ + NDIS_HANDLE hNdisWrapper; +} VBOXNETFLTGLOBALS_MP, *PVBOXNETFLTGLOBALS_MP; + +#ifndef VBOXNETADP +/* protocol layer globals */ +typedef struct VBOXNETFLTGLOBALS_PT +{ + /** our protocol handle */ + NDIS_HANDLE hProtocol; +} VBOXNETFLTGLOBALS_PT, *PVBOXNETFLTGLOBALS_PT; +#endif /* #ifndef VBOXNETADP */ + +typedef struct VBOXNETFLTGLOBALS_WIN +{ + /** synch event used for device creation synchronization */ + KEVENT SynchEvent; + /** Device reference count */ + int cDeviceRefs; + /** ndis device */ + NDIS_HANDLE hDevice; + /** device object */ + PDEVICE_OBJECT pDevObj; + /* loopback flags */ + /* ndis packet flags to disable packet loopback */ + UINT fPacketDontLoopBack; + /* ndis packet flags specifying whether the packet is looped back */ + UINT fPacketIsLoopedBack; + /* Minport info */ + VBOXNETFLTGLOBALS_MP Mp; +#ifndef VBOXNETADP + /* Protocol info */ + VBOXNETFLTGLOBALS_PT Pt; + /** lock protecting the filter list */ + NDIS_SPIN_LOCK lockFilters; + /** the head of filter list */ + RTLISTANCHOR listFilters; + /** IP address change notifier handle */ + HANDLE hNotifier; +#endif +} VBOXNETFLTGLOBALS_WIN, *PVBOXNETFLTGLOBALS_WIN; + +extern VBOXNETFLTGLOBALS_WIN g_VBoxNetFltGlobalsWin; + +/** represents filter driver device context*/ +typedef struct VBOXNETFLTWIN +{ + /** handle used by miniport edge for ndis calls */ + NDIS_HANDLE hMiniport; + /** miniport edge state */ + VBOXNETFLT_WINIF_DEVICE MpState; + /** ndis packet pool used for receives */ + NDIS_HANDLE hRecvPacketPool; + /** ndis buffer pool used for receives */ + NDIS_HANDLE hRecvBufferPool; + /** driver bind adapter state. */ + VBOXNETFLT_WINIFSTATE enmState; +#ifndef VBOXNETADP + /* misc state flags */ + VBOXNETFLTWIN_STATE StateFlags; + /** handle used by protocol edge for ndis calls */ + NDIS_HANDLE hBinding; + /** protocol edge state */ + VBOXNETFLT_WINIF_DEVICE PtState; + /** ndis packet pool used for receives */ + NDIS_HANDLE hSendPacketPool; + /** ndis buffer pool used for receives */ + NDIS_HANDLE hSendBufferPool; + /** used for maintaining the pending send packets for handling packet loopback */ + VBOXNETFLT_INTERLOCKED_SINGLE_LIST SendPacketQueue; + /** used for serializing calls to the NdisRequest in the vboxNetFltWinSynchNdisRequest */ + RTSEMFASTMUTEX hSynchRequestMutex; + /** event used to synchronize with the Ndis Request completion in the vboxNetFltWinSynchNdisRequest */ + KEVENT hSynchCompletionEvent; + /** status of the Ndis Request initiated by the vboxNetFltWinSynchNdisRequest */ + NDIS_STATUS volatile SynchCompletionStatus; + /** pointer to the Ndis Request being executed by the vboxNetFltWinSynchNdisRequest */ + PNDIS_REQUEST volatile pSynchRequest; + /** open/close adapter status. + * Since ndis adapter open and close requests may complete asynchronously, + * we are using event mechanism to wait for open/close completion + * the status field is being set by the completion call-back */ + NDIS_STATUS OpenCloseStatus; + /** open/close adaptor completion event */ + NDIS_EVENT OpenCloseEvent; + /** medium we are attached to */ + NDIS_MEDIUM enmMedium; + /** + * Passdown request info + */ + /** ndis request we pass down to the miniport below */ + NDIS_REQUEST PassDownRequest; + /** Ndis pass down request bytes read or written original pointer */ + PULONG pcPDRBytesRW; + /** Ndis pass down request bytes needed original pointer */ + PULONG pcPDRBytesNeeded; + /** true if we should indicate the receive complete used by the ProtocolReceive mechanism. + * We need to indicate it only with the ProtocolReceive + NdisMEthIndicateReceive path. + * Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + bool abIndicateRxComplete[64]; + /** Pending transfer data packet queue (i.e. packets that were indicated as pending on NdisTransferData call */ + VBOXNETFLT_INTERLOCKED_SINGLE_LIST TransferDataList; + /* mac options initialized on OID_GEN_MAC_OPTIONS */ + ULONG fMacOptions; + /** our miniport devuice name */ + NDIS_STRING MpDeviceName; + /** synchronize with unbind with Miniport initialization */ + NDIS_EVENT MpInitCompleteEvent; + /** media connect status that we indicated */ + NDIS_STATUS MpIndicatedMediaStatus; + /** media connect status pending to indicate */ + NDIS_STATUS MpUnindicatedMediaStatus; + /** packet filter flags set by the upper protocols */ + ULONG fUpperProtocolSetFilter; + /** packet filter flags set by the upper protocols */ + ULONG fSetFilterBuffer; + /** packet filter flags set by us */ + ULONG fOurSetFilter; + /** our own list of filters, needed by notifier */ + RTLISTNODE node; +#else + volatile ULONG cTxSuccess; + volatile ULONG cRxSuccess; + volatile ULONG cTxError; + volatile ULONG cRxError; +#endif +} VBOXNETFLTWIN, *PVBOXNETFLTWIN; + +typedef struct VBOXNETFLT_PACKET_QUEUE_WORKER +{ + /** this event is used to initiate a packet queue worker thread kill */ + KEVENT KillEvent; + /** this event is used to notify a worker thread that the packets are added to the queue */ + KEVENT NotifyEvent; + /** pointer to the packet queue worker thread object */ + PKTHREAD pThread; + /** pointer to the SG used by the packet queue for IntNet receive notifications */ + PINTNETSG pSG; + /** Packet queue */ + VBOXNETFLT_INTERLOCKED_PACKET_QUEUE PacketQueue; + /** Packet info pool, i.e. the pool for the packet queue elements */ + VBOXNETFLT_PACKET_INFO_POOL PacketInfoPool; +} VBOXNETFLT_PACKET_QUEUE_WORKER, *PVBOXNETFLT_PACKET_QUEUE_WORKER; + +/* protocol reserved data held in ndis packet */ +typedef struct VBOXNETFLT_PKTRSVD_PT +{ + /** original packet received from the upperlying protocol + * can be null if the packet was originated by intnet */ + PNDIS_PACKET pOrigPacket; + /** pointer to the buffer to be freed on send completion + * can be null if no buffer is to be freed */ + PVOID pBufToFree; +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + SINGLE_LIST_ENTRY ListEntry; + /* true if the packet is from IntNet */ + bool bFromIntNet; +#endif +} VBOXNETFLT_PKTRSVD_PT, *PVBOXNETFLT_PKTRSVD_PT; + +/** miniport reserved data held in ndis packet */ +typedef struct VBOXNETFLT_PKTRSVD_MP +{ + /** original packet received from the underling miniport + * can be null if the packet was originated by intnet */ + PNDIS_PACKET pOrigPacket; + /** pointer to the buffer to be freed on receive completion + * can be null if no buffer is to be freed */ + PVOID pBufToFree; +} VBOXNETFLT_PKTRSVD_MP, *PVBOXNETFLT_PKTRSVD_MP; + +/** represents the data stored in the protocol reserved field of ndis packet on NdisTransferData processing */ +typedef struct VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT +{ + /** next packet in a list */ + SINGLE_LIST_ENTRY ListEntry; + /* packet buffer start */ + PNDIS_BUFFER pOrigBuffer; +} VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT, *PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT; + +/* VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT should fit into PROTOCOL_RESERVED_SIZE_IN_PACKET because we use protocol reserved part + * of our miniport edge on transfer data processing for honding our own info */ +AssertCompile(sizeof (VBOXNETFLT_PKTRSVD_TRANSFERDATA_PT) <= PROTOCOL_RESERVED_SIZE_IN_PACKET); +/* this should fit in MiniportReserved */ +AssertCompile(sizeof (VBOXNETFLT_PKTRSVD_MP) <= RT_SIZEOFMEMB(NDIS_PACKET, MiniportReserved)); +/* we use RTAsmAtomic*U32 for those, make sure we're correct */ +AssertCompile(sizeof (NDIS_DEVICE_POWER_STATE) == sizeof (uint32_t)); +AssertCompile(sizeof (UINT) == sizeof (uint32_t)); + + +#define NDIS_FLAGS_SKIP_LOOPBACK_W2K 0x400 + +#include "../../VBoxNetFltInternal.h" +#include "VBoxNetFltRt-win.h" +#ifndef VBOXNETADP +# include "VBoxNetFltP-win.h" +#endif +#include "VBoxNetFltM-win.h" + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltCmn_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp new file mode 100644 index 00000000..65bde454 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.cpp @@ -0,0 +1,1570 @@ +/* $Id: VBoxNetFltM-win.cpp $ */ +/** @file + * VBoxNetFltM-win.cpp - Bridged Networking Driver, Windows Specific Code. + * Miniport edge + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ +#include "VBoxNetFltCmn-win.h" + +static const char* vboxNetFltWinMpDumpOid(ULONG oid); + +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinMpInitialize(OUT PNDIS_STATUS OpenErrorStatus, + OUT PUINT SelectedMediumIndex, + IN PNDIS_MEDIUM MediumArray, + IN UINT MediumArraySize, + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE WrapperConfigurationContext) +{ + RT_NOREF1(WrapperConfigurationContext); + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)NdisIMGetDeviceContext(MiniportAdapterHandle); + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + pNetFlt->u.s.WinIf.hMiniport = MiniportAdapterHandle; + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing); + /* the MP state should be already set to kVBoxNetDevOpState_Initializing, just a paranoia + * in case NDIS for some reason calls us in some irregular way */ + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + NDIS_MEDIUM enmMedium = pNetFlt->u.s.WinIf.enmMedium; + if (enmMedium == NdisMediumWan) + enmMedium = NdisMedium802_3; + + UINT i = 0; + for (; i < MediumArraySize; i++) + { + if (MediumArray[i] == enmMedium) + { + *SelectedMediumIndex = i; + break; + } + } + + do + { + if (i != MediumArraySize) + { + NdisMSetAttributesEx(MiniportAdapterHandle, pNetFlt, 0, + NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT | + NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT| + NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER | + NDIS_ATTRIBUTE_DESERIALIZE | + NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND, + NdisInterfaceInternal /* 0 */); + + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = NDIS_STATUS_MEDIA_CONNECT; + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, NdisDeviceStateD0); + Assert(pNetFlt->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); + + Status = NDIS_STATUS_SUCCESS; + break; + } + else + { + Status = NDIS_STATUS_UNSUPPORTED_MEDIA; + } + + Assert(Status != NDIS_STATUS_SUCCESS); + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + Assert(pNetFlt->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } while (0); + + NdisSetEvent(&pNetFlt->u.s.WinIf.MpInitCompleteEvent); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + + *OpenErrorStatus = Status; + + return Status; +} + +/** + * process the packet send in a "passthru" mode + */ +static NDIS_STATUS vboxNetFltWinSendPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + , bool bNetFltActive +#endif + ) +{ + PNDIS_PACKET pMyPacket; + NDIS_STATUS Status = vboxNetFltWinPrepareSendPacket(pNetFlt, pPacket, &pMyPacket); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { +#if !defined(VBOX_LOOPBACK_USEFLAGS) /* || defined(DEBUG_NETFLT_PACKETS) */ +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (bNetFltActive) + vboxNetFltWinLbPutSendPacket(pNetFlt, pMyPacket, false /* bFromIntNet */); +# else + /* no need for the loop enqueue & check in a passthru mode , ndis will do everything for us */ +# endif +#endif + NdisSend(&Status, pNetFlt->u.s.WinIf.hBinding, pMyPacket); + if (Status != NDIS_STATUS_PENDING) + { + NdisIMCopySendCompletePerPacketInfo(pPacket, pMyPacket); +#if defined(VBOXNETFLT_NO_PACKET_QUEUE) && !defined(VBOX_LOOPBACK_USEFLAGS) + if (bNetFltActive) + vboxNetFltWinLbRemoveSendPacket(pNetFlt, pMyPacket); +#endif + NdisFreePacket(pMyPacket); + } + } + return Status; +} + +#else /* defined VBOXNETADP */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoDeinitialization(PVBOXNETFLTINS pNetFlt) +{ + uint64_t NanoTS = RTTimeSystemNanoTS(); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + ASMAtomicUoWriteBool(&pNetFlt->fDisconnectedFromHost, true); + ASMAtomicUoWriteBool(&pNetFlt->fRediscoveryPending, false); + ASMAtomicUoWriteU64(&pNetFlt->NanoTSLastRediscovery, NanoTS); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + + /* check packet pool is empty */ + int cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS vboxNetFltWinMpReadApplyConfig(PVBOXNETFLTINS pThis, NDIS_HANDLE hMiniportAdapter, + NDIS_HANDLE hWrapperConfigurationContext) +{ + RT_NOREF1(hMiniportAdapter); + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + NDIS_HANDLE hConfiguration; + PNDIS_CONFIGURATION_PARAMETER pParameterValue; + NDIS_STRING strMAC = NDIS_STRING_CONST("MAC"); + RTMAC mac; + + NdisOpenConfiguration( + &Status, + &hConfiguration, + hWrapperConfigurationContext); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + do + { + int rc; + NDIS_CONFIGURATION_PARAMETER param; + WCHAR MacBuf[13]; + + NdisReadConfiguration(&Status, + &pParameterValue, + hConfiguration, + &strMAC, + NdisParameterString); +// Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + + rc = vboxNetFltWinMACFromNdisString(&mac, &pParameterValue->ParameterData.StringData); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + break; + } + } + + vboxNetFltWinGenerateMACAddress(&mac); + param.ParameterType = NdisParameterString; + param.ParameterData.StringData.Buffer = MacBuf; + param.ParameterData.StringData.MaximumLength = sizeof(MacBuf); + + rc = vboxNetFltWinMAC2NdisString(&mac, ¶m.ParameterData.StringData); + Assert(RT_SUCCESS(rc)); + if (RT_SUCCESS(rc)) + { + NdisWriteConfiguration(&Status, + hConfiguration, + &strMAC, + ¶m); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status != NDIS_STATUS_SUCCESS) + { + /* ignore the failure */ + Status = NDIS_STATUS_SUCCESS; + } + } + } while (0); + + NdisCloseConfiguration(hConfiguration); + } + else + { + vboxNetFltWinGenerateMACAddress(&mac); + } + + pThis->u.s.MacAddr = mac; + + return NDIS_STATUS_SUCCESS; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoInitialization(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE hMiniportAdapter, NDIS_HANDLE hWrapperConfigurationContext) +{ + NDIS_STATUS Status; + pNetFlt->u.s.WinIf.hMiniport = hMiniportAdapter; + + LogFlowFunc(("ENTER: pNetFlt 0x%p\n", pNetFlt)); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + vboxNetFltWinMpReadApplyConfig(pNetFlt, hMiniportAdapter, hWrapperConfigurationContext); + + NdisMSetAttributesEx(hMiniportAdapter, pNetFlt, + 0, /* CheckForHangTimeInSeconds */ + NDIS_ATTRIBUTE_DESERIALIZE | + NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND, + NdisInterfaceInternal/* 0 */); + + Assert(vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD3); + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, NdisDeviceStateD0); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); + + Status = NDIS_STATUS_SUCCESS; + + LogFlowFunc(("pNetFlt 0x%p, Status 0x%x\n", pNetFlt, Status)); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpInitialize(OUT PNDIS_STATUS OpenErrorStatus, + OUT PUINT SelectedMediumIndex, + IN PNDIS_MEDIUM MediumArray, + IN UINT MediumArraySize, + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE WrapperConfigurationContext) +{ + + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + UINT i = 0; + + LogFlowFuncEnter(); + + for (; i < MediumArraySize; i++) + { + if (MediumArray[i] == NdisMedium802_3) + { + *SelectedMediumIndex = i; + break; + } + } + + if (i != MediumArraySize) + { + PDEVICE_OBJECT pPdo, pFdo; +#define KEY_PREFIX L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class\\" + UCHAR Buf[512]; + PUCHAR pSuffix; + ULONG cbBuf; + NDIS_STRING RtlStr; + + wcscpy((WCHAR*)Buf, KEY_PREFIX); + pSuffix = Buf + (sizeof(KEY_PREFIX)-2); + + NdisMGetDeviceProperty(MiniportAdapterHandle, + &pPdo, + &pFdo, + NULL, //Next Device Object + NULL, + NULL); + + Status = IoGetDeviceProperty (pPdo, + DevicePropertyDriverKeyName, + sizeof(Buf) - (sizeof(KEY_PREFIX)-2), + pSuffix, + &cbBuf); + if (Status == STATUS_SUCCESS) + { + OBJECT_ATTRIBUTES ObjAttr; + HANDLE hDrvKey; + RtlStr.Buffer=(WCHAR*)Buf; + RtlStr.Length=(USHORT)cbBuf - 2 + sizeof(KEY_PREFIX) - 2; + RtlStr.MaximumLength=sizeof(Buf); + + InitializeObjectAttributes(&ObjAttr, &RtlStr, OBJ_CASE_INSENSITIVE, NULL, NULL); + + Status = ZwOpenKey(&hDrvKey, KEY_READ, &ObjAttr); + if (Status == STATUS_SUCCESS) + { + static UNICODE_STRING NetCfgInstanceIdValue = NDIS_STRING_CONST("NetCfgInstanceId"); +// UCHAR valBuf[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + RTUUID_STR_LENGTH*2 + 10]; +// ULONG cLength = sizeof(valBuf); +#define NAME_PREFIX L"\\DEVICE\\" + PKEY_VALUE_PARTIAL_INFORMATION pInfo = (PKEY_VALUE_PARTIAL_INFORMATION)Buf; + Status = ZwQueryValueKey(hDrvKey, + &NetCfgInstanceIdValue, + KeyValuePartialInformation, + pInfo, + sizeof(Buf), + &cbBuf); + if (Status == STATUS_SUCCESS) + { + if (pInfo->Type == REG_SZ && pInfo->DataLength > 2) + { + WCHAR *pName; + Status = vboxNetFltWinMemAlloc((PVOID*)&pName, pInfo->DataLength + sizeof(NAME_PREFIX)); + if (Status == STATUS_SUCCESS) + { + PVBOXNETFLTINS pNetFlt; + wcscpy(pName, NAME_PREFIX); + wcscpy(pName+(sizeof(NAME_PREFIX)-2)/2, (WCHAR*)pInfo->Data); + RtlStr.Buffer=pName; + RtlStr.Length = (USHORT)pInfo->DataLength - 2 + sizeof(NAME_PREFIX) - 2; + RtlStr.MaximumLength = (USHORT)pInfo->DataLength + sizeof(NAME_PREFIX); + + Status = vboxNetFltWinPtInitBind(&pNetFlt, MiniportAdapterHandle, &RtlStr, WrapperConfigurationContext); + + if (Status == STATUS_SUCCESS) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Initialized); +#if 0 + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); +#endif + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + + vboxNetFltWinMemFree(pName); + + } + } + else + { + Status = NDIS_STATUS_FAILURE; + } + } + } + } + } + else + { + Status = NDIS_STATUS_UNSUPPORTED_MEDIA; + } + + /** @todo */ + *OpenErrorStatus = Status; + + LogFlowFunc(("LEAVE: Status (0x%x)\n", Status)); + + return Status; +} +#endif + +static VOID vboxNetFltWinMpSendPackets(IN NDIS_HANDLE hMiniportAdapterContext, + IN PPNDIS_PACKET pPacketArray, + IN UINT cNumberOfPackets) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hMiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + bool bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + Assert(cNumberOfPackets); + + if (vboxNetFltWinIncReferenceWinIfNetFlt(pNetFlt, cNumberOfPackets, &bNetFltActive)) + { + uint32_t cAdaptRefs = cNumberOfPackets; + uint32_t cNetFltRefs; + uint32_t cPassThruRefs; + if (bNetFltActive) + { + cNetFltRefs = cNumberOfPackets; + cPassThruRefs = 0; + } + else + { + cPassThruRefs = cNumberOfPackets; + cNetFltRefs = 0; + } + + for (UINT i = 0; i < cNumberOfPackets; i++) + { + PNDIS_PACKET pPacket; + + pPacket = pPacketArray[i]; + + if (!cNetFltRefs +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + || !vboxNetFltWinPostIntnet(pNetFlt, pPacket, VBOXNETFLT_PACKET_SRC_HOST) +#else + || (fStatus = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, VBOXNETFLT_PACKET_SRC_HOST)) != NDIS_STATUS_SUCCESS +#endif + ) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinSendPassThru(pNetFlt, pPacket +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + , !!cNetFltRefs +#endif + ); +#else + if (!cNetFltRefs) + { +# ifdef VBOXNETADP_REPORT_DISCONNECTED + Status = NDIS_STATUS_MEDIA_DISCONNECT; + STATISTIC_INCREASE(pNetFlt->u.s.WinIf.cTxError); +# else + Status = NDIS_STATUS_SUCCESS; +# endif + } +#endif + + if (Status != NDIS_STATUS_PENDING) + { + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pPacket, Status); + } + else + { + cAdaptRefs--; + } + } + else + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pPacket, NDIS_STATUS_SUCCESS); +#else + cAdaptRefs--; + cNetFltRefs--; +#endif + } + } + + if (cNetFltRefs) + { + vboxNetFltWinDecReferenceNetFlt(pNetFlt, cNetFltRefs); + } + else if (cPassThruRefs) + { + vboxNetFltWinDecReferenceModePassThru(pNetFlt, cPassThruRefs); + } + if (cAdaptRefs) + { + vboxNetFltWinDecReferenceWinIf(pNetFlt, cAdaptRefs); + } + } + else + { + NDIS_HANDLE h = pNetFlt->u.s.WinIf.hMiniport; + AssertFailed(); + if (h) + { + for (UINT i = 0; i < cNumberOfPackets; i++) + { + PNDIS_PACKET pPacket; + pPacket = pPacketArray[i]; + NdisMSendComplete(h, pPacket, NDIS_STATUS_FAILURE); + } + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +#ifndef VBOXNETADP +static UINT vboxNetFltWinMpRequestStatePrep(PVBOXNETFLTINS pNetFlt, NDIS_STATUS *pStatus) +{ + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + + if (vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) > kVBoxNetDevOpState_Initialized /* protocol unbind in progress */ + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0) + { + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + RTSpinlockAcquire(pNetFlt->hSpinlock); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + if (vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) > kVBoxNetDevOpState_Initialized /* protocol unbind in progress */ + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + if ((vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) > NdisDeviceStateD0) + && !pNetFlt->u.s.WinIf.StateFlags.fStandBy) + { + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS | VBOXNDISREQUEST_QUEUED; + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_PENDING; + return VBOXNDISREQUEST_INPROGRESS | VBOXNDISREQUEST_QUEUED; + } + + if (pNetFlt->u.s.WinIf.StateFlags.fStandBy) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pStatus = NDIS_STATUS_FAILURE; + return 0; + } + + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + + RTSpinlockRelease(pNetFlt->hSpinlock); + + *pStatus = NDIS_STATUS_SUCCESS; + return VBOXNDISREQUEST_INPROGRESS; +} + +static NDIS_STATUS vboxNetFltWinMpRequestPostQuery(PVBOXNETFLTINS pNetFlt) +{ + if (pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER && VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + bool fNetFltActive; + const bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + + Assert(pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter); + + if (fNetFltActive) + { + /* netflt is active, simply return the cached value */ + *((PULONG)pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer) = pNetFlt->u.s.WinIf.fUpperProtocolSetFilter; + + /* we've intercepted the query and completed it */ + vboxNetFltWinMpRequestStateComplete(pNetFlt); + + vboxNetFltWinDereferenceNetFlt(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + + return NDIS_STATUS_SUCCESS; + } + else if (fWinIfActive) + { + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + /* we're cleaning it in RequestComplete */ + } + } + + NDIS_STATUS Status; + /* issue the request */ + NdisRequest(&Status, pNetFlt->u.s.WinIf.hBinding, &pNetFlt->u.s.WinIf.PassDownRequest); + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, Status); + Status = NDIS_STATUS_PENDING; + } + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpQueryInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesWritten, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + /* fist check if this is the oid we want to pass down */ + switch (Oid) + { + case OID_PNP_QUERY_POWER: + Status = NDIS_STATUS_SUCCESS; + break; + case OID_TCP_TASK_OFFLOAD: + case OID_GEN_SUPPORTED_GUIDS: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + default: + { + /* the oid is to be passed down, + * check the device state if we can do it + * and update device state accordingly */ + UINT uOp = vboxNetFltWinMpRequestStatePrep(pNetFlt, &Status); + if (uOp) + { + /* save the request info */ + pNetFlt->u.s.WinIf.PassDownRequest.RequestType = NdisRequestQueryInformation; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.Oid = Oid; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBuffer = InformationBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.QUERY_INFORMATION.InformationBufferLength = InformationBufferLength; + pNetFlt->u.s.WinIf.pcPDRBytesNeeded = BytesNeeded; + pNetFlt->u.s.WinIf.pcPDRBytesRW = BytesWritten; + + /* the oid can be processed */ + if (!(uOp & VBOXNDISREQUEST_QUEUED)) + { + Status = vboxNetFltWinMpRequestPostQuery(pNetFlt); + } + } + break; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +#endif /* ifndef VBOXNETADP*/ + +static NDIS_STATUS vboxNetFltWinMpHandlePowerState(PVBOXNETFLTINS pNetFlt, NDIS_DEVICE_POWER_STATE enmState) +{ + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0 + && enmState != NdisDeviceStateD0) + { + /* invalid state transformation */ + AssertFailed(); + return NDIS_STATUS_FAILURE; + } + +#ifndef VBOXNETADP + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) == NdisDeviceStateD0 + && enmState > NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = TRUE; + } + + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) > NdisDeviceStateD0 + && enmState == NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = FALSE; + } +#endif + + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.MpState, enmState); + +#ifndef VBOXNETADP + if (pNetFlt->u.s.WinIf.StateFlags.fStandBy == FALSE) + { + if (pNetFlt->u.s.WinIf.MpIndicatedMediaStatus != pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus) + { + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus, NULL, 0); + NdisMIndicateStatusComplete(pNetFlt->u.s.WinIf.hMiniport); + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus; + } + } + else + { + pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus = pNetFlt->u.s.WinIf.MpIndicatedMediaStatus; + } +#endif + + return NDIS_STATUS_SUCCESS; +} + +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinMpRequestPostSet(PVBOXNETFLTINS pNetFlt) +{ + if (pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER && VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + /* need to disable cleaning promiscuous here ?? */ + bool fNetFltActive; + const bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + + Assert(pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer); + Assert(!pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter); + + if (fNetFltActive) + { + Assert(fWinIfActive); + + /* netflt is active, update the cached value */ + /** @todo in case we are are not in promiscuous now, we are issuing a request. + * what should we do in case of a failure? + * i.e. should we update the fUpperProtocolSetFilter in completion routine in this case? etc. */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *((PULONG)pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer); + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + + if (!(pNetFlt->u.s.WinIf.fOurSetFilter & NDIS_PACKET_TYPE_PROMISCUOUS)) + { + pNetFlt->u.s.WinIf.fSetFilterBuffer = NDIS_PACKET_TYPE_PROMISCUOUS; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer = &pNetFlt->u.s.WinIf.fSetFilterBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBufferLength = sizeof (pNetFlt->u.s.WinIf.fSetFilterBuffer); + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 1; + /* we'll do dereferencing in request complete */ + } + else + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + + /* we've intercepted the query and completed it */ + vboxNetFltWinMpRequestStateComplete(pNetFlt); + return NDIS_STATUS_SUCCESS; + } + } + else if (fWinIfActive) + { + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 1; + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + /* dereference on completion */ + } + } + + NDIS_STATUS Status; + + NdisRequest(&Status, pNetFlt->u.s.WinIf.hBinding, &pNetFlt->u.s.WinIf.PassDownRequest); + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, Status); + } + + return Status; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRequestPost(PVBOXNETFLTINS pNetFlt) +{ + switch (pNetFlt->u.s.WinIf.PassDownRequest.RequestType) + { + case NdisRequestQueryInformation: + return vboxNetFltWinMpRequestPostQuery(pNetFlt); + case NdisRequestSetInformation: + return vboxNetFltWinMpRequestPostSet(pNetFlt); + default: + AssertBreakpoint(); + return NDIS_STATUS_FAILURE; + } +} + +static NDIS_STATUS vboxNetFltWinMpSetInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesRead, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_FAILURE; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + switch (Oid) + { + case OID_PNP_SET_POWER: + { + if (InformationBufferLength >= sizeof (NDIS_DEVICE_POWER_STATE)) + { + NDIS_DEVICE_POWER_STATE *penmState = (NDIS_DEVICE_POWER_STATE*)InformationBuffer; + Status = vboxNetFltWinMpHandlePowerState(pNetFlt, *penmState); + } + else + { + Status = NDIS_STATUS_INVALID_LENGTH; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + *BytesRead = sizeof (NDIS_DEVICE_POWER_STATE); + *BytesNeeded = 0; + } + else + { + *BytesRead = 0; + *BytesNeeded = sizeof (NDIS_DEVICE_POWER_STATE); + } + break; + } + default: + { + /* the oid is to be passed down, + * check the device state if we can do it + * and update device state accordingly */ + UINT uOp = vboxNetFltWinMpRequestStatePrep(pNetFlt, &Status); + if (uOp) + { + /* save the request info */ + pNetFlt->u.s.WinIf.PassDownRequest.RequestType = NdisRequestSetInformation; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.Oid = Oid; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBuffer = InformationBuffer; + pNetFlt->u.s.WinIf.PassDownRequest.DATA.SET_INFORMATION.InformationBufferLength = InformationBufferLength; + pNetFlt->u.s.WinIf.pcPDRBytesNeeded = BytesNeeded; + pNetFlt->u.s.WinIf.pcPDRBytesRW = BytesRead; + + /* the oid can be processed */ + if (!(uOp & VBOXNDISREQUEST_QUEUED)) + { + Status = vboxNetFltWinMpRequestPostSet(pNetFlt); + } + } + break; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} +#else +static NDIS_OID g_vboxNetFltWinMpSupportedOids[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_MAC_OPTIONS, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_CURRENT_PACKET_FILTER, + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, + OID_PNP_SET_POWER, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, +}; + +static NDIS_STATUS vboxNetFltWinMpQueryInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesWritten, + OUT PULONG BytesNeeded) +{ + /* static data */ + static const NDIS_HARDWARE_STATUS enmHwStatus = NdisHardwareStatusReady; + static const NDIS_MEDIUM enmMedium = NdisMedium802_3; + static NDIS_PNP_CAPABILITIES PnPCaps = {0}; + static BOOLEAN bPnPCapsInited = FALSE; + + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + ULONG64 u64Info = 0; + ULONG u32Info = 0; + USHORT u16Info = 0; + /* default is 4bytes */ + const void* pvInfo = (void*)&u32Info; + ULONG cbInfo = sizeof (u32Info); + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + *BytesWritten = 0; + *BytesNeeded = 0; + + switch (Oid) + { + case OID_GEN_SUPPORTED_LIST: + pvInfo = g_vboxNetFltWinMpSupportedOids; + cbInfo = sizeof (g_vboxNetFltWinMpSupportedOids); + break; + + case OID_GEN_HARDWARE_STATUS: + pvInfo = &enmHwStatus; + cbInfo = sizeof (NDIS_HARDWARE_STATUS); + break; + + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + pvInfo = &enmMedium; + cbInfo = sizeof (NDIS_MEDIUM); + break; + + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_CURRENT_LOOKAHEAD: + u32Info = VBOXNETADP_MAX_LOOKAHEAD_SIZE; + break; + + case OID_GEN_MAXIMUM_FRAME_SIZE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE - VBOXNETADP_HEADER_SIZE; + break; + + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE; + break; + + case OID_GEN_MAC_OPTIONS: + u32Info = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK; + break; + + case OID_GEN_LINK_SPEED: + u32Info = VBOXNETADP_LINK_SPEED; + break; + + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + u32Info = VBOXNETADP_MAX_PACKET_SIZE * VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_GEN_VENDOR_ID: + u32Info = VBOXNETADP_VENDOR_ID; + break; + + case OID_GEN_VENDOR_DESCRIPTION: + pvInfo = VBOXNETADP_VENDOR_DESC; + cbInfo = sizeof (VBOXNETADP_VENDOR_DESC); + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + u32Info = VBOXNETADP_VENDOR_DRIVER_VERSION; + break; + + case OID_GEN_DRIVER_VERSION: + u16Info = (USHORT)((VBOXNETFLT_VERSION_MP_NDIS_MAJOR << 8) + VBOXNETFLT_VERSION_MP_NDIS_MINOR); + pvInfo = (PVOID)&u16Info; + cbInfo = sizeof (USHORT); + break; + + case OID_GEN_MAXIMUM_SEND_PACKETS: + u32Info = VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: +#ifdef VBOXNETADP_REPORT_DISCONNECTED + { + bool bNetFltActive; + bool bActive = vboxNetFltWinReferenceWinIfNetFltFromAdapt(pNetFlt, bNetFltActive); + if (bActive && bNetFltActive) + { + u32Info = NdisMediaStateConnected; + } + else + { + u32Info = NdisMediaStateDisconnected; + } + + if (bActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + } +#else + u32Info = NdisMediaStateConnected; +#endif + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + u32Info = NDIS_PACKET_TYPE_BROADCAST + | NDIS_PACKET_TYPE_DIRECTED + | NDIS_PACKET_TYPE_ALL_FUNCTIONAL + | NDIS_PACKET_TYPE_ALL_LOCAL + | NDIS_PACKET_TYPE_GROUP + | NDIS_PACKET_TYPE_MULTICAST; + break; + + case OID_PNP_CAPABILITIES: + if (!bPnPCapsInited) + { + PnPCaps.WakeUpCapabilities.MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + PnPCaps.WakeUpCapabilities.MinPatternWakeUp = NdisDeviceStateUnspecified; + bPnPCapsInited = TRUE; + } + cbInfo = sizeof (NDIS_PNP_CAPABILITIES); + pvInfo = &PnPCaps; + + break; + + case OID_PNP_QUERY_POWER: + Status = NDIS_STATUS_SUCCESS; + break; + + case OID_GEN_XMIT_OK: + u64Info = pNetFlt->u.s.WinIf.cTxSuccess; + pvInfo = &u64Info; + if (InformationBufferLength >= sizeof (ULONG64) || InformationBufferLength == 0) + { + cbInfo = sizeof (ULONG64); + } + else + { + cbInfo = sizeof (ULONG); + } + *BytesNeeded = sizeof (ULONG64); + break; + + case OID_GEN_RCV_OK: + u64Info = pNetFlt->u.s.WinIf.cRxSuccess; + pvInfo = &u64Info; + if (InformationBufferLength >= sizeof (ULONG64) || InformationBufferLength == 0) + { + cbInfo = sizeof (ULONG64); + } + else + { + cbInfo = sizeof (ULONG); + } + *BytesNeeded = sizeof (ULONG64); + break; + + case OID_GEN_XMIT_ERROR: + u32Info = pNetFlt->u.s.WinIf.cTxError; + break; + + case OID_GEN_RCV_ERROR: + u32Info = pNetFlt->u.s.WinIf.cRxError; + break; + + case OID_GEN_RCV_NO_BUFFER: + case OID_GEN_RCV_CRC_ERROR: + u32Info = 0; + break; + + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + u32Info = VBOXNETFLT_PACKET_INFO_POOL_SIZE; + break; + + case OID_802_3_PERMANENT_ADDRESS: + pvInfo = &pNetFlt->u.s.MacAddr; + cbInfo = VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + + case OID_802_3_CURRENT_ADDRESS: + pvInfo = &pNetFlt->u.s.MacAddr; + cbInfo = VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + + case OID_802_3_MAXIMUM_LIST_SIZE: + u32Info = VBOXNETADP_MAX_MCAST_LIST; + break; + + case OID_802_3_MAC_OPTIONS: + case OID_802_3_RCV_ERROR_ALIGNMENT: + case OID_802_3_XMIT_ONE_COLLISION: + case OID_802_3_XMIT_MORE_COLLISIONS: + case OID_802_3_XMIT_DEFERRED: + case OID_802_3_XMIT_MAX_COLLISIONS: + case OID_802_3_RCV_OVERRUN: + case OID_802_3_XMIT_UNDERRUN: + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + case OID_802_3_XMIT_TIMES_CRS_LOST: + case OID_802_3_XMIT_LATE_COLLISIONS: + u32Info = 0; + break; + + default: + Status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + if (cbInfo <= InformationBufferLength) + { + *BytesWritten = cbInfo; + if (cbInfo) + NdisMoveMemory(InformationBuffer, pvInfo, cbInfo); + } + else + { + *BytesNeeded = cbInfo; + Status = NDIS_STATUS_INVALID_LENGTH; + } + } + + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinMpSetInformation(IN NDIS_HANDLE MiniportAdapterContext, + IN NDIS_OID Oid, + IN PVOID InformationBuffer, + IN ULONG InformationBufferLength, + OUT PULONG BytesRead, + OUT PULONG BytesNeeded) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS) MiniportAdapterContext; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Oid (%s)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid))); + + *BytesRead = 0; + *BytesNeeded = 0; + + switch (Oid) + { + case OID_802_3_MULTICAST_LIST: + *BytesRead = InformationBufferLength; + if (InformationBufferLength % VBOXNETADP_ETH_ADDRESS_LENGTH) + { + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + if (InformationBufferLength > (VBOXNETADP_MAX_MCAST_LIST * VBOXNETADP_ETH_ADDRESS_LENGTH)) + { + Status = NDIS_STATUS_MULTICAST_FULL; + *BytesNeeded = VBOXNETADP_MAX_MCAST_LIST * VBOXNETADP_ETH_ADDRESS_LENGTH; + break; + } + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + if (InformationBufferLength != sizeof (ULONG)) + { + *BytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + *BytesRead = InformationBufferLength; + + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + if (InformationBufferLength != sizeof (ULONG)){ + *BytesNeeded = sizeof(ULONG); + Status = NDIS_STATUS_INVALID_LENGTH; + break; + } + + break; + + case OID_PNP_SET_POWER: + if (InformationBufferLength >= sizeof(NDIS_DEVICE_POWER_STATE)) + { + NDIS_DEVICE_POWER_STATE *penmState = (NDIS_DEVICE_POWER_STATE*)InformationBuffer; + Status = vboxNetFltWinMpHandlePowerState(pNetFlt, *penmState); + } + else + { + Status = NDIS_STATUS_INVALID_LENGTH; + } + + if (Status == NDIS_STATUS_SUCCESS) + { + *BytesRead = sizeof (NDIS_DEVICE_POWER_STATE); + *BytesNeeded = 0; + } + else + { + *BytesRead = 0; + *BytesNeeded = sizeof (NDIS_DEVICE_POWER_STATE); + } + break; + + default: + Status = NDIS_STATUS_INVALID_OID; + break; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Oid (%s), Status (0x%x)\n", pNetFlt, vboxNetFltWinMpDumpOid(Oid), Status)); + + return Status; +} + +#endif + +#define VBOXNETFLTDUMP_STRCASE(_t) \ + case _t: return #_t; +#define VBOXNETFLTDUMP_STRCASE_UNKNOWN() \ + default: /*AssertFailed();*/ return "Unknown"; + +static const char* vboxNetFltWinMpDumpOid(ULONG oid) +{ + switch (oid) + { + VBOXNETFLTDUMP_STRCASE(OID_GEN_SUPPORTED_LIST) + VBOXNETFLTDUMP_STRCASE(OID_GEN_HARDWARE_STATUS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_SUPPORTED) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_IN_USE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_LOOKAHEAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_FRAME_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_LINK_SPEED) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_BUFFER_SPACE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RECEIVE_BUFFER_SPACE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_BLOCK_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RECEIVE_BLOCK_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_ID) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_DESCRIPTION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_CURRENT_PACKET_FILTER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_CURRENT_LOOKAHEAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DRIVER_VERSION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_TOTAL_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_PROTOCOL_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAC_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_CONNECT_STATUS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MAXIMUM_SEND_PACKETS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VENDOR_DRIVER_VERSION) + VBOXNETFLTDUMP_STRCASE(OID_GEN_SUPPORTED_GUIDS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_NETWORK_LAYER_ADDRESSES) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSPORT_HEADER_OFFSET) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MACHINE_NAME) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RNDIS_CONFIG_PARAMETER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_VLAN_ID) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_CAPABILITIES) + VBOXNETFLTDUMP_STRCASE(OID_GEN_PHYSICAL_MEDIUM) + VBOXNETFLTDUMP_STRCASE(OID_GEN_XMIT_OK) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_OK) + VBOXNETFLTDUMP_STRCASE(OID_GEN_XMIT_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_NO_BUFFER) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_BYTES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_FRAMES_XMIT) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DIRECTED_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MULTICAST_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_BYTES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_BROADCAST_FRAMES_RCV) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RCV_CRC_ERROR) + VBOXNETFLTDUMP_STRCASE(OID_GEN_TRANSMIT_QUEUE_LENGTH) + VBOXNETFLTDUMP_STRCASE(OID_GEN_GET_TIME_CAPS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_GET_NETCARD_TIME) + VBOXNETFLTDUMP_STRCASE(OID_GEN_NETCARD_LOAD) + VBOXNETFLTDUMP_STRCASE(OID_GEN_DEVICE_PROFILE) + VBOXNETFLTDUMP_STRCASE(OID_GEN_INIT_TIME_MS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_RESET_COUNTS) + VBOXNETFLTDUMP_STRCASE(OID_GEN_MEDIA_SENSE_COUNTS) + VBOXNETFLTDUMP_STRCASE(OID_PNP_CAPABILITIES) + VBOXNETFLTDUMP_STRCASE(OID_PNP_SET_POWER) + VBOXNETFLTDUMP_STRCASE(OID_PNP_QUERY_POWER) + VBOXNETFLTDUMP_STRCASE(OID_PNP_ADD_WAKE_UP_PATTERN) + VBOXNETFLTDUMP_STRCASE(OID_PNP_REMOVE_WAKE_UP_PATTERN) + VBOXNETFLTDUMP_STRCASE(OID_PNP_ENABLE_WAKE_UP) + VBOXNETFLTDUMP_STRCASE(OID_802_3_PERMANENT_ADDRESS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_CURRENT_ADDRESS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MULTICAST_LIST) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MAXIMUM_LIST_SIZE) + VBOXNETFLTDUMP_STRCASE(OID_802_3_MAC_OPTIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_RCV_ERROR_ALIGNMENT) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_ONE_COLLISION) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_MORE_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_DEFERRED) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_MAX_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_802_3_RCV_OVERRUN) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_UNDERRUN) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_HEARTBEAT_FAILURE) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_TIMES_CRS_LOST) + VBOXNETFLTDUMP_STRCASE(OID_802_3_XMIT_LATE_COLLISIONS) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_OFFLOAD) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_ADD_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_DELETE_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_SAN_SUPPORT) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_ADD_UDPESP_SA) + VBOXNETFLTDUMP_STRCASE(OID_TCP_TASK_IPSEC_DELETE_UDPESP_SA) + VBOXNETFLTDUMP_STRCASE_UNKNOWN() + } +} + +DECLHIDDEN(VOID) vboxNetFltWinMpReturnPacket(IN NDIS_HANDLE hMiniportAdapterContext, IN PNDIS_PACKET pPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hMiniportAdapterContext; + PVBOXNETFLT_PKTRSVD_MP pInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + PNDIS_PACKET pOrigPacket = pInfo->pOrigPacket; + PVOID pBufToFree = pInfo->pBufToFree; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (pOrigPacket) + { + /* the packet was sent from underlying miniport */ + NdisFreePacket(pPacket); + NdisReturnPackets(&pOrigPacket, 1); + } + else + { + /* the packet was sent from IntNet or it is a packet we allocated on PtReceive for TransferData processing */ + vboxNetFltWinFreeSGNdisPacket(pPacket, !pBufToFree /* bFreeMem */); + } + + if (pBufToFree) + { + vboxNetFltWinMemFree(pBufToFree); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static NDIS_STATUS vboxNetFltWinMpTransferData(OUT PNDIS_PACKET Packet, + OUT PUINT BytesTransferred, + IN NDIS_HANDLE hContext, + IN NDIS_HANDLE MiniportReceiveContext, + IN UINT ByteOffset, + IN UINT BytesToTransfer) +{ +#ifndef VBOXNETADP + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + NDIS_STATUS Status; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if ( vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) != NdisDeviceStateD0 + || vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.MpState) != NdisDeviceStateD0) + { + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, NDIS_STATUS_FAILURE)); + return NDIS_STATUS_FAILURE; + } + + NdisTransferData(&Status, pNetFlt->u.s.WinIf.hBinding, MiniportReceiveContext, + ByteOffset, BytesToTransfer, Packet, BytesTransferred); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + return Status; + +#else + RT_NOREF6(Packet, BytesTransferred, hContext, MiniportReceiveContext, ByteOffset, BytesToTransfer); + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", hContext)); + /* should never be here */ + AssertFailed(); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", hContext, NDIS_STATUS_FAILURE)); + return NDIS_STATUS_FAILURE; +#endif +} + +static void vboxNetFltWinMpHalt(IN NDIS_HANDLE hContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + NDIS_STATUS Status; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + +#ifndef VBOXNETADP + if (vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Disconnecting) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + vboxNetFltWinPtCloseInterface(pNetFlt, &Status); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else +#endif + { + /* we're NOT called from protocolUnbinAdapter, perform a full disconnect */ + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); +#ifndef VBOXNETADP + AssertBreakpoint(); +#endif + Status = vboxNetFltWinDetachFromInterface(pNetFlt, false); + Assert(Status == NDIS_STATUS_SUCCESS); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +/** + * register the miniport edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRegister(PVBOXNETFLTGLOBALS_MP pGlobalsMp, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr) +{ + NDIS_MINIPORT_CHARACTERISTICS MpChars; + + NdisMInitializeWrapper(&pGlobalsMp->hNdisWrapper, pDriverObject, pRegistryPathStr, NULL); + + NdisZeroMemory(&MpChars, sizeof (MpChars)); + + MpChars.MajorNdisVersion = VBOXNETFLT_VERSION_MP_NDIS_MAJOR; + MpChars.MinorNdisVersion = VBOXNETFLT_VERSION_MP_NDIS_MINOR; + + MpChars.HaltHandler = vboxNetFltWinMpHalt; + MpChars.InitializeHandler = vboxNetFltWinMpInitialize; + MpChars.QueryInformationHandler = vboxNetFltWinMpQueryInformation; + MpChars.SetInformationHandler = vboxNetFltWinMpSetInformation; + MpChars.TransferDataHandler = vboxNetFltWinMpTransferData; + MpChars.ReturnPacketHandler = vboxNetFltWinMpReturnPacket; + MpChars.SendPacketsHandler = vboxNetFltWinMpSendPackets; + +#ifndef VBOXNETADP + NDIS_STATUS Status = NdisIMRegisterLayeredMiniport(pGlobalsMp->hNdisWrapper, &MpChars, sizeof (MpChars), &pGlobalsMp->hMiniport); +#else + NDIS_STATUS Status = NdisMRegisterMiniport(pGlobalsMp->hNdisWrapper, &MpChars, sizeof (MpChars)); +#endif + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + NdisMRegisterUnloadHandler(pGlobalsMp->hNdisWrapper, vboxNetFltWinUnload); + } + + return Status; +} + +/** + * deregister the miniport edge + */ +DECLHIDDEN(VOID) vboxNetFltWinMpDeregister(PVBOXNETFLTGLOBALS_MP pGlobalsMp) +{ +#ifndef VBOXNETADP + NdisIMDeregisterLayeredMiniport(pGlobalsMp->hMiniport); +#endif + NdisTerminateWrapper(pGlobalsMp->hNdisWrapper, NULL); + + NdisZeroMemory(pGlobalsMp, sizeof (*pGlobalsMp)); +} + +#ifndef VBOXNETADP + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpInitializeDevideInstance(PVBOXNETFLTINS pThis) +{ + NDIS_STATUS Status; + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Initializing); + + Status = NdisIMInitializeDeviceInstanceEx(g_VBoxNetFltGlobalsWin.Mp.hMiniport, + &pThis->u.s.WinIf.MpDeviceName, + pThis); + if (Status == NDIS_STATUS_SUCCESS) + { + if (pThis->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + return NDIS_STATUS_SUCCESS; + } + AssertBreakpoint(); + vboxNetFltWinMpDeInitializeDeviceInstance(pThis, &Status); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + return pThis->u.s.WinIf.OpenCloseStatus; + } + + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + return Status; +} + +DECLHIDDEN(bool) vboxNetFltWinMpDeInitializeDeviceInstance(PVBOXNETFLTINS pThis, PNDIS_STATUS pStatus) +{ + NDIS_STATUS Status; + + if (vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initializing) + { + Status = NdisIMCancelInitializeDeviceInstance(g_VBoxNetFltGlobalsWin.Mp.hMiniport, &pThis->u.s.WinIf.MpDeviceName); + if (Status == NDIS_STATUS_SUCCESS) + { + /* we've canceled the initialization successfully */ + Assert(pThis->u.s.WinIf.hMiniport == NULL); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else + NdisWaitEvent(&pThis->u.s.WinIf.MpInitCompleteEvent, 0); + } + else + Status = NDIS_STATUS_SUCCESS; + + Assert( vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized + || vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + if (vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized) + { + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + + Status = NdisIMDeInitializeDeviceInstance(pThis->u.s.WinIf.hMiniport); + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + if (Status != NDIS_STATUS_SUCCESS) + Status = NDIS_STATUS_FAILURE; + + *pStatus = Status; + return true; + } + + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + + *pStatus = Status; + return false; +} + +#endif /* !VBOXNETADP */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h new file mode 100644 index 00000000..7a904aa8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM-win.h @@ -0,0 +1,67 @@ +/* $Id: VBoxNetFltM-win.h $ */ +/** @file + * VBoxNetFltM-win.h - Bridged Networking Driver, Windows Specific Code - Miniport edge API + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRegister(PVBOXNETFLTGLOBALS_MP pGlobalsMp, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr); +DECLHIDDEN(VOID) vboxNetFltWinMpDeregister(PVBOXNETFLTGLOBALS_MP pGlobalsMp); +DECLHIDDEN(VOID) vboxNetFltWinMpReturnPacket(IN NDIS_HANDLE hMiniportAdapterContext, IN PNDIS_PACKET pPacket); + +#ifdef VBOXNETADP +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoInitialization(PVBOXNETFLTINS pThis, NDIS_HANDLE hMiniportAdapter, NDIS_HANDLE hWrapperConfigurationContext); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpDoDeinitialization(PVBOXNETFLTINS pThis); + +#else + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpInitializeDevideInstance(PVBOXNETFLTINS pThis); +DECLHIDDEN(bool) vboxNetFltWinMpDeInitializeDeviceInstance(PVBOXNETFLTINS pThis, PNDIS_STATUS pStatus); + +DECLINLINE(VOID) vboxNetFltWinMpRequestStateComplete(PVBOXNETFLTINS pNetFlt) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = 0; + RTSpinlockRelease(pNetFlt->hSpinlock); +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMpRequestPost(PVBOXNETFLTINS pNetFlt); +#endif + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltM_win_h */ + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf new file mode 100644 index 00000000..fcf30fc8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltM.inf @@ -0,0 +1,82 @@ +; $Id: VBoxNetFltM.inf $ +;; @file +; VBoxNetFltM.inf - VirtualBox Bridged Networking Driver inf file Miniport edge +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +signature = "$Windows NT$" +;cat CatalogFile = VBoxNetFlt.cat +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %ORACLE% +;DriverPackageType=NdisImMiniport +;DriverPackageDisplayName=%VBoxNetFltMP_Desc% +;edit-DriverVer=08/13/2008,1.1.0.1 + +[ControlFlags] +ExcludeFromSelect = sun_VBoxNetFltmp + +[DestinationDirs] +DefaultDestDir=12 +; No files to copy + +[Manufacturer] +%ORACLE% = VBoxNetFltMP@COMMA-NT-ARCH@ + +[VBoxNetFltMP@DOT-NT-ARCH@] +%VBoxNetFltMP_Desc% = VBoxNetFltMP.ndi, sun_VBoxNetFltmp + +[VBoxNetFltMP.ndi] +Characteristics = 0x29 ;NCF_NOT_USER_REMOVABLE | NCF_VIRTUAL | NCF_HIDDEN +CopyFiles = + +[VBoxNetFltMP.ndi.Services] +AddService = VBoxNetFlt,0x2, VBoxNetFltMP.AddService + +[VBoxNetFltMP.AddService] +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 3 ;SERVICE_DEMAND_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetFlt.sys + +[VBoxNetFltMP.AddService.AddReg] + +[Strings] +ORACLE = "Oracle Corporation" +VBoxNetFltMP_Desc = "VirtualBox Bridged Networking Driver Miniport" + +[SourceDisksNames] + +[SourceDisksFiles] + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp new file mode 100644 index 00000000..3e06f3c2 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.cpp @@ -0,0 +1,1595 @@ +/* $Id: VBoxNetFltP-win.cpp $ */ +/** @file + * VBoxNetFltP-win.cpp - Bridged Networking Driver, Windows Specific Code. + * Protocol edge + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ +#include "VBoxNetFltCmn-win.h" + +#ifdef VBOXNETADP +# error "No protocol edge" +#endif + +#define VBOXNETFLT_PT_STATUS_IS_FILTERED(_s) (\ + (_s) == NDIS_STATUS_MEDIA_CONNECT \ + || (_s) == NDIS_STATUS_MEDIA_DISCONNECT \ + ) + +/** + * performs binding to the given adapter + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoBinding(PVBOXNETFLTINS pThis, PNDIS_STRING pOurDeviceName, PNDIS_STRING pBindToDeviceName) +{ + Assert(pThis->u.s.WinIf.PtState.PowerState == NdisDeviceStateD3); + Assert(pThis->u.s.WinIf.PtState.OpState == kVBoxNetDevOpState_Deinitialized); + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Initializing); + + NDIS_STATUS Status = vboxNetFltWinCopyString(&pThis->u.s.WinIf.MpDeviceName, pOurDeviceName); + Assert (Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinSetPowerState(&pThis->u.s.WinIf.PtState, NdisDeviceStateD0); + pThis->u.s.WinIf.OpenCloseStatus = NDIS_STATUS_SUCCESS; + + UINT iMedium; + NDIS_STATUS TmpStatus; + NDIS_MEDIUM aenmNdisMedium[] = + { + /* Ethernet */ + NdisMedium802_3, + /* Wan */ + NdisMediumWan + }; + + NdisResetEvent(&pThis->u.s.WinIf.OpenCloseEvent); + + NdisOpenAdapter(&Status, &TmpStatus, &pThis->u.s.WinIf.hBinding, &iMedium, + aenmNdisMedium, RT_ELEMENTS(aenmNdisMedium), + g_VBoxNetFltGlobalsWin.Pt.hProtocol, + pThis, + pBindToDeviceName, + 0, /* IN UINT OpenOptions, (reserved, should be NULL) */ + NULL /* IN PSTRING AddressingInformation OPTIONAL */ + ); + Assert(Status == NDIS_STATUS_PENDING || Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pThis->u.s.WinIf.OpenCloseEvent, 0); + Status = pThis->u.s.WinIf.OpenCloseStatus; + } + + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + Assert(pThis->u.s.WinIf.hBinding); + pThis->u.s.WinIf.enmMedium = aenmNdisMedium[iMedium]; + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Initialized); + + Status = vboxNetFltWinMpInitializeDevideInstance(pThis); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + return NDIS_STATUS_SUCCESS; + } + else + { + LogRelFunc(("vboxNetFltWinMpInitializeDevideInstance failed, Status 0x%x\n", Status)); + } + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinPtCloseInterface(pThis, &TmpStatus); + Assert(TmpStatus == NDIS_STATUS_SUCCESS); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + } + else + { + LogRelFunc(("NdisOpenAdapter failed, Status (0x%x)", Status)); + } + + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + pThis->u.s.WinIf.hBinding = NULL; + } + + return Status; +} + +static VOID vboxNetFltWinPtBindAdapter(OUT PNDIS_STATUS pStatus, + IN NDIS_HANDLE hBindContext, + IN PNDIS_STRING pDeviceNameStr, + IN PVOID pvSystemSpecific1, + IN PVOID pvSystemSpecific2) +{ + LogFlowFuncEnter(); + RT_NOREF2(hBindContext, pvSystemSpecific2); + + NDIS_STATUS Status; + NDIS_HANDLE hConfig = NULL; + + NdisOpenProtocolConfiguration(&Status, &hConfig, (PNDIS_STRING)pvSystemSpecific1); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + PNDIS_CONFIGURATION_PARAMETER pParam; + NDIS_STRING UppedBindStr = NDIS_STRING_CONST("UpperBindings"); + NdisReadConfiguration(&Status, &pParam, hConfig, &UppedBindStr, NdisParameterString); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLTINS pNetFlt; + Status = vboxNetFltWinPtInitBind(&pNetFlt, &pParam->ParameterData.StringData, pDeviceNameStr); + Assert(Status == NDIS_STATUS_SUCCESS); + } + + NdisCloseConfiguration(hConfig); + } + + *pStatus = Status; + + LogFlowFunc(("LEAVE: Status 0x%x\n", Status)); +} + +static VOID vboxNetFltWinPtOpenAdapterComplete(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS Status, IN NDIS_STATUS OpenErrorStatus) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + RT_NOREF1(OpenErrorStatus); + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Status (0x%x), OpenErrorStatus(0x%x)\n", pNetFlt, Status, OpenErrorStatus)); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + Assert(Status == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + pNetFlt->u.s.WinIf.OpenCloseStatus = Status; + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status != NDIS_STATUS_SUCCESS) + LogRelFunc(("Open Complete status is 0x%x", Status)); + } + else + LogRelFunc(("Adapter maintained status is 0x%x", pNetFlt->u.s.WinIf.OpenCloseStatus)); + NdisSetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x), OpenErrorStatus(0x%x)\n", pNetFlt, Status, OpenErrorStatus)); +} + +static void vboxNetFltWinPtRequestsWaitComplete(PVBOXNETFLTINS pNetFlt) +{ + /* wait for request to complete */ + while (vboxNetFltWinAtomicUoReadWinState(pNetFlt->u.s.WinIf.StateFlags).fRequestInfo == VBOXNDISREQUEST_INPROGRESS) + { + vboxNetFltWinSleep(2); + } + + /* + * If the below miniport is going to low power state, complete the queued request + */ + RTSpinlockAcquire(pNetFlt->hSpinlock); + if (pNetFlt->u.s.WinIf.StateFlags.fRequestInfo & VBOXNDISREQUEST_QUEUED) + { + /* mark the request as InProgress before posting it to RequestComplete */ + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + RTSpinlockRelease(pNetFlt->hSpinlock); + vboxNetFltWinPtRequestComplete(pNetFlt, &pNetFlt->u.s.WinIf.PassDownRequest, NDIS_STATUS_FAILURE); + } + else + { + RTSpinlockRelease(pNetFlt->hSpinlock); + } +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoUnbinding(PVBOXNETFLTINS pNetFlt, bool bOnUnbind) +{ + NDIS_STATUS Status; + uint64_t NanoTS = RTTimeSystemNanoTS(); + int cPPUsage; + + LogFlowFunc(("ENTER: pNetFlt 0x%p\n", pNetFlt)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + + ASMAtomicUoWriteBool(&pNetFlt->fDisconnectedFromHost, true); + ASMAtomicUoWriteBool(&pNetFlt->fRediscoveryPending, false); + ASMAtomicUoWriteU64(&pNetFlt->NanoTSLastRediscovery, NanoTS); + + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitializing); + if (!bOnUnbind) + { + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitializing); + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinPtRequestsWaitComplete(pNetFlt); + + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.PtState); + + /* check packet pool is empty */ + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hSendPacketPool); + Assert(cPPUsage == 0); + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + if (!bOnUnbind || !vboxNetFltWinMpDeInitializeDeviceInstance(pNetFlt, &Status)) + { + vboxNetFltWinPtCloseInterface(pNetFlt, &Status); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); + + if (!bOnUnbind) + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitializing); + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + } + } + else + { + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); + } + + LogFlowFunc(("LEAVE: pNetFlt 0x%p\n", pNetFlt)); + + return Status; +} + +static VOID vboxNetFltWinPtUnbindAdapter(OUT PNDIS_STATUS pStatus, + IN NDIS_HANDLE hContext, + IN NDIS_HANDLE hUnbindContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + RT_NOREF1(hUnbindContext); + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + *pStatus = vboxNetFltWinDetachFromInterface(pNetFlt, true); + Assert(*pStatus == NDIS_STATUS_SUCCESS); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static VOID vboxNetFltWinPtUnloadProtocol() +{ + LogFlowFuncEnter(); + NDIS_STATUS Status = vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); + Assert(Status == NDIS_STATUS_SUCCESS); NOREF(Status); + LogFlowFunc(("LEAVE: PtDeregister Status (0x%x)\n", Status)); +} + + +static VOID vboxNetFltWinPtCloseAdapterComplete(IN NDIS_HANDLE ProtocolBindingContext, IN NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)ProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + Assert(Status == NDIS_STATUS_SUCCESS); + Assert(pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) + { + pNetFlt->u.s.WinIf.OpenCloseStatus = Status; + } + NdisSetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), Status (0x%x)\n", pNetFlt, Status)); +} + +static VOID vboxNetFltWinPtResetComplete(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS Status) +{ + RT_NOREF2(hProtocolBindingContext, Status); + LogFlowFunc(("ENTER: pNetFlt 0x%p, Status 0x%x\n", hProtocolBindingContext, Status)); + /* + * should never be here + */ + AssertFailed(); + LogFlowFunc(("LEAVE: pNetFlt 0x%p, Status 0x%x\n", hProtocolBindingContext, Status)); +} + +static NDIS_STATUS vboxNetFltWinPtHandleQueryInfoComplete(PVBOXNETFLTINS pNetFlt, NDIS_STATUS Status) +{ + PNDIS_REQUEST pRequest = &pNetFlt->u.s.WinIf.PassDownRequest; + + switch (pRequest->DATA.QUERY_INFORMATION.Oid) + { + case OID_PNP_CAPABILITIES: + { + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (NDIS_PNP_CAPABILITIES)) + { + PNDIS_PNP_CAPABILITIES pPnPCaps = (PNDIS_PNP_CAPABILITIES)(pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + PNDIS_PM_WAKE_UP_CAPABILITIES pPmWuCaps = &pPnPCaps->WakeUpCapabilities; + pPmWuCaps->MinMagicPacketWakeUp = NdisDeviceStateUnspecified; + pPmWuCaps->MinPatternWakeUp = NdisDeviceStateUnspecified; + pPmWuCaps->MinLinkChangeWakeUp = NdisDeviceStateUnspecified; + *pNetFlt->u.s.WinIf.pcPDRBytesRW = sizeof (NDIS_PNP_CAPABILITIES); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = 0; + Status = NDIS_STATUS_SUCCESS; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof(NDIS_PNP_CAPABILITIES); + Status = NDIS_STATUS_RESOURCES; + } + } + break; + } + + case OID_GEN_MAC_OPTIONS: + { + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + pNetFlt->u.s.WinIf.fMacOptions = *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer; +#ifndef VBOX_LOOPBACK_USEFLAGS + /* clearing this flag tells ndis we'll handle loopback ourselves + * the ndis layer or nic driver below us would loopback packets as necessary */ + *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer &= ~NDIS_MAC_OPTION_NO_LOOPBACK; +#else + /* we have to catch loopbacks from the underlying driver, so no duplications will occur, + * just indicate NDIS to handle loopbacks for the packets coming from the protocol */ + *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer |= NDIS_MAC_OPTION_NO_LOOPBACK; +#endif + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + break; + } + + case OID_GEN_CURRENT_PACKET_FILTER: + { + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + /* we're here _ONLY_ in the passthru mode */ + Assert(pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter && !pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt); + if (pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter && !pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt) + { + Assert(pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); + vboxNetFltWinDereferenceModePassThru(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.QUERY_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + /* the filter request is issued below only in case netflt is not active, + * simply update the cache here */ + /* cache the filter used by upper protocols */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *(PULONG)pRequest->DATA.QUERY_INFORMATION.InformationBuffer; + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + } + break; + } + + default: + Assert(pRequest->DATA.QUERY_INFORMATION.Oid != OID_PNP_QUERY_POWER); + break; + } + + *pNetFlt->u.s.WinIf.pcPDRBytesRW = pRequest->DATA.QUERY_INFORMATION.BytesWritten; + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = pRequest->DATA.QUERY_INFORMATION.BytesNeeded; + + return Status; +} + +static NDIS_STATUS vboxNetFltWinPtHandleSetInfoComplete(PVBOXNETFLTINS pNetFlt, NDIS_STATUS Status) +{ + PNDIS_REQUEST pRequest = &pNetFlt->u.s.WinIf.PassDownRequest; + + switch (pRequest->DATA.SET_INFORMATION.Oid) + { + case OID_GEN_CURRENT_PACKET_FILTER: + { + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + Assert(Status == NDIS_STATUS_SUCCESS); + if (pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter) + { + if (pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt) + { + Assert(pNetFlt->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE); + pNetFlt->u.s.WinIf.StateFlags.fPPFNetFlt = 0; + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.SET_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + pNetFlt->u.s.WinIf.fOurSetFilter = *((PULONG)pRequest->DATA.SET_INFORMATION.InformationBuffer); + Assert(pNetFlt->u.s.WinIf.fOurSetFilter == NDIS_PACKET_TYPE_PROMISCUOUS); + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else + { + Assert(pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); + + if (Status == NDIS_STATUS_SUCCESS) + { + if (pRequest->DATA.SET_INFORMATION.InformationBufferLength >= sizeof (ULONG)) + { + /* the request was issued when the netflt was not active, simply update the cache here */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = *((PULONG)pRequest->DATA.SET_INFORMATION.InformationBuffer); + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + else + { + AssertFailed(); + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = sizeof (ULONG); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + + pNetFlt->u.s.WinIf.StateFlags.fProcessingPacketFilter = 0; + vboxNetFltWinDereferenceWinIf(pNetFlt); + } +#ifdef DEBUG_misha + else + { + AssertFailed(); + } +#endif + } + break; + } + + default: + Assert(pRequest->DATA.SET_INFORMATION.Oid != OID_PNP_SET_POWER); + break; + } + + *pNetFlt->u.s.WinIf.pcPDRBytesRW = pRequest->DATA.SET_INFORMATION.BytesRead; + *pNetFlt->u.s.WinIf.pcPDRBytesNeeded = pRequest->DATA.SET_INFORMATION.BytesNeeded; + + return Status; +} + +DECLHIDDEN(VOID) vboxNetFltWinPtRequestComplete(NDIS_HANDLE hContext, PNDIS_REQUEST pNdisRequest, NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hContext; + PNDIS_REQUEST pSynchRequest = pNetFlt->u.s.WinIf.pSynchRequest; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); + + if (pSynchRequest == pNdisRequest) + { + /* asynchronous completion of our sync request */ + /*1.set the status */ + pNetFlt->u.s.WinIf.SynchCompletionStatus = Status; + /* 2. set event */ + KeSetEvent(&pNetFlt->u.s.WinIf.hSynchCompletionEvent, 0, FALSE); + /* 3. return; */ + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); + return; + } + + Assert(&pNetFlt->u.s.WinIf.PassDownRequest == pNdisRequest); + Assert(pNetFlt->u.s.WinIf.StateFlags.fRequestInfo == VBOXNDISREQUEST_INPROGRESS); + vboxNetFltWinMpRequestStateComplete(pNetFlt); + + switch (pNdisRequest->RequestType) + { + case NdisRequestQueryInformation: + Status = vboxNetFltWinPtHandleQueryInfoComplete(pNetFlt, Status); + NdisMQueryInformationComplete(pNetFlt->u.s.WinIf.hMiniport, Status); + break; + + case NdisRequestSetInformation: + Status = vboxNetFltWinPtHandleSetInfoComplete(pNetFlt, Status); + NdisMSetInformationComplete(pNetFlt->u.s.WinIf.hMiniport, Status); + break; + + default: + AssertFailed(); + break; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pNdisRequest (0x%p), Status (0x%x)\n", pNetFlt, pNdisRequest, Status)); +} + +static VOID vboxNetFltWinPtStatus(IN NDIS_HANDLE hProtocolBindingContext, IN NDIS_STATUS GeneralStatus, IN PVOID pvStatusBuffer, IN UINT cbStatusBuffer) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), GeneralStatus (0x%x)\n", pNetFlt, GeneralStatus)); + + if (vboxNetFltWinReferenceWinIf(pNetFlt)) + { + Assert(pNetFlt->u.s.WinIf.hMiniport); + + if (VBOXNETFLT_PT_STATUS_IS_FILTERED(GeneralStatus)) + { + pNetFlt->u.s.WinIf.MpIndicatedMediaStatus = GeneralStatus; + } + NdisMIndicateStatus(pNetFlt->u.s.WinIf.hMiniport, + GeneralStatus, + pvStatusBuffer, + cbStatusBuffer); + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + else + { + if (pNetFlt->u.s.WinIf.hMiniport != NULL + && VBOXNETFLT_PT_STATUS_IS_FILTERED(GeneralStatus) + ) + { + pNetFlt->u.s.WinIf.MpUnindicatedMediaStatus = GeneralStatus; + } + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), GeneralStatus (0x%x)\n", pNetFlt, GeneralStatus)); +} + + +static VOID vboxNetFltWinPtStatusComplete(IN NDIS_HANDLE hProtocolBindingContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (vboxNetFltWinReferenceWinIf(pNetFlt)) + { + NdisMIndicateStatusComplete(pNetFlt->u.s.WinIf.hMiniport); + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static VOID vboxNetFltWinPtSendComplete(IN NDIS_HANDLE hProtocolBindingContext, IN PNDIS_PACKET pPacket, IN NDIS_STATUS Status) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + PNDIS_PACKET pOrigPacket = pSendInfo->pOrigPacket; + PVOID pBufToFree = pSendInfo->pBufToFree; + LogFlowFunc(("ENTER: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x)\n", pNetFlt, pPacket, Status)); + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /** @todo for optimization we could check only for netflt-mode packets + * do it for all for now */ + vboxNetFltWinLbRemoveSendPacket(pNetFlt, pPacket); +#endif + + if (pOrigPacket) + { + NdisIMCopySendCompletePerPacketInfo(pOrigPacket, pPacket); + NdisFreePacket(pPacket); + /* the ptk was posted from the upperlying protocol */ + NdisMSendComplete(pNetFlt->u.s.WinIf.hMiniport, pOrigPacket, Status); + } + else + { + /* if the pOrigPacket is zero - the ptk was originated by netFlt send/receive + * need to free packet buffers */ + vboxNetFltWinFreeSGNdisPacket(pPacket, !pBufToFree); + } + + if (pBufToFree) + { + vboxNetFltWinMemFree(pBufToFree); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x)\n", pNetFlt, pPacket, Status)); +} + +/** + * removes searches for the packet in the list and removes it if found + * @return true if the packet was found and removed, false - otherwise + */ +static bool vboxNetFltWinRemovePacketFromList(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + return vboxNetFltWinInterlockedSearchListEntry(pList, &pTDR->ListEntry, true /* remove*/); +} + +/** + * puts the packet to the tail of the list + */ +static void vboxNetFltWinPutPacketToList(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket, PNDIS_BUFFER pOrigBuffer) +{ + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + pTDR->pOrigBuffer = pOrigBuffer; + vboxNetFltWinInterlockedPutTail(pList, &pTDR->ListEntry); +} + +static bool vboxNetFltWinPtTransferDataCompleteActive(PVBOXNETFLTINS pNetFltIf, PNDIS_PACKET pPacket, NDIS_STATUS Status) +{ + PNDIS_BUFFER pBuffer; + PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT pTDR; + + if (!vboxNetFltWinRemovePacketFromList(&pNetFltIf->u.s.WinIf.TransferDataList, pPacket)) + return false; + + pTDR = (PVBOXNETFLT_PKTRSVD_TRANSFERDATA_PT)pPacket->ProtocolReserved; + Assert(pTDR); + Assert(pTDR->pOrigBuffer); + + do + { + NdisUnchainBufferAtFront(pPacket, &pBuffer); + + Assert(pBuffer); + + NdisFreeBuffer(pBuffer); + + pBuffer = pTDR->pOrigBuffer; + + NdisChainBufferAtBack(pPacket, pBuffer); + + /* data transfer was initiated when the netFlt was active + * the netFlt is still retained by us + * 1. check if loopback + * 2. enqueue packet + * 3. release netFlt */ + + if (Status == NDIS_STATUS_SUCCESS) + { + +#ifdef VBOX_LOOPBACK_USEFLAGS + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + /* should not be here */ + AssertFailed(); + } +#else + PNDIS_PACKET pLb = vboxNetFltWinLbSearchLoopBack(pNetFltIf, pPacket, false); + if (pLb) + { +#ifndef DEBUG_NETFLT_RECV_TRANSFERDATA + /* should not be here */ + AssertFailed(); +#endif + if (!vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* the packet is not from int net, need to pass it up to the host */ + NdisMIndicateReceivePacket(pNetFltIf->u.s.WinIf.hMiniport, &pPacket, 1); + /* dereference NetFlt, WinIf will be dereferenced on Packet return */ + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + break; + } + } +#endif + else + { + /* 2. enqueue */ + /* use the same packet info to put the packet in the processing packet queue */ + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + + VBOXNETFLT_LBVERIFY(pNetFltIf, pPacket); + + pRecvInfo->pOrigPacket = NULL; + pRecvInfo->pBufToFree = NULL; + + NdisGetPacketFlags(pPacket) = 0; +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFltIf, pPacket, 0)) + { + /* drop it */ + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + vboxNetFltWinDereferenceWinIf(pNetFltIf); + } + else + { + NdisMIndicateReceivePacket(pNetFltIf->u.s.WinIf.hMiniport, &pPacket, 1); + } + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + break; +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFltIf, pPacket, PACKET_MINE); + if (Status == NDIS_STATUS_SUCCESS) + { + break; + } + AssertFailed(); +# endif + } + } + else + { + AssertFailed(); + } + /* we are here because of error either in data transfer or in enqueueing the packet */ + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + vboxNetFltWinDereferenceNetFlt(pNetFltIf); + vboxNetFltWinDereferenceWinIf(pNetFltIf); + } while (0); + + return true; +} + +static VOID vboxNetFltWinPtTransferDataComplete(IN NDIS_HANDLE hProtocolBindingContext, + IN PNDIS_PACKET pPacket, + IN NDIS_STATUS Status, + IN UINT cbTransferred) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + LogFlowFunc(("ENTER: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x), cbTransfered (%d)\n", pNetFlt, pPacket, Status, cbTransferred)); + if (!vboxNetFltWinPtTransferDataCompleteActive(pNetFlt, pPacket, Status)) + { + if (pNetFlt->u.s.WinIf.hMiniport) + { + NdisMTransferDataComplete(pNetFlt->u.s.WinIf.hMiniport, + pPacket, + Status, + cbTransferred); + } + + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + /* else - all processing is done with vboxNetFltWinPtTransferDataCompleteActive already */ + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), pPacket (0x%p), Status (0x%x), cbTransfered (%d)\n", pNetFlt, pPacket, Status, cbTransferred)); +} + +static INT vboxNetFltWinRecvPacketPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + + PNDIS_PACKET pMyPacket; + NDIS_STATUS Status = vboxNetFltWinPrepareRecvPacket(pNetFlt, pPacket, &pMyPacket, true); + /* the Status holds the current packet status it will be checked for NDIS_STATUS_RESOURCES later + * (see below) */ + Assert(pMyPacket); + if (pMyPacket) + { + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + if (Status == NDIS_STATUS_RESOURCES) + { + NdisDprFreePacket(pMyPacket); + return 0; + } + + return 1; + } + + return 0; +} + +/** + * process the packet receive in a "passthru" mode + */ +static NDIS_STATUS vboxNetFltWinRecvPassThru(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + + NDIS_STATUS Status; + PNDIS_PACKET pMyPacket; + + NdisDprAllocatePacket(&Status, &pMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinCopyPacketInfoOnRecv(pMyPacket, pPacket, true /* force NDIS_STATUS_RESOURCES */); + Assert(NDIS_GET_PACKET_STATUS(pMyPacket) == NDIS_STATUS_RESOURCES); + + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + + NdisDprFreePacket(pMyPacket); + } + return Status; +} + +static VOID vboxNetFltWinRecvIndicatePassThru(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE MacReceiveContext, + PVOID pHeaderBuffer, UINT cbHeaderBuffer, PVOID pLookAheadBuffer, UINT cbLookAheadBuffer, UINT cbPacket) +{ + /* Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + ULONG Proc = KeGetCurrentProcessorNumber(); + Assert(Proc < RT_ELEMENTS(pNetFlt->u.s.WinIf.abIndicateRxComplete)); + pNetFlt->u.s.WinIf.abIndicateRxComplete[Proc] = TRUE; + switch (pNetFlt->u.s.WinIf.enmMedium) + { + case NdisMedium802_3: + case NdisMediumWan: + NdisMEthIndicateReceive(pNetFlt->u.s.WinIf.hMiniport, + MacReceiveContext, + (PCHAR)pHeaderBuffer, + cbHeaderBuffer, + pLookAheadBuffer, + cbLookAheadBuffer, + cbPacket); + break; + default: + AssertFailed(); + break; + } +} + +/** + * process the ProtocolReceive in an "active" mode + * + * @return NDIS_STATUS_SUCCESS - the packet is processed + * NDIS_STATUS_PENDING - the packet is being processed, we are waiting for the ProtocolTransferDataComplete to be called + * NDIS_STATUS_NOT_ACCEPTED - the packet is not needed - typically this is because this is a loopback packet + * NDIS_STATUS_FAILURE - packet processing failed + */ +static NDIS_STATUS vboxNetFltWinPtReceiveActive(PVBOXNETFLTINS pNetFlt, NDIS_HANDLE MacReceiveContext, PVOID pHeaderBuffer, UINT cbHeaderBuffer, + PVOID pLookaheadBuffer, UINT cbLookaheadBuffer, UINT cbPacket) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + do + { + if (cbHeaderBuffer != VBOXNETFLT_PACKET_ETHEADER_SIZE) + { + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + +#ifndef DEBUG_NETFLT_RECV_TRANSFERDATA + if (cbPacket == cbLookaheadBuffer) + { + PINTNETSG pSG; + PUCHAR pRcvData; +#ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb; +#endif + + /* allocate SG buffer */ + Status = vboxNetFltWinAllocSG(cbPacket + cbHeaderBuffer, &pSG); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + pRcvData = (PUCHAR)pSG->aSegs[0].pv; + + NdisMoveMappedMemory(pRcvData, pHeaderBuffer, cbHeaderBuffer); + + NdisCopyLookaheadData(pRcvData+cbHeaderBuffer, + pLookaheadBuffer, + cbLookaheadBuffer, + pNetFlt->u.s.WinIf.fMacOptions); +#ifndef VBOX_LOOPBACK_USEFLAGS + pLb = vboxNetFltWinLbSearchLoopBackBySG(pNetFlt, pSG, false); + if (pLb) + { +#ifndef DEBUG_NETFLT_RECV_NOPACKET + /* should not be here */ + AssertFailed(); +#endif + if (!vboxNetFltWinLbIsFromIntNet(pLb)) + { + PNDIS_PACKET pMyPacket; + pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, /* PVBOXNETFLTINS */ + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + false, /* bool bToWire */ + false); /* bool bCopyMemory */ + if (pMyPacket) + { + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + /* dereference the NetFlt here & indicate SUCCESS, which would mean the caller would not do a dereference + * the WinIf dereference will be done on packet return */ + vboxNetFltWinDereferenceNetFlt(pNetFlt); + Status = NDIS_STATUS_SUCCESS; + } + else + { + vboxNetFltWinMemFree(pSG); + Status = NDIS_STATUS_FAILURE; + } + } + else + { + vboxNetFltWinMemFree(pSG); + Status = NDIS_STATUS_NOT_ACCEPTED; + } + break; + } +#endif + VBOXNETFLT_LBVERIFYSG(pNetFlt, pSG); + + /* enqueue SG */ +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pSG, VBOXNETFLT_PACKET_SG)) + { + /* drop it */ + vboxNetFltWinMemFree(pSG); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + else + { + PNDIS_PACKET pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, /* PVBOXNETFLTINS */ + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + false, /* bool bToWire */ + false); /* bool bCopyMemory */ + Assert(pMyPacket); + if (pMyPacket) + { + NDIS_SET_PACKET_STATUS(pMyPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pMyPacket, pSG); + + LogFlow(("non-ndis packet info, packet created (%p)\n", pMyPacket)); + NdisMIndicateReceivePacket(pNetFlt->u.s.WinIf.hMiniport, &pMyPacket, 1); + } + else + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + Status = NDIS_STATUS_RESOURCES; + } + } + vboxNetFltWinDereferenceNetFlt(pNetFlt); +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFlt, pSG, PACKET_SG | PACKET_MINE); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + vboxNetFltWinMemFree(pSG); + break; + } +# endif +#endif + } + else + { + PNDIS_PACKET pPacket; + PNDIS_BUFFER pTransferBuffer; + PNDIS_BUFFER pOrigBuffer; + PUCHAR pMemBuf; + UINT cbBuf = cbPacket + cbHeaderBuffer; + UINT cbTransferred; + + /* allocate NDIS Packet buffer */ + NdisAllocatePacket(&Status, &pPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + VBOXNETFLT_OOB_INIT(pPacket); + +#ifdef VBOX_LOOPBACK_USEFLAGS + /* set "don't loopback" flags */ + NdisGetPacketFlags(pPacket) = g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + NdisGetPacketFlags(pPacket) = 0; +#endif + + Status = vboxNetFltWinMemAlloc((PVOID*)(&pMemBuf), cbBuf); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + NdisFreePacket(pPacket); + break; + } + NdisAllocateBuffer(&Status, &pTransferBuffer, pNetFlt->u.s.WinIf.hRecvBufferPool, pMemBuf + cbHeaderBuffer, cbPacket); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + NdisFreePacket(pPacket); + vboxNetFltWinMemFree(pMemBuf); + break; + } + + NdisAllocateBuffer(&Status, &pOrigBuffer, pNetFlt->u.s.WinIf.hRecvBufferPool, pMemBuf, cbBuf); + if (Status != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + NdisFreeBuffer(pTransferBuffer); + NdisFreePacket(pPacket); + vboxNetFltWinMemFree(pMemBuf); + break; + } + + NdisChainBufferAtBack(pPacket, pTransferBuffer); + + NdisMoveMappedMemory(pMemBuf, pHeaderBuffer, cbHeaderBuffer); + + vboxNetFltWinPutPacketToList(&pNetFlt->u.s.WinIf.TransferDataList, pPacket, pOrigBuffer); + +#ifdef DEBUG_NETFLT_RECV_TRANSFERDATA + if (cbPacket == cbLookaheadBuffer) + { + NdisCopyLookaheadData(pMemBuf+cbHeaderBuffer, + pLookaheadBuffer, + cbLookaheadBuffer, + pNetFlt->u.s.WinIf.fMacOptions); + } + else +#endif + { + Assert(cbPacket > cbLookaheadBuffer); + + NdisTransferData(&Status, pNetFlt->u.s.WinIf.hBinding, MacReceiveContext, + 0, /* ByteOffset */ + cbPacket, pPacket, &cbTransferred); + } + + if (Status != NDIS_STATUS_PENDING) + { + vboxNetFltWinPtTransferDataComplete(pNetFlt, pPacket, Status, cbTransferred); + } + } + } while (0); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinPtReceive(IN NDIS_HANDLE hProtocolBindingContext, + IN NDIS_HANDLE MacReceiveContext, + IN PVOID pHeaderBuffer, + IN UINT cbHeaderBuffer, + IN PVOID pLookAheadBuffer, + IN UINT cbLookAheadBuffer, + IN UINT cbPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + PNDIS_PACKET pPacket = NULL; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + bool bNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &bNetFltActive); + const bool bPassThruActive = !bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (fWinIfActive) + { + do + { +#ifndef DEBUG_NETFLT_RECV_NOPACKET + pPacket = NdisGetReceivedPacket(pNetFlt->u.s.WinIf.hBinding, MacReceiveContext); + if (pPacket) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb = NULL; +# else + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + AssertFailed(); + /* nothing else to do here, just return the packet */ + //NdisReturnPackets(&pPacket, 1); + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +# endif + + if (bNetFltActive) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + pLb = vboxNetFltWinLbSearchLoopBack(pNetFlt, pPacket, false); + if (!pLb) +# endif + { + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); + +# ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pPacket, 0)) + { + /* drop it */ + break; + } +# else + Status = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, PACKET_COPY); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + //NdisReturnPackets(&pPacket, 1); + fWinIfActive = false; + bNetFltActive = false; + break; + } +# endif + } +# ifndef VBOX_LOOPBACK_USEFLAGS + else if (vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* nothing else to do here, just return the packet */ + //NdisReturnPackets(&pPacket, 1); + Status = NDIS_STATUS_NOT_ACCEPTED; + break; + } + /* we are here because this is a looped back packet set not from intnet + * we will post it to the upper protocol */ +# endif + } + + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { +# ifndef VBOX_LOOPBACK_USEFLAGS + Assert(!pLb || !vboxNetFltWinLbIsFromIntNet(pLb)); +# endif + Status = vboxNetFltWinRecvPassThru(pNetFlt, pPacket); + Assert(Status == STATUS_SUCCESS); + /* we are done with packet processing, and we will + * not receive packet return event for this packet, + * fWinIfActive should be true to ensure we release WinIf*/ + Assert(fWinIfActive); + if (Status == STATUS_SUCCESS) + break; + } + else + { + /* intnet processing failed - fall back to no-packet mode */ + Assert(bNetFltActive); + Assert(fWinIfActive); + } + + } +#endif /* #ifndef DEBUG_NETFLT_RECV_NOPACKET */ + + if (bNetFltActive) + { + Status = vboxNetFltWinPtReceiveActive(pNetFlt, MacReceiveContext, pHeaderBuffer, cbHeaderBuffer, + pLookAheadBuffer, cbLookAheadBuffer, cbPacket); + if (NT_SUCCESS(Status)) + { + if (Status != NDIS_STATUS_NOT_ACCEPTED) + { + fWinIfActive = false; + bNetFltActive = false; + } + else + { +#ifndef VBOX_LOOPBACK_USEFLAGS + /* this is a loopback packet, nothing to do here */ +#else + AssertFailed(); + /* should not be here */ +#endif + } + break; + } + } + + /* we are done with packet processing, and we will + * not receive packet return event for this packet, + * fWinIfActive should be true to ensure we release WinIf*/ + Assert(fWinIfActive); + + vboxNetFltWinRecvIndicatePassThru(pNetFlt, MacReceiveContext, pHeaderBuffer, cbHeaderBuffer, pLookAheadBuffer, cbLookAheadBuffer, cbPacket); + /* the status could contain an error value here in case the IntNet recv failed, + * ensure we return back success status */ + Status = NDIS_STATUS_SUCCESS; + + } while (0); + + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else if (bPassThruActive) + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + if (fWinIfActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); + + return Status; + +} + +static VOID vboxNetFltWinPtReceiveComplete(NDIS_HANDLE hProtocolBindingContext) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + bool fNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &fNetFltActive); + NDIS_HANDLE hMiniport = pNetFlt->u.s.WinIf.hMiniport; + /* Note: we're using KeGetCurrentProcessorNumber, which is not entirely correct in case + * we're running on 64bit win7+, which can handle > 64 CPUs, however since KeGetCurrentProcessorNumber + * always returns the number < than the number of CPUs in the first group, we're guaranteed to have CPU index < 64 + * @todo: use KeGetCurrentProcessorNumberEx for Win7+ 64 and dynamically extended array */ + ULONG iProc = KeGetCurrentProcessorNumber(); + Assert(iProc < RT_ELEMENTS(pNetFlt->u.s.WinIf.abIndicateRxComplete)); + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (hMiniport != NULL && pNetFlt->u.s.WinIf.abIndicateRxComplete[iProc]) + { + switch (pNetFlt->u.s.WinIf.enmMedium) + { + case NdisMedium802_3: + case NdisMediumWan: + NdisMEthIndicateReceiveComplete(hMiniport); + break; + default: + AssertFailed(); + break; + } + } + + pNetFlt->u.s.WinIf.abIndicateRxComplete[iProc] = FALSE; + + if (fWinIfActive) + { + if (fNetFltActive) + vboxNetFltWinDereferenceNetFlt(pNetFlt); + else + vboxNetFltWinDereferenceModePassThru(pNetFlt); + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p)\n", pNetFlt)); +} + +static INT vboxNetFltWinPtReceivePacket(NDIS_HANDLE hProtocolBindingContext, PNDIS_PACKET pPacket) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + INT cRefCount = 0; + bool bNetFltActive; + bool fWinIfActive = vboxNetFltWinReferenceWinIfNetFlt(pNetFlt, &bNetFltActive); + const bool bPassThruActive = !bNetFltActive; + + LogFlowFunc(("ENTER: pNetFlt (0x%p)\n", pNetFlt)); + + if (fWinIfActive) + { + do + { +#ifdef VBOX_LOOPBACK_USEFLAGS + if (vboxNetFltWinIsLoopedBackPacket(pPacket)) + { + AssertFailed(); + Log(("lb_rp")); + + /* nothing else to do here, just return the packet */ + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + break; + } + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +#endif + + if (bNetFltActive) + { +#ifndef VBOX_LOOPBACK_USEFLAGS + PNDIS_PACKET pLb = vboxNetFltWinLbSearchLoopBack(pNetFlt, pPacket, false); + if (!pLb) +#endif + { +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + NDIS_STATUS fStatus; +#endif + bool fResources = NDIS_GET_PACKET_STATUS(pPacket) == NDIS_STATUS_RESOURCES; NOREF(fResources); + + VBOXNETFLT_LBVERIFY(pNetFlt, pPacket); +#ifdef DEBUG_misha + /** @todo remove this assert. + * this is a temporary assert for debugging purposes: + * we're probably doing something wrong with the packets if the miniport reports NDIS_STATUS_RESOURCES */ + Assert(!fResources); +#endif + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + if (vboxNetFltWinPostIntnet(pNetFlt, pPacket, 0)) + { + /* drop it */ + cRefCount = 0; + break; + } + +#else + fStatus = vboxNetFltWinQuEnqueuePacket(pNetFlt, pPacket, fResources ? PACKET_COPY : 0); + if (fStatus == NDIS_STATUS_SUCCESS) + { + bNetFltActive = false; + fWinIfActive = false; + if (fResources) + { + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + } + else + cRefCount = 1; + break; + } + else + { + AssertFailed(); + } +#endif + } +#ifndef VBOX_LOOPBACK_USEFLAGS + else if (vboxNetFltWinLbIsFromIntNet(pLb)) + { + /* the packet is from intnet, it has already been set to the host, + * no need for loopng it back to the host again */ + /* nothing else to do here, just return the packet */ + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + break; + } +#endif + } + + cRefCount = vboxNetFltWinRecvPacketPassThru(pNetFlt, pPacket); + if (cRefCount) + { + Assert(cRefCount == 1); + fWinIfActive = false; + } + + } while (FALSE); + + if (bNetFltActive) + { + vboxNetFltWinDereferenceNetFlt(pNetFlt); + } + else if (bPassThruActive) + { + vboxNetFltWinDereferenceModePassThru(pNetFlt); + } + if (fWinIfActive) + { + vboxNetFltWinDereferenceWinIf(pNetFlt); + } + } + else + { + cRefCount = 0; + //NdisReturnPackets(&pPacket, 1); + } + + LogFlowFunc(("LEAVE: pNetFlt (0x%p), cRefCount (%d)\n", pNetFlt, cRefCount)); + + return cRefCount; +} + +DECLHIDDEN(bool) vboxNetFltWinPtCloseInterface(PVBOXNETFLTINS pNetFlt, PNDIS_STATUS pStatus) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); + + if (pNetFlt->u.s.WinIf.StateFlags.fInterfaceClosing) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + AssertFailed(); + return false; + } + if (pNetFlt->u.s.WinIf.hBinding == NULL) + { + RTSpinlockRelease(pNetFlt->hSpinlock); + AssertFailed(); + return false; + } + + pNetFlt->u.s.WinIf.StateFlags.fInterfaceClosing = TRUE; + RTSpinlockRelease(pNetFlt->hSpinlock); + + NdisResetEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent); + NdisCloseAdapter(pStatus, pNetFlt->u.s.WinIf.hBinding); + if (*pStatus == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pNetFlt->u.s.WinIf.OpenCloseEvent, 0); + *pStatus = pNetFlt->u.s.WinIf.OpenCloseStatus; + } + + Assert (*pStatus == NDIS_STATUS_SUCCESS); + + pNetFlt->u.s.WinIf.hBinding = NULL; + + return true; +} + +static NDIS_STATUS vboxNetFltWinPtPnPSetPower(PVBOXNETFLTINS pNetFlt, NDIS_DEVICE_POWER_STATE enmPowerState) +{ + NDIS_DEVICE_POWER_STATE enmPrevPowerState = vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState); + + RTSpinlockAcquire(pNetFlt->hSpinlock); + + vboxNetFltWinSetPowerState(&pNetFlt->u.s.WinIf.PtState, enmPowerState); + + if (vboxNetFltWinGetPowerState(&pNetFlt->u.s.WinIf.PtState) > NdisDeviceStateD0) + { + if (enmPrevPowerState == NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = TRUE; + } + RTSpinlockRelease(pNetFlt->hSpinlock); + vboxNetFltWinPtRequestsWaitComplete(pNetFlt); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.MpState); + vboxNetFltWinWaitDereference(&pNetFlt->u.s.WinIf.PtState); + + /* check packet pool is empty */ + UINT cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hSendPacketPool); + Assert(cPPUsage == 0); + cPPUsage = NdisPacketPoolUsage(pNetFlt->u.s.WinIf.hRecvPacketPool); + Assert(cPPUsage == 0); + /* for debugging only, ignore the err in release */ + NOREF(cPPUsage); + + Assert(!pNetFlt->u.s.WinIf.StateFlags.fRequestInfo); + } + else + { + if (enmPrevPowerState > NdisDeviceStateD0) + { + pNetFlt->u.s.WinIf.StateFlags.fStandBy = FALSE; + } + + if (pNetFlt->u.s.WinIf.StateFlags.fRequestInfo & VBOXNDISREQUEST_QUEUED) + { + pNetFlt->u.s.WinIf.StateFlags.fRequestInfo = VBOXNDISREQUEST_INPROGRESS; + RTSpinlockRelease(pNetFlt->hSpinlock); + + vboxNetFltWinMpRequestPost(pNetFlt); + } + else + { + RTSpinlockRelease(pNetFlt->hSpinlock); + } + } + + return NDIS_STATUS_SUCCESS; +} + + +static NDIS_STATUS vboxNetFltWinPtPnPEvent(IN NDIS_HANDLE hProtocolBindingContext, IN PNET_PNP_EVENT pNetPnPEvent) +{ + PVBOXNETFLTINS pNetFlt = (PVBOXNETFLTINS)hProtocolBindingContext; + + LogFlowFunc(("ENTER: pNetFlt (0x%p), NetEvent (%d)\n", pNetFlt, pNetPnPEvent->NetEvent)); + + switch (pNetPnPEvent->NetEvent) + { + case NetEventSetPower: + { + NDIS_DEVICE_POWER_STATE enmPowerState = *((PNDIS_DEVICE_POWER_STATE)pNetPnPEvent->Buffer); + NDIS_STATUS rcNdis = vboxNetFltWinPtPnPSetPower(pNetFlt, enmPowerState); + LogFlowFunc(("LEAVE: pNetFlt (0x%p), NetEvent (%d), rcNdis=%#x\n", pNetFlt, pNetPnPEvent->NetEvent, rcNdis)); + return rcNdis; + } + + case NetEventReconfigure: + { + if (!pNetFlt) + { + NdisReEnumerateProtocolBindings(g_VBoxNetFltGlobalsWin.Pt.hProtocol); + } + } + /** @todo r=bird: Is the fall thru intentional?? */ + default: + LogFlowFunc(("LEAVE: pNetFlt (0x%p), NetEvent (%d)\n", pNetFlt, pNetPnPEvent->NetEvent)); + return NDIS_STATUS_SUCCESS; + } + +} + +#ifdef __cplusplus +# define PTCHARS_40(_p) ((_p).Ndis40Chars) +#else +# define PTCHARS_40(_p) (_p) +#endif + +/** + * register the protocol edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtRegister(PVBOXNETFLTGLOBALS_PT pGlobalsPt, PDRIVER_OBJECT pDriverObject, + PUNICODE_STRING pRegistryPathStr) +{ + RT_NOREF2(pDriverObject, pRegistryPathStr); + NDIS_PROTOCOL_CHARACTERISTICS PtChars; + NDIS_STRING NameStr; + + NdisInitUnicodeString(&NameStr, VBOXNETFLT_NAME_PROTOCOL); + + NdisZeroMemory(&PtChars, sizeof (PtChars)); + PTCHARS_40(PtChars).MajorNdisVersion = VBOXNETFLT_VERSION_PT_NDIS_MAJOR; + PTCHARS_40(PtChars).MinorNdisVersion = VBOXNETFLT_VERSION_PT_NDIS_MINOR; + + PTCHARS_40(PtChars).Name = NameStr; + PTCHARS_40(PtChars).OpenAdapterCompleteHandler = vboxNetFltWinPtOpenAdapterComplete; + PTCHARS_40(PtChars).CloseAdapterCompleteHandler = vboxNetFltWinPtCloseAdapterComplete; + PTCHARS_40(PtChars).SendCompleteHandler = vboxNetFltWinPtSendComplete; + PTCHARS_40(PtChars).TransferDataCompleteHandler = vboxNetFltWinPtTransferDataComplete; + PTCHARS_40(PtChars).ResetCompleteHandler = vboxNetFltWinPtResetComplete; + PTCHARS_40(PtChars).RequestCompleteHandler = vboxNetFltWinPtRequestComplete; + PTCHARS_40(PtChars).ReceiveHandler = vboxNetFltWinPtReceive; + PTCHARS_40(PtChars).ReceiveCompleteHandler = vboxNetFltWinPtReceiveComplete; + PTCHARS_40(PtChars).StatusHandler = vboxNetFltWinPtStatus; + PTCHARS_40(PtChars).StatusCompleteHandler = vboxNetFltWinPtStatusComplete; + PTCHARS_40(PtChars).BindAdapterHandler = vboxNetFltWinPtBindAdapter; + PTCHARS_40(PtChars).UnbindAdapterHandler = vboxNetFltWinPtUnbindAdapter; + PTCHARS_40(PtChars).UnloadHandler = vboxNetFltWinPtUnloadProtocol; +#if !defined(DEBUG_NETFLT_RECV) + PTCHARS_40(PtChars).ReceivePacketHandler = vboxNetFltWinPtReceivePacket; +#endif + PTCHARS_40(PtChars).PnPEventHandler = vboxNetFltWinPtPnPEvent; + + NDIS_STATUS Status; + NdisRegisterProtocol(&Status, &pGlobalsPt->hProtocol, &PtChars, sizeof (PtChars)); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +/** + * deregister the protocol edge + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDeregister(PVBOXNETFLTGLOBALS_PT pGlobalsPt) +{ + if (!pGlobalsPt->hProtocol) + return NDIS_STATUS_SUCCESS; + + NDIS_STATUS Status; + + NdisDeregisterProtocol(&Status, pGlobalsPt->hProtocol); + Assert (Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + NdisZeroMemory(pGlobalsPt, sizeof (*pGlobalsPt)); + } + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h new file mode 100644 index 00000000..11910676 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltP-win.h @@ -0,0 +1,52 @@ +/* $Id: VBoxNetFltP-win.h $ */ +/** @file + * VBoxNetFltP-win.h - Bridged Networking Driver, Windows Specific Code. + * Protocol edge API + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef VBOXNETADP +# error "No protocol edge" +#endif +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtRegister(PVBOXNETFLTGLOBALS_PT pGlobalsPt, PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDeregister(PVBOXNETFLTGLOBALS_PT pGlobalsPt); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoUnbinding(PVBOXNETFLTINS pNetFlt, bool bOnUnbind); +DECLHIDDEN(VOID) vboxNetFltWinPtRequestComplete(NDIS_HANDLE hContext, PNDIS_REQUEST pNdisRequest, NDIS_STATUS Status); +DECLHIDDEN(bool) vboxNetFltWinPtCloseInterface(PVBOXNETFLTINS pNetFlt, PNDIS_STATUS pStatus); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtDoBinding(PVBOXNETFLTINS pThis, PNDIS_STRING pOurDeviceName, PNDIS_STRING pBindToDeviceName); +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltP_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp new file mode 100644 index 00000000..44c7338c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.cpp @@ -0,0 +1,3650 @@ +/* $Id: VBoxNetFltRt-win.cpp $ */ +/** @file + * VBoxNetFltRt-win.cpp - Bridged Networking Driver, Windows Specific Runtime Code. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxNetFltCmn-win.h" +#include +#include + +#include +#include + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** represents the job element of the job queue + * see comments for VBOXNETFLT_JOB_QUEUE */ +typedef struct VBOXNETFLT_JOB +{ + /** link in the job queue */ + LIST_ENTRY ListEntry; + /** job function to be executed */ + PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine; + /** parameter to be passed to the job function */ + PVOID pContext; + /** event that will be fired on job completion */ + KEVENT CompletionEvent; + /** true if the job manager should use the completion even for completion indication, false-otherwise*/ + bool bUseCompletionEvent; +} VBOXNETFLT_JOB, *PVBOXNETFLT_JOB; + +/** + * represents the queue of jobs processed by the worker thread + * + * we use the thread to process tasks which are required to be done at passive level + * our callbacks may be called at APC level by IntNet, there are some tasks that we can not create at APC, + * e.g. thread creation. This is why we schedule such jobs to the worker thread working at passive level + */ +typedef struct VBOXNETFLT_JOB_QUEUE +{ + /* jobs */ + LIST_ENTRY Jobs; + /* we are using ExInterlocked..List functions to access the jobs list */ + KSPIN_LOCK Lock; + /** this event is used to initiate a job worker thread kill */ + KEVENT KillEvent; + /** this event is used to notify a worker thread that jobs are added to the queue */ + KEVENT NotifyEvent; + /** worker thread */ + PKTHREAD pThread; +} VBOXNETFLT_JOB_QUEUE, *PVBOXNETFLT_JOB_QUEUE; + +typedef struct _CREATE_INSTANCE_CONTEXT +{ +#ifndef VBOXNETADP + PNDIS_STRING pOurName; + PNDIS_STRING pBindToName; +#else + NDIS_HANDLE hMiniportAdapter; + NDIS_HANDLE hWrapperConfigurationContext; +#endif + NDIS_STATUS Status; +} CREATE_INSTANCE_CONTEXT, *PCREATE_INSTANCE_CONTEXT; + +/*contexts used for our jobs */ +/* Attach context */ +typedef struct _ATTACH_INFO +{ + PVBOXNETFLTINS pNetFltIf; + PCREATE_INSTANCE_CONTEXT pCreateContext; + bool fRediscovery; + int Status; +} ATTACH_INFO, *PATTACH_INFO; + +/* general worker context */ +typedef struct _WORKER_INFO +{ + PVBOXNETFLTINS pNetFltIf; + int Status; +} WORKER_INFO, *PWORKER_INFO; + +/* idc initialization */ +typedef struct _INIT_IDC_INFO +{ + VBOXNETFLT_JOB Job; + bool bInitialized; + volatile bool bStop; + volatile int rc; + KEVENT hCompletionEvent; +} INIT_IDC_INFO, *PINIT_IDC_INFO; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** global job queue. some operations are required to be done at passive level, e.g. thread creation, adapter bind/unbind initiation, + * while IntNet typically calls us APC_LEVEL, so we just create a system thread in our DriverEntry and enqueue the jobs to that thread */ +static VBOXNETFLT_JOB_QUEUE g_VBoxJobQueue; +volatile static bool g_bVBoxIdcInitialized; +INIT_IDC_INFO g_VBoxInitIdcInfo; + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; +/* win-specific global data */ +VBOXNETFLTGLOBALS_WIN g_VBoxNetFltGlobalsWin = {{{0}}}; + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define LIST_ENTRY_2_JOB(pListEntry) \ + ( (PVBOXNETFLT_JOB)((uint8_t *)(pListEntry) - RT_UOFFSETOF(VBOXNETFLT_JOB, ListEntry)) ) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxNetFltWinAttachToInterface(PVBOXNETFLTINS pThis, void * pContext, bool fRediscovery); +static int vboxNetFltWinConnectIt(PVBOXNETFLTINS pThis); +static int vboxNetFltWinFiniIdc(); +static void vboxNetFltWinFiniNetFltBase(); +static int vboxNetFltWinInitNetFltBase(); +static int vboxNetFltWinFiniNetFlt(); +static int vboxNetFltWinStartInitIdcProbing(); +static int vboxNetFltWinStopInitIdcProbing(); + + + +/** makes the current thread to sleep for the given number of miliseconds */ +DECLHIDDEN(void) vboxNetFltWinSleep(ULONG milis) +{ + RTThreadSleep(milis); +} + +/** wait for the given device to be dereferenced */ +DECLHIDDEN(void) vboxNetFltWinWaitDereference(PVBOXNETFLT_WINIF_DEVICE pState) +{ +#ifdef DEBUG + uint64_t StartNanoTS = RTTimeSystemNanoTS(); + uint64_t CurNanoTS; +#endif + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + while (ASMAtomicUoReadU32((volatile uint32_t *)&pState->cReferences)) + { + vboxNetFltWinSleep(2); +#ifdef DEBUG + CurNanoTS = RTTimeSystemNanoTS(); + if (CurNanoTS - StartNanoTS > 20000000) + { + LogRel(("device not idle")); + AssertFailed(); +// break; + } +#endif + } +} + +/** + * mem functions + */ +/* allocates and zeroes the nonpaged memory of a given size */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMemAlloc(PVOID *ppvMemBuf, UINT cbLength) +{ +#ifdef DEBUG_NETFLT_USE_EXALLOC + *ppvMemBuf = ExAllocatePoolWithTag(NonPagedPool, cbLength, VBOXNETFLT_MEM_TAG); + if (*ppvMemBuf) + { + NdisZeroMemory(*ppvMemBuf, cbLength); + return NDIS_STATUS_SUCCESS; + } + return NDIS_STATUS_FAILURE; +#else + NDIS_STATUS fStatus = NdisAllocateMemoryWithTag(ppvMemBuf, cbLength, VBOXNETFLT_MEM_TAG); + if (fStatus == NDIS_STATUS_SUCCESS) + NdisZeroMemory(*ppvMemBuf, cbLength); + return fStatus; +#endif +} + +/* frees memory allocated with vboxNetFltWinMemAlloc */ +DECLHIDDEN(void) vboxNetFltWinMemFree(PVOID pvMemBuf) +{ +#ifdef DEBUG_NETFLT_USE_EXALLOC + ExFreePool(pvMemBuf); +#else + NdisFreeMemory(pvMemBuf, 0, 0); +#endif +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +/* initializes packet info pool and allocates the cSize packet infos for the pool */ +static NDIS_STATUS vboxNetFltWinPpAllocatePacketInfoPool(PVBOXNETFLT_PACKET_INFO_POOL pPool, UINT cSize) +{ + UINT cbBufSize = sizeof(PACKET_INFO)*cSize; + PACKET_INFO * pPacketInfos; + NDIS_STATUS fStatus; + UINT i; + + Assert(cSize > 0); + + INIT_INTERLOCKED_PACKET_QUEUE(&pPool->Queue); + + fStatus = vboxNetFltWinMemAlloc((PVOID*)&pPacketInfos, cbBufSize); + + if (fStatus == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLTPACKET_INFO pInfo; + pPool->pBuffer = pPacketInfos; + + for (i = 0; i < cSize; i++) + { + pInfo = &pPacketInfos[i]; + vboxNetFltWinQuEnqueueTail(&pPool->Queue.Queue, pInfo); + pInfo->pPool = pPool; + } + } + else + { + AssertFailed(); + } + + return fStatus; +} + +/* frees the packet info pool */ +VOID vboxNetFltWinPpFreePacketInfoPool(PVBOXNETFLT_PACKET_INFO_POOL pPool) +{ + vboxNetFltWinMemFree(pPool->pBuffer); + + FINI_INTERLOCKED_PACKET_QUEUE(&pPool->Queue) +} + +#endif + +/** + * copies one string to another. in case the destination string size is not enough to hold the complete source string + * does nothing and returns NDIS_STATUS_RESOURCES . + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinCopyString(PNDIS_STRING pDst, PNDIS_STRING pSrc) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + if (pDst != pSrc) + { + if (pDst->MaximumLength < pSrc->Length) + { + AssertFailed(); + Status = NDIS_STATUS_RESOURCES; + } + else + { + pDst->Length = pSrc->Length; + + if (pDst->Buffer != pSrc->Buffer) + { + NdisMoveMemory(pDst->Buffer, pSrc->Buffer, pSrc->Length); + } + } + } + return Status; +} + +/************************************************************************************ + * PINTNETSG pSG manipulation functions + ************************************************************************************/ + +/* moves the contents of the given NDIS_BUFFER and all other buffers chained to it to the PINTNETSG + * the PINTNETSG is expected to contain one segment whose bugger is large enough to maintain + * the contents of the given NDIS_BUFFER and all other buffers chained to it */ +static NDIS_STATUS vboxNetFltWinNdisBufferMoveToSG0(PNDIS_BUFFER pBuffer, PINTNETSG pSG) +{ + PINTNETSEG paSeg; + uint8_t * ptr; + PVOID pVirtualAddress; + UINT cbCurrentLength; + NDIS_STATUS fStatus = NDIS_STATUS_SUCCESS; + + Assert(pSG->cSegsAlloc == 1); + + paSeg = pSG->aSegs; + ptr = (uint8_t*)paSeg->pv; + paSeg->cb = 0; + paSeg->Phys = NIL_RTHCPHYS; + pSG->cbTotal = 0; + + Assert(paSeg->pv); + + while (pBuffer) + { + NdisQueryBufferSafe(pBuffer, &pVirtualAddress, &cbCurrentLength, NormalPagePriority); + + if (!pVirtualAddress) + { + fStatus = NDIS_STATUS_FAILURE; + break; + } + + pSG->cbTotal += cbCurrentLength; + paSeg->cb += cbCurrentLength; + NdisMoveMemory(ptr, pVirtualAddress, cbCurrentLength); + ptr += cbCurrentLength; + + NdisGetNextBuffer(pBuffer, &pBuffer); + } + + if (fStatus == NDIS_STATUS_SUCCESS) + { + pSG->cSegsUsed = 1; + Assert(pSG->cbTotal == paSeg->cb); + } + return fStatus; +} + +/* converts the PNDIS_BUFFER to PINTNETSG by making the PINTNETSG segments to point to the memory buffers the + * ndis buffer(s) point to (as opposed to vboxNetFltWinNdisBufferMoveToSG0 which copies the memory from ndis buffers(s) to PINTNETSG) */ +static NDIS_STATUS vboxNetFltWinNdisBuffersToSG(PNDIS_BUFFER pBuffer, PINTNETSG pSG) +{ + UINT cSegs = 0; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + PVOID pVirtualAddress; + UINT cbCurrentLength; + + while (pBuffer) + { + NdisQueryBufferSafe(pBuffer, &pVirtualAddress, &cbCurrentLength, NormalPagePriority); + + if (!pVirtualAddress) + { + Status = NDIS_STATUS_FAILURE; + break; + } + + pSG->cbTotal += cbCurrentLength; + pSG->aSegs[cSegs].cb = cbCurrentLength; + pSG->aSegs[cSegs].pv = pVirtualAddress; + pSG->aSegs[cSegs].Phys = NIL_RTHCPHYS; + cSegs++; + + NdisGetNextBuffer(pBuffer, &pBuffer); + } + + AssertFatal(cSegs <= pSG->cSegsAlloc); + + if (Status == NDIS_STATUS_SUCCESS) + { + pSG->cSegsUsed = cSegs; + } + + return Status; +} + +static void vboxNetFltWinDeleteSG(PINTNETSG pSG) +{ + vboxNetFltWinMemFree(pSG); +} + +static PINTNETSG vboxNetFltWinCreateSG(uint32_t cSegs) +{ + PINTNETSG pSG; + NTSTATUS Status = vboxNetFltWinMemAlloc((PVOID*)&pSG, RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs])); + if (Status == STATUS_SUCCESS) + { + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, cSegs, 0 /*cSegsUsed*/); + return pSG; + } + + return NULL; +} + +/************************************************************************************ + * packet queue functions + ************************************************************************************/ +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +#if !defined(VBOXNETADP) +static NDIS_STATUS vboxNetFltWinQuPostPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PINTNETSG pSG, uint32_t fFlags +# ifdef DEBUG_NETFLT_PACKETS + , PNDIS_PACKET pTmpPacket +# endif + ) +{ + NDIS_STATUS Status; + PNDIS_PACKET pMyPacket; + bool bSrcHost = fFlags & PACKET_SRC_HOST; + + LogFlow(("posting packet back to driver stack..\n")); + + if (!pPacket) + { + /* INTNETSG was in the packet queue, create a new NdisPacket from INTNETSG*/ + pMyPacket = vboxNetFltWinNdisPacketFromSG(pNetFlt, + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + bSrcHost, /* bool bToWire */ + false); /* bool bCopyMemory */ + + Assert(pMyPacket); + + NDIS_SET_PACKET_STATUS(pMyPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pMyPacket, pSG); + +#ifdef DEBUG_NETFLT_PACKETS + Assert(pTmpPacket); + + DBG_CHECK_PACKET_AND_SG(pTmpPacket, pSG); + + DBG_CHECK_PACKETS(pTmpPacket, pMyPacket); +#endif + + LogFlow(("non-ndis packet info, packet created (%p)\n", pMyPacket)); + } + else + { + /* NDIS_PACKET was in the packet queue */ + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + + if (!(fFlags & PACKET_MINE)) + { + /* the packet is the one that was passed to us in send/receive callback + * According to the DDK, we can not post it further, + * instead we should allocate our own packet. + * So, allocate our own packet (pMyPacket) and copy the packet info there */ + if (bSrcHost) + { + Status = vboxNetFltWinPrepareSendPacket(pNetFlt, pPacket, &pMyPacket/*, true*/); + LogFlow(("packet from wire, packet created (%p)\n", pMyPacket)); + } + else + { + Status = vboxNetFltWinPrepareRecvPacket(pNetFlt, pPacket, &pMyPacket, false); + LogFlow(("packet from wire, packet created (%p)\n", pMyPacket)); + } + } + else + { + /* the packet enqueued is ours, simply assign pMyPacket and zero pPacket */ + pMyPacket = pPacket; + pPacket = NULL; + } + Assert(pMyPacket); + } + + if (pMyPacket) + { + /* we have successfully initialized our packet, post it to the host or to the wire */ + if (bSrcHost) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + vboxNetFltWinLbPutSendPacket(pNetFlt, pMyPacket, false /* bFromIntNet */); +#endif + NdisSend(&Status, pNetFlt->u.s.hBinding, pMyPacket); + + if (Status != NDIS_STATUS_PENDING) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /* the status is NOT pending, complete the packet */ + bool bTmp = vboxNetFltWinLbRemoveSendPacket(pNetFlt, pMyPacket); + Assert(bTmp); +#endif + if (pPacket) + { + LogFlow(("status is not pending, completing packet (%p)\n", pPacket)); + + NdisIMCopySendCompletePerPacketInfo (pPacket, pMyPacket); + + NdisFreePacket(pMyPacket); + } + else + { + /* should never be here since the PINTNETSG is stored only when the underlying miniport + * indicates NDIS_STATUS_RESOURCES, we should never have this when processing + * the "from-host" packets */ + AssertFailed(); + LogFlow(("status is not pending, freeing myPacket (%p)\n", pMyPacket)); + vboxNetFltWinFreeSGNdisPacket(pMyPacket, false); + } + } + } + else + { + NdisMIndicateReceivePacket(pNetFlt->u.s.hMiniport, &pMyPacket, 1); + + Status = NDIS_STATUS_PENDING; + /* the packet receive completion is always indicated via MiniportReturnPacket */ + } + } + else + { + /*we failed to create our packet */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} +#endif + +static bool vboxNetFltWinQuProcessInfo(PVBOXNETFLTINS pNetFltIf, PPACKET_QUEUE_WORKER pWorker, PVOID pvPacket, const UINT fFlags) +#else +DECLHIDDEN(bool) vboxNetFltWinPostIntnet(PVBOXNETFLTINS pNetFltIf, PVOID pvPacket, const UINT fFlags) +#endif +{ + PNDIS_PACKET pPacket = NULL; + PINTNETSG pSG = NULL; + NDIS_STATUS Status; +#ifndef VBOXNETADP + bool bSrcHost; + bool bDropIt; +# ifndef VBOXNETFLT_NO_PACKET_QUEUE + bool bPending; +# endif +#endif +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + bool bDeleteSG = false; +#endif +#ifdef DEBUG_NETFLT_PACKETS + /* packet used for matching */ + PNDIS_PACKET pTmpPacket = NULL; +#endif + +#ifndef VBOXNETADP + bSrcHost = (fFlags & VBOXNETFLT_PACKET_SRC_HOST) != 0; +#endif + + /* we first need to obtain the INTNETSG to be passed to intnet */ + + /* the queue may contain two "types" of packets: + * the NDIS_PACKET and the INTNETSG. + * I.e. on send/receive we typically enqueue the NDIS_PACKET passed to us by ndis, + * however in case our ProtocolReceive is called or the packet's status is set to NDIS_STSTUS_RESOURCES + * in ProtocolReceivePacket, we must return the packet immediately on ProtocolReceive*** exit + * In this case we allocate the INTNETSG, copy the ndis packet data there and enqueue it. + * In this case the packet info flags has the VBOXNETFLT_PACKET_SG fag set + * + * Besides that the NDIS_PACKET contained in the queue could be either the one passed to us in our send/receive callback + * or the one created by us. The latter is possible in case our ProtocolReceive callback is called and we call NdisTransferData + * in this case we need to allocate the packet the data to be transferred to. + * If the enqueued packet is the one allocated by us the VBOXNETFLT_PACKET_MINE flag is set + * */ + if ((fFlags & VBOXNETFLT_PACKET_SG) == 0) + { + /* we have NDIS_PACKET enqueued, we need to convert it to INTNETSG to be passed to intnet */ + PNDIS_BUFFER pCurrentBuffer = NULL; + UINT cBufferCount; + UINT cbPacketLength; + + pPacket = (PNDIS_PACKET)pvPacket; + + LogFlow(("ndis packet info, packet (%p)\n", pPacket)); + + LogFlow(("preparing pSG")); + NdisQueryPacket(pPacket, NULL, &cBufferCount, &pCurrentBuffer, &cbPacketLength); + Assert(cBufferCount); + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + pSG = vboxNetFltWinCreateSG(cBufferCount); +#else + /* we can not allocate the INTNETSG on stack since in this case we may get stack overflow + * somewhere outside of our driver (3 pages of system thread stack does not seem to be enough) + * + * since we have a "serialized" packet processing, i.e. all packets are being processed and passed + * to intnet by this thread, we just use one previously allocated INTNETSG which is stored in PVBOXNETFLTINS */ + pSG = pWorker->pSG; + + if (cBufferCount > pSG->cSegsAlloc) + { + pSG = vboxNetFltWinCreateSG(cBufferCount + 2); + if (pSG) + { + vboxNetFltWinDeleteSG(pWorker->pSG); + pWorker->pSG = pSG; + } + else + { + LogRel(("Failed to reallocate the pSG\n")); + } + } +#endif + + if (pSG) + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + bDeleteSG = true; +#endif + /* reinitialize */ + IntNetSgInitTempSegs(pSG, 0 /*cbTotal*/, pSG->cSegsAlloc, 0 /*cSegsUsed*/); + + /* convert the ndis buffers to INTNETSG */ + Status = vboxNetFltWinNdisBuffersToSG(pCurrentBuffer, pSG); + if (Status != NDIS_STATUS_SUCCESS) + { + pSG = NULL; + } + else + { + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + } + } + } + else + { + /* we have the INTNETSG enqueued. (see the above comment explaining why/when this may happen) + * just use the INTNETSG to pass it to intnet */ +#ifndef VBOXNETADP + /* the PINTNETSG is stored only when the underlying miniport + * indicates NDIS_STATUS_RESOURCES, we should never have this when processing + * the "from-host" packedts */ + Assert(!bSrcHost); +#endif + pSG = (PINTNETSG)pvPacket; + + LogFlow(("not ndis packet info, pSG (%p)\n", pSG)); + } + +#ifdef DEBUG_NETFLT_PACKETS + if (!pPacket && !pTmpPacket) + { + /* create tmp packet that woud be used for matching */ + pTmpPacket = vboxNetFltWinNdisPacketFromSG(pNetFltIf, + pSG, /* PINTNETSG */ + pSG, /* PVOID pBufToFree */ + bSrcHost, /* bool bToWire */ + true); /* bool bCopyMemory */ + + NDIS_SET_PACKET_STATUS(pTmpPacket, NDIS_STATUS_SUCCESS); + + DBG_CHECK_PACKET_AND_SG(pTmpPacket, pSG); + + Assert(pTmpPacket); + } +#endif + do + { +#ifndef VBOXNETADP + /* the pSG was successfully initialized, post it to the netFlt*/ + bDropIt = pSG ? pNetFltIf->pSwitchPort->pfnRecv(pNetFltIf->pSwitchPort, NULL /* pvIf */, pSG, + bSrcHost ? INTNETTRUNKDIR_HOST : INTNETTRUNKDIR_WIRE + ) + : false; +#else + if (pSG) + { + pNetFltIf->pSwitchPort->pfnRecv(pNetFltIf->pSwitchPort, NULL /* pvIf */, pSG, INTNETTRUNKDIR_HOST); + STATISTIC_INCREASE(pNetFltIf->u.s.WinIf.cTxSuccess); + } + else + { + STATISTIC_INCREASE(pNetFltIf->u.s.WinIf.cTxError); + } +#endif + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +# if !defined(VBOXNETADP) + if (!bDropIt) + { + Status = vboxNetFltWinQuPostPacket(pNetFltIf, pPacket, pSG, fFlags +# ifdef DEBUG_NETFLT_PACKETS + , pTmpPacket +# endif + ); + + if (Status == NDIS_STATUS_PENDING) + { + /* we will process packet completion in the completion routine */ + bPending = true; + break; + } + } + else +# endif + { + Status = NDIS_STATUS_SUCCESS; + } + + /* drop it */ + if (pPacket) + { + if (!(fFlags & PACKET_MINE)) + { +# if !defined(VBOXNETADP) + /* complete the packets */ + if (fFlags & PACKET_SRC_HOST) + { +# endif +/* NDIS_SET_PACKET_STATUS(pPacket, Status); */ + NdisMSendComplete(pNetFltIf->u.s.hMiniport, pPacket, Status); +# if !defined(VBOXNETADP) + } + else + { +# endif +# ifndef VBOXNETADP + NdisReturnPackets(&pPacket, 1); +# endif +# if !defined(VBOXNETADP) + } +# endif + } + else + { + Assert(!(fFlags & PACKET_SRC_HOST)); + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + } + } + else + { + Assert(pSG); + vboxNetFltWinMemFree(pSG); + } +# ifndef VBOXNETADP + bPending = false; +# endif + } while (0); + +#ifdef DEBUG_NETFLT_PACKETS + if (pTmpPacket) + { + vboxNetFltWinFreeSGNdisPacket(pTmpPacket, true); + } +#endif + +#ifndef VBOXNETADP + return bPending; +#else + return false; +#endif +#else /* #ifdef VBOXNETFLT_NO_PACKET_QUEUE */ + } while (0); + + if (bDeleteSG) + vboxNetFltWinMemFree(pSG); + +# ifndef VBOXNETADP + return bDropIt; +# else + return true; +# endif +#endif +} +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/* + * thread start function for the thread which processes the packets enqueued in our send and receive callbacks called by ndis + * + * ndis calls us at DISPATCH_LEVEL, while IntNet is using kernel functions which require Irqlu.s.PacketQueueWorker; + + PVOID apEvents[] = { + (PVOID)&pWorker->KillEvent, + (PVOID)&pWorker->NotifyEvent + }; + + while (fResume) + { + uint32_t cNumProcessed; + uint32_t cNumPostedToHostWire; + + fStatus = KeWaitForMultipleObjects(RT_ELEMENTS(apEvents), apEvents, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); + if (!NT_SUCCESS(fStatus) || fStatus == STATUS_WAIT_0) + { + /* "kill" event was set + * will process queued packets and exit */ + fResume = false; + } + + LogFlow(("processing vboxNetFltWinQuPacketQueueWorkerThreadProc\n")); + + cNumProcessed = 0; + cNumPostedToHostWire = 0; + + do + { + PVBOXNETFLTPACKET_INFO pInfo; + +#ifdef DEBUG_NETFLT_PACKETS + /* packet used for matching */ + PNDIS_PACKET pTmpPacket = NULL; +#endif + + /** @todo FIXME: !!! the better approach for performance would be to dequeue all packets at once + * and then go through all dequeued packets + * the same should be done for enqueue !!! */ + pInfo = vboxNetFltWinQuInterlockedDequeueHead(&pWorker->PacketQueue); + + if (!pInfo) + { + break; + } + + LogFlow(("found info (0x%p)\n", pInfo)); + + if (vboxNetFltWinQuProcessInfo(pNetFltIf, pWorker, pInfo->pPacket, pInfo->fFlags)) + { + cNumPostedToHostWire++; + } + + vboxNetFltWinPpFreePacketInfo(pInfo); + + cNumProcessed++; + } while (TRUE); + + if (cNumProcessed) + { + vboxNetFltWinDecReferenceNetFlt(pNetFltIf, cNumProcessed); + + Assert(cNumProcessed >= cNumPostedToHostWire); + + if (cNumProcessed != cNumPostedToHostWire) + { + vboxNetFltWinDecReferenceWinIf(pNetFltIf, cNumProcessed - cNumPostedToHostWire); + } + } + } + + PsTerminateSystemThread(STATUS_SUCCESS); +} +#endif +/** + * thread start function for the job processing thread + * + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobWorkerThreadProc(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + bool fResume = true; + NTSTATUS Status; + + PVOID apEvents[] = { + (PVOID)&pQueue->KillEvent, + (PVOID)&pQueue->NotifyEvent, + }; + + do + { + Status = KeWaitForMultipleObjects(RT_ELEMENTS(apEvents), apEvents, WaitAny, Executive, KernelMode, FALSE, NULL, NULL); + Assert(NT_SUCCESS(Status)); + if (!NT_SUCCESS(Status) || Status == STATUS_WAIT_0) + { + /* will process queued jobs and exit */ + Assert(Status == STATUS_WAIT_0); + fResume = false; + } + + do + { + PLIST_ENTRY pJobEntry = ExInterlockedRemoveHeadList(&pQueue->Jobs, &pQueue->Lock); + PVBOXNETFLT_JOB pJob; + + if (!pJobEntry) + break; + + pJob = LIST_ENTRY_2_JOB(pJobEntry); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + pJob->pfnRoutine(pJob->pContext); + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + if (pJob->bUseCompletionEvent) + { + KeSetEvent(&pJob->CompletionEvent, 1, FALSE); + } + } while (TRUE); + } while (fResume); + + Assert(Status == STATUS_WAIT_0); + + PsTerminateSystemThread(STATUS_SUCCESS); +} + +/** + * enqueues the job to the job queue to be processed by the job worker thread + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobEnqueueJob(PVBOXNETFLT_JOB_QUEUE pQueue, PVBOXNETFLT_JOB pJob, bool bEnqueueHead) +{ + if (bEnqueueHead) + { + ExInterlockedInsertHeadList(&pQueue->Jobs, &pJob->ListEntry, &pQueue->Lock); + } + else + { + ExInterlockedInsertTailList(&pQueue->Jobs, &pJob->ListEntry, &pQueue->Lock); + } + + KeSetEvent(&pQueue->NotifyEvent, 1, FALSE); +} + +DECLINLINE(VOID) vboxNetFltWinJobInit(PVBOXNETFLT_JOB pJob, PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext, bool bUseEvent) +{ + pJob->pfnRoutine = pfnRoutine; + pJob->pContext = pContext; + pJob->bUseCompletionEvent = bUseEvent; + if (bUseEvent) + KeInitializeEvent(&pJob->CompletionEvent, NotificationEvent, FALSE); +} + +/** + * enqueues the job to the job queue to be processed by the job worker thread and + * blocks until the job is done + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static VOID vboxNetFltWinJobSynchExec(PVBOXNETFLT_JOB_QUEUE pQueue, PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext) +{ + VBOXNETFLT_JOB Job; + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + vboxNetFltWinJobInit(&Job, pfnRoutine, pContext, true); + + vboxNetFltWinJobEnqueueJob(pQueue, &Job, false); + + KeWaitForSingleObject(&Job.CompletionEvent, Executive, KernelMode, FALSE, NULL); +} + +/** + * enqueues the job to be processed by the job worker thread at passive level and + * blocks until the job is done + */ +DECLHIDDEN(VOID) vboxNetFltWinJobSynchExecAtPassive(PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext) +{ + vboxNetFltWinJobSynchExec(&g_VBoxJobQueue, pfnRoutine, pContext); +} + +/** + * helper function used for system thread creation + */ +static NTSTATUS vboxNetFltWinQuCreateSystemThread(PKTHREAD *ppThread, PKSTART_ROUTINE pfnStartRoutine, PVOID pvStartContext) +{ + OBJECT_ATTRIBUTES ObjectAttributes; + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL); + + HANDLE hThread; + NTSTATUS Status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, &ObjectAttributes, NULL, NULL, (PKSTART_ROUTINE)pfnStartRoutine, pvStartContext); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Status = ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)ppThread, NULL); + Assert(Status == STATUS_SUCCESS); + ZwClose(hThread); + if (Status == STATUS_SUCCESS) + { + return STATUS_SUCCESS; + } + + /** @todo how would we fail in this case ?*/ + } + return Status; +} + +/** + * initialize the job queue + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static NTSTATUS vboxNetFltWinJobInitQueue(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + NTSTATUS fStatus; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + NdisZeroMemory(pQueue, sizeof(VBOXNETFLT_JOB_QUEUE)); + + KeInitializeEvent(&pQueue->KillEvent, NotificationEvent, FALSE); + + KeInitializeEvent(&pQueue->NotifyEvent, SynchronizationEvent, FALSE); + + InitializeListHead(&pQueue->Jobs); + + fStatus = vboxNetFltWinQuCreateSystemThread(&pQueue->pThread, (PKSTART_ROUTINE)vboxNetFltWinJobWorkerThreadProc, pQueue); + if (fStatus != STATUS_SUCCESS) + { + pQueue->pThread = NULL; + } + else + { + Assert(pQueue->pThread); + } + + return fStatus; +} + +/** + * deinitialize the job queue + * see comments for PVBOXNETFLT_JOB_QUEUE + */ +static void vboxNetFltWinJobFiniQueue(PVBOXNETFLT_JOB_QUEUE pQueue) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + if (pQueue->pThread) + { + KeSetEvent(&pQueue->KillEvent, 0, FALSE); + + KeWaitForSingleObject(pQueue->pThread, Executive, + KernelMode, FALSE, NULL); + } +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + +/** + * initializes the packet queue + * */ +DECLHIDDEN(NTSTATUS) vboxNetFltWinQuInitPacketQueue(PVBOXNETFLTINS pInstance) +{ + NTSTATUS Status; + PPACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + + AssertFatal(!pWorker->pSG); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + KeInitializeEvent(&pWorker->KillEvent, NotificationEvent, FALSE); + + KeInitializeEvent(&pWorker->NotifyEvent, SynchronizationEvent, FALSE); + + INIT_INTERLOCKED_PACKET_QUEUE(&pWorker->PacketQueue); + + do + { + Status = vboxNetFltWinPpAllocatePacketInfoPool(&pWorker->PacketInfoPool, VBOXNETFLT_PACKET_INFO_POOL_SIZE); + + if (Status == NDIS_STATUS_SUCCESS) + { + pWorker->pSG = vboxNetFltWinCreateSG(PACKET_QUEUE_SG_SEGS_ALLOC); + if (!pWorker->pSG) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + Status = vboxNetFltWinQuCreateSystemThread(&pWorker->pThread, (PKSTART_ROUTINE)vboxNetFltWinQuPacketQueueWorkerThreadProc, pInstance); + if (Status != STATUS_SUCCESS) + { + vboxNetFltWinPpFreePacketInfoPool(&pWorker->PacketInfoPool); + vboxNetFltWinMemFree(pWorker->pSG); + pWorker->pSG = NULL; + break; + } + } + + } while (0); + + return Status; +} + +/* + * deletes the packet queue + */ +DECLHIDDEN(void) vboxNetFltWinQuFiniPacketQueue(PVBOXNETFLTINS pInstance) +{ + PINTNETSG pSG; + PPACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + /* using the pPacketQueueSG as an indicator that the packet queue is initialized */ + RTSpinlockAcquire((pInstance)->hSpinlock); + if (pWorker->pSG) + { + pSG = pWorker->pSG; + pWorker->pSG = NULL; + RTSpinlockRelease((pInstance)->hSpinlock); + KeSetEvent(&pWorker->KillEvent, 0, FALSE); + + KeWaitForSingleObject(pWorker->pThread, Executive, + KernelMode, FALSE, NULL); + + vboxNetFltWinPpFreePacketInfoPool(&pWorker->PacketInfoPool); + + vboxNetFltWinDeleteSG(pSG); + + FINI_INTERLOCKED_PACKET_QUEUE(&pWorker->PacketQueue); + } + else + { + RTSpinlockRelease((pInstance)->hSpinlock); + } +} + +#endif + +/* + * creates the INTNETSG containing one segment pointing to the buffer of size cbBufSize + * the INTNETSG created should be cleaned with vboxNetFltWinMemFree + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinAllocSG(UINT cbPacket, PINTNETSG *ppSG) +{ + NDIS_STATUS Status; + PINTNETSG pSG; + + /* allocation: + * 1. SG_PACKET - with one aSegs pointing to + * 2. buffer of cbPacket containing the entire packet */ + AssertCompileSizeAlignment(INTNETSG, sizeof(PVOID)); + Status = vboxNetFltWinMemAlloc((PVOID*)&pSG, cbPacket + sizeof(INTNETSG)); + if (Status == NDIS_STATUS_SUCCESS) + { + IntNetSgInitTemp(pSG, pSG + 1, cbPacket); + LogFlow(("pSG created (%p)\n", pSG)); + *ppSG = pSG; + } + return Status; +} + +#ifndef VBOXNETFLT_NO_PACKET_QUEUE +/** + * put the packet info to the queue + */ +DECLINLINE(void) vboxNetFltWinQuEnqueueInfo(PVBOXNETFLTPACKET_QUEUE_WORKER pWorker, PVBOXNETFLTPACKET_INFO pInfo) +{ + vboxNetFltWinQuInterlockedEnqueueTail(&pWorker->PacketQueue, pInfo); + + KeSetEvent(&pWorker->NotifyEvent, IO_NETWORK_INCREMENT, FALSE); +} + +/** + * puts the packet to the queue + * + * @return NDIST_STATUS_SUCCESS iff the packet was enqueued successfully + * and error status otherwise. + * NOTE: that the success status does NOT mean that the packet processing is completed, but only that it was enqueued successfully + * the packet can be returned to the caller protocol/moniport only in case the bReleasePacket was set to true (in this case the copy of the packet was enqueued) + * or if vboxNetFltWinQuEnqueuePacket failed, i.e. the packet was NOT enqueued + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQuEnqueuePacket(PVBOXNETFLTINS pInstance, PVOID pPacket, const UINT fPacketFlags) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + PVBOXNETFLT_PACKET_QUEUE_WORKER pWorker = &pInstance->u.s.PacketQueueWorker; + NDIS_STATUS fStatus = NDIS_STATUS_SUCCESS; + + do + { + if (fPacketFlags & PACKET_COPY) + { + PNDIS_BUFFER pBuffer = NULL; + UINT cBufferCount; + UINT uBytesCopied = 0; + UINT cbPacketLength; + PINTNETSG pSG; + + /* the packet is Ndis packet */ + Assert(!(fPacketFlags & PACKET_SG)); + Assert(!(fPacketFlags & PACKET_MINE)); + + NdisQueryPacket((PNDIS_PACKET)pPacket, + NULL, + &cBufferCount, + &pBuffer, + &cbPacketLength); + + + Assert(cBufferCount); + + fStatus = vboxNetFltWinAllocSG(cbPacketLength, &pSG); + if (fStatus != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + break; + } + + pInfo = vboxNetFltWinPpAllocPacketInfo(&pWorker->PacketInfoPool); + + if (!pInfo) + { + AssertFailed(); + /** @todo what status to set? */ + fStatus = NDIS_STATUS_FAILURE; + vboxNetFltWinMemFree(pSG); + break; + } + + Assert(pInfo->pPool); + + /* the packet we are queueing is SG, add PACKET_SG to flags */ + SET_FLAGS_TO_INFO(pInfo, fPacketFlags | PACKET_SG); + SET_PACKET_TO_INFO(pInfo, pSG); + + fStatus = vboxNetFltWinNdisBufferMoveToSG0(pBuffer, pSG); + if (fStatus != NDIS_STATUS_SUCCESS) + { + AssertFailed(); + vboxNetFltWinPpFreePacketInfo(pInfo); + vboxNetFltWinMemFree(pSG); + break; + } + + DBG_CHECK_PACKET_AND_SG((PNDIS_PACKET)pPacket, pSG); + } + else + { + pInfo = vboxNetFltWinPpAllocPacketInfo(&pWorker->PacketInfoPool); + + if (!pInfo) + { + AssertFailed(); + /** @todo what status to set? */ + fStatus = NDIS_STATUS_FAILURE; + break; + } + + Assert(pInfo->pPool); + + SET_FLAGS_TO_INFO(pInfo, fPacketFlags); + SET_PACKET_TO_INFO(pInfo, pPacket); + } + + vboxNetFltWinQuEnqueueInfo(pWorker, pInfo); + + } while (0); + + return fStatus; +} +#endif + + +/* + * netflt + */ +#ifndef VBOXNETADP +static NDIS_STATUS vboxNetFltWinSynchNdisRequest(PVBOXNETFLTINS pNetFlt, PNDIS_REQUEST pRequest) +{ + int rc; + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + + /* 1. serialize */ + rc = RTSemFastMutexRequest(pNetFlt->u.s.WinIf.hSynchRequestMutex); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + NDIS_STATUS fRequestStatus = NDIS_STATUS_SUCCESS; + + /* 2. set pNetFlt->u.s.pSynchRequest */ + Assert(!pNetFlt->u.s.WinIf.pSynchRequest); + pNetFlt->u.s.WinIf.pSynchRequest = pRequest; + + /* 3. call NdisRequest */ + NdisRequest(&fRequestStatus, pNetFlt->u.s.WinIf.hBinding, pRequest); + + if (fRequestStatus == NDIS_STATUS_PENDING) + { + /* 3.1 if pending wait and assign the resulting status */ + KeWaitForSingleObject(&pNetFlt->u.s.WinIf.hSynchCompletionEvent, Executive, + KernelMode, FALSE, NULL); + + fRequestStatus = pNetFlt->u.s.WinIf.SynchCompletionStatus; + } + + /* 4. clear the pNetFlt->u.s.pSynchRequest */ + pNetFlt->u.s.WinIf.pSynchRequest = NULL; + + RTSemFastMutexRelease(pNetFlt->u.s.WinIf.hSynchRequestMutex); AssertRC(rc); + return fRequestStatus; + } + return NDIS_STATUS_FAILURE; +} + + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinGetMacAddress(PVBOXNETFLTINS pNetFlt, PRTMAC pMac) +{ + NDIS_REQUEST request; + NDIS_STATUS status; + request.RequestType = NdisRequestQueryInformation; + request.DATA.QUERY_INFORMATION.InformationBuffer = pMac; + request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(RTMAC); + request.DATA.QUERY_INFORMATION.Oid = OID_802_3_CURRENT_ADDRESS; + status = vboxNetFltWinSynchNdisRequest(pNetFlt, &request); + if (status != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + } + + return status; + +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQueryPhysicalMedium(PVBOXNETFLTINS pNetFlt, NDIS_PHYSICAL_MEDIUM * pMedium) +{ + NDIS_REQUEST Request; + NDIS_STATUS Status; + Request.RequestType = NdisRequestQueryInformation; + Request.DATA.QUERY_INFORMATION.InformationBuffer = pMedium; + Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(NDIS_PHYSICAL_MEDIUM); + Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_PHYSICAL_MEDIUM; + Status = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (Status != NDIS_STATUS_SUCCESS) + { + if (Status == NDIS_STATUS_NOT_SUPPORTED || Status == NDIS_STATUS_NOT_RECOGNIZED || Status == NDIS_STATUS_INVALID_OID) + { + Status = NDIS_STATUS_NOT_SUPPORTED; + } + else + { + LogRel(("OID_GEN_PHYSICAL_MEDIUM failed: Status (0x%x)", Status)); + AssertFailed(); + } + } + return Status; +} + +DECLHIDDEN(bool) vboxNetFltWinIsPromiscuous(PVBOXNETFLTINS pNetFlt) +{ + /** @todo r=bird: This is too slow and is probably returning the wrong + * information. What we're interested in is whether someone besides us + * has put the interface into promiscuous mode. */ + NDIS_REQUEST request; + NDIS_STATUS status; + ULONG filter; + Assert(VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)); + request.RequestType = NdisRequestQueryInformation; + request.DATA.QUERY_INFORMATION.InformationBuffer = &filter; + request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(filter); + request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + status = vboxNetFltWinSynchNdisRequest(pNetFlt, &request); + if (status != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return false; + } + return (filter & NDIS_PACKET_TYPE_PROMISCUOUS) != 0; +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinSetPromiscuous(PVBOXNETFLTINS pNetFlt, bool bYes) +{ +/** @todo Need to report changes to the switch via: + * pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, fPromisc); + */ + Assert(VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)); + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pNetFlt)) + { + NDIS_REQUEST Request; + NDIS_STATUS fStatus; + ULONG fFilter; + ULONG fExpectedFilter; + ULONG fOurFilter; + Request.RequestType = NdisRequestQueryInformation; + Request.DATA.QUERY_INFORMATION.InformationBuffer = &fFilter; + Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(fFilter); + Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + fStatus = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (fStatus != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return fStatus; + } + + if (!pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized) + { + /* the cache was not initialized yet, initiate it with the current filter value */ + pNetFlt->u.s.WinIf.fUpperProtocolSetFilter = fFilter; + pNetFlt->u.s.WinIf.StateFlags.fUpperProtSetFilterInitialized = TRUE; + } + + + if (bYes) + { + fExpectedFilter = NDIS_PACKET_TYPE_PROMISCUOUS; + fOurFilter = NDIS_PACKET_TYPE_PROMISCUOUS; + } + else + { + fExpectedFilter = pNetFlt->u.s.WinIf.fUpperProtocolSetFilter; + fOurFilter = 0; + } + + if (fExpectedFilter != fFilter) + { + Request.RequestType = NdisRequestSetInformation; + Request.DATA.SET_INFORMATION.InformationBuffer = &fExpectedFilter; + Request.DATA.SET_INFORMATION.InformationBufferLength = sizeof(fExpectedFilter); + Request.DATA.SET_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + fStatus = vboxNetFltWinSynchNdisRequest(pNetFlt, &Request); + if (fStatus != NDIS_STATUS_SUCCESS) + { + /** @todo */ + AssertFailed(); + return fStatus; + } + } + pNetFlt->u.s.WinIf.fOurSetFilter = fOurFilter; + return fStatus; + } + return NDIS_STATUS_NOT_SUPPORTED; +} + +#else /* VBOXNETADP */ + +/** + * Generates a new unique MAC address based on our vendor ID + */ +DECLHIDDEN(void) vboxNetFltWinGenerateMACAddress(RTMAC *pMac) +{ + /* temporary use a time info */ + uint64_t NanoTS = RTTimeSystemNanoTS(); + pMac->au8[0] = (uint8_t)((VBOXNETADP_VENDOR_ID >> 16) & 0xff); + pMac->au8[1] = (uint8_t)((VBOXNETADP_VENDOR_ID >> 8) & 0xff); + pMac->au8[2] = (uint8_t)(VBOXNETADP_VENDOR_ID & 0xff); + pMac->au8[3] = (uint8_t)(NanoTS & 0xff0000); + pMac->au16[2] = (uint16_t)(NanoTS & 0xffff); +} + +DECLHIDDEN(int) vboxNetFltWinMAC2NdisString(RTMAC *pMac, PNDIS_STRING pNdisString) +{ + static const char s_achDigits[17] = "0123456789abcdef"; + PWSTR pString; + + /* validate parameters */ + AssertPtrReturn(pMac, VERR_INVALID_PARAMETER); + AssertPtrReturn(pNdisString, VERR_INVALID_PARAMETER); + AssertReturn(pNdisString->MaximumLength >= 13*sizeof(pNdisString->Buffer[0]), VERR_INVALID_PARAMETER); + + pString = pNdisString->Buffer; + + for (int i = 0; i < 6; i++) + { + uint8_t u8 = pMac->au8[i]; + pString[0] = s_achDigits[(u8 >> 4) & 0xf]; + pString[1] = s_achDigits[(u8/*>>0*/)& 0xf]; + pString += 2; + } + + pNdisString->Length = 12*sizeof(pNdisString->Buffer[0]); + + *pString = L'\0'; + + return VINF_SUCCESS; +} + +static int vboxNetFltWinWchar2Byte(WCHAR c, uint8_t *pb) +{ + if (c >= L'A' && c <= L'F') + *pb = (c - L'A') + 10; + else if (c >= L'a' && c <= L'f') + *pb = (c - L'a') + 10; + else if (c >= L'0' && c <= L'9') + *pb = (c - L'0'); + else + return VERR_INVALID_PARAMETER; + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxNetFltWinMACFromNdisString(RTMAC *pMac, PNDIS_STRING pNdisString) +{ + + /* validate parameters */ + AssertPtrReturn(pMac, VERR_INVALID_PARAMETER); + AssertPtrReturn(pNdisString, VERR_INVALID_PARAMETER); + AssertReturn(pNdisString->Length >= 12*sizeof(pNdisString->Buffer[0]), VERR_INVALID_PARAMETER); + + int rc = VINF_SUCCESS; + PWSTR pString = pNdisString->Buffer; + for (int i = 0; i < 6; i++) + { + uint8_t v1, v2; + rc = vboxNetFltWinWchar2Byte(pString[0], &v1); + if (RT_FAILURE(rc)) + break; + + rc = vboxNetFltWinWchar2Byte(pString[1], &v2); + if (RT_FAILURE(rc)) + break; + + pMac->au8[i] = (v1 << 4) | v2; + + pString += 2; + } + + return rc; +} + +#endif /* VBOXNETADP */ + +/** + * creates a NDIS_PACKET from the PINTNETSG + */ +DECLHIDDEN(PNDIS_PACKET) vboxNetFltWinNdisPacketFromSG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, PVOID pBufToFree, bool bToWire, bool bCopyMemory) +{ + NDIS_STATUS fStatus; + PNDIS_PACKET pPacket; + + Assert(pSG->aSegs[0].pv); + Assert(pSG->cbTotal >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + +/** @todo Hrmpf, how can we fix this assumption? I fear this'll cause data + * corruption and maybe even BSODs ... */ + AssertReturn(pSG->cSegsUsed == 1 || bCopyMemory, NULL); + +#ifdef VBOXNETADP + NdisAllocatePacket(&fStatus, &pPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); +#else + NdisAllocatePacket(&fStatus, &pPacket, bToWire ? pNetFlt->u.s.WinIf.hSendPacketPool : pNetFlt->u.s.WinIf.hRecvPacketPool); +#endif + if (fStatus == NDIS_STATUS_SUCCESS) + { + PNDIS_BUFFER pBuffer; + PVOID pvMemBuf; + + /** @todo generally we do not always need to zero-initialize the complete OOB data here, reinitialize only when/what we need, + * however we DO need to reset the status for the packets we indicate via NdisMIndicateReceivePacket to avoid packet loss + * in case the status contains NDIS_STATUS_RESOURCES */ + VBOXNETFLT_OOB_INIT(pPacket); + + if (bCopyMemory) + { + fStatus = vboxNetFltWinMemAlloc(&pvMemBuf, pSG->cbTotal); + Assert(fStatus == NDIS_STATUS_SUCCESS); + if (fStatus == NDIS_STATUS_SUCCESS) + IntNetSgRead(pSG, pvMemBuf); + } + else + { + pvMemBuf = pSG->aSegs[0].pv; + } + if (fStatus == NDIS_STATUS_SUCCESS) + { +#ifdef VBOXNETADP + NdisAllocateBuffer(&fStatus, &pBuffer, + pNetFlt->u.s.WinIf.hRecvBufferPool, + pvMemBuf, + pSG->cbTotal); +#else + NdisAllocateBuffer(&fStatus, &pBuffer, + bToWire ? pNetFlt->u.s.WinIf.hSendBufferPool : pNetFlt->u.s.WinIf.hRecvBufferPool, + pvMemBuf, + pSG->cbTotal); +#endif + + if (fStatus == NDIS_STATUS_SUCCESS) + { + NdisChainBufferAtBack(pPacket, pBuffer); + + if (bToWire) + { + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + pSendInfo->pOrigPacket = NULL; + pSendInfo->pBufToFree = pBufToFree; +#ifdef VBOX_LOOPBACK_USEFLAGS + /* set "don't loopback" flags */ + NdisGetPacketFlags(pPacket) = g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + NdisGetPacketFlags(pPacket) = 0; +#endif + } + else + { + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)pPacket->MiniportReserved; + pRecvInfo->pOrigPacket = NULL; + pRecvInfo->pBufToFree = pBufToFree; + + /* we must set the header size on receive */ + NDIS_SET_PACKET_HEADER_SIZE(pPacket, VBOXNETFLT_PACKET_ETHEADER_SIZE); + /* NdisAllocatePacket zero-initializes the OOB data, + * but keeps the packet flags, clean them here */ + NdisGetPacketFlags(pPacket) = 0; + } + /** @todo set out of bound data */ + } + else + { + AssertFailed(); + if (bCopyMemory) + { + vboxNetFltWinMemFree(pvMemBuf); + } + NdisFreePacket(pPacket); + pPacket = NULL; + } + } + else + { + AssertFailed(); + NdisFreePacket(pPacket); + pPacket = NULL; + } + } + else + { + pPacket = NULL; + } + + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + + return pPacket; +} + +/* + * frees NDIS_PACKET created with vboxNetFltWinNdisPacketFromSG + */ +DECLHIDDEN(void) vboxNetFltWinFreeSGNdisPacket(PNDIS_PACKET pPacket, bool bFreeMem) +{ + UINT cBufCount; + PNDIS_BUFFER pFirstBuffer; + UINT uTotalPacketLength; + PNDIS_BUFFER pBuffer; + + NdisQueryPacket(pPacket, NULL, &cBufCount, &pFirstBuffer, &uTotalPacketLength); + + Assert(cBufCount == 1); + + do + { + NdisUnchainBufferAtBack(pPacket, &pBuffer); + if (pBuffer != NULL) + { + PVOID pvMemBuf; + UINT cbLength; + + NdisQueryBufferSafe(pBuffer, &pvMemBuf, &cbLength, NormalPagePriority); + NdisFreeBuffer(pBuffer); + if (bFreeMem) + { + vboxNetFltWinMemFree(pvMemBuf); + } + } + else + { + break; + } + } while (true); + + NdisFreePacket(pPacket); +} + +#if !defined(VBOXNETADP) +static void vboxNetFltWinAssociateMiniportProtocol(PVBOXNETFLTGLOBALS_WIN pGlobalsWin) +{ + NdisIMAssociateMiniport(pGlobalsWin->Mp.hMiniport, pGlobalsWin->Pt.hProtocol); +} +#endif + +/* + * NetFlt driver unload function + */ +DECLHIDDEN(VOID) vboxNetFltWinUnload(IN PDRIVER_OBJECT DriverObject) +{ + int rc; + UNREFERENCED_PARAMETER(DriverObject); + + LogFlowFunc(("ENTER: DO (0x%x)\n", DriverObject)); + + rc = vboxNetFltWinFiniIdc(); + if (RT_FAILURE(rc)) + { + /** @todo we can not prevent driver unload here */ + AssertFailed(); + + LogFlowFunc(("vboxNetFltWinFiniIdc - failed, busy.\n")); + } + + vboxNetFltWinJobFiniQueue(&g_VBoxJobQueue); +#ifndef VBOXNETADP + vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); +#endif + + vboxNetFltWinMpDeregister(&g_VBoxNetFltGlobalsWin.Mp); + +#ifndef VBOXNETADP + NdisFreeSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +#endif /* VBOXNETADP */ + + LogFlow(("LEAVE: DO (0x%x)\n", DriverObject)); + + vboxNetFltWinFiniNetFltBase(); + /* don't use logging or any RT after de-init */ +} + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + /* the idc registration is initiated via IOCTL since our driver + * can be loaded when the VBoxDrv is not in case we are a Ndis IM driver */ + rc = vboxNetFltWinInitNetFltBase(); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Status = vboxNetFltWinJobInitQueue(&g_VBoxJobQueue); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + ULONG MjVersion; + ULONG MnVersion; + + /* note: we do it after we initialize the Job Queue */ + vboxNetFltWinStartInitIdcProbing(); + + NdisZeroMemory(&g_VBoxNetFltGlobalsWin, sizeof (g_VBoxNetFltGlobalsWin)); + KeInitializeEvent(&g_VBoxNetFltGlobalsWin.SynchEvent, SynchronizationEvent, TRUE /* signalled*/); + + PsGetVersion(&MjVersion, &MnVersion, + NULL, /* PULONG BuildNumber OPTIONAL */ + NULL /* PUNICODE_STRING CSDVersion OPTIONAL */ + ); + + g_VBoxNetFltGlobalsWin.fPacketDontLoopBack = NDIS_FLAGS_DONT_LOOPBACK; + + if (MjVersion == 5 && MnVersion == 0) + { + /* this is Win2k, we don't support it actually, but just in case */ + g_VBoxNetFltGlobalsWin.fPacketDontLoopBack |= NDIS_FLAGS_SKIP_LOOPBACK_W2K; + } + + g_VBoxNetFltGlobalsWin.fPacketIsLoopedBack = NDIS_FLAGS_IS_LOOPBACK_PACKET; + +#ifndef VBOXNETADP + RTListInit(&g_VBoxNetFltGlobalsWin.listFilters); + NdisAllocateSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +#endif + + Status = vboxNetFltWinMpRegister(&g_VBoxNetFltGlobalsWin.Mp, DriverObject, RegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinPtRegister(&g_VBoxNetFltGlobalsWin.Pt, DriverObject, RegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) +#endif + { +#ifndef VBOXNETADP + vboxNetFltWinAssociateMiniportProtocol(&g_VBoxNetFltGlobalsWin); +#endif + return STATUS_SUCCESS; + +//#ifndef VBOXNETADP +// vboxNetFltWinPtDeregister(&g_VBoxNetFltGlobalsWin.Pt); +//#endif + } +#ifndef VBOXNETADP /* unreachable for VBOXNETADP because of the above return */ + vboxNetFltWinMpDeregister(&g_VBoxNetFltGlobalsWin.Mp); +# ifndef VBOXNETADP + NdisFreeSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); +# endif +#endif + } + vboxNetFltWinJobFiniQueue(&g_VBoxJobQueue); + } + vboxNetFltWinFiniNetFlt(); + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} + +#ifndef VBOXNETADP +/** + * creates and initializes the packet to be sent to the underlying miniport given a packet posted to our miniport edge + * according to DDK docs we must create our own packet rather than posting the one passed to us + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket) +{ + NDIS_STATUS Status; + + NdisAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hSendPacketPool); + + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLT_PKTRSVD_PT pSendInfo = (PVBOXNETFLT_PKTRSVD_PT)((*ppMyPacket)->ProtocolReserved); + pSendInfo->pOrigPacket = pPacket; + pSendInfo->pBufToFree = NULL; + /* the rest will be filled on send */ + + vboxNetFltWinCopyPacketInfoOnSend(*ppMyPacket, pPacket); + +#ifdef VBOX_LOOPBACK_USEFLAGS + NdisGetPacketFlags(*ppMyPacket) |= g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#endif + } + else + { + *ppMyPacket = NULL; + } + + return Status; +} + +/** + * creates and initializes the packet to be sent to the upperlying protocol given a packet indicated to our protocol edge + * according to DDK docs we must create our own packet rather than posting the one passed to us + */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareRecvPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket, bool bDpr) +{ + NDIS_STATUS Status; + + if (bDpr) + { + Assert(KeGetCurrentIrql() == DISPATCH_LEVEL); + NdisDprAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + } + else + { + NdisAllocatePacket(&Status, ppMyPacket, pNetFlt->u.s.WinIf.hRecvPacketPool); + } + + if (Status == NDIS_STATUS_SUCCESS) + { + PVBOXNETFLT_PKTRSVD_MP pRecvInfo = (PVBOXNETFLT_PKTRSVD_MP)((*ppMyPacket)->MiniportReserved); + pRecvInfo->pOrigPacket = pPacket; + pRecvInfo->pBufToFree = NULL; + + Status = vboxNetFltWinCopyPacketInfoOnRecv(*ppMyPacket, pPacket, false); + } + else + { + *ppMyPacket = NULL; + } + return Status; +} +#endif +/** + * initializes the VBOXNETFLTINS (our context structure) and binds to the given adapter + */ +#if defined(VBOXNETADP) +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, NDIS_HANDLE hMiniportAdapter, PNDIS_STRING pBindToMiniportName /* actually this is our miniport name*/, NDIS_HANDLE hWrapperConfigurationContext) +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, PNDIS_STRING pOurMiniportName, PNDIS_STRING pBindToMiniportName) +#endif +{ + NDIS_STATUS Status; + do + { + ANSI_STRING AnsiString; + int rc; + PVBOXNETFLTINS pInstance; + USHORT cbAnsiName = pBindToMiniportName->Length;/* the length is is bytes ; *2 ;RtlUnicodeStringToAnsiSize(pBindToMiniportName)*/ + CREATE_INSTANCE_CONTEXT Context; + +# ifndef VBOXNETADP + Context.pOurName = pOurMiniportName; + Context.pBindToName = pBindToMiniportName; +# else + Context.hMiniportAdapter = hMiniportAdapter; + Context.hWrapperConfigurationContext = hWrapperConfigurationContext; +# endif + Context.Status = NDIS_STATUS_SUCCESS; + + AnsiString.Buffer = 0; /* will be allocated by RtlUnicodeStringToAnsiString */ + AnsiString.Length = 0; + AnsiString.MaximumLength = cbAnsiName; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + Status = RtlUnicodeStringToAnsiString(&AnsiString, pBindToMiniportName, true); + + if (Status != STATUS_SUCCESS) + { + break; + } + + rc = vboxNetFltSearchCreateInstance(&g_VBoxNetFltGlobals, AnsiString.Buffer, &pInstance, &Context); + RtlFreeAnsiString(&AnsiString); + if (RT_FAILURE(rc)) + { + AssertFailed(); + Status = Context.Status != NDIS_STATUS_SUCCESS ? Context.Status : NDIS_STATUS_FAILURE; + break; + } + + Assert(pInstance); + + if (rc == VINF_ALREADY_INITIALIZED) + { + /* the case when our adapter was unbound while IntNet was connected to it */ + /* the instance remains valid until IntNet disconnects from it, we simply search and re-use it*/ + rc = vboxNetFltWinAttachToInterface(pInstance, &Context, true); + if (RT_FAILURE(rc)) + { + AssertFailed(); + Status = Context.Status != NDIS_STATUS_SUCCESS ? Context.Status : NDIS_STATUS_FAILURE; + /* release netflt */ + vboxNetFltRelease(pInstance, false); + + break; + } + } + + *ppNetFlt = pInstance; + + } while (FALSE); + + return Status; +} +/* + * deinitializes the VBOXNETFLTWIN + */ +DECLHIDDEN(VOID) vboxNetFltWinPtFiniWinIf(PVBOXNETFLTWIN pWinIf) +{ +#ifndef VBOXNETADP + int rc; +#endif + + LogFlowFunc(("ENTER: pWinIf 0x%p\n", pWinIf)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); +#ifndef VBOXNETADP + if (pWinIf->MpDeviceName.Buffer) + { + vboxNetFltWinMemFree(pWinIf->MpDeviceName.Buffer); + } + + FINI_INTERLOCKED_SINGLE_LIST(&pWinIf->TransferDataList); +# if defined(DEBUG_NETFLT_LOOPBACK) || !defined(VBOX_LOOPBACK_USEFLAGS) + FINI_INTERLOCKED_SINGLE_LIST(&pWinIf->SendPacketQueue); +# endif + NdisFreeBufferPool(pWinIf->hSendBufferPool); + NdisFreePacketPool(pWinIf->hSendPacketPool); + rc = RTSemFastMutexDestroy(pWinIf->hSynchRequestMutex); AssertRC(rc); +#endif + + /* NOTE: NULL is a valid handle */ + NdisFreeBufferPool(pWinIf->hRecvBufferPool); + NdisFreePacketPool(pWinIf->hRecvPacketPool); + + LogFlowFunc(("LEAVE: pWinIf 0x%p\n", pWinIf)); +} + +#ifndef VBOXNETADP +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf, IN PNDIS_STRING pOurDeviceName) +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf) +#endif +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; +#ifndef VBOXNETADP + int rc; +#endif + + LogFlowFunc(("ENTER: pWinIf 0x%p\n", pWinIf)); + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + NdisZeroMemory(pWinIf, sizeof (VBOXNETFLTWIN)); + NdisAllocatePacketPoolEx(&Status, &pWinIf->hRecvPacketPool, + VBOXNETFLT_PACKET_POOL_SIZE_NORMAL, + VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW, + PROTOCOL_RESERVED_SIZE_IN_PACKET); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + /* NOTE: NULL is a valid handle !!! */ + NdisAllocateBufferPool(&Status, &pWinIf->hRecvBufferPool, VBOXNETFLT_BUFFER_POOL_SIZE_RX); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pWinIf->MpState.PowerState = NdisDeviceStateD3; + vboxNetFltWinSetOpState(&pWinIf->MpState, kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + pWinIf->PtState.PowerState = NdisDeviceStateD3; + vboxNetFltWinSetOpState(&pWinIf->PtState, kVBoxNetDevOpState_Deinitialized); + + NdisAllocateBufferPool(&Status, + &pWinIf->hSendBufferPool, + VBOXNETFLT_BUFFER_POOL_SIZE_TX); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + INIT_INTERLOCKED_SINGLE_LIST(&pWinIf->TransferDataList); + +# if defined(DEBUG_NETFLT_LOOPBACK) || !defined(VBOX_LOOPBACK_USEFLAGS) + INIT_INTERLOCKED_SINGLE_LIST(&pWinIf->SendPacketQueue); +# endif + NdisInitializeEvent(&pWinIf->OpenCloseEvent); + + KeInitializeEvent(&pWinIf->hSynchCompletionEvent, SynchronizationEvent, FALSE); + + NdisInitializeEvent(&pWinIf->MpInitCompleteEvent); + + NdisAllocatePacketPoolEx(&Status, &pWinIf->hSendPacketPool, + VBOXNETFLT_PACKET_POOL_SIZE_NORMAL, + VBOXNETFLT_PACKET_POOL_SIZE_OVERFLOW, + sizeof (PVBOXNETFLT_PKTRSVD_PT)); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + rc = RTSemFastMutexCreate(&pWinIf->hSynchRequestMutex); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Status = vboxNetFltWinMemAlloc((PVOID*)&pWinIf->MpDeviceName.Buffer, pOurDeviceName->Length); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pWinIf->MpDeviceName.MaximumLength = pOurDeviceName->Length; + pWinIf->MpDeviceName.Length = 0; + Status = vboxNetFltWinCopyString(&pWinIf->MpDeviceName, pOurDeviceName); +#endif + return NDIS_STATUS_SUCCESS; +#ifndef VBOXNETADP + // unreachable: vboxNetFltWinMemFree(pWinIf->MpDeviceName.Buffer); + } + RTSemFastMutexDestroy(pWinIf->hSynchRequestMutex); + } + else + Status = NDIS_STATUS_FAILURE; + NdisFreePacketPool(pWinIf->hSendPacketPool); + } + NdisFreeBufferPool(pWinIf->hSendBufferPool); + } + NdisFreeBufferPool(pWinIf->hRecvBufferPool); +#endif + } + NdisFreePacketPool(pWinIf->hRecvPacketPool); + } + + LogFlowFunc(("LEAVE: pWinIf 0x%p, Status 0x%x\n", pWinIf, Status)); + + return Status; +} + +/** + * match packets + */ +#define NEXT_LIST_ENTRY(_Entry) ((_Entry)->Flink) +#define PREV_LIST_ENTRY(_Entry) ((_Entry)->Blink) +#define FIRST_LIST_ENTRY NEXT_LIST_ENTRY +#define LAST_LIST_ENTRY PREV_LIST_ENTRY + +#define MIN(_a, _b) ((_a) < (_b) ? (_a) : (_b)) + +#ifndef VBOXNETADP + +#ifdef DEBUG_misha + +RTMAC g_vboxNetFltWinVerifyMACBroadcast = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; +RTMAC g_vboxNetFltWinVerifyMACGuest = {0x08, 0x00, 0x27, 0x01, 0x02, 0x03}; + +DECLHIDDEN(PRTNETETHERHDR) vboxNetFltWinGetEthHdr(PNDIS_PACKET pPacket) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + RTNETETHERHDR* pEth; + UINT cbLength1 = 0; + UINT i = 0; + + NdisQueryPacket(pPacket, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + + Assert(pBuffer1); + Assert(uTotalPacketLength1 >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + if (uTotalPacketLength1 < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + NdisQueryBufferSafe(pBuffer1, &pEth, &cbLength1, NormalPagePriority); + Assert(cbLength1 >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + if (cbLength1 < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + return pEth; +} + +DECLHIDDEN(PRTNETETHERHDR) vboxNetFltWinGetEthHdrSG(PINTNETSG pSG) +{ + Assert(pSG->cSegsUsed); + Assert(pSG->cSegsAlloc >= pSG->cSegsUsed); + Assert(pSG->aSegs[0].cb >= VBOXNETFLT_PACKET_ETHEADER_SIZE); + + if (!pSG->cSegsUsed) + return NULL; + + if (pSG->aSegs[0].cb < VBOXNETFLT_PACKET_ETHEADER_SIZE) + return NULL; + + return (PRTNETETHERHDR)pSG->aSegs[0].pv; +} + +DECLHIDDEN(bool) vboxNetFltWinCheckMACs(PNDIS_PACKET pPacket, PRTMAC pDst, PRTMAC pSrc) +{ + PRTNETETHERHDR pHdr = vboxNetFltWinGetEthHdr(pPacket); + Assert(pHdr); + + if (!pHdr) + return false; + + if (pDst && memcmp(pDst, &pHdr->DstMac, sizeof(RTMAC))) + return false; + + if (pSrc && memcmp(pSrc, &pHdr->SrcMac, sizeof(RTMAC))) + return false; + + return true; +} + +DECLHIDDEN(bool) vboxNetFltWinCheckMACsSG(PINTNETSG pSG, PRTMAC pDst, PRTMAC pSrc) +{ + PRTNETETHERHDR pHdr = vboxNetFltWinGetEthHdrSG(pSG); + Assert(pHdr); + + if (!pHdr) + return false; + + if (pDst && memcmp(pDst, &pHdr->DstMac, sizeof(RTMAC))) + return false; + + if (pSrc && memcmp(pSrc, &pHdr->SrcMac, sizeof(RTMAC))) + return false; + + return true; +} +#endif + +# if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) +/* + * answers whether the two given packets match based on the packet length and the first cbMatch bytes of the packets + * if cbMatch < 0 matches complete packets. + */ +DECLHIDDEN(bool) vboxNetFltWinMatchPackets(PNDIS_PACKET pPacket1, PNDIS_PACKET pPacket2, const INT cbMatch) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + uint8_t *pbMemBuf1 = NULL; + UINT cbLength1 = 0; + + UINT cBufCount2; + PNDIS_BUFFER pBuffer2; + UINT uTotalPacketLength2; + uint8_t *pbMemBuf2 = NULL; + UINT cbLength2 = 0; + bool bMatch = true; + +#ifdef DEBUG_NETFLT_PACKETS + bool bCompleteMatch = false; +#endif + + NdisQueryPacket(pPacket1, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + NdisQueryPacket(pPacket2, NULL, &cBufCount2, &pBuffer2, &uTotalPacketLength2); + + Assert(pBuffer1); + Assert(pBuffer2); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + bMatch = false; + } + else + { + UINT ucbLength2Match = 0; + UINT ucbMatch; + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; +#ifdef DEBUG_NETFLT_PACKETS + bCompleteMatch = true; +#endif + } + else + { + ucbMatch = (UINT)cbMatch; + } + + for (;;) + { + if (!cbLength1) + { + NdisQueryBufferSafe(pBuffer1, &pbMemBuf1, &cbLength1, NormalPagePriority); + NdisGetNextBuffer(pBuffer1, &pBuffer1); + } + else + { + Assert(pbMemBuf1); + Assert(ucbLength2Match); + pbMemBuf1 += ucbLength2Match; + } + + if (!cbLength2) + { + NdisQueryBufferSafe(pBuffer2, &pbMemBuf2, &cbLength2, NormalPagePriority); + NdisGetNextBuffer(pBuffer2, &pBuffer2); + } + else + { + Assert(pbMemBuf2); + Assert(ucbLength2Match); + pbMemBuf2 += ucbLength2Match; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + break; + } + + ucbMatch -= ucbLength2Match; + if (!ucbMatch) + break; + + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } + } + +#ifdef DEBUG_NETFLT_PACKETS + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_PACKETS(pPacket1, pPacket2); + } +#endif + + return bMatch; +} + +/* + * answers whether the ndis packet and PINTNETSG match based on the packet length and the first cbMatch bytes of the packet and PINTNETSG + * if cbMatch < 0 matches complete packets. + */ +DECLHIDDEN(bool) vboxNetFltWinMatchPacketAndSG(PNDIS_PACKET pPacket, PINTNETSG pSG, const INT cbMatch) +{ + UINT cBufCount1; + PNDIS_BUFFER pBuffer1; + UINT uTotalPacketLength1; + uint8_t *pbMemBuf1 = NULL; + UINT cbLength1 = 0; + UINT uTotalPacketLength2 = pSG->cbTotal; + uint8_t *pbMemBuf2 = NULL; + UINT cbLength2 = 0; + bool bMatch = true; + bool bCompleteMatch = false; + UINT i = 0; + + NdisQueryPacket(pPacket, NULL, &cBufCount1, &pBuffer1, &uTotalPacketLength1); + + Assert(pBuffer1); + Assert(pSG->cSegsUsed); + Assert(pSG->cSegsAlloc >= pSG->cSegsUsed); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + AssertFailed(); + bMatch = false; + } + else + { + UINT ucbLength2Match = 0; + UINT ucbMatch; + + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; + bCompleteMatch = true; + } + else + { + ucbMatch = (UINT)cbMatch; + } + + for (;;) + { + if (!cbLength1) + { + NdisQueryBufferSafe(pBuffer1, &pbMemBuf1, &cbLength1, NormalPagePriority); + NdisGetNextBuffer(pBuffer1, &pBuffer1); + } + else + { + Assert(pbMemBuf1); + Assert(ucbLength2Match); + pbMemBuf1 += ucbLength2Match; + } + + if (!cbLength2) + { + Assert(i < pSG->cSegsUsed); + pbMemBuf2 = (uint8_t*)pSG->aSegs[i].pv; + cbLength2 = pSG->aSegs[i].cb; + i++; + } + else + { + Assert(pbMemBuf2); + Assert(ucbLength2Match); + pbMemBuf2 += ucbLength2Match; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + AssertFailed(); + break; + } + + ucbMatch -= ucbLength2Match; + if (!ucbMatch) + break; + + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } + } + + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_PACKET_AND_SG(pPacket, pSG); + } + return bMatch; +} + +# if 0 +/* + * answers whether the two PINTNETSGs match based on the packet length and the first cbMatch bytes of the PINTNETSG + * if cbMatch < 0 matches complete packets. + */ +static bool vboxNetFltWinMatchSGs(PINTNETSG pSG1, PINTNETSG pSG2, const INT cbMatch) +{ + UINT uTotalPacketLength1 = pSG1->cbTotal; + PVOID pbMemBuf1 = NULL; + UINT cbLength1 = 0; + UINT i1 = 0; + UINT uTotalPacketLength2 = pSG2->cbTotal; + PVOID pbMemBuf2 = NULL; + UINT cbLength2 = 0; + + bool bMatch = true; + bool bCompleteMatch = false; + UINT i2 = 0; + + Assert(pSG1->cSegsUsed); + Assert(pSG2->cSegsUsed); + Assert(pSG1->cSegsAlloc >= pSG1->cSegsUsed); + Assert(pSG2->cSegsAlloc >= pSG2->cSegsUsed); + + if (uTotalPacketLength1 != uTotalPacketLength2) + { + AssertFailed(); + bMatch = false; + } + else + { + UINT ucbMatch; + if (cbMatch < 0 || (UINT)cbMatch > uTotalPacketLength1) + { + /* NOTE: assuming uTotalPacketLength1 == uTotalPacketLength2*/ + ucbMatch = uTotalPacketLength1; + bCompleteMatch = true; + } + else + { + ucbMatch = (UINT)cbMatch; + } + + do + { + UINT ucbLength2Match; + if (!cbLength1) + { + Assert(i1 < pSG1->cSegsUsed); + pbMemBuf1 = pSG1->aSegs[i1].pv; + cbLength1 = pSG1->aSegs[i1].cb; + i1++; + } + + if (!cbLength2) + { + Assert(i2 < pSG2->cSegsUsed); + pbMemBuf2 = pSG2->aSegs[i2].pv; + cbLength2 = pSG2->aSegs[i2].cb; + i2++; + } + + ucbLength2Match = MIN(ucbMatch, cbLength1); + ucbLength2Match = MIN(ucbLength2Match, cbLength2); + + if (memcmp(pbMemBuf1, pbMemBuf2, ucbLength2Match)) + { + bMatch = false; + AssertFailed(); + break; + } + ucbMatch -= ucbLength2Match; + cbLength1 -= ucbLength2Match; + cbLength2 -= ucbLength2Match; + } while (ucbMatch); + } + + if (bMatch && !bCompleteMatch) + { + /* check that the packets fully match */ + DBG_CHECK_SGS(pSG1, pSG2); + } + return bMatch; +} +# endif +# endif +#endif + +static void vboxNetFltWinFiniNetFltBase() +{ + do + { + vboxNetFltDeleteGlobals(&g_VBoxNetFltGlobals); + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + RTR0Term(); + } while (0); +} + +/* + * Defines max timeout for waiting for driver unloading + * (3000 * 100 ms = 5 minutes) + */ +#define MAX_UNLOAD_PROBES 3000 + +static int vboxNetFltWinFiniIdc() +{ + int rc; + int i; + + vboxNetFltWinStopInitIdcProbing(); + + if (g_bVBoxIdcInitialized) + { + for (i = 0; (rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals)) == VERR_WRONG_ORDER + && i < MAX_UNLOAD_PROBES; i++) + { + RTThreadSleep(100); + } + if (i == MAX_UNLOAD_PROBES) + { + // seems something hungs in driver + LogFlow(("vboxNetFltWinFiniIdc - Can't delete Idc. pInH=%p cFRefs=%d fIDcOpen=%s", + g_VBoxNetFltGlobals.pInstanceHead, g_VBoxNetFltGlobals.cFactoryRefs, + g_VBoxNetFltGlobals.fIDCOpen ? "true" : "false")); + LogFlow(("vboxNetFltWinFiniIdc g_VBoxNetFltGlobalsWin cDvRefs=%d hDev=%x pDev=%p Mp=%x \n", + g_VBoxNetFltGlobalsWin.cDeviceRefs, g_VBoxNetFltGlobalsWin.hDevice, + g_VBoxNetFltGlobalsWin.pDevObj, g_VBoxNetFltGlobalsWin.Mp.hMiniport)); + Assert(i == MAX_UNLOAD_PROBES); + return VERR_WRONG_ORDER; + } + + if (RT_SUCCESS(rc)) + { + g_bVBoxIdcInitialized = false; + } + } + else + { + rc = VINF_SUCCESS; + } + return rc; + +} + +static int vboxNetFltWinFiniNetFlt() +{ + int rc = vboxNetFltWinFiniIdc(); + if (RT_SUCCESS(rc)) + { + vboxNetFltWinFiniNetFltBase(); + } + return rc; +} + +/** + * base netflt initialization + */ +static int vboxNetFltWinInitNetFltBase() +{ + int rc; + + do + { + Assert(!g_bVBoxIdcInitialized); + + rc = RTR0Init(0); + if (!RT_SUCCESS(rc)) + { + break; + } + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobals(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + { + RTR0Term(); + break; + } + }while (0); + + return rc; +} + +/** + * initialize IDC + */ +static int vboxNetFltWinInitIdc() +{ + int rc; + + do + { + if (g_bVBoxIdcInitialized) + { + rc = VINF_ALREADY_INITIALIZED; + break; + } + + /* + * connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + rc = vboxNetFltInitIdc(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + { + break; + } + + g_bVBoxIdcInitialized = true; + } while (0); + + return rc; +} + +static VOID vboxNetFltWinInitIdcProbingWorker(PVOID pvContext) +{ + PINIT_IDC_INFO pInitIdcInfo = (PINIT_IDC_INFO)pvContext; + int rc = vboxNetFltWinInitIdc(); + if (RT_FAILURE(rc)) + { + bool bInterupted = ASMAtomicUoReadBool(&pInitIdcInfo->bStop); + if (!bInterupted) + { + RTThreadSleep(1000); /* 1 s */ + bInterupted = ASMAtomicUoReadBool(&pInitIdcInfo->bStop); + if (!bInterupted) + { + vboxNetFltWinJobEnqueueJob(&g_VBoxJobQueue, &pInitIdcInfo->Job, false); + return; + } + } + + /* it's interrupted */ + rc = VERR_INTERRUPTED; + } + + ASMAtomicUoWriteS32(&pInitIdcInfo->rc, rc); + KeSetEvent(&pInitIdcInfo->hCompletionEvent, 0, FALSE); +} + +static int vboxNetFltWinStopInitIdcProbing() +{ + if (!g_VBoxInitIdcInfo.bInitialized) + return VERR_INVALID_STATE; + + ASMAtomicUoWriteBool(&g_VBoxInitIdcInfo.bStop, true); + KeWaitForSingleObject(&g_VBoxInitIdcInfo.hCompletionEvent, Executive, KernelMode, FALSE, NULL); + + return g_VBoxInitIdcInfo.rc; +} + +static int vboxNetFltWinStartInitIdcProbing() +{ + Assert(!g_bVBoxIdcInitialized); + KeInitializeEvent(&g_VBoxInitIdcInfo.hCompletionEvent, NotificationEvent, FALSE); + g_VBoxInitIdcInfo.bStop = false; + g_VBoxInitIdcInfo.bInitialized = true; + vboxNetFltWinJobInit(&g_VBoxInitIdcInfo.Job, vboxNetFltWinInitIdcProbingWorker, &g_VBoxInitIdcInfo, false); + vboxNetFltWinJobEnqueueJob(&g_VBoxJobQueue, &g_VBoxInitIdcInfo.Job, false); + return VINF_SUCCESS; +} + +static int vboxNetFltWinInitNetFlt() +{ + int rc; + + do + { + rc = vboxNetFltWinInitNetFltBase(); + if (RT_FAILURE(rc)) + { + AssertFailed(); + break; + } + + /* + * connect to the support driver. + * + * This will call back vboxNetFltOsOpenSupDrv (and maybe vboxNetFltOsCloseSupDrv) + * for establishing the connect to the support driver. + */ + rc = vboxNetFltWinInitIdc(); + if (RT_FAILURE(rc)) + { + AssertFailed(); + vboxNetFltWinFiniNetFltBase(); + break; + } + } while (0); + + return rc; +} + +/* detach*/ +static int vboxNetFltWinDeleteInstance(PVBOXNETFLTINS pThis) +{ + LogFlow(("vboxNetFltWinDeleteInstance: pThis=0x%p \n", pThis)); + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + Assert(pThis); + Assert(pThis->fDisconnectedFromHost); + Assert(!pThis->fRediscoveryPending); + Assert(pThis->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE); +#ifndef VBOXNETADP + Assert(pThis->u.s.WinIf.PtState.OpState == kVBoxNetDevOpState_Deinitialized); + Assert(!pThis->u.s.WinIf.hBinding); +#endif + Assert(pThis->u.s.WinIf.MpState.OpState == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + Assert(!pThis->u.s.PacketQueueWorker.pSG); +#endif + + RTSemMutexDestroy(pThis->u.s.hWinIfMutex); + + vboxNetFltWinDrvDereference(); + + return VINF_SUCCESS; +} + +static NDIS_STATUS vboxNetFltWinDisconnectIt(PVBOXNETFLTINS pInstance) +{ +#ifndef VBOXNETFLT_NO_PACKET_QUEUE + vboxNetFltWinQuFiniPacketQueue(pInstance); +#else + RT_NOREF1(pInstance); +#endif + return NDIS_STATUS_SUCCESS; +} + +/* detach*/ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinDetachFromInterface(PVBOXNETFLTINS pNetFlt, bool bOnUnbind) +{ + NDIS_STATUS Status; + int rc; + LogFlowFunc(("ENTER: pThis=%0xp\n", pNetFlt)); + + Assert(KeGetCurrentIrql() < DISPATCH_LEVEL); + Assert(pNetFlt); + + /* paranoia to ensure the instance is not removed while we're waiting on the mutex + * in case ndis does something unpredictable, e.g. calls our miniport halt independently + * from protocol unbind and concurrently with it*/ + vboxNetFltRetain(pNetFlt, false); + + rc = RTSemMutexRequest(pNetFlt->u.s.hWinIfMutex, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + Assert(vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Connected); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Initialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); +#endif + if (vboxNetFltWinGetWinIfState(pNetFlt) == kVBoxWinIfState_Connected) + { + vboxNetFltWinSetWinIfState(pNetFlt, kVBoxWinIfState_Disconnecting); +#ifndef VBOXNETADP + Status = vboxNetFltWinPtDoUnbinding(pNetFlt, bOnUnbind); +#else + Status = vboxNetFltWinMpDoDeinitialization(pNetFlt); +#endif + Assert(Status == NDIS_STATUS_SUCCESS); + + vboxNetFltWinSetWinIfState(pNetFlt, kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pNetFlt->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + vboxNetFltWinPtFiniWinIf(&pNetFlt->u.s.WinIf); + + /* we're unbinding, make an unbind-related release */ + vboxNetFltRelease(pNetFlt, false); + } + else + { + AssertBreakpoint(); +#ifndef VBOXNETADP + pNetFlt->u.s.WinIf.OpenCloseStatus = NDIS_STATUS_FAILURE; +#endif + if (!bOnUnbind) + { + vboxNetFltWinSetOpState(&pNetFlt->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); + } + Status = NDIS_STATUS_FAILURE; + } + RTSemMutexRelease(pNetFlt->u.s.hWinIfMutex); + } + else + { + AssertBreakpoint(); + Status = NDIS_STATUS_FAILURE; + } + + /* release for the retain we made before waining on the mutex */ + vboxNetFltRelease(pNetFlt, false); + + LogFlowFunc(("LEAVE: Status 0x%x\n", Status)); + + return Status; +} + + +/** + * Checks if the host (not us) has put the adapter in promiscuous mode. + * + * @returns true if promiscuous, false if not. + * @param pThis The instance. + */ +static bool vboxNetFltWinIsPromiscuous2(PVBOXNETFLTINS pThis) +{ +#ifndef VBOXNETADP + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)) + { + bool bPromiscuous; + if (!vboxNetFltWinReferenceWinIf(pThis)) + return false; + + bPromiscuous = (pThis->u.s.WinIf.fUpperProtocolSetFilter & NDIS_PACKET_TYPE_PROMISCUOUS) == NDIS_PACKET_TYPE_PROMISCUOUS; + /*vboxNetFltWinIsPromiscuous(pAdapt);*/ + + vboxNetFltWinDereferenceWinIf(pThis); + return bPromiscuous; + } + return false; +#else + RT_NOREF1(pThis); + return true; +#endif +} + + +/** + * Report the MAC address, promiscuous mode setting, GSO capabilities and + * no-preempt destinations to the internal network. + * + * Does nothing if we're not currently connected to an internal network. + * + * @param pThis The instance data. + */ +static void vboxNetFltWinReportStuff(PVBOXNETFLTINS pThis) +{ + /** @todo Keep these up to date, esp. the promiscuous mode bit. */ + if (pThis->pSwitchPort + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pThis->u.s.MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, + vboxNetFltWinIsPromiscuous2(pThis)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, + INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + /** @todo We should be able to do pfnXmit at DISPATCH_LEVEL... */ + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } +} + +/** + * Worker for vboxNetFltWinAttachToInterface. + * + * @param pAttachInfo Structure for communicating with + * vboxNetFltWinAttachToInterface. + */ +static void vboxNetFltWinAttachToInterfaceWorker(PATTACH_INFO pAttachInfo) +{ + PVBOXNETFLTINS pThis = pAttachInfo->pNetFltIf; + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + /* to ensure we're not removed while we're here */ + vboxNetFltRetain(pThis, false); + + rc = RTSemMutexRequest(pThis->u.s.hWinIfMutex, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + Assert(vboxNetFltWinGetWinIfState(pThis) == kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + if (vboxNetFltWinGetWinIfState(pThis) == kVBoxWinIfState_Disconnected) + { + if (pAttachInfo->fRediscovery) + { + /* rediscovery means adaptor bind is performed while intnet is already using it + * i.e. adaptor was unbound while being used by intnet and now being bound back again */ + Assert( ((VBOXNETFTLINSSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pThis->enmState)) + == kVBoxNetFltInsState_Connected); + } +#ifndef VBOXNETADP + Status = vboxNetFltWinPtInitWinIf(&pThis->u.s.WinIf, pAttachInfo->pCreateContext->pOurName); +#else + Status = vboxNetFltWinPtInitWinIf(&pThis->u.s.WinIf); +#endif + if (Status == NDIS_STATUS_SUCCESS) + { + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Connecting); + +#ifndef VBOXNETADP + Status = vboxNetFltWinPtDoBinding(pThis, pAttachInfo->pCreateContext->pOurName, pAttachInfo->pCreateContext->pBindToName); +#else + Status = vboxNetFltWinMpDoInitialization(pThis, pAttachInfo->pCreateContext->hMiniportAdapter, pAttachInfo->pCreateContext->hWrapperConfigurationContext); +#endif + if (Status == NDIS_STATUS_SUCCESS) + { + if (!pAttachInfo->fRediscovery) + vboxNetFltWinDrvReference(); +#ifndef VBOXNETADP + if (pThis->u.s.WinIf.OpenCloseStatus == NDIS_STATUS_SUCCESS) +#endif + { + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Connected); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Initialized); +#endif + /* 4. mark as connected */ + RTSpinlockAcquire(pThis->hSpinlock); + ASMAtomicUoWriteBool(&pThis->fDisconnectedFromHost, false); + RTSpinlockRelease(pThis->hSpinlock); + + pAttachInfo->Status = VINF_SUCCESS; + pAttachInfo->pCreateContext->Status = NDIS_STATUS_SUCCESS; + + RTSemMutexRelease(pThis->u.s.hWinIfMutex); + + vboxNetFltRelease(pThis, false); + + /* 5. Report MAC address, promiscuousness and GSO capabilities. */ + vboxNetFltWinReportStuff(pThis); + + return; + } +#ifndef VBOXNETADP /* unreachable for VBOXNETADP because of the return above */ + AssertBreakpoint(); + + if (!pAttachInfo->fRediscovery) + { + vboxNetFltWinDrvDereference(); + } +# ifndef VBOXNETADP + vboxNetFltWinPtDoUnbinding(pThis, true); +/*# else - unreachable + vboxNetFltWinMpDoDeinitialization(pThis); */ +# endif +#endif + } + AssertBreakpoint(); + vboxNetFltWinPtFiniWinIf(&pThis->u.s.WinIf); + } + AssertBreakpoint(); + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Disconnected); + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.MpState) == kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + Assert(vboxNetFltWinGetOpState(&pThis->u.s.WinIf.PtState) == kVBoxNetDevOpState_Deinitialized); +#endif + } + AssertBreakpoint(); + + pAttachInfo->Status = VERR_GENERAL_FAILURE; + pAttachInfo->pCreateContext->Status = Status; + RTSemMutexRelease(pThis->u.s.hWinIfMutex); + } + else + { + AssertBreakpoint(); + pAttachInfo->Status = rc; + } + + vboxNetFltRelease(pThis, false); + + return; +} + +/** + * Common code for vboxNetFltOsInitInstance and + * vboxNetFltOsMaybeRediscovered. + * + * @returns IPRT status code. + * @param pThis The instance. + * @param fRediscovery True if vboxNetFltOsMaybeRediscovered is calling, + * false if it's vboxNetFltOsInitInstance. + */ +static int vboxNetFltWinAttachToInterface(PVBOXNETFLTINS pThis, void * pContext, bool fRediscovery) +{ + ATTACH_INFO Info; + Info.pNetFltIf = pThis; + Info.fRediscovery = fRediscovery; + Info.pCreateContext = (PCREATE_INSTANCE_CONTEXT)pContext; + + vboxNetFltWinAttachToInterfaceWorker(&Info); + + return Info.Status; +} +static NTSTATUS vboxNetFltWinPtDevDispatch(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pIrpSl = IoGetCurrentIrpStackLocation(pIrp);; + NTSTATUS Status = STATUS_SUCCESS; + + switch (pIrpSl->MajorFunction) + { + case IRP_MJ_DEVICE_CONTROL: + Status = STATUS_NOT_SUPPORTED; + break; + case IRP_MJ_CREATE: + case IRP_MJ_CLEANUP: + case IRP_MJ_CLOSE: + break; + default: + AssertFailed(); + break; + } + + pIrp->IoStatus.Status = Status; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevCreate(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + NDIS_STRING DevName, LinkName; + PDRIVER_DISPATCH aMajorFunctions[IRP_MJ_MAXIMUM_FUNCTION+1]; + NdisInitUnicodeString(&DevName, VBOXNETFLT_NAME_DEVICE); + NdisInitUnicodeString(&LinkName, VBOXNETFLT_NAME_LINK); + + Assert(!pGlobals->hDevice); + Assert(!pGlobals->pDevObj); + NdisZeroMemory(aMajorFunctions, sizeof (aMajorFunctions)); + aMajorFunctions[IRP_MJ_CREATE] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_CLEANUP] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_CLOSE] = vboxNetFltWinPtDevDispatch; + aMajorFunctions[IRP_MJ_DEVICE_CONTROL] = vboxNetFltWinPtDevDispatch; + + NDIS_STATUS Status = NdisMRegisterDevice(pGlobals->Mp.hNdisWrapper, + &DevName, &LinkName, + aMajorFunctions, + &pGlobals->pDevObj, + &pGlobals->hDevice); + Assert(Status == NDIS_STATUS_SUCCESS); + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevDestroy(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(pGlobals->hDevice); + Assert(pGlobals->pDevObj); + NDIS_STATUS Status = NdisMDeregisterDevice(pGlobals->hDevice); + Assert(Status == NDIS_STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + pGlobals->hDevice = NULL; + pGlobals->pDevObj = NULL; + } + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevCreateReference(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + NDIS_STATUS Status = KeWaitForSingleObject(&pGlobals->SynchEvent, Executive, KernelMode, FALSE, NULL); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Assert(pGlobals->cDeviceRefs >= 0); + if (++pGlobals->cDeviceRefs == 1) + { + Status = vboxNetFltWinDevCreate(pGlobals); + if (Status == NDIS_STATUS_SUCCESS) + { + ObReferenceObject(pGlobals->pDevObj); + } + } + else + { + Status = NDIS_STATUS_SUCCESS; + } + KeSetEvent(&pGlobals->SynchEvent, 0, FALSE); + } + else + { + /* should never happen actually */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + return Status; +} + +static NDIS_STATUS vboxNetFltWinDevDereference(PVBOXNETFLTGLOBALS_WIN pGlobals) +{ + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + NDIS_STATUS Status = KeWaitForSingleObject(&pGlobals->SynchEvent, Executive, KernelMode, FALSE, NULL); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Assert(pGlobals->cDeviceRefs > 0); + if (!(--pGlobals->cDeviceRefs)) + { + ObDereferenceObject(pGlobals->pDevObj); + Status = vboxNetFltWinDevDestroy(pGlobals); + } + else + { + Status = NDIS_STATUS_SUCCESS; + } + KeSetEvent(&pGlobals->SynchEvent, 0, FALSE); + } + else + { + /* should never happen actually */ + AssertFailed(); + Status = NDIS_STATUS_FAILURE; + } + return Status; +} + +/* reference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvReference() +{ + vboxNetFltWinDevCreateReference(&g_VBoxNetFltGlobalsWin); +} + +/* dereference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvDereference() +{ + vboxNetFltWinDevDereference(&g_VBoxNetFltGlobalsWin); +} + +/* + * + * The OS specific interface definition + * + */ + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + /* AttachToInterface true if disconnected */ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + RT_NOREF1(pvIfData); + int rc = VINF_SUCCESS; + uint32_t cRefs = 0; +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_WIRE) + cRefs++; + if (fDst & INTNETTRUNKDIR_HOST) + cRefs++; +#else + if ((fDst & INTNETTRUNKDIR_WIRE) || (fDst & INTNETTRUNKDIR_HOST)) + cRefs = 1; +#endif + + AssertReturn(cRefs, VINF_SUCCESS); + + if (!vboxNetFltWinIncReferenceWinIf(pThis, cRefs)) + { + return VERR_GENERAL_FAILURE; + } +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_WIRE) + { + PNDIS_PACKET pPacket; + + pPacket = vboxNetFltWinNdisPacketFromSG(pThis, pSG, NULL /*pBufToFree*/, + true /*fToWire*/, true /*fCopyMemory*/); + + if (pPacket) + { + NDIS_STATUS fStatus; + +#ifndef VBOX_LOOPBACK_USEFLAGS + /* force "don't loopback" flags to prevent loopback branch invocation in any case + * to avoid ndis misbehave */ + NdisGetPacketFlags(pPacket) |= g_VBoxNetFltGlobalsWin.fPacketDontLoopBack; +#else + /* this is done by default in vboxNetFltWinNdisPacketFromSG */ +#endif + +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + vboxNetFltWinLbPutSendPacket(pThis, pPacket, true /* bFromIntNet */); +#endif + NdisSend(&fStatus, pThis->u.s.WinIf.hBinding, pPacket); + if (fStatus != NDIS_STATUS_PENDING) + { +#if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) + /* the status is NOT pending, complete the packet */ + bool fTmp = vboxNetFltWinLbRemoveSendPacket(pThis, pPacket); + Assert(fTmp); NOREF(fTmp); +#endif + if (!NT_SUCCESS(fStatus)) + rc = VERR_GENERAL_FAILURE; /** @todo convert status to VERR_xxx */ + + vboxNetFltWinFreeSGNdisPacket(pPacket, true); + } + else + { + /* pending, dereference on packet complete */ + cRefs--; + } + } + else + { + AssertFailed(); + rc = VERR_NO_MEMORY; + } + } +#endif + +#ifndef VBOXNETADP + if (fDst & INTNETTRUNKDIR_HOST) +#else + if (cRefs) +#endif + { + PNDIS_PACKET pPacket = vboxNetFltWinNdisPacketFromSG(pThis, pSG, NULL /*pBufToFree*/, + false /*fToWire*/, true /*fCopyMemory*/); + if (pPacket) + { + NdisMIndicateReceivePacket(pThis->u.s.WinIf.hMiniport, &pPacket, 1); + cRefs--; +#ifdef VBOXNETADP + STATISTIC_INCREASE(pThis->u.s.WinIf.cRxSuccess); +#endif + } + else + { + AssertFailed(); +#ifdef VBOXNETADP + STATISTIC_INCREASE(pThis->u.s.WinIf.cRxError); +#endif + rc = VERR_NO_MEMORY; + } + } + + Assert(cRefs <= 2); + + if (cRefs) + { + vboxNetFltWinDecReferenceWinIf(pThis, cRefs); + } + + return rc; +} + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ +#ifndef VBOXNETADP + NDIS_STATUS Status; +#endif + /* we first wait for all pending ops to complete + * this might include all packets queued for processing */ + for (;;) + { + if (fActive) + { + if (!pThis->u.s.cModePassThruRefs) + { + break; + } + } + else + { + if (!pThis->u.s.cModeNetFltRefs) + { + break; + } + } + vboxNetFltWinSleep(2); + } + + if (!vboxNetFltWinReferenceWinIf(pThis)) + return; +#ifndef VBOXNETADP + + if (fActive) + { +#ifdef DEBUG_misha + NDIS_PHYSICAL_MEDIUM PhMedium; + bool bPromiscSupported; + + Status = vboxNetFltWinQueryPhysicalMedium(pThis, &PhMedium); + if (Status != NDIS_STATUS_SUCCESS) + { + + LogRel(("vboxNetFltWinQueryPhysicalMedium failed, Status (0x%x), setting medium to NdisPhysicalMediumUnspecified\n", Status)); + Assert(Status == NDIS_STATUS_NOT_SUPPORTED); + if (Status != NDIS_STATUS_NOT_SUPPORTED) + { + LogRel(("vboxNetFltWinQueryPhysicalMedium failed, Status (0x%x), setting medium to NdisPhysicalMediumUnspecified\n", Status)); + } + PhMedium = NdisPhysicalMediumUnspecified; + } + else + { + LogRel(("(SUCCESS) vboxNetFltWinQueryPhysicalMedium SUCCESS\n")); + } + + bPromiscSupported = (!(PhMedium == NdisPhysicalMediumWirelessWan + || PhMedium == NdisPhysicalMediumWirelessLan + || PhMedium == NdisPhysicalMediumNative802_11 + || PhMedium == NdisPhysicalMediumBluetooth + /*|| PhMedium == NdisPhysicalMediumWiMax */ + )); + + Assert(bPromiscSupported == VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)); +#endif + } + + if (VBOXNETFLT_PROMISCUOUS_SUPPORTED(pThis)) + { + Status = vboxNetFltWinSetPromiscuous(pThis, fActive); + if (Status != NDIS_STATUS_SUCCESS) + { + LogRel(("vboxNetFltWinSetPromiscuous failed, Status (0x%x), fActive (%d)\n", Status, fActive)); + AssertFailed(); + } + } +#else +# ifdef VBOXNETADP_REPORT_DISCONNECTED + if (fActive) + { + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); + } + else + { + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_DISCONNECT, + (PVOID)NULL, + 0); + } +#else + if (fActive) + { + /* indicate status change to make the ip settings be re-picked for dhcp */ + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_DISCONNECT, + (PVOID)NULL, + 0); + + NdisMIndicateStatus(pThis->u.s.WinIf.hMiniport, + NDIS_STATUS_MEDIA_CONNECT, + (PVOID)NULL, + 0); + } +# endif +#endif + vboxNetFltWinDereferenceWinIf(pThis); + + return; +} + +#ifndef VBOXNETADP + +DECLINLINE(bool) vboxNetFltWinIsAddrLinkLocal4(PCRTNETADDRIPV4 pAddr) +{ + return (pAddr->s.Lo == 0xfea9); /* 169.254 */ +} + +DECLINLINE(bool) vboxNetFltWinIsAddrLinkLocal6(PCRTNETADDRIPV6 pAddr) +{ + return ((pAddr->au8[0] == 0xfe) && ((pAddr->au8[1] & 0xc0) == 0x80)); +} + +void vboxNetFltWinNotifyHostAddress(PTA_ADDRESS pAddress, bool fAdded) +{ + void *pvAddr = NULL; + INTNETADDRTYPE enmAddrType = kIntNetAddrType_Invalid; + + LogFlow(("==>vboxNetFltWinNotifyHostAddress: AddrType=%d %s\n", + pAddress->AddressType, fAdded ? "added" : "deleted")); + if (pAddress->AddressType == TDI_ADDRESS_TYPE_IP) + { + PTDI_ADDRESS_IP pTdiAddrIp = (PTDI_ADDRESS_IP)pAddress->Address; + /* + * Note that we do not get loopback addresses here. If we did we should + * have checked and ignored them too. + */ + if (!vboxNetFltWinIsAddrLinkLocal4((PCRTNETADDRIPV4)(&pTdiAddrIp->in_addr))) + { + pvAddr = &pTdiAddrIp->in_addr; + enmAddrType = kIntNetAddrType_IPv4; + } + else + Log2(("vboxNetFltWinNotifyHostAddress: ignoring link-local address %RTnaipv4\n", + pTdiAddrIp->in_addr)); + } + else if (pAddress->AddressType == TDI_ADDRESS_TYPE_IP6) + { + PTDI_ADDRESS_IP6 pTdiAddrIp6 = (PTDI_ADDRESS_IP6)pAddress->Address; + if (!vboxNetFltWinIsAddrLinkLocal6((PCRTNETADDRIPV6)(pTdiAddrIp6->sin6_addr))) + { + pvAddr = pTdiAddrIp6->sin6_addr; + enmAddrType = kIntNetAddrType_IPv6; + } + else + Log2(("vboxNetFltWinNotifyHostAddress: ignoring link-local address %RTnaipv6\n", + pTdiAddrIp6->sin6_addr)); + } + else + { + Log2(("vboxNetFltWinNotifyHostAddress: ignoring irrelevant address type %d\n", + pAddress->AddressType)); + LogFlow(("<==vboxNetFltWinNotifyHostAddress\n")); + return; + } + if (pvAddr) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + /* At this point the list must contain at least one element. */ + PVBOXNETFLTINS pInstance = NULL; + PVBOXNETFLTWIN pFilter; + RTListForEach(&g_VBoxNetFltGlobalsWin.listFilters, pFilter, VBOXNETFLTWIN, node) + { + pInstance = RT_FROM_MEMBER(pFilter, VBOXNETFLTINS, u.s.WinIf); + if (vboxNetFltWinReferenceWinIf(pInstance)) + { + if (pInstance->pSwitchPort && pInstance->pSwitchPort->pfnNotifyHostAddress) + break; + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + Log2(("vboxNetFltWinNotifyHostAddress: failed to retain filter instance %p\n", pInstance)); + pInstance = NULL; + } + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + if (pInstance) + { + if (enmAddrType == kIntNetAddrType_IPv4) + Log2(("vboxNetFltWin%sAddressHandler: %RTnaipv4\n", + fAdded ? "Add" : "Del", *(PCRTNETADDRIPV4)pvAddr)); + else + Log2(("vboxNetFltWin%sAddressHandler: %RTnaipv6\n", + fAdded ? "Add" : "Del", pvAddr)); + pInstance->pSwitchPort->pfnNotifyHostAddress(pInstance->pSwitchPort, fAdded, + enmAddrType, pvAddr); + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + Log2(("vboxNetFltWinNotifyHostAddress: no filters require notification\n")); + } + LogFlow(("<==vboxNetFltWinNotifyHostAddress\n")); +} + +void vboxNetFltWinAddAddressHandler(PTA_ADDRESS Address, + PUNICODE_STRING DeviceName, + PTDI_PNP_CONTEXT Context) +{ + RT_NOREF2(DeviceName, Context); + vboxNetFltWinNotifyHostAddress(Address, true); +} + +void vboxNetFltWinDelAddressHandler(PTA_ADDRESS Address, + PUNICODE_STRING DeviceName, + PTDI_PNP_CONTEXT Context) +{ + RT_NOREF2(DeviceName, Context); + vboxNetFltWinNotifyHostAddress(Address, false); +} + +void vboxNetFltWinRegisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltWinRegisterIpAddrNotifier: instance=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + if (pThis->pSwitchPort && pThis->pSwitchPort->pfnNotifyHostAddress) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + bool fRegisterHandlers = RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters); + RTListPrepend(&g_VBoxNetFltGlobalsWin.listFilters, &pThis->u.s.WinIf.node); + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + + if (fRegisterHandlers) + { + TDI_CLIENT_INTERFACE_INFO Info; + UNICODE_STRING ClientName = RTL_CONSTANT_STRING(L"VBoxNetFlt"); + memset(&Info, 0, sizeof(Info)); + Info.MajorTdiVersion = 2; + Info.MinorTdiVersion = 0; + Info.ClientName = &ClientName; + Info.AddAddressHandlerV2 = vboxNetFltWinAddAddressHandler; + Info.DelAddressHandlerV2 = vboxNetFltWinDelAddressHandler; + Assert(!g_VBoxNetFltGlobalsWin.hNotifier); + NTSTATUS Status = TdiRegisterPnPHandlers(&Info, sizeof(Info), &g_VBoxNetFltGlobalsWin.hNotifier); + Log2(("vboxNetFltWinRegisterIpAddrNotifier: TdiRegisterPnPHandlers returned %d\n", Status)); NOREF(Status); + } + else + Log2(("vboxNetFltWinRegisterIpAddrNotifier: already registed\n")); + } + else + Log2(("vboxNetFltWinRegisterIpAddrNotifier: this instance does not require notifications, ignoring...\n")); + LogFlow(("<==vboxNetFltWinRegisterIpAddrNotifier: notifier=%p\n", g_VBoxNetFltGlobalsWin.hNotifier)); +} + +void vboxNetFltWinUnregisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltWinUnregisterIpAddrNotifier: notifier=%p\n", g_VBoxNetFltGlobalsWin.hNotifier)); + if (pThis->pSwitchPort && pThis->pSwitchPort->pfnNotifyHostAddress) + { + NdisAcquireSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + /* At this point the list must contain at least one element. */ + Assert(!RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters)); + RTListNodeRemove(&pThis->u.s.WinIf.node); + HANDLE hNotifier = NULL; + if (RTListIsEmpty(&g_VBoxNetFltGlobalsWin.listFilters)) + { + /* + * The list has become empty, so we need to deregister handlers. We + * grab hNotifier and reset it while still holding the lock. This + * guaranties that we won't interfere with setting it in + * vboxNetFltWinRegisterIpAddrNotifier(). It is inconceivable that + * vboxNetFltWinUnregisterIpAddrNotifier() will be called for the + * same filter instance while it is still being processed by + * vboxNetFltWinRegisterIpAddrNotifier(). This would require trunk + * destruction in the middle of its creation. It is possible that + * vboxNetFltWinUnregisterIpAddrNotifier() is called for another + * filter instance, but in such case we won't even get here as the + * list won't be empty. + */ + hNotifier = g_VBoxNetFltGlobalsWin.hNotifier; + g_VBoxNetFltGlobalsWin.hNotifier = NULL; + } + NdisReleaseSpinLock(&g_VBoxNetFltGlobalsWin.lockFilters); + if (hNotifier) + { + NTSTATUS Status = TdiDeregisterPnPHandlers(hNotifier); + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: TdiDeregisterPnPHandlers(%p) returned %d\n", + hNotifier, Status)); NOREF(Status); + } + else + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: filters remain, do not deregister handlers yet\n")); + } + else + Log2(("vboxNetFltWinUnregisterIpAddrNotifier: this instance did not require notifications, ignoring...\n")); + LogFlow(("<==vboxNetFltWinUnregisterIpAddrNotifier\n")); +} +#else /* VBOXNETADP */ +#define vboxNetFltWinRegisterIpAddrNotifier(x) +#define vboxNetFltWinUnregisterIpAddrNotifier(x) +#endif /* VBOXNETADP */ + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + NDIS_STATUS Status = vboxNetFltWinDisconnectIt(pThis); + Log2(("vboxNetFltOsDisconnectIt: pThis=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + vboxNetFltWinUnregisterIpAddrNotifier(pThis); + return Status == NDIS_STATUS_SUCCESS ? VINF_SUCCESS : VERR_GENERAL_FAILURE; +} + +static void vboxNetFltWinConnectItWorker(PVOID pvContext) +{ + PWORKER_INFO pInfo = (PWORKER_INFO)pvContext; +#if !defined(VBOXNETADP) || !defined(VBOXNETFLT_NO_PACKET_QUEUE) + NDIS_STATUS Status; +#endif + PVBOXNETFLTINS pInstance = pInfo->pNetFltIf; + + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + /* this is not a rediscovery, initialize Mac cache */ + if (vboxNetFltWinReferenceWinIf(pInstance)) + { +#ifndef VBOXNETADP + Status = vboxNetFltWinGetMacAddress(pInstance, &pInstance->u.s.MacAddr); + if (Status == NDIS_STATUS_SUCCESS) +#endif + { +#ifdef VBOXNETFLT_NO_PACKET_QUEUE + pInfo->Status = VINF_SUCCESS; +#else + Status = vboxNetFltWinQuInitPacketQueue(pInstance); + if (Status == NDIS_STATUS_SUCCESS) + { + pInfo->Status = VINF_SUCCESS; + } + else + { + pInfo->Status = VERR_GENERAL_FAILURE; + } +#endif + } +#ifndef VBOXNETADP + else + { + pInfo->Status = VERR_INTNET_FLT_IF_FAILED; + } +#endif + + vboxNetFltWinDereferenceWinIf(pInstance); + } + else + { + pInfo->Status = VERR_INTNET_FLT_IF_NOT_FOUND; + } +} + +static int vboxNetFltWinConnectIt(PVBOXNETFLTINS pThis) +{ + WORKER_INFO Info; + Info.pNetFltIf = pThis; + + vboxNetFltWinJobSynchExecAtPassive(vboxNetFltWinConnectItWorker, &Info); + + if (RT_SUCCESS(Info.Status)) + vboxNetFltWinReportStuff(pThis); + + return Info.Status; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + Log2(("vboxNetFltOsConnectIt: pThis=%p pThis->pSwitchPort=%p pThis->pSwitchPort->pfnNotifyHostAddress=%p\n", + pThis, pThis->pSwitchPort, pThis->pSwitchPort ? pThis->pSwitchPort->pfnNotifyHostAddress : NULL)); + vboxNetFltWinRegisterIpAddrNotifier(pThis); + return vboxNetFltWinConnectIt(pThis); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + vboxNetFltWinDeleteInstance(pThis); +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + int rc = RTSemMutexCreate(&pThis->u.s.hWinIfMutex); + if (RT_SUCCESS(rc)) + { + rc = vboxNetFltWinAttachToInterface(pThis, pvContext, false /*fRediscovery*/ ); + if (RT_SUCCESS(rc)) + { + return rc; + } + RTSemMutexDestroy(pThis->u.s.hWinIfMutex); + } + return rc; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + pThis->u.s.cModeNetFltRefs = 0; + pThis->u.s.cModePassThruRefs = 0; + vboxNetFltWinSetWinIfState(pThis, kVBoxWinIfState_Disconnected); + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.MpState, kVBoxNetDevOpState_Deinitialized); +#ifndef VBOXNETADP + vboxNetFltWinSetOpState(&pThis->u.s.WinIf.PtState, kVBoxNetDevOpState_Deinitialized); +#endif + return VINF_SUCCESS; +} + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + RT_NOREF3(pThis, pvIfData, pMac); +} + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + /* Nothing to do */ + RT_NOREF3(pThis, pvIf, ppvIfData); + return VINF_SUCCESS; +} + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + /* Nothing to do */ + RT_NOREF2(pThis, pvIfData); + return VINF_SUCCESS; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h new file mode 100644 index 00000000..36235409 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetFltRt-win.h @@ -0,0 +1,972 @@ +/* $Id: VBoxNetFltRt-win.h $ */ +/** @file + * VBoxNetFltRt-win.h - Bridged Networking Driver, Windows Specific Code. + * NetFlt Runtime API + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +DECLHIDDEN(VOID) vboxNetFltWinUnload(IN PDRIVER_OBJECT DriverObject); + +#ifndef VBOXNETADP +# if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) +DECLHIDDEN(bool) vboxNetFltWinMatchPackets(PNDIS_PACKET pPacket1, PNDIS_PACKET pPacket2, const INT cbMatch); +DECLHIDDEN(bool) vboxNetFltWinMatchPacketAndSG(PNDIS_PACKET pPacket, PINTNETSG pSG, const INT cbMatch); +# endif +#endif + +/************************* + * packet queue API * + *************************/ + + +#define LIST_ENTRY_2_PACKET_INFO(pListEntry) \ + ( (PVBOXNETFLT_PACKET_INFO)((uint8_t *)(pListEntry) - RT_UOFFSETOF(VBOXNETFLT_PACKET_INFO, ListEntry)) ) + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +#define VBOX_SLE_2_PKTRSVD_PT(_pEntry) \ + ( (PVBOXNETFLT_PKTRSVD_PT)((uint8_t *)(_pEntry) - RT_UOFFSETOF(VBOXNETFLT_PKTRSVD_PT, ListEntry)) ) + +#define VBOX_SLE_2_SENDPACKET(_pEntry) \ + ( (PNDIS_PACKET)((uint8_t *)(VBOX_SLE_2_PKTRSVD_PT(_pEntry)) - RT_UOFFSETOF(NDIS_PACKET, ProtocolReserved)) ) + +#endif +/** + * enqueus the packet info to the tail of the queue + */ +DECLINLINE(void) vboxNetFltWinQuEnqueueTail(PVBOXNETFLT_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + InsertTailList(pQueue, &pPacketInfo->ListEntry); +} + +DECLINLINE(void) vboxNetFltWinQuEnqueueHead(PVBOXNETFLT_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + Assert(pPacketInfo->pPool); + InsertHeadList(pQueue, &pPacketInfo->ListEntry); +} + +/** + * enqueus the packet info to the tail of the queue + */ +DECLINLINE(void) vboxNetFltWinQuInterlockedEnqueueTail(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + Assert(pPacketInfo->pPool); + NdisAcquireSpinLock(&pQueue->Lock); + vboxNetFltWinQuEnqueueTail(&pQueue->Queue, pPacketInfo); + NdisReleaseSpinLock(&pQueue->Lock); +} + +DECLINLINE(void) vboxNetFltWinQuInterlockedEnqueueHead(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pQueue, PVBOXNETFLT_PACKET_INFO pPacketInfo) +{ + NdisAcquireSpinLock(&pQueue->Lock); + vboxNetFltWinQuEnqueueHead(&pQueue->Queue, pPacketInfo); + NdisReleaseSpinLock(&pQueue->Lock); +} + +/** + * dequeus the packet info from the head of the queue + */ +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuDequeueHead(PVBOXNETFLT_PACKET_QUEUE pQueue) +{ + PLIST_ENTRY pListEntry = RemoveHeadList(pQueue); + if (pListEntry != pQueue) + { + PVBOXNETFLT_PACKET_INFO pInfo = LIST_ENTRY_2_PACKET_INFO(pListEntry); + Assert(pInfo->pPool); + return pInfo; + } + return NULL; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuDequeueTail(PVBOXNETFLT_PACKET_QUEUE pQueue) +{ + PLIST_ENTRY pListEntry = RemoveTailList(pQueue); + if (pListEntry != pQueue) + { + PVBOXNETFLT_PACKET_INFO pInfo = LIST_ENTRY_2_PACKET_INFO(pListEntry); + Assert(pInfo->pPool); + return pInfo; + } + return NULL; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuInterlockedDequeueHead(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + pInfo = vboxNetFltWinQuDequeueHead(&pInterlockedQueue->Queue); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); + return pInfo; +} + +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinQuInterlockedDequeueTail(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue) +{ + PVBOXNETFLT_PACKET_INFO pInfo; + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + pInfo = vboxNetFltWinQuDequeueTail(&pInterlockedQueue->Queue); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); + return pInfo; +} + +DECLINLINE(void) vboxNetFltWinQuDequeue(PVBOXNETFLT_PACKET_INFO pInfo) +{ + RemoveEntryList(&pInfo->ListEntry); +} + +DECLINLINE(void) vboxNetFltWinQuInterlockedDequeue(PVBOXNETFLT_INTERLOCKED_PACKET_QUEUE pInterlockedQueue, PVBOXNETFLT_PACKET_INFO pInfo) +{ + NdisAcquireSpinLock(&pInterlockedQueue->Lock); + vboxNetFltWinQuDequeue(pInfo); + NdisReleaseSpinLock(&pInterlockedQueue->Lock); +} + +/** + * allocates the packet info from the pool + */ +DECLINLINE(PVBOXNETFLT_PACKET_INFO) vboxNetFltWinPpAllocPacketInfo(PVBOXNETFLT_PACKET_INFO_POOL pPool) +{ + return vboxNetFltWinQuInterlockedDequeueHead(&pPool->Queue); +} + +/** + * returns the packet info to the pool + */ +DECLINLINE(void) vboxNetFltWinPpFreePacketInfo(PVBOXNETFLT_PACKET_INFO pInfo) +{ + PVBOXNETFLT_PACKET_INFO_POOL pPool = pInfo->pPool; + vboxNetFltWinQuInterlockedEnqueueHead(&pPool->Queue, pInfo); +} + +/** initializes the packet queue */ +#define INIT_PACKET_QUEUE(_pQueue) InitializeListHead((_pQueue)) + +/** initializes the packet queue */ +#define INIT_INTERLOCKED_PACKET_QUEUE(_pQueue) \ + { \ + INIT_PACKET_QUEUE(&(_pQueue)->Queue); \ + NdisAllocateSpinLock(&(_pQueue)->Lock); \ + } + +/** delete the packet queue */ +#define FINI_INTERLOCKED_PACKET_QUEUE(_pQueue) NdisFreeSpinLock(&(_pQueue)->Lock) + +/** returns the packet the packet info contains */ +#define GET_PACKET_FROM_INFO(_pPacketInfo) (ASMAtomicUoReadPtr((void * volatile *)&(_pPacketInfo)->pPacket)) + +/** assignes the packet to the packet info */ +#define SET_PACKET_TO_INFO(_pPacketInfo, _pPacket) (ASMAtomicUoWritePtr(&(_pPacketInfo)->pPacket, (_pPacket))) + +/** returns the flags the packet info contains */ +#define GET_FLAGS_FROM_INFO(_pPacketInfo) (ASMAtomicUoReadU32((volatile uint32_t *)&(_pPacketInfo)->fFlags)) + +/** sets flags to the packet info */ +#define SET_FLAGS_TO_INFO(_pPacketInfo, _fFlags) (ASMAtomicUoWriteU32((volatile uint32_t *)&(_pPacketInfo)->fFlags, (_fFlags))) + +#ifdef VBOXNETFLT_NO_PACKET_QUEUE +DECLHIDDEN(bool) vboxNetFltWinPostIntnet(PVBOXNETFLTINS pInstance, PVOID pvPacket, const UINT fFlags); +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQuEnqueuePacket(PVBOXNETFLTINS pInstance, PVOID pPacket, const UINT fPacketFlags); +DECLHIDDEN(void) vboxNetFltWinQuFiniPacketQueue(PVBOXNETFLTINS pInstance); +DECLHIDDEN(NTSTATUS) vboxNetFltWinQuInitPacketQueue(PVBOXNETFLTINS pInstance); +#endif /* #ifndef VBOXNETFLT_NO_PACKET_QUEUE */ + + +#ifndef VBOXNETADP +/** + * searches the list entry in a single-linked list + */ +DECLINLINE(bool) vboxNetFltWinSearchListEntry(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry2Search, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + if (pEntry2Search == pCur) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return true; + } + } + return false; +} + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinSearchPacket(PVBOXNETFLT_SINGLE_LIST pList, PNDIS_PACKET pPacket2Search, int cbMatch, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + PNDIS_PACKET pCurPacket; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + pCurPacket = VBOX_SLE_2_SENDPACKET(pCur); + if (pCurPacket == pPacket2Search || vboxNetFltWinMatchPackets(pPacket2Search, pCurPacket, cbMatch)) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return pCurPacket; + } + } + return NULL; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinSearchPacketBySG(PVBOXNETFLT_SINGLE_LIST pList, PINTNETSG pSG, int cbMatch, bool bRemove) +{ + PSINGLE_LIST_ENTRY pHead = &pList->Head; + PSINGLE_LIST_ENTRY pCur; + PSINGLE_LIST_ENTRY pPrev; + PNDIS_PACKET pCurPacket; + for (pCur = pHead->Next, pPrev = pHead; pCur; pPrev = pCur, pCur = pCur->Next) + { + pCurPacket = VBOX_SLE_2_SENDPACKET(pCur); + if (vboxNetFltWinMatchPacketAndSG(pCurPacket, pSG, cbMatch)) + { + if (bRemove) + { + pPrev->Next = pCur->Next; + if (pCur == pList->pTail) + { + pList->pTail = pPrev; + } + } + return pCurPacket; + } + } + return NULL; +} + +#endif /* #if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) */ + +DECLINLINE(bool) vboxNetFltWinSListIsEmpty(PVBOXNETFLT_SINGLE_LIST pList) +{ + return !pList->Head.Next; +} + +DECLINLINE(void) vboxNetFltWinPutTail(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + pList->pTail->Next = pEntry; + pList->pTail = pEntry; + pEntry->Next = NULL; +} + +DECLINLINE(void) vboxNetFltWinPutHead(PVBOXNETFLT_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + pEntry->Next = pList->Head.Next; + pList->Head.Next = pEntry; + if (!pEntry->Next) + pList->pTail = pEntry; +} + +DECLINLINE(PSINGLE_LIST_ENTRY) vboxNetFltWinGetHead(PVBOXNETFLT_SINGLE_LIST pList) +{ + PSINGLE_LIST_ENTRY pEntry = pList->Head.Next; + if (pEntry && pEntry == pList->pTail) + { + pList->Head.Next = NULL; + pList->pTail = &pList->Head; + } + return pEntry; +} + +DECLINLINE(bool) vboxNetFltWinInterlockedSearchListEntry(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry2Search, bool bRemove) +{ + bool bFound; + NdisAcquireSpinLock(&pList->Lock); + bFound = vboxNetFltWinSearchListEntry(&pList->List, pEntry2Search, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return bFound; +} + +#if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinInterlockedSearchPacket(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PNDIS_PACKET pPacket2Search, int cbMatch, bool bRemove) +{ + PNDIS_PACKET pFound; + NdisAcquireSpinLock(&pList->Lock); + pFound = vboxNetFltWinSearchPacket(&pList->List, pPacket2Search, cbMatch, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return pFound; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinInterlockedSearchPacketBySG(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PINTNETSG pSG, int cbMatch, bool bRemove) +{ + PNDIS_PACKET pFound; + NdisAcquireSpinLock(&pList->Lock); + pFound = vboxNetFltWinSearchPacketBySG(&pList->List, pSG, cbMatch, bRemove); + NdisReleaseSpinLock(&pList->Lock); + return pFound; +} +#endif /* #if !defined(VBOX_LOOPBACK_USEFLAGS) || defined(DEBUG_NETFLT_PACKETS) */ + +DECLINLINE(void) vboxNetFltWinInterlockedPutTail(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + NdisAcquireSpinLock(&pList->Lock); + vboxNetFltWinPutTail(&pList->List, pEntry); + NdisReleaseSpinLock(&pList->Lock); +} + +DECLINLINE(void) vboxNetFltWinInterlockedPutHead(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList, PSINGLE_LIST_ENTRY pEntry) +{ + NdisAcquireSpinLock(&pList->Lock); + vboxNetFltWinPutHead(&pList->List, pEntry); + NdisReleaseSpinLock(&pList->Lock); +} + +DECLINLINE(PSINGLE_LIST_ENTRY) vboxNetFltWinInterlockedGetHead(PVBOXNETFLT_INTERLOCKED_SINGLE_LIST pList) +{ + PSINGLE_LIST_ENTRY pEntry; + NdisAcquireSpinLock(&pList->Lock); + pEntry = vboxNetFltWinGetHead(&pList->List); + NdisReleaseSpinLock(&pList->Lock); + return pEntry; +} + +# if defined(DEBUG_NETFLT_PACKETS) || !defined(VBOX_LOOPBACK_USEFLAGS) +DECLINLINE(void) vboxNetFltWinLbPutSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, bool bFromIntNet) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + pSrv->bFromIntNet = bFromIntNet; + vboxNetFltWinInterlockedPutHead(&pNetFlt->u.s.WinIf.SendPacketQueue, &pSrv->ListEntry); +} + +DECLINLINE(bool) vboxNetFltWinLbIsFromIntNet(PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + return pSrv->bFromIntNet; +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinLbSearchLoopBack(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, bool bRemove) +{ + return vboxNetFltWinInterlockedSearchPacket(&pNetFlt->u.s.WinIf.SendPacketQueue, pPacket, VBOXNETFLT_PACKETMATCH_LENGTH, bRemove); +} + +DECLINLINE(PNDIS_PACKET) vboxNetFltWinLbSearchLoopBackBySG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, bool bRemove) +{ + return vboxNetFltWinInterlockedSearchPacketBySG(&pNetFlt->u.s.WinIf.SendPacketQueue, pSG, VBOXNETFLT_PACKETMATCH_LENGTH, bRemove); +} + +DECLINLINE(bool) vboxNetFltWinLbRemoveSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket) +{ + PVBOXNETFLT_PKTRSVD_PT pSrv = (PVBOXNETFLT_PKTRSVD_PT)pPacket->ProtocolReserved; + bool bRet = vboxNetFltWinInterlockedSearchListEntry(&pNetFlt->u.s.WinIf.SendPacketQueue, &pSrv->ListEntry, true); +#ifdef DEBUG_misha + Assert(bRet == (pNetFlt->enmTrunkState == INTNETTRUNKIFSTATE_ACTIVE)); +#endif + return bRet; +} + +# endif + +#endif + +#ifdef DEBUG_misha +DECLHIDDEN(bool) vboxNetFltWinCheckMACs(PNDIS_PACKET pPacket, PRTMAC pDst, PRTMAC pSrc); +DECLHIDDEN(bool) vboxNetFltWinCheckMACsSG(PINTNETSG pSG, PRTMAC pDst, PRTMAC pSrc); +extern RTMAC g_vboxNetFltWinVerifyMACBroadcast; +extern RTMAC g_vboxNetFltWinVerifyMACGuest; + +# define VBOXNETFLT_LBVERIFY(_pnf, _p) \ + do { \ + Assert(!vboxNetFltWinCheckMACs(_p, NULL, &g_vboxNetFltWinVerifyMACGuest)); \ + Assert(!vboxNetFltWinCheckMACs(_p, NULL, &(_pnf)->u.s.MacAddr)); \ + } while (0) + +# define VBOXNETFLT_LBVERIFYSG(_pnf, _p) \ + do { \ + Assert(!vboxNetFltWinCheckMACsSG(_p, NULL, &g_vboxNetFltWinVerifyMACGuest)); \ + Assert(!vboxNetFltWinCheckMACsSG(_p, NULL, &(_pnf)->u.s.MacAddr)); \ + } while (0) + +#else +# define VBOXNETFLT_LBVERIFY(_pnf, _p) do { } while (0) +# define VBOXNETFLT_LBVERIFYSG(_pnf, _p) do { } while (0) +#endif + +/** initializes the list */ +#define INIT_SINGLE_LIST(_pList) \ + { \ + (_pList)->Head.Next = NULL; \ + (_pList)->pTail = &(_pList)->Head; \ + } + +/** initializes the list */ +#define INIT_INTERLOCKED_SINGLE_LIST(_pList) \ + do { \ + INIT_SINGLE_LIST(&(_pList)->List); \ + NdisAllocateSpinLock(&(_pList)->Lock); \ + } while (0) + +/** delete the packet queue */ +#define FINI_INTERLOCKED_SINGLE_LIST(_pList) \ + do { \ + Assert(vboxNetFltWinSListIsEmpty(&(_pList)->List)); \ + NdisFreeSpinLock(&(_pList)->Lock); \ + } while (0) + + +/************************************************************************** + * PVBOXNETFLTINS , WinIf reference/dereference (i.e. retain/release) API * + **************************************************************************/ + + +DECLHIDDEN(void) vboxNetFltWinWaitDereference(PVBOXNETFLT_WINIF_DEVICE pState); + +DECLINLINE(void) vboxNetFltWinReferenceModeNetFlt(PVBOXNETFLTINS pIns) +{ + ASMAtomicIncU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs); +} + +DECLINLINE(void) vboxNetFltWinReferenceModePassThru(PVBOXNETFLTINS pIns) +{ + ASMAtomicIncU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs); +} + +DECLINLINE(void) vboxNetFltWinIncReferenceModeNetFlt(PVBOXNETFLTINS pIns, uint32_t v) +{ + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs, v); +} + +DECLINLINE(void) vboxNetFltWinIncReferenceModePassThru(PVBOXNETFLTINS pIns, uint32_t v) +{ + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs, v); +} + +DECLINLINE(void) vboxNetFltWinDereferenceModeNetFlt(PVBOXNETFLTINS pIns) +{ + ASMAtomicDecU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs); +} + +DECLINLINE(void) vboxNetFltWinDereferenceModePassThru(PVBOXNETFLTINS pIns) +{ + ASMAtomicDecU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceModeNetFlt(PVBOXNETFLTINS pIns, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModeNetFltRefs, (uint32_t)(-((int32_t)v))); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceModePassThru(PVBOXNETFLTINS pIns, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((volatile uint32_t *)&pIns->u.s.cModePassThruRefs, (uint32_t)(-((int32_t)v))); +} + +DECLINLINE(void) vboxNetFltWinSetPowerState(PVBOXNETFLT_WINIF_DEVICE pState, NDIS_DEVICE_POWER_STATE State) +{ + ASMAtomicUoWriteU32((volatile uint32_t *)&pState->PowerState, State); +} + +DECLINLINE(NDIS_DEVICE_POWER_STATE) vboxNetFltWinGetPowerState(PVBOXNETFLT_WINIF_DEVICE pState) +{ + return (NDIS_DEVICE_POWER_STATE)ASMAtomicUoReadU32((volatile uint32_t *)&pState->PowerState); +} + +DECLINLINE(void) vboxNetFltWinSetOpState(PVBOXNETFLT_WINIF_DEVICE pState, VBOXNETDEVOPSTATE State) +{ + ASMAtomicUoWriteU32((volatile uint32_t *)&pState->OpState, State); +} + +DECLINLINE(VBOXNETDEVOPSTATE) vboxNetFltWinGetOpState(PVBOXNETFLT_WINIF_DEVICE pState) +{ + return (VBOXNETDEVOPSTATE)ASMAtomicUoReadU32((volatile uint32_t *)&pState->OpState); +} + +DECLINLINE(bool) vboxNetFltWinDoReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState) +{ + if (vboxNetFltWinGetPowerState(pState) == NdisDeviceStateD0 && vboxNetFltWinGetOpState(pState) == kVBoxNetDevOpState_Initialized) + { + /** @todo r=bird: Since this is a volatile member, why don't you declare it as + * such and save yourself all the casting? */ + ASMAtomicIncU32((uint32_t volatile *)&pState->cReferences); + return true; + } + return false; +} + +#ifndef VBOXNETADP +DECLINLINE(bool) vboxNetFltWinDoReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2) +{ + if (vboxNetFltWinGetPowerState(pState1) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState1) == kVBoxNetDevOpState_Initialized + && vboxNetFltWinGetPowerState(pState2) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState2) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicIncU32((uint32_t volatile *)&pState1->cReferences); + ASMAtomicIncU32((uint32_t volatile *)&pState2->cReferences); + return true; + } + return false; +} +#endif + +DECLINLINE(void) vboxNetFltWinDereferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState) +{ + ASMAtomicDecU32((uint32_t volatile *)&pState->cReferences); + /** @todo r=bird: Add comment explaining why these cannot hit 0 or why + * reference are counted */ +} + +#ifndef VBOXNETADP +DECLINLINE(void) vboxNetFltWinDereferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2) +{ + ASMAtomicDecU32((uint32_t volatile *)&pState1->cReferences); + ASMAtomicDecU32((uint32_t volatile *)&pState2->cReferences); +} +#endif + +DECLINLINE(void) vboxNetFltWinDecReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState, uint32_t v) +{ + Assert(v); + ASMAtomicAddU32((uint32_t volatile *)&pState->cReferences, (uint32_t)(-((int32_t)v))); +} + +#ifndef VBOXNETADP +DECLINLINE(void) vboxNetFltWinDecReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2, uint32_t v) +{ + ASMAtomicAddU32((uint32_t volatile *)&pState1->cReferences, (uint32_t)(-((int32_t)v))); + ASMAtomicAddU32((uint32_t volatile *)&pState2->cReferences, (uint32_t)(-((int32_t)v))); +} +#endif + +DECLINLINE(bool) vboxNetFltWinDoIncReferenceDevice(PVBOXNETFLT_WINIF_DEVICE pState, uint32_t v) +{ + Assert(v); + if (vboxNetFltWinGetPowerState(pState) == NdisDeviceStateD0 && vboxNetFltWinGetOpState(pState) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicAddU32((uint32_t volatile *)&pState->cReferences, v); + return true; + } + return false; +} + +#ifndef VBOXNETADP +DECLINLINE(bool) vboxNetFltWinDoIncReferenceDevices(PVBOXNETFLT_WINIF_DEVICE pState1, PVBOXNETFLT_WINIF_DEVICE pState2, uint32_t v) +{ + if (vboxNetFltWinGetPowerState(pState1) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState1) == kVBoxNetDevOpState_Initialized + && vboxNetFltWinGetPowerState(pState2) == NdisDeviceStateD0 + && vboxNetFltWinGetOpState(pState2) == kVBoxNetDevOpState_Initialized) + { + ASMAtomicAddU32((uint32_t volatile *)&pState1->cReferences, v); + ASMAtomicAddU32((uint32_t volatile *)&pState2->cReferences, v); + return true; + } + return false; +} +#endif + + +DECLINLINE(bool) vboxNetFltWinReferenceWinIfNetFlt(PVBOXNETFLTINS pNetFlt, bool * pbNetFltActive) +{ + RTSpinlockAcquire((pNetFlt)->hSpinlock); +#ifndef VBOXNETADP + if (!vboxNetFltWinDoReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState)) +#else + if (!vboxNetFltWinDoReferenceDevice(&pNetFlt->u.s.WinIf.MpState)) +#endif + { + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return false; + } + + if (pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE) + { + vboxNetFltWinReferenceModePassThru(pNetFlt); + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return true; + } + + vboxNetFltRetain((pNetFlt), true /* fBusy */); + vboxNetFltWinReferenceModeNetFlt(pNetFlt); + RTSpinlockRelease((pNetFlt)->hSpinlock); + + *pbNetFltActive = true; + return true; +} + +DECLINLINE(bool) vboxNetFltWinIncReferenceWinIfNetFlt(PVBOXNETFLTINS pNetFlt, uint32_t v, bool *pbNetFltActive) +{ + uint32_t i; + + Assert(v); + if (!v) + { + *pbNetFltActive = false; + return false; + } + + RTSpinlockAcquire((pNetFlt)->hSpinlock); +#ifndef VBOXNETADP + if (!vboxNetFltWinDoIncReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v)) +#else + if (!vboxNetFltWinDoIncReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + *pbNetFltActive = false; + return false; + } + + if (pNetFlt->enmTrunkState != INTNETTRUNKIFSTATE_ACTIVE) + { + vboxNetFltWinIncReferenceModePassThru(pNetFlt, v); + + RTSpinlockRelease((pNetFlt)->hSpinlock); + *pbNetFltActive = false; + return true; + } + + vboxNetFltRetain(pNetFlt, true /* fBusy */); + + vboxNetFltWinIncReferenceModeNetFlt(pNetFlt, v); + + RTSpinlockRelease(pNetFlt->hSpinlock); + + /* we have marked it as busy, so can do the res references outside the lock */ + for (i = 0; i < v-1; i++) + { + vboxNetFltRetain(pNetFlt, true /* fBusy */); + } + + *pbNetFltActive = true; + + return true; +} + +DECLINLINE(void) vboxNetFltWinDecReferenceNetFlt(PVBOXNETFLTINS pNetFlt, uint32_t n) +{ + uint32_t i; + for (i = 0; i < n; i++) + { + vboxNetFltRelease(pNetFlt, true); + } + + vboxNetFltWinDecReferenceModeNetFlt(pNetFlt, n); +} + +DECLINLINE(void) vboxNetFltWinDereferenceNetFlt(PVBOXNETFLTINS pNetFlt) +{ + vboxNetFltRelease(pNetFlt, true); + + vboxNetFltWinDereferenceModeNetFlt(pNetFlt); +} + +DECLINLINE(void) vboxNetFltWinDecReferenceWinIf(PVBOXNETFLTINS pNetFlt, uint32_t v) +{ +#ifdef VBOXNETADP + vboxNetFltWinDecReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v); +#else + vboxNetFltWinDecReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v); +#endif +} + +DECLINLINE(void) vboxNetFltWinDereferenceWinIf(PVBOXNETFLTINS pNetFlt) +{ +#ifdef VBOXNETADP + vboxNetFltWinDereferenceDevice(&pNetFlt->u.s.WinIf.MpState); +#else + vboxNetFltWinDereferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState); +#endif +} + +DECLINLINE(bool) vboxNetFltWinIncReferenceWinIf(PVBOXNETFLTINS pNetFlt, uint32_t v) +{ + Assert(v); + if (!v) + { + return false; + } + + RTSpinlockAcquire(pNetFlt->hSpinlock); +#ifdef VBOXNETADP + if (vboxNetFltWinDoIncReferenceDevice(&pNetFlt->u.s.WinIf.MpState, v)) +#else + if (vboxNetFltWinDoIncReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState, v)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + return true; + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + return false; +} + +DECLINLINE(bool) vboxNetFltWinReferenceWinIf(PVBOXNETFLTINS pNetFlt) +{ + RTSpinlockAcquire(pNetFlt->hSpinlock); +#ifdef VBOXNETADP + if (vboxNetFltWinDoReferenceDevice(&pNetFlt->u.s.WinIf.MpState)) +#else + if (vboxNetFltWinDoReferenceDevices(&pNetFlt->u.s.WinIf.MpState, &pNetFlt->u.s.WinIf.PtState)) +#endif + { + RTSpinlockRelease(pNetFlt->hSpinlock); + return true; + } + + RTSpinlockRelease(pNetFlt->hSpinlock); + return false; +} + +/*********************************************** + * methods for accessing the network card info * + ***********************************************/ + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinGetMacAddress(PVBOXNETFLTINS pNetFlt, PRTMAC pMac); +DECLHIDDEN(bool) vboxNetFltWinIsPromiscuous(PVBOXNETFLTINS pNetFlt); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinSetPromiscuous(PVBOXNETFLTINS pNetFlt, bool bYes); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinQueryPhysicalMedium(PVBOXNETFLTINS pNetFlt, NDIS_PHYSICAL_MEDIUM * pMedium); + +/********************* + * mem alloc API * + *********************/ + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinMemAlloc(PVOID* ppMemBuf, UINT cbLength); + +DECLHIDDEN(void) vboxNetFltWinMemFree(PVOID pMemBuf); + +/* convenience method used which allocates and initializes the PINTNETSG containing one + * segment referring the buffer of size cbBufSize + * the allocated PINTNETSG should be freed with the vboxNetFltWinMemFree. + * + * This is used when our ProtocolReceive callback is called and we have to return the indicated NDIS_PACKET + * on a callback exit. This is why we allocate the PINTNETSG and put the packet info there and enqueue it + * for the packet queue */ +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinAllocSG(UINT cbBufSize, PINTNETSG *ppSG); + +/************************ + * WinIf init/fini API * + ************************/ +#if defined(VBOXNETADP) +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, NDIS_HANDLE hMiniportAdapter, PNDIS_STRING pBindToMiniportName /* actually this is our miniport name*/, NDIS_HANDLE hWrapperConfigurationContext); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf); +#else +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitBind(PVBOXNETFLTINS *ppNetFlt, PNDIS_STRING pOurMiniportName, PNDIS_STRING pBindToMiniportName); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPtInitWinIf(PVBOXNETFLTWIN pWinIf, PNDIS_STRING pOurDeviceName); +#endif + +DECLHIDDEN(VOID) vboxNetFltWinPtFiniWinIf(PVBOXNETFLTWIN pWinIf); + +/************************************ + * Execute Job at passive level API * + ************************************/ + +typedef VOID (*PFNVBOXNETFLT_JOB_ROUTINE) (PVOID pContext); + +DECLHIDDEN(VOID) vboxNetFltWinJobSynchExecAtPassive(PFNVBOXNETFLT_JOB_ROUTINE pfnRoutine, PVOID pContext); + +/******************************* + * Ndis Packets processing API * + *******************************/ +DECLHIDDEN(PNDIS_PACKET) vboxNetFltWinNdisPacketFromSG(PVBOXNETFLTINS pNetFlt, PINTNETSG pSG, PVOID pBufToFree, bool bToWire, bool bCopyMemory); + +DECLHIDDEN(void) vboxNetFltWinFreeSGNdisPacket(PNDIS_PACKET pPacket, bool bFreeMem); + +#ifdef DEBUG_NETFLT_PACKETS +#define DBG_CHECK_PACKETS(_p1, _p2) \ + { \ + bool _b = vboxNetFltWinMatchPackets(_p1, _p2, -1); \ + Assert(_b); \ + } + +#define DBG_CHECK_PACKET_AND_SG(_p, _sg) \ + { \ + bool _b = vboxNetFltWinMatchPacketAndSG(_p, _sg, -1); \ + Assert(_b); \ + } + +#define DBG_CHECK_SGS(_sg1, _sg2) \ + { \ + bool _b = vboxNetFltWinMatchSGs(_sg1, _sg2, -1); \ + Assert(_b); \ + } + +#else +#define DBG_CHECK_PACKETS(_p1, _p2) +#define DBG_CHECK_PACKET_AND_SG(_p, _sg) +#define DBG_CHECK_SGS(_sg1, _sg2) +#endif + +/** + * Ndis loops back broadcast packets posted to the wire by IntNet + * This routine is used in the mechanism of preventing this looping + * + * @param pAdapt + * @param pPacket + * @param bOnRecv true is we are receiving the packet from the wire + * false otherwise (i.e. the packet is from the host) + * + * @return true if the packet is a looped back one, false otherwise + */ +#ifdef VBOX_LOOPBACK_USEFLAGS +DECLINLINE(bool) vboxNetFltWinIsLoopedBackPacket(PNDIS_PACKET pPacket) +{ + return (NdisGetPacketFlags(pPacket) & g_fPacketIsLoopedBack) == g_fPacketIsLoopedBack; +} +#endif + +/************************************************************** + * utility methods for ndis packet creation/initialization * + **************************************************************/ + +#define VBOXNETFLT_OOB_INIT(_p) \ + { \ + NdisZeroMemory(NDIS_OOB_DATA_FROM_PACKET(_p), sizeof(NDIS_PACKET_OOB_DATA)); \ + NDIS_SET_PACKET_HEADER_SIZE(_p, VBOXNETFLT_PACKET_ETHEADER_SIZE); \ + } + +#ifndef VBOXNETADP + +DECLINLINE(NDIS_STATUS) vboxNetFltWinCopyPacketInfoOnRecv(PNDIS_PACKET pDstPacket, PNDIS_PACKET pSrcPacket, bool bForceStatusResources) +{ + NDIS_STATUS Status = bForceStatusResources ? NDIS_STATUS_RESOURCES : NDIS_GET_PACKET_STATUS(pSrcPacket); + NDIS_SET_PACKET_STATUS(pDstPacket, Status); + + NDIS_PACKET_FIRST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(pSrcPacket); + NDIS_PACKET_LAST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(pSrcPacket); + + NdisGetPacketFlags(pDstPacket) = NdisGetPacketFlags(pSrcPacket); + + NDIS_SET_ORIGINAL_PACKET(pDstPacket, NDIS_GET_ORIGINAL_PACKET(pSrcPacket)); + NDIS_SET_PACKET_HEADER_SIZE(pDstPacket, NDIS_GET_PACKET_HEADER_SIZE(pSrcPacket)); + + return Status; +} + +DECLINLINE(void) vboxNetFltWinCopyPacketInfoOnSend(PNDIS_PACKET pDstPacket, PNDIS_PACKET pSrcPacket) +{ + NDIS_PACKET_FIRST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_FIRST_NDIS_BUFFER(pSrcPacket); + NDIS_PACKET_LAST_NDIS_BUFFER(pDstPacket) = NDIS_PACKET_LAST_NDIS_BUFFER(pSrcPacket); + + NdisGetPacketFlags(pDstPacket) = NdisGetPacketFlags(pSrcPacket); + + NdisMoveMemory(NDIS_OOB_DATA_FROM_PACKET(pDstPacket), + NDIS_OOB_DATA_FROM_PACKET(pSrcPacket), + sizeof (NDIS_PACKET_OOB_DATA)); + + NdisIMCopySendPerPacketInfo(pDstPacket, pSrcPacket); + + PVOID pMediaSpecificInfo = NULL; + UINT fMediaSpecificInfoSize = 0; + + NDIS_GET_PACKET_MEDIA_SPECIFIC_INFO(pSrcPacket, &pMediaSpecificInfo, &fMediaSpecificInfoSize); + + if (pMediaSpecificInfo || fMediaSpecificInfoSize) + { + NDIS_SET_PACKET_MEDIA_SPECIFIC_INFO(pDstPacket, pMediaSpecificInfo, fMediaSpecificInfoSize); + } +} + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareSendPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinPrepareRecvPacket(PVBOXNETFLTINS pNetFlt, PNDIS_PACKET pPacket, PNDIS_PACKET *ppMyPacket, bool bDpr); +#endif + +DECLHIDDEN(void) vboxNetFltWinSleep(ULONG milis); + +#define MACS_EQUAL(_m1, _m2) \ + ((_m1).au16[0] == (_m2).au16[0] \ + && (_m1).au16[1] == (_m2).au16[1] \ + && (_m1).au16[2] == (_m2).au16[2]) + + +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinDetachFromInterface(PVBOXNETFLTINS pNetFlt, bool bOnUnbind); +DECLHIDDEN(NDIS_STATUS) vboxNetFltWinCopyString(PNDIS_STRING pDst, PNDIS_STRING pSrc); + + +/** + * Sets the enmState member atomically. + * + * Used for all updates. + * + * @param pThis The instance. + * @param enmNewState The new value. + */ +DECLINLINE(void) vboxNetFltWinSetWinIfState(PVBOXNETFLTINS pNetFlt, VBOXNETFLT_WINIFSTATE enmNewState) +{ + ASMAtomicWriteU32((uint32_t volatile *)&pNetFlt->u.s.WinIf.enmState, enmNewState); +} + +/** + * Gets the enmState member atomically. + * + * Used for all reads. + * + * @returns The enmState value. + * @param pThis The instance. + */ +DECLINLINE(VBOXNETFLT_WINIFSTATE) vboxNetFltWinGetWinIfState(PVBOXNETFLTINS pNetFlt) +{ + return (VBOXNETFLT_WINIFSTATE)ASMAtomicUoReadU32((uint32_t volatile *)&pNetFlt->u.s.WinIf.enmState); +} + +/* reference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvReference(); +/* dereference the driver module to prevent driver unload */ +DECLHIDDEN(void) vboxNetFltWinDrvDereference(); + + +#ifndef VBOXNETADP +# define VBOXNETFLT_PROMISCUOUS_SUPPORTED(_pNetFlt) (!(_pNetFlt)->fDisablePromiscuous) +#else +# define STATISTIC_INCREASE(_s) ASMAtomicIncU32((uint32_t volatile *)&(_s)); + +DECLHIDDEN(void) vboxNetFltWinGenerateMACAddress(RTMAC *pMac); +DECLHIDDEN(int) vboxNetFltWinMAC2NdisString(RTMAC *pMac, PNDIS_STRING pNdisString); +DECLHIDDEN(int) vboxNetFltWinMACFromNdisString(RTMAC *pMac, PNDIS_STRING pNdisString); + +#endif +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetFltRt_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp new file mode 100644 index 00000000..6520e909 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.cpp @@ -0,0 +1,2736 @@ +/* $Id: VBoxNetLwf-win.cpp $ */ +/** @file + * VBoxNetLwf-win.cpp - NDIS6 Bridged Networking Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ +#define LOG_GROUP LOG_GROUP_NET_FLT_DRV + +/* + * If VBOXNETLWF_SYNC_SEND is defined we won't allocate data buffers, but use + * the original buffers coming from IntNet to build MDLs around them. This + * also means that we need to wait for send operation to complete before + * returning the buffers, which hinders performance way too much. + */ +//#define VBOXNETLWF_SYNC_SEND + +/* + * If VBOXNETLWF_FIXED_SIZE_POOLS is defined we pre-allocate data buffers of + * fixed size in five pools. Each pool uses different size to accomodate packets + * of various sizes. We allocate these buffers once and re-use them when send + * operation is complete. + * If VBOXNETLWF_FIXED_SIZE_POOLS is not defined we allocate data buffers before + * each send operation and free then upon completion. + */ +#define VBOXNETLWF_FIXED_SIZE_POOLS + +/* + * Don't ask me why it is 42. Empirically this is what goes down the stack. + * OTOH, as we know from trustworthy sources, 42 is the answer, so be it. + */ +#define VBOXNETLWF_MAX_FRAME_SIZE(mtu) (mtu + 42) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LogError(x) DbgPrint x + +#if 0 +#undef Log +#define Log(x) DbgPrint x +#undef LogFlow +#define LogFlow(x) DbgPrint x +#endif + +/** We have an entirely different structure than the one defined in VBoxNetFltCmn-win.h */ +typedef struct VBOXNETFLTWIN +{ + /** filter module context handle */ + NDIS_HANDLE hModuleCtx; + /** IP address change notifier handle */ + HANDLE hNotifier; /* Must be here as hModuleCtx may already be NULL when vboxNetFltOsDeleteInstance is called */ +} VBOXNETFLTWIN, *PVBOXNETFLTWIN; +#define VBOXNETFLT_NO_PACKET_QUEUE +#define VBOXNETFLT_OS_SPECFIC 1 +#include "VBoxNetFltInternal.h" + +#include "VBoxNetLwf-win.h" +#include "VBox/VBoxNetCmn-win.h" + +typedef enum { + LwfState_Detached = 0, + LwfState_Attaching, + LwfState_Paused, + LwfState_Restarting, + LwfState_Running, + LwfState_Pausing, + LwfState_32BitHack = 0x7fffffff +} VBOXNETLWFSTATE; + +/* + * Valid state transitions are: + * 1) Disconnected -> Connecting : start the worker thread, attempting to init IDC; + * 2) Connecting -> Disconnected : failed to start IDC init worker thread; + * 3) Connecting -> Connected : IDC init successful, terminate the worker; + * 4) Connecting -> Stopping : IDC init incomplete, but the driver is being unloaded, terminate the worker; + * 5) Connected -> Stopping : IDC init was successful, no worker, the driver is being unloaded; + * + * Driver terminates in Stopping state. + */ +typedef enum { + LwfIdcState_Disconnected = 0, /* Initial state */ + LwfIdcState_Connecting, /* Attemping to init IDC, worker thread running */ + LwfIdcState_Connected, /* Successfully connected to IDC, worker thread terminated */ + LwfIdcState_Stopping /* Terminating the worker thread and disconnecting IDC */ +} VBOXNETLWFIDCSTATE; + +struct _VBOXNETLWF_MODULE; + +typedef struct VBOXNETLWFGLOBALS +{ + /** synch event used for device creation synchronization */ + //KEVENT SynchEvent; + /** Device reference count */ + //int cDeviceRefs; + /** ndis device */ + NDIS_HANDLE hDevice; + /** device object */ + PDEVICE_OBJECT pDevObj; + /** our filter driver handle */ + NDIS_HANDLE hFilterDriver; + /** lock protecting the module list */ + NDIS_SPIN_LOCK Lock; + /** the head of module list */ + RTLISTANCHOR listModules; + /** IDC initialization state */ + volatile uint32_t enmIdcState; + /** IDC init thread handle */ + HANDLE hInitIdcThread; +} VBOXNETLWFGLOBALS, *PVBOXNETLWFGLOBALS; + +/** + * The (common) global data. + */ +static VBOXNETFLTGLOBALS g_VBoxNetFltGlobals; +/* win-specific global data */ +VBOXNETLWFGLOBALS g_VBoxNetLwfGlobals; + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS +static ULONG g_cbPool[] = { 576+56, 1556, 4096+56, 6192+56, 9056 }; +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + +typedef struct _VBOXNETLWF_MODULE { + RTLISTNODE node; + + NDIS_HANDLE hFilter; +#ifndef VBOXNETLWF_FIXED_SIZE_POOLS + NDIS_HANDLE hPool; +#else /* VBOXNETLWF_FIXED_SIZE_POOLS */ + NDIS_HANDLE hPool[RT_ELEMENTS(g_cbPool)]; +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + PVBOXNETLWFGLOBALS pGlobals; + /** Associated instance of NetFlt, one-to-one relationship */ + PVBOXNETFLTINS pNetFlt; /// @todo Consider automic access! + /** Module state as described in http://msdn.microsoft.com/en-us/library/windows/hardware/ff550017(v=vs.85).aspx */ + volatile uint32_t enmState; /* No lock needed yet, atomic should suffice. */ + /** Mutex to prevent pausing while transmitting on behalf of NetFlt */ + NDIS_MUTEX InTransmit; +#ifdef VBOXNETLWF_SYNC_SEND + /** Event signalled when sending to the wire is complete */ + KEVENT EventWire; + /** Event signalled when NDIS returns our receive notification */ + KEVENT EventHost; +#else /* !VBOXNETLWF_SYNC_SEND */ + /** Event signalled when all pending sends (both to wire and host) have completed */ + NDIS_EVENT EventSendComplete; + /** Counter for pending sends (both to wire and host) */ + int32_t cPendingBuffers; + /** Work Item to deliver offloading indications at passive IRQL */ + NDIS_HANDLE hWorkItem; +#endif /* !VBOXNETLWF_SYNC_SEND */ + /** MAC address of underlying adapter */ + RTMAC MacAddr; + /** Size of offload config structure */ + USHORT cbOffloadConfig; + /** Saved offload configuration */ + PNDIS_OFFLOAD pSavedOffloadConfig; + /** Temporary buffer for disabling offload configuration */ + PNDIS_OFFLOAD pDisabledOffloadConfig; + /** the cloned request we have passed down */ + PNDIS_OID_REQUEST pPendingRequest; + /** true if the underlying miniport supplied offloading config */ + bool fOffloadConfigValid; + /** true if the trunk expects data from us */ + bool fActive; + /** true if the host wants the adapter to be in promisc mode */ + bool fHostPromisc; + /** true if the user wants packets being sent or received by VMs to be visible to the host in promisc mode */ + bool fPassVmTrafficToHost; + /** Name of underlying adapter */ + char szMiniportName[1]; +} VBOXNETLWF_MODULE; +typedef VBOXNETLWF_MODULE *PVBOXNETLWF_MODULE; + +/* + * A structure to wrap OID requests in. + */ +typedef struct _VBOXNETLWF_OIDREQ { + NDIS_OID_REQUEST Request; + NDIS_STATUS Status; + NDIS_EVENT Event; +} VBOXNETLWF_OIDREQ; +typedef VBOXNETLWF_OIDREQ *PVBOXNETLWF_OIDREQ; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static FILTER_ATTACH vboxNetLwfWinAttach; +static FILTER_DETACH vboxNetLwfWinDetach; +static FILTER_RESTART vboxNetLwfWinRestart; +static FILTER_PAUSE vboxNetLwfWinPause; +static FILTER_OID_REQUEST vboxNetLwfWinOidRequest; +static FILTER_OID_REQUEST_COMPLETE vboxNetLwfWinOidRequestComplete; +//static FILTER_CANCEL_OID_REQUEST vboxNetLwfWinCancelOidRequest; +static FILTER_STATUS vboxNetLwfWinStatus; +//static FILTER_NET_PNP_EVENT vboxNetLwfWinPnPEvent; +static FILTER_SEND_NET_BUFFER_LISTS vboxNetLwfWinSendNetBufferLists; +static FILTER_SEND_NET_BUFFER_LISTS_COMPLETE vboxNetLwfWinSendNetBufferListsComplete; +static FILTER_RECEIVE_NET_BUFFER_LISTS vboxNetLwfWinReceiveNetBufferLists; +static FILTER_RETURN_NET_BUFFER_LISTS vboxNetLwfWinReturnNetBufferLists; +static KSTART_ROUTINE vboxNetLwfWinInitIdcWorker; + +static VOID vboxNetLwfWinUnloadDriver(IN PDRIVER_OBJECT pDriver); +static int vboxNetLwfWinInitBase(void); +static int vboxNetLwfWinFini(void); + + + +/** + * Logs an error to the system event log. + * + * @param ErrCode Error to report to event log. + * @param ReturnedStatus Error that was reported by the driver to the caller. + * @param uErrId Unique error id representing the location in the driver. + * @param cbDumpData Number of bytes at pDumpData. + * @param pDumpData Pointer to data that will be added to the message (see 'details' tab). + */ +static void vboxNetLwfLogErrorEvent(NTSTATUS uErrCode, NTSTATUS uReturnedStatus, ULONG uErrId) +{ + /* Figure out how many modules are attached and if they are going to fit into the dump data. */ + unsigned cMaxModules = (ERROR_LOG_MAXIMUM_SIZE - FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData)) / sizeof(RTMAC); + unsigned cModules = 0; + PVBOXNETLWF_MODULE pModuleCtx; + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + ++cModules; + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + /* Prevent overflow */ + if (cModules > cMaxModules) + cModules = cMaxModules; + + /* DumpDataSize must be a multiple of sizeof(ULONG). */ + unsigned cbDumpData = (cModules * sizeof(RTMAC) + 3) & ~3; + /* Prevent underflow */ + unsigned cbTotal = RT_MAX(FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData, + sizeof(IO_ERROR_LOG_PACKET)); + + PIO_ERROR_LOG_PACKET pErrEntry; + pErrEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(g_VBoxNetLwfGlobals.pDevObj, + (UCHAR)cbTotal); + if (pErrEntry) + { + PRTMAC pDump = (PRTMAC)pErrEntry->DumpData; + /* + * Initialize the whole structure with zeros in case we are suddenly short + * of data because the list is empty or has become smaller. + */ + memset(pErrEntry, 0, cbTotal); + + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + { + /* The list could have been modified while we were allocating the entry, rely on cModules instead! */ + if (cModules-- == 0) + break; + *pDump++ = pModuleCtx->MacAddr; + } + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + + pErrEntry->DumpDataSize = cbDumpData; + pErrEntry->ErrorCode = uErrCode; + pErrEntry->UniqueErrorValue = uErrId; + pErrEntry->FinalStatus = uReturnedStatus; + IoWriteErrorLogEntry(pErrEntry); + } + else + { + DbgPrint("Failed to allocate error log entry (cb=%u)\n", cbTotal); + } +} + +#ifdef DEBUG + +static const char *vboxNetLwfWinStatusToText(NDIS_STATUS code) +{ + switch (code) + { + case NDIS_STATUS_MEDIA_CONNECT: return "NDIS_STATUS_MEDIA_CONNECT"; + case NDIS_STATUS_MEDIA_DISCONNECT: return "NDIS_STATUS_MEDIA_DISCONNECT"; + case NDIS_STATUS_RESET_START: return "NDIS_STATUS_RESET_START"; + case NDIS_STATUS_RESET_END: return "NDIS_STATUS_RESET_END"; + case NDIS_STATUS_MEDIA_BUSY: return "NDIS_STATUS_MEDIA_BUSY"; + case NDIS_STATUS_MEDIA_SPECIFIC_INDICATION: return "NDIS_STATUS_MEDIA_SPECIFIC_INDICATION"; + case NDIS_STATUS_LINK_SPEED_CHANGE: return "NDIS_STATUS_LINK_SPEED_CHANGE"; + case NDIS_STATUS_LINK_STATE: return "NDIS_STATUS_LINK_STATE"; + case NDIS_STATUS_PORT_STATE: return "NDIS_STATUS_PORT_STATE"; + case NDIS_STATUS_OPER_STATUS: return "NDIS_STATUS_OPER_STATUS"; + case NDIS_STATUS_NETWORK_CHANGE: return "NDIS_STATUS_NETWORK_CHANGE"; + case NDIS_STATUS_PACKET_FILTER: return "NDIS_STATUS_PACKET_FILTER"; + case NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG: return "NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG"; + case NDIS_STATUS_TASK_OFFLOAD_HARDWARE_CAPABILITIES: return "NDIS_STATUS_TASK_OFFLOAD_HARDWARE_CAPABILITIES"; + case NDIS_STATUS_OFFLOAD_ENCASPULATION_CHANGE: return "NDIS_STATUS_OFFLOAD_ENCASPULATION_CHANGE"; + case NDIS_STATUS_TCP_CONNECTION_OFFLOAD_HARDWARE_CAPABILITIES: return "NDIS_STATUS_TCP_CONNECTION_OFFLOAD_HARDWARE_CAPABILITIES"; + } + return "unknown"; +} + +static void vboxNetLwfWinDumpFilterTypes(ULONG uFlags) +{ + if (uFlags & NDIS_PACKET_TYPE_DIRECTED) Log5((" NDIS_PACKET_TYPE_DIRECTED\n")); + if (uFlags & NDIS_PACKET_TYPE_MULTICAST) Log5((" NDIS_PACKET_TYPE_MULTICAST\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_MULTICAST) Log5((" NDIS_PACKET_TYPE_ALL_MULTICAST\n")); + if (uFlags & NDIS_PACKET_TYPE_BROADCAST) Log5((" NDIS_PACKET_TYPE_BROADCAST\n")); + if (uFlags & NDIS_PACKET_TYPE_PROMISCUOUS) Log5((" NDIS_PACKET_TYPE_PROMISCUOUS\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_FUNCTIONAL) Log5((" NDIS_PACKET_TYPE_ALL_FUNCTIONAL\n")); + if (uFlags & NDIS_PACKET_TYPE_ALL_LOCAL) Log5((" NDIS_PACKET_TYPE_ALL_LOCAL\n")); + if (uFlags & NDIS_PACKET_TYPE_FUNCTIONAL) Log5((" NDIS_PACKET_TYPE_FUNCTIONAL\n")); + if (uFlags & NDIS_PACKET_TYPE_GROUP) Log5((" NDIS_PACKET_TYPE_GROUP\n")); + if (uFlags & NDIS_PACKET_TYPE_MAC_FRAME) Log5((" NDIS_PACKET_TYPE_MAC_FRAME\n")); + if (uFlags & NDIS_PACKET_TYPE_SMT) Log5((" NDIS_PACKET_TYPE_SMT\n")); + if (uFlags & NDIS_PACKET_TYPE_SOURCE_ROUTING) Log5((" NDIS_PACKET_TYPE_SOURCE_ROUTING\n")); + if (uFlags == 0) Log5((" NONE\n")); +} + +DECLINLINE(void) vboxNetLwfWinDumpEncapsulation(const char *pcszText, ULONG uEncapsulation) +{ + if (uEncapsulation == NDIS_ENCAPSULATION_NOT_SUPPORTED) + Log5(("%s not supported\n", pcszText)); + else + { + Log5(("%s", pcszText)); + if (uEncapsulation & NDIS_ENCAPSULATION_NULL) + Log5((" null")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3) + Log5((" 802.3")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q) + Log5((" 802.3pq")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q_IN_OOB) + Log5((" 802.3pq(oob)")); + if (uEncapsulation & NDIS_ENCAPSULATION_IEEE_LLC_SNAP_ROUTED) + Log5((" LLC")); + Log5(("\n")); + } +} + +DECLINLINE(const char *) vboxNetLwfWinSetOnOffText(ULONG uOnOff) +{ + switch (uOnOff) + { + case NDIS_OFFLOAD_SET_NO_CHANGE: return "no change"; + case NDIS_OFFLOAD_SET_ON: return "on"; + case NDIS_OFFLOAD_SET_OFF: return "off"; + } + return "unknown"; +} + +DECLINLINE(const char *) vboxNetLwfWinOnOffText(ULONG uOnOff) +{ + switch (uOnOff) + { + case NDIS_OFFLOAD_NOT_SUPPORTED: return "off"; + case NDIS_OFFLOAD_SUPPORTED: return "on"; + } + return "unknown"; +} + +DECLINLINE(const char *) vboxNetLwfWinSupportedText(ULONG uSupported) +{ + switch (uSupported) + { + case NDIS_OFFLOAD_NOT_SUPPORTED: return "not supported"; + case NDIS_OFFLOAD_SUPPORTED: return "supported"; + } + return "unknown"; +} + +static void vboxNetLwfWinDumpSetOffloadSettings(PNDIS_OFFLOAD pOffloadConfig) +{ + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv4Transmit.Encapsulation); + Log5((" Checksum.IPv4Transmit.IpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum))); + Log5((" Checksum.IPv4Transmit.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum))); + Log5((" Checksum.IPv4Transmit.IpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Receive.Encapsulation =", pOffloadConfig->Checksum.IPv4Receive.Encapsulation); + Log5((" Checksum.IPv4Receive.IpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpChecksum))); + Log5((" Checksum.IPv4Receive.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.UdpChecksum))); + Log5((" Checksum.IPv4Receive.IpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv6Transmit.Encapsulation); + Log5((" Checksum.IPv6Transmit.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv6Transmit.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum))); + Log5((" Checksum.IPv6Transmit.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Receive.Encapsulation =", pOffloadConfig->Checksum.IPv6Receive.Encapsulation); + Log5((" Checksum.IPv6Receive.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv6Receive.TcpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpChecksum))); + Log5((" Checksum.IPv6Receive.UdpChecksum = %s\n", vboxNetLwfWinSetOnOffText(pOffloadConfig->Checksum.IPv6Receive.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" LsoV1.IPv4.Encapsulation =", pOffloadConfig->LsoV1.IPv4.Encapsulation); + Log5((" LsoV1.IPv4.TcpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.TcpOptions))); + Log5((" LsoV1.IPv4.IpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.IpOptions))); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv4.Encapsulation =", pOffloadConfig->LsoV2.IPv4.Encapsulation); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv6.Encapsulation =", pOffloadConfig->LsoV2.IPv6.Encapsulation); + Log5((" LsoV2.IPv6.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported))); + Log5((" LsoV2.IPv6.TcpOptionsSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported))); +} + +static void vboxNetLwfWinDumpOffloadSettings(PNDIS_OFFLOAD pOffloadConfig) +{ + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv4Transmit.Encapsulation); + Log5((" Checksum.IPv4Transmit.IpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv4Transmit.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum))); + Log5((" Checksum.IPv4Transmit.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum))); + Log5((" Checksum.IPv4Transmit.IpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Transmit.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv4Receive.Encapsulation =", pOffloadConfig->Checksum.IPv4Receive.Encapsulation); + Log5((" Checksum.IPv4Receive.IpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv4Receive.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.TcpChecksum))); + Log5((" Checksum.IPv4Receive.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.UdpChecksum))); + Log5((" Checksum.IPv4Receive.IpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv4Receive.IpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Transmit.Encapsulation =", pOffloadConfig->Checksum.IPv6Transmit.Encapsulation); + Log5((" Checksum.IPv6Transmit.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Transmit.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported))); + Log5((" Checksum.IPv6Transmit.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum))); + Log5((" Checksum.IPv6Transmit.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" Checksum.IPv6Receive.Encapsulation =", pOffloadConfig->Checksum.IPv6Receive.Encapsulation); + Log5((" Checksum.IPv6Receive.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.IpExtensionHeadersSupported))); + Log5((" Checksum.IPv6Receive.TcpOptionsSupported = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpOptionsSupported))); + Log5((" Checksum.IPv6Receive.TcpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.TcpChecksum))); + Log5((" Checksum.IPv6Receive.UdpChecksum = %s\n", vboxNetLwfWinOnOffText(pOffloadConfig->Checksum.IPv6Receive.UdpChecksum))); + vboxNetLwfWinDumpEncapsulation(" LsoV1.IPv4.Encapsulation =", pOffloadConfig->LsoV1.IPv4.Encapsulation); + Log5((" LsoV1.IPv4.TcpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.TcpOptions))); + Log5((" LsoV1.IPv4.IpOptions = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV1.IPv4.IpOptions))); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv4.Encapsulation =", pOffloadConfig->LsoV2.IPv4.Encapsulation); + vboxNetLwfWinDumpEncapsulation(" LsoV2.IPv6.Encapsulation =", pOffloadConfig->LsoV2.IPv6.Encapsulation); + Log5((" LsoV2.IPv6.IpExtensionHeadersSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported))); + Log5((" LsoV2.IPv6.TcpOptionsSupported = %s\n", vboxNetLwfWinSupportedText(pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported))); +} + +static const char *vboxNetLwfWinStateToText(uint32_t enmState) +{ + switch (enmState) + { + case LwfState_Detached: return "Detached"; + case LwfState_Attaching: return "Attaching"; + case LwfState_Paused: return "Paused"; + case LwfState_Restarting: return "Restarting"; + case LwfState_Running: return "Running"; + case LwfState_Pausing: return "Pausing"; + } + return "invalid"; +} + +static void vboxNetLwfWinDumpPackets(const char *pszMsg, PNET_BUFFER_LIST pBufLists) +{ + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + Log6(("%s packet: src=%p cb=%d offset=%d", pszMsg, pList->SourceHandle, NET_BUFFER_DATA_LENGTH(pBuf), NET_BUFFER_DATA_OFFSET(pBuf))); + for (PMDL pMdl = NET_BUFFER_FIRST_MDL(pBuf); + pMdl != NULL; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + Log6((" MDL: cb=%d", MmGetMdlByteCount(pMdl))); + } + Log6(("\n")); + } + } +} + +DECLINLINE(const char *) vboxNetLwfWinEthTypeStr(uint16_t uType) +{ + switch (uType) + { + case RTNET_ETHERTYPE_IPV4: return "IP"; + case RTNET_ETHERTYPE_IPV6: return "IPv6"; + case RTNET_ETHERTYPE_ARP: return "ARP"; + } + return "unknown"; +} + +#define VBOXNETLWF_PKTDMPSIZE 0x50 + +/** + * Dump a packet to debug log. + * + * @param cpPacket The packet. + * @param cb The size of the packet. + * @param cszText A string denoting direction of packet transfer. + */ +DECLINLINE(void) vboxNetLwfWinDumpPacket(PCINTNETSG pSG, const char *cszText) +{ + uint8_t bPacket[VBOXNETLWF_PKTDMPSIZE]; + + uint32_t cb = pSG->cbTotal < VBOXNETLWF_PKTDMPSIZE ? pSG->cbTotal : VBOXNETLWF_PKTDMPSIZE; + IntNetSgReadEx(pSG, 0, cb, bPacket); + + AssertReturnVoid(cb >= 14); + + uint8_t *pHdr = bPacket; + uint8_t *pEnd = bPacket + cb; + AssertReturnVoid(pEnd - pHdr >= 14); + uint16_t uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+12)); + Log2(("NetLWF: %s (%d bytes), %RTmac => %RTmac, EthType=%s(0x%x)\n", + cszText, pSG->cbTotal, pHdr+6, pHdr, vboxNetLwfWinEthTypeStr(uEthType), uEthType)); + pHdr += sizeof(RTNETETHERHDR); + if (uEthType == RTNET_ETHERTYPE_VLAN) + { + AssertReturnVoid(pEnd - pHdr >= 4); + uEthType = RT_N2H_U16(*(uint16_t*)(pHdr+2)); + Log2((" + VLAN: id=%d EthType=%s(0x%x)\n", RT_N2H_U16(*(uint16_t*)(pHdr)) & 0xFFF, + vboxNetLwfWinEthTypeStr(uEthType), uEthType)); + pHdr += 2 * sizeof(uint16_t); + } + uint8_t uProto = 0xFF; + switch (uEthType) + { + case RTNET_ETHERTYPE_IPV6: + AssertReturnVoid(pEnd - pHdr >= 40); + uProto = pHdr[6]; + Log2((" + IPv6: %RTnaipv6 => %RTnaipv6\n", pHdr+8, pHdr+24)); + pHdr += 40; + break; + case RTNET_ETHERTYPE_IPV4: + AssertReturnVoid(pEnd - pHdr >= 20); + uProto = pHdr[9]; + Log2((" + IP: %RTnaipv4 => %RTnaipv4\n", *(uint32_t*)(pHdr+12), *(uint32_t*)(pHdr+16))); + pHdr += (pHdr[0] & 0xF) * 4; + break; + case RTNET_ETHERTYPE_ARP: + AssertReturnVoid(pEnd - pHdr >= 28); + AssertReturnVoid(RT_N2H_U16(*(uint16_t*)(pHdr+2)) == RTNET_ETHERTYPE_IPV4); + switch (RT_N2H_U16(*(uint16_t*)(pHdr+6))) + { + case 1: /* ARP request */ + Log2((" + ARP-REQ: who-has %RTnaipv4 tell %RTnaipv4\n", + *(uint32_t*)(pHdr+24), *(uint32_t*)(pHdr+14))); + break; + case 2: /* ARP reply */ + Log2((" + ARP-RPL: %RTnaipv4 is-at %RTmac\n", + *(uint32_t*)(pHdr+14), pHdr+8)); + break; + default: + Log2((" + ARP: unknown op %d\n", RT_N2H_U16(*(uint16_t*)(pHdr+6)))); + break; + } + break; + /* There is no default case as uProto is initialized with 0xFF */ + } + while (uProto != 0xFF) + { + switch (uProto) + { + case 0: /* IPv6 Hop-by-Hop option*/ + case 60: /* IPv6 Destination option*/ + case 43: /* IPv6 Routing option */ + case 44: /* IPv6 Fragment option */ + Log2((" + IPv6 option (%d): \n", uProto)); + uProto = pHdr[0]; + pHdr += pHdr[1] * 8 + 8; /* Skip to the next extension/protocol */ + break; + case 51: /* IPv6 IPsec AH */ + Log2((" + IPv6 IPsec AH: \n")); + uProto = pHdr[0]; + pHdr += (pHdr[1] + 2) * 4; /* Skip to the next extension/protocol */ + break; + case 50: /* IPv6 IPsec ESP */ + /* Cannot decode IPsec, fall through */ + Log2((" + IPv6 IPsec ESP: \n")); + uProto = 0xFF; + break; + case 59: /* No Next Header */ + Log2((" + IPv6 No Next Header\n")); + uProto = 0xFF; + break; + case 58: /* IPv6-ICMP */ + switch (pHdr[0]) + { + case 1: Log2((" + IPv6-ICMP: destination unreachable, code %d\n", pHdr[1])); break; + case 128: Log2((" + IPv6-ICMP: echo request\n")); break; + case 129: Log2((" + IPv6-ICMP: echo reply\n")); break; + default: Log2((" + IPv6-ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 1: /* ICMP */ + switch (pHdr[0]) + { + case 0: Log2((" + ICMP: echo reply\n")); break; + case 8: Log2((" + ICMP: echo request\n")); break; + case 3: Log2((" + ICMP: destination unreachable, code %d\n", pHdr[1])); break; + default: Log2((" + ICMP: unknown type %d, code %d\n", pHdr[0], pHdr[1])); break; + } + uProto = 0xFF; + break; + case 6: /* TCP */ + Log2((" + TCP: src=%d dst=%d seq=%x ack=%x\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)), + RT_N2H_U32(*(uint32_t*)(pHdr+4)), RT_N2H_U32(*(uint32_t*)(pHdr+8)))); + uProto = 0xFF; + break; + case 17: /* UDP */ + Log2((" + UDP: src=%d dst=%d\n", + RT_N2H_U16(*(uint16_t*)(pHdr)), RT_N2H_U16(*(uint16_t*)(pHdr+2)))); + uProto = 0xFF; + break; + default: + Log2((" + Unknown: proto=0x%x\n", uProto)); + uProto = 0xFF; + break; + } + } + Log3(("%.*Rhxd\n", cb, bPacket)); +} + +#else /* !DEBUG */ +# define vboxNetLwfWinDumpFilterTypes(uFlags) do { } while (0) +# define vboxNetLwfWinDumpOffloadSettings(p) do { } while (0) +# define vboxNetLwfWinDumpSetOffloadSettings(p) do { } while (0) +# define vboxNetLwfWinDumpPackets(m,l) do { } while (0) +# define vboxNetLwfWinDumpPacket(p,t) do { } while (0) +#endif /* !DEBUG */ + +DECLINLINE(bool) vboxNetLwfWinChangeState(PVBOXNETLWF_MODULE pModuleCtx, uint32_t enmNew, uint32_t enmOld = LwfState_32BitHack) +{ + AssertReturn(pModuleCtx, false); + + bool fSuccess = true; + if (enmOld != LwfState_32BitHack) + { + fSuccess = ASMAtomicCmpXchgU32(&pModuleCtx->enmState, enmNew, enmOld); + if (fSuccess) + Log(("vboxNetLwfWinChangeState: state change %s -> %s\n", + vboxNetLwfWinStateToText(enmOld), + vboxNetLwfWinStateToText(enmNew))); + else + Log(("ERROR! vboxNetLwfWinChangeState: failed state change %s (actual=%s) -> %s\n", + vboxNetLwfWinStateToText(enmOld), + vboxNetLwfWinStateToText(ASMAtomicReadU32(&pModuleCtx->enmState)), + vboxNetLwfWinStateToText(enmNew))); + Assert(fSuccess); + } + else + { + uint32_t enmPrevState = ASMAtomicXchgU32(&pModuleCtx->enmState, enmNew); + Log(("vboxNetLwfWinChangeState: state change %s -> %s\n", + vboxNetLwfWinStateToText(enmPrevState), + vboxNetLwfWinStateToText(enmNew))); + NOREF(enmPrevState); + } + return fSuccess; +} + +DECLINLINE(void) vboxNetLwfWinInitOidRequest(PVBOXNETLWF_OIDREQ pRequest) +{ + NdisZeroMemory(pRequest, sizeof(VBOXNETLWF_OIDREQ)); + + NdisInitializeEvent(&pRequest->Event); + + pRequest->Request.Header.Type = NDIS_OBJECT_TYPE_OID_REQUEST; + pRequest->Request.Header.Revision = NDIS_OID_REQUEST_REVISION_1; + pRequest->Request.Header.Size = NDIS_SIZEOF_OID_REQUEST_REVISION_1; + + pRequest->Request.RequestId = (PVOID)VBOXNETLWF_REQ_ID; +} + +static NDIS_STATUS vboxNetLwfWinSyncOidRequest(PVBOXNETLWF_MODULE pModuleCtx, PVBOXNETLWF_OIDREQ pRequest) +{ + NDIS_STATUS Status = NdisFOidRequest(pModuleCtx->hFilter, &pRequest->Request); + if (Status == NDIS_STATUS_PENDING) + { + NdisWaitEvent(&pRequest->Event, 0); + Status = pRequest->Status; + } + return Status; +} + +DECLINLINE(void) vboxNetLwfWinCopyOidRequestResults(PNDIS_OID_REQUEST pFrom, PNDIS_OID_REQUEST pTo) +{ + switch (pFrom->RequestType) + { + case NdisRequestSetInformation: + pTo->DATA.SET_INFORMATION.BytesRead = pFrom->DATA.SET_INFORMATION.BytesRead; + pTo->DATA.SET_INFORMATION.BytesNeeded = pFrom->DATA.SET_INFORMATION.BytesNeeded; + break; + case NdisRequestMethod: + pTo->DATA.METHOD_INFORMATION.OutputBufferLength = pFrom->DATA.METHOD_INFORMATION.OutputBufferLength; + pTo->DATA.METHOD_INFORMATION.BytesWritten = pFrom->DATA.METHOD_INFORMATION.BytesWritten; + pTo->DATA.METHOD_INFORMATION.BytesRead = pFrom->DATA.METHOD_INFORMATION.BytesRead; + pTo->DATA.METHOD_INFORMATION.BytesNeeded = pFrom->DATA.METHOD_INFORMATION.BytesNeeded; + break; + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + default: + pTo->DATA.QUERY_INFORMATION.BytesWritten = pFrom->DATA.QUERY_INFORMATION.BytesWritten; + pTo->DATA.QUERY_INFORMATION.BytesNeeded = pFrom->DATA.QUERY_INFORMATION.BytesNeeded; + } +} + +void inline vboxNetLwfWinOverridePacketFiltersUp(PVBOXNETLWF_MODULE pModuleCtx, ULONG *pFilters) +{ + if (ASMAtomicReadBool(&pModuleCtx->fActive) && !ASMAtomicReadBool(&pModuleCtx->fHostPromisc)) + *pFilters &= ~NDIS_PACKET_TYPE_PROMISCUOUS; +} + +NDIS_STATUS vboxNetLwfWinOidRequest(IN NDIS_HANDLE hModuleCtx, + IN PNDIS_OID_REQUEST pOidRequest) +{ + LogFlow(("==>vboxNetLwfWinOidRequest: module=%p\n", hModuleCtx)); + vboxNetCmnWinDumpOidRequest(__FUNCTION__, pOidRequest); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + PNDIS_OID_REQUEST pClone = NULL; + NDIS_STATUS Status = NdisAllocateCloneOidRequest(pModuleCtx->hFilter, + pOidRequest, + VBOXNETLWF_MEM_TAG, + &pClone); + if (Status == NDIS_STATUS_SUCCESS) + { + /* Save the pointer to the original */ + *((PNDIS_OID_REQUEST*)(pClone->SourceReserved)) = pOidRequest; + + pClone->RequestId = pOidRequest->RequestId; + /* We are not supposed to get another request until we are through with the one we "postponed" */ + PNDIS_OID_REQUEST pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, pClone, PNDIS_OID_REQUEST); + Assert(pPrev == NULL); + pModuleCtx->pPendingRequest = pClone; + if (pOidRequest->RequestType == NdisRequestSetInformation + && pOidRequest->DATA.SET_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + { + ASMAtomicWriteBool(&pModuleCtx->fHostPromisc, !!(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer & NDIS_PACKET_TYPE_PROMISCUOUS)); + Log(("vboxNetLwfWinOidRequest: host wanted to set packet filter value to:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + /* Keep adapter in promisc mode as long as we are active. */ + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + *(ULONG*)pClone->DATA.SET_INFORMATION.InformationBuffer |= NDIS_PACKET_TYPE_PROMISCUOUS; + Log5(("vboxNetLwfWinOidRequest: pass the following packet filters to miniport:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + } + if (pOidRequest->RequestType == NdisRequestSetInformation + && pOidRequest->DATA.SET_INFORMATION.Oid == OID_TCP_OFFLOAD_CURRENT_CONFIG) + { + Log5(("vboxNetLwfWinOidRequest: offloading set to:\n")); + vboxNetLwfWinDumpSetOffloadSettings((PNDIS_OFFLOAD)pOidRequest->DATA.SET_INFORMATION.InformationBuffer); + } + + /* Forward the clone to underlying filters/miniport */ + Status = NdisFOidRequest(pModuleCtx->hFilter, pClone); + if (Status != NDIS_STATUS_PENDING) + { + /* Synchronous completion */ + pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, NULL, PNDIS_OID_REQUEST); + Assert(pPrev == pClone); + Log5(("vboxNetLwfWinOidRequest: got the following packet filters from miniport:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + /* + * The host does not expect the adapter to be in promisc mode, + * unless it enabled the mode. Let's not disillusion it. + */ + if ( pOidRequest->RequestType == NdisRequestQueryInformation + && pOidRequest->DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + Log5(("vboxNetLwfWinOidRequest: reporting to the host the following packet filters:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pOidRequest->DATA.QUERY_INFORMATION.InformationBuffer); + vboxNetLwfWinCopyOidRequestResults(pClone, pOidRequest); + NdisFreeCloneOidRequest(pModuleCtx->hFilter, pClone); + } + /* In case of async completion we do the rest in vboxNetLwfWinOidRequestComplete() */ + } + else + { + LogError(("vboxNetLwfWinOidRequest: NdisAllocateCloneOidRequest failed with 0x%x\n", Status)); + } + LogFlow(("<==vboxNetLwfWinOidRequest: Status=0x%x\n", Status)); + return Status; +} + +VOID vboxNetLwfWinOidRequestComplete(IN NDIS_HANDLE hModuleCtx, + IN PNDIS_OID_REQUEST pRequest, + IN NDIS_STATUS Status) +{ + LogFlow(("==>vboxNetLwfWinOidRequestComplete: module=%p req=%p status=0x%x\n", hModuleCtx, pRequest, Status)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + PNDIS_OID_REQUEST pOriginal = *((PNDIS_OID_REQUEST*)(pRequest->SourceReserved)); + if (pOriginal) + { + /* NDIS is supposed to serialize requests */ + PNDIS_OID_REQUEST pPrev = ASMAtomicXchgPtrT(&pModuleCtx->pPendingRequest, NULL, PNDIS_OID_REQUEST); + Assert(pPrev == pRequest); NOREF(pPrev); + + Log5(("vboxNetLwfWinOidRequestComplete: completed rq type=%d oid=%x\n", pRequest->RequestType, pRequest->DATA.QUERY_INFORMATION.Oid)); + vboxNetLwfWinCopyOidRequestResults(pRequest, pOriginal); + if ( pRequest->RequestType == NdisRequestQueryInformation + && pRequest->DATA.QUERY_INFORMATION.Oid == OID_GEN_CURRENT_PACKET_FILTER) + { + Log5(("vboxNetLwfWinOidRequestComplete: underlying miniport reports its packet filters:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + Log5(("vboxNetLwfWinOidRequestComplete: reporting the following packet filters to upper protocol:\n")); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pRequest->DATA.QUERY_INFORMATION.InformationBuffer); + } + NdisFreeCloneOidRequest(pModuleCtx->hFilter, pRequest); + NdisFOidRequestComplete(pModuleCtx->hFilter, pOriginal, Status); + } + else + { + /* This is not a clone, we originated it */ + Log(("vboxNetLwfWinOidRequestComplete: locally originated request (%p) completed, status=0x%x\n", pRequest, Status)); + PVBOXNETLWF_OIDREQ pRqWrapper = RT_FROM_MEMBER(pRequest, VBOXNETLWF_OIDREQ, Request); + pRqWrapper->Status = Status; + NdisSetEvent(&pRqWrapper->Event); + } + LogFlow(("<==vboxNetLwfWinOidRequestComplete\n")); +} + + +static bool vboxNetLwfWinIsPromiscuous(PVBOXNETLWF_MODULE pModuleCtx) +{ + return ASMAtomicReadBool(&pModuleCtx->fHostPromisc); +} + +#if 0 +static NDIS_STATUS vboxNetLwfWinGetPacketFilter(PVBOXNETLWF_MODULE pModuleCtx) +{ + LogFlow(("==>vboxNetLwfWinGetPacketFilter: module=%p\n", pModuleCtx)); + VBOXNETLWF_OIDREQ Rq; + vboxNetLwfWinInitOidRequest(&Rq); + Rq.Request.RequestType = NdisRequestQueryInformation; + Rq.Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.QUERY_INFORMATION.InformationBuffer = &pModuleCtx->uPacketFilter; + Rq.Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(pModuleCtx->uPacketFilter); + NDIS_STATUS Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinGetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed with 0x%x\n", Status)); + return FALSE; + } + if (Rq.Request.DATA.QUERY_INFORMATION.BytesWritten != sizeof(pModuleCtx->uPacketFilter)) + { + LogError(("vboxNetLwfWinGetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed to write neccessary amount (%d bytes), actually written %d bytes\n", sizeof(pModuleCtx->uPacketFilter), Rq.Request.DATA.QUERY_INFORMATION.BytesWritten)); + } + + Log5(("vboxNetLwfWinGetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER query returned the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(pModuleCtx->uPacketFilter); + + LogFlow(("<==vboxNetLwfWinGetPacketFilter: status=0x%x\n", Status)); + return Status; +} +#endif + +static NDIS_STATUS vboxNetLwfWinSetPacketFilter(PVBOXNETLWF_MODULE pModuleCtx, bool fPromisc) +{ + LogFlow(("==>vboxNetLwfWinSetPacketFilter: module=%p %s\n", pModuleCtx, fPromisc ? "promiscuous" : "normal")); + ULONG uFilter = 0; + VBOXNETLWF_OIDREQ Rq; + vboxNetLwfWinInitOidRequest(&Rq); + Rq.Request.RequestType = NdisRequestQueryInformation; + Rq.Request.DATA.QUERY_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.QUERY_INFORMATION.InformationBuffer = &uFilter; + Rq.Request.DATA.QUERY_INFORMATION.InformationBufferLength = sizeof(uFilter); + NDIS_STATUS Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed with 0x%x\n", Status)); + return Status; + } + if (Rq.Request.DATA.QUERY_INFORMATION.BytesWritten != sizeof(uFilter)) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(query, OID_GEN_CURRENT_PACKET_FILTER) failed to write neccessary amount (%d bytes), actually written %d bytes\n", sizeof(uFilter), Rq.Request.DATA.QUERY_INFORMATION.BytesWritten)); + return NDIS_STATUS_FAILURE; + } + + Log5(("vboxNetLwfWinSetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER query returned the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(uFilter); + + if (fPromisc) + { + /* If we about to go promiscuous, save the state before we change it. */ + ASMAtomicWriteBool(&pModuleCtx->fHostPromisc, !!(uFilter & NDIS_PACKET_TYPE_PROMISCUOUS)); + uFilter |= NDIS_PACKET_TYPE_PROMISCUOUS; + } + else + { + /* Reset promisc only if it was not enabled before we had changed it. */ + if (!ASMAtomicReadBool(&pModuleCtx->fHostPromisc)) + uFilter &= ~NDIS_PACKET_TYPE_PROMISCUOUS; + } + + Log5(("vboxNetLwfWinSetPacketFilter: OID_GEN_CURRENT_PACKET_FILTER about to set the following filters:\n")); + vboxNetLwfWinDumpFilterTypes(uFilter); + + NdisResetEvent(&Rq.Event); /* need to reset as it has been set by query op */ + Rq.Request.RequestType = NdisRequestSetInformation; + Rq.Request.DATA.SET_INFORMATION.Oid = OID_GEN_CURRENT_PACKET_FILTER; + Rq.Request.DATA.SET_INFORMATION.InformationBuffer = &uFilter; + Rq.Request.DATA.SET_INFORMATION.InformationBufferLength = sizeof(uFilter); + Status = vboxNetLwfWinSyncOidRequest(pModuleCtx, &Rq); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinSetPacketFilter: vboxNetLwfWinSyncOidRequest(set, OID_GEN_CURRENT_PACKET_FILTER, vvv below vvv) failed with 0x%x\n", Status)); + vboxNetLwfWinDumpFilterTypes(uFilter); + } + LogFlow(("<==vboxNetLwfWinSetPacketFilter: status=0x%x\n", Status)); + return Status; +} + + +static NTSTATUS vboxNetLwfWinDevDispatch(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pIrpSl = IoGetCurrentIrpStackLocation(pIrp);; + NTSTATUS Status = STATUS_SUCCESS; + + switch (pIrpSl->MajorFunction) + { + case IRP_MJ_DEVICE_CONTROL: + Status = STATUS_NOT_SUPPORTED; + break; + case IRP_MJ_CREATE: + case IRP_MJ_CLEANUP: + case IRP_MJ_CLOSE: + break; + default: + AssertFailed(); + break; + } + + pIrp->IoStatus.Status = Status; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + return Status; +} + +/** @todo So far we had no use for device, should we even bother to create it? */ +static NDIS_STATUS vboxNetLwfWinDevCreate(PVBOXNETLWFGLOBALS pGlobals) +{ + NDIS_STRING DevName, LinkName; + PDRIVER_DISPATCH aMajorFunctions[IRP_MJ_MAXIMUM_FUNCTION+1]; + NdisInitUnicodeString(&DevName, VBOXNETLWF_NAME_DEVICE); + NdisInitUnicodeString(&LinkName, VBOXNETLWF_NAME_LINK); + + Assert(!pGlobals->hDevice); + Assert(!pGlobals->pDevObj); + NdisZeroMemory(aMajorFunctions, sizeof (aMajorFunctions)); + aMajorFunctions[IRP_MJ_CREATE] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_CLEANUP] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_CLOSE] = vboxNetLwfWinDevDispatch; + aMajorFunctions[IRP_MJ_DEVICE_CONTROL] = vboxNetLwfWinDevDispatch; + + NDIS_DEVICE_OBJECT_ATTRIBUTES DeviceAttributes; + NdisZeroMemory(&DeviceAttributes, sizeof(DeviceAttributes)); + DeviceAttributes.Header.Type = NDIS_OBJECT_TYPE_DEVICE_OBJECT_ATTRIBUTES; + DeviceAttributes.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1; + DeviceAttributes.Header.Size = sizeof(DeviceAttributes); + DeviceAttributes.DeviceName = &DevName; + DeviceAttributes.SymbolicName = &LinkName; + DeviceAttributes.MajorFunctions = aMajorFunctions; + //DeviceAttributes.ExtensionSize = sizeof(FILTER_DEVICE_EXTENSION); + + NDIS_STATUS Status = NdisRegisterDeviceEx(pGlobals->hFilterDriver, + &DeviceAttributes, + &pGlobals->pDevObj, + &pGlobals->hDevice); + Log(("vboxNetLwfWinDevCreate: NdisRegisterDeviceEx returned 0x%x\n", Status)); + Assert(Status == NDIS_STATUS_SUCCESS); +#if 0 + if (Status == NDIS_STATUS_SUCCESS) + { + PFILTER_DEVICE_EXTENSION pExtension; + pExtension = NdisGetDeviceReservedExtension(pGlobals->pDevObj); + pExtension->Signature = VBOXNETLWF_MEM_TAG; + pExtension->Handle = pGlobals->hFilterDriver; + } +#endif + return Status; +} + +static void vboxNetLwfWinDevDestroy(PVBOXNETLWFGLOBALS pGlobals) +{ + Assert(pGlobals->hDevice); + Assert(pGlobals->pDevObj); + NdisDeregisterDeviceEx(pGlobals->hDevice); + pGlobals->hDevice = NULL; + pGlobals->pDevObj = NULL; +} + +static void vboxNetLwfWinDisableOffloading(PNDIS_OFFLOAD pOffloadConfig) +{ + pOffloadConfig->Checksum.IPv4Transmit.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.IpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.TcpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv4Transmit.IpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.TcpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->Checksum.IPv6Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv4.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.Encapsulation = NDIS_ENCAPSULATION_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + pOffloadConfig->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_NOT_SUPPORTED; +} + +static void vboxNetLwfWinUpdateSavedOffloadConfig(PVBOXNETLWF_MODULE pModuleCtx, PNDIS_OFFLOAD pOffload) +{ + if (pModuleCtx->cbOffloadConfig < pOffload->Header.Size) + { + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 10); + return; + } + + NdisMoveMemory(pModuleCtx->pSavedOffloadConfig, pOffload, pOffload->Header.Size); + NdisMoveMemory(pModuleCtx->pDisabledOffloadConfig, pOffload, pOffload->Header.Size); + vboxNetLwfWinDisableOffloading(pModuleCtx->pDisabledOffloadConfig); + pModuleCtx->fOffloadConfigValid = true; +} + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS +static void vboxNetLwfWinFreePools(PVBOXNETLWF_MODULE pModuleCtx, int cPools) +{ + for (int i = 0; i < cPools; ++i) + { + if (pModuleCtx->hPool[i]) + { + NdisFreeNetBufferListPool(pModuleCtx->hPool[i]); + Log4(("vboxNetLwfWinFreePools: freed NBL+NB pool 0x%p\n", pModuleCtx->hPool[i])); + } + } +} +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ + + +static void vboxNetLwfWinFreeModuleResources(PVBOXNETLWF_MODULE pModuleCtx) +{ +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS + vboxNetLwfWinFreePools(pModuleCtx, RT_ELEMENTS(g_cbPool)); +#else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + if (pModuleCtx->hPool) + { + NdisFreeNetBufferListPool(pModuleCtx->hPool); + Log4(("vboxNetLwfWinFreeModuleResources: freed NBL+NB pool 0x%p\n", pModuleCtx->hPool)); + } +#endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + if (pModuleCtx->pDisabledOffloadConfig) + NdisFreeMemory(pModuleCtx->pDisabledOffloadConfig, 0, 0); + if (pModuleCtx->pSavedOffloadConfig) + NdisFreeMemory(pModuleCtx->pSavedOffloadConfig, 0, 0); + if (pModuleCtx->hWorkItem) + NdisFreeIoWorkItem(pModuleCtx->hWorkItem); + NdisFreeMemory(pModuleCtx, 0, 0); +} + + +DECLARE_GLOBAL_CONST_UNICODE_STRING(g_strHostOnlyMiniportName, L"VirtualBox Host-Only"); + +static NDIS_STATUS vboxNetLwfWinAttach(IN NDIS_HANDLE hFilter, IN NDIS_HANDLE hDriverCtx, + IN PNDIS_FILTER_ATTACH_PARAMETERS pParameters) +{ + LogFlow(("==>vboxNetLwfWinAttach: filter=%p\n", hFilter)); + + PVBOXNETLWFGLOBALS pGlobals = (PVBOXNETLWFGLOBALS)hDriverCtx; + if (!pGlobals) + { + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_FAILURE, 1); + return NDIS_STATUS_FAILURE; + } + + /* + * We need a copy of NDIS_STRING structure as we are going to modify length + * of the base miniport instance name since RTL does not support comparing + * first n characters of two strings. We check if miniport names start with + * "Virtual Host-Only" to detect host-only adapters. It is a waste of resources + * to bind our filter to host-only adapters since they now operate independently. + */ + NDIS_STRING strTruncatedInstanceName = *pParameters->BaseMiniportInstanceName; /* Do not copy data, only the structure itself */ + strTruncatedInstanceName.Length = g_strHostOnlyMiniportName.Length; /* Truncate instance name */ + if (RtlEqualUnicodeString(&strTruncatedInstanceName, &g_strHostOnlyMiniportName, TRUE /* Case insensitive */)) + { + DbgPrint("vboxNetLwfWinAttach: won't attach to %wZ\n", pParameters->BaseMiniportInstanceName); + return NDIS_STATUS_FAILURE; + } + + ANSI_STRING strMiniportName; + /* We use the miniport name to associate this filter module with the netflt instance */ + NTSTATUS rc = RtlUnicodeStringToAnsiString(&strMiniportName, + pParameters->BaseMiniportName, + TRUE); + if (rc != STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinAttach: RtlUnicodeStringToAnsiString(%ls) failed with 0x%x\n", + pParameters->BaseMiniportName, rc)); + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_FAILURE, 2); + return NDIS_STATUS_FAILURE; + } + DbgPrint("vboxNetLwfWinAttach: friendly name=%wZ\n", pParameters->BaseMiniportInstanceName); + DbgPrint("vboxNetLwfWinAttach: name=%Z\n", &strMiniportName); + + UINT cbModuleWithNameExtra = sizeof(VBOXNETLWF_MODULE) + strMiniportName.Length; + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)NdisAllocateMemoryWithTagPriority(hFilter, + cbModuleWithNameExtra, + VBOXNETLWF_MEM_TAG, + LowPoolPriority); + if (!pModuleCtx) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate module context for %ls\n", pParameters->BaseMiniportName)); + RtlFreeAnsiString(&strMiniportName); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 3); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated module context 0x%p\n", pModuleCtx)); + + NdisZeroMemory(pModuleCtx, cbModuleWithNameExtra); + NdisMoveMemory(pModuleCtx->szMiniportName, strMiniportName.Buffer, strMiniportName.Length); + RtlFreeAnsiString(&strMiniportName); + + pModuleCtx->hWorkItem = NdisAllocateIoWorkItem(g_VBoxNetLwfGlobals.hFilterDriver); + if (!pModuleCtx->hWorkItem) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate work item for %ls\n", + pParameters->BaseMiniportName)); + NdisFreeMemory(pModuleCtx, 0, 0); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 4); + return NDIS_STATUS_RESOURCES; + } + + Assert(pParameters->MacAddressLength == sizeof(RTMAC)); + NdisMoveMemory(&pModuleCtx->MacAddr, pParameters->CurrentMacAddress, RT_MIN(sizeof(RTMAC), pParameters->MacAddressLength)); + + pModuleCtx->cbOffloadConfig = sizeof(NDIS_OFFLOAD) * 2; /* Best guess to accomodate future expansion. */ + /* Get the exact size, if possible. */ + if (pParameters->DefaultOffloadConfiguration) + pModuleCtx->cbOffloadConfig = pParameters->DefaultOffloadConfiguration->Header.Size; + else + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 8); + + pModuleCtx->pSavedOffloadConfig = + (PNDIS_OFFLOAD)NdisAllocateMemoryWithTagPriority(hFilter, pModuleCtx->cbOffloadConfig, + VBOXNETLWF_MEM_TAG, LowPoolPriority); + pModuleCtx->pDisabledOffloadConfig = + (PNDIS_OFFLOAD)NdisAllocateMemoryWithTagPriority(hFilter, pModuleCtx->cbOffloadConfig, + VBOXNETLWF_MEM_TAG, LowPoolPriority); + if (!pModuleCtx->pSavedOffloadConfig || !pModuleCtx->pDisabledOffloadConfig) + { + LogError(("vboxNetLwfWinAttach: Failed to allocate offload config buffers for %ls\n", + pParameters->BaseMiniportName)); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 9); + return NDIS_STATUS_RESOURCES; + } + + if (pParameters->DefaultOffloadConfiguration) + vboxNetLwfWinUpdateSavedOffloadConfig(pModuleCtx, pParameters->DefaultOffloadConfiguration); + else + { + NdisZeroMemory(pModuleCtx->pDisabledOffloadConfig, pModuleCtx->cbOffloadConfig); + pModuleCtx->pDisabledOffloadConfig->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + pModuleCtx->pDisabledOffloadConfig->Header.Revision = NDIS_OFFLOAD_REVISION_1; + pModuleCtx->pDisabledOffloadConfig->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + } + + pModuleCtx->pGlobals = pGlobals; + pModuleCtx->hFilter = hFilter; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Attaching); + /* Initialize transmission mutex and events */ + NDIS_INIT_MUTEX(&pModuleCtx->InTransmit); +#ifdef VBOXNETLWF_SYNC_SEND + KeInitializeEvent(&pModuleCtx->EventWire, SynchronizationEvent, FALSE); + KeInitializeEvent(&pModuleCtx->EventHost, SynchronizationEvent, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + NdisInitializeEvent(&pModuleCtx->EventSendComplete); + pModuleCtx->cPendingBuffers = 0; +#endif /* !VBOXNETLWF_SYNC_SEND */ + +#ifdef VBOXNETLWF_FIXED_SIZE_POOLS + for (int i = 0; i < RT_ELEMENTS(g_cbPool); ++i) + { + /* Allocate buffer pools */ + NET_BUFFER_LIST_POOL_PARAMETERS PoolParams; + NdisZeroMemory(&PoolParams, sizeof(PoolParams)); + PoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + PoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + PoolParams.Header.Size = sizeof(PoolParams); + PoolParams.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + PoolParams.fAllocateNetBuffer = TRUE; + PoolParams.ContextSize = 0; /** @todo Do we need to consider underlying drivers? I think not. */ + PoolParams.PoolTag = VBOXNETLWF_MEM_TAG; + PoolParams.DataSize = g_cbPool[i]; + pModuleCtx->hPool[i] = NdisAllocateNetBufferListPool(hFilter, &PoolParams); + if (!pModuleCtx->hPool[i]) + { + LogError(("vboxNetLwfWinAttach: NdisAllocateNetBufferListPool failed\n")); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 7); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated NBL+NB pool (data size=%u) 0x%p\n", + PoolParams.DataSize, pModuleCtx->hPool[i])); + } +#else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + /* Allocate buffer pools */ + NET_BUFFER_LIST_POOL_PARAMETERS PoolParams; + NdisZeroMemory(&PoolParams, sizeof(PoolParams)); + PoolParams.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + PoolParams.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + PoolParams.Header.Size = sizeof(PoolParams); + PoolParams.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT; + PoolParams.fAllocateNetBuffer = TRUE; + PoolParams.ContextSize = 0; /** @todo Do we need to consider underlying drivers? I think not. */ + PoolParams.PoolTag = VBOXNETLWF_MEM_TAG; + pModuleCtx->hPool = NdisAllocateNetBufferListPool(hFilter, &PoolParams); + if (!pModuleCtx->hPool) + { + LogError(("vboxNetLwfWinAttach: NdisAllocateNetBufferListPool failed\n")); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INSUFFICIENT_RESOURCES, NDIS_STATUS_RESOURCES, 7); + return NDIS_STATUS_RESOURCES; + } + Log4(("vboxNetLwfWinAttach: allocated NBL+NB pool 0x%p\n", pModuleCtx->hPool)); +#endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + + NDIS_FILTER_ATTRIBUTES Attributes; + NdisZeroMemory(&Attributes, sizeof(Attributes)); + Attributes.Header.Revision = NDIS_FILTER_ATTRIBUTES_REVISION_1; + Attributes.Header.Size = sizeof(Attributes); + Attributes.Header.Type = NDIS_OBJECT_TYPE_FILTER_ATTRIBUTES; + Attributes.Flags = 0; + NDIS_STATUS Status = NdisFSetAttributes(hFilter, pModuleCtx, &Attributes); + if (Status != NDIS_STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinAttach: NdisFSetAttributes failed with 0x%x\n", Status)); + vboxNetLwfWinFreeModuleResources(pModuleCtx); + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, NDIS_STATUS_RESOURCES, 5); + return NDIS_STATUS_RESOURCES; + } + /* Insert into module chain */ + NdisAcquireSpinLock(&pGlobals->Lock); + RTListPrepend(&pGlobals->listModules, &pModuleCtx->node); + NdisReleaseSpinLock(&pGlobals->Lock); + + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Paused); + + /// @todo Somehow the packet filter is 0 at this point: Status = vboxNetLwfWinGetPacketFilter(pModuleCtx); + /// @todo We actually update it later in status handler, perhaps we should not do anything here. + + LogFlow(("<==vboxNetLwfWinAttach: Status = 0x%x\n", Status)); + return Status; +} + +static VOID vboxNetLwfWinDetach(IN NDIS_HANDLE hModuleCtx) +{ + LogFlow(("==>vboxNetLwfWinDetach: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Detached, LwfState_Paused); + + /* Remove from module chain */ + NdisAcquireSpinLock(&pModuleCtx->pGlobals->Lock); + RTListNodeRemove(&pModuleCtx->node); + NdisReleaseSpinLock(&pModuleCtx->pGlobals->Lock); + + PVBOXNETFLTINS pNetFltIns = pModuleCtx->pNetFlt; /// @todo Atomic? + if (pNetFltIns && vboxNetFltTryRetainBusyNotDisconnected(pNetFltIns)) + { + /* + * Set hModuleCtx to null now in order to prevent filter restart, + * OID requests and other stuff associated with NetFlt deactivation. + */ + pNetFltIns->u.s.WinIf.hModuleCtx = NULL; + /* Notify NetFlt that we are going down */ + pNetFltIns->pSwitchPort->pfnDisconnect(pNetFltIns->pSwitchPort, &pNetFltIns->MyPort, vboxNetFltPortReleaseBusy); + /* We do not 'release' netflt instance since it has been done by pfnDisconnect */ + } + pModuleCtx->pNetFlt = NULL; + + /* + * We have to make sure that all NET_BUFFER_LIST structures have been freed by now, but + * it does not require us to do anything here since it has already been taken care of + * by vboxNetLwfWinPause(). + */ + vboxNetLwfWinFreeModuleResources(pModuleCtx); + Log4(("vboxNetLwfWinDetach: freed module context 0x%p\n", pModuleCtx)); + LogFlow(("<==vboxNetLwfWinDetach\n")); +} + + +static NDIS_STATUS vboxNetLwfWinPause(IN NDIS_HANDLE hModuleCtx, IN PNDIS_FILTER_PAUSE_PARAMETERS pParameters) +{ + RT_NOREF1(pParameters); + LogFlow(("==>vboxNetLwfWinPause: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Pausing, LwfState_Running); + /* Wait for pending send/indication operations to complete. */ + NDIS_WAIT_FOR_MUTEX(&pModuleCtx->InTransmit); +#ifndef VBOXNETLWF_SYNC_SEND + NdisWaitEvent(&pModuleCtx->EventSendComplete, 1000 /* ms */); +#endif /* !VBOXNETLWF_SYNC_SEND */ + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Paused, LwfState_Pausing); + NDIS_RELEASE_MUTEX(&pModuleCtx->InTransmit); + LogFlow(("<==vboxNetLwfWinPause\n")); + return NDIS_STATUS_SUCCESS; /* Failure is not an option */ +} + + +static void vboxNetLwfWinIndicateOffload(PVBOXNETLWF_MODULE pModuleCtx, PNDIS_OFFLOAD pOffload) +{ + Log5(("vboxNetLwfWinIndicateOffload: offload config changed to:\n")); + vboxNetLwfWinDumpOffloadSettings(pOffload); + NDIS_STATUS_INDICATION OffloadingIndication; + NdisZeroMemory(&OffloadingIndication, sizeof(OffloadingIndication)); + OffloadingIndication.Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION; + OffloadingIndication.Header.Revision = NDIS_STATUS_INDICATION_REVISION_1; + OffloadingIndication.Header.Size = NDIS_SIZEOF_STATUS_INDICATION_REVISION_1; + OffloadingIndication.SourceHandle = pModuleCtx->hFilter; + OffloadingIndication.StatusCode = NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG; + OffloadingIndication.StatusBuffer = pOffload; + OffloadingIndication.StatusBufferSize = pOffload->Header.Size; + NdisFIndicateStatus(pModuleCtx->hFilter, &OffloadingIndication); +} + + +static NDIS_STATUS vboxNetLwfWinRestart(IN NDIS_HANDLE hModuleCtx, IN PNDIS_FILTER_RESTART_PARAMETERS pParameters) +{ + RT_NOREF1(pParameters); + LogFlow(("==>vboxNetLwfWinRestart: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Restarting, LwfState_Paused); + + /* By default the packets that go between VMs and wire are invisible to the host. */ + pModuleCtx->fPassVmTrafficToHost = false; + + NDIS_HANDLE hConfig; + NDIS_CONFIGURATION_OBJECT cfgObj; + cfgObj.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT; + cfgObj.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1; + cfgObj.Header.Size = sizeof(NDIS_CONFIGURATION_OBJECT); + cfgObj.NdisHandle = g_VBoxNetLwfGlobals.hFilterDriver; + + NDIS_STATUS Status = NdisOpenConfigurationEx(&cfgObj, &hConfig); + if (Status == NDIS_STATUS_SUCCESS) + { + NDIS_STRING strCfgParam = NDIS_STRING_CONST("PassVmTrafficToHost"); + PNDIS_CONFIGURATION_PARAMETER pParam = NULL; + NdisReadConfiguration(&Status, &pParam, hConfig, &strCfgParam, NdisParameterInteger); + if (Status != NDIS_STATUS_SUCCESS) + { + Log(("vboxNetLwfWinRestart: Failed to read 'PassVmTrafficToHost' from the registry.\n")); + } + else if (pParam->ParameterData.IntegerData != 0) + { + Log(("vboxNetLwfWinRestart: Allowing the host to see VM traffic in promisc mode by user request.\n")); + pModuleCtx->fPassVmTrafficToHost = true; + } + NdisCloseConfiguration(hConfig); + } + vboxNetLwfWinChangeState(pModuleCtx, LwfState_Running, LwfState_Restarting); + LogFlow(("<==vboxNetLwfWinRestart: Status = 0x%x, returning NDIS_STATUS_SUCCESS nontheless.\n", Status)); + return NDIS_STATUS_SUCCESS; +} + + +static void vboxNetLwfWinDestroySG(PINTNETSG pSG) +{ + NdisFreeMemory(pSG, 0, 0); + Log4(("vboxNetLwfWinDestroySG: freed SG 0x%p\n", pSG)); +} + +/** + * Worker for vboxNetLwfWinNBtoSG() that gets the max segment count needed. + * @note vboxNetLwfWinNBtoSG may use fewer depending on cbPacket and offset! + * @note vboxNetAdpWinCalcSegments() is a copy of this code. + */ +DECLINLINE(ULONG) vboxNetLwfWinCalcSegments(PNET_BUFFER pNetBuf) +{ + ULONG cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); pMdl; pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + /* Skip empty MDLs (see @bugref{9233}) */ + if (MmGetMdlByteCount(pMdl)) + cSegs++; + } + return cSegs; +} + +DECLINLINE(void) vboxNetLwfWinFreeMdlChain(PMDL pMdl) +{ +#ifndef VBOXNETLWF_FIXED_SIZE_POOLS + PMDL pMdlNext; + while (pMdl) + { + pMdlNext = pMdl->Next; +# ifndef VBOXNETLWF_SYNC_SEND + PUCHAR pDataBuf; + ULONG cb = 0; + NdisQueryMdl(pMdl, &pDataBuf, &cb, NormalPagePriority); +# endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFreeMdl(pMdl); + Log4(("vboxNetLwfWinFreeMdlChain: freed MDL 0x%p\n", pMdl)); +# ifndef VBOXNETLWF_SYNC_SEND + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetLwfWinFreeMdlChain: freed data buffer 0x%p\n", pDataBuf)); +# endif /* !VBOXNETLWF_SYNC_SEND */ + pMdl = pMdlNext; + } +#else /* VBOXNETLWF_FIXED_SIZE_POOLS */ + RT_NOREF1(pMdl); +#endif /* VBOXNETLWF_FIXED_SIZE_POOLS */ +} + +/** @todo + * 1) Copy data from SG to MDL (if we decide to complete asynchronously). + * 2) Provide context/backfill space. Nobody does it, should we? + * 3) We always get a single segment from intnet. Simplify? + */ +static PNET_BUFFER_LIST vboxNetLwfWinSGtoNB(PVBOXNETLWF_MODULE pModule, PINTNETSG pSG) +{ + AssertReturn(pSG->cSegsUsed >= 1, NULL); + LogFlow(("==>vboxNetLwfWinSGtoNB: segments=%d hPool=%p cb=%u\n", pSG->cSegsUsed, + pModule->hPool, pSG->cbTotal)); + AssertReturn(pModule->hPool, NULL); + +#ifdef VBOXNETLWF_SYNC_SEND + PINTNETSEG pSeg = pSG->aSegs; + PMDL pMdl = NdisAllocateMdl(pModule->hFilter, pSeg->pv, pSeg->cb); + if (!pMdl) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL\n")); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + Log4(("vboxNetLwfWinSGtoNB: allocated Mdl 0x%p\n", pMdl)); + PMDL pMdlCurr = pMdl; + for (int i = 1; i < pSG->cSegsUsed; i++) + { + pSeg = &pSG->aSegs[i]; + pMdlCurr->Next = NdisAllocateMdl(pModule->hFilter, pSeg->pv, pSeg->cb); + if (!pMdlCurr->Next) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL\n")); + /* Tear down all MDL we chained so far */ + vboxNetLwfWinFreeMdlChain(pMdl); + return NULL; + } + pMdlCurr = pMdlCurr->Next; + Log4(("vboxNetLwfWinSGtoNB: allocated Mdl 0x%p\n", pMdlCurr)); + } + PNET_BUFFER_LIST pBufList = NdisAllocateNetBufferAndNetBufferList(pModule->hPool, + 0 /* ContextSize */, + 0 /* ContextBackFill */, + pMdl, + 0 /* DataOffset */, + pSG->cbTotal); + if (pBufList) + { + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + /** @todo Do we need to initialize anything else? */ + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an NBL+NB\n")); + vboxNetLwfWinFreeMdlChain(pMdl); + } +#else /* !VBOXNETLWF_SYNC_SEND */ + +# ifdef VBOXNETLWF_FIXED_SIZE_POOLS + int iPool = 0; + ULONG cbFrame = VBOXNETLWF_MAX_FRAME_SIZE(pSG->cbTotal); + /* Let's find the appropriate pool first */ + for (iPool = 0; iPool < RT_ELEMENTS(g_cbPool); ++iPool) + if (cbFrame <= g_cbPool[iPool]) + break; + if (iPool >= RT_ELEMENTS(g_cbPool)) + { + LogError(("vboxNetLwfWinSGtoNB: frame is too big (%u > %u), drop it.\n", cbFrame, g_cbPool[RT_ELEMENTS(g_cbPool)-1])); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + PNET_BUFFER_LIST pBufList = NdisAllocateNetBufferList(pModule->hPool[iPool], + 0 /** @todo ContextSize */, + 0 /** @todo ContextBackFill */); + if (!pBufList) + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate netbuffer (cb=%u) from pool %d\n", cbFrame, iPool)); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + const ULONG cbAlignmentMask = sizeof(USHORT) - 1; /* Microsoft LB/FO provider expects packets to be aligned at word boundary. */ + ULONG cbAlignedFrame = (pSG->cbTotal + cbAlignmentMask) & ~cbAlignmentMask; + Assert(cbAlignedFrame >= pSG->cbTotal); + Assert(cbFrame >= cbAlignedFrame); + NET_BUFFER *pBuffer = NET_BUFFER_LIST_FIRST_NB(pBufList); + NDIS_STATUS Status = NdisRetreatNetBufferDataStart(pBuffer, cbAlignedFrame, 0 /** @todo DataBackfill */, NULL); + if (cbAlignedFrame - pSG->cbTotal > 0) + { + /* Make sure padding zeros do not get to the wire. */ + if (NET_BUFFER_DATA_LENGTH(pBuffer) != cbAlignedFrame) + vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 11); + else + NET_BUFFER_DATA_LENGTH(pBuffer) = pSG->cbTotal; + } + if (Status == NDIS_STATUS_SUCCESS) + { + uint8_t *pDst = (uint8_t*)NdisGetDataBuffer(pBuffer, pSG->cbTotal, NULL, 1, 0); + if (pDst) + { + for (int i = 0; i < pSG->cSegsUsed; i++) + { + NdisMoveMemory(pDst, pSG->aSegs[i].pv, pSG->aSegs[i].cb); + pDst += pSG->aSegs[i].cb; + } + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to obtain the buffer pointer (size=%u)\n", pSG->cbTotal)); + NdisAdvanceNetBufferDataStart(pBuffer, cbAlignedFrame, false, NULL); /** @todo why bother? */ + NdisFreeNetBufferList(pBufList); + pBufList = NULL; + } + } + else + { + LogError(("vboxNetLwfWinSGtoNB: NdisRetreatNetBufferDataStart failed with 0x%x (size=%u)\n", Status, pSG->cbTotal)); + NdisFreeNetBufferList(pBufList); + pBufList = NULL; + } +# else /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + PNET_BUFFER_LIST pBufList = NULL; + ULONG cbMdl = VBOXNETLWF_MAX_FRAME_SIZE(pSG->cbTotal); + ULONG uDataOffset = cbMdl - pSG->cbTotal; + PUCHAR pDataBuf = (PUCHAR)NdisAllocateMemoryWithTagPriority(pModule->hFilter, cbMdl, + VBOXNETLWF_MEM_TAG, NormalPoolPriority); + if (pDataBuf) + { + Log4(("vboxNetLwfWinSGtoNB: allocated data buffer (cb=%u) 0x%p\n", cbMdl, pDataBuf)); + PMDL pMdl = NdisAllocateMdl(pModule->hFilter, pDataBuf, cbMdl); + if (!pMdl) + { + NdisFreeMemory(pDataBuf, 0, 0); + Log4(("vboxNetLwfWinSGtoNB: freed data buffer 0x%p\n", pDataBuf)); + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an MDL (cb=%u)\n", cbMdl)); + LogFlow(("<==vboxNetLwfWinSGtoNB: return NULL\n")); + return NULL; + } + PUCHAR pDst = pDataBuf + uDataOffset; + for (int i = 0; i < pSG->cSegsUsed; i++) + { + NdisMoveMemory(pDst, pSG->aSegs[i].pv, pSG->aSegs[i].cb); + pDst += pSG->aSegs[i].cb; + } + pBufList = NdisAllocateNetBufferAndNetBufferList(pModule->hPool, + 0 /* ContextSize */, + 0 /* ContextBackFill */, + pMdl, + uDataOffset, + pSG->cbTotal); + if (pBufList) + { + Log4(("vboxNetLwfWinSGtoNB: allocated NBL+NB 0x%p\n", pBufList)); + pBufList->SourceHandle = pModule->hFilter; + /** @todo Do we need to initialize anything else? */ + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate an NBL+NB\n")); + vboxNetLwfWinFreeMdlChain(pMdl); + } + } + else + { + LogError(("vboxNetLwfWinSGtoNB: failed to allocate data buffer (size=%u)\n", cbMdl)); + } +# endif /* !VBOXNETLWF_FIXED_SIZE_POOLS */ + +#endif /* !VBOXNETLWF_SYNC_SEND */ + LogFlow(("<==vboxNetLwfWinSGtoNB: return %p\n", pBufList)); + return pBufList; +} + +/** + * @note vboxNetAdpWinNBtoSG() is a copy of this code. + */ +static PINTNETSG vboxNetLwfWinNBtoSG(PVBOXNETLWF_MODULE pModule, PNET_BUFFER pNetBuf) +{ + ULONG cbPacket = NET_BUFFER_DATA_LENGTH(pNetBuf); + ULONG cSegs = vboxNetLwfWinCalcSegments(pNetBuf); + /* Allocate and initialize SG */ + PINTNETSG pSG = (PINTNETSG)NdisAllocateMemoryWithTagPriority(pModule->hFilter, + RT_UOFFSETOF_DYN(INTNETSG, aSegs[cSegs]), + VBOXNETLWF_MEM_TAG, + NormalPoolPriority); + AssertReturn(pSG, pSG); + Log4(("vboxNetLwfWinNBtoSG: allocated SG 0x%p\n", pSG)); + IntNetSgInitTempSegs(pSG, cbPacket /*cbTotal*/, cSegs, cSegs /*cSegsUsed*/); + + ULONG uOffset = NET_BUFFER_CURRENT_MDL_OFFSET(pNetBuf); + cSegs = 0; + for (PMDL pMdl = NET_BUFFER_CURRENT_MDL(pNetBuf); + pMdl != NULL && cbPacket > 0; + pMdl = NDIS_MDL_LINKAGE(pMdl)) + { + ULONG cbSrc = MmGetMdlByteCount(pMdl); + if (cbSrc == 0) + continue; /* Skip empty MDLs (see @bugref{9233}) */ + + PUCHAR pSrc = (PUCHAR)MmGetSystemAddressForMdlSafe(pMdl, LowPagePriority); + if (!pSrc) + { + vboxNetLwfWinDestroySG(pSG); + return NULL; + } + + /* Handle the offset in the current (which is the first for us) MDL */ + if (uOffset) + { + if (uOffset < cbSrc) + { + pSrc += uOffset; + cbSrc -= uOffset; + uOffset = 0; + } + else + { + /* This is an invalid MDL chain */ + vboxNetLwfWinDestroySG(pSG); + return NULL; + } + } + + /* Do not read the last MDL beyond packet's end */ + if (cbSrc > cbPacket) + cbSrc = cbPacket; + + Assert(cSegs < pSG->cSegsAlloc); + pSG->aSegs[cSegs].pv = pSrc; + pSG->aSegs[cSegs].cb = cbSrc; + pSG->aSegs[cSegs].Phys = NIL_RTHCPHYS; + cSegs++; + cbPacket -= cbSrc; + } + + Assert(cbPacket == 0); + Assert(cSegs <= pSG->cSegsUsed); + + /* Update actual segment count in case we used fewer than anticipated. */ + pSG->cSegsUsed = (uint16_t)cSegs; + + return pSG; +} + +VOID vboxNetLwfWinStatus(IN NDIS_HANDLE hModuleCtx, IN PNDIS_STATUS_INDICATION pIndication) +{ + LogFlow(("==>vboxNetLwfWinStatus: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)hModuleCtx; + Log(("vboxNetLwfWinStatus: Got status indication: %s\n", vboxNetLwfWinStatusToText(pIndication->StatusCode))); + switch (pIndication->StatusCode) + { + case NDIS_STATUS_PACKET_FILTER: + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pIndication->StatusBuffer); + vboxNetLwfWinOverridePacketFiltersUp(pModuleCtx, (ULONG*)pIndication->StatusBuffer); + Log(("vboxNetLwfWinStatus: Reporting status: %s\n", vboxNetLwfWinStatusToText(pIndication->StatusCode))); + vboxNetLwfWinDumpFilterTypes(*(ULONG*)pIndication->StatusBuffer); + break; + case NDIS_STATUS_TASK_OFFLOAD_CURRENT_CONFIG: + Log5(("vboxNetLwfWinStatus: offloading currently set to:\n")); + vboxNetLwfWinDumpOffloadSettings((PNDIS_OFFLOAD)pIndication->StatusBuffer); + vboxNetLwfWinUpdateSavedOffloadConfig(pModuleCtx, (PNDIS_OFFLOAD)pIndication->StatusBuffer); + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + vboxNetLwfWinDisableOffloading((PNDIS_OFFLOAD)pIndication->StatusBuffer); + Log5(("vboxNetLwfWinStatus: reporting offloading up as:\n")); + vboxNetLwfWinDumpOffloadSettings((PNDIS_OFFLOAD)pIndication->StatusBuffer); + break; + } + NdisFIndicateStatus(pModuleCtx->hFilter, pIndication); + LogFlow(("<==vboxNetLwfWinStatus\n")); +} + +static bool vboxNetLwfWinForwardToIntNet(PVBOXNETLWF_MODULE pModuleCtx, PNET_BUFFER_LIST pBufLists, uint32_t fSrc) +{ + /* We must not forward anything to the trunk unless it is ready to receive. */ + if (!ASMAtomicReadBool(&pModuleCtx->fActive)) + { + Log(("vboxNetLwfWinForwardToIntNet: trunk is inactive, won't forward\n")); + return false; + } + /* Some NPF protocols make NDIS to loop back packets at miniport level, we must ignore those. */ + if (NdisTestNblFlag(pBufLists, NDIS_NBL_FLAGS_IS_LOOPBACK_PACKET)) + { + if (pBufLists->SourceHandle == pModuleCtx->hFilter && !pModuleCtx->fPassVmTrafficToHost) + { + /* Drop the packets we've injected. */ + vboxNetLwfWinDumpPackets("vboxNetLwfWinForwardToIntNet: dropping loopback", pBufLists); + return true; + } + vboxNetLwfWinDumpPackets("vboxNetLwfWinForwardToIntNet: passing through loopback", pBufLists); + return false; + } + + AssertReturn(pModuleCtx->pNetFlt, false); + AssertReturn(pModuleCtx->pNetFlt->pSwitchPort, false); + AssertReturn(pModuleCtx->pNetFlt->pSwitchPort->pfnRecv, false); + LogFlow(("==>vboxNetLwfWinForwardToIntNet: module=%p\n", pModuleCtx)); + Assert(pBufLists); /* The chain must contain at least one list */ + Assert(NET_BUFFER_LIST_NEXT_NBL(pBufLists) == NULL); /* The caller is supposed to unlink the list from the chain */ + /* + * Even if NBL contains more than one buffer we are prepared to deal with it. + * When any of buffers should not be dropped we keep the whole list. It is + * better to leak some "unexpected" packets to the wire/host than to loose any. + */ + bool fDropIt = false; + bool fDontDrop = false; + int nLists = 0; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + int nBuffers = 0; + nLists++; + for (PNET_BUFFER pBuf = NET_BUFFER_LIST_FIRST_NB(pList); pBuf; pBuf = NET_BUFFER_NEXT_NB(pBuf)) + { + nBuffers++; + PINTNETSG pSG = vboxNetLwfWinNBtoSG(pModuleCtx, pBuf); + if (pSG) + { + vboxNetLwfWinDumpPacket(pSG, (fSrc & INTNETTRUNKDIR_WIRE)?"intnet <-- wire":"intnet <-- host"); + /* A bit paranoid, but we do not use any locks, so... */ + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + if (pModuleCtx->pNetFlt->pSwitchPort->pfnRecv(pModuleCtx->pNetFlt->pSwitchPort, NULL, pSG, fSrc)) + fDropIt = true; + else + fDontDrop = true; + vboxNetLwfWinDestroySG(pSG); + } + } + Log(("vboxNetLwfWinForwardToIntNet: list=%d buffers=%d\n", nLists, nBuffers)); + } + Log(("vboxNetLwfWinForwardToIntNet: lists=%d drop=%s don't=%s\n", nLists, fDropIt ? "true":"false", fDontDrop ? "true":"false")); + + /* If the host (and the user) wants to see all packets we must not drop any. */ + if (pModuleCtx->fPassVmTrafficToHost && vboxNetLwfWinIsPromiscuous(pModuleCtx)) + fDropIt = false; + + LogFlow(("<==vboxNetLwfWinForwardToIntNet: return '%s'\n", + fDropIt ? (fDontDrop ? "do not drop (some)" : "drop it") : "do not drop (any)")); + return fDropIt && !fDontDrop; /* Drop the list if ALL its buffers are being dropped! */ +} + +DECLINLINE(bool) vboxNetLwfWinIsRunning(PVBOXNETLWF_MODULE pModule) +{ + Log(("vboxNetLwfWinIsRunning: state=%d\n", ASMAtomicReadU32(&pModule->enmState))); + return ASMAtomicReadU32(&pModule->enmState) == LwfState_Running; +} + +VOID vboxNetLwfWinSendNetBufferLists(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN NDIS_PORT_NUMBER nPort, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinSendNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: got", pBufLists); + + if (!ASMAtomicReadBool(&pModule->fActive)) + { + /* + * The trunk is inactive, jusp pass along all packets to the next + * underlying driver. + */ + NdisFSendNetBufferLists(pModule->hFilter, pBufLists, nPort, fFlags); + return; + } + + if (vboxNetLwfWinIsRunning(pModule)) + { + PNET_BUFFER_LIST pNext = NULL; + PNET_BUFFER_LIST pDropHead = NULL; + PNET_BUFFER_LIST pDropTail = NULL; + PNET_BUFFER_LIST pPassHead = NULL; + PNET_BUFFER_LIST pPassTail = NULL; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = pNext) + { + pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink */ + if (vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_HOST)) + { + NET_BUFFER_LIST_STATUS(pList) = NDIS_STATUS_SUCCESS; + if (pDropHead) + { + NET_BUFFER_LIST_NEXT_NBL(pDropTail) = pList; + pDropTail = pList; + } + else + pDropHead = pDropTail = pList; + } + else + { + if (pPassHead) + { + NET_BUFFER_LIST_NEXT_NBL(pPassTail) = pList; + pPassTail = pList; + } + else + pPassHead = pPassTail = pList; + } + } + Assert((pBufLists == pPassHead) || (pBufLists == pDropHead)); + if (pPassHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: passing down", pPassHead); + NdisFSendNetBufferLists(pModule->hFilter, pBufLists, nPort, fFlags); + } + if (pDropHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: consumed", pDropHead); + NdisFSendNetBufferListsComplete(pModule->hFilter, pDropHead, + fFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL ? NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0); + } + } + else + { + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + NET_BUFFER_LIST_STATUS(pList) = NDIS_STATUS_PAUSED; + } + vboxNetLwfWinDumpPackets("vboxNetLwfWinSendNetBufferLists: consumed", pBufLists); + NdisFSendNetBufferListsComplete(pModule->hFilter, pBufLists, + fFlags & NDIS_SEND_FLAGS_DISPATCH_LEVEL ? NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL : 0); + + } + LogFlow(("<==vboxNetLwfWinSendNetBufferLists\n")); +} + +VOID vboxNetLwfWinSendNetBufferListsComplete(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinSendNetBufferListsComplete: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + PNET_BUFFER_LIST pList = pBufLists; + PNET_BUFFER_LIST pNextList; + PNET_BUFFER_LIST pPrevList = NULL; + while (pList) + { + pNextList = NET_BUFFER_LIST_NEXT_NBL(pList); + if (pList->SourceHandle == pModule->hFilter) + { + /* We allocated this NET_BUFFER_LIST, let's free it up */ + Assert(NET_BUFFER_LIST_FIRST_NB(pList)); + Assert(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* + * All our NBLs hold a single NB each, no need to iterate over a list. + * There is no need to free an associated NB explicitly either, as it was + * preallocated with NBL structure. + */ + Assert(!NET_BUFFER_NEXT_NB(NET_BUFFER_LIST_FIRST_NB(pList))); + vboxNetLwfWinFreeMdlChain(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* Unlink this list from the chain */ + if (pPrevList) + NET_BUFFER_LIST_NEXT_NBL(pPrevList) = pNextList; + else + pBufLists = pNextList; + Log(("vboxNetLwfWinSendNetBufferListsComplete: our list %p, next=%p, previous=%p, head=%p\n", pList, pNextList, pPrevList, pBufLists)); + NdisFreeNetBufferList(pList); +#ifdef VBOXNETLWF_SYNC_SEND + Log4(("vboxNetLwfWinSendNetBufferListsComplete: freed NBL+NB 0x%p\n", pList)); + KeSetEvent(&pModule->EventWire, 0, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + Log4(("vboxNetLwfWinSendNetBufferListsComplete: freed NBL+NB+MDL+Data 0x%p\n", pList)); + Assert(ASMAtomicReadS32(&pModule->cPendingBuffers) > 0); + if (ASMAtomicDecS32(&pModule->cPendingBuffers) == 0) + NdisSetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + } + else + { + pPrevList = pList; + Log(("vboxNetLwfWinSendNetBufferListsComplete: passing list %p, next=%p, previous=%p, head=%p\n", pList, pNextList, pPrevList, pBufLists)); + } + pList = pNextList; + } + if (pBufLists) + { + /* There are still lists remaining in the chain, pass'em up */ + NdisFSendNetBufferListsComplete(pModule->hFilter, pBufLists, fFlags); + } + LogFlow(("<==vboxNetLwfWinSendNetBufferListsComplete\n")); +} + +VOID vboxNetLwfWinReceiveNetBufferLists(IN NDIS_HANDLE hModuleCtx, + IN PNET_BUFFER_LIST pBufLists, + IN NDIS_PORT_NUMBER nPort, + IN ULONG nBufLists, + IN ULONG fFlags) +{ + /// @todo Do we need loopback handling? + LogFlow(("==>vboxNetLwfWinReceiveNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: got", pBufLists); + + if (!ASMAtomicReadBool(&pModule->fActive)) + { + /* + * The trunk is inactive, just pass along all packets to the next + * overlying driver. + */ + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pBufLists, nPort, nBufLists, fFlags); + LogFlow(("<==vboxNetLwfWinReceiveNetBufferLists: inactive trunk\n")); + return; + } + + if (vboxNetLwfWinIsRunning(pModule)) + { + if (NDIS_TEST_RECEIVE_CANNOT_PEND(fFlags)) + { + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = NET_BUFFER_LIST_NEXT_NBL(pList)) + { + PNET_BUFFER_LIST pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink temporarily */ + if (!vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_WIRE)) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: passing up", pList); + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pList, nPort, nBufLists, fFlags); + } + NET_BUFFER_LIST_NEXT_NBL(pList) = pNext; /* Restore the link */ + } + } + else + { + /* We collect dropped NBLs in a separate list in order to "return" them. */ + PNET_BUFFER_LIST pNext = NULL; + PNET_BUFFER_LIST pDropHead = NULL; + PNET_BUFFER_LIST pDropTail = NULL; + PNET_BUFFER_LIST pPassHead = NULL; + PNET_BUFFER_LIST pPassTail = NULL; + ULONG nDrop = 0, nPass = 0; + for (PNET_BUFFER_LIST pList = pBufLists; pList; pList = pNext) + { + pNext = NET_BUFFER_LIST_NEXT_NBL(pList); + NET_BUFFER_LIST_NEXT_NBL(pList) = NULL; /* Unlink */ + if (vboxNetLwfWinForwardToIntNet(pModule, pList, INTNETTRUNKDIR_WIRE)) + { + if (nDrop++) + { + NET_BUFFER_LIST_NEXT_NBL(pDropTail) = pList; + pDropTail = pList; + } + else + pDropHead = pDropTail = pList; + } + else + { + if (nPass++) + { + NET_BUFFER_LIST_NEXT_NBL(pPassTail) = pList; + pPassTail = pList; + } + else + pPassHead = pPassTail = pList; + } + } + Assert((pBufLists == pPassHead) || (pBufLists == pDropHead)); + Assert(nDrop + nPass == nBufLists); + if (pPassHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: passing up", pPassHead); + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pPassHead, nPort, nPass, fFlags); + } + if (pDropHead) + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: consumed", pDropHead); + NdisFReturnNetBufferLists(pModule->hFilter, pDropHead, + fFlags & NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ? NDIS_RETURN_FLAGS_DISPATCH_LEVEL : 0); + } + } + + } + else + { + vboxNetLwfWinDumpPackets("vboxNetLwfWinReceiveNetBufferLists: consumed", pBufLists); + if ((fFlags & NDIS_RECEIVE_FLAGS_RESOURCES) == 0) + NdisFReturnNetBufferLists(pModule->hFilter, pBufLists, + fFlags & NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ? NDIS_RETURN_FLAGS_DISPATCH_LEVEL : 0); + } + LogFlow(("<==vboxNetLwfWinReceiveNetBufferLists\n")); +} + +VOID vboxNetLwfWinReturnNetBufferLists(IN NDIS_HANDLE hModuleCtx, IN PNET_BUFFER_LIST pBufLists, IN ULONG fFlags) +{ + LogFlow(("==>vboxNetLwfWinReturnNetBufferLists: module=%p\n", hModuleCtx)); + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)hModuleCtx; + PNET_BUFFER_LIST pList = pBufLists; + PNET_BUFFER_LIST pNextList; + PNET_BUFFER_LIST pPrevList = NULL; + /** @todo Move common part into a separate function to be used by vboxNetLwfWinSendNetBufferListsComplete() as well */ + while (pList) + { + pNextList = NET_BUFFER_LIST_NEXT_NBL(pList); + if (pList->SourceHandle == pModule->hFilter) + { + /* We allocated this NET_BUFFER_LIST, let's free it up */ + Assert(NET_BUFFER_LIST_FIRST_NB(pList)); + Assert(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* + * All our NBLs hold a single NB each, no need to iterate over a list. + * There is no need to free an associated NB explicitly either, as it was + * preallocated with NBL structure. + */ + vboxNetLwfWinFreeMdlChain(NET_BUFFER_FIRST_MDL(NET_BUFFER_LIST_FIRST_NB(pList))); + /* Unlink this list from the chain */ + if (pPrevList) + NET_BUFFER_LIST_NEXT_NBL(pPrevList) = pNextList; + else + pBufLists = pNextList; + NdisFreeNetBufferList(pList); +#ifdef VBOXNETLWF_SYNC_SEND + Log4(("vboxNetLwfWinReturnNetBufferLists: freed NBL+NB 0x%p\n", pList)); + KeSetEvent(&pModule->EventHost, 0, FALSE); +#else /* !VBOXNETLWF_SYNC_SEND */ + Log4(("vboxNetLwfWinReturnNetBufferLists: freed NBL+NB+MDL+Data 0x%p\n", pList)); + Assert(ASMAtomicReadS32(&pModule->cPendingBuffers) > 0); + if (ASMAtomicDecS32(&pModule->cPendingBuffers) == 0) + NdisSetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + } + else + pPrevList = pList; + pList = pNextList; + } + if (pBufLists) + { + /* There are still lists remaining in the chain, pass'em up */ + NdisFReturnNetBufferLists(pModule->hFilter, pBufLists, fFlags); + } + LogFlow(("<==vboxNetLwfWinReturnNetBufferLists\n")); +} + +/** + * register the filter driver + */ +DECLHIDDEN(NDIS_STATUS) vboxNetLwfWinRegister(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPathStr) +{ + RT_NOREF1(pRegistryPathStr); + NDIS_FILTER_DRIVER_CHARACTERISTICS FChars; + NDIS_STRING FriendlyName; + NDIS_STRING UniqueName; + NDIS_STRING ServiceName; + + NdisInitUnicodeString(&FriendlyName, VBOXNETLWF_NAME_FRIENDLY); + NdisInitUnicodeString(&UniqueName, VBOXNETLWF_NAME_UNIQUE); + NdisInitUnicodeString(&ServiceName, VBOXNETLWF_NAME_SERVICE); + + NdisZeroMemory(&FChars, sizeof (FChars)); + + FChars.Header.Type = NDIS_OBJECT_TYPE_FILTER_DRIVER_CHARACTERISTICS; + FChars.Header.Size = sizeof(NDIS_FILTER_DRIVER_CHARACTERISTICS); + FChars.Header.Revision = NDIS_FILTER_CHARACTERISTICS_REVISION_1; + + FChars.MajorNdisVersion = VBOXNETLWF_VERSION_NDIS_MAJOR; + FChars.MinorNdisVersion = VBOXNETLWF_VERSION_NDIS_MINOR; + + FChars.FriendlyName = FriendlyName; + FChars.UniqueName = UniqueName; + FChars.ServiceName = ServiceName; + + /* Mandatory functions */ + FChars.AttachHandler = vboxNetLwfWinAttach; + FChars.DetachHandler = vboxNetLwfWinDetach; + FChars.RestartHandler = vboxNetLwfWinRestart; + FChars.PauseHandler = vboxNetLwfWinPause; + + /* Optional functions, non changeble at run-time */ + FChars.OidRequestHandler = vboxNetLwfWinOidRequest; + FChars.OidRequestCompleteHandler = vboxNetLwfWinOidRequestComplete; + //FChars.CancelOidRequestHandler = vboxNetLwfWinCancelOidRequest; + FChars.StatusHandler = vboxNetLwfWinStatus; + //FChars.NetPnPEventHandler = vboxNetLwfWinPnPEvent; + + /* Datapath functions */ + FChars.SendNetBufferListsHandler = vboxNetLwfWinSendNetBufferLists; + FChars.SendNetBufferListsCompleteHandler = vboxNetLwfWinSendNetBufferListsComplete; + FChars.ReceiveNetBufferListsHandler = vboxNetLwfWinReceiveNetBufferLists; + FChars.ReturnNetBufferListsHandler = vboxNetLwfWinReturnNetBufferLists; + + pDriverObject->DriverUnload = vboxNetLwfWinUnloadDriver; + + NDIS_STATUS Status; + g_VBoxNetLwfGlobals.hFilterDriver = NULL; + Log(("vboxNetLwfWinRegister: registering filter driver...\n")); + Status = NdisFRegisterFilterDriver(pDriverObject, + (NDIS_HANDLE)&g_VBoxNetLwfGlobals, + &FChars, + &g_VBoxNetLwfGlobals.hFilterDriver); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Log(("vboxNetLwfWinRegister: successfully registered filter driver; registering device...\n")); + Status = vboxNetLwfWinDevCreate(&g_VBoxNetLwfGlobals); + Assert(Status == STATUS_SUCCESS); + Log(("vboxNetLwfWinRegister: vboxNetLwfWinDevCreate() returned 0x%x\n", Status)); + } + else + { + LogError(("vboxNetLwfWinRegister: failed to register filter driver, status=0x%x", Status)); + } + return Status; +} + +static int vboxNetLwfWinStartInitIdcThread() +{ + int rc = VERR_INVALID_STATE; + + if (ASMAtomicCmpXchgU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Connecting, LwfIdcState_Disconnected)) + { + Log(("vboxNetLwfWinStartInitIdcThread: IDC state change Diconnected -> Connecting\n")); + + NTSTATUS Status = PsCreateSystemThread(&g_VBoxNetLwfGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, + NULL, + NULL, + NULL, + vboxNetLwfWinInitIdcWorker, + &g_VBoxNetLwfGlobals); + Log(("vboxNetLwfWinStartInitIdcThread: create IDC initialization thread, status=0x%x\n", Status)); + if (Status != STATUS_SUCCESS) + { + LogError(("vboxNetLwfWinStartInitIdcThread: IDC initialization failed (system thread creation, status=0x%x)\n", Status)); + /* + * We failed to init IDC and there will be no second chance. + */ + Log(("vboxNetLwfWinStartInitIdcThread: IDC state change Connecting -> Diconnected\n")); + ASMAtomicWriteU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Disconnected); + } + rc = RTErrConvertFromNtStatus(Status); + } + return rc; +} + +static void vboxNetLwfWinStopInitIdcThread() +{ +} + + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) +{ + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + int rc; + + /* the idc registration is initiated via IOCTL since our driver + * can be loaded when the VBoxDrv is not in case we are a Ndis IM driver */ + rc = vboxNetLwfWinInitBase(); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + NdisZeroMemory(&g_VBoxNetLwfGlobals, sizeof (g_VBoxNetLwfGlobals)); + RTListInit(&g_VBoxNetLwfGlobals.listModules); + NdisAllocateSpinLock(&g_VBoxNetLwfGlobals.Lock); + /* + * We choose to ignore IDC initialization errors here because if we fail to load + * our filter the upper protocols won't bind to the associated adapter, causing + * network failure at the host. Better to have non-working filter than broken + * networking on the host. + */ + rc = vboxNetLwfWinStartInitIdcThread(); + AssertRC(rc); + + Status = vboxNetLwfWinRegister(pDriverObject, pRegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == NDIS_STATUS_SUCCESS) + { + Log(("NETLWF: started successfully\n")); + return STATUS_SUCCESS; + } + NdisFreeSpinLock(&g_VBoxNetLwfGlobals.Lock); + vboxNetLwfWinFini(); + } + else + { + Status = NDIS_STATUS_FAILURE; + } + + return Status; +} + + +static VOID vboxNetLwfWinUnloadDriver(IN PDRIVER_OBJECT pDriver) +{ + RT_NOREF1(pDriver); + LogFlow(("==>vboxNetLwfWinUnloadDriver: driver=%p\n", pDriver)); + vboxNetLwfWinDevDestroy(&g_VBoxNetLwfGlobals); + NdisFDeregisterFilterDriver(g_VBoxNetLwfGlobals.hFilterDriver); + NdisFreeSpinLock(&g_VBoxNetLwfGlobals.Lock); + LogFlow(("<==vboxNetLwfWinUnloadDriver\n")); + vboxNetLwfWinFini(); +} + +static const char *vboxNetLwfWinIdcStateToText(uint32_t enmState) +{ + switch (enmState) + { + case LwfIdcState_Disconnected: return "Disconnected"; + case LwfIdcState_Connecting: return "Connecting"; + case LwfIdcState_Connected: return "Connected"; + case LwfIdcState_Stopping: return "Stopping"; + } + return "Unknown"; +} + +static VOID vboxNetLwfWinInitIdcWorker(PVOID pvContext) +{ + int rc; + PVBOXNETLWFGLOBALS pGlobals = (PVBOXNETLWFGLOBALS)pvContext; + + while (ASMAtomicReadU32(&pGlobals->enmIdcState) == LwfIdcState_Connecting) + { + rc = vboxNetFltInitIdc(&g_VBoxNetFltGlobals); + if (RT_SUCCESS(rc)) + { + if (!ASMAtomicCmpXchgU32(&pGlobals->enmIdcState, LwfIdcState_Connected, LwfIdcState_Connecting)) + { + /* The state has been changed (the only valid transition is to "Stopping"), undo init */ + rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals); + Log(("vboxNetLwfWinInitIdcWorker: state change (Connecting -> %s) while initializing IDC, deleted IDC, rc=0x%x\n", + vboxNetLwfWinIdcStateToText(ASMAtomicReadU32(&pGlobals->enmIdcState)), rc)); + } + else + { + Log(("vboxNetLwfWinInitIdcWorker: IDC state change Connecting -> Connected\n")); + } + } + else + { + LARGE_INTEGER WaitIn100nsUnits; + WaitIn100nsUnits.QuadPart = -(LONGLONG)10000000; /* 1 sec */ + KeDelayExecutionThread(KernelMode, FALSE /* non-alertable */, &WaitIn100nsUnits); + } + } + PsTerminateSystemThread(STATUS_SUCCESS); +} + +static int vboxNetLwfWinTryFiniIdc() +{ + int rc = VINF_SUCCESS; + NTSTATUS Status; + PKTHREAD pThread = NULL; + uint32_t enmPrevState = ASMAtomicXchgU32(&g_VBoxNetLwfGlobals.enmIdcState, LwfIdcState_Stopping); + + Log(("vboxNetLwfWinTryFiniIdc: IDC state change %s -> Stopping\n", vboxNetLwfWinIdcStateToText(enmPrevState))); + + switch (enmPrevState) + { + case LwfIdcState_Disconnected: + /* Have not even attempted to connect -- nothing to do. */ + break; + case LwfIdcState_Stopping: + /* Impossible, but another thread is alreading doing FiniIdc, bail out */ + LogError(("vboxNetLwfWinTryFiniIdc: called in 'Stopping' state\n")); + rc = VERR_INVALID_STATE; + break; + case LwfIdcState_Connecting: + /* the worker thread is running, let's wait for it to stop */ + Status = ObReferenceObjectByHandle(g_VBoxNetLwfGlobals.hInitIdcThread, + THREAD_ALL_ACCESS, NULL, KernelMode, + (PVOID*)&pThread, NULL); + if (Status == STATUS_SUCCESS) + { + KeWaitForSingleObject(pThread, Executive, KernelMode, FALSE, NULL); + ObDereferenceObject(pThread); + } + else + { + LogError(("vboxNetLwfWinTryFiniIdc: ObReferenceObjectByHandle(%p) failed with 0x%x\n", + g_VBoxNetLwfGlobals.hInitIdcThread, Status)); + } + rc = RTErrConvertFromNtStatus(Status); + break; + case LwfIdcState_Connected: + /* the worker succeeded in IDC init and terminated */ + rc = vboxNetFltTryDeleteIdc(&g_VBoxNetFltGlobals); + Log(("vboxNetLwfWinTryFiniIdc: deleted IDC, rc=0x%x\n", rc)); + break; + } + return rc; +} + +static void vboxNetLwfWinFiniBase() +{ + vboxNetFltDeleteGlobals(&g_VBoxNetFltGlobals); + + /* + * Undo the work done during start (in reverse order). + */ + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + RTLogDestroy(RTLogSetDefaultInstance(NULL)); + + RTR0Term(); +} + +static int vboxNetLwfWinInitBase() +{ + int rc = RTR0Init(0); + if (!RT_SUCCESS(rc)) + return rc; + + memset(&g_VBoxNetFltGlobals, 0, sizeof(g_VBoxNetFltGlobals)); + rc = vboxNetFltInitGlobals(&g_VBoxNetFltGlobals); + if (!RT_SUCCESS(rc)) + RTR0Term(); + + return rc; +} + +static int vboxNetLwfWinFini() +{ + int rc = vboxNetLwfWinTryFiniIdc(); + if (RT_SUCCESS(rc)) + { + vboxNetLwfWinFiniBase(); + } + return rc; +} + + +/* + * + * The OS specific interface definition + * + */ + + +bool vboxNetFltOsMaybeRediscovered(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltOsMaybeRediscovered: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsMaybeRediscovered: return %RTbool\n", !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost))); + /* AttachToInterface true if disconnected */ + return !ASMAtomicUoReadBool(&pThis->fDisconnectedFromHost); +} + +int vboxNetFltPortOsXmit(PVBOXNETFLTINS pThis, void *pvIfData, PINTNETSG pSG, uint32_t fDst) +{ + RT_NOREF1(pvIfData); + int rc = VINF_SUCCESS; + + PVBOXNETLWF_MODULE pModule = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltPortOsXmit: instance=%p module=%p\n", pThis, pModule)); + if (!pModule) + { + LogFlow(("<==vboxNetFltPortOsXmit: pModule is null, return %d\n", VERR_INTERNAL_ERROR)); + return VERR_INTERNAL_ERROR; + } + /* Prevent going into "paused" state until all transmissions have been completed. */ + NDIS_WAIT_FOR_MUTEX(&pModule->InTransmit); + /* Ignore all sends if the stack is paused or being paused, etc... */ + if (!vboxNetLwfWinIsRunning(pModule)) + { + NDIS_RELEASE_MUTEX(&pModule->InTransmit); + return VINF_SUCCESS; + } + + vboxNetLwfWinDumpPacket(pSG, !(fDst & INTNETTRUNKDIR_WIRE) ? "intnet --> host" + : !(fDst & INTNETTRUNKDIR_HOST) ? "intnet --> wire" : "intnet --> all"); + + /* + * There are two possible strategies to deal with incoming SGs: + * 1) make a copy of data and complete asynchronously; + * 2) complete synchronously using the original data buffers. + * Before we consider implementing (1) it is quite interesting to see + * how well (2) performs. So we block until our requests are complete. + * Actually there is third possibility -- to use SG retain/release + * callbacks, but those seem not be fully implemented yet. + * Note that ansynchronous completion will require different implementation + * of vboxNetLwfWinPause(), not relying on InTransmit mutex. + */ +#ifdef VBOXNETLWF_SYNC_SEND + PVOID aEvents[2]; /* To wire and to host */ + ULONG nEvents = 0; + LARGE_INTEGER timeout; + timeout.QuadPart = -(LONGLONG)10000000; /* 1 sec */ +#endif /* VBOXNETLWF_SYNC_SEND */ + if (fDst & INTNETTRUNKDIR_WIRE) + { + PNET_BUFFER_LIST pBufList = vboxNetLwfWinSGtoNB(pModule, pSG); + if (pBufList) + { + vboxNetLwfWinDumpPackets("vboxNetFltPortOsXmit: sending down", pBufList); +#ifdef VBOXNETLWF_SYNC_SEND + aEvents[nEvents++] = &pModule->EventWire; +#else /* !VBOXNETLWF_SYNC_SEND */ + if (ASMAtomicIncS32(&pModule->cPendingBuffers) == 1) + NdisResetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFSendNetBufferLists(pModule->hFilter, pBufList, NDIS_DEFAULT_PORT_NUMBER, 0); /** @todo sendFlags! */ + } + } + if (fDst & INTNETTRUNKDIR_HOST) + { + PNET_BUFFER_LIST pBufList = vboxNetLwfWinSGtoNB(pModule, pSG); + if (pBufList) + { + vboxNetLwfWinDumpPackets("vboxNetFltPortOsXmit: sending up", pBufList); +#ifdef VBOXNETLWF_SYNC_SEND + aEvents[nEvents++] = &pModule->EventHost; +#else /* !VBOXNETLWF_SYNC_SEND */ + if (ASMAtomicIncS32(&pModule->cPendingBuffers) == 1) + NdisResetEvent(&pModule->EventSendComplete); +#endif /* !VBOXNETLWF_SYNC_SEND */ + NdisFIndicateReceiveNetBufferLists(pModule->hFilter, pBufList, NDIS_DEFAULT_PORT_NUMBER, 1, 0); + } + } +#ifdef VBOXNETLWF_SYNC_SEND + if (nEvents) + { + NTSTATUS Status = KeWaitForMultipleObjects(nEvents, aEvents, WaitAll, Executive, KernelMode, FALSE, &timeout, NULL); + if (Status != STATUS_SUCCESS) + { + LogError(("vboxNetFltPortOsXmit: KeWaitForMultipleObjects() failed with 0x%x\n", Status)); + if (Status == STATUS_TIMEOUT) + rc = VERR_TIMEOUT; + else + rc = RTErrConvertFromNtStatus(Status); + } + } +#endif /* VBOXNETLWF_SYNC_SEND */ + NDIS_RELEASE_MUTEX(&pModule->InTransmit); + + LogFlow(("<==vboxNetFltPortOsXmit: return %d\n", rc)); + return rc; +} + + +NDIS_IO_WORKITEM_FUNCTION vboxNetLwfWinToggleOffloading; + +VOID vboxNetLwfWinToggleOffloading(PVOID WorkItemContext, NDIS_HANDLE NdisIoWorkItemHandle) +{ + /* WARNING! Call this with IRQL=Passive! */ + RT_NOREF1(NdisIoWorkItemHandle); + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)WorkItemContext; + + if (ASMAtomicReadBool(&pModuleCtx->fActive)) + { + /* Disable offloading temporarily by indicating offload config change. */ + /** @todo Be sure to revise this when implementing offloading support! */ + vboxNetLwfWinIndicateOffload(pModuleCtx, pModuleCtx->pDisabledOffloadConfig); + Log(("vboxNetLwfWinToggleOffloading: set offloading off\n")); + } + else + { + /* The filter is inactive -- restore offloading configuration. */ + if (pModuleCtx->fOffloadConfigValid) + { + vboxNetLwfWinIndicateOffload(pModuleCtx, pModuleCtx->pSavedOffloadConfig); + Log(("vboxNetLwfWinToggleOffloading: restored offloading config\n")); + } + else + DbgPrint("VBoxNetLwf: no saved offload config to restore for %s\n", pModuleCtx->szMiniportName); + } +} + + +void vboxNetFltPortOsSetActive(PVBOXNETFLTINS pThis, bool fActive) +{ + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltPortOsSetActive: instance=%p module=%p fActive=%RTbool\n", pThis, pModuleCtx, fActive)); + if (!pModuleCtx) + { + LogFlow(("<==vboxNetFltPortOsSetActive: pModuleCtx is null\n")); + return; + } + + NDIS_STATUS Status = STATUS_SUCCESS; + bool fOldActive = ASMAtomicXchgBool(&pModuleCtx->fActive, fActive); + if (fOldActive != fActive) + { + NdisQueueIoWorkItem(pModuleCtx->hWorkItem, vboxNetLwfWinToggleOffloading, pModuleCtx); + Status = vboxNetLwfWinSetPacketFilter(pModuleCtx, fActive); + LogFlow(("<==vboxNetFltPortOsSetActive: vboxNetLwfWinSetPacketFilter() returned 0x%x\n", Status)); + } + else + LogFlow(("<==vboxNetFltPortOsSetActive: no change, remain %sactive\n", fActive ? "":"in")); +} + +int vboxNetFltOsDisconnectIt(PVBOXNETFLTINS pThis) +{ + RT_NOREF1(pThis); + LogFlow(("==>vboxNetFltOsDisconnectIt: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsDisconnectIt: return 0\n")); + return VINF_SUCCESS; +} + +int vboxNetFltOsConnectIt(PVBOXNETFLTINS pThis) +{ + RT_NOREF1(pThis); + LogFlow(("==>vboxNetFltOsConnectIt: instance=%p\n", pThis)); + LogFlow(("<==vboxNetFltOsConnectIt: return 0\n")); + return VINF_SUCCESS; +} + +/* + * Uncommenting the following line produces debug log messages on IP address changes, + * including wired interfaces. No actual calls to a switch port are made. This is for + * debug purposes only! + * #define VBOXNETLWFWIN_DEBUGIPADDRNOTIF 1 + */ +static void __stdcall vboxNetLwfWinIpAddrChangeCallback(IN PVOID pvCtx, + IN PMIB_UNICASTIPADDRESS_ROW pRow, + IN MIB_NOTIFICATION_TYPE enmNotifType) +{ + PVBOXNETFLTINS pThis = (PVBOXNETFLTINS)pvCtx; + + /* We are only interested in add or remove notifications. */ + bool fAdded; + if (enmNotifType == MibAddInstance) + fAdded = true; + else if (enmNotifType == MibDeleteInstance) + fAdded = false; + else + return; + + if ( pRow +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + && pThis->pSwitchPort->pfnNotifyHostAddress +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + ) + { + switch (pRow->Address.si_family) + { + case AF_INET: + if ( IN4_IS_ADDR_LINKLOCAL(&pRow->Address.Ipv4.sin_addr) + || pRow->Address.Ipv4.sin_addr.s_addr == IN4ADDR_LOOPBACK) + { + Log(("vboxNetLwfWinIpAddrChangeCallback: ignoring %s address (%RTnaipv4)\n", + pRow->Address.Ipv4.sin_addr.s_addr == IN4ADDR_LOOPBACK ? "loopback" : "link-local", + pRow->Address.Ipv4.sin_addr)); + break; + } + Log(("vboxNetLwfWinIpAddrChangeCallback: %s IPv4 addr=%RTnaipv4 on luid=(%u,%u)\n", + fAdded ? "add" : "remove", pRow->Address.Ipv4.sin_addr, + pRow->InterfaceLuid.Info.IfType, pRow->InterfaceLuid.Info.NetLuidIndex)); +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, kIntNetAddrType_IPv4, + &pRow->Address.Ipv4.sin_addr); +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + break; + case AF_INET6: + if (Ipv6AddressScope(pRow->Address.Ipv6.sin6_addr.u.Byte) <= ScopeLevelLink) + { + Log(("vboxNetLwfWinIpAddrChangeCallback: ignoring link-local address (%RTnaipv6)\n", + &pRow->Address.Ipv6.sin6_addr)); + break; + } + Log(("vboxNetLwfWinIpAddrChangeCallback: %s IPv6 addr=%RTnaipv6 scope=%d luid=(%u,%u)\n", + fAdded ? "add" : "remove", &pRow->Address.Ipv6.sin6_addr, + Ipv6AddressScope(pRow->Address.Ipv6.sin6_addr.u.Byte), + pRow->InterfaceLuid.Info.IfType, pRow->InterfaceLuid.Info.NetLuidIndex)); +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + pThis->pSwitchPort->pfnNotifyHostAddress(pThis->pSwitchPort, fAdded, kIntNetAddrType_IPv6, + &pRow->Address.Ipv6.sin6_addr); +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + break; + } + } + else + Log(("vboxNetLwfWinIpAddrChangeCallback: pRow=%p pfnNotifyHostAddress=%p\n", + pRow, pThis->pSwitchPort->pfnNotifyHostAddress)); +} + +void vboxNetLwfWinRegisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetLwfWinRegisterIpAddrNotifier: instance=%p\n", pThis)); + if ( pThis->pSwitchPort +#ifndef VBOXNETLWFWIN_DEBUGIPADDRNOTIF + && pThis->pSwitchPort->pfnNotifyHostAddress +#endif /* !VBOXNETLWFWIN_DEBUGIPADDRNOTIF */ + ) + { + NETIO_STATUS Status; + /* First we need to go over all host IP addresses and add them via pfnNotifyHostAddress. */ + PMIB_UNICASTIPADDRESS_TABLE HostIpAddresses = NULL; + Status = GetUnicastIpAddressTable(AF_UNSPEC, &HostIpAddresses); + if (NETIO_SUCCESS(Status)) + { + for (unsigned i = 0; i < HostIpAddresses->NumEntries; i++) + vboxNetLwfWinIpAddrChangeCallback(pThis, &HostIpAddresses->Table[i], MibAddInstance); + } + else + LogError(("vboxNetLwfWinRegisterIpAddrNotifier: GetUnicastIpAddressTable failed with %x\n", Status)); + /* Now we can register a callback function to keep track of address changes. */ + Status = NotifyUnicastIpAddressChange(AF_UNSPEC, vboxNetLwfWinIpAddrChangeCallback, + pThis, false, &pThis->u.s.WinIf.hNotifier); + if (NETIO_SUCCESS(Status)) + Log(("vboxNetLwfWinRegisterIpAddrNotifier: notifier=%p\n", pThis->u.s.WinIf.hNotifier)); + else + LogError(("vboxNetLwfWinRegisterIpAddrNotifier: NotifyUnicastIpAddressChange failed with %x\n", Status)); + } + else + pThis->u.s.WinIf.hNotifier = NULL; + LogFlow(("<==vboxNetLwfWinRegisterIpAddrNotifier\n")); +} + +void vboxNetLwfWinUnregisterIpAddrNotifier(PVBOXNETFLTINS pThis) +{ + Log(("vboxNetLwfWinUnregisterIpAddrNotifier: notifier=%p\n", pThis->u.s.WinIf.hNotifier)); + if (pThis->u.s.WinIf.hNotifier) + CancelMibChangeNotify2(pThis->u.s.WinIf.hNotifier); +} + +void vboxNetFltOsDeleteInstance(PVBOXNETFLTINS pThis) +{ + PVBOXNETLWF_MODULE pModuleCtx = (PVBOXNETLWF_MODULE)pThis->u.s.WinIf.hModuleCtx; + LogFlow(("==>vboxNetFltOsDeleteInstance: instance=%p module=%p\n", pThis, pModuleCtx)); + /* Cancel IP address change notifications */ + vboxNetLwfWinUnregisterIpAddrNotifier(pThis); + /* Technically it is possible that the module has already been gone by now. */ + if (pModuleCtx) + { + Assert(!pModuleCtx->fActive); /* Deactivation ensures bypass mode */ + pModuleCtx->pNetFlt = NULL; + pThis->u.s.WinIf.hModuleCtx = NULL; + } + LogFlow(("<==vboxNetFltOsDeleteInstance\n")); +} + +static void vboxNetLwfWinReportCapabilities(PVBOXNETFLTINS pThis, PVBOXNETLWF_MODULE pModuleCtx) +{ + if (pThis->pSwitchPort + && vboxNetFltTryRetainBusyNotDisconnected(pThis)) + { + pThis->pSwitchPort->pfnReportMacAddress(pThis->pSwitchPort, &pModuleCtx->MacAddr); + pThis->pSwitchPort->pfnReportPromiscuousMode(pThis->pSwitchPort, + vboxNetLwfWinIsPromiscuous(pModuleCtx)); + pThis->pSwitchPort->pfnReportGsoCapabilities(pThis->pSwitchPort, 0, + INTNETTRUNKDIR_WIRE | INTNETTRUNKDIR_HOST); + pThis->pSwitchPort->pfnReportNoPreemptDsts(pThis->pSwitchPort, 0 /* none */); + vboxNetFltRelease(pThis, true /*fBusy*/); + } +} + +int vboxNetFltOsInitInstance(PVBOXNETFLTINS pThis, void *pvContext) +{ + RT_NOREF1(pvContext); + LogFlow(("==>vboxNetFltOsInitInstance: instance=%p context=%p\n", pThis, pvContext)); + AssertReturn(pThis, VERR_INVALID_PARAMETER); + Log(("vboxNetFltOsInitInstance: trunk name=%s\n", pThis->szName)); + NdisAcquireSpinLock(&g_VBoxNetLwfGlobals.Lock); + PVBOXNETLWF_MODULE pModuleCtx; + RTListForEach(&g_VBoxNetLwfGlobals.listModules, pModuleCtx, VBOXNETLWF_MODULE, node) + { + DbgPrint("vboxNetFltOsInitInstance: evaluating module, name=%s\n", pModuleCtx->szMiniportName); + if (!RTStrICmp(pThis->szName, pModuleCtx->szMiniportName)) + { + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + Log(("vboxNetFltOsInitInstance: found matching module, name=%s\n", pThis->szName)); + pThis->u.s.WinIf.hModuleCtx = pModuleCtx; + pModuleCtx->pNetFlt = pThis; + vboxNetLwfWinReportCapabilities(pThis, pModuleCtx); + vboxNetLwfWinRegisterIpAddrNotifier(pThis); + LogFlow(("<==vboxNetFltOsInitInstance: return 0\n")); + return VINF_SUCCESS; + } + } + NdisReleaseSpinLock(&g_VBoxNetLwfGlobals.Lock); + // Internal network code will try to reconnect periodically, we should not spam in event log + //vboxNetLwfLogErrorEvent(IO_ERR_INTERNAL_ERROR, STATUS_SUCCESS, 6); + LogFlow(("<==vboxNetFltOsInitInstance: return VERR_INTNET_FLT_IF_NOT_FOUND\n")); + return VERR_INTNET_FLT_IF_NOT_FOUND; +} + +int vboxNetFltOsPreInitInstance(PVBOXNETFLTINS pThis) +{ + LogFlow(("==>vboxNetFltOsPreInitInstance: instance=%p\n", pThis)); + pThis->u.s.WinIf.hModuleCtx = 0; + pThis->u.s.WinIf.hNotifier = NULL; + LogFlow(("<==vboxNetFltOsPreInitInstance: return 0\n")); + return VINF_SUCCESS; +} + +void vboxNetFltPortOsNotifyMacAddress(PVBOXNETFLTINS pThis, void *pvIfData, PCRTMAC pMac) +{ + RT_NOREF3(pThis, pvIfData, pMac); + LogFlow(("==>vboxNetFltPortOsNotifyMacAddress: instance=%p data=%p mac=%RTmac\n", pThis, pvIfData, pMac)); + LogFlow(("<==vboxNetFltPortOsNotifyMacAddress\n")); +} + +int vboxNetFltPortOsConnectInterface(PVBOXNETFLTINS pThis, void *pvIf, void **ppvIfData) +{ + RT_NOREF3(pThis, pvIf, ppvIfData); + LogFlow(("==>vboxNetFltPortOsConnectInterface: instance=%p if=%p data=%p\n", pThis, pvIf, ppvIfData)); + LogFlow(("<==vboxNetFltPortOsConnectInterface: return 0\n")); + /* Nothing to do */ + return VINF_SUCCESS; +} + +int vboxNetFltPortOsDisconnectInterface(PVBOXNETFLTINS pThis, void *pvIfData) +{ + RT_NOREF2(pThis, pvIfData); + LogFlow(("==>vboxNetFltPortOsDisconnectInterface: instance=%p data=%p\n", pThis, pvIfData)); + LogFlow(("<==vboxNetFltPortOsDisconnectInterface: return 0\n")); + /* Nothing to do */ + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h new file mode 100644 index 00000000..1a0a9e7e --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf-win.h @@ -0,0 +1,55 @@ +/* $Id: VBoxNetLwf-win.h $ */ +/** @file + * VBoxNetLwf-win.h - Bridged Networking Driver, Windows-specific code. + */ +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define VBOXNETLWF_VERSION_NDIS_MAJOR 6 +#define VBOXNETLWF_VERSION_NDIS_MINOR 0 + +#define VBOXNETLWF_NAME_FRIENDLY L"VirtualBox NDIS Light-Weight Filter" +#define VBOXNETLWF_NAME_UNIQUE L"{7af6b074-048d-4444-bfce-1ecc8bc5cb76}" +#define VBOXNETLWF_NAME_SERVICE L"VBoxNetLwf" + +#define VBOXNETLWF_NAME_LINK L"\\DosDevices\\Global\\VBoxNetLwf" +#define VBOXNETLWF_NAME_DEVICE L"\\Device\\VBoxNetLwf" + +#define VBOXNETLWF_MEM_TAG 'FLBV' +#define VBOXNETLWF_REQ_ID 'fLBV' + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_drv_VBoxNetLwf_win_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf new file mode 100644 index 00000000..f5d9cdca --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/drv/VBoxNetLwf.inf @@ -0,0 +1,120 @@ +; $Id: VBoxNetLwf.inf $ +; @file +; VBoxNetLwf.inf - VirtualBox Bridged Networking Driver inf file +; + +; +; Copyright (C) 2014-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature = "$Windows NT$" +;cat CatalogFile = VBoxNetLwf.cat +Class = NetService +ClassGUID = {4D36E974-E325-11CE-BFC1-08002BE10318} +Provider = %ORACLE% +;edit-DriverVer=10/23/2014,1.0.1.0 + + +[Manufacturer] +%ORACLE% = VBoxNetLwf@COMMA-NT-ARCH@ + +[ControlFlags] + +[VBoxNetLwf@DOT-NT-ARCH@] +%VBoxNetLwf_Desc% = VBoxNetLwf.ndi, oracle_VBoxNetLwf + +[VBoxNetLwf.ndi] +AddReg = VBoxNetLwf.ndi.AddReg, VBoxNetLwf.AddReg +Characteristics = 0x40000 ; NCF_LW_FILTER +CopyFiles = VBoxNetLwf.Files.Sys +NetCfgInstanceId = "{7af6b074-048d-4444-bfce-1ecc8bc5cb76}" + +[VBoxNetLwf.ndi.Remove.Services] +DelService = VBoxNetLwf,0x200 ; Stop the service before uninstalling + +[VBoxNetLwf.ndi.Services] +AddService = VBoxNetLwf,, VBoxNetLwf.AddService, VBoxNetLwf.AddEventLog + +[VBoxNetLwf.AddService] +DisplayName = %VBoxNetLwfService_Desc% +ServiceType = 1 ;SERVICE_KERNEL_DRIVER +StartType = 1 ;SERVICE_SYSTEM_START +ErrorControl = 1 ;SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxNetLwf.sys +LoadOrderGroup = NDIS +AddReg = VBoxNetLwf.AddService.AddReg + +[VBoxNetLwf.AddService.AddReg] + +[VBoxNetLwf.AddEventLog] +AddReg = VBoxNetLwf.AddEventLog.AddReg + +[VBoxNetLwf.AddEventLog.AddReg] +HKR,,EventMessageFile,0x00020000,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,0x00010001,7 + + +[SourceDisksNames] +1=%DiskDescription%,"",, + +[SourceDisksFiles] +VBoxNetLwf.sys=1 + +[DestinationDirs] +DefaultDestDir = 12 +VBoxNetLwf.Files.Sys = 12 ; %windir%\System32\drivers + +[VBoxNetLwf.Files.Sys] +VBoxNetLwf.sys,,,2 + + +[VBoxNetLwf.ndi.AddReg] +HKR, Ndi, HelpText, , %VBoxNetLwf_HELP% +;HKR, Ndi, ClsID, 0, {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} +;HKR, Ndi, ComponentDll, , VBoxNetLwfNobj.dll +HKR, Ndi, FilterClass, , compression +HKR, Ndi, FilterType, 0x10001, 0x2 +HKR, Ndi, FilterRunType,0x10001, 2 ; OPTIONAL, to prevent unbinding of protocol drivers +HKR, Ndi, Service, , VBoxNetLwf +HKR, Ndi, CoServices, 0x10000, VBoxNetLwf +HKR, Ndi\Interfaces, UpperRange, , noupper +HKR, Ndi\Interfaces, LowerRange, , nolower +HKR, Ndi\Interfaces, FilterMediaTypes, , ethernet + +[VBoxNetLwf.AddReg] +;HKR, Parameters, Param1, 0, 4 + +[Strings] +ORACLE = "Oracle Corporation" +DiskDescription = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwf_Desc = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwf_HELP = "VirtualBox NDIS6 Bridged Networking Driver" +VBoxNetLwfService_Desc = "VirtualBox NDIS6 Bridged Networking Service" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp new file mode 100644 index 00000000..a5cf004c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.cpp @@ -0,0 +1,747 @@ +/* $Id: VBoxNetFltNobj.cpp $ */ +/** @file + * VBoxNetFltNobj.cpp - Notify Object for Bridged Networking Driver. + * + * Used to filter Bridged Networking Driver bindings + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxNetFltNobj.h" +#include +#include +#include +#include + +#include + +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +//# define VBOXNETFLTNOTIFY_DEBUG_BIND + +#ifdef DEBUG +# define NonStandardAssert(a) Assert(a) +# define NonStandardAssertBreakpoint() AssertFailed() +#else +# define NonStandardAssert(a) do{}while (0) +# define NonStandardAssertBreakpoint() do{}while (0) +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static HMODULE g_hModSelf = (HMODULE)~(uintptr_t)0; + + +VBoxNetFltNobj::VBoxNetFltNobj() + : mpNetCfg(NULL) + , mpNetCfgComponent(NULL) + , mbInstalling(FALSE) +{ +} + +VBoxNetFltNobj::~VBoxNetFltNobj() +{ + cleanup(); +} + +void VBoxNetFltNobj::cleanup() +{ + if (mpNetCfg) + { + mpNetCfg->Release(); + mpNetCfg = NULL; + } + + if (mpNetCfgComponent) + { + mpNetCfgComponent->Release(); + mpNetCfgComponent = NULL; + } +} + +void VBoxNetFltNobj::init(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) +{ + cleanup(); + + NonStandardAssert(pNetCfg); + NonStandardAssert(pNetCfgComponent); + if (pNetCfg) + { + pNetCfg->AddRef(); + mpNetCfg = pNetCfg; + } + + if (pNetCfgComponent) + { + pNetCfgComponent->AddRef(); + mpNetCfgComponent = pNetCfgComponent; + } + + mbInstalling = bInstalling; +} + +/* INetCfgComponentControl methods */ +STDMETHODIMP VBoxNetFltNobj::Initialize(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling) +{ + init(pNetCfgComponent, pNetCfg, bInstalling); + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::ApplyRegistryChanges() +{ + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::ApplyPnpChanges(IN INetCfgPnpReconfigCallback *pCallback) +{ + RT_NOREF1(pCallback); + return S_OK; +} + +STDMETHODIMP VBoxNetFltNobj::CancelChanges() +{ + return S_OK; +} + +static HRESULT vboxNetFltWinQueryInstanceKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) +{ + LPWSTR pwszPnpId; + HRESULT hrc = pComponent->GetPnpDevNodeId(&pwszPnpId); + if (hrc == S_OK) + { + WCHAR wszKeyName[MAX_PATH]; + RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Enum\\"); + int rc = RTUtf16Cat(wszKeyName, MAX_PATH, pwszPnpId); + if (RT_SUCCESS(rc)) + { + LSTATUS lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); + if (lrc != ERROR_SUCCESS) + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + } + else + AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); + + CoTaskMemFree(pwszPnpId); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinQueryDriverKey(IN HKEY InstanceKey, OUT PHKEY phKey) +{ + HRESULT hrc = S_OK; + + WCHAR wszValue[MAX_PATH]; + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + LSTATUS lrc = RegQueryValueExW(InstanceKey, L"Driver", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; /* registry strings does not need to be zero terminated. */ + + WCHAR wszKeyName[MAX_PATH]; + RTUtf16Copy(wszKeyName, MAX_PATH, L"SYSTEM\\CurrentControlSet\\Control\\Class\\"); + int rc = RTUtf16Cat(wszKeyName, MAX_PATH, wszValue); + if (RT_SUCCESS(rc)) + { + lrc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszKeyName, 0 /*ulOptions*/, KEY_READ, phKey); + if (lrc != ERROR_SUCCESS) + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + } + else + AssertRCStmt(rc, hrc = ERROR_BUFFER_OVERFLOW); + } + else + { + hrc = HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + NonStandardAssertBreakpoint(); + } + } + else + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + + return hrc; +} + +static HRESULT vboxNetFltWinQueryDriverKey(IN INetCfgComponent *pComponent, OUT PHKEY phKey) +{ + HKEY hKeyInstance = NULL; + HRESULT hrc = vboxNetFltWinQueryInstanceKey(pComponent, &hKeyInstance); + if (hrc == S_OK) + { + hrc = vboxNetFltWinQueryDriverKey(hKeyInstance, phKey); + if (hrc != S_OK) + NonStandardAssertBreakpoint(); + RegCloseKey(hKeyInstance); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckNetAdp(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + LPWSTR pwszDevId = NULL; + HRESULT hrc = pComponent->GetId(&pwszDevId); + if (hrc == S_OK) + { + /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"sun_VBoxNetAdp", sizeof(L"sun_VBoxNetAdp")/2)) + * which includes the terminator, so it translates to a full compare. Goes way back. */ + if (RTUtf16ICmpAscii(pwszDevId, "sun_VBoxNetAdp") == 0) + *pfShouldBind = false; + else + hrc = S_FALSE; + CoTaskMemFree(pwszDevId); + } + else + NonStandardAssertBreakpoint(); + + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckMsLoop(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + LPWSTR pwszDevId = NULL; + HRESULT hrc = pComponent->GetId(&pwszDevId); + if (hrc == S_OK) + { + /** @todo r=bird: This was _wcsnicmp(pwszDevId, L"*msloop", sizeof(L"*msloop")/2) + * which includes the terminator, making it a full compare. Goes way back. */ + if (RTUtf16ICmpAscii(pwszDevId, "*msloop") == 0) + { + /* we need to detect the medium the adapter is presenting + * to do that we could examine in the registry the *msloop params */ + HKEY hKeyDriver; + hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); + if (hrc == S_OK) + { + WCHAR wszValue[64]; /* 2 should be enough actually, paranoid check for extra spaces */ + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + LSTATUS lrc = RegQueryValueExW(hKeyDriver, L"Medium", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + wszValue[RT_ELEMENTS(wszValue) - 1] = '\0'; + + char szUtf8[256]; + char *pszUtf8 = szUtf8; + RTUtf16ToUtf8Ex(wszValue, RTSTR_MAX, &pszUtf8, sizeof(szUtf8), NULL); + pszUtf8 = RTStrStrip(pszUtf8); + + uint64_t uValue = 0; + int rc = RTStrToUInt64Ex(pszUtf8, NULL, 0, &uValue); + if (RT_SUCCESS(rc)) + { + if (uValue == 0) /* 0 is Ethernet */ + *pfShouldBind = true; + else + *pfShouldBind = false; + } + else + { + NonStandardAssertBreakpoint(); + *pfShouldBind = true; + } + } + else + NonStandardAssertBreakpoint(); + } + else + { + /** @todo we should check the default medium in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\\Ndi\Params\Medium, REG_SZ "Default" value */ + NonStandardAssertBreakpoint(); + *pfShouldBind = true; + } + + RegCloseKey(hKeyDriver); + } + else + NonStandardAssertBreakpoint(); + } + else + hrc = S_FALSE; + CoTaskMemFree(pwszDevId); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyCheckLowerRange(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + HKEY hKeyDriver = NULL; + HRESULT hrc = vboxNetFltWinQueryDriverKey(pComponent, &hKeyDriver); + if (hrc == S_OK) + { + HKEY hKeyInterfaces = NULL; + LSTATUS lrc = RegOpenKeyExW(hKeyDriver, L"Ndi\\Interfaces", 0 /*ulOptions*/, KEY_READ, &hKeyInterfaces); + if (lrc == ERROR_SUCCESS) + { + WCHAR wszValue[MAX_PATH]; + DWORD cbValue = sizeof(wszValue) - sizeof(WCHAR); + DWORD dwType = REG_SZ; + lrc = RegQueryValueExW(hKeyInterfaces, L"LowerRange", NULL /*lpReserved*/, &dwType, (LPBYTE)wszValue, &cbValue); + if (lrc == ERROR_SUCCESS) + { + if (dwType == REG_SZ) + { + if (RTUtf16FindAscii(wszValue, "ethernet") >= 0 || RTUtf16FindAscii(wszValue, "wan") >= 0) + *pfShouldBind = true; + else + *pfShouldBind = false; + } + } + else + { + /* do not set err status to it */ + *pfShouldBind = false; + NonStandardAssertBreakpoint(); + } + + RegCloseKey(hKeyInterfaces); + } + else + { + hrc = HRESULT_FROM_WIN32(lrc); + NonStandardAssertBreakpoint(); + } + + RegCloseKey(hKeyDriver); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgComponent *pComponent, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + /* filter out only physical adapters */ + DWORD fCharacteristics = 0; + HRESULT hrc = pComponent->GetCharacteristics(&fCharacteristics); + if (hrc != S_OK) + { + NonStandardAssertBreakpoint(); + return hrc; + } + + /* we are not binding to hidden adapters */ + if (fCharacteristics & NCF_HIDDEN) + return S_OK; + + hrc = vboxNetFltWinNotifyCheckMsLoop(pComponent, pfShouldBind); + if ( hrc == S_OK /* this is a loopback adapter, the pfShouldBind already contains the result */ + || hrc != S_FALSE /* error occurred */) + return hrc; + + hrc = vboxNetFltWinNotifyCheckNetAdp(pComponent, pfShouldBind); + if ( hrc == S_OK /* this is a VBoxNetAdp adapter, the pfShouldBind already contains the result */ + || hrc != S_FALSE /* error occurred */) + return hrc; + + //if (!(fCharacteristics & NCF_PHYSICAL)) + //{ + // *pfShouldBind = false; /* we are binding to physical adapters only */ + // return S_OK; + //} + + return vboxNetFltWinNotifyCheckLowerRange(pComponent, pfShouldBind); +} + + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingInterface *pIf, OUT bool *pfShouldBind) +{ + INetCfgComponent *pAdapterComponent = NULL; + HRESULT hrc = pIf->GetLowerComponent(&pAdapterComponent); + if (hrc == S_OK) + { + hrc = vboxNetFltWinNotifyShouldBind(pAdapterComponent, pfShouldBind); + + pAdapterComponent->Release(); + } + else + { + NonStandardAssertBreakpoint(); + *pfShouldBind = false; + } + return hrc; +} + +static HRESULT vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath, OUT bool *pfShouldBind) +{ + *pfShouldBind = false; + + IEnumNetCfgBindingInterface *pIEnumBinding = NULL; + HRESULT hrc = pPath->EnumBindingInterfaces(&pIEnumBinding); + if (hrc == S_OK) + { + hrc = pIEnumBinding->Reset(); + if (hrc == S_OK) + { + for (;;) + { + ULONG uCount = 0; + INetCfgBindingInterface *pIBinding = NULL; + hrc = pIEnumBinding->Next(1, &pIBinding, &uCount); + if (hrc == S_OK) + { + hrc = vboxNetFltWinNotifyShouldBind(pIBinding, pfShouldBind); + pIBinding->Release(); + + if (hrc != S_OK) + break; /* break on failure. */ + if (!*pfShouldBind) + break; + } + else if (hrc == S_FALSE) + { + /* no more elements */ + hrc = S_OK; + break; + } + else + { + NonStandardAssertBreakpoint(); + /* break on falure */ + break; + } + } + } + else + NonStandardAssertBreakpoint(); + + pIEnumBinding->Release(); + } + else + NonStandardAssertBreakpoint(); + return hrc; +} + +static bool vboxNetFltWinNotifyShouldBind(IN INetCfgBindingPath *pPath) +{ +#ifdef VBOXNETFLTNOTIFY_DEBUG_BIND + return VBOXNETFLTNOTIFY_DEBUG_BIND; +#else + bool fShouldBind; + HRESULT hrc = vboxNetFltWinNotifyShouldBind(pPath, &fShouldBind); + if (hrc != S_OK) + fShouldBind = VBOXNETFLTNOTIFY_ONFAIL_BINDDEFAULT; + + return fShouldBind; +#endif +} + + +/* INetCfgComponentNotifyBinding methods */ +STDMETHODIMP VBoxNetFltNobj::NotifyBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) +{ + if (!(dwChangeFlag & NCN_ENABLE) || (dwChangeFlag & NCN_REMOVE) || vboxNetFltWinNotifyShouldBind(pNetCfgBP)) + return S_OK; + return NETCFG_S_DISABLE_QUERY; +} + +STDMETHODIMP VBoxNetFltNobj::QueryBindingPath(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP) +{ + RT_NOREF1(dwChangeFlag); + if (vboxNetFltWinNotifyShouldBind(pNetCfgBP)) + return S_OK; + return NETCFG_S_DISABLE_QUERY; +} + + +static ATL::CComModule _Module; + +BEGIN_OBJECT_MAP(ObjectMap) + OBJECT_ENTRY(CLSID_VBoxNetFltNobj, VBoxNetFltNobj) +END_OBJECT_MAP() + + +extern "C" +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hModSelf = (HMODULE)hInstance; + + _Module.Init(ObjectMap, hInstance); + DisableThreadLibraryCalls(hInstance); + } + else if (dwReason == DLL_PROCESS_DETACH) + { + _Module.Term(); + } + return TRUE; +} + +STDAPI DllCanUnloadNow(void) +{ + return _Module.GetLockCount() == 0 ? S_OK : S_FALSE; +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + return _Module.GetClassObject(rclsid, riid, ppv); +} + + +/* + * ATL::CComModule does not suport server registration/unregistration methods, + * so we need to do it manually. Since this is the only place we do registraton + * manually, we do it the quick-and-dirty way. + */ + +#ifdef RT_EXCEPTIONS_ENABLED +/* Someday we may want to log errors. */ +class AdHocRegError +{ +public: + AdHocRegError(LSTATUS rc) { RT_NOREF1(rc); }; +}; +#endif + +/** + * A simple wrapper on Windows registry functions. + */ +class AdHocRegKey +{ +public: + AdHocRegKey(HKEY hKey) : m_hKey(hKey) {}; + AdHocRegKey(LPCWSTR pcwszName, HKEY hParent = HKEY_CLASSES_ROOT); + ~AdHocRegKey() { RegCloseKey(m_hKey); }; + + AdHocRegKey *create(LPCWSTR pcwszSubkey); + LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue); + HKEY getKey(void) { return m_hKey; }; +private: + HKEY m_hKey; +}; + +AdHocRegKey::AdHocRegKey(LPCWSTR pcwszName, HKEY hParent) : m_hKey(NULL) +{ + LSTATUS rc = RegOpenKeyExW(hParent, pcwszName, 0, KEY_ALL_ACCESS, &m_hKey); + if (rc != ERROR_SUCCESS) +#ifdef RT_EXCEPTIONS_ENABLED + throw AdHocRegError(rc); +#else + m_hKey = NULL; +#endif +} + +AdHocRegKey *AdHocRegKey::create(LPCWSTR pcwszSubkey) +{ + HKEY hSubkey; + LSTATUS rc = RegCreateKeyExW(m_hKey, pcwszSubkey, + 0 /*Reserved*/, NULL /*pszClass*/, 0 /*fOptions*/, + KEY_ALL_ACCESS, NULL /*pSecAttr*/, &hSubkey, NULL /*pdwDisposition*/); + if (rc != ERROR_SUCCESS) +#ifdef RT_EXCEPTIONS_ENABLED + throw AdHocRegError(rc); +#else + return NULL; +#endif + AdHocRegKey *pSubkey = new AdHocRegKey(hSubkey); + if (!pSubkey) + RegCloseKey(hSubkey); + return pSubkey; +} + +LSTATUS AdHocRegKey::setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) +{ + LSTATUS rc = RegSetValueExW(m_hKey, pcwszName, 0, REG_SZ, (const BYTE *)pcwszValue, + (DWORD)((RTUtf16Len(pcwszValue) + 1) * sizeof(WCHAR))); +#ifdef RT_EXCEPTIONS_ENABLED + if (rc != ERROR_SUCCESS) + throw AdHocRegError(rc); +#endif + return rc; +} + +/** + * Auxiliary class that facilitates automatic destruction of AdHocRegKey objects + * allocated in heap. No reference counting here! + */ +class AdHocRegKeyPtr +{ +public: + AdHocRegKeyPtr(AdHocRegKey *pKey) : m_pKey(pKey) {}; + ~AdHocRegKeyPtr() + { + if (m_pKey) + { + delete m_pKey; + m_pKey = NULL; + } + } + + AdHocRegKey *create(LPCWSTR pcwszSubkey) + { return m_pKey ? m_pKey->create(pcwszSubkey) : NULL; }; + + LSTATUS setValue(LPCWSTR pcwszName, LPCWSTR pcwszValue) + { return m_pKey ? m_pKey->setValue(pcwszName, pcwszValue) : ERROR_INVALID_STATE; }; + +private: + AdHocRegKey *m_pKey; + /* Prevent copying, since we do not support reference counting */ + AdHocRegKeyPtr(const AdHocRegKeyPtr&); + AdHocRegKeyPtr& operator=(const AdHocRegKeyPtr&); +}; + + +STDAPI DllRegisterServer(void) +{ + /* Get the path to the DLL we're running inside. */ + WCHAR wszModule[MAX_PATH + 1]; + UINT cwcModule = GetModuleFileNameW(g_hModSelf, wszModule, MAX_PATH); + if (cwcModule == 0 || cwcModule > MAX_PATH) + return SELFREG_E_CLASS; + wszModule[MAX_PATH] = '\0'; + + /* + * Create registry keys and values. When exceptions are disabled, we depend + * on setValue() to propagate fail key creation failures. + */ +#ifdef RT_EXCEPTIONS_ENABLED + try +#endif + { + AdHocRegKey keyCLSID(L"CLSID"); + AdHocRegKeyPtr pkeyNobjClass(keyCLSID.create(L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}")); + LSTATUS lrc = pkeyNobjClass.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + + AdHocRegKeyPtr pkeyNobjSrv(pkeyNobjClass.create(L"InProcServer32")); + lrc = pkeyNobjSrv.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + lrc = pkeyNobjSrv.setValue(L"ThreadingModel", L"Both"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_CLASS; + } +#ifdef RT_EXCEPTIONS_ENABLED + catch (AdHocRegError) { return SELFREG_E_CLASS; } +#endif + +#ifdef RT_EXCEPTIONS_ENABLED + try +#endif + { + AdHocRegKey keyTypeLib(L"TypeLib"); + AdHocRegKeyPtr pkeyNobjLib(keyTypeLib.create(L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}\\1.1")); + LSTATUS lrc = pkeyNobjLib.setValue(NULL, L"VirtualBox Bridged Networking Driver Notify Object v1.1 Type Library"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + + AdHocRegKeyPtr pkeyNobjLib0(pkeyNobjLib.create(L"0\\win64")); + lrc = pkeyNobjLib0.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + AdHocRegKeyPtr pkeyNobjLibFlags(pkeyNobjLib.create(L"FLAGS")); + lrc = pkeyNobjLibFlags.setValue(NULL, L"0"); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + + if (GetSystemDirectoryW(wszModule, MAX_PATH) == 0) + return SELFREG_E_TYPELIB; + AdHocRegKeyPtr pkeyNobjLibHelpDir(pkeyNobjLib.create(L"HELPDIR")); + lrc = pkeyNobjLibHelpDir.setValue(NULL, wszModule); + if (lrc != ERROR_SUCCESS) + return SELFREG_E_TYPELIB; + } +#ifdef RT_EXCEPTIONS_ENABLED + catch (AdHocRegError) { return SELFREG_E_TYPELIB; } +#endif + + return S_OK; +} + + +STDAPI DllUnregisterServer(void) +{ + static struct { HKEY hKeyRoot; wchar_t const *pwszParentKey; wchar_t const *pwszKeyToDelete; HRESULT hrcFail; } + s_aKeys[] = + { + { HKEY_CLASSES_ROOT, L"TypeLib", L"{2A0C94D1-40E1-439C-8FE8-24107CAB0840}", SELFREG_E_TYPELIB }, + { HKEY_CLASSES_ROOT, L"CLSID", L"{f374d1a0-bf08-4bdc-9cb2-c15ddaeef955}", SELFREG_E_CLASS }, + }; + + HRESULT hrc = S_OK; + for (size_t i = 0; i < RT_ELEMENTS(s_aKeys); i++) + { + HKEY hKey = NULL; + LSTATUS lrc = RegOpenKeyExW(s_aKeys[i].hKeyRoot, s_aKeys[i].pwszParentKey, 0, KEY_ALL_ACCESS, &hKey); + if (lrc == ERROR_SUCCESS) + { + lrc = RegDeleteTreeW(hKey, s_aKeys[i].pwszKeyToDelete); /* Vista and later */ + RegCloseKey(hKey); + } + + if (lrc != ERROR_SUCCESS && lrc != ERROR_FILE_NOT_FOUND && hrc == S_OK) + hrc = s_aKeys[i].hrcFail; + } + + return S_OK; +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def new file mode 100644 index 00000000..abfe3834 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.def @@ -0,0 +1,42 @@ +; $Id: VBoxNetFltNobj.def $ +; @file +; VBoxNetFltNobj.def - Notify Object for Bridged Networking Driver. +; Library def file +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; +LIBRARY VBoxNetFltNobj +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h new file mode 100644 index 00000000..87eed992 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.h @@ -0,0 +1,96 @@ +/* $Id: VBoxNetFltNobj.h $ */ +/** @file + * VBoxNetFltNobj.h - Notify Object for Bridged Networking Driver. + * Used to filter Bridged Networking Driver bindings + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +#include "VBox/com/defs.h" +#include "VBoxNetFltNobjT.h" +#include "VBoxNetFltNobjRc.h" + +#define VBOXNETFLTNOTIFY_ONFAIL_BINDDEFAULT false + +/* + * VirtualBox Bridging driver notify object. + * Needed to make our driver bind to "real" host adapters only + */ +class ATL_NO_VTABLE VBoxNetFltNobj + : public ATL::CComObjectRootEx + , public ATL::CComCoClass + , public INetCfgComponentControl + , public INetCfgComponentNotifyBinding +{ +public: + VBoxNetFltNobj(); + virtual ~VBoxNetFltNobj(); + + BEGIN_COM_MAP(VBoxNetFltNobj) + COM_INTERFACE_ENTRY(INetCfgComponentControl) + COM_INTERFACE_ENTRY(INetCfgComponentNotifyBinding) + END_COM_MAP() + + // this is a "just in case" conditional, which is not defined +#ifdef VBOX_FORCE_REGISTER_SERVER + DECLARE_REGISTRY_RESOURCEID(IDR_VBOXNETFLT_NOBJ) +#endif + + /* INetCfgComponentControl methods */ + STDMETHOD(Initialize)(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling); + STDMETHOD(ApplyRegistryChanges)(); + STDMETHOD(ApplyPnpChanges)(IN INetCfgPnpReconfigCallback *pCallback); + STDMETHOD(CancelChanges)(); + + /* INetCfgComponentNotifyBinding methods */ + STDMETHOD(NotifyBindingPath)(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP); + STDMETHOD(QueryBindingPath)(IN DWORD dwChangeFlag, IN INetCfgBindingPath *pNetCfgBP); +private: + + void init(IN INetCfgComponent *pNetCfgComponent, IN INetCfg *pNetCfg, IN BOOL bInstalling); + void cleanup(); + + /* these two used to maintain the component info passed to + * INetCfgComponentControl::Initialize */ + INetCfg *mpNetCfg; + INetCfgComponent *mpNetCfgComponent; + BOOL mbInstalling; +}; + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobj_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc new file mode 100644 index 00000000..4a1398c6 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rc @@ -0,0 +1,78 @@ +/* $Id: VBoxNetFltNobj.rc $ */ +/** @file + * VBoxNetFltNobj - Resource file containing version info and icon. + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +#include "VBoxNetFltNobjRc.h" + +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 "040904E4" // Lang=US English, CharSet=Windows Multilingual + BEGIN + VALUE "FileDescription", "VirtualBox Bridged Networking Driver Notify Object v1.1\0" + VALUE "InternalName", "VBoxNetFltNobj\0" + VALUE "OriginalFilename", "VBoxNetFltNobj.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, 1252 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// REGISTRY +// + +IDR_VBOXNETFLT_NOBJ REGISTRY "VBoxNetFltNobj.rgs" + +1 TYPELIB "VBoxNetFltNobjT.tlb" diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs new file mode 100644 index 00000000..d6f9bae3 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobj.rgs @@ -0,0 +1,13 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {f374d1a0-bf08-4bdc-9cb2-c15ddaeef955} = s 'VirtualBox Bridged Networking Driver Notify Object v1.1' + { + InProcServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Both' + } + } + } +} \ No newline at end of file diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h new file mode 100644 index 00000000..187af500 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjRc.h @@ -0,0 +1,46 @@ +/* $Id: VBoxNetFltNobjRc.h $ */ +/** @file + * VBoxNetFltNobjRc.h - Notify Object for Bridged Networking Driver. + * Resource definitions + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h +#define VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* registry script rc ID */ +#define IDR_VBOXNETFLT_NOBJ 101 + +#endif /* !VBOX_INCLUDED_SRC_VBoxNetFlt_win_nobj_VBoxNetFltNobjRc_h */ diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl new file mode 100644 index 00000000..9bf8b6f9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/nobj/VBoxNetFltNobjT.idl @@ -0,0 +1,55 @@ +/* $Id: VBoxNetFltNobjT.idl $ */ +/** @file + * VBoxNetFltNobjT.idl - Notify Object for Bridged Networking Driver, typelib definition. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include + +[ + uuid(2a0c94d1-40e1-439c-8fe8-24107cab0840), + version(1.1), + helpstring("VirtualBox Bridged Networking Driver Notify Object v1.1 Type Library") +] +library VBoxNetFltNobjLib +{ + [ + uuid(f374d1a0-bf08-4bdc-9cb2-c15ddaeef955), + helpstring("VirtualBox Bridged Networking Driver Notify Object Class") + ] + coclass VBoxNetFltNobj + { + [restricted] interface INetCfgComponentControl; + [restricted] interface INetCfgComponentNotifyBinding; + }; +}; diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp new file mode 100644 index 00000000..40a79b1a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpInstall.cpp @@ -0,0 +1,341 @@ +/* $Id: VBoxNetAdpInstall.cpp $ */ +/** @file + * NetAdpInstall - VBoxNetAdp installer command line tool. + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETADP_APP_NAME L"NetAdpInstall" + +#define VBOX_NETADP_HWID L"sun_VBoxNetAdp" +#ifdef NDIS60 +# define VBOX_NETADP_INF L"VBoxNetAdp6.inf" +#else +# define VBOX_NETADP_INF L"VBoxNetAdp.inf" +#endif + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < sizeof(wsz) / sizeof(wsz[0])) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + + +static int VBoxNetAdpInstall(void) +{ + RTMsgInfo("Adding host-only interface..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + WCHAR wszInfFile[MAX_PATH]; + DWORD cwcInfFile = MyGetfullPathNameW(VBOX_NETADP_INF, RT_ELEMENTS(wszInfFile), wszInfFile); + if (cwcInfFile > 0) + { + INetCfg *pnc; + LPWSTR lpszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETADP_APP_NAME, 10000, &lpszLockedBy); + if (hr == S_OK) + { + + hr = VBoxNetCfgWinNetAdpInstall(pnc, wszInfFile); + + if (hr == S_OK) + RTMsgInfo("Installed successfully!"); + else + RTMsgError("failed to install VBoxNetAdp: %Rhrc", hr); + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + } + else + RTMsgError("VBoxNetCfgWinQueryINetCfg failed: %Rhrc", hr); + /* + hr = VBoxDrvCfgInfInstall(MpInf); + if (FAILED(hr)) + printf("VBoxDrvCfgInfInstall failed %#x\n", hr); + + GUID guid; + BSTR name, errMsg; + + hr = VBoxNetCfgWinCreateHostOnlyNetworkInterface (MpInf, true, &guid, &name, &errMsg); + if (SUCCEEDED(hr)) + { + ULONG ip, mask; + hr = VBoxNetCfgWinGenHostOnlyNetworkNetworkIp(&ip, &mask); + if (SUCCEEDED(hr)) + { + // ip returned by VBoxNetCfgWinGenHostOnlyNetworkNetworkIp is a network ip, + // i.e. 192.168.xxx.0, assign 192.168.xxx.1 for the hostonly adapter + ip = ip | (1 << 24); + hr = VBoxNetCfgWinEnableStaticIpConfig(&guid, ip, mask); + if (SUCCEEDED(hr)) + printf("installation successful\n"); + else + printf("VBoxNetCfgWinEnableStaticIpConfig failed: hr=%#lx\n", hr); + } + else + printf("VBoxNetCfgWinGenHostOnlyNetworkNetworkIp failed: hr=%#lx\n", hr); + } + else + printf("VBoxNetCfgWinCreateHostOnlyNetworkInterface failed: hr=%#lx\n", hr); + */ + } + else + { + DWORD dwErr = GetLastError(); + RTMsgError("MyGetfullPathNameW failed: %Rwc", dwErr); + hr = HRESULT_FROM_WIN32(dwErr); + } + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpUninstall(void) +{ + RTMsgInfo("Uninstalling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinRemoveAllNetDevicesOfId(VBOX_NETADP_HWID); + if (SUCCEEDED(hr)) + { + hr = VBoxDrvCfgInfUninstallAllSetupDi(&GUID_DEVCLASS_NET, L"Net", VBOX_NETADP_HWID, 0/* could be SUOI_FORCEDELETE */); + if (SUCCEEDED(hr)) + RTMsgInfo("Uninstallation successful!"); + else + RTMsgWarning("uninstalled successfully, but failed to remove infs (%Rhrc)\n", hr); + } + else + RTMsgError("uninstall failed: %Rhrc", hr); + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpUpdate(void) +{ + RTMsgInfo("Uninstalling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + BOOL fRebootRequired = FALSE; + /* + * Before we can update the driver for existing adapters we need to remove + * all old driver packages from the driver cache. Otherwise we may end up + * with both NDIS5 and NDIS6 versions of VBoxNetAdp in the cache which + * will cause all sorts of trouble. + */ + VBoxDrvCfgInfUninstallAllF(L"Net", VBOX_NETADP_HWID, SUOI_FORCEDELETE); + hr = VBoxNetCfgWinUpdateHostOnlyNetworkInterface(VBOX_NETADP_INF, &fRebootRequired, VBOX_NETADP_HWID); + if (SUCCEEDED(hr)) + { + if (fRebootRequired) + RTMsgWarning("!!REBOOT REQUIRED!!"); + RTMsgInfo("Updated successfully!"); + } + else + RTMsgError("update failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpDisable(void) +{ + RTMsgInfo("Disabling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinPropChangeAllNetDevicesOfId(VBOX_NETADP_HWID, VBOXNECTFGWINPROPCHANGE_TYPE_DISABLE); + if (SUCCEEDED(hr)) + RTMsgInfo("Disabling successful"); + else + RTMsgError("disable failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static int VBoxNetAdpEnable(void) +{ + RTMsgInfo("Enabling all host-only interfaces..."); + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) + { + hr = VBoxNetCfgWinPropChangeAllNetDevicesOfId(VBOX_NETADP_HWID, VBOXNECTFGWINPROPCHANGE_TYPE_ENABLE); + if (SUCCEEDED(hr)) + RTMsgInfo("Enabling successful!"); + else + RTMsgError("enabling failed: %hrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return SUCCEEDED(hr) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + +static void printUsage(void) +{ + RTPrintf("host-only network adapter configuration tool\n" + " Usage: %s [cmd]\n" + " cmd can be one of the following values:\n" + " i - install a new host-only interface (default command)\n" + " u - uninstall all host-only interfaces\n" + " a - update the host-only driver\n" + " d - disable all host-only interfaces\n" + " e - enable all host-only interfaces\n" + " h - print this message\n", + RTProcShortName()); +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + if (argc < 2) + return VBoxNetAdpInstall(); + if (argc > 2) + { + printUsage(); + return RTEXITCODE_SYNTAX; + } + + if (!strcmp(argv[1], "i")) + return VBoxNetAdpInstall(); + if (!strcmp(argv[1], "u")) + return VBoxNetAdpUninstall(); + if (!strcmp(argv[1], "a")) + return VBoxNetAdpUpdate(); + if (!strcmp(argv[1], "d")) + return VBoxNetAdpDisable(); + if (!strcmp(argv[1], "e")) + return VBoxNetAdpEnable(); + + printUsage(); + return !strcmp(argv[1], "h") ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX; +} diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp new file mode 100644 index 00000000..03508669 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetAdpUninstall.cpp @@ -0,0 +1,107 @@ +/* $Id: VBoxNetAdpUninstall.cpp $ */ +/** @file + * NetAdpUninstall - VBoxNetAdp uninstaller command line tool + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include + +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#ifdef NDIS60 +# define VBOX_NETADP_HWID L"sun_VBoxNetAdp6" +#else +# define VBOX_NETADP_HWID L"sun_VBoxNetAdp" +#endif + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +static int VBoxNetAdpUninstall(void) +{ + RTMsgInfo("Uninstalling all Host-Only interfaces ..."); + + int rcExit = RTEXITCODE_FAILURE; + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + hr = VBoxNetCfgWinRemoveAllNetDevicesOfId(VBOX_NETADP_HWID); + if (hr == S_OK) + { + hr = VBoxDrvCfgInfUninstallAllSetupDi(&GUID_DEVCLASS_NET, L"Net", VBOX_NETADP_HWID, 0/* could be SUOI_FORCEDELETE */); + if (hr == S_OK) + RTMsgInfo("Uninstalled successfully!"); + else + RTMsgError("uninstalled successfully, but failed to remove infs (%Rhrc)\n", hr); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("uninstall failed: %Rhrc", hr); + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetAdpUninstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp new file mode 100644 index 00000000..e6c4f2de --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltInstall.cpp @@ -0,0 +1,201 @@ +/* $Id: VBoxNetFltInstall.cpp $ */ +/** @file + * NetFltInstall - VBoxNetFlt installer command line tool + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define NETFLT_ID L"sun_VBoxNetFlt" +#define VBOX_NETCFG_APP_NAME L"NetFltInstall" +#define VBOX_NETFLT_PT_INF L".\\VBoxNetFlt.inf" +#define VBOX_NETFLT_MP_INF L".\\VBoxNetFltM.inf" +#define VBOX_NETFLT_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + printf("%s", pszString); +} + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < RT_ELEMENTS(wsz)) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + +static int VBoxNetFltInstall() +{ + WCHAR wszPtInf[MAX_PATH]; + WCHAR wszMpInf[MAX_PATH]; + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + DWORD dwSize; + dwSize = MyGetfullPathNameW(VBOX_NETFLT_PT_INF, RT_ELEMENTS(wszPtInf), wszPtInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszPtInf) == dwSize (string length in WCHARs) */ + + dwSize = MyGetfullPathNameW(VBOX_NETFLT_MP_INF, RT_ELEMENTS(wszMpInf), wszMpInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszMpInf) == dwSize (string length in WHCARs) */ + + LPCWSTR apwszInfs[] = { wszPtInf, wszMpInf }; + hr = VBoxNetCfgWinNetFltInstall(pnc, apwszInfs, 2); + if (hr == S_OK) + { + wprintf(L"installed successfully\n"); + rcExit = RTEXITCODE_SUCCESS; + } + else + wprintf(L"error installing VBoxNetFlt (%#lx)\n", hr); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + wprintf(L"error getting full inf path for VBoxNetFltM.inf (%#lx)\n", hr); + } + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + wprintf(L"error getting full inf path for VBoxNetFlt.inf (%#lx)\n", hr); + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETFLT_RETRIES && !wcscmp(pwszLockedBy, L"6to4svc.dll")) + { + wprintf(L"6to4svc.dll is holding the lock, retrying %d out of %d\n", i + 1, VBOX_NETFLT_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + wprintf(L"Error: write lock is owned by another application (%s), close the application and retry installing\n", pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + wprintf(L"Error getting the INetCfg interface (%#lx)\n", hr); + break; + } + } + + CoUninitialize(); + } + else + wprintf(L"Error initializing COM (%#lx)\n", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetFltInstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp new file mode 100644 index 00000000..c62d7726 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetFltUninstall.cpp @@ -0,0 +1,133 @@ +/* $Id: VBoxNetFltUninstall.cpp $ */ +/** @file + * NetFltUninstall - VBoxNetFlt uninstaller command line tool + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define NETFLT_ID L"sun_VBoxNetFlt" +#define VBOX_NETCFG_APP_NAME L"NetFltUninstall" +#define VBOX_NETFLT_PT_INF L".\\VBoxNetFlt.inf" +#define VBOX_NETFLT_MP_INF L".\\VBoxNetFltM.inf" +#define VBOX_NETFLT_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + printf("%s", pszString); +} + +static int VBoxNetFltUninstall() +{ + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + hr = VBoxNetCfgWinNetFltUninstall(pnc); + if (hr != S_OK && hr != S_FALSE) + wprintf(L"error uninstalling VBoxNetFlt (%#lx)\n", hr); + else + { + wprintf(L"uninstalled successfully\n"); + rcExit = RTEXITCODE_SUCCESS; + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETFLT_RETRIES && !wcscmp(pwszLockedBy, L"6to4svc.dll")) + { + wprintf(L"6to4svc.dll is holding the lock, retrying %d out of %d\n", i + 1, VBOX_NETFLT_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + wprintf(L"Error: write lock is owned by another application (%s), close the application and retry uninstalling\n", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + wprintf(L"Error getting the INetCfg interface (%#lx)\n", hr); + break; + } + } + + CoUninitialize(); + } + else + wprintf(L"Error initializing COM (%#lx)\n", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetFltUninstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp new file mode 100644 index 00000000..1b8cf693 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfInstall.cpp @@ -0,0 +1,186 @@ +/* $Id: VBoxNetLwfInstall.cpp $ */ +/** @file + * NetLwfInstall - VBoxNetLwf installer command line tool + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETCFG_APP_NAME L"NetLwfInstall" +#define VBOX_NETLWF_INF L".\\VBoxNetLwf.inf" +#define VBOX_NETLWF_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +/** Wrapper around GetfullPathNameW that will try an alternative INF location. + * + * The default location is the current directory. If not found there, the + * alternative location is the executable directory. If not found there either, + * the first alternative is present to the caller. + */ +static DWORD MyGetfullPathNameW(LPCWSTR pwszName, size_t cchFull, LPWSTR pwszFull) +{ + LPWSTR pwszFilePart; + DWORD dwSize = GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, &pwszFilePart); + if (dwSize <= 0) + return dwSize; + + /* if it doesn't exist, see if the file exists in the same directory as the executable. */ + if (GetFileAttributesW(pwszFull) == INVALID_FILE_ATTRIBUTES) + { + WCHAR wsz[512]; + DWORD cch = GetModuleFileNameW(GetModuleHandle(NULL), &wsz[0], RT_ELEMENTS(wsz)); + if (cch > 0) + { + while (cch > 0 && wsz[cch - 1] != '/' && wsz[cch - 1] != '\\' && wsz[cch - 1] != ':') + cch--; + unsigned i = 0; + while (cch < RT_ELEMENTS(wsz)) + { + wsz[cch] = pwszFilePart[i++]; + if (!wsz[cch]) + { + dwSize = GetFullPathNameW(wsz, (DWORD)cchFull, pwszFull, NULL); + if (dwSize > 0 && GetFileAttributesW(pwszFull) != INVALID_FILE_ATTRIBUTES) + return dwSize; + break; + } + cch++; + } + } + } + + /* fallback */ + return GetFullPathNameW(pwszName, (DWORD)cchFull, pwszFull, NULL); +} + +static int VBoxNetLwfInstall() +{ + WCHAR wszInf[MAX_PATH]; + INetCfg *pnc; + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + DWORD dwSize; + dwSize = MyGetfullPathNameW(VBOX_NETLWF_INF, RT_ELEMENTS(wszInf), wszInf); + if (dwSize > 0) + { + /** @todo add size check for (RT_ELEMENTS(wszInf) == dwSize (string length in WCHARs) */ + hr = VBoxNetCfgWinNetLwfInstall(pnc, wszInf); + if (hr == S_OK) + { + RTMsgInfo("Installed successfully!"); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("Failed installing VBoxNetLwf: %Rhrc", hr); + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + RTMsgError("Failed getting full inf path for VBoxNetLwf.inf: %Rhrc", hr); + } + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETLWF_RETRIES && RTUtf16ICmpAscii(pwszLockedBy, "6to4svc.dll") == 0) + { + RTMsgInfo("6to4svc.dll is holding the lock - retrying %d out of %d\n", i + 1, VBOX_NETLWF_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + RTMsgError("write lock is owned by another application (%ls), close the application and retry installing", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + RTMsgError("Failed getting the INetCfg interface: %Rhrc", hr); + break; + } + } + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetLwfInstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp new file mode 100644 index 00000000..0648a127 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxNetFlt/win/tools/VBoxNetLwfUninstall.cpp @@ -0,0 +1,130 @@ +/* $Id: VBoxNetLwfUninstall.cpp $ */ +/** @file + * NetLwfUninstall - VBoxNetLwf uninstaller command line tool + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOX_NETCFG_APP_NAME L"NetLwfUninstall" +#define VBOX_NETLWF_RETRIES 10 + + +static DECLCALLBACK(void) winNetCfgLogger(const char *pszString) +{ + RTMsgInfo("%s", pszString); +} + +static int VBoxNetLwfUninstall() +{ + int rcExit = RTEXITCODE_FAILURE; + + VBoxNetCfgWinSetLogging(winNetCfgLogger); + + HRESULT hr = CoInitialize(NULL); + if (hr == S_OK) + { + for (int i = 0;; i++) + { + LPWSTR pwszLockedBy = NULL; + INetCfg *pnc = NULL; + hr = VBoxNetCfgWinQueryINetCfg(&pnc, TRUE, VBOX_NETCFG_APP_NAME, 10000, &pwszLockedBy); + if (hr == S_OK) + { + hr = VBoxNetCfgWinNetLwfUninstall(pnc); + if (hr == S_OK) + { + RTMsgInfo("uninstalled successfully!"); + rcExit = RTEXITCODE_SUCCESS; + } + else + RTMsgError("error uninstalling VBoxNetLwf: %Rhrc"); + + VBoxNetCfgWinReleaseINetCfg(pnc, TRUE); + break; + } + + if (hr == NETCFG_E_NO_WRITE_LOCK && pwszLockedBy) + { + if (i < VBOX_NETLWF_RETRIES && RTUtf16ICmpAscii(pwszLockedBy, "6to4svc.dll") == 0) + { + RTMsgInfo("6to4svc.dll is holding the lock - retry %d out of %d ...", i + 1, VBOX_NETLWF_RETRIES); + CoTaskMemFree(pwszLockedBy); + } + else + { + RTMsgError("Write lock is owned by another application (%ls), close the application and retry uninstalling", + pwszLockedBy); + CoTaskMemFree(pwszLockedBy); + break; + } + } + else + { + RTMsgError("Failed getting the INetCfg interface: %Rhrc", hr); + break; + } + } + + CoUninitialize(); + } + else + RTMsgError("Failed initializing COM: %Rhrc", hr); + + VBoxNetCfgWinSetLogging(NULL); + + return rcExit; +} + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + + return VBoxNetLwfUninstall(); +} + diff --git a/src/VBox/HostDrivers/VBoxPci/Makefile.kmk b/src/VBox/HostDrivers/VBoxPci/Makefile.kmk new file mode 100644 index 00000000..736824d3 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/Makefile.kmk @@ -0,0 +1,93 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the PCI passthru driver (VBoxPci). +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk +if1of ($(KBUILD_TARGET), linux) + + ifdef VBOX_WITH_VBOXDRV + # + # The driver. + # + SYSMODS += VBoxPci + VBoxPci_TEMPLATE = VBoxR0Drv + VBoxPci_INST = $(INST_VBOXPCI)$(if $(eq $(KBUILD_TARGET),darwin),Contents/MacOS/) + VBoxPci_NAME.linux = vboxpci + VBoxPci_DEFS = IN_RT_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) IN_SUP_STATIC + VBoxPci_INCS = \ + . + VBoxPci_SOURCES = \ + VBoxPci.c + VBoxPci_LIBS += \ + $(PATH_STAGE_LIB)/SUPR0IdcClient$(VBOX_SUFF_LIB) + endif + + # + # Install the sources. + # + include $(PATH_SUB_CURRENT)/linux/files_vboxpci + INSTALLS += VBoxPci-src + VBoxPci-src_INST = bin/src/vboxpci/ + VBoxPci-src_SOURCES = \ + $(subst $(DQUOTE),,$(VBOX_VBOXPCI_SOURCES)) \ + $(VBoxPci-src_0_OUTDIR)/Makefile + VBoxPci-src_CLEAN = \ + $(VBoxPci-src_0_OUTDIR)/Makefile \ + $(PATH_TARGET)/VBoxPciSrc-src-1.dep + + # Generate the scripts needed for building the kernel module. + + includedep $(PATH_TARGET)/VBoxPci-src-1.dep + $$(VBoxPci-src_0_OUTDIR)/Makefile: \ + $(PATH_SUB_CURRENT)/linux/Makefile \ + $$(if $$(eq $$(VBoxPci/linux/Makefile_VBOX_HARDENED),$$(VBOX_WITH_HARDENING)),,FORCE) \ + | $$(dir $$@) + $(QUIET)$(RM) -f -- $@ + ifndef VBOX_WITH_HARDENING + $(QUIET)$(SED) -e "s;VBOX_WITH_HARDENING;;g" --output $@ $< + else + $(QUIET)$(CP) -f $< $@ + endif + %$(QUIET2)$(APPEND) -t '$(PATH_TARGET)/VBoxPci-src-1.dep' 'VBoxPci/linux/Makefile_VBOX_HARDENED=$(VBOX_WITH_HARDENING)' + + # + # Build test for the linux host kernel modules. + # + $(evalcall2 VBOX_LINUX_KMOD_TEST_BUILD_RULE_FN,VBoxPci-src,vboxdrv-src,) + +endif # Supported platform. +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxPci/VBoxPci.c b/src/VBox/HostDrivers/VBoxPci/VBoxPci.c new file mode 100644 index 00000000..d4e756a4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/VBoxPci.c @@ -0,0 +1,800 @@ +/* $Id: VBoxPci.c $ */ +/** @file + * VBoxPci - PCI card passthrough support (Host), Common Code. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @page pg_rawpci VBoxPci - host PCI support + * + * This is a kernel module that works as host proxy between guest and + * PCI hardware. + * + */ + +#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "VBoxPciInternal.h" + + +#define DEVPORT_2_VBOXRAWPCIINS(pPort) \ + ( (PVBOXRAWPCIINS)((uint8_t *)pPort - RT_OFFSETOF(VBOXRAWPCIINS, DevPort)) ) + + +/** + * Implements the SUPDRV component factor interface query method. + * + * @returns Pointer to an interface. NULL if not supported. + * + * @param pSupDrvFactory Pointer to the component factory registration structure. + * @param pSession The session - unused. + * @param pszInterfaceUuid The factory interface id. + */ +static DECLCALLBACK(void *) vboxPciQueryFactoryInterface(PCSUPDRVFACTORY pSupDrvFactory, PSUPDRVSESSION pSession, const char *pszInterfaceUuid) +{ + PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pSupDrvFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, SupDrvFactory)); + + /* + * Convert the UUID strings and compare them. + */ + RTUUID UuidReq; + int rc = RTUuidFromStr(&UuidReq, pszInterfaceUuid); + if (RT_SUCCESS(rc)) + { + if (!RTUuidCompareStr(&UuidReq, RAWPCIFACTORY_UUID_STR)) + { + ASMAtomicIncS32(&pGlobals->cFactoryRefs); + return &pGlobals->RawPciFactory; + } + } + else + Log(("VBoxRawPci: rc=%Rrc, uuid=%s\n", rc, pszInterfaceUuid)); + + return NULL; +} +DECLINLINE(int) vboxPciDevLock(PVBOXRAWPCIINS pThis) +{ +#ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS + RTSpinlockAcquire(pThis->hSpinlock); + return VINF_SUCCESS; +#else + int rc = RTSemFastMutexRequest(pThis->hFastMtx); + + AssertRC(rc); + return rc; +#endif +} + +DECLINLINE(void) vboxPciDevUnlock(PVBOXRAWPCIINS pThis) +{ +#ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS + RTSpinlockRelease(pThis->hSpinlock); +#else + RTSemFastMutexRelease(pThis->hFastMtx); +#endif +} + +DECLINLINE(int) vboxPciVmLock(PVBOXRAWPCIDRVVM pThis) +{ + int rc = RTSemFastMutexRequest(pThis->hFastMtx); + AssertRC(rc); + return rc; +} + +DECLINLINE(void) vboxPciVmUnlock(PVBOXRAWPCIDRVVM pThis) +{ + RTSemFastMutexRelease(pThis->hFastMtx); +} + +DECLINLINE(int) vboxPciGlobalsLock(PVBOXRAWPCIGLOBALS pGlobals) +{ + int rc = RTSemFastMutexRequest(pGlobals->hFastMtx); + AssertRC(rc); + return rc; +} + +DECLINLINE(void) vboxPciGlobalsUnlock(PVBOXRAWPCIGLOBALS pGlobals) +{ + RTSemFastMutexRelease(pGlobals->hFastMtx); +} + +static PVBOXRAWPCIINS vboxPciFindInstanceLocked(PVBOXRAWPCIGLOBALS pGlobals, uint32_t iHostAddress) +{ + PVBOXRAWPCIINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur != NULL; pCur = pCur->pNext) + { + if (iHostAddress == pCur->HostPciAddress) + return pCur; + } + return NULL; +} + +static void vboxPciUnlinkInstanceLocked(PVBOXRAWPCIGLOBALS pGlobals, PVBOXRAWPCIINS pToUnlink) +{ + if (pGlobals->pInstanceHead == pToUnlink) + pGlobals->pInstanceHead = pToUnlink->pNext; + else + { + PVBOXRAWPCIINS pCur; + for (pCur = pGlobals->pInstanceHead; pCur != NULL; pCur = pCur->pNext) + { + if (pCur->pNext == pToUnlink) + { + pCur->pNext = pToUnlink->pNext; + break; + } + } + } + pToUnlink->pNext = NULL; +} + + +#if 0 /** @todo r=bird: Who the heck is supposed to call this?!? */ +DECLHIDDEN(void) vboxPciDevCleanup(PVBOXRAWPCIINS pThis) +{ + pThis->DevPort.pfnDeinit(&pThis->DevPort, 0); + + if (pThis->hFastMtx) + { + RTSemFastMutexDestroy(pThis->hFastMtx); + pThis->hFastMtx = NIL_RTSEMFASTMUTEX; + } + + if (pThis->hSpinlock) + { + RTSpinlockDestroy(pThis->hSpinlock); + pThis->hSpinlock = NIL_RTSPINLOCK; + } + + vboxPciGlobalsLock(pThis->pGlobals); + vboxPciUnlinkInstanceLocked(pThis->pGlobals, pThis); + vboxPciGlobalsUnlock(pThis->pGlobals); +} +#endif + + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnInit} + */ +static DECLCALLBACK(int) vboxPciDevInit(PRAWPCIDEVPORT pPort, uint32_t fFlags) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevInit(pThis, fFlags); + + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnDeinit} + */ +static DECLCALLBACK(int) vboxPciDevDeinit(PRAWPCIDEVPORT pPort, uint32_t fFlags) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + if (pThis->IrqHandler.pfnIrqHandler) + { + vboxPciOsDevUnregisterIrqHandler(pThis, pThis->IrqHandler.iHostIrq); + pThis->IrqHandler.iHostIrq = 0; + pThis->IrqHandler.pfnIrqHandler = NULL; + } + + rc = vboxPciOsDevDeinit(pThis, fFlags); + + vboxPciDevUnlock(pThis); + + return rc; +} + + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnDestroy} + */ +static DECLCALLBACK(int) vboxPciDevDestroy(PRAWPCIDEVPORT pPort) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + rc = vboxPciOsDevDestroy(pThis); + if (rc == VINF_SUCCESS) + { + if (pThis->hFastMtx) + { + RTSemFastMutexDestroy(pThis->hFastMtx); + pThis->hFastMtx = NIL_RTSEMFASTMUTEX; + } + + if (pThis->hSpinlock) + { + RTSpinlockDestroy(pThis->hSpinlock); + pThis->hSpinlock = NIL_RTSPINLOCK; + } + + vboxPciGlobalsLock(pThis->pGlobals); + vboxPciUnlinkInstanceLocked(pThis->pGlobals, pThis); + vboxPciGlobalsUnlock(pThis->pGlobals); + + RTMemFree(pThis); + } + + return rc; +} +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnGetRegionInfo} + */ +static DECLCALLBACK(int) vboxPciDevGetRegionInfo(PRAWPCIDEVPORT pPort, + int32_t iRegion, + RTHCPHYS *pRegionStart, + uint64_t *pu64RegionSize, + bool *pfPresent, + uint32_t *pfFlags) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevGetRegionInfo(pThis, iRegion, + pRegionStart, pu64RegionSize, + pfPresent, pfFlags); + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnMapRegion} + */ +static DECLCALLBACK(int) vboxPciDevMapRegion(PRAWPCIDEVPORT pPort, + int32_t iRegion, + RTHCPHYS RegionStart, + uint64_t u64RegionSize, + int32_t fFlags, + RTR0PTR *pRegionBaseR0) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevMapRegion(pThis, iRegion, RegionStart, u64RegionSize, fFlags, pRegionBaseR0); + + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnUnmapRegion} + */ +static DECLCALLBACK(int) vboxPciDevUnmapRegion(PRAWPCIDEVPORT pPort, + int32_t iRegion, + RTHCPHYS RegionStart, + uint64_t u64RegionSize, + RTR0PTR RegionBase) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevUnmapRegion(pThis, iRegion, RegionStart, u64RegionSize, RegionBase); + + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnPciCfgRead} + */ +static DECLCALLBACK(int) vboxPciDevPciCfgRead(PRAWPCIDEVPORT pPort, + uint32_t Register, + PCIRAWMEMLOC *pValue) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevPciCfgRead(pThis, Register, pValue); + + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIDEVPORT,pfnPciCfgWrite} + */ +static DECLCALLBACK(int) vboxPciDevPciCfgWrite(PRAWPCIDEVPORT pPort, + uint32_t Register, + PCIRAWMEMLOC *pValue) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevPciCfgWrite(pThis, Register, pValue); + + vboxPciDevUnlock(pThis); + + return rc; +} + +static DECLCALLBACK(int) vboxPciDevRegisterIrqHandler(PRAWPCIDEVPORT pPort, + PFNRAWPCIISR pfnHandler, + void* pIrqContext, + PCIRAWISRHANDLE *phIsr) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + int32_t iHostIrq = 0; + + if (pfnHandler == NULL) + return VERR_INVALID_PARAMETER; + + vboxPciDevLock(pThis); + + if (pThis->IrqHandler.pfnIrqHandler) + { + rc = VERR_ALREADY_EXISTS; + } + else + { + rc = vboxPciOsDevRegisterIrqHandler(pThis, pfnHandler, pIrqContext, &iHostIrq); + if (RT_SUCCESS(rc)) + { + *phIsr = 0xcafe0000; + pThis->IrqHandler.iHostIrq = iHostIrq; + pThis->IrqHandler.pfnIrqHandler = pfnHandler; + pThis->IrqHandler.pIrqContext = pIrqContext; + } + } + + vboxPciDevUnlock(pThis); + + return rc; +} + +static DECLCALLBACK(int) vboxPciDevUnregisterIrqHandler(PRAWPCIDEVPORT pPort, + PCIRAWISRHANDLE hIsr) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + if (hIsr != 0xcafe0000) + return VERR_INVALID_PARAMETER; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevUnregisterIrqHandler(pThis, pThis->IrqHandler.iHostIrq); + if (RT_SUCCESS(rc)) + { + pThis->IrqHandler.pfnIrqHandler = NULL; + pThis->IrqHandler.pIrqContext = NULL; + pThis->IrqHandler.iHostIrq = 0; + } + vboxPciDevUnlock(pThis); + + return rc; +} + +static DECLCALLBACK(int) vboxPciDevPowerStateChange(PRAWPCIDEVPORT pPort, + PCIRAWPOWERSTATE aState, + uint64_t *pu64Param) +{ + PVBOXRAWPCIINS pThis = DEVPORT_2_VBOXRAWPCIINS(pPort); + int rc; + + vboxPciDevLock(pThis); + + rc = vboxPciOsDevPowerStateChange(pThis, aState); + + switch (aState) + { + case PCIRAW_POWER_ON: + /* + * Let virtual device know about VM caps. + */ + *pu64Param = VBOX_DRV_VMDATA(pThis)->pPerVmData->fVmCaps; + break; + default: + pu64Param = 0; + break; + } + + + vboxPciDevUnlock(pThis); + + return rc; +} + +/** + * Creates a new instance. + * + * @returns VBox status code. + * @param pGlobals The globals. + * @param u32HostAddress Host address. + * @param fFlags Flags. + * @param pVmCtx VM context. + * @param ppDevPort Where to store the pointer to our port interface. + * @param pfDevFlags The device flags. + */ +static int vboxPciNewInstance(PVBOXRAWPCIGLOBALS pGlobals, + uint32_t u32HostAddress, + uint32_t fFlags, + PRAWPCIPERVM pVmCtx, + PRAWPCIDEVPORT *ppDevPort, + uint32_t *pfDevFlags) +{ + int rc; + PVBOXRAWPCIINS pNew = (PVBOXRAWPCIINS)RTMemAllocZ(sizeof(*pNew)); + if (!pNew) + return VERR_NO_MEMORY; + + pNew->pGlobals = pGlobals; + pNew->hSpinlock = NIL_RTSPINLOCK; + pNew->cRefs = 1; + pNew->pNext = NULL; + pNew->HostPciAddress = u32HostAddress; + pNew->pVmCtx = pVmCtx; + + pNew->DevPort.u32Version = RAWPCIDEVPORT_VERSION; + + pNew->DevPort.pfnInit = vboxPciDevInit; + pNew->DevPort.pfnDeinit = vboxPciDevDeinit; + pNew->DevPort.pfnDestroy = vboxPciDevDestroy; + pNew->DevPort.pfnGetRegionInfo = vboxPciDevGetRegionInfo; + pNew->DevPort.pfnMapRegion = vboxPciDevMapRegion; + pNew->DevPort.pfnUnmapRegion = vboxPciDevUnmapRegion; + pNew->DevPort.pfnPciCfgRead = vboxPciDevPciCfgRead; + pNew->DevPort.pfnPciCfgWrite = vboxPciDevPciCfgWrite; + pNew->DevPort.pfnPciCfgRead = vboxPciDevPciCfgRead; + pNew->DevPort.pfnPciCfgWrite = vboxPciDevPciCfgWrite; + pNew->DevPort.pfnRegisterIrqHandler = vboxPciDevRegisterIrqHandler; + pNew->DevPort.pfnUnregisterIrqHandler = vboxPciDevUnregisterIrqHandler; + pNew->DevPort.pfnPowerStateChange = vboxPciDevPowerStateChange; + pNew->DevPort.u32VersionEnd = RAWPCIDEVPORT_VERSION; + + rc = RTSpinlockCreate(&pNew->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxPCI"); + if (RT_SUCCESS(rc)) + { + rc = RTSemFastMutexCreate(&pNew->hFastMtx); + if (RT_SUCCESS(rc)) + { + rc = pNew->DevPort.pfnInit(&pNew->DevPort, fFlags); + if (RT_SUCCESS(rc)) + { + *ppDevPort = &pNew->DevPort; + + pNew->pNext = pGlobals->pInstanceHead; + pGlobals->pInstanceHead = pNew; + } + else + { + RTSemFastMutexDestroy(pNew->hFastMtx); + RTSpinlockDestroy(pNew->hSpinlock); + RTMemFree(pNew); + } + } + } + + return rc; +} + +/** + * @interface_method_impl{RAWPCIFACTORY,pfnCreateAndConnect} + */ +static DECLCALLBACK(int) vboxPciFactoryCreateAndConnect(PRAWPCIFACTORY pFactory, + uint32_t u32HostAddress, + uint32_t fFlags, + PRAWPCIPERVM pVmCtx, + PRAWPCIDEVPORT *ppDevPort, + uint32_t *pfDevFlags) +{ + PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, RawPciFactory)); + int rc; + + LogFlow(("vboxPciFactoryCreateAndConnect: PCI=%x fFlags=%#x\n", u32HostAddress, fFlags)); + Assert(pGlobals->cFactoryRefs > 0); + rc = vboxPciGlobalsLock(pGlobals); + AssertRCReturn(rc, rc); + + /* First search if there's no existing instance with same host device + * address - if so - we cannot continue. + */ + if (vboxPciFindInstanceLocked(pGlobals, u32HostAddress) != NULL) + { + rc = VERR_RESOURCE_BUSY; + goto unlock; + } + + rc = vboxPciNewInstance(pGlobals, u32HostAddress, fFlags, pVmCtx, ppDevPort, pfDevFlags); + +unlock: + vboxPciGlobalsUnlock(pGlobals); + + return rc; +} + +/** + * @interface_method_impl{RAWPCIFACTORY,pfnRelease} + */ +static DECLCALLBACK(void) vboxPciFactoryRelease(PRAWPCIFACTORY pFactory) +{ + PVBOXRAWPCIGLOBALS pGlobals = (PVBOXRAWPCIGLOBALS)((uint8_t *)pFactory - RT_OFFSETOF(VBOXRAWPCIGLOBALS, RawPciFactory)); + + int32_t cRefs = ASMAtomicDecS32(&pGlobals->cFactoryRefs); + Assert(cRefs >= 0); NOREF(cRefs); + LogFlow(("vboxPciFactoryRelease: cRefs=%d (new)\n", cRefs)); +} + +/** + * @interface_method_impl{RAWPCIFACTORY,pfnInitVm} + */ +static DECLCALLBACK(int) vboxPciFactoryInitVm(PRAWPCIFACTORY pFactory, + PVM pVM, + PRAWPCIPERVM pVmData) +{ + PVBOXRAWPCIDRVVM pThis = (PVBOXRAWPCIDRVVM)RTMemAllocZ(sizeof(VBOXRAWPCIDRVVM)); + int rc; + + if (!pThis) + return VERR_NO_MEMORY; + + rc = RTSemFastMutexCreate(&pThis->hFastMtx); + if (RT_SUCCESS(rc)) + { + rc = vboxPciOsInitVm(pThis, pVM, pVmData); + + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_IOMMU + /* If IOMMU notification routine in pVmData->pfnContigMemInfo + is set - we have functional IOMMU hardware. */ + if (pVmData->pfnContigMemInfo) + pVmData->fVmCaps |= PCIRAW_VMFLAGS_HAS_IOMMU; +#endif + pThis->pPerVmData = pVmData; + pVmData->pDriverData = pThis; + return VINF_SUCCESS; + } + + RTSemFastMutexDestroy(pThis->hFastMtx); + pThis->hFastMtx = NIL_RTSEMFASTMUTEX; + RTMemFree(pThis); + } + + return rc; +} + +/** + * @interface_method_impl{RAWPCIFACTORY,pfnDeinitVm} + */ +static DECLCALLBACK(void) vboxPciFactoryDeinitVm(PRAWPCIFACTORY pFactory, + PVM pVM, + PRAWPCIPERVM pVmData) +{ + if (pVmData->pDriverData) + { + PVBOXRAWPCIDRVVM pThis = (PVBOXRAWPCIDRVVM)pVmData->pDriverData; + +#ifdef VBOX_WITH_IOMMU + /* If we have IOMMU, need to unmap all guest's physical pages from IOMMU on VM termination. */ +#endif + + vboxPciOsDeinitVm(pThis, pVM); + + if (pThis->hFastMtx) + { + RTSemFastMutexDestroy(pThis->hFastMtx); + pThis->hFastMtx = NIL_RTSEMFASTMUTEX; + } + + RTMemFree(pThis); + pVmData->pDriverData = NULL; + } +} + + +static bool vboxPciCanUnload(PVBOXRAWPCIGLOBALS pGlobals) +{ + int rc = vboxPciGlobalsLock(pGlobals); + bool fRc = !pGlobals->pInstanceHead + && pGlobals->cFactoryRefs <= 0; + vboxPciGlobalsUnlock(pGlobals); + AssertRC(rc); + return fRc; +} + + +static int vboxPciInitIdc(PVBOXRAWPCIGLOBALS pGlobals) +{ + int rc; + Assert(!pGlobals->fIDCOpen); + + /* + * Establish a connection to SUPDRV and register our component factory. + */ + rc = SUPR0IdcOpen(&pGlobals->SupDrvIDC, 0 /* iReqVersion = default */, 0 /* iMinVersion = default */, NULL, NULL, NULL); + if (RT_SUCCESS(rc)) + { + rc = SUPR0IdcComponentRegisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + if (RT_SUCCESS(rc)) + { + pGlobals->fIDCOpen = true; + Log(("VBoxRawPci: pSession=%p\n", SUPR0IdcGetSession(&pGlobals->SupDrvIDC))); + return rc; + } + + /* bail out. */ + LogRel(("VBoxRawPci: Failed to register component factory, rc=%Rrc\n", rc)); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + } + + return rc; +} + + +/** + * Try to close the IDC connection to SUPDRV if established. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_WRONG_ORDER if we're busy. + * + * @param pGlobals Pointer to the globals. + */ +static int vboxPciDeleteIdc(PVBOXRAWPCIGLOBALS pGlobals) +{ + int rc; + + Assert(pGlobals->hFastMtx != NIL_RTSEMFASTMUTEX); + + /* + * Check before trying to deregister the factory. + */ + if (!vboxPciCanUnload(pGlobals)) + return VERR_WRONG_ORDER; + + if (!pGlobals->fIDCOpen) + rc = VINF_SUCCESS; + else + { + /* + * Disconnect from SUPDRV. + */ + rc = SUPR0IdcComponentDeregisterFactory(&pGlobals->SupDrvIDC, &pGlobals->SupDrvFactory); + AssertRC(rc); + SUPR0IdcClose(&pGlobals->SupDrvIDC); + pGlobals->fIDCOpen = false; + } + + return rc; +} + + +/** + * Initializes the globals. + * + * @returns VBox status code. + * @param pGlobals Pointer to the globals. + */ +static int vboxPciInitGlobals(PVBOXRAWPCIGLOBALS pGlobals) +{ + /* + * Initialize the common portions of the structure. + */ + int rc = RTSemFastMutexCreate(&pGlobals->hFastMtx); + if (RT_SUCCESS(rc)) + { + pGlobals->pInstanceHead = NULL; + pGlobals->RawPciFactory.pfnRelease = vboxPciFactoryRelease; + pGlobals->RawPciFactory.pfnCreateAndConnect = vboxPciFactoryCreateAndConnect; + pGlobals->RawPciFactory.pfnInitVm = vboxPciFactoryInitVm; + pGlobals->RawPciFactory.pfnDeinitVm = vboxPciFactoryDeinitVm; + memcpy(pGlobals->SupDrvFactory.szName, "VBoxRawPci", sizeof("VBoxRawPci")); + pGlobals->SupDrvFactory.pfnQueryFactoryInterface = vboxPciQueryFactoryInterface; + pGlobals->fIDCOpen = false; + } + return rc; +} + + +/** + * Deletes the globals. + * + * @param pGlobals Pointer to the globals. + */ +static void vboxPciDeleteGlobals(PVBOXRAWPCIGLOBALS pGlobals) +{ + Assert(!pGlobals->fIDCOpen); + + /* + * Release resources. + */ + if (pGlobals->hFastMtx) + { + RTSemFastMutexDestroy(pGlobals->hFastMtx); + pGlobals->hFastMtx = NIL_RTSEMFASTMUTEX; + } +} + + +int vboxPciInit(PVBOXRAWPCIGLOBALS pGlobals) +{ + + /* + * Initialize the common portions of the structure. + */ + int rc = vboxPciInitGlobals(pGlobals); + if (RT_SUCCESS(rc)) + { + rc = vboxPciInitIdc(pGlobals); + if (RT_SUCCESS(rc)) + return rc; + + /* bail out. */ + vboxPciDeleteGlobals(pGlobals); + } + + return rc; +} + +void vboxPciShutdown(PVBOXRAWPCIGLOBALS pGlobals) +{ + int rc = vboxPciDeleteIdc(pGlobals); + if (RT_SUCCESS(rc)) + vboxPciDeleteGlobals(pGlobals); +} + diff --git a/src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h b/src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h new file mode 100644 index 00000000..c82cd18d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h @@ -0,0 +1,213 @@ +/* $Id: VBoxPciInternal.h $ */ +/** @file + * VBoxPci - PCI driver (Host), Internal Header. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxPci_VBoxPciInternal_h +#define VBOX_INCLUDED_SRC_VBoxPci_VBoxPciInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include +#include + +#ifdef RT_OS_LINUX + +#if RTLNX_VER_MIN(2,6,35) && defined(CONFIG_IOMMU_API) +# define VBOX_WITH_IOMMU +#endif + +#ifdef VBOX_WITH_IOMMU +#include +#include +#endif + +#endif + +RT_C_DECLS_BEGIN + +/* Forward declaration. */ +typedef struct VBOXRAWPCIGLOBALS *PVBOXRAWPCIGLOBALS; +typedef struct VBOXRAWPCIDRVVM *PVBOXRAWPCIDRVVM; +typedef struct VBOXRAWPCIINS *PVBOXRAWPCIINS; + +typedef struct VBOXRAWPCIISRDESC +{ + /** Handler function. */ + PFNRAWPCIISR pfnIrqHandler; + /** Handler context. */ + void *pIrqContext; + /** Host IRQ. */ + int32_t iHostIrq; +} VBOXRAWPCIISRDESC; +typedef struct VBOXRAWPCIISRDESC *PVBOXRAWPCIISRDESC; + +/** + * The per-instance data of the VBox raw PCI interface. + * + * This is data associated with a host PCI card attached to the VM. + * + */ +typedef struct VBOXRAWPCIINS +{ + /** Pointer to the globals. */ + PVBOXRAWPCIGLOBALS pGlobals; + + /** Mutex protecting device access. */ + RTSEMFASTMUTEX hFastMtx; + /** The spinlock protecting the state variables and device access. */ + RTSPINLOCK hSpinlock; + /** Pointer to the next device in the list. */ + PVBOXRAWPCIINS pNext; + /** Reference count. */ + uint32_t volatile cRefs; + + /* Host PCI address of this device. */ + uint32_t HostPciAddress; + +#ifdef RT_OS_LINUX + struct pci_dev * pPciDev; + char szPrevDriver[64]; +#endif + bool fMsiUsed; + bool fMsixUsed; + bool fIommuUsed; + bool fPad0; + + /** Port, given to the outside world. */ + RAWPCIDEVPORT DevPort; + + /** IRQ handler. */ + VBOXRAWPCIISRDESC IrqHandler; + + /** Pointer to per-VM context in hypervisor data. */ + PRAWPCIPERVM pVmCtx; + + RTR0PTR aRegionR0Mapping[/* XXX: magic */ 7]; +} VBOXRAWPCIINS; + +/** + * Per-VM data of the VBox PCI driver. Pointed to by pGVM->rawpci.s.pDriverData. + * + */ +typedef struct VBOXRAWPCIDRVVM +{ + /** Mutex protecting state changes. */ + RTSEMFASTMUTEX hFastMtx; + +#ifdef RT_OS_LINUX +# ifdef VBOX_WITH_IOMMU + /* IOMMU domain. */ + struct iommu_domain* pIommuDomain; +# endif +#endif + /* Back pointer to pGVM->rawpci.s. */ + PRAWPCIPERVM pPerVmData; +} VBOXRAWPCIDRVVM; + +/** + * The global data of the VBox PCI driver. + * + * This contains the bit required for communicating with support driver, VBoxDrv + * (start out as SupDrv). + */ +typedef struct VBOXRAWPCIGLOBALS +{ + /** Mutex protecting the list of instances and state changes. */ + RTSEMFASTMUTEX hFastMtx; + + /** Pointer to a list of instance data. */ + PVBOXRAWPCIINS pInstanceHead; + + /** The raw PCI interface factory. */ + RAWPCIFACTORY RawPciFactory; + /** The SUPDRV component factory registration. */ + SUPDRVFACTORY SupDrvFactory; + /** The number of current factory references. */ + int32_t volatile cFactoryRefs; + /** Whether the IDC connection is open or not. + * This is only for cleaning up correctly after the separate IDC init on Windows. */ + bool fIDCOpen; + /** The SUPDRV IDC handle (opaque struct). */ + SUPDRVIDCHANDLE SupDrvIDC; +#ifdef RT_OS_LINUX + bool fPciStubModuleAvail; + struct module * pciStubModule; +#endif +} VBOXRAWPCIGLOBALS; + +DECLHIDDEN(int) vboxPciInit(PVBOXRAWPCIGLOBALS pGlobals); +DECLHIDDEN(void) vboxPciShutdown(PVBOXRAWPCIGLOBALS pGlobals); + +DECLHIDDEN(int) vboxPciOsInitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM, PRAWPCIPERVM pVmData); +DECLHIDDEN(void) vboxPciOsDeinitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM); + +DECLHIDDEN(int) vboxPciOsDevInit (PVBOXRAWPCIINS pIns, uint32_t fFlags); +DECLHIDDEN(int) vboxPciOsDevDeinit(PVBOXRAWPCIINS pIns, uint32_t fFlags); +DECLHIDDEN(int) vboxPciOsDevDestroy(PVBOXRAWPCIINS pIns); + +DECLHIDDEN(int) vboxPciOsDevGetRegionInfo(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS *pRegionStart, + uint64_t *pu64RegionSize, + bool *pfPresent, + uint32_t *pfFlags); +DECLHIDDEN(int) vboxPciOsDevMapRegion(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS pRegionStart, + uint64_t u64RegionSize, + uint32_t fFlags, + RTR0PTR *pRegionBase); +DECLHIDDEN(int) vboxPciOsDevUnmapRegion(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS RegionStart, + uint64_t u64RegionSize, + RTR0PTR RegionBase); + +DECLHIDDEN(int) vboxPciOsDevPciCfgWrite(PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue); +DECLHIDDEN(int) vboxPciOsDevPciCfgRead (PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue); + +DECLHIDDEN(int) vboxPciOsDevRegisterIrqHandler (PVBOXRAWPCIINS pIns, PFNRAWPCIISR pfnHandler, void* pIrqContext, int32_t *piHostIrq); +DECLHIDDEN(int) vboxPciOsDevUnregisterIrqHandler(PVBOXRAWPCIINS pIns, int32_t iHostIrq); + +DECLHIDDEN(int) vboxPciOsDevPowerStateChange(PVBOXRAWPCIINS pIns, PCIRAWPOWERSTATE aState); + +#define VBOX_DRV_VMDATA(pIns) ((PVBOXRAWPCIDRVVM)(pIns->pVmCtx ? pIns->pVmCtx->pDriverData : NULL)) + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxPci_VBoxPciInternal_h */ diff --git a/src/VBox/HostDrivers/VBoxPci/linux/Makefile b/src/VBox/HostDrivers/VBoxPci/linux/Makefile new file mode 100644 index 00000000..bbcefe76 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/linux/Makefile @@ -0,0 +1,81 @@ +# $Id: Makefile $ +## @file +# Makefile for the VirtualBox Linux Host PCI Driver. +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + + +# Linux kbuild sets this to our source directory if we are called from there +obj ?= $(CURDIR) +include $(obj)/Makefile-header.gmk +VBOXPCI_DIR := $(VBOX_MODULE_SRC_DIR) + +# Allow building directly from the subdirectory without assuming the toplevel +# makefile has done the copying. Not the default use case, but can be handy. +ifndef KBUILD_EXTRA_SYMBOLS +KBUILD_EXTRA_SYMBOLS=$(abspath $(VBOXPCI_DIR)/../vboxdrv/Module.symvers) +endif + +# override is required by the Debian guys +VBOXMOD_NAME = vboxpci +VBOXMOD_OBJS = \ + linux/VBoxPci-linux.o \ + VBoxPci.o \ + SUPR0IdcClient.o \ + SUPR0IdcClientComponent.o \ + linux/SUPR0IdcClient-linux.o +ifeq ($(VBOX_KBUILD_TARGET_ARCH),x86) +VBOXMOD_OBJS += \ + math/gcc/divdi3.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 +VBOXMOD_INCL = \ + $(VBOXPCI_DIR) \ + $(VBOXPCI_DIR)include \ + $(VBOXPCI_DIR)r0drv/linux +VBOXMOD_DEFS = \ + RT_OS_LINUX \ + IN_RING0 \ + IN_RT_R0 \ + IN_SUP_R0 \ + VBOX \ + RT_WITH_VBOX \ + VBOX_WITH_HARDENING +VBOXMOD_CFLAGS = -include $(VBOXPCI_DIR)include/VBox/SUPDrvMangling.h -fno-pie + +include $(obj)/Makefile-footer.gmk diff --git a/src/VBox/HostDrivers/VBoxPci/linux/Makefile.kup b/src/VBox/HostDrivers/VBoxPci/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c b/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c new file mode 100644 index 00000000..36685dd9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c @@ -0,0 +1,1186 @@ +/* $Id: VBoxPci-linux.c $ */ +/** @file + * VBoxPci - PCI Driver (Host), Linux Specific Code. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "the-linux-kernel.h" +#include "version-generated.h" +#include "revision-generated.h" +#include "product-generated.h" + +#define LOG_GROUP LOG_GROUP_DEV_PCI_RAW +#include +#include +#include +#include +#include +#include + +#include "../VBoxPciInternal.h" + +#ifdef VBOX_WITH_IOMMU +# include +# include +# include +# if RTLNX_VER_MAX(3,1,0) && \ + (RTLNX_VER_MAX(2,6,41) || RTLNX_VER_MIN(3,0,0)) +# include +# else +# include +# endif +# if RTLNX_VER_MAX(3,2,0) +# define IOMMU_PRESENT() iommu_found() +# define IOMMU_DOMAIN_ALLOC() iommu_domain_alloc() +# else +# define IOMMU_PRESENT() iommu_present(&pci_bus_type) +# define IOMMU_DOMAIN_ALLOC() iommu_domain_alloc(&pci_bus_type) +# endif +#endif /* VBOX_WITH_IOMMU */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int __init VBoxPciLinuxInit(void); +static void __exit VBoxPciLinuxUnload(void); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static VBOXRAWPCIGLOBALS g_VBoxPciGlobals; + +module_init(VBoxPciLinuxInit); +module_exit(VBoxPciLinuxUnload); + +MODULE_AUTHOR(VBOX_VENDOR); +MODULE_DESCRIPTION(VBOX_PRODUCT " PCI access Driver"); +MODULE_LICENSE("GPL"); +#ifdef MODULE_VERSION +MODULE_VERSION(VBOX_VERSION_STRING " r" RT_XSTR(VBOX_SVN_REV)); +#endif + + +#if RTLNX_VER_MIN(2,6,20) +# define PCI_DEV_GET(v,d,p) pci_get_device(v,d,p) +# define PCI_DEV_PUT(x) pci_dev_put(x) +#if RTLNX_VER_MIN(4,17,0) +/* assume the domain number to be zero - exactly the same assumption of + * pci_get_bus_and_slot() + */ +# define PCI_DEV_GET_SLOT(bus, devfn) pci_get_domain_bus_and_slot(0, bus, devfn) +#else +# define PCI_DEV_GET_SLOT(bus, devfn) pci_get_bus_and_slot(bus, devfn) +#endif +#else +# define PCI_DEV_GET(v,d,p) pci_find_device(v,d,p) +# define PCI_DEV_PUT(x) do { } while (0) +# define PCI_DEV_GET_SLOT(bus, devfn) pci_find_slot(bus, devfn) +#endif + +/** + * Name of module used to attach to the host PCI device, when + * PCI device passthrough is used. + */ +#define PCI_STUB_MODULE "pci-stub" +/* For some reasons my kernel names module for find_module() this way, + * while device name seems to be above one. + */ +#define PCI_STUB_MODULE_NAME "pci_stub" + +/** + * Our driver name. + */ +#define DRIVER_NAME "vboxpci" + +/* + * Currently we keep the device bound to pci stub driver, so + * dev_printk() &co would report that instead of our name. They also + * expect non-NULL dev pointer in older kernels. + */ +#define vbpci_printk(level, pdev, format, arg...) \ + printk(level DRIVER_NAME "%s%s: " format, \ + pdev ? " " : "", pdev ? pci_name(pdev) : "", \ + ## arg) + + +/** + * Initialize module. + * + * @returns appropriate status code. + */ +static int __init VBoxPciLinuxInit(void) +{ + int rc; + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + + if (RT_FAILURE(rc)) + goto error; + + + LogRel(("VBoxPciLinuxInit\n")); + + RT_ZERO(g_VBoxPciGlobals); + + rc = vboxPciInit(&g_VBoxPciGlobals); + if (RT_FAILURE(rc)) + { + LogRel(("cannot do VBoxPciInit: %Rc\n", rc)); + goto error; + } + +#if defined(CONFIG_PCI_STUB) + /* nothing to do, pci_stub module part of the kernel */ + g_VBoxPciGlobals.fPciStubModuleAvail = true; + +#elif defined(CONFIG_PCI_STUB_MODULE) + if (request_module(PCI_STUB_MODULE) == 0) + { +# if RTLNX_VER_MIN(2,6,30) + /* find_module() is static before Linux 2.6.30 */ + mutex_lock(&module_mutex); + g_VBoxPciGlobals.pciStubModule = find_module(PCI_STUB_MODULE_NAME); + mutex_unlock(&module_mutex); + if (g_VBoxPciGlobals.pciStubModule) + { + if (try_module_get(g_VBoxPciGlobals.pciStubModule)) + g_VBoxPciGlobals.fPciStubModuleAvail = true; + } + else + printk(KERN_INFO "vboxpci: find_module %s failed\n", PCI_STUB_MODULE); +# endif + } + else + printk(KERN_INFO "vboxpci: cannot load %s\n", PCI_STUB_MODULE); + +#else + printk(KERN_INFO "vboxpci: %s module not available, cannot detach PCI devices\n", + PCI_STUB_MODULE); +#endif + +#ifdef VBOX_WITH_IOMMU + if (IOMMU_PRESENT()) + printk(KERN_INFO "vboxpci: IOMMU found\n"); + else + printk(KERN_INFO "vboxpci: IOMMU not found (not registered)\n"); +#else + printk(KERN_INFO "vboxpci: IOMMU not found (not compiled)\n"); +#endif + + return 0; + + error: + return -RTErrConvertToErrno(rc); +} + +/** + * Unload the module. + */ +static void __exit VBoxPciLinuxUnload(void) +{ + LogRel(("VBoxPciLinuxLinuxUnload\n")); + + /* + * Undo the work done during start (in reverse order). + */ + vboxPciShutdown(&g_VBoxPciGlobals); + + RTR0Term(); + + if (g_VBoxPciGlobals.pciStubModule) + { + module_put(g_VBoxPciGlobals.pciStubModule); + g_VBoxPciGlobals.pciStubModule = NULL; + } + + Log(("VBoxPciLinuxUnload - done\n")); +} + +static int vboxPciLinuxDevRegisterWithIommu(PVBOXRAWPCIINS pIns) +{ +#ifdef VBOX_WITH_IOMMU + int rc = VINF_SUCCESS; + struct pci_dev *pPciDev = pIns->pPciDev; + PVBOXRAWPCIDRVVM pData = VBOX_DRV_VMDATA(pIns); + IPRT_LINUX_SAVE_EFL_AC(); + + if (RT_LIKELY(pData)) + { + if (RT_LIKELY(pData->pIommuDomain)) + { + /** @todo KVM checks IOMMU_CAP_CACHE_COHERENCY and sets + * flag IOMMU_CACHE later used when mapping physical + * addresses, which could improve performance. + */ + int rcLnx = iommu_attach_device(pData->pIommuDomain, &pPciDev->dev); + if (!rcLnx) + { + vbpci_printk(KERN_DEBUG, pPciDev, "attached to IOMMU\n"); + pIns->fIommuUsed = true; + rc = VINF_SUCCESS; + } + else + { + vbpci_printk(KERN_DEBUG, pPciDev, "failed to attach to IOMMU, error %d\n", rcLnx); + rc = VERR_INTERNAL_ERROR; + } + } + else + { + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "cannot attach to IOMMU, no domain\n"); + rc = VERR_NOT_FOUND; + } + } + else + { + vbpci_printk(KERN_DEBUG, pPciDev, "cannot attach to IOMMU, no VM data\n"); + rc = VERR_INVALID_PARAMETER; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +#else + return VERR_NOT_SUPPORTED; +#endif +} + +static int vboxPciLinuxDevUnregisterWithIommu(PVBOXRAWPCIINS pIns) +{ +#ifdef VBOX_WITH_IOMMU + int rc = VINF_SUCCESS; + struct pci_dev *pPciDev = pIns->pPciDev; + PVBOXRAWPCIDRVVM pData = VBOX_DRV_VMDATA(pIns); + IPRT_LINUX_SAVE_EFL_AC(); + + if (RT_LIKELY(pData)) + { + if (RT_LIKELY(pData->pIommuDomain)) + { + if (pIns->fIommuUsed) + { + iommu_detach_device(pData->pIommuDomain, &pIns->pPciDev->dev); + vbpci_printk(KERN_DEBUG, pPciDev, "detached from IOMMU\n"); + pIns->fIommuUsed = false; + } + } + else + { + vbpci_printk(KERN_DEBUG, pPciDev, + "cannot detach from IOMMU, no domain\n"); + rc = VERR_NOT_FOUND; + } + } + else + { + vbpci_printk(KERN_DEBUG, pPciDev, + "cannot detach from IOMMU, no VM data\n"); + rc = VERR_INVALID_PARAMETER; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +#else + return VERR_NOT_SUPPORTED; +#endif +} + +static int vboxPciLinuxDevReset(PVBOXRAWPCIINS pIns) +{ + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + + if (RT_LIKELY(pIns->pPciDev)) + { +#if RTLNX_VER_MIN(2,6,28) + if (pci_reset_function(pIns->pPciDev)) + { + vbpci_printk(KERN_DEBUG, pIns->pPciDev, + "pci_reset_function() failed\n"); + rc = VERR_INTERNAL_ERROR; + } +#else + rc = VERR_NOT_SUPPORTED; +#endif + } + else + rc = VERR_INVALID_PARAMETER; + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +static struct file* vboxPciFileOpen(const char* path, int flags) +{ + struct file* filp = NULL; + int err = 0; + + filp = filp_open(path, flags, 0); + + if (IS_ERR(filp)) + { + err = PTR_ERR(filp); + printk(KERN_DEBUG "vboxPciFileOpen: error %d\n", err); + return NULL; + } + + if (!filp->f_op || !filp->f_op->write) + { + printk(KERN_DEBUG "Not writable FS\n"); + filp_close(filp, NULL); + return NULL; + } + + return filp; +} + +static void vboxPciFileClose(struct file* file) +{ + filp_close(file, NULL); +} + +static int vboxPciFileWrite(struct file* file, unsigned long long offset, unsigned char* data, unsigned int size) +{ + int ret; + mm_segment_t fs_save; + + fs_save = get_fs(); + set_fs(KERNEL_DS); +#if RTLNX_VER_MIN(4,14,0) + ret = kernel_write(file, data, size, &offset); +#else + ret = vfs_write(file, data, size, &offset); +#endif + set_fs(fs_save); + if (ret < 0) + printk(KERN_DEBUG "vboxPciFileWrite: error %d\n", ret); + + return ret; +} + +static int vboxPciLinuxDevDetachHostDriver(PVBOXRAWPCIINS pIns) +{ + struct pci_dev *pPciDev = NULL; + uint8_t uBus = (pIns->HostPciAddress) >> 8; + uint8_t uDevFn = (pIns->HostPciAddress) & 0xff; + const char* currentDriver; + uint16_t uVendor, uDevice; + bool fDetach = 0; + + if (!g_VBoxPciGlobals.fPciStubModuleAvail) + { + printk(KERN_INFO "vboxpci: stub module %s not detected: cannot detach\n", + PCI_STUB_MODULE); + return VERR_ACCESS_DENIED; + } + + pPciDev = PCI_DEV_GET_SLOT(uBus, uDevFn); + + if (!pPciDev) + { + printk(KERN_INFO "vboxpci: device at %02x:%02x.%d not found\n", + uBus, uDevFn>>3, uDevFn&7); + return VERR_NOT_FOUND; + } + + uVendor = pPciDev->vendor; + uDevice = pPciDev->device; + + currentDriver = pPciDev->driver ? pPciDev->driver->name : NULL; + + printk(KERN_DEBUG "vboxpci: detected device: %04x:%04x at %02x:%02x.%d, driver %s\n", + uVendor, uDevice, uBus, uDevFn>>3, uDevFn&7, + currentDriver ? currentDriver : ""); + + fDetach = (currentDriver == NULL || (strcmp(currentDriver, PCI_STUB_MODULE) != 0)); + + /* Init previous driver data. */ + pIns->szPrevDriver[0] = '\0'; + + if (fDetach && currentDriver) + { + /* Dangerous: if device name for some reasons contains slashes - arbitrary file could be written to. */ + if (strchr(currentDriver, '/') != 0) + { + printk(KERN_DEBUG "vboxpci: ERROR: %s contains invalid symbols\n", currentDriver); + return VERR_ACCESS_DENIED; + } + /** @todo RTStrCopy not exported. */ + strncpy(pIns->szPrevDriver, currentDriver, sizeof(pIns->szPrevDriver) - 1); + pIns->szPrevDriver[sizeof(pIns->szPrevDriver) - 1] = '\0'; + } + + PCI_DEV_PUT(pPciDev); + pPciDev = NULL; + + if (fDetach) + { + char* szCmdBuf; + char* szFileBuf; + struct file* pFile; + int iCmdLen; + const int cMaxBuf = 128; +#if RTLNX_VER_MIN(2,6,29) + const struct cred *pOldCreds; + struct cred *pNewCreds; +#endif + + /* + * Now perform kernel analog of: + * + * echo -n "10de 040a" > /sys/bus/pci/drivers/pci-stub/new_id + * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/nvidia/unbind + * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/pci-stub/bind + * + * We do this way, as this interface is presumingly more stable than + * in-kernel ones. + */ + szCmdBuf = kmalloc(cMaxBuf, GFP_KERNEL); + szFileBuf = kmalloc(cMaxBuf, GFP_KERNEL); + if (!szCmdBuf || !szFileBuf) + goto done; + + /* Somewhat ugly hack - override current credentials */ +#if RTLNX_VER_MIN(2,6,29) + pNewCreds = prepare_creds(); + if (!pNewCreds) + goto done; + +# if RTLNX_VER_MIN(3,5,0) + pNewCreds->fsuid = GLOBAL_ROOT_UID; +# else + pNewCreds->fsuid = 0; +# endif + pOldCreds = override_creds(pNewCreds); +#endif + + RTStrPrintf(szFileBuf, cMaxBuf, + "/sys/bus/pci/drivers/%s/new_id", + PCI_STUB_MODULE); + pFile = vboxPciFileOpen(szFileBuf, O_WRONLY); + if (pFile) + { + iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf, + "%04x %04x", + uVendor, uDevice); + /* Don't write trailing \0 */ + vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen); + vboxPciFileClose(pFile); + } + else + printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf); + + iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf, + "0000:%02x:%02x.%d", + uBus, uDevFn>>3, uDevFn&7); + + /* Unbind if bound to smth */ + if (pIns->szPrevDriver[0]) + { + RTStrPrintf(szFileBuf, cMaxBuf, + "/sys/bus/pci/drivers/%s/unbind", + pIns->szPrevDriver); + pFile = vboxPciFileOpen(szFileBuf, O_WRONLY); + if (pFile) + { + + /* Don't write trailing \0 */ + vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen); + vboxPciFileClose(pFile); + } + else + printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf); + } + + RTStrPrintf(szFileBuf, cMaxBuf, + "/sys/bus/pci/drivers/%s/bind", + PCI_STUB_MODULE); + pFile = vboxPciFileOpen(szFileBuf, O_WRONLY); + if (pFile) + { + /* Don't write trailing \0 */ + vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen); + vboxPciFileClose(pFile); + } + else + printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf); + +#if RTLNX_VER_MIN(2,6,29) + revert_creds(pOldCreds); + put_cred(pNewCreds); +#endif + + done: + kfree(szCmdBuf); + kfree(szFileBuf); + } + + return 0; +} + +static int vboxPciLinuxDevReattachHostDriver(PVBOXRAWPCIINS pIns) +{ + struct pci_dev *pPciDev = pIns->pPciDev; + + if (!pPciDev) + return VINF_SUCCESS; + + if (pIns->szPrevDriver[0]) + { + char* szCmdBuf; + char* szFileBuf; + struct file* pFile; + int iCmdLen; + const int cMaxBuf = 128; +#if RTLNX_VER_MIN(2,6,29) + const struct cred *pOldCreds; + struct cred *pNewCreds; +#endif + uint8_t uBus = (pIns->HostPciAddress) >> 8; + uint8_t uDevFn = (pIns->HostPciAddress) & 0xff; + + vbpci_printk(KERN_DEBUG, pPciDev, + "reattaching old host driver %s\n", pIns->szPrevDriver); + /* + * Now perform kernel analog of: + * + * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/pci-stub/unbind + * echo -n 0000:03:00.0 > /sys/bus/pci/drivers/nvidia/bind + */ + szCmdBuf = kmalloc(cMaxBuf, GFP_KERNEL); + szFileBuf = kmalloc(cMaxBuf, GFP_KERNEL); + + if (!szCmdBuf || !szFileBuf) + goto done; + + iCmdLen = RTStrPrintf(szCmdBuf, cMaxBuf, + "0000:%02x:%02x.%d", + uBus, uDevFn>>3, uDevFn&7); + + /* Somewhat ugly hack - override current credentials */ +#if RTLNX_VER_MIN(2,6,29) + pNewCreds = prepare_creds(); + if (!pNewCreds) + goto done; + +# if RTLNX_VER_MIN(3,5,0) + pNewCreds->fsuid = GLOBAL_ROOT_UID; +# else + pNewCreds->fsuid = 0; +# endif + pOldCreds = override_creds(pNewCreds); +#endif + RTStrPrintf(szFileBuf, cMaxBuf, + "/sys/bus/pci/drivers/%s/unbind", + PCI_STUB_MODULE); + pFile = vboxPciFileOpen(szFileBuf, O_WRONLY); + if (pFile) + { + + /* Don't write trailing \0 */ + vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen); + vboxPciFileClose(pFile); + } + else + printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf); + + RTStrPrintf(szFileBuf, cMaxBuf, + "/sys/bus/pci/drivers/%s/bind", + pIns->szPrevDriver); + pFile = vboxPciFileOpen(szFileBuf, O_WRONLY); + if (pFile) + { + + /* Don't write trailing \0 */ + vboxPciFileWrite(pFile, 0, szCmdBuf, iCmdLen); + vboxPciFileClose(pFile); + pIns->szPrevDriver[0] = '\0'; + } + else + printk(KERN_DEBUG "vboxpci: cannot open %s\n", szFileBuf); + +#if RTLNX_VER_MIN(2,6,29) + revert_creds(pOldCreds); + put_cred(pNewCreds); +#endif + + done: + kfree(szCmdBuf); + kfree(szFileBuf); + } + + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxPciOsDevInit(PVBOXRAWPCIINS pIns, uint32_t fFlags) +{ + struct pci_dev *pPciDev = NULL; + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + + if (fFlags & PCIRAWDRIVERRFLAG_DETACH_HOST_DRIVER) + { + rc = vboxPciLinuxDevDetachHostDriver(pIns); + if (RT_FAILURE(rc)) + { + printk(KERN_DEBUG "Cannot detach host driver for device %x: %d\n", + pIns->HostPciAddress, rc); + } + } + + if (RT_SUCCESS(rc)) + { + pPciDev = PCI_DEV_GET_SLOT((pIns->HostPciAddress) >> 8, + (pIns->HostPciAddress) & 0xff); + + if (RT_LIKELY(pPciDev)) + { + int rcLnx = pci_enable_device(pPciDev); + + if (!rcLnx) + { + pIns->pPciDev = pPciDev; + vbpci_printk(KERN_DEBUG, pPciDev, "%s\n", __func__); + +#if RTLNX_VER_MIN(2,6,1) + if (pci_enable_msi(pPciDev) == 0) + pIns->fMsiUsed = true; +#endif + + /** @todo + * pci_enable_msix(pPciDev, entries, nvec) + * + * In fact, if device uses interrupts, and cannot be forced to use MSI or MSI-X + * we have to refuse using it, as we cannot work with shared PCI interrupts (unless we're lucky + * to grab unshared PCI interrupt). + */ + } + else + rc = RTErrConvertFromErrno(RT_ABS(rcLnx)); + } + else + rc = VERR_NOT_FOUND; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +DECLHIDDEN(int) vboxPciOsDevDeinit(PVBOXRAWPCIINS pIns, uint32_t fFlags) +{ + int rc = VINF_SUCCESS; + struct pci_dev *pPciDev = pIns->pPciDev; + IPRT_LINUX_SAVE_EFL_AC(); + + vbpci_printk(KERN_DEBUG, pPciDev, "%s\n", __func__); + + if (RT_LIKELY(pPciDev)) + { + int iRegion; + for (iRegion = 0; iRegion < 7; ++iRegion) + { + if (pIns->aRegionR0Mapping[iRegion]) + { + iounmap(pIns->aRegionR0Mapping[iRegion]); + pIns->aRegionR0Mapping[iRegion] = 0; + pci_release_region(pPciDev, iRegion); + } + } + + vboxPciLinuxDevUnregisterWithIommu(pIns); + +#if RTLNX_VER_MIN(2,6,1) + if (pIns->fMsiUsed) + pci_disable_msi(pPciDev); +#endif + // pci_disable_msix(pPciDev); + pci_disable_device(pPciDev); + vboxPciLinuxDevReattachHostDriver(pIns); + + PCI_DEV_PUT(pPciDev); + pIns->pPciDev = NULL; + } + else + rc = VERR_INVALID_PARAMETER; + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +DECLHIDDEN(int) vboxPciOsDevDestroy(PVBOXRAWPCIINS pIns) +{ + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxPciOsDevGetRegionInfo(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS *pRegionStart, + uint64_t *pu64RegionSize, + bool *pfPresent, + uint32_t *pfFlags) +{ + int rc = VINF_SUCCESS; + struct pci_dev *pPciDev = pIns->pPciDev; + IPRT_LINUX_SAVE_EFL_AC(); + + if (RT_LIKELY(pPciDev)) + { + int fFlags = pci_resource_flags(pPciDev, iRegion); + + if ( ((fFlags & (IORESOURCE_MEM | IORESOURCE_IO)) == 0) + || ((fFlags & IORESOURCE_DISABLED) != 0)) + { + *pfPresent = false; + rc = VERR_INVALID_PARAMETER; + } + else + { + uint32_t fResFlags = 0; + *pfPresent = true; + + if (fFlags & IORESOURCE_MEM) + fResFlags |= PCIRAW_ADDRESS_SPACE_MEM; + + if (fFlags & IORESOURCE_IO) + fResFlags |= PCIRAW_ADDRESS_SPACE_IO; + +#ifdef IORESOURCE_MEM_64 + if (fFlags & IORESOURCE_MEM_64) + fResFlags |= PCIRAW_ADDRESS_SPACE_BAR64; +#endif + + if (fFlags & IORESOURCE_PREFETCH) + fResFlags |= PCIRAW_ADDRESS_SPACE_MEM_PREFETCH; + + *pfFlags = fResFlags; + *pRegionStart = pci_resource_start(pPciDev, iRegion); + *pu64RegionSize = pci_resource_len (pPciDev, iRegion); + + vbpci_printk(KERN_DEBUG, pPciDev, + "region %d: %s %llx+%lld\n", + iRegion, (fFlags & IORESOURCE_MEM) ? "mmio" : "pio", + *pRegionStart, *pu64RegionSize); + } + } + else + { + *pfPresent = false; + rc = VERR_INVALID_PARAMETER; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +DECLHIDDEN(int) vboxPciOsDevMapRegion(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS RegionStart, + uint64_t u64RegionSize, + uint32_t fFlags, + RTR0PTR *pRegionBase) +{ + int rc = VINF_SUCCESS; + struct pci_dev *pPciDev = pIns->pPciDev; + IPRT_LINUX_SAVE_EFL_AC(); + + if (!pPciDev || iRegion < 0 || iRegion > 0) + { + if (pPciDev) + vbpci_printk(KERN_DEBUG, pPciDev, "invalid region %d\n", iRegion); + + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INVALID_PARAMETER; + } + + vbpci_printk(KERN_DEBUG, pPciDev, "reg=%d start=%llx size=%lld\n", + iRegion, RegionStart, u64RegionSize); + + if ( (pci_resource_flags(pPciDev, iRegion) & IORESOURCE_IO) + || RegionStart != pci_resource_start(pPciDev, iRegion) + || u64RegionSize != pci_resource_len(pPciDev, iRegion)) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INVALID_PARAMETER; + } + + /* + * XXX: Current code never calls unmap. To avoid leaking mappings + * only request and map resources once. + */ + if (!pIns->aRegionR0Mapping[iRegion]) + { + int rcLnx; + *pRegionBase = pIns->aRegionR0Mapping[iRegion]; + + rcLnx = pci_request_region(pPciDev, iRegion, "vboxpci"); + if (!rcLnx) + { +#if RTLNX_VER_MIN(2,6,25) + /* + * ioremap() defaults to no caching since the 2.6 kernels. + * ioremap_nocache() has been removed finally in 5.6-rc1. + */ + RTR0PTR R0PtrMapping = ioremap(pci_resource_start(pPciDev, iRegion), + pci_resource_len(pPciDev, iRegion)); +#else /* KERNEL_VERSION < 2.6.25 */ + /* For now no caching, try to optimize later. */ + RTR0PTR R0PtrMapping = ioremap_nocache(pci_resource_start(pPciDev, iRegion), + pci_resource_len(pPciDev, iRegion)); +#endif /* KERNEL_VERSION < 2.6.25 */ + if (R0PtrMapping != NIL_RTR0PTR) + pIns->aRegionR0Mapping[iRegion] = R0PtrMapping; + else + { +#if RTLNX_VER_MIN(2,6,25) + vbpci_printk(KERN_DEBUG, pPciDev, "ioremap() failed\n"); +#else + vbpci_printk(KERN_DEBUG, pPciDev, "ioremap_nocache() failed\n"); +#endif + pci_release_region(pPciDev, iRegion); + rc = VERR_MAP_FAILED; + } + } + else + rc = VERR_RESOURCE_BUSY; + } + + if (RT_SUCCESS(rc)) + *pRegionBase = pIns->aRegionR0Mapping[iRegion]; + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +DECLHIDDEN(int) vboxPciOsDevUnmapRegion(PVBOXRAWPCIINS pIns, + int32_t iRegion, + RTHCPHYS RegionStart, + uint64_t u64RegionSize, + RTR0PTR RegionBase) +{ + /* XXX: Current code never calls unmap. */ + return VERR_NOT_IMPLEMENTED; +} + +DECLHIDDEN(int) vboxPciOsDevPciCfgWrite(PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue) +{ + struct pci_dev *pPciDev = pIns->pPciDev; + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + + if (RT_LIKELY(pPciDev)) + { + switch (pValue->cb) + { + case 1: + pci_write_config_byte(pPciDev, Register, pValue->u.u8); + break; + case 2: + pci_write_config_word(pPciDev, Register, pValue->u.u16); + break; + case 4: + pci_write_config_dword(pPciDev, Register, pValue->u.u32); + break; + } + } + else + rc = VERR_INVALID_PARAMETER; + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} + +DECLHIDDEN(int) vboxPciOsDevPciCfgRead(PVBOXRAWPCIINS pIns, uint32_t Register, PCIRAWMEMLOC *pValue) +{ + struct pci_dev *pPciDev = pIns->pPciDev; + int rc = VINF_SUCCESS; + + if (RT_LIKELY(pPciDev)) + { + IPRT_LINUX_SAVE_EFL_AC(); + + switch (pValue->cb) + { + case 1: + pci_read_config_byte(pPciDev, Register, &pValue->u.u8); + break; + case 2: + pci_read_config_word(pPciDev, Register, &pValue->u.u16); + break; + case 4: + pci_read_config_dword(pPciDev, Register, &pValue->u.u32); + break; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + +/** + * Interrupt service routine. + * + * @returns In 2.6 we indicate whether we've handled the IRQ or not. + * + * @param iIrq The IRQ number. + * @param pvDevId The device ID, a pointer to PVBOXRAWPCIINS. + * @param pRegs Register set. Removed in 2.6.19. + */ +#if RTLNX_VER_MIN(2,6,19) && !defined(DOXYGEN_RUNNING) +static irqreturn_t vboxPciOsIrqHandler(int iIrq, void *pvDevId) +#else +static irqreturn_t vboxPciOsIrqHandler(int iIrq, void *pvDevId, struct pt_regs *pRegs) +#endif +{ + PVBOXRAWPCIINS pIns = (PVBOXRAWPCIINS)pvDevId; + bool fTaken = true; + + if (pIns && pIns->IrqHandler.pfnIrqHandler) + fTaken = pIns->IrqHandler.pfnIrqHandler(pIns->IrqHandler.pIrqContext, iIrq); +#ifndef VBOX_WITH_SHARED_PCI_INTERRUPTS + /* If we don't allow interrupts sharing, we consider all interrupts as non-shared, thus targetted to us. */ + fTaken = true; +#endif + + return fTaken; +} + +DECLHIDDEN(int) vboxPciOsDevRegisterIrqHandler(PVBOXRAWPCIINS pIns, PFNRAWPCIISR pfnHandler, void* pIrqContext, int32_t *piHostIrq) +{ + int rc; + int32_t iIrq = pIns->pPciDev->irq; + IPRT_LINUX_SAVE_EFL_AC(); + + if (iIrq == 0) + { + vbpci_printk(KERN_NOTICE, pIns->pPciDev, "no irq assigned\n"); + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_INVALID_PARAMETER; + } + + rc = request_irq(iIrq, + vboxPciOsIrqHandler, +#ifdef VBOX_WITH_SHARED_PCI_INTERRUPTS + /* Allow interrupts sharing. */ +# if RTLNX_VER_MIN(2,6,20) + IRQF_SHARED, +# else + SA_SHIRQ, +# endif + +#else + + /* We don't allow interrupts sharing */ + /* XXX overhaul */ +# if RTLNX_VER_MIN(2,6,20) && RTLNX_VER_MAX(4,1,0) + IRQF_DISABLED, /* keep irqs disabled when calling the action handler */ +# else + 0, +# endif +#endif + DRIVER_NAME, + pIns); + if (rc) + { + vbpci_printk(KERN_DEBUG, pIns->pPciDev, + "could not request irq %d, error %d\n", iIrq, rc); + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_RESOURCE_BUSY; + } + + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "got irq %d\n", iIrq); + *piHostIrq = iIrq; + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxPciOsDevUnregisterIrqHandler(PVBOXRAWPCIINS pIns, int32_t iHostIrq) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "freeing irq %d\n", iHostIrq); + free_irq(iHostIrq, pIns); + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} + +DECLHIDDEN(int) vboxPciOsDevPowerStateChange(PVBOXRAWPCIINS pIns, PCIRAWPOWERSTATE aState) +{ + int rc; + + switch (aState) + { + case PCIRAW_POWER_ON: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_ON\n"); + /* Reset device, just in case. */ + vboxPciLinuxDevReset(pIns); + /* register us with IOMMU */ + rc = vboxPciLinuxDevRegisterWithIommu(pIns); + break; + case PCIRAW_POWER_RESET: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_RESET\n"); + rc = vboxPciLinuxDevReset(pIns); + break; + case PCIRAW_POWER_OFF: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_OFF\n"); + /* unregister us from IOMMU */ + rc = vboxPciLinuxDevUnregisterWithIommu(pIns); + break; + case PCIRAW_POWER_SUSPEND: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_SUSPEND\n"); + rc = VINF_SUCCESS; + /// @todo what do we do here? + break; + case PCIRAW_POWER_RESUME: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "PCIRAW_POWER_RESUME\n"); + rc = VINF_SUCCESS; + /// @todo what do we do here? + break; + default: + vbpci_printk(KERN_DEBUG, pIns->pPciDev, "unknown power state %u\n", aState); + /* to make compiler happy */ + rc = VERR_NOT_SUPPORTED; + break; + } + + return rc; +} + + +#ifdef VBOX_WITH_IOMMU +/** Callback for FNRAWPCICONTIGPHYSMEMINFO. */ +static DECLCALLBACK(int) vboxPciOsContigMemInfo(PRAWPCIPERVM pVmCtx, RTHCPHYS HostStart, RTGCPHYS GuestStart, + uint64_t cMemSize, PCIRAWMEMINFOACTION Action) +{ + struct iommu_domain* domain = ((PVBOXRAWPCIDRVVM)(pVmCtx->pDriverData))->pIommuDomain; + int rc = VINF_SUCCESS; + IPRT_LINUX_SAVE_EFL_AC(); + + switch (Action) + { + case PCIRAW_MEMINFO_MAP: + { + int flags, r; + + if (iommu_iova_to_phys(domain, GuestStart)) + break; + + flags = IOMMU_READ | IOMMU_WRITE; + /** @todo flags |= IOMMU_CACHE; */ + + r = iommu_map(domain, GuestStart, HostStart, get_order(cMemSize), flags); + if (r) + { + printk(KERN_ERR "vboxPciOsContigMemInfo:" + "iommu failed to map pfn=%llx\n", HostStart); + rc = VERR_GENERAL_FAILURE; + break; + } + rc = VINF_SUCCESS; + break; + } + case PCIRAW_MEMINFO_UNMAP: + { + int order; + order = iommu_unmap(domain, GuestStart, get_order(cMemSize)); + NOREF(order); + break; + } + + default: + printk(KERN_DEBUG "Unsupported action: %d\n", (int)Action); + rc = VERR_NOT_SUPPORTED; + break; + } + + IPRT_LINUX_RESTORE_EFL_AC(); + return rc; +} +#endif + +DECLHIDDEN(int) vboxPciOsInitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM, PRAWPCIPERVM pVmData) +{ + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_IOMMU + IPRT_LINUX_SAVE_EFL_AC(); + + if (IOMMU_PRESENT()) + { + pThis->pIommuDomain = IOMMU_DOMAIN_ALLOC(); + if (!pThis->pIommuDomain) + { + vbpci_printk(KERN_DEBUG, NULL, "cannot allocate IOMMU domain\n"); + rc = VERR_NO_MEMORY; + } + else + { + pVmData->pfnContigMemInfo = vboxPciOsContigMemInfo; + + vbpci_printk(KERN_DEBUG, NULL, "created IOMMU domain %p\n", + pThis->pIommuDomain); + } + } + + IPRT_LINUX_RESTORE_EFL_AC(); +#endif + return rc; +} + +DECLHIDDEN(void) vboxPciOsDeinitVm(PVBOXRAWPCIDRVVM pThis, PVM pVM) +{ +#ifdef VBOX_WITH_IOMMU + IPRT_LINUX_SAVE_EFL_AC(); + + if (pThis->pIommuDomain) + { + vbpci_printk(KERN_DEBUG, NULL, "freeing IOMMU domain %p\n", + pThis->pIommuDomain); + iommu_domain_free(pThis->pIommuDomain); + pThis->pIommuDomain = NULL; + } + + IPRT_LINUX_RESTORE_EFL_AC(); +#endif +} diff --git a/src/VBox/HostDrivers/VBoxPci/linux/files_vboxpci b/src/VBox/HostDrivers/VBoxPci/linux/files_vboxpci new file mode 100755 index 00000000..70e15ed2 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxPci/linux/files_vboxpci @@ -0,0 +1,111 @@ +#!/bin/sh +# $Id: files_vboxpci $ +## @file +# Shared file between Makefile.kmk and export_modules.sh. +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +VBOX_VBOXPCI_SOURCES=" \ + ${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/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/heap.h=>include/iprt/heap.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/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/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/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-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/rawpci.h=>include/VBox/rawpci.h \ + ${PATH_ROOT}/include/VBox/vmm/stam.h=>include/VBox/vmm/stam.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}/src/VBox/HostDrivers/VBoxPci/linux/VBoxPci-linux.c=>linux/VBoxPci-linux.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxPci/VBoxPci.c=>VBoxPci.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/VBoxPci/VBoxPciInternal.h=>VBoxPciInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPDrvIDC.h=>SUPDrvIDC.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClient.c=>SUPR0IdcClient.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientComponent.c=>SUPR0IdcClientComponent.c \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/SUPR0IdcClientInternal.h=>SUPR0IdcClientInternal.h \ + ${PATH_ROOT}/src/VBox/HostDrivers/Support/linux/SUPR0IdcClient-linux.c=>linux/SUPR0IdcClient-linux.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/math/gcc/divdi3.c=>math/gcc/divdi3.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/r0drv/linux/the-linux-kernel.h=>r0drv/linux/the-linux-kernel.h \ + ${PATH_OUT}/version-generated.h=>version-generated.h \ + ${PATH_OUT}/revision-generated.h=>revision-generated.h \ + ${PATH_OUT}/product-generated.h=>product-generated.h \ +" + diff --git a/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk new file mode 100644 index 00000000..8936ba55 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk @@ -0,0 +1,85 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Windows USB drivers. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +if1of ($(KBUILD_TARGET), solaris win) + include $(PATH_SUB_CURRENT)/$(KBUILD_TARGET)/Makefile.kmk +endif + +# +# USBLib - The static USB Library for use in VBoxSVC and VBoxDD. +# +# Note! Drivers using USBFilter.cpp should just add ../USBFilter.cpp +# to their source list. +# +LIBRARIES += USBLib +USBLib_TEMPLATE = VBoxR3Dll +USBLib_SDKS.win = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) +USBLib_DEFS = IN_USBLIB +USBLib_DEFS.os2 = STATIC_USBCALLS +USBLib_DEFS.win = _WIN32_WINNT=0x0501 +USBLib_SOURCES = \ + USBLib.cpp \ + USBFilter.cpp + +# OS specific bits if applicable. +USBLib_SOURCES.os2 = \ + os2/usbcalls.c +USBLib_SOURCES.solaris = \ + solaris/USBLib-solaris.cpp +USBLib_SOURCES.win = \ + win/lib/VBoxUsbLib-win.cpp + +ifdef VBOX_WITH_TESTCASES + # + # USBFilter testcase. + # + PROGRAMS += tstUSBFilter + tstUSBFilter_TEMPLATE = VBoxR3TstExe + tstUSBFilter_DEFS = IN_USBLIB + tstUSBFilter_SOURCES = \ + testcase/tstUSBFilter.cpp + tstUSBFilter_LIBS = \ + $(USBLib_1_TARGET) +endif # VBOX_WITH_TESTCASES + + +# generate rules +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp new file mode 100644 index 00000000..e2e6e199 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp @@ -0,0 +1,1860 @@ +/* $Id: USBFilter.cpp $ */ +/** @file + * VirtualBox USB filter abstraction. + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + + +/** @todo split this up for the sake of device drivers and such. */ + + +/** + * Initializes an USBFILTER structure. + * + * @param pFilter The filter to initialize. + * @param enmType The filter type. If not valid, the filter will not + * be properly initialized and all other calls will fail. + */ +USBLIB_DECL(void) USBFilterInit(PUSBFILTER pFilter, USBFILTERTYPE enmType) +{ + memset(pFilter, 0, sizeof(*pFilter)); + AssertReturnVoid(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END); + pFilter->u32Magic = USBFILTER_MAGIC; + pFilter->enmType = enmType; + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + pFilter->aFields[i].enmMatch = USBFILTERMATCH_IGNORE; +} + + +/** + * Make a clone of the specified filter. + * + * @param pFilter The target filter. + * @param pToClone The source filter. + */ +USBLIB_DECL(void) USBFilterClone(PUSBFILTER pFilter, PCUSBFILTER pToClone) +{ + memcpy(pFilter, pToClone, sizeof(*pToClone)); +} + + +/** + * Deletes (invalidates) an USBFILTER structure. + * + * @param pFilter The filter to delete. + */ +USBLIB_DECL(void) USBFilterDelete(PUSBFILTER pFilter) +{ + pFilter->u32Magic = ~USBFILTER_MAGIC; + pFilter->enmType = USBFILTERTYPE_INVALID; + pFilter->offCurEnd = 0xfffff; +} + + +/** + * Skips blanks. + * + * @returns Next non-blank char in the string. + * @param psz The string. + */ +DECLINLINE(const char *) usbfilterSkipBlanks(const char *psz) +{ + while (RT_C_IS_BLANK(*psz)) + psz++; + return psz; +} + + +/** + * Worker for usbfilterReadNumber that parses a hexadecimal number. + * + * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS. + * @param pszExpr Where to start converting, first char is a valid digit. + * @param ppszExpr See usbfilterReadNumber. + * @param pu16Val See usbfilterReadNumber. + */ +static int usbfilterReadNumberHex(const char *pszExpr, const char **ppszExpr, uint16_t *pu16Val) +{ + int rc = VINF_SUCCESS; + uint32_t u32 = 0; + do + { + unsigned uDigit = *pszExpr >= 'a' && *pszExpr <= 'f' + ? *pszExpr - 'a' + 10 + : *pszExpr >= 'A' && *pszExpr <= 'F' + ? *pszExpr - 'A' + 10 + : *pszExpr - '0'; + if (uDigit >= 16) + break; + u32 *= 16; + u32 += uDigit; + if (u32 > UINT16_MAX) + rc = VWRN_NUMBER_TOO_BIG; + } while (*++pszExpr); + + *ppszExpr = usbfilterSkipBlanks(pszExpr); + *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX; + return VINF_SUCCESS; +} + + +/** + * Worker for usbfilterReadNumber that parses a decimal number. + * + * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS. + * @param pszExpr Where to start converting, first char is a valid digit. + * @param uBase The base - 8 or 16. + * @param ppszExpr See usbfilterReadNumber. + * @param pu16Val See usbfilterReadNumber. + */ +static int usbfilterReadNumberDecimal(const char *pszExpr, unsigned uBase, const char **ppszExpr, uint16_t *pu16Val) +{ + int rc = VINF_SUCCESS; + uint32_t u32 = 0; + do + { + unsigned uDigit = *pszExpr - '0'; + if (uDigit >= uBase) + break; + u32 *= uBase; + u32 += uDigit; + if (u32 > UINT16_MAX) + rc = VWRN_NUMBER_TOO_BIG; + } while (*++pszExpr); + + *ppszExpr = usbfilterSkipBlanks(pszExpr); + *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX; + return rc; +} + + +/** + * Reads a number from a numeric expression. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if all is fine. *ppszExpr and *pu16Val are updated. + * @retval VWRN_NUMBER_TOO_BIG if the number exceeds unsigned 16-bit, both *ppszExpr and *pu16Val are updated. + * @retval VERR_NO_DIGITS if there aren't any digits. + * + * @param ppszExpr Pointer to the current expression pointer. + * This is advanced past the expression and trailing blanks on success. + * @param pu16Val Where to store the value on success. + */ +static int usbfilterReadNumber(const char **ppszExpr, uint16_t *pu16Val) +{ + const char *pszExpr = usbfilterSkipBlanks(*ppszExpr); + if (!RT_C_IS_DIGIT(*pszExpr)) + return VERR_NO_DIGITS; + + if (*pszExpr == '0') + { + if (pszExpr[1] == 'x' || pszExpr[1] == 'X') + { + if (!RT_C_IS_XDIGIT(pszExpr[2])) + return VERR_NO_DIGITS; + return usbfilterReadNumberHex(pszExpr + 2, ppszExpr, pu16Val); + } + if (RT_C_IS_ODIGIT(pszExpr[1])) + return usbfilterReadNumberDecimal(pszExpr + 1, 8, ppszExpr, pu16Val); + /* Solitary 0! */ + if (RT_C_IS_DIGIT(pszExpr[1])) + return VERR_NO_DIGITS; + } + return usbfilterReadNumberDecimal(pszExpr, 10, ppszExpr, pu16Val); +} + + +/** + * Validates a numeric expression. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if valid. + * @retval VERR_INVALID_PARAMETER if invalid. + * @retval VERR_NO_DIGITS if some expression is short of digits. + * + * @param pszExpr The numeric expression. + */ +static int usbfilterValidateNumExpression(const char *pszExpr) +{ + /* + * An empty expression is fine. + */ + if (!*pszExpr) + return VINF_SUCCESS; + + /* + * The string format is: "int:(()|([]-[]))(,()|([]-[]))*" + * where and are numbers in decimal, hex (0xNNN) or octal (0NNN). + * Spaces are allowed around and . + */ + unsigned cSubExpressions = 0; + while (*pszExpr) + { + if (!strncmp(pszExpr, RT_STR_TUPLE("int:"))) + pszExpr += strlen("int:"); + + /* + * Skip remnants of the previous expression and any empty expressions. + * ('|' is the expression separator.) + */ + while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr) || *pszExpr == '(' || *pszExpr == ')') + pszExpr++; + if (!*pszExpr) + break; + + /* + * Parse the expression. + */ + int rc; + uint16_t u16First = 0; + uint16_t u16Last = 0; + if (*pszExpr == '-') + { + /* -N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M or M,N or M-N or M- */ + rc = usbfilterReadNumber(&pszExpr, &u16First); + if (RT_SUCCESS(rc)) + { + pszExpr = usbfilterSkipBlanks(pszExpr); + if (*pszExpr == '-') + { + pszExpr++; + if (*pszExpr) /* M-N */ + rc = usbfilterReadNumber(&pszExpr, &u16Last); + else /* M- */ + u16Last = UINT16_MAX; + } + else if (*pszExpr == ',') + { + /* M,N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M */ + u16Last = u16First; + } + } + } + if (RT_FAILURE(rc)) + return rc; + + /* + * We should either be at the end of the string, at an expression separator (|), + * or at the end of an interval filter (')'). + */ + if (*pszExpr && *pszExpr != '|' && *pszExpr != ')') + return VERR_INVALID_PARAMETER; + + cSubExpressions++; + } + + return cSubExpressions ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + + +/** + * Validates a string pattern. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if valid. + * @retval VERR_INVALID_PARAMETER if invalid. + * + * @param psz The string pattern. + */ +static int usbfilterValidateStringPattern(const char *psz) +{ + /* + * This is only becomes important if we start doing + * sets ([0-9]) and such like. + */ + RT_NOREF1(psz); + return VINF_SUCCESS; +} + + +/** + * Thoroughly validates the USB Filter. + * + * @returns Appropriate VBox status code. + * @param pFilter The filter to validate. + */ +USBLIB_DECL(int) USBFilterValidate(PCUSBFILTER pFilter) +{ + if (!RT_VALID_PTR(pFilter)) + return VERR_INVALID_POINTER; + + if (pFilter->u32Magic != USBFILTER_MAGIC) + return VERR_INVALID_MAGIC; + + if ( pFilter->enmType <= USBFILTERTYPE_INVALID + || pFilter->enmType >= USBFILTERTYPE_END) + { + Log(("USBFilter: %p - enmType=%d!\n", pFilter, pFilter->enmType)); + return VERR_INVALID_PARAMETER; + } + + if (pFilter->offCurEnd >= sizeof(pFilter->achStrTab)) + { + Log(("USBFilter: %p - offCurEnd=%#x!\n", pFilter, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + + /* Validate that string value offsets are inside the string table. */ + for (uint32_t i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch) + && pFilter->aFields[i].u16Value > pFilter->offCurEnd) + { + Log(("USBFilter: %p - bad offset=%#x\n", pFilter, pFilter->aFields[i].u16Value)); + return VERR_INVALID_PARAMETER; + } + } + + /* + * Validate the string table. + */ + if (pFilter->achStrTab[0]) + { + Log(("USBFilter: %p - bad null string\n", pFilter)); + return VERR_INVALID_PARAMETER; + } + + const char *psz = &pFilter->achStrTab[1]; + while (psz < &pFilter->achStrTab[pFilter->offCurEnd]) + { + const char *pszEnd = RTStrEnd(psz, &pFilter->achStrTab[sizeof(pFilter->achStrTab)] - psz); + if (!pszEnd) + { + Log(("USBFilter: %p - string at %#x isn't terminated!\n", + pFilter, psz - &pFilter->achStrTab[0])); + return VERR_INVALID_PARAMETER; + } + + uint16_t off = (uint16_t)(uintptr_t)(psz - &pFilter->achStrTab[0]); + unsigned i; + for (i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch) + && pFilter->aFields[i].u16Value == off) + break; + if (i >= RT_ELEMENTS(pFilter->aFields)) + { + Log(("USBFilter: %p - string at %#x isn't used by anyone! (%s)\n", + pFilter, psz - &pFilter->achStrTab[0], psz)); + return VERR_INVALID_PARAMETER; + } + + psz = pszEnd + 1; + } + + if ((uintptr_t)(psz - &pFilter->achStrTab[0] - 1) != pFilter->offCurEnd) + { + Log(("USBFilter: %p - offCurEnd=%#x currently at %#x\n", + pFilter, pFilter->offCurEnd, psz - &pFilter->achStrTab[0] - 1)); + return VERR_INVALID_PARAMETER; + } + + while (psz < &pFilter->achStrTab[sizeof(pFilter->achStrTab)]) + { + if (*psz) + { + Log(("USBFilter: %p - str tab isn't zero padded! %#x: %c\n", + pFilter, psz - &pFilter->achStrTab[0], *psz)); + return VERR_INVALID_PARAMETER; + } + psz++; + } + + + /* + * Validate the fields. + */ + int rc; + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + if (pFilter->aFields[i].u16Value) + { + Log(("USBFilter: %p - #%d/%d u16Value=%d expected 0!\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + if (!USBFilterIsNumericField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not numeric field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if (!USBFilterIsNumericField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not numeric field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value]; + rc = usbfilterValidateNumExpression(psz); + if (RT_FAILURE(rc)) + { + Log(("USBFilter: %p - #%d / %d - bad num expr: %s (rc=%Rrc)\n", + pFilter, i, pFilter->aFields[i].enmMatch, psz, rc)); + return rc; + } + break; + + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + if (!USBFilterIsStringField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not string field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + if (!USBFilterIsStringField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not string field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value]; + rc = usbfilterValidateStringPattern(psz); + if (RT_FAILURE(rc)) + { + Log(("USBFilter: %p - #%d / %d - bad string pattern: %s (rc=%Rrc)\n", + pFilter, i, pFilter->aFields[i].enmMatch, psz, rc)); + return rc; + } + break; + + default: + Log(("USBFilter: %p - #%d enmMatch=%d!\n", pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + } + + return VINF_SUCCESS; +} + + +/** + * Find the specified field in the string table. + * + * @returns Pointer to the string in the string table on success. + * NULL if the field is invalid or it doesn't have a string value. + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +DECLINLINE(const char *) usbfilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + { + switch (pFilter->aFields[enmFieldIdx].enmMatch) + { + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + Assert(pFilter->aFields[enmFieldIdx].u16Value < sizeof(pFilter->achStrTab)); + return &pFilter->achStrTab[pFilter->aFields[enmFieldIdx].u16Value]; + + default: + AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch)); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + break; + } + } + return NULL; +} + + +/** + * Gets a number value of a field. + * + * The field must contain a numeric value. + * + * @returns The field value on success, -1 on failure (invalid input / not numeric). + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +DECLINLINE(int) usbfilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + { + switch (pFilter->aFields[enmFieldIdx].enmMatch) + { + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return pFilter->aFields[enmFieldIdx].u16Value; + + default: + AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch)); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + break; + } + } + return -1; +} + + +/** + * Performs simple pattern matching. + * + * @returns true on match and false on mismatch. + * @param pszExpr The numeric expression. + * @param u16Value The value to match. + */ +static bool usbfilterMatchNumExpression(const char *pszExpr, uint16_t u16Value) +{ + /* + * The string format is: "int:(()|([]-[]))(,()|([]-[]))*" + * where and are numbers in decimal, hex (0xNNN) or octal (0NNN). + * Spaces are allowed around and . + */ + while (*pszExpr) + { + if (!strncmp(pszExpr, RT_STR_TUPLE("int:"))) + pszExpr += strlen("int:"); + + /* + * Skip remnants of the previous expression and any empty expressions. + * ('|' is the expression separator.) + */ + while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr) || *pszExpr == '(' || *pszExpr == ')') + pszExpr++; + if (!*pszExpr) + break; + + /* + * Parse the expression. + */ + int rc; + uint16_t u16First = 0; + uint16_t u16Last = 0; + if (*pszExpr == '-') + { + /* -N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M or M,N or M-N or M- */ + rc = usbfilterReadNumber(&pszExpr, &u16First); + if (RT_SUCCESS(rc)) + { + pszExpr = usbfilterSkipBlanks(pszExpr); + if (*pszExpr == '-') + { + pszExpr++; + if (*pszExpr) /* M-N */ + rc = usbfilterReadNumber(&pszExpr, &u16Last); + else /* M- */ + u16Last = UINT16_MAX; + } + else if (*pszExpr == ',') + { + /* M,N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M */ + u16Last = u16First; + } + } + } + + /* On success, we should either be at the end of the string, at an expression + * separator (|), or at the end of an interval filter (')'). + */ + if (RT_SUCCESS(rc) && *pszExpr && *pszExpr != '|' && *pszExpr != ')') + rc = VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + /* + * Swap the values if the order is mixed up. + */ + if (u16First > u16Last) + { + uint16_t u16Tmp = u16First; + u16First = u16Last; + u16Last = u16Tmp; + } + + /* + * Perform the compare. + */ + if ( u16Value >= u16First + && u16Value <= u16Last) + return true; + } + else + { + /* + * Skip the bad expression. + * ('|' is the expression separator.) + */ + while (*pszExpr && *pszExpr != '|') + pszExpr++; + } + } + + return false; +} + + +/** + * Performs simple pattern matching. + * + * @returns true on match and false on mismatch. + * @param pszPattern The pattern to match against. + * @param psz The string to match. + */ +static bool usbfilterMatchStringPattern(const char *pszPattern, const char *psz) +{ + char ch; + while ((ch = *pszPattern++)) + { + if (ch == '?') + { + /* + * Matches one char or end of string. + */ + if (*psz) + psz++; + } + else if (ch == '*') + { + /* + * Matches zero or more characters. + */ + /* skip subsequent wildcards */ + while ( (ch = *pszPattern) == '*' + || ch == '?') + pszPattern++; + if (!ch) + /* Pattern ends with a '*' and thus matches the rest of psz. */ + return true; + + /* Find the length of the following exact pattern sequence. */ + ssize_t cchMatch = 1; + while ( (ch = pszPattern[cchMatch]) != '\0' + && ch != '*' + && ch != '?') + cchMatch++; + + /* Check if the exact pattern sequence is too long. */ + ssize_t cch = strlen(psz); + cch -= cchMatch; + if (cch < 0) + return false; + + /* Is the rest an exact match? */ + if (!ch) + return memcmp(psz + cch, pszPattern, cchMatch) == 0; + + /* + * This is where things normally starts to get recursive or ugly. + * + * Just to make life simple, we'll skip the nasty stuff and say + * that we will do a maximal wildcard match and forget about any + * alternative matches. + * + * If somebody is bored out of their mind one day, feel free to + * implement correct matching without using recursion. + */ + ch = *pszPattern; + const char *pszMatch = NULL; + while ( cch-- >= 0 + && *psz) + { + if ( *psz == ch + && !strncmp(psz, pszPattern, cchMatch)) + pszMatch = psz; + psz++; + } + if (!pszMatch) + return false; + + /* advance */ + psz = pszMatch + cchMatch; + pszPattern += cchMatch; + } + else + { + /* exact match */ + if (ch != *psz) + return false; + psz++; + } + } + + return *psz == '\0'; +} + + +/** + * Match a filter against a device. + * + * @returns true if they match, false if not. + * + * @param pFilter The filter to match with. + * @param pDevice The device data. This is a filter (type ignored) that + * contains 'exact' values for all present fields and 'ignore' + * values for the non-present fields. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(bool) USBFilterMatch(PCUSBFILTER pFilter, PCUSBFILTER pDevice) +{ + return USBFilterMatchRated(pFilter, pDevice) > 0; +} + + +#if 0 /*def IN_RING0*/ /** @todo convert to proper logging. */ +extern "C" int printf(const char *format, ...); +# define dprintf(a) printf a +#else +# define dprintf(a) do {} while (0) +#endif + +/** + * Match a filter against a device and rate the result. + * + * @returns -1 if no match, matching rate between 1 and 100 (inclusive) if matched. + * + * @param pFilter The filter to match with. + * @param pDevice The device data. This is a filter (type ignored) that + * contains 'exact' values for all present fields and 'ignore' + * values for the non-present fields. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(int) USBFilterMatchRated(PCUSBFILTER pFilter, PCUSBFILTER pDevice) +{ + unsigned iRate = 0; +dprintf(("USBFilterMatchRated: %p %p\n", pFilter, pDevice)); + + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + iRate += 2; + break; + + case USBFILTERMATCH_PRESENT: + if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE) + { +dprintf(("filter match[%d]: !present\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXACT: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value) + { +if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE) + dprintf(("filter match[%d]: !num_exact device=ignore\n", i)); +else + dprintf(("filter match[%d]: !num_exact %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXACT_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value) + { +dprintf(("filter match[%d]: !num_exact_np %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXPRESSION: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), + pDevice->aFields[i].u16Value)) + { +dprintf(("filter match[%d]: !num_expression\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), + pDevice->aFields[i].u16Value)) + { +dprintf(("filter match[%d]: !num_expression_no\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_STR_EXACT: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_exact\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_STR_EXACT_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_exact_np\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_STR_PATTERN: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_pattern\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_STR_PATTERN_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_pattern_np\n", i)); + return -1; + } + iRate += 1; + break; + + default: + AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch)); + return -1; + } + } + + /* iRate is the range 0..2*cFields - recalc to percent. */ +dprintf(("filter match: iRate=%d", iRate)); + return iRate == 2 * RT_ELEMENTS(pFilter->aFields) + ? 100 + : (iRate * 100) / (2 * RT_ELEMENTS(pFilter->aFields)); +} + + +/** + * Match a filter against a USBDEVICE. + * + * @returns true if they match, false if not. + * + * @param pFilter The filter to match with. + * @param pDevice The device to match. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(bool) USBFilterMatchDevice(PCUSBFILTER pFilter, PUSBDEVICE pDevice) +{ + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + break; + + case USBFILTERMATCH_PRESENT: + { + const char *psz; + switch (i) + { + case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break; + case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break; + case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break; + default: psz = ""; break; + } + if (!psz) + return false; + break; + } + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + { + uint16_t u16Value; + switch (i) + { + case USBFILTERIDX_VENDOR_ID: u16Value = pDevice->idVendor; break; + case USBFILTERIDX_PRODUCT_ID: u16Value = pDevice->idProduct; break; + case USBFILTERIDX_DEVICE: u16Value = pDevice->bcdDevice; break; + case USBFILTERIDX_DEVICE_CLASS: u16Value = pDevice->bDeviceClass; break; + case USBFILTERIDX_DEVICE_SUB_CLASS: u16Value = pDevice->bDeviceSubClass; break; + case USBFILTERIDX_DEVICE_PROTOCOL: u16Value = pDevice->bDeviceProtocol; break; + case USBFILTERIDX_BUS: u16Value = pDevice->bBus; break; + case USBFILTERIDX_PORT: u16Value = pDevice->bPort; break; + default: u16Value = UINT16_MAX; break; + + } + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + if (pFilter->aFields[i].u16Value != u16Value) + return false; + break; + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if (!usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), u16Value)) + return false; + break; + } + break; + } + + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + { + const char *psz; + switch (i) + { + case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break; + case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break; + case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break; + default: psz = NULL; break; + } + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_STR_EXACT: + if ( !psz + || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_EXACT_NP: + if ( psz + && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_PATTERN: + if ( !psz + || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_PATTERN_NP: + if ( psz + && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + } + break; + } + + default: + AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch)); + return false; + } + } + + return true; +} + + +/** + * Checks if the two filters are identical. + * + * @returns true if the are identical, false if they aren't. + * @param pFilter The first filter. + * @param pFilter2 The second filter. + */ +USBLIB_DECL(bool) USBFilterIsIdentical(PCUSBFILTER pFilter, PCUSBFILTER pFilter2) +{ + /* Lazy works here because we're darn strict with zero padding and such elsewhere. */ + return memcmp(pFilter, pFilter2, sizeof(*pFilter)) == 0; +} + + + +/** + * Sets the filter type. + * + * @returns VBox status code. + * @retval VERR_INVALID_PARAMETER if the filter type is invalid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmType The new filter type. + */ +USBLIB_DECL(int) USBFilterSetFilterType(PUSBFILTER pFilter, USBFILTERTYPE enmType) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END, VERR_INVALID_PARAMETER); + + pFilter->enmType = enmType; + return VINF_SUCCESS; +} + + +/** + * Replaces the string value of a field. + * + * This will remove any existing string value current held by the field from the + * string table and then attempt to add the new value. This function can be used + * to delete any assigned string before changing the type to numeric by passing + * in an empty string. This works because the first byte in the string table is + * reserved for the empty (NULL) string. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_POINTER if pszString isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pszString The string to add. + * @param fPurge Purge invalid UTF-8 encoding and control characters + * before setting it. + */ +static int usbfilterSetString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszString, bool fPurge) +{ + /* + * Validate input. + */ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszString, VERR_INVALID_POINTER); + + Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab)); + Assert(pFilter->achStrTab[pFilter->offCurEnd] == '\0'); + + /* + * Remove old string value if any. + */ + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch) + && pFilter->aFields[enmFieldIdx].u16Value != 0) + { + uint32_t off = pFilter->aFields[enmFieldIdx].u16Value; + pFilter->aFields[enmFieldIdx].u16Value = 0; /* Assign it to the NULL string. */ + + unsigned cchShift = (unsigned)strlen(&pFilter->achStrTab[off]) + 1; + ssize_t cchToMove = (pFilter->offCurEnd + 1) - (off + cchShift); + Assert(cchToMove >= 0); + if (cchToMove > 0) + { + /* We're not last - must shift the strings. */ + memmove(&pFilter->achStrTab[off], &pFilter->achStrTab[off + cchShift], cchToMove); + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + if ( pFilter->aFields[i].u16Value >= off + && USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch)) + pFilter->aFields[i].u16Value -= cchShift; + } + pFilter->offCurEnd -= cchShift; + Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab)); + Assert(pFilter->offCurEnd + cchShift <= sizeof(pFilter->achStrTab)); + + /* zero the unused string table (to allow lazyness/strictness elsewhere). */ + memset(&pFilter->achStrTab[pFilter->offCurEnd], '\0', cchShift); + } + + /* + * Make a special case for the empty string. + * (This also makes the delete logical above work correctly for the last string.) + */ + if (!*pszString) + pFilter->aFields[enmFieldIdx].u16Value = 0; + else + { + size_t cch = strlen(pszString); + if (pFilter->offCurEnd + cch + 2 > sizeof(pFilter->achStrTab)) + return VERR_BUFFER_OVERFLOW; + + pFilter->aFields[enmFieldIdx].u16Value = pFilter->offCurEnd + 1; + memcpy(&pFilter->achStrTab[pFilter->offCurEnd + 1], pszString, cch + 1); + if (fPurge) + cch = USBLibPurgeEncoding(&pFilter->achStrTab[pFilter->offCurEnd + 1]); + pFilter->offCurEnd += (uint32_t)cch + 1; + } + + return VINF_SUCCESS; +} + +/** + * Wrapper around usbfilterSetString() that deletes any string value + * currently assigned to a field. + * + * Upon successful return the field contains a null string, nothing or a number. + * + * This function will validate the field index if there isn't any string + * value to delete, thus preventing any extra validating of the index. + * + * @returns VBox status code. See usbfilterSetString. + * @param pFilter The filter. + * @param enmFieldIdx The index of the field which string value should be deleted. + */ +static int usbfilterDeleteAnyStringValue(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = VINF_SUCCESS; + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch) + && pFilter->aFields[enmFieldIdx].u16Value != 0) + rc = usbfilterSetString(pFilter, enmFieldIdx, "", false /*fPurge*/); + else if ((unsigned)enmFieldIdx >= (unsigned)USBFILTERIDX_END) + rc = VERR_INVALID_PARAMETER; + return rc; +} + + +/** + * Sets a field to always match (ignore whatever is thrown at it). + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + */ +USBLIB_DECL(int) USBFilterSetIgnore(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_IGNORE; + pFilter->aFields[enmFieldIdx].u16Value = 0; + } + return rc; +} + + +/** + * Sets a field to match on device field present only. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + */ +USBLIB_DECL(int) USBFilterSetPresentOnly(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_PRESENT; + pFilter->aFields[enmFieldIdx].u16Value = 0; + } + return rc; +} + + +/** + * Sets a field to exactly match a number. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param u16Value The string pattern. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetNumExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t u16Value, bool fMustBePresent) +{ + int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].u16Value = u16Value; + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXACT : USBFILTERMATCH_NUM_EXACT_NP; + } + } + + return rc; +} + + +/** + * Sets a field to match a numeric expression. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or the numeric expression aren't valid. + * @retval VERR_INVALID_POINTER if pszExpression isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszExpression The numeric expression. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetNumExpression(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszExpression, bool fMustBePresent) +{ + int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + /* Strip leading spaces and empty sub expressions (||). */ + while (*pszExpression && (RT_C_IS_BLANK(*pszExpression) || *pszExpression == '|')) + pszExpression++; + + rc = usbfilterValidateNumExpression(pszExpression); + if (RT_SUCCESS(rc)) + { + /* We could optimize the expression further (stripping spaces, convert numbers), + but it's more work than what it's worth and it could upset some users. */ + rc = usbfilterSetString(pFilter, enmFieldIdx, pszExpression, false /*fPurge*/); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXPRESSION : USBFILTERMATCH_NUM_EXPRESSION_NP; + else if (rc == VERR_NO_DIGITS) + rc = VERR_INVALID_PARAMETER; + } + } + return rc; +} + + +/** + * Sets a field to exactly match a string. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszValue The string value. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + * @param fPurge Purge invalid UTF-8 encoding and control + * characters before setting it. + */ +USBLIB_DECL(int) USBFilterSetStringExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszValue, + bool fMustBePresent, bool fPurge) +{ + int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterSetString(pFilter, enmFieldIdx, pszValue, fPurge); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_EXACT : USBFILTERMATCH_STR_EXACT_NP; + } + return rc; +} + + +/** + * Sets a field to match a string pattern. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszPattern The string pattern. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetStringPattern(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszPattern, bool fMustBePresent) +{ + int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterValidateStringPattern(pszPattern); + if (RT_SUCCESS(rc)) + { + rc = usbfilterSetString(pFilter, enmFieldIdx, pszPattern, false /*fPurge*/); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_PATTERN : USBFILTERMATCH_STR_PATTERN_NP; + } + } + return rc; +} + + +/** + * Sets the must-be-present part of a field. + * + * This only works on field which already has matching criteria. This means + * that field marked 'ignore' will not be processed and will result in a + * warning status code. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VWRN_INVALID_PARAMETER if the field is marked 'ignore'. No assertions. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetMustBePresent(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, bool fMustBePresent) +{ + AssertPtrReturn(pFilter, VERR_INVALID_POINTER); + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER); + + USBFILTERMATCH enmMatch = (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch; + if (fMustBePresent) + { + switch (enmMatch) + { + case USBFILTERMATCH_IGNORE: + return VWRN_INVALID_PARAMETER; + + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_PATTERN: + break; + + case USBFILTERMATCH_NUM_EXACT_NP: + enmMatch = USBFILTERMATCH_NUM_EXACT; + break; + case USBFILTERMATCH_NUM_EXPRESSION_NP: + enmMatch = USBFILTERMATCH_NUM_EXPRESSION; + break; + case USBFILTERMATCH_STR_EXACT_NP: + enmMatch = USBFILTERMATCH_STR_EXACT; + break; + case USBFILTERMATCH_STR_PATTERN_NP: + enmMatch = USBFILTERMATCH_STR_PATTERN; + break; + default: + AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC); + } + } + else + { + switch (enmMatch) + { + case USBFILTERMATCH_IGNORE: + return VWRN_INVALID_PARAMETER; + + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN_NP: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + break; + + case USBFILTERMATCH_PRESENT: + enmMatch = USBFILTERMATCH_IGNORE; + break; + case USBFILTERMATCH_NUM_EXACT: + enmMatch = USBFILTERMATCH_NUM_EXACT_NP; + break; + case USBFILTERMATCH_NUM_EXPRESSION: + enmMatch = USBFILTERMATCH_NUM_EXPRESSION_NP; + break; + case USBFILTERMATCH_STR_EXACT: + enmMatch = USBFILTERMATCH_STR_EXACT_NP; + break; + case USBFILTERMATCH_STR_PATTERN: + enmMatch = USBFILTERMATCH_STR_PATTERN_NP; + break; + + default: + AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC); + } + } + + pFilter->aFields[enmFieldIdx].enmMatch = enmMatch; + return VINF_SUCCESS; +} + + +/** + * Gets the filter type. + * + * @returns The filter type. + * USBFILTERTYPE_INVALID if the filter is invalid. + * @param pFilter The filter. + */ +USBLIB_DECL(USBFILTERTYPE) USBFilterGetFilterType(PCUSBFILTER pFilter) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, USBFILTERTYPE_INVALID); + return pFilter->enmType; +} + + +/** + * Gets the matching method for a field. + * + * @returns The matching method on success, UBFILTERMATCH_INVALID on invalid field index. + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(USBFILTERMATCH) USBFilterGetMatchingMethod(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ( pFilter->u32Magic == USBFILTER_MAGIC + && (unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + return (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch; + return USBFILTERMATCH_INVALID; +} + + +/** + * Gets the numeric value of a field. + * + * The field must contain a number, we're not doing any conversions for you. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a number. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pu16Value Where to store the value. + */ +USBLIB_DECL(int) USBFilterQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t *pu16Value) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + int iValue = usbfilterGetNum(pFilter, enmFieldIdx); + if (iValue == -1) + return VERR_INVALID_PARAMETER; + *pu16Value = (uint16_t)iValue; + return VINF_SUCCESS; +} + + +/** + * Gets the numeric value of a field. + * + * The field must contain a number, we're not doing any conversions for you. + * + * @returns The field value on success, -1 on failure (invalid input / not numeric). + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(int) USBFilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, -1); + return usbfilterGetNum(pFilter, enmFieldIdx); +} + + +/** + * Gets the string value of a field. + * + * The field must contain a string, we're not doing any conversions for you. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the buffer isn't sufficient to hold the string. The buffer + * will be filled with as much of the string that'll fit. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a string. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pszBuf Where to store the string. + * @param cchBuf The size of the buffer. + */ +USBLIB_DECL(int) USBFilterQueryString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, char *pszBuf, size_t cchBuf) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_UNLIKELY(!psz)) + return VERR_INVALID_PARAMETER; + + int rc = VINF_SUCCESS; + size_t cch = strlen(psz); + if (cch < cchBuf) + memcpy(pszBuf, psz, cch + 1); + else + { + rc = VERR_BUFFER_OVERFLOW; + if (cchBuf) + { + memcpy(pszBuf, psz, cchBuf - 1); + pszBuf[cchBuf - 1] = '\0'; + } + } + + return rc; +} + + +/** + * Gets the string table entry for a field. + * + * @returns Pointer to the string. (readonly!) + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(const char *) USBFilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, NULL); + + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_UNLIKELY(!psz)) + return NULL; + return psz; +} + + +/** + * Gets the string length of a field containing a string. + * + * @returns String length on success, -1 on failure (not a string, bad filter). + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(ssize_t) USBFilterGetStringLen(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if (RT_LIKELY(pFilter->u32Magic == USBFILTER_MAGIC)) + { + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_LIKELY(psz)) + return strlen(psz); + } + return -1; +} + + +/** + * Check if any of the fields are set to something substatial. + * + * Consider the fileter a wildcard if this returns false. + * + * @returns true / false. + * @param pFilter The filter. + */ +USBLIB_DECL(bool) USBFilterHasAnySubstatialCriteria(PCUSBFILTER pFilter) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, false); + + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + break; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + return true; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + { + const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i); + if (psz) + { + while (*psz && (*psz == '|' || RT_C_IS_BLANK(*psz))) + psz++; + if (*psz) + return true; + } + break; + } + + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + { + const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i); + if (psz) + { + while (*psz && (*psz == '*' || *psz == '?')) + psz++; + if (*psz) + return true; + } + break; + } + } + } + + return false; +} + + + +/** + * Checks whether the specified field is a numeric field or not. + * + * @returns true / false. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(bool) USBFilterIsNumericField(USBFILTERIDX enmFieldIdx) +{ + switch (enmFieldIdx) + { + case USBFILTERIDX_VENDOR_ID: + case USBFILTERIDX_PRODUCT_ID: + case USBFILTERIDX_DEVICE: + case USBFILTERIDX_DEVICE_CLASS: + case USBFILTERIDX_DEVICE_SUB_CLASS: + case USBFILTERIDX_DEVICE_PROTOCOL: + case USBFILTERIDX_BUS: + case USBFILTERIDX_PORT: + return true; + + default: + AssertMsgFailed(("%d\n", enmFieldIdx)); + RT_FALL_THRU(); + case USBFILTERIDX_MANUFACTURER_STR: + case USBFILTERIDX_PRODUCT_STR: + case USBFILTERIDX_SERIAL_NUMBER_STR: + return false; + } +} + + +/** + * Checks whether the specified field is a string field or not. + * + * @returns true / false. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(bool) USBFilterIsStringField(USBFILTERIDX enmFieldIdx) +{ + switch (enmFieldIdx) + { + default: + AssertMsgFailed(("%d\n", enmFieldIdx)); + RT_FALL_THRU(); + case USBFILTERIDX_VENDOR_ID: + case USBFILTERIDX_PRODUCT_ID: + case USBFILTERIDX_DEVICE: + case USBFILTERIDX_DEVICE_CLASS: + case USBFILTERIDX_DEVICE_SUB_CLASS: + case USBFILTERIDX_DEVICE_PROTOCOL: + case USBFILTERIDX_BUS: + case USBFILTERIDX_PORT: + return false; + + case USBFILTERIDX_MANUFACTURER_STR: + case USBFILTERIDX_PRODUCT_STR: + case USBFILTERIDX_SERIAL_NUMBER_STR: + return true; + } +} + + +/** + * Checks whether the specified matching method uses a numeric value or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodUsingNumericValue(USBFILTERMATCH enmMatchingMethod) +{ + switch (enmMatchingMethod) + { + default: + AssertMsgFailed(("%d\n", enmMatchingMethod)); + RT_FALL_THRU(); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + return false; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return true; + } +} + + +/** + * Checks whether the specified matching method uses a string value or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodUsingStringValue(USBFILTERMATCH enmMatchingMethod) +{ + switch (enmMatchingMethod) + { + default: + AssertMsgFailed(("%d\n", enmMatchingMethod)); + RT_FALL_THRU(); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return false; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + return true; + } +} + + +/** + * Checks if a matching method is for numeric fields or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodNumeric(USBFILTERMATCH enmMatchingMethod) +{ + return enmMatchingMethod >= USBFILTERMATCH_NUM_FIRST + && enmMatchingMethod <= USBFILTERMATCH_NUM_LAST; +} + +/** + * Checks if a matching method is for string fields or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodString(USBFILTERMATCH enmMatchingMethod) +{ + return enmMatchingMethod >= USBFILTERMATCH_STR_FIRST + && enmMatchingMethod <= USBFILTERMATCH_STR_LAST; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp b/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp new file mode 100644 index 00000000..b3f9ec6b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp @@ -0,0 +1,71 @@ +/* $Id: USBLib.cpp $ */ +/** @file + * VirtualBox USB Library, Common Bits. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + + +/** + * Calculate the hash of the serial string. + * + * 64bit FNV1a, chosen because it is designed to hash in to a power of two + * space, and is much quicker and simpler than, say, a half MD4. + * + * @returns the hash. + * @param pszSerial The serial string. + */ +USBLIB_DECL(uint64_t) USBLibHashSerial(const char *pszSerial) +{ + if (!pszSerial) + pszSerial = ""; + + const uint8_t *pu8 = (const uint8_t *)pszSerial; + uint64_t u64 = UINT64_C(14695981039346656037); + for (;;) + { + uint8_t u8 = *pu8; + if (!u8) + break; + u64 = (u64 * UINT64_C(1099511628211)) ^ u8; + pu8++; + } + + return u64; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp new file mode 100644 index 00000000..7533e653 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp @@ -0,0 +1,571 @@ +/* $Id: VBoxUSBFilterMgr.cpp $ */ +/** @file + * VirtualBox Ring-0 USB Filter Manager. + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include "VBoxUSBFilterMgr.h" + +#include +#include +#include +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK +# include +#else +# include +#endif +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** @def VBOXUSBFILTERMGR_LOCK + * Locks the filter list. Careful with scoping since this may + * create a temporary variable. Don't call twice in the same function. + */ + +/** @def VBOXUSBFILTERMGR_UNLOCK + * Unlocks the filter list. + */ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + +# define VBOXUSBFILTERMGR_LOCK() \ + RTSpinlockAcquire(g_Spinlock) + +# define VBOXUSBFILTERMGR_UNLOCK() \ + RTSpinlockRelease(g_Spinlock) + +#else + +# define VBOXUSBFILTERMGR_LOCK() \ + do { int rc2 = RTSemFastMutexRequest(g_Mtx); AssertRC(rc2); } while (0) + +# define VBOXUSBFILTERMGR_UNLOCK() \ + do { int rc2 = RTSemFastMutexRelease(g_Mtx); AssertRC(rc2); } while (0) + +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to an VBoxUSB filter. */ +typedef struct VBOXUSBFILTER *PVBOXUSBFILTER; +/** Pointer to PVBOXUSBFILTER. */ +typedef PVBOXUSBFILTER *PPVBOXUSBFILTER; + +/** + * VBoxUSB internal filter representation. + */ +typedef struct VBOXUSBFILTER +{ + /** The core filter. */ + USBFILTER Core; + /** The filter owner. */ + VBOXUSBFILTER_CONTEXT Owner; + /** The filter Id. */ + uint32_t uHnd; + /** Pointer to the next filter in the list. */ + PVBOXUSBFILTER pNext; +} VBOXUSBFILTER; + +/** + * VBoxUSB filter list. + */ +typedef struct VBOXUSBFILTERLIST +{ + /** The head pointer. */ + PVBOXUSBFILTER pHead; + /** The tail pointer. */ + PVBOXUSBFILTER pTail; +} VBOXUSBFILTERLIST; +/** Pointer to a VBOXUSBFILTERLIST. */ +typedef VBOXUSBFILTERLIST *PVBOXUSBFILTERLIST; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK +/** Spinlock protecting the filter lists. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +#else +/** Mutex protecting the filter lists. */ +static RTSEMFASTMUTEX g_Mtx = NIL_RTSEMFASTMUTEX; +#endif +/** The per-type filter lists. + * @remark The first entry is empty (USBFILTERTYPE_INVALID). */ +static VBOXUSBFILTERLIST g_aLists[USBFILTERTYPE_END]; +/** The handle table to match handles to the right filter. */ +static RTHANDLETABLE g_hHndTableFilters = NIL_RTHANDLETABLE; + + + +/** + * Initializes the VBoxUSB filter manager. + * + * @returns IPRT status code. + */ +int VBoxUSBFilterInit(void) +{ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + int rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxUSBFilter"); +#else + int rc = RTSemFastMutexCreate(&g_Mtx); +#endif + if (RT_SUCCESS(rc)) + { + uint32_t fFlags; +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + fFlags = RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE; +#else + fFlags = RTHANDLETABLE_FLAGS_LOCKED; +#endif + rc = RTHandleTableCreateEx(&g_hHndTableFilters, fFlags, 1 /* uBase */, 8192 /* cMax */, + NULL, NULL); + if (RT_SUCCESS(rc)) + { + /* not really required, but anyway... */ + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + g_aLists[i].pHead = g_aLists[i].pTail = NULL; + } + else + { +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; +#else + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; +#endif + } + } + return rc; +} + + +/** + * Internal worker that frees a filter. + * + * @param pFilter The filter to free. + */ +static void vboxUSBFilterFree(PVBOXUSBFILTER pFilter) +{ + USBFilterDelete(&pFilter->Core); + pFilter->Owner = VBOXUSBFILTER_CONTEXT_NIL; + pFilter->pNext = NULL; + RTMemFree(pFilter); +} + + +/** + * Terminates the VBoxUSB filter manager. + */ +void VBoxUSBFilterTerm(void) +{ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; +#else + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; +#endif + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + g_aLists[i].pHead = g_aLists[i].pTail = NULL; + while (pCur) + { + PVBOXUSBFILTER pNext = pCur->pNext; + RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + vboxUSBFilterFree(pCur); + pCur = pNext; + } + } + + RTHandleTableDestroy(g_hHndTableFilters, NULL, NULL); +} + + +/** + * Adds a new filter. + * + * The filter will be validate, duplicated and added. + * + * @returns IPRT status code. + * @param pFilter The filter. + * @param Owner The filter owner. Must be non-zero. + * @param puId Where to store the filter ID. + */ +int VBoxUSBFilterAdd(PCUSBFILTER pFilter, VBOXUSBFILTER_CONTEXT Owner, uintptr_t *puId) +{ + /* + * Validate input. + */ + int rc = USBFilterValidate(pFilter); + if (RT_FAILURE(rc)) + return rc; + if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL) + return VERR_INVALID_PARAMETER; + if (!RT_VALID_PTR(puId)) + return VERR_INVALID_POINTER; + + /* + * Allocate a new filter. + */ + PVBOXUSBFILTER pNew = (PVBOXUSBFILTER)RTMemAlloc(sizeof(*pNew)); + if (!pNew) + return VERR_NO_MEMORY; + memcpy(&pNew->Core, pFilter, sizeof(pNew->Core)); + pNew->Owner = Owner; + pNew->pNext = NULL; + + rc = RTHandleTableAlloc(g_hHndTableFilters, pNew, &pNew->uHnd); + if (RT_SUCCESS(rc)) + { + *puId = pNew->uHnd; + + /* + * Insert it. + */ + PVBOXUSBFILTERLIST pList = &g_aLists[pFilter->enmType]; + + VBOXUSBFILTERMGR_LOCK(); + + if (pList->pTail) + pList->pTail->pNext = pNew; + else + pList->pHead = pNew; + pList->pTail = pNew; + + VBOXUSBFILTERMGR_UNLOCK(); + } + else + RTMemFree(pNew); + + return rc; +} + + +/** + * Removes an existing filter. + * + * The filter will be validate, duplicated and added. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if successfully removed. + * @retval VERR_FILE_NOT_FOUND if the specified filter/owner cannot be found. + * + * @param Owner The filter owner. + * @param uId The ID of the filter that's to be removed. + * Returned by VBoxUSBFilterAdd(). + */ +int VBoxUSBFilterRemove(VBOXUSBFILTER_CONTEXT Owner, uintptr_t uId) +{ + /* + * Validate input. + */ + if (!uId || uId != (uint32_t)uId) + return VERR_INVALID_PARAMETER; + if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL) + return VERR_INVALID_PARAMETER; + + /* + * Locate and unlink it. + */ + uint32_t uHnd = (uint32_t)uId; + PVBOXUSBFILTER pCur = NULL; + + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; !pCur && i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + pCur = g_aLists[i].pHead; + while (pCur) + { + if ( pCur->uHnd == uHnd + && pCur->Owner == Owner) + { + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + break; + } + + pPrev = pCur; + pCur = pCur->pNext; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + /* + * Free it (if found). + */ + if (pCur) + { + void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + Assert(pv == pCur); NOREF(pv); + vboxUSBFilterFree(pCur); + return VINF_SUCCESS; + } + + return VERR_FILE_NOT_FOUND; +} + +VBOXUSBFILTER_CONTEXT VBoxUSBFilterGetOwner(uintptr_t uId) +{ + Assert(uId); + /* + * Validate input. + */ + if (!uId || uId != (uint32_t)uId) + return VBOXUSBFILTER_CONTEXT_NIL; + + /* + * Result. + */ + VBOXUSBFILTER_CONTEXT Owner = VBOXUSBFILTER_CONTEXT_NIL; + + VBOXUSBFILTERMGR_LOCK(); + + PVBOXUSBFILTER pCur = (PVBOXUSBFILTER)RTHandleTableLookup(g_hHndTableFilters, (uint32_t)uId); + if (pCur) + Owner = pCur->Owner; + + Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL); + + VBOXUSBFILTERMGR_UNLOCK(); + + return Owner; +} + +/** + * Removes all filters belonging to the specified owner. + * + * This is typically called when an owner disconnects or + * terminates unexpectedly. + * + * @param Owner The owner + */ +void VBoxUSBFilterRemoveOwner(VBOXUSBFILTER_CONTEXT Owner) +{ + /* + * Collect the filters that should be freed. + */ + PVBOXUSBFILTER pToFree = NULL; + + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + while (pCur) + { + if (pCur->Owner == Owner) + { + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + + pCur->pNext = pToFree; + pToFree = pCur; + + pCur = pNext; + } + else + { + pPrev = pCur; + pCur = pCur->pNext; + } + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + /* + * Free any filters we've found. + */ + while (pToFree) + { + PVBOXUSBFILTER pNext = pToFree->pNext; + void *pv = RTHandleTableFree(g_hHndTableFilters, pToFree->uHnd); + Assert(pv == pToFree); NOREF(pv); + vboxUSBFilterFree(pToFree); + pToFree = pNext; + } +} + +/** + * Match the specified device against the filters. + * Unlike the VBoxUSBFilterMatch, returns Owner also if exclude filter is matched + * + * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched. + * @param pDevice The device data as a filter structure. + * See USBFilterMatch for how to construct this. + * @param puId Where to store the filter id (optional). + * @param fRemoveFltIfOneShot Whether or not to remove one-shot filters on + * match. + * @param pfFilter Where to store whether the device must be filtered or not + * @param pfIsOneShot Where to return whetehr the match was a one-shot + * filter or not. Optional. + * + */ +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatchEx(PCUSBFILTER pDevice, uintptr_t *puId, + bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot) +{ + /* + * Validate input. + */ + int rc = USBFilterValidate(pDevice); + AssertRCReturn(rc, VBOXUSBFILTER_CONTEXT_NIL); + + *pfFilter = false; + if (puId) + *puId = 0; + + /* + * Search the lists for a match. + * (The lists are ordered by priority.) + */ + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + while (pCur) + { + if (USBFilterMatch(&pCur->Core, pDevice)) + { + /* + * Take list specific actions and return. + * + * The code does NOT implement the case where there are two or more + * filter clients, and one of them is releasing a device that's + * requested by some of the others. It's just too much work for a + * situation that noone will encounter. + */ + if (puId) + *puId = pCur->uHnd; + VBOXUSBFILTER_CONTEXT Owner = pCur->Owner; + *pfFilter = !!(i != USBFILTERTYPE_IGNORE + && i != USBFILTERTYPE_ONESHOT_IGNORE); + + if ( i == USBFILTERTYPE_ONESHOT_IGNORE + || i == USBFILTERTYPE_ONESHOT_CAPTURE) + { + if (fRemoveFltIfOneShot) + { + /* unlink */ + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + if ( i == USBFILTERTYPE_ONESHOT_IGNORE + || i == USBFILTERTYPE_ONESHOT_CAPTURE) + { + if (fRemoveFltIfOneShot) + { + void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + Assert(pv == pCur); NOREF(pv); + vboxUSBFilterFree(pCur); + } + if (pfIsOneShot) + *pfIsOneShot = true; + } + else + { + if (pfIsOneShot) + *pfIsOneShot = false; + } + return Owner; + } + + pPrev = pCur; + pCur = pCur->pNext; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + return VBOXUSBFILTER_CONTEXT_NIL; +} + +/** + * Match the specified device against the filters. + * + * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched. + * @param pDevice The device data as a filter structure. + * See USBFilterMatch for how to construct this. + * @param puId Where to store the filter id (optional). + */ +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatch(PCUSBFILTER pDevice, uintptr_t *puId) +{ + bool fFilter = false; + VBOXUSBFILTER_CONTEXT Owner = VBoxUSBFilterMatchEx(pDevice, puId, + true, /* remove filter is it's a one-shot*/ + &fFilter, NULL /* bool * fIsOneShot */); + if (fFilter) + { + Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL); + return Owner; + } + return VBOXUSBFILTER_CONTEXT_NIL; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h new file mode 100644 index 00000000..170c750a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h @@ -0,0 +1,68 @@ +/* $Id: VBoxUSBFilterMgr.h $ */ +/** @file + * VirtualBox Ring-0 USB Filter Manager. + */ + +/* + * 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 . + * + * 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_VBoxUSB_VBoxUSBFilterMgr_h +#define VBOX_INCLUDED_SRC_VBoxUSB_VBoxUSBFilterMgr_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +RT_C_DECLS_BEGIN + +/** @todo r=bird: VBOXUSBFILTER_CONTEXT isn't following the coding + * guildlines. Don't know which clueless dude did this... */ +#if defined(RT_OS_WINDOWS) +typedef struct VBOXUSBFLTCTX *VBOXUSBFILTER_CONTEXT; +#define VBOXUSBFILTER_CONTEXT_NIL NULL +#else +typedef RTPROCESS VBOXUSBFILTER_CONTEXT; +#define VBOXUSBFILTER_CONTEXT_NIL NIL_RTPROCESS +#endif + +int VBoxUSBFilterInit(void); +void VBoxUSBFilterTerm(void); +void VBoxUSBFilterRemoveOwner(VBOXUSBFILTER_CONTEXT Owner); +int VBoxUSBFilterAdd(PCUSBFILTER pFilter, VBOXUSBFILTER_CONTEXT Owner, uintptr_t *puId); +int VBoxUSBFilterRemove(VBOXUSBFILTER_CONTEXT Owner, uintptr_t uId); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatch(PCUSBFILTER pDevice, uintptr_t *puId); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatchEx(PCUSBFILTER pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterGetOwner(uintptr_t uId); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_VBoxUSBFilterMgr_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c new file mode 100644 index 00000000..5950cc6f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c @@ -0,0 +1,1278 @@ +#define INCL_DOSERRORS +#define INCL_DOSMEMMGR +#define INCL_DOSSEMAPHORES +#define INCL_DOSDEVICES +#define INCL_DOSDEVIOCTL +#define INCL_DOSMODULEMGR +#include + +#if !defined(__GNUC__) || defined(STATIC_USBCALLS) +#include +#else +#define memcpy __builtin_memcpy +#endif +#include +#include + +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include + +#ifdef __GNUC__ +# define APIEXPORT __declspec(dllexport) +#else +# define APIEXPORT +#endif + +#ifndef ERROR_USER_DEFINED_BASE +/*#define ERROR_USER_DEFINED_BASE 0xFF00 */ + +#define ERROR_I24_WRITE_PROTECT 0 +#define ERROR_I24_BAD_UNIT 1 +#define ERROR_I24_NOT_READY 2 +#define ERROR_I24_BAD_COMMAND 3 +#define ERROR_I24_CRC 4 +#define ERROR_I24_BAD_LENGTH 5 +#define ERROR_I24_SEEK 6 +#define ERROR_I24_NOT_DOS_DISK 7 +#define ERROR_I24_SECTOR_NOT_FOUND 8 +#define ERROR_I24_OUT_OF_PAPER 9 +#define ERROR_I24_WRITE_FAULT 10 +#define ERROR_I24_READ_FAULT 11 +#define ERROR_I24_GEN_FAILURE 12 +#define ERROR_I24_DISK_CHANGE 13 +#define ERROR_I24_WRONG_DISK 15 +#define ERROR_I24_UNCERTAIN_MEDIA 16 +#define ERROR_I24_CHAR_CALL_INTERRUPTED 17 +#define ERROR_I24_NO_MONITOR_SUPPORT 18 +#define ERROR_I24_INVALID_PARAMETER 19 +#define ERROR_I24_DEVICE_IN_USE 20 +#define ERROR_I24_QUIET_INIT_FAIL 21 +#endif + +#include "usbcalls.h" + +#define IOCAT_USBRES 0x000000A0 /* USB Resource device control */ +#define IOCTLF_NUMDEVICE 0x00000031 /* Get Number of plugged in Devices */ +#define IOCTLF_GETINFO 0x00000032 /* Get Info About a device */ +#define IOCTLF_AQUIREDEVICE 0x00000033 +#define IOCTLF_RELEASEDEVICE 0x00000034 +#define IOCTLF_GETSTRING 0x00000035 +#define IOCTLF_SENDCONTROLURB 0x00000036 +#define IOCTLF_SENDBULKURB 0x00000037 /* Send */ +#define IOCTLF_START_IRQ_PROC 0x00000038 /* Start IRQ polling in a buffer */ +#define IOCTLF_GETDEVINFO 0x00000039 /* Get information about device */ +#define IOCTLF_STOP_IRQ_PROC 0x0000003A /* Stop IRQ Polling */ +#define IOCTLF_START_ISO_PROC 0x0000003B /* Start ISO buffering in a Ringbuffer */ +#define IOCTLF_STOP_ISO_PROC 0x0000003C /* Stop ISO buffering */ +#define IOCTLF_CANCEL_IORB 0x0000003D /* Abort an IORB; */ +#define IOCTLF_SELECT_BULKPIPE 0x0000003E /* Select which Bulk endpoints can be used via Read/Write */ +#define IOCTLF_SENDIRQURB 0x0000003F /* Start IRQ polling in a buffer */ +#define IOCTLF_FIXUPDEVUCE 0x00000040 /* Fixup USB device configuration data */ +#define IOCTLF_REG_STATUSSEM 0x00000041 /* Register Semaphore for general Statuschange */ +#define IOCTLF_DEREG_STATUSSEM 0x00000042 /* Deregister Semaphore */ +#define IOCTLF_REG_DEVICESEM 0x00000043 /* Register Semaphore for a vendor&deviceID */ +#define IOCTLF_DEREG_DEVICESEM 0x00000044 /* Deregister Semaphore */ + + +#define NOTIFY_FREE 0 +#define NOTIFY_CHANGE 1 +#define NOTIFY_DEVICE 2 +#define MAX_NOTIFICATIONS 256 + +#pragma pack(1) + +typedef struct +{ + HEV hDeviceAdded; + HEV hDeviceRemoved; + USHORT usFlags; + USHORT usVendor; + USHORT usProduct; + USHORT usBCDDevice; +} NOTIFYENTRY, *PNOTIFYENTRY; + +#define DEV_SEM_ADD 0x00000001 +#define DEV_SEM_REMOVE 0x00000002 +#define DEV_SEM_MASK 0x00000003 +#define DEV_SEM_VENDORID 0x00000004 +#define DEV_SEM_PRODUCTID 0x00000008 +#define DEV_SEM_BCDDEVICE 0x00000010 + +typedef struct{ + ULONG ulSize; + ULONG ulCaps; + ULONG ulSemDeviceAdd; + ULONG ulSemDeviceRemove; +} STATUSEVENTSET, * PSTATUSEVENTSET; + + +typedef struct{ + ULONG ulSize; + ULONG ulCaps; + ULONG ulSemDeviceAdd; + ULONG ulSemDeviceRemove; + USHORT usVendorID; + USHORT usProductID; + USHORT usBCDDevice; + USHORT usStatus; +} DEVEVENTSET, * PDEVEVENTSET; + +typedef struct +{ + USHORT usVendorID; + USHORT usProductID; + USHORT usBCDDevice; + USHORT usDeviceNumber; /* Get the usDeviceNumber device in the system fi. if 2 acquire the 2nd device + 0 means first not acquired device. */ +} AQUIREDEV, *PAQUIREDEV; + +typedef struct +{ + UCHAR bRequestType; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + ULONG ulTimeout; /* in milliseconds */ +} SETUPPACKET, *PSETUPPACKET; + +typedef struct +{ + ULONG ulHandle; + UCHAR bRequestType; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + ULONG ulTimeout; /* in milliseconds */ + USHORT usStatus; +} USBCALLS_CTRL_REQ, *PUSBCALLS_CTRL_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; + ULONG ulID; +} USBCALLS_ISO_START, *NPUSBCALLS_ISO_START, FAR *PUSBCALLS_ISO_START, + USBCALLS_IRQ_START, *NPUSBCALLS_IRQ_START, FAR *PUSBCALLS_IRQ_START, + USBCALLS_CANCEL_REQ, *NPUSBCALLS_CANCEL_REQ, FAR *PUSBCALLS_CANCEL_REQ; + +#define ISO_DIRMASK 0x80 +typedef struct +{ + ULONG hSemAccess; /* Synchronise access to the Pos values */ + ULONG hDevice; + USHORT usPosWrite; + USHORT usPosRead; + USHORT usBufSize; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + UCHAR ucBuffer[16*1023]; +} ISORINGBUFFER, *PISORINGBUFFER; + +typedef USBCALLS_ISO_START USBCALLS_ISO_STOP, * NPUSBCALLS_ISO_STOP, FAR *PUSBCALLS_ISO_STOP; +typedef USBCALLS_ISO_START USBCALLS_IRQ_STOP, * NPUSBCALLS_IRQ_STOP, FAR *PUSBCALLS_IRQ_STOP; + +#define USB_TRANSFER_FULL_SIZE 0x01 + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; +/* ULONG ulID; - yeah, right */ + ULONG ulTimeout; + USHORT usDataProcessed; + USHORT usDataRemain; + USHORT usFlags; +} USBCALLS_BULK_REQ, *PUSBCALLS_BULK_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; + ULONG ulID; + ULONG ulTimeout; + USHORT usDataLen; +} LIBUSB_IRQ_REQ, *NPLIBUSB_IRQ_REQ, FAR *PLIBUSB_IRQ_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucConfiguration; + UCHAR ucAltInterface; + USHORT usStatus; +} LIBUSB_FIXUP, *NPLIBUSB_FIXUP, FAR *PLIBUSB_FIXUP; + +#pragma pack() + +/******************************************************************************/ + +HFILE g_hUSBDrv; +ULONG g_cInit; +ULONG g_ulFreeNotifys; +HMTX g_hSemNotifytable; +NOTIFYENTRY g_Notifications[MAX_NOTIFICATIONS]; + +HMTX g_hSemRingBuffers; +PISORINGBUFFER g_pIsoRingBuffers; +ULONG g_ulNumIsoRingBuffers; + +APIEXPORT APIRET APIENTRY +InitUsbCalls(void) +{ + int i; + ULONG ulAction; + APIRET rc; + + if (++g_cInit > 1) + return NO_ERROR; + + rc = DosOpen( (PCSZ)"USBRESM$", + &g_hUSBDrv, + &ulAction, + 0, + FILE_NORMAL, + OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_ACCESS_READWRITE | + OPEN_FLAGS_NOINHERIT | + OPEN_SHARE_DENYNONE, + 0 ); + if(rc) + { + g_hUSBDrv = 0; + g_cInit = 0; + + } + else + { + /* @@ToDO Add EnvVar or INI for dynamically setting the number */ + g_ulNumIsoRingBuffers = 8; + for(i=0;ihDevice = 0; + pIter->hSemAccess = 0; /* Synchronise access to the Pos values */ + pIter->usPosWrite = 0; + pIter->usPosRead = 0; + pIter->usBufSize = 16*1023; + pIter->ucEndpoint = 0; + pIter->ucAltInterface = 0; + /*pIter->ucBuffer */ + } + rc=DosCreateMutexSem(NULL,&g_hSemRingBuffers,DC_SEM_SHARED,FALSE); + if(!rc) + { + rc=DosCreateMutexSem(NULL,&g_hSemNotifytable,DC_SEM_SHARED,FALSE); + if(rc) + { + DosCloseMutexSem(g_hSemRingBuffers); + DosFreeMem(g_pIsoRingBuffers); + } + } + else + { + DosFreeMem(g_pIsoRingBuffers); + } + } + + if(rc) + { + DosClose(g_hUSBDrv); + g_hUSBDrv = 0; + g_cInit = 0; + } + } + return g_cInit ? NO_ERROR : rc ? rc : ERROR_GEN_FAILURE; +} + +APIEXPORT APIRET APIENTRY +TermUsbCalls(void) +{ + if (!g_cInit) + return ERROR_GEN_FAILURE; + if(!--g_cInit) + { + int i; + for(i=0;iMaxID) + return ERROR_INVALID_PARAMETER; + + Index = NotifyID - MinID; + + if(Index % sizeof(NOTIFYENTRY)) + return ERROR_INVALID_PARAMETER; + + Index /= sizeof(NOTIFYENTRY); + + rc = DosRequestMutexSem(g_hSemNotifytable,SEM_INDEFINITE_WAIT); + + switch(g_Notifications[Index].usFlags) + { + case NOTIFY_FREE: + DosReleaseMutexSem(g_hSemNotifytable); + return ERROR_INVALID_PARAMETER; + case NOTIFY_CHANGE: + ulFunction = IOCTLF_DEREG_STATUSSEM; + ulSize = sizeof(STATUSEVENTSET); + EventSet.ulSize = ulSize; + EventSet.ulCaps = DEV_SEM_ADD | DEV_SEM_REMOVE; + EventSet.ulSemDeviceAdd = g_Notifications[Index].hDeviceAdded; + EventSet.ulSemDeviceRemove = g_Notifications[Index].hDeviceRemoved; + break; + case NOTIFY_DEVICE: + ulFunction = IOCTLF_DEREG_DEVICESEM; + ulSize = sizeof(DEVEVENTSET); + EventSet.ulSize = ulSize; + EventSet.ulCaps = DEV_SEM_ADD | DEV_SEM_REMOVE | + DEV_SEM_VENDORID | DEV_SEM_PRODUCTID | + DEV_SEM_BCDDEVICE ; + EventSet.ulSemDeviceAdd = g_Notifications[Index].hDeviceAdded; + EventSet.ulSemDeviceRemove = g_Notifications[Index].hDeviceRemoved; + EventSet.usVendorID = g_Notifications[Index].usVendor; + EventSet.usProductID = g_Notifications[Index].usProduct; + EventSet.usBCDDevice = g_Notifications[Index].usBCDDevice; + EventSet.usStatus = 0; + break; + default: + DosReleaseMutexSem(g_hSemNotifytable); + return ERROR_GEN_FAILURE; + } + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, ulFunction, + NULL, 0, NULL, + &EventSet,ulSize, &ulSize); + if(0==rc) + { + g_Notifications[Index].usFlags = NOTIFY_FREE; + g_Notifications[Index].hDeviceAdded = 0; + g_Notifications[Index].hDeviceRemoved = 0; + g_Notifications[Index].usVendor = 0; + g_Notifications[Index].usProduct = 0; + g_Notifications[Index].usBCDDevice = 0; + } else + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= EventSet.usStatus; + } + DosReleaseMutexSem(g_hSemNotifytable); + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbOpen( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice) +{ + ULONG ulCat, ulFunc; + ULONG ulParmLen, ulDataLen; + AQUIREDEV Aquire; + APIRET rc; + + if(!g_cInit) + return USB_NOT_INIT; + if(IsBadWritePointer(pHandle,sizeof(USBHANDLE)) ) + return ERROR_INVALID_PARAMETER; + + Aquire.usVendorID = usVendor; + Aquire.usProductID = usProduct; + Aquire.usBCDDevice = usBCDDevice; + Aquire.usDeviceNumber = usEnumDevice; + ulCat = 0xA0; + ulFunc = 0x33; + ulParmLen = sizeof(Aquire); + ulDataLen = sizeof(USBHANDLE); + rc = DosDevIOCtl( g_hUSBDrv, + ulCat,ulFunc, /*IOCAT_USBRES, IOCTLF_AQUIREDEVICE, */ + &Aquire, ulParmLen, &ulParmLen, + pHandle, ulDataLen, &ulDataLen); + + /* @@ ToDO maybe gether some info about device here (endpoints etc for safety checks) */ + return rc; + +} + +APIEXPORT APIRET APIENTRY +UsbClose( USBHANDLE Handle) +{ + APIRET rc; + ULONG ulDataLen,ulParmLen; + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(USBHANDLE); + ulDataLen = 0; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_RELEASEDEVICE, + (PVOID)&Handle, ulParmLen, &ulParmLen, + NULL, ulDataLen, &ulDataLen); + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbCtrlMessage( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pData, + ULONG ulTimeout) +{ + APIRET rc; + USBCALLS_CTRL_REQ CtrlRequest; + ULONG ulParmLen, ulDataLen; + + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(USBCALLS_CTRL_REQ); + CtrlRequest.ulHandle = Handle; + CtrlRequest.bRequestType = ucRequestType; + CtrlRequest.bRequest = ucRequest; + CtrlRequest.wValue = usValue; + CtrlRequest.wIndex = usIndex; + CtrlRequest.wLength = usLength; + CtrlRequest.ulTimeout = ulTimeout; + ulDataLen = usLength; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDCONTROLURB, + (PVOID)&CtrlRequest, ulParmLen, &ulParmLen, + ulDataLen>0?(PVOID)pData:NULL, + ulDataLen, + ulDataLen>0?&ulDataLen:NULL); + if( rc != NO_ERROR ) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= CtrlRequest.usStatus; + } + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbBulkRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + return UsbBulkRead2(Handle, Endpoint, AltInterface, TRUE /* fShortOk */, ulNumBytes, pvData, ulTimeout); +} + +APIEXPORT APIRET APIENTRY +UsbBulkRead2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen, ulToProcess, ulTotalProcessed; + USBCALLS_BULK_REQ BulkRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + if(*ulNumBytes==0) + return 0; + + /* just require this */ + if ((ULONG)pvData & 0xfff) + return ERROR_INVALID_ADDRESS; + if ((ULONG)pvData >= 0x20000000) + return ERROR_INVALID_ADDRESS; + + ulToProcess = *ulNumBytes; + ulTotalProcessed = 0; + + do + { + /* Process up to 64k, making sure we're working on segments. */ + ulDataLen = 0x10000 - ((ULONG)pvData & 0xffff); + if (ulDataLen > ulToProcess) + ulDataLen = ulToProcess; + + ulParmLen = sizeof(USBCALLS_BULK_REQ); + + memset(&BulkRequest, 0, sizeof(BulkRequest)); + BulkRequest.ulDevHandle = Handle; + BulkRequest.ucEndpoint = Endpoint; + BulkRequest.ucAltInterface = AltInterface; + BulkRequest.usStatus = 0; + BulkRequest.ulEvent = 0; + //BulkRequest.ulID = (ULONG)pvData; + BulkRequest.ulTimeout = ulTimeout; + BulkRequest.usDataProcessed = 0; + BulkRequest.usDataRemain = ulDataLen; + BulkRequest.usFlags = fShortOk && ulDataLen == ulToProcess ? 0 : USB_TRANSFER_FULL_SIZE; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDBULKURB, + (PVOID)&BulkRequest, ulParmLen, &ulParmLen, + pvData, ulDataLen, &ulDataLen); + Log(("BulkRead: usStatus=%d rc=%ld usDataProcessed=%d usDataRemain=%d ulDataLen=%ld\n", + BulkRequest.usStatus, rc, BulkRequest.usDataProcessed, BulkRequest.usDataRemain, ulDataLen)); + + if (rc != NO_ERROR) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= BulkRequest.usStatus; + break; + } + + /* Adjust count and source pointer */ + ulToProcess -= ulDataLen; + pvData = (PBYTE)pvData + ulDataLen; + ulTotalProcessed += BulkRequest.usDataProcessed; + + if (BulkRequest.usDataProcessed != ulDataLen) + { + /* Transferred less than we wanted? so something is wrong, + or device doesn't wish to send more, exit loop */ + rc = USB_ERROR_LESSTRANSFERED; + break; + } + } while( ulToProcess>0 ); + + *ulNumBytes = ulTotalProcessed; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbBulkWrite( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + return UsbBulkWrite2(Handle, Endpoint, AltInterface, FALSE /* fShortOk */, ulNumBytes, pvData, ulTimeout); +} + +APIEXPORT APIRET APIENTRY +UsbBulkWrite2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + USBCALLS_BULK_REQ BulkRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + /* just require this */ + if ((ULONG)pvData & 0xfff) + return ERROR_INVALID_ADDRESS; + if ((ULONG)pvData >= 0x20000000) + return ERROR_INVALID_ADDRESS; + + do + { + /* Process up to 64k, making sure we're working on segments. */ + ulDataLen = 0x10000 - ((ULONG)pvData & 0xffff); + if (ulDataLen > ulNumBytes) + ulDataLen = ulNumBytes; + + ulParmLen = sizeof(USBCALLS_BULK_REQ); + + memset(&BulkRequest, 0, sizeof(BulkRequest)); + BulkRequest.ulDevHandle = Handle; + BulkRequest.ucEndpoint = Endpoint; + BulkRequest.ucAltInterface = AltInterface; + BulkRequest.usStatus = 0; + BulkRequest.ulEvent = 0; + //BulkRequest.ulID = (ULONG)pvData; + BulkRequest.ulTimeout = ulTimeout; + BulkRequest.usDataProcessed = 0; + BulkRequest.usDataRemain = ulDataLen; + BulkRequest.usFlags = fShortOk && ulDataLen == ulNumBytes ? 0 : USB_TRANSFER_FULL_SIZE; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDBULKURB, + &BulkRequest, ulParmLen, &ulParmLen, + pvData, ulDataLen, &ulDataLen ); + Log(("BulkWrite: usStatus=%d rc=%ld usDataProcessed=%d usDataRemain=%d ulDataLen=%ld\n", + BulkRequest.usStatus, rc, BulkRequest.usDataProcessed, BulkRequest.usDataRemain, ulDataLen)); + if (rc != NO_ERROR) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= BulkRequest.usStatus; + break; + } + /* Adjust count and source pointer */ + ulNumBytes -= ulDataLen; + pvData = (PBYTE)pvData + ulDataLen; + } while( ulNumBytes > 0 ); + + return rc; +} + +APIRET APIENTRY +UsbIrqStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + USHORT ulNumBytes, + PVOID pData, + PHEV pHevModified) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + USBCALLS_IRQ_START IrqStart; + HEV hEvent; + + if(!g_cInit) + return USB_NOT_INIT; + + if(0==ulNumBytes || IsBadWritePointer(pData, ulNumBytes)) + return ERROR_INVALID_PARAMETER; + + rc = DosCreateEventSem( NULL, + &hEvent, + DC_SEM_SHARED, + FALSE); + if(rc) + return rc; + + IrqStart.ulDevHandle = Handle; + IrqStart.ucEndpoint = Endpoint; + IrqStart.ucAltInterface = AltInterface; + IrqStart.usStatus = 0; + IrqStart.ulEvent = hEvent; + ulParmLen = sizeof(IrqStart); + ulDataLen = ulNumBytes; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_START_IRQ_PROC, + (PVOID)&IrqStart, ulParmLen, &ulParmLen, + pData, ulDataLen,&ulDataLen); + if(rc) + DosCloseEventSem(hEvent); + else + *pHevModified = hEvent; + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIrqStop( USBHANDLE Handle, + HEV HevModified) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(Handle); + ulDataLen = sizeof(HevModified); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_STOP_IRQ_PROC, + (PVOID)&Handle, ulParmLen, &ulParmLen, + &HevModified, ulDataLen, &ulDataLen); + if(!rc) + DosCloseEventSem(HevModified); + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ISOHANDLE *phIso) +{ + APIRET rc; + PISORINGBUFFER pIter = g_pIsoRingBuffers; + USBCALLS_ISO_START IsoStart; + ULONG ulParmLen, ulDataLen; + int i; + + if(!g_cInit) + return USB_NOT_INIT; + + rc = DosRequestMutexSem(g_hSemRingBuffers,SEM_INDEFINITE_WAIT); + if(rc) + return rc; + + for(i=0;i< g_ulNumIsoRingBuffers;i++,pIter++) + { + if (pIter->hDevice==0) + { + pIter->hDevice = Handle; + break; + } + } + DosReleaseMutexSem(g_hSemRingBuffers); + + if(i==g_ulNumIsoRingBuffers) + return USB_ERROR_OUTOF_RESOURCES; + + IsoStart.ulDevHandle = Handle; + IsoStart.ucEndpoint = Endpoint; + IsoStart.ucAltInterface = AltInterface; + ulParmLen = sizeof(IsoStart); + ulDataLen = sizeof(ISORINGBUFFER); + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_STOP_IRQ_PROC, + (PVOID)&IsoStart, ulParmLen, &ulParmLen, + pIter, ulDataLen, &ulDataLen); + if(rc) + { + pIter->hDevice = 0; + *phIso = 0; + } + else + { + pIter->ucEndpoint = Endpoint; + pIter->ucAltInterface = AltInterface; + } + return rc; +} + +static APIRET IsInvalidIsoHandle(const ISOHANDLE hIso) +{ + PISORINGBUFFER pIter; + ULONG i; + pIter = g_pIsoRingBuffers; + + for(i=0;ihDevice) + return 0; + } + return ERROR_INVALID_PARAMETER; +} + +APIEXPORT APIRET APIENTRY +UsbIsoStop( ISOHANDLE hIso) +{ + + APIRET rc = NO_ERROR; + if(!g_cInit) + return USB_NOT_INIT; + +/* rc = DosDevIOCtl( g_hUSBDrv, */ + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoDequeue( ISOHANDLE hIso, + PVOID pBuffer, + ULONG ulNumBytes) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(!(pRB->ucEndpoint & ISO_DIRMASK)) + return ERROR_INVALID_PARAMETER; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoPeekQueue( ISOHANDLE hIso, + UCHAR * pByte, + ULONG ulOffset) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(!(pRB->ucEndpoint & ISO_DIRMASK)) + return ERROR_INVALID_PARAMETER; + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoEnqueue( ISOHANDLE hIso, + const UCHAR * pBuffer, + ULONG ulNumBytes) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(pRB->ucEndpoint & ISO_DIRMASK) + return ERROR_INVALID_PARAMETER; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoGetLength( ISOHANDLE hIso, + ULONG *pulLength) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER) hIso; + USHORT ri,wi; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + wi = pRB->usPosWrite; + ri = pRB->usPosRead; + + if (ri == wi) + *pulLength = 0; + else if (ri < wi) + *pulLength = wi - ri; + else + *pulLength = wi + (pRB->usBufSize - ri); + + return 0; +} + +APIEXPORT APIRET APIENTRY +UsbIrqRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG *ulNumBytes, + PVOID pData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + LIBUSB_IRQ_REQ IrqRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + /* 10 01 2003 - KIEWITZ -> Still @@ToDo Add Endpoint check based on descriptors + We currently only allow Endpoint-addresses 80h->8Fh here */ + if ((Endpoint<0x80) || (Endpoint>0x8F)) + return USB_ERROR_INVALID_ENDPOINT; + + if(*ulNumBytes==0) + return 0; + + IrqRequest.ulDevHandle = Handle; + IrqRequest.ucEndpoint = Endpoint; + IrqRequest.ucAltInterface = AltInterface; + IrqRequest.usStatus = 0; + IrqRequest.ulEvent = 0; + IrqRequest.ulTimeout = ulTimeout; + ulParmLen = sizeof(LIBUSB_IRQ_REQ); + ulDataLen = *ulNumBytes; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDIRQURB, + (PVOID)&IrqRequest, ulParmLen, &ulParmLen, + pData, ulDataLen, &ulDataLen); + + if( rc == NO_ERROR ) + { + *ulNumBytes = IrqRequest.usDataLen; + } else + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= IrqRequest.usStatus; + } + return rc; +} + + +APIEXPORT APIRET APIENTRY +UsbFixupDevice( USBHANDLE Handle, + UCHAR ucConfiguration, + UCHAR *pucConfigurationData, + ULONG ulConfigurationLen ) +{ + LIBUSB_FIXUP request; + ULONG ulParmLen; + APIRET rc; + + request.ulDevHandle= Handle; + request.ucConfiguration= ucConfiguration; + request.usStatus= 0; + ulParmLen= sizeof(LIBUSB_FIXUP); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_FIXUPDEVUCE, + (PVOID)&request, ulParmLen, &ulParmLen, + pucConfigurationData, ulConfigurationLen, &ulConfigurationLen); + if( rc != NO_ERROR ) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= request.usStatus; + } + return rc; +} + +#ifndef STATIC_USBCALLS + /*+-------------------------------------------------------------------+*/ + /*| _CRT_init is the C run-time environment initialization function. |*/ + /*|It will return 0 to indicate success and -1 to indicate failure. |*/ + /*+-------------------------------------------------------------------+*/ + +/* int _CRT_init (void); */ + + /*+-------------------------------------------------------------------+*/ + /*| _CRT_term is the C run-time environment termination function. |*/ + /*+-------------------------------------------------------------------+*/ + +/* void _CRT_term (unsigned long);*/ + + /*+-------------------------------------------------------------------+*/ + /*| _DLL_InitTerm is the function that gets called by the operating |*/ + /*| system loader when it loads and frees this DLL for each process |*/ + /*| that accesses this DLL. However, it only gets called the first |*/ + /*| time the DLL is loaded and the last time it is freed for a |*/ + /*| particular process. The system linkage convention must be used |*/ + /*| because the operating system loader is calling this function. |*/ + /*+-------------------------------------------------------------------+*/ + +#ifdef STATIC_LINK +int _CRT_init (void); +void _CRT_term(0UL); +#endif + +unsigned long _System _DLL_InitTerm (unsigned long modhandle, unsigned long flag) +{ + + /* If flag is zero then the DLL is being loaded so initialization */ + /* should be performed. If flag is 1 then the DLL is being freed */ + /* so termination should be performed. */ + + switch (flag) + { + case 0: + /* The C run-time environment initialization function must */ + /* be called before any calls to C run-time functions that */ + /* are not inlined. */ + +#ifdef STATIC_LINK + if (_CRT_init () == -1) + return 0UL; +#endif + InitUsbCalls(); + break; + + case 1: + TermUsbCalls(); +#ifdef STATIC_LINK + _CRT_term(0UL); +#endif + break; + + default: + return 0UL; + + } + + /* A nonzero value must be returned to indicate success. */ + return 1UL; +} +#endif /* !STATIC_USBCALLS */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h new file mode 100644 index 00000000..151a5bc1 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h @@ -0,0 +1,242 @@ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h +#define VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +typedef ULONG USBHANDLE, *PUSBHANDLE; +typedef ULONG USBNOTIFY, *PUSBNOTIFY; +typedef ULONG ISOHANDLE, *PISOHANDLE; + +#define USB_NOT_INIT 7000 +#define USB_ERROR_NO_MORE_NOTIFICATIONS 7001 +#define USB_ERROR_OUTOF_RESOURCES 7002 +#define USB_ERROR_INVALID_ENDPOINT 7003 +#define USB_ERROR_LESSTRANSFERED 7004 + +/* IORB status codes */ +#define USB_IORB_DONE 0x0000 +#define USB_IORB_FAILED 0x8000 + +#define USB_ANY_PRODUCTVERSION 0xFFFF +#define USB_OPEN_FIRST_UNUSED 0 + +#ifdef USB_BIND_DYNAMIC + typedef APIRET APIENTRY USBREGISTERDEVICENOTIFICATION( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDVersion); + typedef USBREGISTERDEVICENOTIFICATION *PUSBREGISTERDEVICENOTIFICATION; + + typedef APIRET APIENTRY USBDEREGISTERNOTIFICATION( USBNOTIFY NotifyID); + + typedef USBDEREGISTERNOTIFICATION *PUSBDEREGISTERNOTIFICATION; + + typedef APIRET APIENTRY USBOPEN( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice); + typedef USBOPEN *PUSBOPEN; + typedef APIRET APIENTRY USBCLOSE( USBHANDLE Handle); + typedef USBCLOSE *PUSBCLOSE; + + typedef APIRET APIENTRY USBCTRLMESSAGE( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pvData, + ULONG ulTimeout); + typedef USBCTRLMESSAGE *PUSBCTRLMESSAGE; +#else + APIRET APIENTRY UsbQueryNumberDevices( ULONG *pulNumDev); + + APIRET APIENTRY UsbQueryDeviceReport( ULONG ulDevNumber, + ULONG *ulBufLen, + PVOID pvData); + APIRET APIENTRY UsbRegisterChangeNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved); + + APIRET APIENTRY UsbRegisterDeviceNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDVersion); + + APIRET APIENTRY UsbDeregisterNotification( USBNOTIFY NotifyID); + + APIRET APIENTRY UsbOpen( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice); + + APIRET APIENTRY UsbClose( USBHANDLE Handle); + + APIRET APIENTRY UsbCtrlMessage( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkRead2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + BOOL fShortOk, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkWrite( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkWrite2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbIrqStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + USHORT usNumBytes, + PVOID pvData, + PHEV pHevModified); + APIRET APIENTRY UsbIrqStop( USBHANDLE Handle, + HEV HevModified); + + APIRET APIENTRY UsbIsoStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ISOHANDLE *phIso); + APIRET APIENTRY UsbIsoStop( ISOHANDLE hIso); + + APIRET APIENTRY UsbIsoDequeue( ISOHANDLE hIso, + PVOID pBuffer, + ULONG ulNumBytes); + APIRET APIENTRY UsbIsoEnqueue( ISOHANDLE hIso, + const UCHAR * pBuffer, + ULONG ulNumBytes); + APIRET APIENTRY UsbIsoPeekQueue( ISOHANDLE hIso, + UCHAR * pByte, + ULONG ulOffset); + APIRET APIENTRY UsbIsoGetLength( ISOHANDLE hIso, + ULONG *pulLength); + + APIRET APIENTRY UsbIrqRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbFixupDevice( USBHANDLE Handle, + UCHAR ucConfiguration, + UCHAR *pucConfigurationData, + ULONG ulConfigurationLen ); + + APIRET APIENTRY InitUsbCalls(void); + APIRET APIENTRY TermUsbCalls(void); + + /* Standard USB Requests See 9.4. in USB 1.1. spec. */ + + /* 09 01 2003 - KIEWITZ */ + #define UsbDeviceClearFeature(HANDLE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x00, 0x01, FEAT, 0, 0, NULL, 0) + #define UsbDeviceSetFeature(HANDLE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, 0, 0, NULL, 0) + #define UsbInterfaceClearFeature(HANDLE, IFACE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x01, 0x01, FEAT, IFACE, 0, NULL, 0) + #define UsbInterfaceSetFeature(HANDLE, IFACE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, IFACE, 0, NULL, 0) + #define UsbEndpointClearFeature(HANDLE, ENDPOINT, FEAT) \ + UsbCtrlMessage(HANDLE, 0x02, 0x01, FEAT, ENDPOINT, 0, NULL, 0) + #define UsbEndpointSetFeature(HANDLE, ENDPOINT, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, ENDPOINT, 0, NULL, 0) + #define FEATURE_DEVICE_REMOTE_WAKEUP 1 + #define FEATURE_ENDPOINT_HALT 0 + #define UsbEndpointClearHalt(HANDLE, ENDPOINT) \ + UsbEndpointClearFeature(HANDLE, ENDPOINT, FEATURE_ENDPOINT_HALT) + + #define UsbDeviceGetConfiguration(HANDLE, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x08, 0, 0, 1, DATA, 0) + #define UsbDeviceSetConfiguration(HANDLE, CONFIG) \ + UsbCtrlMessage(HANDLE, 0x00, 0x09, CONFIG, 0, 0, NULL, 0) + + #define UsbDeviceGetStatus(HANDLE, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, 0, 2, STATUS, 0) + #define UsbInterfaceGetStatus(HANDLE, IFACE, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, IFACE, 2, STATUS, 0) + #define UsbEndpointGetStatus(HANDLE, ENDPOINT, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, ENDPOINT, 2, STATUS, 0) + + #define STATUS_ENDPOINT_HALT 0x0001 + #define STATUS_DEVICE_SELFPOWERD 0x0001 + #define STATUS_DEVICE_REMOTEWAKEUP 0x0002 + + #define UsbDeviceSetAddress(HANDLE, ADDRESS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x05, ADDRESS, 0, 0, NULL, 0) + + #define UsbDeviceGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0100|INDEX), LID, LEN, DATA, 0) + #define UsbDeviceSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0100|INDEX), LID, LEN, DATA, 0) + #define UsbConfigurationGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0200|INDEX), LID, LEN, DATA, 0) + #define UsbConfigurationSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0200|INDEX), LID, LEN, DATA, 0) + #define UsbStringGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0300|INDEX), LID, LEN, DATA, 0) + #define UsbStringSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0300|INDEX), LID, LEN, DATA, 0) + #define UsbInterfaceGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0400|INDEX), LID, LEN, DATA, 0) + #define UsbInterfaceSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0400|INDEX), LID, LEN, DATA, 0) + #define UsbEndpointGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0500|INDEX), LID, LEN, DATA, 0) + #define UsbEndpointSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0500|INDEX), LID, LEN, DATA, 0) + + #define UsbInterfaceGetAltSetting(HANDLE, IFACE, SETTING) \ + UsbCtrlMessage(HANDLE, 0x81, 0x0A, 0, IFACE, 1, SETTING, 0) + #define UsbInterfaceSetAltSetting(HANDLE, IFACE, ALTSET) \ + UsbCtrlMessage(HANDLE, 0x01, 0x0B, ALTSET, IFACE, 0, NULL, 0) + + #define UsbEndpointSynchFrame(HANDLE, ENDPOINT, FRAMENUM) \ + UsbCtrlMessage(HANDLE, 0x82, 0x0B, 0, ENDPOINT, 2, FRAMENUM, 0) +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk new file mode 100644 index 00000000..2ba0f963 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk @@ -0,0 +1,67 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Solaris VBoxUSB kernel extension. +# + +# +# 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 . +# +# 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 + +# +# vboxusbmon - The Solaris USB Monitor Kernel module. +# +SYSMODS.solaris += vboxusbmon +vboxusbmon_TEMPLATE = VBoxR0Drv +vboxusbmon_DEFS = IN_RT_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxusbmon_DEPS += $(VBOX_SVN_REV_KMK) +vboxusbmon_INCS := . .. include +vboxusbmon_LDFLAGS += -N drv/vboxdrv -N misc/usba +vboxusbmon_SOURCES = \ + VBoxUSBMon-solaris.c \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp + +# +# vboxusb - The Solaris Generic USB Client Kernel module. +# +SYSMODS.solaris += vboxusb +vboxusb_TEMPLATE = VBoxR0Drv +vboxusb_DEFS = IN_RT_R0 IN_SUP_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxusb_DEPS += $(VBOX_SVN_REV_KMK) +vboxusb_INCS := . include/ +vboxusb_LDFLAGS += -N drv/vboxdrv -N drv/vboxusbmon -N misc/usba +vboxusb_SOURCES = \ + VBoxUSB-solaris.c + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp b/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp new file mode 100644 index 00000000..10eaf026 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp @@ -0,0 +1,279 @@ +/** $Id: USBLib-solaris.cpp $ */ +/** @file + * USBLib - Library for wrapping up the VBoxUSB functionality, Solaris flavor. + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +# include +# include +# include +# include +# include +# include +# include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Logging class. */ +#define USBLIBR3 "USBLibR3" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Reference counter. */ +static uint32_t volatile g_cUsers = 0; +/** VBoxUSB Device handle. */ +static RTFILE g_hFile = NIL_RTFILE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usblibDoIOCtl(unsigned iFunction, void *pvData, size_t cbData); + + +USBLIB_DECL(int) USBLibInit(void) +{ + LogFlow((USBLIBR3 ":USBLibInit\n")); + + /* + * Already open? + * This isn't properly serialized, but we'll be fine with the current usage. + */ + if (g_cUsers) + { + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; + } + + RTFILE File; + int rc = RTFileOpen(&File, VBOXUSB_DEVICE_NAME, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + LogRel((USBLIBR3 ":failed to open the VBoxUSB monitor device node '%s' rc=%Rrc\n", VBOXUSB_DEVICE_NAME, rc)); + return rc; + } + g_hFile = File; + + ASMAtomicIncU32(&g_cUsers); + /* + * Check the USBMonitor version. + */ + VBOXUSBREQ_GET_VERSION Req; + bzero(&Req, sizeof(Req)); + rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_GET_VERSION, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + { + if ( Req.u32Major != VBOXUSBMON_VERSION_MAJOR + || Req.u32Minor < VBOXUSBMON_VERSION_MINOR) + { + rc = VERR_VERSION_MISMATCH; + LogRel((USBLIBR3 ":USBMonitor version mismatch! driver v%d.%d, expecting ~v%d.%d\n", + Req.u32Major, Req.u32Minor, VBOXUSBMON_VERSION_MAJOR, VBOXUSBMON_VERSION_MINOR)); + + RTFileClose(File); + g_hFile = NIL_RTFILE; + ASMAtomicDecU32(&g_cUsers); + return rc; + } + } + else + { + LogRel((USBLIBR3 ":USBMonitor driver version query failed. rc=%Rrc\n", rc)); + RTFileClose(File); + g_hFile = NIL_RTFILE; + ASMAtomicDecU32(&g_cUsers); + return rc; + } + + return VINF_SUCCESS; +} + + +USBLIB_DECL(int) USBLibTerm(void) +{ + LogFlow((USBLIBR3 ":USBLibTerm\n")); + + if (!g_cUsers) + return VERR_WRONG_ORDER; + if (ASMAtomicDecU32(&g_cUsers) != 0) + return VINF_SUCCESS; + + /* + * We're the last guy, close down the connection. + */ + RTFILE File = g_hFile; + g_hFile = NIL_RTFILE; + if (File == NIL_RTFILE) + return VERR_INTERNAL_ERROR; + + int rc = RTFileClose(File); + AssertRC(rc); + return rc; +} + + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + LogFlow((USBLIBR3 ":USBLibAddFilter pFilter=%p\n", pFilter)); + + VBOXUSBREQ_ADD_FILTER Req; + Req.Filter = *pFilter; + Req.uId = 0; + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_ADD_FILTER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + return (void *)Req.uId; + + AssertMsgFailed((USBLIBR3 ":VBOXUSBMON_IOCTL_ADD_FILTER failed! rc=%Rrc\n", rc)); + return NULL; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + LogFlow((USBLIBR3 ":USBLibRemoveFilter pvId=%p\n", pvId)); + + VBOXUSBREQ_REMOVE_FILTER Req; + Req.uId = (uintptr_t)pvId; + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_REMOVE_FILTER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + return; + + AssertMsgFailed((USBLIBR3 ":VBOXUSBMON_IOCTL_REMOVE_FILTER failed! rc=%Rrc\n", rc)); +} + + +USBLIB_DECL(int) USBLibGetClientInfo(char *pszDeviceIdent, char **ppszClientPath, int *pInstance) +{ + LogFlow((USBLIBR3 ":USBLibGetClientInfo pszDeviceIdent=%s ppszClientPath=%p pInstance=%p\n", + pszDeviceIdent, ppszClientPath, pInstance)); + + AssertPtrReturn(pInstance, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppszClientPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszDeviceIdent, VERR_INVALID_PARAMETER); + + VBOXUSBREQ_CLIENT_INFO Req; + bzero(&Req, sizeof(Req)); + RTStrPrintf(Req.szDeviceIdent, sizeof(Req.szDeviceIdent), "%s", pszDeviceIdent); + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_CLIENT_INFO, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + { + *pInstance = Req.Instance; + rc = RTStrDupEx(ppszClientPath, Req.szClientPath); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + LogRel((USBLIBR3 ":USBLibGetClientInfo RTStrDupEx failed! rc=%Rrc szClientPath=%s\n", rc, Req.szClientPath)); + } + else + LogRel((USBLIBR3 ":USBLibGetClientInfo VBOXUSBMON_IOCTL_CLIENTPATH failed! rc=%Rrc\n", rc)); + + return rc; +} + + +USBLIB_DECL(int) USBLibResetDevice(char *pszDevicePath, bool fReattach) +{ + LogFlow((USBLIBR3 ":USBLibResetDevice pszDevicePath=%s\n", pszDevicePath)); + + size_t cbPath = strlen(pszDevicePath) + 1; + size_t cbReq = sizeof(VBOXUSBREQ_RESET_DEVICE) + cbPath; + VBOXUSBREQ_RESET_DEVICE *pReq = (VBOXUSBREQ_RESET_DEVICE *)RTMemTmpAllocZ(cbReq); + if (RT_UNLIKELY(!pReq)) + return VERR_NO_MEMORY; + + pReq->fReattach = fReattach; + if (strlcpy(pReq->szDevicePath, pszDevicePath, cbPath) >= cbPath) + { + LogRel((USBLIBR3 ":USBLibResetDevice buffer overflow. cbPath=%u pszDevicePath=%s\n", cbPath, pszDevicePath)); + return VERR_BUFFER_OVERFLOW; + } + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_RESET_DEVICE, pReq, cbReq); + if (RT_FAILURE(rc)) + LogRel((USBLIBR3 ":VBOXUSBMON_IOCTL_RESET_DEVICE failed! rc=%Rrc\n", rc)); + + RTMemFree(pReq); + return rc; +} + + +static int usblibDoIOCtl(unsigned iFunction, void *pvData, size_t cbData) +{ + if (g_hFile == NIL_RTFILE) + { + LogRel((USBLIBR3 ":IOCtl failed, device not open.\n")); + return VERR_FILE_NOT_FOUND; + } + + VBOXUSBREQ Hdr; + Hdr.u32Magic = VBOXUSBMON_MAGIC; + Hdr.cbData = cbData; /* Don't include full size because the header size is fixed. */ + Hdr.pvDataR3 = pvData; + + int rc = ioctl(RTFileToNative(g_hFile), iFunction, &Hdr); + if (rc < 0) + { + rc = errno; + LogRel((USBLIBR3 ":IOCtl failed iFunction=%x errno=%d g_file=%d\n", iFunction, rc, RTFileToNative(g_hFile))); + return RTErrConvertFromErrno(rc); + } + + rc = Hdr.rc; + if (RT_UNLIKELY(RT_FAILURE(rc))) + LogRel((USBLIBR3 ":Function (%x) failed. rc=%Rrc\n", iFunction, rc)); + + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c new file mode 100644 index 00000000..0568bf66 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c @@ -0,0 +1,4054 @@ +/* $Id: VBoxUSB-solaris.c $ */ +/** @file + * VirtualBox USB Client Driver, Solaris Hosts. + */ + +/* + * 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 . + * + * 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_USB_DRV +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USBDRV_MAJOR_VER 2 +#define USBDRV_MINOR_VER 0 +#include +#include +#include "usbai_private.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxusb" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox USB" + +/** -=-=-=-=-=-=- Standard Specifics -=-=-=-=-=-=- */ +/** Max. supported endpoints. */ +#define VBOXUSB_MAX_ENDPOINTS 32 +/** Size of USB Ctrl Xfer Header in bytes. */ +#define VBOXUSB_CTRL_XFER_SIZE 8 +/** + * USB2.0 (Sec. 9-13) Bits 10..0 is the max packet size; for high speed Isoc/Intr, bits 12..11 is + * number of additional transaction opportunities per microframe. + */ +#define VBOXUSB_PKT_SIZE(pkt) (pkt & 0x07FF) * (1 + ((pkt >> 11) & 3)) +/** Endpoint Xfer Type. */ +#define VBOXUSB_XFER_TYPE(endp) ((endp)->EpDesc.bmAttributes & USB_EP_ATTR_MASK) +/** Endpoint Xfer Direction. */ +#define VBOXUSB_XFER_DIR(endp) ((endp)->EpDesc.bEndpointAddress & USB_EP_DIR_IN) +/** Create an Endpoint index from an Endpoint address. */ +#define VBOXUSB_GET_EP_INDEX(epaddr) (((epaddr) & USB_EP_NUM_MASK) + \ + (((epaddr) & USB_EP_DIR_MASK) ? 16 : 0)) + + +/** -=-=-=-=-=-=- Tunable Parameters -=-=-=-=-=-=- */ +/** Time to wait while draining inflight UBRs on suspend, in seconds. */ +#define VBOXUSB_DRAIN_TIME 20 +/** Ctrl Xfer timeout in seconds. */ +#define VBOXUSB_CTRL_XFER_TIMEOUT 15 +/** Maximum URB queue length. */ +#define VBOXUSB_URB_QUEUE_SIZE 512 +/** Maximum asynchronous requests per pipe. */ +#define VBOXUSB_MAX_PIPE_ASYNC_REQS 2 + +/** For enabling global symbols while debugging. **/ +#if defined(DEBUG_ramshankar) +# define LOCAL +#else +# define LOCAL static +#endif + + +/********************************************************************************************************************************* +* Kernel Entry Hooks * +*********************************************************************************************************************************/ +int VBoxUSBSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +int VBoxUSBSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +int VBoxUSBSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +int VBoxUSBSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +int VBoxUSBSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal); +int VBoxUSBSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead); +int VBoxUSBSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult); +int VBoxUSBSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +int VBoxUSBSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +int VBoxUSBSolarisPower(dev_info_t *pDip, int Component, int Level); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxUSBSolarisCbOps = +{ + VBoxUSBSolarisOpen, + VBoxUSBSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + VBoxUSBSolarisRead, + VBoxUSBSolarisWrite, + VBoxUSBSolarisIOCtl, + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + VBoxUSBSolarisPoll, + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV, /* revision */ + nodev, /* c aread */ + nodev /* c awrite */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_VBoxUSBSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxUSBSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxUSBSolarisAttach, + VBoxUSBSolarisDetach, + nodev, /* reset */ + &g_VBoxUSBSolarisCbOps, + NULL, /* bus ops */ + VBoxUSBSolarisPower, + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxUSBSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxUSBSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxUSBSolarisModLinkage = +{ + MODREV_1, + &g_VBoxUSBSolarisModule, + NULL, +}; + +/** + * vboxusb_ep_t: Endpoint structure with info. for managing an endpoint. + */ +typedef struct vboxusb_ep_t +{ + bool fInitialized; /* Whether this Endpoint is initialized */ + usb_ep_descr_t EpDesc; /* Endpoint descriptor */ + usb_pipe_handle_t pPipe; /* Endpoint pipe handle */ + usb_pipe_policy_t PipePolicy; /* Endpoint policy */ + bool fIsocPolling; /* Whether Isoc. IN polling is enabled */ + list_t hIsocInUrbs; /* Isoc. IN inflight URBs */ + uint16_t cIsocInUrbs; /* Number of Isoc. IN inflight URBs */ + list_t hIsocInLandedReqs; /* Isoc. IN landed requests */ + uint16_t cbIsocInLandedReqs; /* Cumulative size of landed Isoc. IN requests */ + size_t cbMaxIsocData; /* Maximum size of Isoc. IN landed buffer */ +} vboxusb_ep_t; + +/** + * vboxusb_isoc_req_t: Isoc IN. requests queued from device till they are reaped. + */ +typedef struct vboxusb_isoc_req_t +{ + mblk_t *pMsg; /* Pointer to the data buffer */ + uint32_t cIsocPkts; /* Number of Isoc pkts */ + VUSBISOC_PKT_DESC aIsocPkts[8]; /* Array of Isoc pkt descriptors */ + list_node_t hListLink; +} vboxusb_isoc_req_t; + +/** + * VBOXUSB_URB_STATE: Internal USB URB state. + */ +typedef enum VBOXUSB_URB_STATE +{ + VBOXUSB_URB_STATE_FREE = 0x00, + VBOXUSB_URB_STATE_INFLIGHT = 0x04, + VBOXUSB_URB_STATE_LANDED = 0x08 +} VBOXUSB_URB_STATE; + +/** + * vboxusb_urb_t: kernel URB representation. + */ +typedef struct vboxusb_urb_t +{ + void *pvUrbR3; /* Userspace URB address (untouched, returned while reaping) */ + uint8_t bEndpoint; /* Endpoint address */ + VUSBXFERTYPE enmType; /* Xfer type */ + VUSBDIRECTION enmDir; /* Xfer direction */ + VUSBSTATUS enmStatus; /* URB status */ + bool fShortOk; /* Whether receiving less data than requested is acceptable */ + RTR3PTR pvDataR3; /* Userspace address of the original data buffer */ + size_t cbDataR3; /* Size of the data buffer */ + mblk_t *pMsg; /* Pointer to the data buffer */ + uint32_t cIsocPkts; /* Number of Isoc pkts */ + VUSBISOC_PKT_DESC aIsocPkts[8]; /* Array of Isoc pkt descriptors */ + VBOXUSB_URB_STATE enmState; /* URB state (free/in-flight/landed). */ + struct vboxusb_state_t *pState; /* Pointer to the device instance */ + list_node_t hListLink; /* List node link handle */ +} vboxusb_urb_t; + +/** + * vboxusb_power_t: Per Device Power Management info. + */ +typedef struct vboxusb_power_t +{ + uint_t PowerStates; /* Bit mask of the power states */ + int PowerBusy; /* Busy reference counter */ + bool fPowerWakeup; /* Whether remote power wakeup is enabled */ + bool fPowerRaise; /* Whether to raise the power level */ + uint8_t PowerLevel; /* Current power level */ +} vboxusb_power_t; + +/** + * vboxusb_state_t: Per Device instance state info. + */ +typedef struct vboxusb_state_t +{ + dev_info_t *pDip; /* Per instance device info. */ + usb_client_dev_data_t *pDevDesc; /* Parsed & complete device descriptor */ + uint8_t DevState; /* Current USB Device state */ + bool fDefaultPipeOpen; /* Whether the device (default control pipe) is closed */ + bool fPollPending; /* Whether the userland process' poll is pending */ + kmutex_t Mtx; /* Mutex state protection */ + usb_serialization_t StateMulti; /* State serialization */ + size_t cbMaxBulkXfer; /* Maximum bulk xfer size */ + vboxusb_ep_t aEps[VBOXUSB_MAX_ENDPOINTS]; /* Array of all endpoints structures */ + list_t hFreeUrbs; /* List of free URBs */ + list_t hInflightUrbs; /* List of inflight URBs */ + list_t hLandedUrbs; /* List of landed URBs */ + uint32_t cFreeUrbs; /* Number of free URBs */ + uint32_t cInflightUrbs; /* Number of inflight URBs */ + uint32_t cLandedUrbs; /* Number of landed URBs */ + pollhead_t PollHead; /* Handle to pollhead for waking polling processes */ + RTPROCESS Process; /* The process (pid) of the user session */ + VBOXUSBREQ_CLIENT_INFO ClientInfo; /* Registration data */ + vboxusb_power_t *pPower; /* Power Management */ + char szMfg[255]; /* Parsed manufacturer string */ + char szProduct[255]; /* Parsed product string */ +} vboxusb_state_t; +AssertCompileMemberSize(vboxusb_state_t, szMfg, USB_MAXSTRINGLEN); +AssertCompileMemberSize(vboxusb_state_t, szProduct, USB_MAXSTRINGLEN); + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +LOCAL int vboxUsbSolarisInitEp(vboxusb_state_t *pState, usb_ep_data_t *pEpData); +LOCAL int vboxUsbSolarisInitEpsForCfg(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisInitEpsForIfAlt(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt); +LOCAL void vboxUsbSolarisDestroyAllEps(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDestroyEp(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL void vboxUsbSolarisCloseAllPipes(vboxusb_state_t *pState, bool fControlPipe); +LOCAL int vboxUsbSolarisOpenPipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL void vboxUsbSolarisClosePipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL int vboxUsbSolarisCtrlXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisCtrlXferCompleted(usb_pipe_handle_t pPipe, usb_ctrl_req_t *pReq); +LOCAL int vboxUsbSolarisBulkXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *purb); +LOCAL void vboxUsbSolarisBulkXferCompleted(usb_pipe_handle_t pPipe, usb_bulk_req_t *pReq); +LOCAL int vboxUsbSolarisIntrXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisIntrXferCompleted(usb_pipe_handle_t pPipe, usb_intr_req_t *pReq); +LOCAL int vboxUsbSolarisIsocXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisIsocInXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL void vboxUsbSolarisIsocInXferError(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL void vboxUsbSolarisIsocOutXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL vboxusb_urb_t *vboxUsbSolarisGetIsocInUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq); +LOCAL vboxusb_urb_t *vboxUsbSolarisQueueUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, mblk_t *pMsg); +LOCAL VUSBSTATUS vboxUsbSolarisGetUrbStatus(usb_cr_t Status); +LOCAL void vboxUsbSolarisConcatMsg(vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisDeQueueUrb(vboxusb_urb_t *pUrb, int URBStatus); +LOCAL void vboxUsbSolarisNotifyComplete(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisProcessIOCtl(int iFunction, void *pvState, int Mode, PVBOXUSBREQ pUSBReq, void *pvBuf, + size_t *pcbDataOut); +LOCAL bool vboxUsbSolarisIsUSBDevice(dev_info_t *pDip); + +/** @name Device Operation Hooks + * @{ */ +LOCAL int vboxUsbSolarisSendUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode); +LOCAL int vboxUsbSolarisReapUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode); +LOCAL int vboxUsbSolarisClearEndPoint(vboxusb_state_t *pState, uint8_t bEndpoint); +LOCAL int vboxUsbSolarisSetConfig(vboxusb_state_t *pState, uint8_t bCfgValue); +LOCAL int vboxUsbSolarisGetConfig(vboxusb_state_t *pState, uint8_t *pCfgValue); +LOCAL int vboxUsbSolarisSetInterface(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt); +LOCAL int vboxUsbSolarisCloseDevice(vboxusb_state_t *pState, VBOXUSB_RESET_LEVEL enmReset); +LOCAL int vboxUsbSolarisAbortPipe(vboxusb_state_t *pState, uint8_t bEndpoint); +LOCAL int vboxUsbSolarisGetConfigIndex(vboxusb_state_t *pState, uint_t bCfgValue); +/** @} */ + +/** @name Hotplug & Power Management Hooks + * @{ */ +LOCAL void vboxUsbSolarisNotifyUnplug(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisDeviceDisconnected(dev_info_t *pDip); +LOCAL int vboxUsbSolarisDeviceReconnected(dev_info_t *pDip); + +LOCAL int vboxUsbSolarisInitPower(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDestroyPower(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisDeviceSuspend(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDeviceResume(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDeviceRestore(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisPowerBusy(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisPowerIdle(vboxusb_state_t *pState); +/** @} */ + +/** @name Monitor Hooks + * @{ */ +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip); +/** @} */ + +/** @name Callbacks from Monitor + * @{ */ +LOCAL int vboxUsbSolarisSetConsumerCredentials(RTPROCESS Process, int Instance, void *pvReserved); +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global list of all device instances. */ +static void *g_pVBoxUSBSolarisState; + +/** The default endpoint descriptor */ +static usb_ep_descr_t g_VBoxUSBSolarisDefaultEpDesc = { 7, 5, 0, USB_EP_ATTR_CONTROL, 8, 0 }; + +/** Size of the usb_ep_data_t struct (used to index into data). */ +static size_t g_cbUsbEpData = ~0UL; + +/** The offset of usb_ep_data_t::ep_desc. */ +static size_t g_offUsbEpDataDescr = ~0UL; + + +#ifdef LOG_ENABLED +/** + * Gets the description of an Endpoint's transfer type. + * + * @param pEp The Endpoint. + * @returns The type of the Endpoint. + */ +static const char *vboxUsbSolarisEpType(vboxusb_ep_t *pEp) +{ + uint8_t uType = VBOXUSB_XFER_TYPE(pEp); + switch (uType) + { + case 0: return "CTRL"; + case 1: return "ISOC"; + case 2: return "BULK"; + default: return "INTR"; + } +} + + +/** + * Gets the description of an Endpoint's direction. + * + * @param pEp The Endpoint. + * @returns The direction of the Endpoint. + */ +static const char *vboxUsbSolarisEpDir(vboxusb_ep_t *pEp) +{ + return VBOXUSB_XFER_DIR(pEp) == USB_EP_DIR_IN ? "IN " : "OUT"; +} +#endif + + +/** + * Caches device strings from the parsed device descriptors. + * + * @param pState The USB device instance. + * + * @remarks Must only be called after usb_get_dev_data(). + */ +static void vboxUsbSolarisGetDeviceStrings(vboxusb_state_t *pState) +{ + AssertReturnVoid(pState); + AssertReturnVoid(pState->pDevDesc); + + if (pState->pDevDesc->dev_product) + strlcpy(&pState->szMfg[0], pState->pDevDesc->dev_mfg, sizeof(pState->szMfg)); + else + strlcpy(&pState->szMfg[0], "", sizeof(pState->szMfg)); + + if (pState->pDevDesc->dev_product) + strlcpy(&pState->szProduct[0], pState->pDevDesc->dev_product, sizeof(pState->szProduct)); + else + strlcpy(&pState->szProduct[0], "", sizeof(pState->szProduct)); +} + + +/** + * Queries the necessary symbols at runtime. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisQuerySymbols(void) +{ + RTDBGKRNLINFO hKrnlDbgInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlDbgInfo, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + /* + * Query and sanitize the size of usb_ep_data_t struct. + */ + size_t cbPrevUsbEpData = g_cbUsbEpData; + rc = RTR0DbgKrnlInfoQuerySize(hKrnlDbgInfo, "usba", "usb_ep_data_t", &g_cbUsbEpData); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to query size of \"usb_ep_data_t\" in the \"usba\" module, rc=%Rrc\n", rc)); + return rc; + } + if (g_cbUsbEpData > _4K) + { + LogRel(("Size of \"usb_ep_data_t\" (%u bytes) seems implausible, too paranoid to continue\n", g_cbUsbEpData)); + return VERR_MISMATCH; + } + + /* + * Query and sanitizie the offset of usb_ep_data_t::ep_descr. + */ + size_t offPrevUsbEpDataDescr = g_offUsbEpDataDescr; + rc = RTR0DbgKrnlInfoQueryMember(hKrnlDbgInfo, "usba", "usb_ep_data_t", "ep_descr", &g_offUsbEpDataDescr); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to query offset of usb_ep_data_t::ep_descr, rc=%Rrc\n", rc)); + return rc; + } + if (g_offUsbEpDataDescr > _4K - sizeof(usb_ep_descr_t)) + { + LogRel(("Offset of \"ep_desrc\" (%u) seems implausible, too paranoid to continue\n", g_offUsbEpDataDescr)); + return VERR_MISMATCH; + } + + /* + * Log only when it changes / first time, since _init() seems to be called often (e.g. on failed attaches). + * cmn_err, CE_CONT and '!' is used to not show the message on console during boot each time. + */ + if ( cbPrevUsbEpData != g_cbUsbEpData + || offPrevUsbEpDataDescr != g_offUsbEpDataDescr) + { + cmn_err(CE_CONT, "!usba_ep_data_t is %lu bytes\n", g_cbUsbEpData); + cmn_err(CE_CONT, "!usba_ep_data_t::ep_descr @ 0x%lx (%ld)\n", g_offUsbEpDataDescr, g_offUsbEpDataDescr); + } + + RTR0DbgKrnlInfoRelease(hKrnlDbgInfo); + } + + return rc; +} + + +/** + * Kernel entry points + */ +int _init(void) +{ + LogFunc((DEVICE_NAME ": _init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxUSBSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": _init: failed to disable autounloading!\n")); + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + rc = vboxUsbSolarisQuerySymbols(); + if (RT_FAILURE(rc)) + { + RTR0Term(); + return EINVAL; + } + + rc = ddi_soft_state_init(&g_pVBoxUSBSolarisState, sizeof(vboxusb_state_t), 4 /* pre-alloc */); + if (!rc) + { + rc = mod_install(&g_VBoxUSBSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ": _init: mod_install failed! rc=%d\n", rc)); + ddi_soft_state_fini(&g_pVBoxUSBSolarisState); + } + else + LogRel((DEVICE_NAME ": _init: failed to initialize soft state\n")); + + RTR0Term(); + } + else + LogRel((DEVICE_NAME ": _init: RTR0Init failed! rc=%d\n", rc)); + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + + LogFunc((DEVICE_NAME ": _fini\n")); + + rc = mod_remove(&g_VBoxUSBSolarisModLinkage); + if (!rc) + { + ddi_soft_state_fini(&g_pVBoxUSBSolarisState); + RTR0Term(); + } + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ": _info\n")); + + return mod_info(&g_VBoxUSBSolarisModLinkage, pModInfo); +} + + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @returns Solaris error code. + */ +int VBoxUSBSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisAttach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + int rc; + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = NULL; + + switch (enmCmd) + { + case DDI_ATTACH: + { + rc = ddi_soft_state_zalloc(g_pVBoxUSBSolarisState, instance); + if (rc == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_LIKELY(pState)) + { + pState->pDip = pDip; + pState->pDevDesc = NULL; + pState->fPollPending = false; + pState->cInflightUrbs = 0; + pState->cFreeUrbs = 0; + pState->cLandedUrbs = 0; + pState->Process = NIL_RTPROCESS; + pState->pPower = NULL; + bzero(pState->aEps, sizeof(pState->aEps)); + list_create(&pState->hFreeUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + list_create(&pState->hInflightUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + list_create(&pState->hLandedUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + + /* + * There is a bug in usb_client_attach() as of Nevada 120 which panics when we bind to + * a non-USB device. So check if we are really binding to a USB device or not. + */ + if (vboxUsbSolarisIsUSBDevice(pState->pDip)) + { + /* + * Here starts the USB specifics. + */ + rc = usb_client_attach(pState->pDip, USBDRV_VERSION, 0); + if (rc == USB_SUCCESS) + { + pState->fDefaultPipeOpen = true; + + /* + * Parse out the entire descriptor. + */ + rc = usb_get_dev_data(pState->pDip, &pState->pDevDesc, USB_PARSE_LVL_ALL, 0 /* Unused */); + if (rc == USB_SUCCESS) + { + /* + * Cache some device descriptor strings. + */ + vboxUsbSolarisGetDeviceStrings(pState); +#ifdef DEBUG_ramshankar + usb_print_descr_tree(pState->pDip, pState->pDevDesc); +#endif + + /* + * Initialize state locks. + */ + mutex_init(&pState->Mtx, NULL, MUTEX_DRIVER, pState->pDevDesc->dev_iblock_cookie); + pState->StateMulti = usb_init_serialization(pState->pDip, USB_INIT_SER_CHECK_SAME_THREAD); + + /* + * Get maximum bulk transfer size supported by the HCD. + */ + rc = usb_pipe_get_max_bulk_transfer_size(pState->pDip, &pState->cbMaxBulkXfer); + if (rc == USB_SUCCESS) + { + Log((DEVICE_NAME ": VBoxUSBSolarisAttach: cbMaxBulkXfer=%d\n", pState->cbMaxBulkXfer)); + + /* + * Initialize the default endpoint. + */ + rc = vboxUsbSolarisInitEp(pState, NULL /* pEp */); + if (RT_SUCCESS(rc)) + { + /* + * Set the device state. + */ + pState->DevState = USB_DEV_ONLINE; + + /* + * Initialize power management for the device. + */ + rc = vboxUsbSolarisInitPower(pState); + if (RT_SUCCESS(rc)) + { + /* + * Initialize endpoints for the current config. + */ + rc = vboxUsbSolarisInitEpsForCfg(pState); + AssertRC(rc); + + /* + * Publish the minor node. + */ + rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0, + "none", "none", 0666); + if (RT_LIKELY(rc == DDI_SUCCESS)) + { + /* + * Register hotplug callbacks. + */ + rc = usb_register_hotplug_cbs(pState->pDip, &vboxUsbSolarisDeviceDisconnected, + &vboxUsbSolarisDeviceReconnected); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + /* + * Register with our monitor driver. + */ + bzero(&pState->ClientInfo, sizeof(pState->ClientInfo)); + char szDevicePath[MAXPATHLEN]; + ddi_pathname(pState->pDip, szDevicePath); + RTStrPrintf(pState->ClientInfo.szClientPath, + sizeof(pState->ClientInfo.szClientPath), + "/devices%s:%s", szDevicePath, DEVICE_NAME); + RTStrPrintf(pState->ClientInfo.szDeviceIdent, + sizeof(pState->ClientInfo.szDeviceIdent), + "%#x:%#x:%d:%s", + pState->pDevDesc->dev_descr->idVendor, + pState->pDevDesc->dev_descr->idProduct, + pState->pDevDesc->dev_descr->bcdDevice, szDevicePath); + pState->ClientInfo.Instance = instance; + pState->ClientInfo.pfnSetConsumerCredentials = &vboxUsbSolarisSetConsumerCredentials; + rc = VBoxUSBMonSolarisRegisterClient(pState->pDip, &pState->ClientInfo); + if (RT_SUCCESS(rc)) + { +#if 0 + LogRel((DEVICE_NAME ": Captured %s %s (Ident=%s)\n", pState->szMfg, + pState->szProduct, pState->ClientInfo.szDeviceIdent)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Captured %s %s (Ident=%s)\n", pState->szMfg, + pState->szProduct, pState->ClientInfo.szDeviceIdent); +#endif + return DDI_SUCCESS; + } + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisRegisterClient failed! rc=%d " + "path=%s instance=%d\n", rc, pState->ClientInfo.szClientPath, + instance)); + + usb_unregister_hotplug_cbs(pState->pDip); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to register hotplug callbacks! rc=%d\n", rc)); + + ddi_remove_minor_node(pState->pDip, NULL); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: ddi_create_minor_node failed! rc=%d\n", rc)); + + mutex_enter(&pState->Mtx); + vboxUsbSolarisDestroyPower(pState); + mutex_exit(&pState->Mtx); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to init power management! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: vboxUsbSolarisInitEp failed! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: usb_pipe_get_max_bulk_transfer_size failed! rc=%d\n", rc)); + + usb_fini_serialization(pState->StateMulti); + mutex_destroy(&pState->Mtx); + usb_free_dev_data(pState->pDip, pState->pDevDesc); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to get device descriptor. rc=%d\n", rc)); + + usb_client_detach(pState->pDip, NULL); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: usb_client_attach failed! rc=%d\n", rc)); + } + else + { + /* This would appear on every boot if it were LogRel() */ + Log((DEVICE_NAME ": VBoxUSBSolarisAttach: Not a USB device\n")); + } + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to get soft state\n", sizeof(*pState))); + + ddi_soft_state_free(g_pVBoxUSBSolarisState, instance); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to alloc soft state. rc=%d\n", rc)); + + return DDI_FAILURE; + } + + case DDI_RESUME: + { + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: DDI_RESUME failed to get soft state on detach\n")); + return DDI_FAILURE; + } + + vboxUsbSolarisDeviceResume(pState); + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @returns Solaris error code. + */ +int VBoxUSBSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisDetach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisDetach: Failed to get soft state on detach\n")); + return DDI_FAILURE; + } + + switch (enmCmd) + { + case DDI_DETACH: + { + /* + * At this point it must be assumed that the default control pipe has + * already been closed by userland (via VBoxUSBSolarisClose() entry point). + * Once it's closed we can no longer open or reference the device here. + */ + + /* + * Notify userland if any that we're gone (while resetting device held by us). + */ + mutex_enter(&pState->Mtx); + pState->DevState = USB_DEV_DISCONNECTED; + vboxUsbSolarisNotifyUnplug(pState); + mutex_exit(&pState->Mtx); + + + /* + * Unregister hotplug callback events first without holding the mutex as the callbacks + * would otherwise block on the mutex. + */ + usb_unregister_hotplug_cbs(pDip); + + /* + * Serialize: paranoid; drain other driver activity. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0 /* timeout */); + usb_release_access(pState->StateMulti); + mutex_enter(&pState->Mtx); + + /* + * Close all pipes. + */ + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + Assert(!pState->fDefaultPipeOpen); + + /* + * Deinitialize power, destroy all endpoints. + */ + vboxUsbSolarisDestroyPower(pState); + vboxUsbSolarisDestroyAllEps(pState); + + /* + * Free up all URB lists. + */ + vboxusb_urb_t *pUrb = NULL; + while ((pUrb = list_remove_head(&pState->hFreeUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + while ((pUrb = list_remove_head(&pState->hInflightUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + while ((pUrb = list_remove_head(&pState->hLandedUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + pState->cFreeUrbs = 0; + pState->cLandedUrbs = 0; + pState->cInflightUrbs = 0; + list_destroy(&pState->hFreeUrbs); + list_destroy(&pState->hInflightUrbs); + list_destroy(&pState->hLandedUrbs); + + /* + * Destroy locks, free up descriptor and detach from USBA. + */ + mutex_exit(&pState->Mtx); + usb_fini_serialization(pState->StateMulti); + mutex_destroy(&pState->Mtx); + + usb_free_dev_data(pState->pDip, pState->pDevDesc); + usb_client_detach(pState->pDip, NULL); + + /* + * Deregister with our Monitor driver. + */ + VBoxUSBMonSolarisUnregisterClient(pState->pDip); + + ddi_remove_minor_node(pState->pDip, NULL); + +#if 0 + LogRel((DEVICE_NAME ": Released %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Released %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, pState->ClientInfo.szDeviceIdent); +#endif + + ddi_soft_state_free(g_pVBoxUSBSolarisState, instance); + pState = NULL; + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { + int rc = vboxUsbSolarisDeviceSuspend(pState); + if (RT_SUCCESS(rc)) + return DDI_SUCCESS; + + return DDI_FAILURE; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @returns Solaris error code. + */ +int VBoxUSBSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisGetInfo\n")); + + vboxusb_state_t *pState = NULL; + int instance = getminor((dev_t)pvArg); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + /* + * One is to one mapping of instance & minor number as we publish only one minor node per device. + */ + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (pState) + { + *ppvResult = (void *)pState->pDip; + return DDI_SUCCESS; + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisGetInfo: Failed to get device state\n")); + return DDI_FAILURE; + } + + case DDI_INFO_DEVT2INSTANCE: + { + *ppvResult = (void *)(uintptr_t)instance; + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Callback invoked from the VirtualBox USB Monitor driver when a VM process + * tries to access this USB client instance. + * + * This determines which VM process will be allowed to open and access this USB + * device. + * + * @returns VBox status code. + * + * @param Process The VM process performing the client info. query. + * @param Instance This client instance (the one set while we register + * ourselves to the Monitor driver) + * @param pvReserved Reserved for future, unused. + */ +LOCAL int vboxUsbSolarisSetConsumerCredentials(RTPROCESS Process, int Instance, void *pvReserved) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Process=%u Instance=%d\n", Process, Instance)); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, Instance); + if (!pState) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Failed to get device state for instance %d\n", Instance)); + return VERR_INVALID_STATE; + } + + int rc = VINF_SUCCESS; + mutex_enter(&pState->Mtx); + + if (pState->Process == NIL_RTPROCESS) + pState->Process = Process; + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Failed! Process %u already has client open\n", + pState->Process)); + rc = VERR_RESOURCE_BUSY; + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +int VBoxUSBSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisOpen: pDev=%p fFlag=%d fType=%d pCred=%p\n", pDev, fFlag, fType, pCred)); + + /* + * Verify we are being opened as a character device + */ + if (fType != OTYP_CHR) + return EINVAL; + + /* + * One is to one mapping. (Minor<=>Instance). + */ + int instance = getminor((dev_t)*pDev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: Failed to get device state for instance %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + + /* + * Only one user process can open a device instance at a time. + */ + if (pState->Process != RTProcSelf()) + { + if (pState->Process == NIL_RTPROCESS) + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: No prior information about authorized process\n")); + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: Process %u is already using this device instance\n", pState->Process)); + + mutex_exit(&pState->Mtx); + return EPERM; + } + + mutex_exit(&pState->Mtx); + + NOREF(fFlag); + NOREF(pCred); + + return 0; +} + + +int VBoxUSBSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisClose: Dev=%d fFlag=%d fType=%d pCred=%p\n", Dev, fFlag, fType, pCred)); + + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisClose: Failed to get device state for instance %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + pState->fPollPending = false; + pState->Process = NIL_RTPROCESS; + mutex_exit(&pState->Mtx); + + return 0; +} + + +int VBoxUSBSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisRead\n")); + return ENOTSUP; +} + + +int VBoxUSBSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisWrite\n")); + return ENOTSUP; +} + + +int VBoxUSBSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisPoll: Dev=%d fEvents=%d fAnyYet=%d pReqEvents=%p\n", Dev, fEvents, fAnyYet, pReqEvents)); + + /* + * Get the device state (one to one mapping). + */ + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisPoll: No state data for %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + + /* + * Disconnect event (POLLHUP) is invalid in "fEvents". + */ + if (pState->DevState == USB_DEV_DISCONNECTED) + *pReqEvents |= POLLHUP; + else if (pState->cLandedUrbs) + *pReqEvents |= POLLIN; + else + { + *pReqEvents = 0; + if (!fAnyYet) + { + *ppPollHead = &pState->PollHead; + pState->fPollPending = true; + } + } + + mutex_exit(&pState->Mtx); + + return 0; +} + + +int VBoxUSBSolarisPower(dev_info_t *pDip, int Component, int Level) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisPower: pDip=%p Component=%d Level=%d\n", pDip, Component, Level)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisPower: Failed! State Gone\n")); + return DDI_FAILURE; + } + + if (!pState->pPower) + return DDI_SUCCESS; + + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + int rc = USB_FAILURE; + if (pState->DevState == USB_DEV_ONLINE) + { + /* + * Check if we are transitioning to a valid power state. + */ + if (!USB_DEV_PWRSTATE_OK(pState->pPower->PowerStates, Level)) + { + switch (Level) + { + case USB_DEV_OS_PWR_OFF: + { + if (pState->pPower->PowerBusy) + break; + + /* + * USB D3 command. + */ + pState->pPower->PowerLevel = USB_DEV_OS_PWR_OFF; + mutex_exit(&pState->Mtx); + rc = USB_SUCCESS; /* usb_set_device_pwrlvl3(pDip); */ + mutex_enter(&pState->Mtx); + break; + } + + case USB_DEV_OS_FULL_PWR: + { + /* + * Can happen during shutdown of the OS. + */ + pState->pPower->PowerLevel = USB_DEV_OS_FULL_PWR; + mutex_exit(&pState->Mtx); + rc = USB_SUCCESS; /* usb_set_device_pwrlvl0(pDip); */ + mutex_enter(&pState->Mtx); + break; + } + + default: /* Power levels 1, 2 not implemented */ + break; + } + } + else + Log((DEVICE_NAME ": VBoxUSBSolarisPower: USB_DEV_PWRSTATE_OK failed\n")); + } + else + rc = USB_SUCCESS; + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + return rc == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE; +} + + +/** @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(Code) (((Code) >> 16) & IOCPARM_MASK) +#endif + +int VBoxUSBSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal) +{ + /* LogFunc((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Dev=%d Cmd=%d pArg=%p Mode=%d\n", Dev, Cmd, pArg)); */ + + /* + * Get the device state (one to one mapping). + */ + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: No state data for %d\n", instance)); + return EINVAL; + } + + /* + * Read the request wrapper. + */ + VBOXUSBREQ ReqWrap; + if (IOCPARM_LEN(Cmd) != sizeof(ReqWrap)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad request %#x size=%d expected=%d\n", Cmd, IOCPARM_LEN(Cmd), + sizeof(ReqWrap))); + return ENOTTY; + } + + int rc = ddi_copyin((void *)pArg, &ReqWrap, sizeof(ReqWrap), Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyin failed to read header pArg=%p Cmd=%d. rc=%d\n", pArg, Cmd, rc)); + return EINVAL; + } + + if (ReqWrap.u32Magic != VBOXUSB_MAGIC) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad magic %#x; pArg=%p Cmd=%d\n", ReqWrap.u32Magic, pArg, Cmd)); + return EINVAL; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || ReqWrap.cbData > _1M*16)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad size %#x; pArg=%p Cmd=%d\n", ReqWrap.cbData, pArg, Cmd)); + return EINVAL; + } + + /* + * Read the request. + */ + void *pvBuf = RTMemTmpAlloc(ReqWrap.cbData); + if (RT_UNLIKELY(!pvBuf)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: RTMemTmpAlloc failed to alloc %d bytes\n", ReqWrap.cbData)); + return ENOMEM; + } + + rc = ddi_copyin((void *)(uintptr_t)ReqWrap.pvDataR3, pvBuf, ReqWrap.cbData, Mode); + if (RT_UNLIKELY(rc)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyin failed! pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, rc)); + return EFAULT; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || pvBuf == NULL)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Invalid request! pvBuf=%p cbData=%d\n", pvBuf, ReqWrap.cbData)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + size_t cbDataOut = 0; + rc = vboxUsbSolarisProcessIOCtl(Cmd, pState, Mode, &ReqWrap, pvBuf, &cbDataOut); + ReqWrap.rc = rc; + rc = 0; + + if (RT_UNLIKELY(cbDataOut > ReqWrap.cbData)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Too much output data %d expected %d Truncating!\n", cbDataOut, + ReqWrap.cbData)); + cbDataOut = ReqWrap.cbData; + } + + ReqWrap.cbData = cbDataOut; + + /* + * Copy VBOXUSBREQ back to userspace (which contains rc for USB operation). + */ + rc = ddi_copyout(&ReqWrap, (void *)pArg, sizeof(ReqWrap), Mode); + if (RT_LIKELY(!rc)) + { + /* + * Copy payload (if any) back to userspace. + */ + if (cbDataOut > 0) + { + rc = ddi_copyout(pvBuf, (void *)(uintptr_t)ReqWrap.pvDataR3, cbDataOut, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyout failed! pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, + Cmd, rc)); + rc = EFAULT; + } + } + } + else + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyout(1)failed! pReqWrap=%p pArg=%p Cmd=%d. rc=%d\n", &ReqWrap, pArg, + Cmd, rc)); + rc = EFAULT; + } + + *pVal = rc; + RTMemTmpFree(pvBuf); + return rc; +} + + +/** + * IOCtl processor for user to kernel and kernel to kernel communication. + * + * @returns VBox status code. + * + * @param iFunction The requested function. + * @param pvState The USB device instance. + * @param Mode The IOCtl mode. + * @param pUSBReq Pointer to the VBOXUSB request. + * @param pvBuf Pointer to the ring-3 URB. + * @param pcbDataOut Where to store the IOCtl OUT data size. + */ +LOCAL int vboxUsbSolarisProcessIOCtl(int iFunction, void *pvState, int Mode, PVBOXUSBREQ pUSBReq, void *pvBuf, + size_t *pcbDataOut) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: iFunction=%d pvState=%p pUSBReq=%p\n", iFunction, pvState, pUSBReq)); */ + + AssertPtrReturn(pvState, VERR_INVALID_PARAMETER); + vboxusb_state_t *pState = (vboxusb_state_t *)pvState; + size_t cbData = pUSBReq->cbData; + int rc; + +#define CHECKRET_MIN_SIZE(mnemonic, cbMin) \ + do { \ + if (RT_UNLIKELY(cbData < (cbMin))) \ + { \ + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: " mnemonic ": cbData=%#zx (%zu) min is %#zx (%zu)\n", \ + cbData, cbData, (size_t)(cbMin), (size_t)(cbMin))); \ + return VERR_BUFFER_OVERFLOW; \ + } \ + if (RT_UNLIKELY((cbMin) != 0 && !RT_VALID_PTR(pvBuf))) \ + { \ + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: " mnemonic ": Invalid pointer %p\n", pvBuf)); \ + return VERR_INVALID_PARAMETER; \ + } \ + } while (0) + + switch (iFunction) + { + case VBOXUSB_IOCTL_SEND_URB: + { + CHECKRET_MIN_SIZE("SEND_URB", sizeof(VBOXUSBREQ_URB)); + + PVBOXUSBREQ_URB pUrbReq = (PVBOXUSBREQ_URB)pvBuf; + rc = vboxUsbSolarisSendUrb(pState, pUrbReq, Mode); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SEND_URB returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_REAP_URB: + { + CHECKRET_MIN_SIZE("REAP_URB", sizeof(VBOXUSBREQ_URB)); + + PVBOXUSBREQ_URB pUrbReq = (PVBOXUSBREQ_URB)pvBuf; + rc = vboxUsbSolarisReapUrb(pState, pUrbReq, Mode); + *pcbDataOut = sizeof(VBOXUSBREQ_URB); + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: REAP_URB returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_CLEAR_EP: + { + CHECKRET_MIN_SIZE("CLEAR_EP", sizeof(VBOXUSBREQ_CLEAR_EP)); + + PVBOXUSBREQ_CLEAR_EP pClearEpReq = (PVBOXUSBREQ_CLEAR_EP)pvBuf; + rc = vboxUsbSolarisClearEndPoint(pState, pClearEpReq->bEndpoint); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: CLEAR_EP returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_SET_CONFIG: + { + CHECKRET_MIN_SIZE("SET_CONFIG", sizeof(VBOXUSBREQ_SET_CONFIG)); + + PVBOXUSBREQ_SET_CONFIG pSetCfgReq = (PVBOXUSBREQ_SET_CONFIG)pvBuf; + rc = vboxUsbSolarisSetConfig(pState, pSetCfgReq->bConfigValue); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SET_CONFIG returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_SET_INTERFACE: + { + CHECKRET_MIN_SIZE("SET_INTERFACE", sizeof(VBOXUSBREQ_SET_INTERFACE)); + + PVBOXUSBREQ_SET_INTERFACE pSetInterfaceReq = (PVBOXUSBREQ_SET_INTERFACE)pvBuf; + rc = vboxUsbSolarisSetInterface(pState, pSetInterfaceReq->bInterface, pSetInterfaceReq->bAlternate); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SET_INTERFACE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_CLOSE_DEVICE: + { + CHECKRET_MIN_SIZE("CLOSE_DEVICE", sizeof(VBOXUSBREQ_CLOSE_DEVICE)); + + PVBOXUSBREQ_CLOSE_DEVICE pCloseDeviceReq = (PVBOXUSBREQ_CLOSE_DEVICE)pvBuf; + if ( pCloseDeviceReq->ResetLevel != VBOXUSB_RESET_LEVEL_REATTACH + || (Mode & FKIOCTL)) + { + rc = vboxUsbSolarisCloseDevice(pState, pCloseDeviceReq->ResetLevel); + } + else + { + /* Userland IOCtls are not allowed to perform a reattach of the device. */ + rc = VERR_NOT_SUPPORTED; + } + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: CLOSE_DEVICE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_ABORT_PIPE: + { + CHECKRET_MIN_SIZE("ABORT_PIPE", sizeof(VBOXUSBREQ_ABORT_PIPE)); + + PVBOXUSBREQ_ABORT_PIPE pAbortPipeReq = (PVBOXUSBREQ_ABORT_PIPE)pvBuf; + rc = vboxUsbSolarisAbortPipe(pState, pAbortPipeReq->bEndpoint); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: ABORT_PIPE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_GET_CONFIG: + { + CHECKRET_MIN_SIZE("GET_CONFIG", sizeof(VBOXUSBREQ_GET_CONFIG)); + + PVBOXUSBREQ_GET_CONFIG pGetCfgReq = (PVBOXUSBREQ_GET_CONFIG)pvBuf; + rc = vboxUsbSolarisGetConfig(pState, &pGetCfgReq->bConfigValue); + *pcbDataOut = sizeof(VBOXUSBREQ_GET_CONFIG); + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: GET_CONFIG returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_GET_VERSION: + { + CHECKRET_MIN_SIZE("GET_VERSION", sizeof(VBOXUSBREQ_GET_VERSION)); + + PVBOXUSBREQ_GET_VERSION pGetVersionReq = (PVBOXUSBREQ_GET_VERSION)pvBuf; + pGetVersionReq->u32Major = VBOXUSB_VERSION_MAJOR; + pGetVersionReq->u32Minor = VBOXUSB_VERSION_MINOR; + *pcbDataOut = sizeof(VBOXUSBREQ_GET_VERSION); + rc = VINF_SUCCESS; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: GET_VERSION returned %d\n", rc)); + break; + } + + default: + { + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: Unknown request %#x\n", iFunction)); + rc = VERR_NOT_SUPPORTED; + *pcbDataOut = 0; + break; + } + } + + pUSBReq->cbData = *pcbDataOut; + return rc; +} + + +/** + * Initializes device power management. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitPower(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitPower: pState=%p\n", pState)); + + int rc = usb_handle_remote_wakeup(pState->pDip, USB_REMOTE_WAKEUP_ENABLE); + if (rc == USB_SUCCESS) + { + vboxusb_power_t *pPower = RTMemAllocZ(sizeof(vboxusb_power_t)); + if (RT_LIKELY(pPower)) + { + mutex_enter(&pState->Mtx); + pState->pPower = pPower; + pState->pPower->fPowerWakeup = false; + mutex_exit(&pState->Mtx); + + uint_t PowerStates; + rc = usb_create_pm_components(pState->pDip, &PowerStates); + if (rc == USB_SUCCESS) + { + pState->pPower->fPowerWakeup = true; + pState->pPower->PowerLevel = USB_DEV_OS_FULL_PWR; + pState->pPower->PowerStates = PowerStates; + + rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + + if (rc != DDI_SUCCESS) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to raise power level usb(%#x,%#x)\n", + pState->pDevDesc->dev_descr->idVendor, pState->pDevDesc->dev_descr->idProduct)); + } + } + else + Log((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to create power components\n")); + + return VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + } + else + { + Log((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to enable remote wakeup, No PM!\n")); + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Destroys device power management. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisDestroyPower(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyPower: pState=%p\n", pState)); + + if (pState->pPower) + { + mutex_exit(&pState->Mtx); + vboxUsbSolarisPowerBusy(pState); + mutex_enter(&pState->Mtx); + + int rc = -1; + if ( pState->pPower->fPowerWakeup + && pState->DevState != USB_DEV_DISCONNECTED) + { + mutex_exit(&pState->Mtx); + rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Raising power failed! rc=%d\n", rc)); + + rc = usb_handle_remote_wakeup(pState->pDip, USB_REMOTE_WAKEUP_DISABLE); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Failed to disable remote wakeup\n")); + } + else + mutex_exit(&pState->Mtx); + + rc = pm_lower_power(pState->pDip, 0 /* component */, USB_DEV_OS_PWR_OFF); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Lowering power failed! rc=%d\n", rc)); + + vboxUsbSolarisPowerIdle(pState); + mutex_enter(&pState->Mtx); + RTMemFree(pState->pPower); + pState->pPower = NULL; + } +} + + +/** + * Converts Solaris' USBA URB status to VBox's USB URB status. + * + * @param Status Solaris USBA USB URB status. + * + * @returns VBox USB URB status. + */ +LOCAL VUSBSTATUS vboxUsbSolarisGetUrbStatus(usb_cr_t Status) +{ + switch (Status) + { + case USB_CR_OK: return VUSBSTATUS_OK; + case USB_CR_CRC: return VUSBSTATUS_CRC; + case USB_CR_DEV_NOT_RESP: return VUSBSTATUS_DNR; + case USB_CR_DATA_UNDERRUN: return VUSBSTATUS_DATA_UNDERRUN; + case USB_CR_DATA_OVERRUN: return VUSBSTATUS_DATA_OVERRUN; + case USB_CR_STALL: return VUSBSTATUS_STALL; + /* + case USB_CR_BITSTUFFING: + case USB_CR_DATA_TOGGLE_MM: + case USB_CR_PID_CHECKFAILURE: + case USB_CR_UNEXP_PID: + case USB_CR_BUFFER_OVERRUN: + case USB_CR_BUFFER_UNDERRUN: + case USB_CR_TIMEOUT: + case USB_CR_NOT_ACCESSED: + case USB_CR_NO_RESOURCES: + case USB_CR_UNSPECIFIED_ERR: + case USB_CR_STOPPED_POLLING: + case USB_CR_PIPE_CLOSING: + case USB_CR_PIPE_RESET: + case USB_CR_NOT_SUPPORTED: + case USB_CR_FLUSHED: + case USB_CR_HC_HARDWARE_ERR: + */ + default: return VUSBSTATUS_INVALID; + } +} + + +/** + * Converts Solaris' USBA error code to VBox's error code. + * + * @param UsbRc Solaris USBA error code. + * + * @returns VBox error code. + */ +static int vboxUsbSolarisToVBoxRC(int UsbRc) +{ + switch (UsbRc) + { + case USB_SUCCESS: return VINF_SUCCESS; + case USB_INVALID_ARGS: return VERR_INVALID_PARAMETER; + case USB_INVALID_PIPE: return VERR_BAD_PIPE; + case USB_INVALID_CONTEXT: return VERR_INVALID_CONTEXT; + case USB_BUSY: return VERR_PIPE_BUSY; + case USB_PIPE_ERROR: return VERR_PIPE_IO_ERROR; + /* + case USB_FAILURE: + case USB_NO_RESOURCES: + case USB_NO_BANDWIDTH: + case USB_NOT_SUPPORTED: + case USB_PIPE_ERROR: + case USB_NO_FRAME_NUMBER: + case USB_INVALID_START_FRAME: + case USB_HC_HARDWARE_ERROR: + case USB_INVALID_REQUEST: + case USB_INVALID_VERSION: + case USB_INVALID_PERM: + */ + default: return VERR_GENERAL_FAILURE; + } +} + + +/** + * Converts Solaris' USBA device state to VBox's error code. + * + * @param uDeviceState The USB device state to convert. + * + * @returns VBox error code. + */ +static int vboxUsbSolarisDeviceState(uint8_t uDeviceState) +{ + switch (uDeviceState) + { + case USB_DEV_ONLINE: return VINF_SUCCESS; + case USB_DEV_SUSPENDED: return VERR_VUSB_DEVICE_IS_SUSPENDED; + case USB_DEV_DISCONNECTED: + case USB_DEV_PWRED_DOWN: return VERR_VUSB_DEVICE_NOT_ATTACHED; + default: return VERR_GENERAL_FAILURE; + } +} + + +/** + * Checks if the device is a USB device. + * + * @param pDip Pointer to this device info. structure. + * + * @returns If this is really a USB device returns true, otherwise false. + */ +LOCAL bool vboxUsbSolarisIsUSBDevice(dev_info_t *pDip) +{ + int rc = DDI_FAILURE; + + /* + * Check device for "usb" compatible property, root hubs->device would likely mean parent has no "usb" property. + */ + char **ppszCompatible = NULL; + uint_t cCompatible; + rc = ddi_prop_lookup_string_array(DDI_DEV_T_ANY, pDip, DDI_PROP_DONTPASS, "compatible", &ppszCompatible, &cCompatible); + if (RT_LIKELY(rc == DDI_PROP_SUCCESS)) + { + while (cCompatible--) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Compatible[%d]=%s\n", cCompatible, ppszCompatible[cCompatible])); + if (!strncmp(ppszCompatible[cCompatible], "usb", 3)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Verified device as USB. pszCompatible=%s\n", + ppszCompatible[cCompatible])); + ddi_prop_free(ppszCompatible); + return true; + } + } + + ddi_prop_free(ppszCompatible); + ppszCompatible = NULL; + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: USB property lookup failed, rc=%d\n", rc)); + + /* + * Check parent for "usb" compatible property. + */ + dev_info_t *pParentDip = ddi_get_parent(pDip); + if (pParentDip) + { + rc = ddi_prop_lookup_string_array(DDI_DEV_T_ANY, pParentDip, DDI_PROP_DONTPASS, "compatible", &ppszCompatible, + &cCompatible); + if (RT_LIKELY(rc == DDI_PROP_SUCCESS)) + { + while (cCompatible--) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Parent compatible[%d]=%s\n", cCompatible, + ppszCompatible[cCompatible])); + if (!strncmp(ppszCompatible[cCompatible], "usb", 3)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Verified device as USB. parent pszCompatible=%s\n", + ppszCompatible[cCompatible])); + ddi_prop_free(ppszCompatible); + return true; + } + } + + ddi_prop_free(ppszCompatible); + ppszCompatible = NULL; + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: USB parent property lookup failed. rc=%d\n", rc)); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Failed to obtain parent device for property lookup\n")); + + return false; +} + + +/** + * Submits a URB. + * + * @param pState The USB device instance. + * @param pUrbReq Pointer to the VBox USB URB. + * @param Mode The IOCtl mode. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSendUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode) +{ + int iEpIndex = VBOXUSB_GET_EP_INDEX(pUrbReq->bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + AssertPtrReturn(pEp, VERR_INVALID_POINTER); + Assert(pUrbReq); + +#if 0 + LogFunc((DEVICE_NAME ": vboxUsbSolarisSendUrb: pState=%p pUrbReq=%p bEndpoint=%#x[%d] enmDir=%#x enmType=%#x " + "cbData=%d pvData=%p\n", pState, pUrbReq, pUrbReq->bEndpoint, iEpIndex, pUrbReq->enmDir, + pUrbReq->enmType, pUrbReq->cbData, pUrbReq->pvData)); +#endif + + if (RT_UNLIKELY(!pUrbReq->pvData)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Invalid request - No data\n")); + return VERR_INVALID_POINTER; + } + + /* + * Allocate message block & copy userspace buffer for host to device Xfers and for + * Control Xfers (since input has Setup header that needs copying). + */ + mblk_t *pMsg = NULL; + int rc = VINF_SUCCESS; + if ( pUrbReq->enmDir == VUSBDIRECTION_OUT + || pUrbReq->enmType == VUSBXFERTYPE_MSG) + { + pMsg = allocb(pUrbReq->cbData, BPRI_HI); + if (RT_UNLIKELY(!pMsg)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Failed to allocate %u bytes\n", pUrbReq->cbData)); + return VERR_NO_MEMORY; + } + + rc = ddi_copyin(pUrbReq->pvData, pMsg->b_wptr, pUrbReq->cbData, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: ddi_copyin failed! rc=%d\n", rc)); + freemsg(pMsg); + return VERR_NO_MEMORY; + } + + pMsg->b_wptr += pUrbReq->cbData; + } + + mutex_enter(&pState->Mtx); + rc = vboxUsbSolarisDeviceState(pState->DevState); + if (!pState->fDefaultPipeOpen) /* Required for Isoc. IN Xfers which don't Xfer through the pipe after polling starts */ + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + if (RT_SUCCESS(rc)) + { + /* + * Open the pipe if needed. + */ + rc = vboxUsbSolarisOpenPipe(pState, pEp); + if (RT_UNLIKELY(RT_FAILURE(rc))) + { + mutex_exit(&pState->Mtx); + freemsg(pMsg); + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: OpenPipe failed! pState=%p pUrbReq=%p bEndpoint=%#x enmDir=%#x " + "enmType=%#x cbData=%d pvData=%p rc=%d\n", pState, pUrbReq, pUrbReq->bEndpoint, pUrbReq->enmDir, + pUrbReq->enmType, pUrbReq->cbData, pUrbReq->pvData, rc)); + return VERR_BAD_PIPE; + } + + mutex_exit(&pState->Mtx); + + vboxusb_urb_t *pUrb = NULL; + if ( pUrbReq->enmType == VUSBXFERTYPE_ISOC + && pUrbReq->enmDir == VUSBDIRECTION_IN) + pUrb = vboxUsbSolarisGetIsocInUrb(pState, pUrbReq); + else + pUrb = vboxUsbSolarisQueueUrb(pState, pUrbReq, pMsg); + + if (RT_LIKELY(pUrb)) + { + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + rc = vboxUsbSolarisCtrlXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_BULK: + { + rc = vboxUsbSolarisBulkXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_INTR: + { + rc = vboxUsbSolarisIntrXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_ISOC: + { + rc = vboxUsbSolarisIsocXfer(pState, pEp, pUrb); + break; + } + + default: + { + LogRelMax(5, (DEVICE_NAME ": vboxUsbSolarisSendUrb: URB type unsupported %d\n", pUrb->enmType)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(rc)) + { + mutex_enter(&pState->Mtx); + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + pMsg = NULL; + + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + RTMemFree(pUrb); + pUrb = NULL; + } + else + { + /* + * Xfer failed, move URB back to the free list. + */ + list_remove(&pState->hInflightUrbs, pUrb); + Assert(pState->cInflightUrbs > 0); + --pState->cInflightUrbs; + + pUrb->enmState = VBOXUSB_URB_STATE_FREE; + Assert(!pUrb->pMsg); + list_insert_head(&pState->hFreeUrbs, pUrb); + ++pState->cFreeUrbs; + } + mutex_exit(&pState->Mtx); + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Failed to queue URB\n")); + rc = VERR_NO_MEMORY; + freemsg(pMsg); + } + } + else + { + mutex_exit(&pState->Mtx); + freemsg(pMsg); + } + + return rc; +} + + +/** + * Reaps a completed URB. + * + * @param pState The USB device instance. + * @param pUrbReq Pointer to the VBox USB URB. + * @param Mode The IOCtl mode. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisReapUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisReapUrb: pState=%p pUrbReq=%p\n", pState, pUrbReq)); */ + + AssertPtrReturn(pUrbReq, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + mutex_enter(&pState->Mtx); + rc = vboxUsbSolarisDeviceState(pState->DevState); + if (!pState->fDefaultPipeOpen) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + if (RT_SUCCESS(rc)) + { + vboxusb_urb_t *pUrb = list_remove_head(&pState->hLandedUrbs); + if (pUrb) + { + Assert(pState->cLandedUrbs > 0); + --pState->cLandedUrbs; + } + + /* + * It is safe to access pUrb->pMsg outside the state mutex because this is from the landed URB list + * and not the inflight URB list. + */ + mutex_exit(&pState->Mtx); + if (pUrb) + { + /* + * Copy the URB which will then be copied to user-space. + */ + pUrbReq->pvUrbR3 = pUrb->pvUrbR3; + pUrbReq->bEndpoint = pUrb->bEndpoint; + pUrbReq->enmType = pUrb->enmType; + pUrbReq->enmDir = pUrb->enmDir; + pUrbReq->enmStatus = pUrb->enmStatus; + pUrbReq->pvData = (void *)pUrb->pvDataR3; + pUrbReq->cbData = pUrb->cbDataR3; + + if (RT_LIKELY(pUrb->pMsg)) + { + /* + * Copy the message back into the user buffer. + */ + if (RT_LIKELY(pUrb->pvDataR3 != NIL_RTR3PTR)) + { + Assert(!pUrb->pMsg->b_cont); /* We really should have a single message block always. */ + size_t cbData = RT_MIN(MBLKL(pUrb->pMsg), pUrb->cbDataR3); + pUrbReq->cbData = cbData; + + if (RT_LIKELY(cbData)) + { + rc = ddi_copyout(pUrb->pMsg->b_rptr, (void *)pUrbReq->pvData, cbData, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisReapUrb: ddi_copyout failed! rc=%d\n", rc)); + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + } + } + + Log((DEVICE_NAME ": vboxUsbSolarisReapUrb: pvUrbR3=%p pvDataR3=%p cbData=%d\n", pUrbReq->pvUrbR3, + pUrbReq->pvData, pUrbReq->cbData)); + } + else + { + pUrbReq->cbData = 0; + rc = VERR_INVALID_POINTER; + LogRel((DEVICE_NAME ": vboxUsbSolarisReapUrb: Missing pvDataR3!!\n")); + } + + /* + * Free buffer allocated in vboxUsbSolarisSendUrb or vboxUsbSolaris[Ctrl|Bulk|Intr]Xfer(). + */ + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + } + else + { + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + pUrbReq->cbData = 0; + } + } + + /* + * Copy Isoc packet descriptors. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + AssertCompile(sizeof(pUrbReq->aIsocPkts) == sizeof(pUrb->aIsocPkts)); + pUrbReq->cIsocPkts = pUrb->cIsocPkts; + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + pUrbReq->aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cbPkt; + pUrbReq->aIsocPkts[i].cbActPkt = pUrb->aIsocPkts[i].cbActPkt; + pUrbReq->aIsocPkts[i].enmStatus = pUrb->aIsocPkts[i].enmStatus; + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + RTMemFree(pUrb); + pUrb = NULL; + } + } + + if (pUrb) + { + /* + * Add URB back to the free list. + */ + Assert(!pUrb->pMsg); + pUrb->cbDataR3 = 0; + pUrb->pvDataR3 = NIL_RTR3PTR; + pUrb->enmState = VBOXUSB_URB_STATE_FREE; + mutex_enter(&pState->Mtx); + list_insert_head(&pState->hFreeUrbs, pUrb); + ++pState->cFreeUrbs; + mutex_exit(&pState->Mtx); + } + } + else + { + pUrbReq->pvUrbR3 = NULL; + pUrbReq->cbData = 0; + pUrbReq->pvData = NULL; + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + } + } + else + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Clears a pipe (CLEAR_FEATURE). + * + * @param pState The USB device instance. + * @param bEndpoint The Endpoint address. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisClearEndPoint(vboxusb_state_t *pState, uint8_t bEndpoint) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: pState=%p bEndpoint=%#x\n", pState, bEndpoint)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + int iEpIndex = VBOXUSB_GET_EP_INDEX(bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + if (RT_LIKELY(pEp)) + { + /* + * Check if the endpoint is open to be cleared. + */ + if (pEp->pPipe) + { + mutex_exit(&pState->Mtx); + + /* + * Synchronous reset pipe. + */ + usb_pipe_reset(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + NULL, /* Completion callback */ + NULL); /* Exception callback */ + + mutex_enter(&pState->Mtx); + + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: bEndpoint=%#x[%d] returns %d\n", bEndpoint, iEpIndex, rc)); + + rc = VINF_SUCCESS; + } + else + { + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Not opened to be cleared. Faking success. bEndpoint=%#x\n", + bEndpoint)); + rc = VINF_SUCCESS; + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Endpoint missing! bEndpoint=%#x[%d]\n", bEndpoint, iEpIndex)); + rc = VERR_GENERAL_FAILURE; + } + } + else + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Device not online, state=%d\n", pState->DevState)); + + mutex_exit(&pState->Mtx); + return rc; +} + + +/** + * Sets configuration (SET_CONFIGURATION) + * + * @param pState The USB device instance. + * @param bConfig The Configuration. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSetConfig(vboxusb_state_t *pState, uint8_t bConfig) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetConfig: pState=%p bConfig=%u\n", pState, bConfig)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + vboxUsbSolarisCloseAllPipes(pState, false /* ControlPipe */); + int iCfgIndex = vboxUsbSolarisGetConfigIndex(pState, bConfig); + + if ( iCfgIndex >= 0 + && iCfgIndex < pState->pDevDesc->dev_n_cfg) + { + /* + * Switch Config synchronously. + */ + mutex_exit(&pState->Mtx); + rc = usb_set_cfg(pState->pDip, (uint_t)iCfgIndex, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback data */); + mutex_enter(&pState->Mtx); + + if (rc == USB_SUCCESS) + { + int rc2 = vboxUsbSolarisInitEpsForCfg(pState); + AssertRC(rc2); NOREF(rc2); + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConfig: usb_set_cfg failed for iCfgIndex=%#x bConfig=%u rc=%d\n", + iCfgIndex, bConfig, rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConfig: Invalid iCfgIndex=%d bConfig=%u\n", iCfgIndex, bConfig)); + rc = VERR_OUT_OF_RANGE; + } + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Gets configuration (GET_CONFIGURATION) + * + * @param pState The USB device instance. + * @param pbConfig Where to store the Configuration. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisGetConfig(vboxusb_state_t *pState, uint8_t *pbConfig) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisGetConfig: pState=%p pbConfig=%p\n", pState, pbConfig)); + AssertPtrReturn(pbConfig, VERR_INVALID_POINTER); + + /* + * Get Config synchronously. + */ + uint_t bConfig; + int rc = usb_get_cfg(pState->pDip, &bConfig, USB_FLAGS_SLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + *pbConfig = bConfig; + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisGetConfig: Failed, rc=%d\n", rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + + Log((DEVICE_NAME ": vboxUsbSolarisGetConfig: Returns %d bConfig=%u\n", rc, *pbConfig)); + return rc; +} + + +/** + * Sets interface (SET_INTERFACE) and alternate. + * + * @param pState The USB device instance. + * @param bIf The Interface. + * @param bAlt The Alternate setting. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSetInterface(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetInterface: pState=%p bIf=%#x bAlt=%#x\n", pState, bIf, bAlt)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + /* + * Set Interface & Alt setting synchronously. + */ + mutex_exit(&pState->Mtx); + rc = usb_set_alt_if(pState->pDip, bIf, bAlt, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback data */); + mutex_enter(&pState->Mtx); + + if (rc == USB_SUCCESS) + { + Log((DEVICE_NAME ": vboxUsbSolarisSetInterface: Success, bIf=%#x bAlt=%#x\n", bIf, bAlt, rc)); + int rc2 = vboxUsbSolarisInitEpsForIfAlt(pState, bIf, bAlt); + AssertRC(rc2); NOREF(rc2); + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetInterface: usb_set_alt_if failed for bIf=%#x bAlt=%#x rc=%d\n", bIf, bAlt, rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Closes the USB device and optionally resets it. + * + * @param pState The USB device instance. + * @param enmReset The reset level. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisCloseDevice(vboxusb_state_t *pState, VBOXUSB_RESET_LEVEL enmReset) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCloseDevice: pState=%p enmReset=%d\n", pState, enmReset)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + + if (enmReset == VBOXUSB_RESET_LEVEL_CLOSE) + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + else + vboxUsbSolarisCloseAllPipes(pState, false /* ControlPipe */); + + mutex_exit(&pState->Mtx); + + if (RT_SUCCESS(rc)) + { + switch (enmReset) + { + case VBOXUSB_RESET_LEVEL_REATTACH: + rc = usb_reset_device(pState->pDip, USB_RESET_LVL_REATTACH); + break; + + case VBOXUSB_RESET_LEVEL_SOFT: + rc = usb_reset_device(pState->pDip, USB_RESET_LVL_DEFAULT); + break; + + default: + rc = USB_SUCCESS; + break; + } + + rc = vboxUsbSolarisToVBoxRC(rc); + } + + Log((DEVICE_NAME ": vboxUsbSolarisCloseDevice: Returns %d\n", rc)); + return rc; +} + + +/** + * Aborts pending requests and reset the pipe. + * + * @param pState The USB device instance. + * @param bEndpoint The Endpoint address. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisAbortPipe(vboxusb_state_t *pState, uint8_t bEndpoint) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisAbortPipe: pState=%p bEndpoint=%#x\n", pState, bEndpoint)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + int iEpIndex = VBOXUSB_GET_EP_INDEX(bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + if (RT_LIKELY(pEp)) + { + if (pEp->pPipe) + { + /* + * Aborting requests not supported for the default control pipe. + */ + if ((pEp->EpDesc.bEndpointAddress & USB_EP_NUM_MASK) == 0) + { + mutex_exit(&pState->Mtx); + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Cannot reset default control pipe\n")); + return VERR_NOT_SUPPORTED; + } + + mutex_exit(&pState->Mtx); + usb_pipe_reset(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + NULL, /* Completion callback */ + NULL); /* Callback's parameter */ + + /* + * Allow pending async requests to complete. + */ + /** @todo this is most likely not required. */ + rc = usb_pipe_drain_reqs(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + 5, /* Timeout (seconds) */ + NULL, /* Completion callback */ + NULL); /* Callback's parameter */ + + mutex_enter(&pState->Mtx); + + Log((DEVICE_NAME ": vboxUsbSolarisAbortPipe: usb_pipe_drain_reqs returns %d\n", rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: pipe not open. bEndpoint=%#x\n", bEndpoint)); + rc = VERR_PIPE_IO_ERROR; + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Invalid pipe bEndpoint=%#x[%d]\n", bEndpoint, iEpIndex)); + rc = VERR_INVALID_HANDLE; + } + } + + mutex_exit(&pState->Mtx); + + LogFunc((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Returns %d\n", rc)); + return rc; +} + + +/** + * Initializes an Endpoint. + * + * @param pState The USB device instance. + * @param pEpData The Endpoint data (NULL implies the default + * endpoint). + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisInitEp(vboxusb_state_t *pState, usb_ep_data_t *pEpData) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEp: pState=%p pEpData=%p", pState, pEpData)); + + /* + * Is this the default endpoint? + */ + usb_ep_descr_t *pEpDesc = NULL; + vboxusb_ep_t *pEp = NULL; + int iEpIndex; + if (!pEpData) + { + iEpIndex = 0; + pEpDesc = &g_VBoxUSBSolarisDefaultEpDesc; + } + else + { + iEpIndex = VBOXUSB_GET_EP_INDEX(pEpData->ep_descr.bEndpointAddress); + pEpDesc = (usb_ep_descr_t *)((uint8_t *)pEpData + g_offUsbEpDataDescr); + } + + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + pEp = &pState->aEps[iEpIndex]; + + /* + * Initialize the endpoint. + */ + pEp->EpDesc = *pEpDesc; + if (!pEp->fInitialized) + { + pEp->pPipe = NULL; + bzero(&pEp->PipePolicy, sizeof(pEp->PipePolicy)); + pEp->PipePolicy.pp_max_async_reqs = VBOXUSB_MAX_PIPE_ASYNC_REQS; + pEp->fIsocPolling = false; + list_create(&pEp->hIsocInUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + pEp->cIsocInUrbs = 0; + list_create(&pEp->hIsocInLandedReqs, sizeof(vboxusb_isoc_req_t), offsetof(vboxusb_isoc_req_t, hListLink)); + pEp->cbIsocInLandedReqs = 0; + pEp->cbMaxIsocData = 0; + pEp->fInitialized = true; + } + + Log((DEVICE_NAME ": vboxUsbSolarisInitEp: Success, %s[%2d] %s %s bEndpoint=%#x\n", !pEpData ? "Default " : "Endpoint", + iEpIndex, vboxUsbSolarisEpType(pEp), vboxUsbSolarisEpDir(pEp), pEp->EpDesc.bEndpointAddress)); + return VINF_SUCCESS; +} + + +/** + * Initializes Endpoints for the current configuration, all interfaces and + * alternate setting 0 for each interface. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitEpsForCfg(vboxusb_state_t *pState) +{ + uint_t uCfgIndex = usb_get_current_cfgidx(pState->pDip); + if (uCfgIndex >= pState->pDevDesc->dev_n_cfg) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: Invalid current config index %u\n", uCfgIndex)); + return VERR_OUT_OF_RANGE; + } + + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[uCfgIndex]; + uchar_t bConfig = pConfig->cfg_descr.bConfigurationValue; + + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: pState=%p bConfig=%u uCfgIndex=%u\n", pState, bConfig, uCfgIndex)); + + const uint_t cIfs = pConfig->cfg_n_if; + for (uchar_t uIf = 0; uIf < cIfs; uIf++) + { + usb_if_data_t *pIf = &pConfig->cfg_if[uIf]; + const uint_t cAlts = pIf->if_n_alt; + for (uchar_t uAlt = 0; uAlt < cAlts; uAlt++) + { + usb_alt_if_data_t *pAlt = &pIf->if_alt[uAlt]; + if (pAlt->altif_descr.bAlternateSetting == 0) /* Refer USB 2.0 spec 9.6.5 "Interface" */ + { + const uint_t cEps = pAlt->altif_n_ep; + for (uchar_t uEp = 0; uEp < cEps; uEp++) + { + uint8_t *pbEpData = (uint8_t *)&pAlt->altif_ep[0]; + usb_ep_data_t *pEpData = (usb_ep_data_t *)(pbEpData + uEp * g_cbUsbEpData); + int rc = vboxUsbSolarisInitEp(pState, pEpData); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: Failed to init endpoint! " + "bConfig=%u bIf=%#x bAlt=%#x\n", bConfig, pAlt->altif_descr.bInterfaceNumber, + pAlt->altif_descr.bAlternateSetting)); + return rc; + } + } + break; /* move on to next interface. */ + } + } + } + return VINF_SUCCESS; +} + + +/** + * Initializes Endpoints for the given Interface & Alternate setting. + * + * @param pState The USB device instance. + * @param bIf The Interface. + * @param bAlt The Alterate. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitEpsForIfAlt(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: pState=%p bIf=%d uAlt=%d\n", pState, bIf, bAlt)); + + /* Doesn't hurt to be paranoid */ + uint_t uCfgIndex = usb_get_current_cfgidx(pState->pDip); + if (uCfgIndex >= pState->pDevDesc->dev_n_cfg) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: Invalid current config index %d\n", uCfgIndex)); + return VERR_OUT_OF_RANGE; + } + + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[uCfgIndex]; + for (uchar_t uIf = 0; uIf < pConfig->cfg_n_if; uIf++) + { + usb_if_data_t *pInterface = &pConfig->cfg_if[uIf]; + const uint_t cAlts = pInterface->if_n_alt; + for (uchar_t uAlt = 0; uAlt < cAlts; uAlt++) + { + usb_alt_if_data_t *pAlt = &pInterface->if_alt[uAlt]; + if ( pAlt->altif_descr.bInterfaceNumber == bIf + && pAlt->altif_descr.bAlternateSetting == bAlt) + { + const uint_t cEps = pAlt->altif_n_ep; + for (uchar_t uEp = 0; uEp < cEps; uEp++) + { + uint8_t *pbEpData = (uint8_t *)&pAlt->altif_ep[0]; + usb_ep_data_t *pEpData = (usb_ep_data_t *)(pbEpData + uEp * g_cbUsbEpData); + int rc = vboxUsbSolarisInitEp(pState, pEpData); + if (RT_FAILURE(rc)) + { + uint8_t bCfgValue = pConfig->cfg_descr.bConfigurationValue; + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: Failed to init endpoint! " + "bCfgValue=%u bIf=%#x bAlt=%#x\n", bCfgValue, bIf, bAlt)); + return rc; + } + } + return VINF_SUCCESS; + } + } + } + return VERR_NOT_FOUND; +} + + +/** + * Destroys all Endpoints. + * + * @param pState The USB device instance. + * + * @remarks Requires the state mutex to be held. + * Call only from Detach() or similar as callbacks + */ +LOCAL void vboxUsbSolarisDestroyAllEps(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyAllEps: pState=%p\n", pState)); + + Assert(mutex_owned(&pState->Mtx)); + for (unsigned i = 0; i < VBOXUSB_MAX_ENDPOINTS; i++) + { + vboxusb_ep_t *pEp = &pState->aEps[i]; + if (pEp->fInitialized) + vboxUsbSolarisDestroyEp(pState, pEp); + } +} + + +/** + * Destroys an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * + * @remarks Requires the state mutex to be held. + */ +LOCAL void vboxUsbSolarisDestroyEp(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyEp: pState=%p pEp=%p\n", pState, pEp)); + + Assert(pEp->fInitialized); + Assert(mutex_owned(&pState->Mtx)); + vboxusb_urb_t *pUrb = list_remove_head(&pEp->hIsocInUrbs); + while (pUrb) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + pUrb = list_remove_head(&pEp->hIsocInUrbs); + } + pEp->cIsocInUrbs = 0; + list_destroy(&pEp->hIsocInUrbs); + + vboxusb_isoc_req_t *pIsocReq = list_remove_head(&pEp->hIsocInLandedReqs); + while (pIsocReq) + { + kmem_free(pIsocReq, sizeof(vboxusb_isoc_req_t)); + pIsocReq = list_remove_head(&pEp->hIsocInLandedReqs); + } + pEp->cbIsocInLandedReqs = 0; + list_destroy(&pEp->hIsocInLandedReqs); + + pEp->fInitialized = false; +} + + +/** + * Closes all non-default pipes and drains the default pipe. + * + * @param pState The USB device instance. + * @param fDefault Whether to close the default control pipe. + * + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisCloseAllPipes(vboxusb_state_t *pState, bool fDefault) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: pState=%p\n", pState)); + + for (int i = 1; i < VBOXUSB_MAX_ENDPOINTS; i++) + { + vboxusb_ep_t *pEp = &pState->aEps[i]; + if ( pEp + && pEp->pPipe) + { + Log((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: Closing[%d]\n", i)); + vboxUsbSolarisClosePipe(pState, pEp); + } + } + + if (fDefault) + { + vboxusb_ep_t *pEp = &pState->aEps[0]; + if ( pEp + && pEp->pPipe) + { + vboxUsbSolarisClosePipe(pState, pEp); + Log((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: Closed default pipe\n")); + } + } +} + + +/** + * Opens the pipe associated with an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * @remarks Requires the device state mutex to be held. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisOpenPipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + Assert(mutex_owned(&pState->Mtx)); + + /* + * Make sure the Endpoint isn't open already. + */ + if (pEp->pPipe) + return VINF_SUCCESS; + + /* + * Default Endpoint; already opened just copy the pipe handle. + */ + if ((pEp->EpDesc.bEndpointAddress & USB_EP_NUM_MASK) == 0) + { + pEp->pPipe = pState->pDevDesc->dev_default_ph; + Log((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Default pipe opened\n")); + return VINF_SUCCESS; + } + + /* + * Open the non-default pipe for the Endpoint. + */ + mutex_exit(&pState->Mtx); + int rc = usb_pipe_open(pState->pDip, &pEp->EpDesc, &pEp->PipePolicy, USB_FLAGS_NOSLEEP, &pEp->pPipe); + mutex_enter(&pState->Mtx); + if (rc == USB_SUCCESS) + { + LogFunc((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Opened pipe, pState=%p pEp=%p\n", pState, pEp)); + usb_pipe_set_private(pEp->pPipe, (usb_opaque_t)pEp); + + /* + * Determine input buffer size for Isoc. IN transfers. + */ + if ( VBOXUSB_XFER_TYPE(pEp) == VUSBXFERTYPE_ISOC + && VBOXUSB_XFER_DIR(pEp) == VUSB_DIR_TO_HOST) + { + /* + * wMaxPacketSize bits 10..0 specifies maximum packet size which can hold 1024 bytes. + * If bits 12..11 is non-zero, cbMax will be more than 1024 and thus the Endpoint is a + * high-bandwidth Endpoint. + */ + uint16_t cbMax = VBOXUSB_PKT_SIZE(pEp->EpDesc.wMaxPacketSize); + if (cbMax <= 1024) + { + /* Buffer 1 second for highspeed and 8 seconds for fullspeed Endpoints. */ + pEp->cbMaxIsocData = 1000 * cbMax * 8; + } + else + { + /* Buffer about 400 milliseconds of data for highspeed high-bandwidth endpoints. */ + pEp->cbMaxIsocData = 400 * cbMax * 8; + } + Log((DEVICE_NAME ": vboxUsbSolarisOpenPipe: bEndpoint=%#x cbMaxIsocData=%u\n", pEp->EpDesc.bEndpointAddress, + pEp->cbMaxIsocData)); + } + + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Failed! rc=%d pState=%p pEp=%p\n", rc, pState, pEp)); + rc = VERR_BAD_PIPE; + } + + return rc; +} + + +/** + * Closes the pipe associated with an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisClosePipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisClosePipe: pState=%p pEp=%p\n", pState, pEp)); + AssertPtr(pEp); + + if (pEp->pPipe) + { + /* + * Default pipe: allow completion of pending requests. + */ + if (pEp->pPipe == pState->pDevDesc->dev_default_ph) + { + mutex_exit(&pState->Mtx); + usb_pipe_drain_reqs(pState->pDip, pEp->pPipe, 0, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback arg. */); + mutex_enter(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Closed default pipe\n")); + pState->fDefaultPipeOpen = false; + } + else + { + /* + * Stop Isoc. IN polling if required. + */ + if (pEp->fIsocPolling) + { + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pEp->pPipe, USB_FLAGS_NOSLEEP); + mutex_enter(&pState->Mtx); + } + + /* + * Non-default pipe: close it. + */ + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Pipe bmAttributes=%#x bEndpoint=%#x\n", pEp->EpDesc.bmAttributes, + pEp->EpDesc.bEndpointAddress)); + mutex_exit(&pState->Mtx); + usb_pipe_close(pState->pDip, pEp->pPipe, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback arg. */); + mutex_enter(&pState->Mtx); + } + + /* + * Free the Endpoint data message block and reset pipe handle. + */ + pEp->pPipe = NULL; + + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Success, bEndpoint=%#x\n", pEp->EpDesc.bEndpointAddress)); + } + + Assert(pEp->pPipe == NULL); +} + + +/** + * Finds the Configuration index for the passed in Configuration value. + * + * @param pState The USB device instance. + * @param bConfig The Configuration. + * + * @returns The configuration index if found, otherwise -1. + */ +LOCAL int vboxUsbSolarisGetConfigIndex(vboxusb_state_t *pState, uint_t bConfig) +{ + for (int CfgIndex = 0; CfgIndex < pState->pDevDesc->dev_n_cfg; CfgIndex++) + { + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[CfgIndex]; + if (pConfig->cfg_descr.bConfigurationValue == bConfig) + return CfgIndex; + } + + return -1; +} + + +/** + * Allocates and initializes an Isoc. In URB from the ring-3 equivalent. + * + * @param pState The USB device instance. + * @param pUrbReq Opaque pointer to the complete request. + * + * @returns The allocated Isoc. In URB to be used. + */ +LOCAL vboxusb_urb_t *vboxUsbSolarisGetIsocInUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq) +{ + /* + * Isoc. In URBs are not queued into the Inflight list like every other URBs. + * For now we allocate each URB which gets queued into the respective Endpoint during Xfer. + */ + vboxusb_urb_t *pUrb = RTMemAllocZ(sizeof(vboxusb_urb_t)); + if (RT_LIKELY(pUrb)) + { + pUrb->enmState = VBOXUSB_URB_STATE_INFLIGHT; + pUrb->pState = pState; + + if (RT_LIKELY(pUrbReq)) + { + pUrb->pvUrbR3 = pUrbReq->pvUrbR3; + pUrb->bEndpoint = pUrbReq->bEndpoint; + pUrb->enmType = pUrbReq->enmType; + pUrb->enmDir = pUrbReq->enmDir; + pUrb->enmStatus = pUrbReq->enmStatus; + pUrb->cbDataR3 = pUrbReq->cbData; + pUrb->pvDataR3 = (RTR3PTR)pUrbReq->pvData; + pUrb->cIsocPkts = pUrbReq->cIsocPkts; + + for (unsigned i = 0; i < pUrbReq->cIsocPkts; i++) + pUrb->aIsocPkts[i].cbPkt = pUrbReq->aIsocPkts[i].cbPkt; + + pUrb->pMsg = NULL; + } + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisGetIsocInUrb: Failed to alloc %d bytes\n", sizeof(vboxusb_urb_t))); + return pUrb; +} + + +/** + * Queues a URB reusing previously allocated URBs as required. + * + * @param pState The USB device instance. + * @param pUrbReq Opaque pointer to the complete request. + * @param pMsg Pointer to the allocated request data. + * + * @returns The allocated URB to be used, or NULL upon failure. + */ +LOCAL vboxusb_urb_t *vboxUsbSolarisQueueUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, mblk_t *pMsg) +{ + Assert(pUrbReq); + LogFunc((DEVICE_NAME ": vboxUsbSolarisQueueUrb: pState=%p pUrbReq=%p\n", pState, pUrbReq)); + + mutex_enter(&pState->Mtx); + + /* + * Grab a URB from the free list. + */ + vboxusb_urb_t *pUrb = list_remove_head(&pState->hFreeUrbs); + if (pUrb) + { + Assert(pUrb->enmState == VBOXUSB_URB_STATE_FREE); + Assert(!pUrb->pMsg); + Assert(pState->cFreeUrbs > 0); + --pState->cFreeUrbs; + } + else + { + /* + * We can't discard "old" URBs. For instance, INTR IN URBs that don't complete as + * they don't have a timeout can essentially take arbitrarily long to complete depending + * on the device and it's not safe to discard them in case they -do- complete. However, + * we also have to reasonably assume a device doesn't have too many pending URBs always. + * + * Thus we just use a large queue and simply refuse further transfers. This is not + * a situation which normally ever happens as usually there are at most than 4 or 5 URBs + * in-flight until we reap them. + */ + uint32_t const cTotalUrbs = pState->cInflightUrbs + pState->cFreeUrbs + pState->cLandedUrbs; + if (cTotalUrbs >= VBOXUSB_URB_QUEUE_SIZE) + { + mutex_exit(&pState->Mtx); + LogRelMax(5, (DEVICE_NAME ": vboxUsbSolarisQueueUrb: Max queue size %u reached, refusing further transfers", + cTotalUrbs)); + return NULL; + } + + /* + * Allocate a new URB as we have no free URBs. + */ + mutex_exit(&pState->Mtx); + pUrb = RTMemAllocZ(sizeof(vboxusb_urb_t)); + if (RT_UNLIKELY(!pUrb)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisQueueUrb: Failed to alloc %d bytes\n", sizeof(vboxusb_urb_t))); + return NULL; + } + mutex_enter(&pState->Mtx); + } + + /* + * Add the URB to the inflight list. + */ + list_insert_tail(&pState->hInflightUrbs, pUrb); + ++pState->cInflightUrbs; + + Assert(!pUrb->pMsg); + pUrb->pMsg = pMsg; + pUrb->pState = pState; + pUrb->enmState = VBOXUSB_URB_STATE_INFLIGHT; + pUrb->pvUrbR3 = pUrbReq->pvUrbR3; + pUrb->bEndpoint = pUrbReq->bEndpoint; + pUrb->enmType = pUrbReq->enmType; + pUrb->enmDir = pUrbReq->enmDir; + pUrb->enmStatus = pUrbReq->enmStatus; + pUrb->fShortOk = pUrbReq->fShortOk; + pUrb->pvDataR3 = (RTR3PTR)pUrbReq->pvData; + pUrb->cbDataR3 = pUrbReq->cbData; + pUrb->cIsocPkts = pUrbReq->cIsocPkts; + if (pUrbReq->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrbReq->cIsocPkts; i++) + pUrb->aIsocPkts[i].cbPkt = pUrbReq->aIsocPkts[i].cbPkt; + } + + mutex_exit(&pState->Mtx); + return pUrb; +} + + +/** + * Dequeues a completed URB into the landed list and informs user-land. + * + * @param pUrb The URB to move. + * @param URBStatus The Solaris URB completion code. + * + * @remarks All pipes could be closed at this point (e.g. Device disconnected during inflight URBs) + */ +LOCAL void vboxUsbSolarisDeQueueUrb(vboxusb_urb_t *pUrb, int URBStatus) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeQueue: pUrb=%p\n", pUrb)); + AssertPtrReturnVoid(pUrb); + + pUrb->enmStatus = vboxUsbSolarisGetUrbStatus(URBStatus); + if (pUrb->enmStatus != VUSBSTATUS_OK) + Log((DEVICE_NAME ": vboxUsbSolarisDeQueueUrb: URB failed! URBStatus=%d bEndpoint=%#x\n", URBStatus, pUrb->bEndpoint)); + + vboxusb_state_t *pState = pUrb->pState; + if (RT_LIKELY(pState)) + { + mutex_enter(&pState->Mtx); + pUrb->enmState = VBOXUSB_URB_STATE_LANDED; + + /* + * Remove it from the inflight list & move it to the landed list. + */ + list_remove(&pState->hInflightUrbs, pUrb); + Assert(pState->cInflightUrbs > 0); + --pState->cInflightUrbs; + + list_insert_tail(&pState->hLandedUrbs, pUrb); + ++pState->cLandedUrbs; + + vboxUsbSolarisNotifyComplete(pUrb->pState); + mutex_exit(&pState->Mtx); + return; + } + + /* Well, let's at least not leak memory... */ + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + pUrb->enmStatus = VUSBSTATUS_INVALID; + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeQueue: State Gone\n")); +} + + +/** + * Concatenates a chain message block into a single message block if possible. + * + * @param pUrb The URB to move. + */ +LOCAL void vboxUsbSolarisConcatMsg(vboxusb_urb_t *pUrb) +{ + /* + * Concatenate the whole message rather than doing a chained copy while reaping. + */ + if ( pUrb->pMsg + && pUrb->pMsg->b_cont) + { + mblk_t *pFullMsg = msgpullup(pUrb->pMsg, -1 /* all data */); + if (RT_LIKELY(pFullMsg)) + { + freemsg(pUrb->pMsg); + pUrb->pMsg = pFullMsg; + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisConcatMsg: Failed. Expect glitches due to truncated data!\n")); + } +} + + +/** + * Wakes up a user process signalling URB completion. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisNotifyComplete(vboxusb_state_t *pState) +{ + if (pState->fPollPending) + { + pollhead_t *pPollHead = &pState->PollHead; + pState->fPollPending = false; + mutex_exit(&pState->Mtx); + pollwakeup(pPollHead, POLLIN); + mutex_enter(&pState->Mtx); + } +} + + +/** + * Wakes up a user process signalling a device unplug events. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisNotifyUnplug(vboxusb_state_t *pState) +{ + if (pState->fPollPending) + { + pollhead_t *pPollHead = &pState->PollHead; + pState->fPollPending = false; + mutex_exit(&pState->Mtx); + pollwakeup(pPollHead, POLLHUP); + mutex_enter(&pState->Mtx); + } +} + + +/** + * Performs a Control Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisCtrlXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + AssertPtrReturn(pUrb->pMsg, VERR_INVALID_PARAMETER); + const size_t cbData = pUrb->cbDataR3 > VBOXUSB_CTRL_XFER_SIZE ? pUrb->cbDataR3 - VBOXUSB_CTRL_XFER_SIZE : 0; + + /* + * Allocate a wrapper request. + */ + usb_ctrl_req_t *pReq = usb_alloc_ctrl_req(pState->pDip, cbData, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + uchar_t *pSetupData = pUrb->pMsg->b_rptr; + + /* + * Initialize the Ctrl Xfer Header. + */ + pReq->ctrl_bmRequestType = pSetupData[0]; + pReq->ctrl_bRequest = pSetupData[1]; + pReq->ctrl_wValue = (pSetupData[3] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[2]; + pReq->ctrl_wIndex = (pSetupData[5] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[4]; + pReq->ctrl_wLength = (pSetupData[7] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[6]; + + if ( pUrb->enmDir == VUSBDIRECTION_OUT + && cbData) + { + bcopy(pSetupData + VBOXUSB_CTRL_XFER_SIZE, pReq->ctrl_data->b_wptr, cbData); + pReq->ctrl_data->b_wptr += cbData; + } + + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + + /* + * Initialize callbacks and timeouts. + */ + pReq->ctrl_cb = vboxUsbSolarisCtrlXferCompleted; + pReq->ctrl_exc_cb = vboxUsbSolarisCtrlXferCompleted; + pReq->ctrl_timeout = VBOXUSB_CTRL_XFER_TIMEOUT; + pReq->ctrl_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK; + pReq->ctrl_client_private = (usb_opaque_t)pUrb; + + /* + * Submit the request. + */ + int rc = usb_pipe_ctrl_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: Request failed! bEndpoint=%#x rc=%d\n", pUrb->bEndpoint, rc)); + + usb_free_ctrl_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: Failed to alloc request for %u bytes\n", cbData)); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Control Xfers. + * + * @param pPipe The Ctrl pipe handle. + * @param pReq The Ctrl request. + */ +LOCAL void vboxUsbSolarisCtrlXferCompleted(usb_pipe_handle_t pPipe, usb_ctrl_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + Assert(pReq); + Assert(!(pReq->ctrl_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->ctrl_client_private; + if (RT_LIKELY(pUrb)) + { + /* + * Funky stuff: We need to reconstruct the header for control transfers. + * Let us chain along the data and concatenate the entire message. + */ + mblk_t *pSetupMsg = allocb(sizeof(VUSBSETUP), BPRI_MED); + if (RT_LIKELY(pSetupMsg)) + { + VUSBSETUP SetupData; + SetupData.bmRequestType = pReq->ctrl_bmRequestType; + SetupData.bRequest = pReq->ctrl_bRequest; + SetupData.wValue = pReq->ctrl_wValue; + SetupData.wIndex = pReq->ctrl_wIndex; + SetupData.wLength = pReq->ctrl_wLength; + + bcopy(&SetupData, pSetupMsg->b_wptr, sizeof(VUSBSETUP)); + pSetupMsg->b_wptr += sizeof(VUSBSETUP); + + /* + * Should be safe to update pMsg here without the state mutex as typically nobody else + * touches this URB in the inflight list. + * + * The reason we choose to use vboxUsbSolarisConcatMsg here is that we don't assume the + * message returned by Solaris is one contiguous chunk in 'pMsg->b_rptr'. + */ + Assert(!pUrb->pMsg); + pUrb->pMsg = pSetupMsg; + pUrb->pMsg->b_cont = pReq->ctrl_data; + pReq->ctrl_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: Failed to alloc %u bytes for header\n", sizeof(VUSBSETUP))); + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->ctrl_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: Extreme error! missing private data\n")); + + usb_free_ctrl_req(pReq); +} + + +/** + * Performs a Bulk Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisBulkXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisBulkXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + /* + * Allocate a wrapper request. + */ + size_t const cbAlloc = pUrb->enmDir == VUSBDIRECTION_IN ? pUrb->cbDataR3 : 0; + usb_bulk_req_t *pReq = usb_alloc_bulk_req(pState->pDip, cbAlloc, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Bulk Xfer, callbacks and timeouts. + */ + usb_req_attrs_t fAttributes = USB_ATTRS_AUTOCLEARING; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->bulk_data = pUrb->pMsg; + pUrb->pMsg = NULL; + } + else if ( pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->fShortOk) + { + fAttributes |= USB_ATTRS_SHORT_XFER_OK; + } + + Assert(!pUrb->pMsg); + pReq->bulk_len = pUrb->cbDataR3; + pReq->bulk_cb = vboxUsbSolarisBulkXferCompleted; + pReq->bulk_exc_cb = vboxUsbSolarisBulkXferCompleted; + pReq->bulk_timeout = 0; + pReq->bulk_attributes = fAttributes; + pReq->bulk_client_private = (usb_opaque_t)pUrb; + + /* Don't obtain state lock here, we're just reading unchanging data... */ + if (RT_UNLIKELY(pUrb->cbDataR3 > pState->cbMaxBulkXfer)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Requesting %d bytes when only %d bytes supported by device\n", + pUrb->cbDataR3, pState->cbMaxBulkXfer)); + } + + /* + * Submit the request. + */ + int rc = usb_pipe_bulk_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Request failed! Ep=%#x rc=%d cbData=%u\n", pUrb->bEndpoint, rc, + pReq->bulk_len)); + + usb_free_bulk_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Failed to alloc bulk request\n")); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Bulk Xfers. + * + * @param pPipe The Bulk pipe handle. + * @param pReq The Bulk request. + */ +LOCAL void vboxUsbSolarisBulkXferCompleted(usb_pipe_handle_t pPipe, usb_bulk_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + Assert(pReq); + Assert(!(pReq->bulk_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_LIKELY(pEp)) + { + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->bulk_client_private; + if (RT_LIKELY(pUrb)) + { + Assert(!pUrb->pMsg); + if ( pUrb->enmDir == VUSBDIRECTION_IN + && pReq->bulk_data) + { + pUrb->pMsg = pReq->bulk_data; + pReq->bulk_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + + /* + * Update the URB and move to tail for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->bulk_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: Extreme error! private request data missing!\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: Pipe Gone!\n")); + + usb_free_bulk_req(pReq); +} + + +/** + * Performs an Interrupt Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisIntrXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIntrXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + usb_intr_req_t *pReq = usb_alloc_intr_req(pState->pDip, 0 /* length */, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Intr Xfer, callbacks & timeouts. + */ + usb_req_attrs_t fAttributes = USB_ATTRS_AUTOCLEARING; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->intr_data = pUrb->pMsg; + pUrb->pMsg = NULL; + } + else + { + Assert(pUrb->enmDir == VUSBDIRECTION_IN); + fAttributes |= USB_ATTRS_ONE_XFER; + if (pUrb->fShortOk) + fAttributes |= USB_ATTRS_SHORT_XFER_OK; + } + + Assert(!pUrb->pMsg); + pReq->intr_len = pUrb->cbDataR3; /* Not pEp->EpDesc.wMaxPacketSize */ + pReq->intr_cb = vboxUsbSolarisIntrXferCompleted; + pReq->intr_exc_cb = vboxUsbSolarisIntrXferCompleted; + pReq->intr_timeout = 0; + pReq->intr_attributes = fAttributes; + pReq->intr_client_private = (usb_opaque_t)pUrb; + + /* + * Submit the request. + */ + int rc = usb_pipe_intr_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXfer: usb_pipe_intr_xfer failed! rc=%d bEndpoint=%#x\n", rc, pUrb->bEndpoint)); + + usb_free_intr_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXfer: Failed to alloc intr request\n")); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Intr Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + */ +LOCAL void vboxUsbSolarisIntrXferCompleted(usb_pipe_handle_t pPipe, usb_intr_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIntrXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + Assert(pReq); + Assert(!(pReq->intr_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->intr_client_private; + if (RT_LIKELY(pUrb)) + { + if ( pUrb->enmDir == VUSBDIRECTION_IN + && pReq->intr_data) + { + pUrb->pMsg = pReq->intr_data; + pReq->intr_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->intr_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXferCompleted: Extreme error! private request data missing\n")); + + usb_free_intr_req(pReq); +} + + +/** + * Performs an Isochronous Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisIsocXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocXfer: pState=%p pEp=%p pUrb=%p\n", pState, pEp, pUrb)); */ + + /* + * For Isoc. IN transfers we perform one request and USBA polls the device continuously + * and supplies our Xfer callback with input data. We cannot perform one-shot Isoc. In transfers. + */ + size_t cbData = (pUrb->enmDir == VUSBDIRECTION_IN ? pUrb->cIsocPkts * pUrb->aIsocPkts[0].cbPkt : 0); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Isoc. IN - Queueing\n")); + + mutex_enter(&pState->Mtx); + if (pEp->fIsocPolling) + { + /* + * Queue a maximum of cbMaxIsocData bytes, else fail. + */ + if (pEp->cbIsocInLandedReqs + cbData > pEp->cbMaxIsocData) + { + mutex_exit(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Max Isoc. data %d bytes queued\n", pEp->cbMaxIsocData)); + return VERR_TOO_MUCH_DATA; + } + + list_insert_tail(&pEp->hIsocInUrbs, pUrb); + ++pEp->cIsocInUrbs; + + mutex_exit(&pState->Mtx); + return VINF_SUCCESS; + } + mutex_exit(&pState->Mtx); + } + + int rc = VINF_SUCCESS; + usb_isoc_req_t *pReq = usb_alloc_isoc_req(pState->pDip, pUrb->cIsocPkts, cbData, USB_FLAGS_NOSLEEP); + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: enmDir=%#x cIsocPkts=%d aIsocPkts[0]=%d cbDataR3=%d\n", pUrb->enmDir, + pUrb->cIsocPkts, pUrb->aIsocPkts[0].cbPkt, pUrb->cbDataR3)); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Isoc Xfer, callbacks & timeouts. + */ + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + pReq->isoc_pkt_descr[i].isoc_pkt_length = pUrb->aIsocPkts[i].cbPkt; + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->isoc_data = pUrb->pMsg; + pReq->isoc_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_ISOC_XFER_ASAP; + pReq->isoc_cb = vboxUsbSolarisIsocOutXferCompleted; + pReq->isoc_exc_cb = vboxUsbSolarisIsocOutXferCompleted; + pReq->isoc_client_private = (usb_opaque_t)pUrb; + } + else + { + pReq->isoc_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_ISOC_XFER_ASAP | USB_ATTRS_SHORT_XFER_OK; + pReq->isoc_cb = vboxUsbSolarisIsocInXferCompleted; + pReq->isoc_exc_cb = vboxUsbSolarisIsocInXferError; + pReq->isoc_client_private = (usb_opaque_t)pState; + } + pReq->isoc_pkts_count = pUrb->cIsocPkts; + pReq->isoc_pkts_length = 0; /* auto compute */ + + /* + * Submit the request. + */ + rc = usb_pipe_isoc_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + /* + * Add the first Isoc. IN URB to the queue as well. + */ + mutex_enter(&pState->Mtx); + list_insert_tail(&pEp->hIsocInUrbs, pUrb); + ++pEp->cIsocInUrbs; + pEp->fIsocPolling = true; + mutex_exit(&pState->Mtx); + } + + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocXfer: usb_pipe_isoc_xfer failed! rc=%d\n", rc)); + rc = VERR_PIPE_IO_ERROR; + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + mutex_enter(&pState->Mtx); + vboxusb_urb_t *pIsocFailedUrb = list_remove_tail(&pEp->hIsocInUrbs); + if (pIsocFailedUrb) + { + RTMemFree(pIsocFailedUrb); + --pEp->cIsocInUrbs; + } + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + } + } + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + } + + usb_free_isoc_req(pReq); + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Failed to alloc isoc req for %d packets\n", pUrb->cIsocPkts)); + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * Completion/Exception callback for Isoc IN Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocInXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); */ + + vboxusb_state_t *pState = (vboxusb_state_t *)pReq->isoc_client_private; + if (RT_LIKELY(pState)) + { + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if ( pEp + && pEp->pPipe) + { +#if 0 + /* + * Stop polling if all packets failed. + */ + if (pReq->isoc_error_count == pReq->isoc_pkts_count) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Stopping polling! Too many errors\n")); + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pPipe, USB_FLAGS_NOSLEEP); + mutex_enter(&pState->Mtx); + pEp->fIsocPolling = false; + } +#endif + + /** @todo Query and verify this at runtime. */ + AssertCompile(sizeof(VUSBISOC_PKT_DESC) == sizeof(usb_isoc_pkt_descr_t)); + if (RT_LIKELY(pReq->isoc_data)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: cIsocInUrbs=%d cbIsocInLandedReqs=%d\n", pEp->cIsocInUrbs, + pEp->cbIsocInLandedReqs)); + + mutex_enter(&pState->Mtx); + + /* + * If there are waiting URBs, satisfy the oldest one. + */ + if ( pEp->cIsocInUrbs > 0 + && pEp->cbIsocInLandedReqs == 0) + { + vboxusb_urb_t *pUrb = list_remove_head(&pEp->hIsocInUrbs); + if (RT_LIKELY(pUrb)) + { + --pEp->cIsocInUrbs; + mutex_exit(&pState->Mtx); + + for (unsigned i = 0; i < pReq->isoc_pkts_count; i++) + { + pUrb->aIsocPkts[i].cbActPkt = pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].enmStatus = vboxUsbSolarisGetUrbStatus(pReq->isoc_pkt_descr[i].isoc_pkt_status); + } + + pUrb->pMsg = pReq->isoc_data; + pReq->isoc_data = NULL; + + /* + * Move to landed list + */ + mutex_enter(&pState->Mtx); + list_insert_tail(&pState->hLandedUrbs, pUrb); + ++pState->cLandedUrbs; + vboxUsbSolarisNotifyComplete(pState); + } + else + { + /* Huh!? cIsocInUrbs is wrong then! Should never happen unless we decide to decrement cIsocInUrbs in + Reap time */ + pEp->cIsocInUrbs = 0; + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Extreme error! Isoc. counter borked!\n")); + } + + mutex_exit(&pState->Mtx); + usb_free_isoc_req(pReq); + return; + } + + mutex_exit(&pState->Mtx); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Data missing\n")); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Pipe Gone\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: State Gone\n")); + + usb_free_isoc_req(pReq); +} + + +/** + * Exception callback for Isoc IN Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocInXferError(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: pPipe=%p pReq=%p\n", pPipe, pReq)); + + vboxusb_state_t *pState = (vboxusb_state_t *)pReq->isoc_client_private; + if (RT_UNLIKELY(!pState)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: State Gone\n")); + usb_free_isoc_req(pReq); + return; + } + + mutex_enter(&pState->Mtx); + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_UNLIKELY(!pEp)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Pipe Gone\n")); + mutex_exit(&pState->Mtx); + usb_free_isoc_req(pReq); + return; + } + + switch(pReq->isoc_completion_reason) + { + case USB_CR_NO_RESOURCES: + { + /* + * Resubmit the request in case the original request did not complete due to + * immediately unavailable requests + */ + mutex_exit(&pState->Mtx); + usb_pipe_isoc_xfer(pPipe, pReq, USB_FLAGS_NOSLEEP); + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Resubmitted Isoc. IN request due to unavailable resources\n")); + return; + } + + case USB_CR_PIPE_CLOSING: + case USB_CR_STOPPED_POLLING: + case USB_CR_PIPE_RESET: + { + pEp->fIsocPolling = false; + usb_free_isoc_req(pReq); + break; + } + + default: + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Stopping Isoc. IN polling due to rc=%d\n", + pReq->isoc_completion_reason)); + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pPipe, USB_FLAGS_NOSLEEP); + usb_free_isoc_req(pReq); + mutex_enter(&pState->Mtx); + break; + } + } + + /* + * Dequeue i.e. delete the last queued Isoc In. URB. as failed. + */ + vboxusb_urb_t *pUrb = list_remove_tail(&pEp->hIsocInUrbs); + if (pUrb) + { + --pEp->cIsocInUrbs; + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Deleting last queued URB as it failed\n")); + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + vboxUsbSolarisNotifyComplete(pState); + } + + mutex_exit(&pState->Mtx); +} + + +/** + * Completion/Exception callback for Isoc OUT Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocOutXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_LIKELY(pEp)) + { + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->isoc_client_private; + if (RT_LIKELY(pUrb)) + { + size_t cbActPkt = 0; + for (int i = 0; i < pReq->isoc_pkts_count; i++) + { + cbActPkt += pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].cbActPkt = pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].enmStatus = vboxUsbSolarisGetUrbStatus(pReq->isoc_pkt_descr[i].isoc_pkt_status); + } + + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: cIsocPkts=%d cbData=%d cbActPkt=%d\n", pUrb->cIsocPkts, + pUrb->cbDataR3, cbActPkt)); + + if (pReq->isoc_completion_reason == USB_CR_OK) + { + if (RT_UNLIKELY(pUrb->pMsg != pReq->isoc_data)) /* Paranoia */ + { + freemsg(pUrb->pMsg); + pUrb->pMsg = pReq->isoc_data; + } + } + pReq->isoc_data = NULL; + + pUrb->cIsocPkts = pReq->isoc_pkts_count; + pUrb->cbDataR3 = cbActPkt; + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->isoc_completion_reason); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: Missing private data!?! Dropping OUT pUrb\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: Pipe Gone\n")); + + usb_free_isoc_req(pReq); +} + + +/** + * Callback when the device gets disconnected. + * + * @param pDip The module structure instance. + * + * @returns Solaris USB error code. + */ +LOCAL int vboxUsbSolarisDeviceDisconnected(dev_info_t *pDip) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceDisconnected: pDip=%p\n", pDip)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + + if (RT_LIKELY(pState)) + { + /* + * Serialize access: exclusive access to the state. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + pState->DevState = USB_DEV_DISCONNECTED; + + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + vboxUsbSolarisNotifyUnplug(pState); + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + return USB_SUCCESS; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceDisconnected: Failed to get device state!\n")); + return USB_FAILURE; +} + + +/** + * Callback when the device gets reconnected. + * + * @param pDip The module structure instance. + * + * @returns Solaris USB error code. + */ +LOCAL int vboxUsbSolarisDeviceReconnected(dev_info_t *pDip) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceReconnected: pDip=%p\n", pDip)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + + if (RT_LIKELY(pState)) + { + vboxUsbSolarisDeviceRestore(pState); + return USB_SUCCESS; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceReconnected: Failed to get device state!\n")); + return USB_FAILURE; +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisDeviceRestore(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceRestore: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + /* + * Raise device power. + */ + vboxUsbSolarisPowerBusy(pState); + int rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + + /* + * Check if the same device is resumed/reconnected. + */ + rc = usb_check_same_device(pState->pDip, + NULL, /* log handle */ + USB_LOG_L2, /* log level */ + -1, /* log mask */ + USB_CHK_ALL, /* check level */ + NULL); /* device string */ + + if (rc != USB_SUCCESS) + { + mutex_enter(&pState->Mtx); + pState->DevState = USB_DEV_DISCONNECTED; + mutex_exit(&pState->Mtx); + + /* Do we need to inform userland here? */ + vboxUsbSolarisPowerIdle(pState); + Log((DEVICE_NAME ": vboxUsbSolarisDeviceRestore: Not the same device\n")); + return; + } + + /* + * Serialize access to not race with other PM functions. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + + mutex_enter(&pState->Mtx); + if (pState->DevState == USB_DEV_DISCONNECTED) + pState->DevState = USB_DEV_ONLINE; + else if (pState->DevState == USB_DEV_SUSPENDED) + pState->DevState = USB_DEV_ONLINE; + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + vboxUsbSolarisPowerIdle(pState); +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisDeviceSuspend(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: pState=%p\n", pState)); + + int rc = VERR_VUSB_DEVICE_IS_SUSPENDED; + mutex_enter(&pState->Mtx); + + switch (pState->DevState) + { + case USB_DEV_SUSPENDED: + { + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: Invalid device state %d\n", pState->DevState)); + break; + } + + case USB_DEV_ONLINE: + case USB_DEV_DISCONNECTED: + case USB_DEV_PWRED_DOWN: + { + int PreviousState = pState->DevState; + pState->DevState = USB_DEV_DISCONNECTED; + + /** @todo this doesn't make sense when for e.g. an INTR IN URB with infinite + * timeout is pending on the device. Fix suspend logic later. */ + /* + * Drain pending URBs. + */ + for (int i = 0; i < VBOXUSB_DRAIN_TIME; i++) + { + if (pState->cInflightUrbs < 1) + break; + + mutex_exit(&pState->Mtx); + delay(drv_usectohz(100000)); + mutex_enter(&pState->Mtx); + } + + /* + * Deny suspend if we still have pending URBs. + */ + if (pState->cInflightUrbs > 0) + { + pState->DevState = PreviousState; + LogRel((DEVICE_NAME ": Cannot suspend %s %s (Ident=%s), %d inflight URBs\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent, pState->cInflightUrbs)); + + mutex_exit(&pState->Mtx); + return VERR_RESOURCE_BUSY; + } + + pState->cInflightUrbs = 0; + + /* + * Serialize access to not race with Open/Detach/Close and + * Close all pipes including the default pipe. + */ + mutex_exit(&pState->Mtx); + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + vboxUsbSolarisCloseAllPipes(pState, true /* default pipe */); + vboxUsbSolarisNotifyUnplug(pState); + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + LogRel((DEVICE_NAME ": Suspended %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent)); + return VINF_SUCCESS; + } + } + + mutex_exit(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: Returns %d\n", rc)); + return rc; +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisDeviceResume(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceResume: pState=%p\n", pState)); + return vboxUsbSolarisDeviceRestore(pState); +} + + +/** + * Flags the PM component as busy so the system will not manage it's power. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisPowerBusy(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisPowerBusy: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + mutex_enter(&pState->Mtx); + if (pState->pPower) + { + pState->pPower->PowerBusy++; + mutex_exit(&pState->Mtx); + + int rc = pm_busy_component(pState->pDip, 0 /* component */); + if (rc != DDI_SUCCESS) + { + Log((DEVICE_NAME ": vboxUsbSolarisPowerBusy: Busy component failed! rc=%d\n", rc)); + mutex_enter(&pState->Mtx); + pState->pPower->PowerBusy--; + mutex_exit(&pState->Mtx); + } + } + else + mutex_exit(&pState->Mtx); +} + + +/** + * Flags the PM component as idle so its power managed by the system. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisPowerIdle(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisPowerIdle: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + if (pState->pPower) + { + int rc = pm_idle_component(pState->pDip, 0 /* component */); + if (rc == DDI_SUCCESS) + { + mutex_enter(&pState->Mtx); + Assert(pState->pPower->PowerBusy > 0); + pState->pPower->PowerBusy--; + mutex_exit(&pState->Mtx); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisPowerIdle: Idle component failed! rc=%d\n", rc)); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c new file mode 100644 index 00000000..27e3868c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c @@ -0,0 +1,1064 @@ +/* $Id: VBoxUSBMon-solaris.c $ */ +/** @file + * VirtualBox USB Monitor Driver, Solaris Hosts. + */ + +/* + * 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 . + * + * 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_USB_DRV +#include "VBoxUSBFilterMgr.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USBDRV_MAJOR_VER 2 +#define USBDRV_MINOR_VER 0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxusbmon" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox USBMon" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int VBoxUSBMonSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +static int VBoxUSBMonSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +static int VBoxUSBMonSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int VBoxUSBMonSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int VBoxUSBMonSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal); +static int VBoxUSBMonSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult); +static int VBoxUSBMonSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int VBoxUSBMonSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxUSBMonSolarisCbOps = +{ + VBoxUSBMonSolarisOpen, + VBoxUSBMonSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + VBoxUSBMonSolarisRead, + VBoxUSBMonSolarisWrite, + VBoxUSBMonSolarisIOCtl, + 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_VBoxUSBMonSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxUSBMonSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxUSBMonSolarisAttach, + VBoxUSBMonSolarisDetach, + nodev, /* reset */ + &g_VBoxUSBMonSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxUSBMonSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxUSBMonSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxUSBMonSolarisModLinkage = +{ + MODREV_1, + &g_VBoxUSBMonSolarisModule, + NULL, +}; + +/** + * Client driver info. + */ +typedef struct vboxusbmon_client_t +{ + dev_info_t *pDip; /* Client device info. pointer */ + VBOXUSB_CLIENT_INFO Info; /* Client registration data. */ + struct vboxusbmon_client_t *pNext; /* Pointer to next client */ +} vboxusbmon_client_t; + +/** + * Device state info. + */ +typedef struct +{ + RTPROCESS Process; /* The process (id) of the session */ +} vboxusbmon_state_t; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global Device handle we only support one instance. */ +static dev_info_t *g_pDip = NULL; +/** Global Mutex. */ +static kmutex_t g_VBoxUSBMonSolarisMtx; +/** Global list of client drivers registered with us. */ +vboxusbmon_client_t *g_pVBoxUSBMonSolarisClients = NULL; +/** Opaque pointer to list of soft states. */ +static void *g_pVBoxUSBMonSolarisState; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int vboxUSBMonSolarisProcessIOCtl(int iFunction, void *pvState, void *pvData, size_t cbData, size_t *pcbReturnedData); +static int vboxUSBMonSolarisResetDevice(char *pszDevicePath, bool fReattach); + + +/********************************************************************************************************************************* +* Monitor Global Hooks * +*********************************************************************************************************************************/ +static int vboxUSBMonSolarisClientInfo(vboxusbmon_state_t *pState, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip); +int VBoxUSBMonSolarisElectDriver(usb_dev_descr_t *pDevDesc, usb_dev_str_t *pDevStrings, char *pszDevicePath, int Bus, int Port, + char **ppszDrv, void *pvReserved); + + +/** + * Kernel entry points + */ +int _init(void) +{ + int rc; + + LogFunc((DEVICE_NAME ": _init\n")); + + g_pDip = NULL; + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxUSBMonSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": _init: Failed to disable autounloading!\n")); + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize global mutex. + */ + mutex_init(&g_VBoxUSBMonSolarisMtx, NULL, MUTEX_DRIVER, NULL); + rc = VBoxUSBFilterInit(); + if (RT_SUCCESS(rc)) + { + rc = ddi_soft_state_init(&g_pVBoxUSBMonSolarisState, sizeof(vboxusbmon_state_t), 1); + if (!rc) + { + rc = mod_install(&g_VBoxUSBMonSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ": _init: mod_install failed! rc=%d\n", rc)); + ddi_soft_state_fini(&g_pVBoxUSBMonSolarisState); + } + else + LogRel((DEVICE_NAME ": _init: ddi_soft_state_init failed! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": _init: VBoxUSBFilterInit failed! rc=%d\n", rc)); + + mutex_destroy(&g_VBoxUSBMonSolarisMtx); + RTR0Term(); + } + else + LogRel((DEVICE_NAME ": _init: RTR0Init failed! rc=%d\n", rc)); + + return -1; +} + + +int _fini(void) +{ + int rc; + + LogFunc((DEVICE_NAME ": _fini\n")); + + rc = mod_remove(&g_VBoxUSBMonSolarisModLinkage); + if (!rc) + { + ddi_soft_state_fini(&g_pVBoxUSBMonSolarisState); + VBoxUSBFilterTerm(); + mutex_destroy(&g_VBoxUSBMonSolarisMtx); + + RTR0Term(); + } + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ": _info\n")); + + return mod_info(&g_VBoxUSBMonSolarisModLinkage, pModInfo); +} + + +/** + * Attach entry point, to attach a device to the system or resume it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @returns corresponding solaris error code. + */ +static int VBoxUSBMonSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisAttach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + switch (enmCmd) + { + case DDI_ATTACH: + { + if (RT_UNLIKELY(g_pDip)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: Global instance already initialized\n")); + return DDI_FAILURE; + } + + g_pDip = pDip; + int rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME, S_IFCHR, 0 /* instance */, DDI_PSEUDO, 0 /* flags */, + "none", "none", 0660); + if (rc == DDI_SUCCESS) + { + rc = usb_register_dev_driver(g_pDip, VBoxUSBMonSolarisElectDriver); + if (rc == DDI_SUCCESS) + { + ddi_report_dev(pDip); + return DDI_SUCCESS; + } + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: Failed to register driver election callback! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: ddi_create_minor_node failed! rc=%d\n", rc)); + return DDI_FAILURE; + } + + case DDI_RESUME: + { + /* We don't have to bother about power management. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Detach entry point, to detach a device to the system or suspend it. + * + * @param pDip The module structure instance. + * @param enmCmd Attach type (ddi_attach_cmd_t) + * + * @returns corresponding solaris error code. + */ +static int VBoxUSBMonSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisDetach\n")); + + switch (enmCmd) + { + case DDI_DETACH: + { + /* + * Free all registered clients' info. + */ + mutex_enter(&g_VBoxUSBMonSolarisMtx); + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + while (pCur) + { + vboxusbmon_client_t *pNext = pCur->pNext; + RTMemFree(pCur); + pCur = pNext; + } + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + usb_unregister_dev_driver(g_pDip); + + ddi_remove_minor_node(pDip, NULL); + g_pDip = NULL; + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { + /* We don't have to bother about power management. */ + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Info entry point, called by solaris kernel for obtaining driver info. + * + * @param pDip The module structure instance (do not use). + * @param enmCmd Information request type. + * @param pvArg Type specific argument. + * @param ppvResult Where to store the requested info. + * + * @returns corresponding solaris error code. + */ +static int VBoxUSBMonSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + int rc = DDI_SUCCESS; + + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisGetInfo\n")); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + *ppvResult = (void *)g_pDip; + if (!*ppvResult) + rc = DDI_FAILURE; + break; + } + + case DDI_INFO_DEVT2INSTANCE: + { + /* There can only be a single-instance of this driver and thus its instance number is 0. */ + *ppvResult = (void *)0; + break; + } + + default: + rc = DDI_FAILURE; + break; + } + return rc; +} + + +static int VBoxUSBMonSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred) +{ + vboxusbmon_state_t *pState = NULL; + unsigned iOpenInstance; + + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisOpen\n")); + + /* + * Verify we are being opened as a character device. + */ + if (fType != OTYP_CHR) + return EINVAL; + + /* + * Verify that we're called after attach. + */ + if (!g_pDip) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisOpen: Invalid state for opening\n")); + return ENXIO; + } + + for (iOpenInstance = 0; iOpenInstance < 4096; iOpenInstance++) + { + if ( !ddi_get_soft_state(g_pVBoxUSBMonSolarisState, iOpenInstance) /* faster */ + && ddi_soft_state_zalloc(g_pVBoxUSBMonSolarisState, iOpenInstance) == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, iOpenInstance); + break; + } + } + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisOpen: Too many open instances")); + return ENXIO; + } + + pState->Process = RTProcSelf(); + *pDev = makedevice(getmajor(*pDev), iOpenInstance); + + NOREF(fFlag); + NOREF(pCred); + + return 0; +} + + +static int VBoxUSBMonSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred) +{ + vboxusbmon_state_t *pState = NULL; + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisClose\n")); + + pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, getminor(Dev)); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisClose: Failed to get state\n")); + return EFAULT; + } + + /* + * Remove all filters for this client process. + */ + VBoxUSBFilterRemoveOwner(pState->Process); + + ddi_soft_state_free(g_pVBoxUSBMonSolarisState, getminor(Dev)); + pState = NULL; + + NOREF(fFlag); + NOREF(fType); + NOREF(pCred); + + return 0; +} + + +static int VBoxUSBMonSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisRead\n")); + return 0; +} + + +static int VBoxUSBMonSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisWrite\n")); + return 0; +} + + +/** @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(Code) (((Code) >> 16) & IOCPARM_MASK) +#endif + +static int VBoxUSBMonSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Dev=%d Cmd=%d pArg=%p Mode=%d\n", Dev, Cmd, pArg)); + + /* + * Get the session from the soft state item. + */ + vboxusbmon_state_t *pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, getminor(Dev)); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: No state data for minor instance %d\n", getminor(Dev))); + return EINVAL; + } + + /* + * Read the request wrapper. Though We don't really need wrapper struct. now + * it's room for the future as Solaris isn't generous regarding the size. + */ + VBOXUSBREQ ReqWrap; + if (IOCPARM_LEN(Cmd) != sizeof(ReqWrap)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: bad request %#x size=%d expected=%d\n", Cmd, IOCPARM_LEN(Cmd), + sizeof(ReqWrap))); + return ENOTTY; + } + + int rc = ddi_copyin((void *)pArg, &ReqWrap, sizeof(ReqWrap), Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyin failed to read header pArg=%p Cmd=%d. rc=%d\n", pArg, Cmd, rc)); + return EINVAL; + } + + if (ReqWrap.u32Magic != VBOXUSBMON_MAGIC) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Bad magic %#x; pArg=%p Cmd=%d\n", ReqWrap.u32Magic, pArg, Cmd)); + return EINVAL; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || ReqWrap.cbData > _1M*16)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Bad size %#x; pArg=%p Cmd=%d\n", ReqWrap.cbData, pArg, Cmd)); + return EINVAL; + } + + /* + * Read the request. + */ + void *pvBuf = RTMemTmpAlloc(ReqWrap.cbData); + if (RT_UNLIKELY(!pvBuf)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: RTMemTmpAlloc failed to alloc %d bytes\n", ReqWrap.cbData)); + return ENOMEM; + } + + rc = ddi_copyin((void *)(uintptr_t)ReqWrap.pvDataR3, pvBuf, ReqWrap.cbData, Mode); + if (RT_UNLIKELY(rc)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyin failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, + rc)); + return EFAULT; + } + if (RT_UNLIKELY( ReqWrap.cbData != 0 + && !RT_VALID_PTR(pvBuf))) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: pvBuf Invalid pointer %p\n", pvBuf)); + return EINVAL; + } + Log((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: pid=%d\n", (int)RTProcSelf())); + + /* + * Process the IOCtl. + */ + size_t cbDataReturned = 0; + rc = vboxUSBMonSolarisProcessIOCtl(Cmd, pState, pvBuf, ReqWrap.cbData, &cbDataReturned); + ReqWrap.rc = rc; + rc = 0; + + if (RT_UNLIKELY(cbDataReturned > ReqWrap.cbData)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Too much output data %d expected %d\n", cbDataReturned, ReqWrap.cbData)); + cbDataReturned = ReqWrap.cbData; + } + + ReqWrap.cbData = cbDataReturned; + + /* + * Copy the request back to user space. + */ + rc = ddi_copyout(&ReqWrap, (void *)pArg, sizeof(ReqWrap), Mode); + if (RT_LIKELY(!rc)) + { + /* + * Copy the payload (if any) back to user space. + */ + if (cbDataReturned > 0) + { + rc = ddi_copyout(pvBuf, (void *)(uintptr_t)ReqWrap.pvDataR3, cbDataReturned, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyout failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, + pArg, Cmd, rc)); + rc = EFAULT; + } + } + } + else + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyout(1) failed pArg=%p Cmd=%d\n", pArg, Cmd)); + rc = EFAULT; + } + + *pVal = rc; + RTMemTmpFree(pvBuf); + return rc; +} + + +/** + * IOCtl processor for user to kernel and kernel to kernel communication. + * + * @returns VBox status code. + * + * @param iFunction The requested function. + * @param pvState Opaque pointer to driver state used for getting + * ring-3 process (Id). + * @param pvData The input/output data buffer. Can be NULL + * depending on the function. + * @param cbData The max size of the data buffer. + * @param pcbReturnedData Where to store the amount of returned data. Can + * be NULL. + */ +static int vboxUSBMonSolarisProcessIOCtl(int iFunction, void *pvState, void *pvData, size_t cbData, size_t *pcbReturnedData) +{ + LogFunc((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: iFunction=%d pvBuf=%p cbBuf=%zu\n", iFunction, pvData, cbData)); + + AssertPtrReturn(pvState, VERR_INVALID_POINTER); + vboxusbmon_state_t *pState = (vboxusbmon_state_t *)pvState; + int rc; + +#define CHECKRET_MIN_SIZE(mnemonic, cbMin) \ + do { \ + if (RT_UNLIKELY(cbData < (cbMin))) \ + { \ + LogRel(("vboxUSBSolarisProcessIOCtl: " mnemonic ": cbData=%#zx (%zu) min is %#zx (%zu)\n", \ + cbData, cbData, (size_t)(cbMin), (size_t)(cbMin))); \ + return VERR_BUFFER_OVERFLOW; \ + } \ + if (RT_UNLIKELY((cbMin) != 0 && !RT_VALID_PTR(pvData))) \ + { \ + LogRel(("vboxUSBSolarisProcessIOCtl: " mnemonic ": Invalid pointer %p\n", pvData)); \ + return VERR_INVALID_POINTER; \ + } \ + } while (0) + + switch (iFunction) + { + case VBOXUSBMON_IOCTL_ADD_FILTER: + { + CHECKRET_MIN_SIZE("ADD_FILTER", sizeof(VBOXUSBREQ_ADD_FILTER)); + + VBOXUSBREQ_ADD_FILTER *pReq = (VBOXUSBREQ_ADD_FILTER *)pvData; + PUSBFILTER pFilter = (PUSBFILTER)&pReq->Filter; + + Log(("vboxUSBMonSolarisProcessIOCtl: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x " + "bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT))); + Log(("vboxUSBMonSolarisProcessIOCtl: Manufacturer=%s Product=%s Serial=%s\n", + USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "")); + + rc = USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false /* fMustBePresent */); AssertRC(rc); + + rc = VBoxUSBFilterAdd(pFilter, pState->Process, &pReq->uId); + *pcbReturnedData = cbData; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: ADD_FILTER (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_REMOVE_FILTER: + { + CHECKRET_MIN_SIZE("REMOVE_FILTER", sizeof(VBOXUSBREQ_REMOVE_FILTER)); + + VBOXUSBREQ_REMOVE_FILTER *pReq = (VBOXUSBREQ_REMOVE_FILTER *)pvData; + rc = VBoxUSBFilterRemove(pState->Process, (uintptr_t)pReq->uId); + *pcbReturnedData = 0; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: REMOVE_FILTER (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_RESET_DEVICE: + { + CHECKRET_MIN_SIZE("RESET_DEVICE", sizeof(VBOXUSBREQ_RESET_DEVICE)); + + VBOXUSBREQ_RESET_DEVICE *pReq = (VBOXUSBREQ_RESET_DEVICE *)pvData; + rc = vboxUSBMonSolarisResetDevice(pReq->szDevicePath, pReq->fReattach); + *pcbReturnedData = 0; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: RESET_DEVICE (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_CLIENT_INFO: + { + CHECKRET_MIN_SIZE("CLIENT_INFO", sizeof(VBOXUSBREQ_CLIENT_INFO)); + + VBOXUSBREQ_CLIENT_INFO *pReq = (VBOXUSBREQ_CLIENT_INFO *)pvData; + rc = vboxUSBMonSolarisClientInfo(pState, pReq); + *pcbReturnedData = cbData; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: CLIENT_INFO (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_GET_VERSION: + { + CHECKRET_MIN_SIZE("GET_VERSION", sizeof(VBOXUSBREQ_GET_VERSION)); + + PVBOXUSBREQ_GET_VERSION pGetVersionReq = (PVBOXUSBREQ_GET_VERSION)pvData; + pGetVersionReq->u32Major = VBOXUSBMON_VERSION_MAJOR; + pGetVersionReq->u32Minor = VBOXUSBMON_VERSION_MINOR; + *pcbReturnedData = sizeof(VBOXUSBREQ_GET_VERSION); + rc = VINF_SUCCESS; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: GET_VERSION returned %d\n", rc)); + break; + } + + default: + { + LogRel((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: Unknown request (Process:%d) %#x\n", pState->Process, + iFunction)); + *pcbReturnedData = 0; + rc = VERR_NOT_SUPPORTED; + break; + } + } + return rc; +} + + +static int vboxUSBMonSolarisResetDevice(char *pszDevicePath, bool fReattach) +{ + int rc = VERR_GENERAL_FAILURE; + + LogFunc((DEVICE_NAME ": vboxUSBMonSolarisResetDevice: pszDevicePath=%s fReattach=%d\n", pszDevicePath, fReattach)); + + /* + * Try grabbing the dev_info_t. + */ + dev_info_t *pDeviceInfo = e_ddi_hold_devi_by_path(pszDevicePath, 0); + if (pDeviceInfo) + { + ddi_release_devi(pDeviceInfo); + + /* + * Grab the root device node from the parent hub for resetting. + */ + dev_info_t *pTmpDeviceInfo = NULL; + for (;;) + { + pTmpDeviceInfo = ddi_get_parent(pDeviceInfo); + if (!pTmpDeviceInfo) + { + LogRel((DEVICE_NAME ":vboxUSBMonSolarisResetDevice: Failed to get parent device info for %s\n", pszDevicePath)); + return VERR_GENERAL_FAILURE; + } + + if (ddi_prop_exists(DDI_DEV_T_ANY, pTmpDeviceInfo, DDI_PROP_DONTPASS, "usb-port-count")) /* parent hub */ + break; + + pDeviceInfo = pTmpDeviceInfo; + } + + /* + * Try re-enumerating the device. + */ + rc = usb_reset_device(pDeviceInfo, fReattach ? USB_RESET_LVL_REATTACH : USB_RESET_LVL_DEFAULT); + Log((DEVICE_NAME ": vboxUSBMonSolarisResetDevice: usb_reset_device for %s level=%s rc=%d\n", pszDevicePath, + fReattach ? "ReAttach" : "Default", rc)); + + switch (rc) + { + case USB_SUCCESS: rc = VINF_SUCCESS; break; + case USB_INVALID_PERM: rc = VERR_PERMISSION_DENIED; break; + case USB_INVALID_ARGS: rc = VERR_INVALID_PARAMETER; break; + case USB_BUSY: rc = VERR_RESOURCE_BUSY; break; + case USB_INVALID_CONTEXT: rc = VERR_INVALID_CONTEXT; break; + case USB_FAILURE: rc = VERR_GENERAL_FAILURE; break; + + default: rc = VERR_UNRESOLVED_ERROR; break; + } + } + else + { + rc = VERR_INVALID_HANDLE; + LogRel((DEVICE_NAME ": vboxUSBMonSolarisResetDevice: Cannot obtain device info for %s\n", pszDevicePath)); + } + + return rc; +} + + +/** + * Query client driver information. This also has a side-effect that it informs + * the client driver which upcoming VM process should be allowed to open it. + * + * @returns VBox status code. + * @param pState Pointer to the device state. + * @param pClientInfo Pointer to the client info. object. + */ +static int vboxUSBMonSolarisClientInfo(vboxusbmon_state_t *pState, PVBOXUSB_CLIENT_INFO pClientInfo) +{ + LogFunc((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: pState=%p pClientInfo=%p\n", pState, pClientInfo)); + + AssertPtrReturn(pState, VERR_INVALID_POINTER); + AssertPtrReturn(pClientInfo, VERR_INVALID_POINTER); + + mutex_enter(&g_VBoxUSBMonSolarisMtx); + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + vboxusbmon_client_t *pPrev = NULL; + while (pCur) + { + if (strncmp(pClientInfo->szDeviceIdent, pCur->Info.szDeviceIdent, sizeof(pCur->Info.szDeviceIdent) - 1) == 0) + { + pClientInfo->Instance = pCur->Info.Instance; + RTStrPrintf(pClientInfo->szClientPath, sizeof(pClientInfo->szClientPath), "%s", pCur->Info.szClientPath); + + /* + * Inform the client driver that this is the client process that is going to open it. We can predict the future! + */ + int rc; + if (pCur->Info.pfnSetConsumerCredentials) + { + rc = pCur->Info.pfnSetConsumerCredentials(pState->Process, pCur->Info.Instance, NULL /* pvReserved */); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: pfnSetConsumerCredentials failed! rc=%d\n", rc)); + } + else + rc = VERR_INVALID_FUNCTION; + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: Found %s, rc=%d\n", pClientInfo->szDeviceIdent, rc)); + return rc; + } + pPrev = pCur; + pCur = pCur->pNext; + } + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + LogRel((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: Failed to find client %s\n", pClientInfo->szDeviceIdent)); + return VERR_NOT_FOUND; +} + + +/** + * Registers client driver. + * + * @returns VBox status code. + */ +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisRegisterClient: pClientDip=%p pClientInfo=%p\n", pClientDip, pClientInfo)); + AssertPtrReturn(pClientInfo, VERR_INVALID_PARAMETER); + + if (RT_LIKELY(g_pDip)) + { + vboxusbmon_client_t *pClient = RTMemAllocZ(sizeof(vboxusbmon_client_t)); + if (RT_LIKELY(pClient)) + { + pClient->Info.Instance = pClientInfo->Instance; + strncpy(pClient->Info.szClientPath, pClientInfo->szClientPath, sizeof(pClient->Info.szClientPath)); + strncpy(pClient->Info.szDeviceIdent, pClientInfo->szDeviceIdent, sizeof(pClient->Info.szDeviceIdent)); + pClient->Info.pfnSetConsumerCredentials = pClientInfo->pfnSetConsumerCredentials; + pClient->pDip = pClientDip; + + mutex_enter(&g_VBoxUSBMonSolarisMtx); + pClient->pNext = g_pVBoxUSBMonSolarisClients; + g_pVBoxUSBMonSolarisClients = pClient; + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": Client registered (ClientPath=%s Ident=%s)\n", pClient->Info.szClientPath, + pClient->Info.szDeviceIdent)); + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; + } + return VERR_INVALID_STATE; +} + + +/** + * Deregisters client driver. + * + * @returns VBox status code. + */ +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisUnregisterClient: pClientDip=%p\n", pClientDip)); + AssertReturn(pClientDip, VERR_INVALID_PARAMETER); + + if (RT_LIKELY(g_pDip)) + { + mutex_enter(&g_VBoxUSBMonSolarisMtx); + + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + vboxusbmon_client_t *pPrev = NULL; + while (pCur) + { + if (pCur->pDip == pClientDip) + { + if (pPrev) + pPrev->pNext = pCur->pNext; + else + g_pVBoxUSBMonSolarisClients = pCur->pNext; + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": Client unregistered (ClientPath=%s Ident=%s)\n", pCur->Info.szClientPath, + pCur->Info.szDeviceIdent)); + RTMemFree(pCur); + return VINF_SUCCESS; + } + pPrev = pCur; + pCur = pCur->pNext; + } + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisUnregisterClient: Failed to find registered client %p\n", pClientDip)); + return VERR_NOT_FOUND; + } + return VERR_INVALID_STATE; +} + + +/** + * USBA driver election callback. + * + * @returns USB_SUCCESS if we want to capture the device, USB_FAILURE otherwise. + * @param pDevDesc The parsed device descriptor (does not include subconfigs). + * @param pDevStrings Device strings: Manufacturer, Product, Serial Number. + * @param pszDevicePath The physical path of the device being attached. + * @param Bus The Bus number on which the device is on. + * @param Port The Port number on the bus. + * @param ppszDrv The name of the driver we wish to capture the device with. + * @param pvReserved Reserved for future use. + */ +int VBoxUSBMonSolarisElectDriver(usb_dev_descr_t *pDevDesc, usb_dev_str_t *pDevStrings, char *pszDevicePath, int Bus, int Port, + char **ppszDrv, void *pvReserved) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: pDevDesc=%p pDevStrings=%p pszDevicePath=%s Bus=%d Port=%d\n", pDevDesc, + pDevStrings, pszDevicePath, Bus, Port)); + + AssertPtrReturn(pDevDesc, USB_FAILURE); + AssertPtrReturn(pDevStrings, USB_FAILURE); + + /* + * Create a filter from the device being attached. + */ + USBFILTER Filter; + USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&Filter, USBFILTERIDX_VENDOR_ID, pDevDesc->idVendor, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_PRODUCT_ID, pDevDesc->idProduct, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_REV, pDevDesc->bcdDevice, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_CLASS, pDevDesc->bDeviceClass, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pDevDesc->bDeviceSubClass, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pDevDesc->bDeviceProtocol, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_BUS, 0x0 /* Bus */, true); /* Use 0x0 as userland initFilterFromDevice function in Main: see comment on "SetMustBePresent" below */ + USBFilterSetNumExact(&Filter, USBFILTERIDX_PORT, Port, true); + USBFilterSetStringExact(&Filter, USBFILTERIDX_MANUFACTURER_STR, pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "", + true /*fMustBePresent*/, true /*fPurge*/); + USBFilterSetStringExact(&Filter, USBFILTERIDX_PRODUCT_STR, pDevStrings->usb_product ? pDevStrings->usb_product : "", + true /*fMustBePresent*/, true /*fPurge*/); + USBFilterSetStringExact(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR, pDevStrings->usb_serialno ? pDevStrings->usb_serialno : "", + true /*fMustBePresent*/, true /*fPurge*/); + + /* This doesn't work like it should (USBFilterMatch fails on matching field (6) i.e. Bus despite this. Investigate later. */ + USBFilterSetMustBePresent(&Filter, USBFILTERIDX_BUS, false /* fMustBePresent */); + + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x " + "bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(&Filter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(&Filter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(&Filter, USBFILTERIDX_BUS), + USBFilterGetNum(&Filter, USBFILTERIDX_PORT))); + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: Manufacturer=%s Product=%s Serial=%s\n", + USBFilterGetString(&Filter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_MANUFACTURER_STR) : "", + USBFilterGetString(&Filter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_PRODUCT_STR) : "", + USBFilterGetString(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR) : "")); + + /* + * Run through user filters and try to see if it has a match. + */ + uintptr_t uId = 0; + RTPROCESS Owner = VBoxUSBFilterMatch(&Filter, &uId); + USBFilterDelete(&Filter); + if (Owner == NIL_RTPROCESS) + { + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: No matching filters, device %#x:%#x uninteresting\n", + pDevDesc->idVendor, pDevDesc->idProduct)); + return USB_FAILURE; + } + + *ppszDrv = ddi_strdup(VBOXUSB_DRIVER_NAME, KM_SLEEP); +#if 0 + LogRel((DEVICE_NAME ": Capturing %s %s %#x:%#x:%s Bus=%d Port=%d\n", + pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "", + pDevStrings->usb_product ? pDevStrings->usb_product : "", + pDevDesc->idVendor, pDevDesc->idProduct, pszDevicePath, Bus, Port)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Capturing %s %s 0x%x:0x%x:%s Bus=%d Port=%d\n", + pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "", + pDevStrings->usb_product ? pDevStrings->usb_product : "", + pDevDesc->idVendor, pDevDesc->idProduct, pszDevicePath, Bus, Port); +#endif + return USB_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h b/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h new file mode 100644 index 00000000..2e61d804 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h @@ -0,0 +1,161 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009-2010 Oracle Corporation. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_USB_USBA_USBAI_PRIVATE_H +#define _SYS_USB_USBA_USBAI_PRIVATE_H + +/* + * Unstable interfaces not part of USBAI but used by Solaris client drivers. + * These interfaces may not be present in future releases and are highly + * unstable. + * + * Status key: + * C = Remove from Sun client drivers before removing from this file + * D = May be needed by legacy (DDK) drivers. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * convenience function for getting default config index + * as saved in usba_device structure + * + * Status: C + */ +uint_t usb_get_current_cfgidx(dev_info_t *); + +/* + * Usb logging, debug and console message handling. + */ +typedef struct usb_log_handle *usb_log_handle_t; + +#define USB_LOG_L0 0 /* warnings, console & syslog buffer */ +#define USB_LOG_L1 1 /* errors, syslog buffer */ +#define USB_LOG_L2 2 /* recoverable errors, debug only */ +#define USB_LOG_L3 3 /* interesting data, debug only */ +#define USB_LOG_L4 4 /* tracing, debug only */ + +#define USB_CHK_BASIC 0 /* Empty mask. Basics always done. */ +#define USB_CHK_SERIAL 0x00000001 /* Compare device serial numbers. */ +#define USB_CHK_CFG 0x00000002 /* Compare raw config clouds. */ +#define USB_CHK_VIDPID 0x00000004 /* Compare product and vendor ID. */ +#define USB_CHK_ALL 0xFFFFFFFF /* Perform maximum checking. */ + +int usb_check_same_device(dev_info_t *dip, + usb_log_handle_t log_handle, + int log_level, + int log_mask, + uint_t check_mask, + char *device_string); + +/* + * ************************************************************************** + * Serialization functions remaining Contracted Consolidation Private + * ************************************************************************** + */ + +/* This whole section: status: C and D. */ + +/* + * opaque serialization handle. + * Used by all usb_serialization routines. + * + * This handle is opaque to the client driver. + */ +typedef struct usb_serialization *usb_serialization_t; + +/* + * usb_init_serialization + * setup for serialization + * + * ARGUMENTS: + * s_dip - devinfo pointer + * flag - USB_INIT_SER_CHECK_SAME_THREAD + * when set, usb_release_access() will verify that the same + * thread releases access. If not, a console warning will + * be issued but access will be released anyways. + * + * RETURNS: + * usb_serialization handle + * + */ +usb_serialization_t usb_init_serialization(dev_info_t *s_dip, + uint_t flag); + +#define USB_INIT_SER_CHECK_SAME_THREAD 1 + +/* fini for serialization */ +void usb_fini_serialization(usb_serialization_t usb_serp); + +/* + * Various ways of calling usb_serialize_access. These correspond to + * their cv_*wait* function counterparts for usb_serialize_access. + */ +#define USB_WAIT 0 +#define USB_WAIT_SIG 1 +#define USB_TIMEDWAIT 2 +#define USB_TIMEDWAIT_SIG 3 + +/* + * usb_serialize_access: + * acquire serialized access + * + * ARGUMENTS: + * usb_serp - usb_serialization handle + * how_to_wait - Which cv_*wait* function to wait for condition. + * USB_WAIT: use cv_wait + * USB_WAIT_SIG: use cv_wait_sig + * USB_TIMEDWAIT: use cv_timedwait + * USB_TIMEDWAIT_SIG: use cv_timedwait_sig + * delta_timeout - Time in ms from current time to timeout. Checked + * only if USB_TIMEDWAIT or USB_TIMEDWAIT_SIG + * specified in how_to_wait. + * RETURNS: + * Same as values returned by cv_*wait* functions, + * except for when how_to_wait == USB_WAIT, where 0 is always returned. + * For calls where a timeout or signal could be expected, use this value + * to tell whether a kill(2) signal or timeout occurred. + */ +int usb_serialize_access(usb_serialization_t usb_serp, + uint_t how_to_wait, + uint_t delta_timeout); + +/* + * usb_release_access: + * release serialized access + * + * ARGUMENTS: + * usb_serp - usb_serialization handle + */ +void usb_release_access(usb_serialization_t usb_serp); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_USB_USBA_USBAI_PRIVATE_H */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf new file mode 100644 index 00000000..17993e73 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf @@ -0,0 +1,37 @@ +# +# Solaris Host USB Client Driver Configuration +# + +# +# 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 . +# +# 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 +# + +name="vboxusb" parent="pseudo"; + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf new file mode 100644 index 00000000..98a3b5e1 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf @@ -0,0 +1,37 @@ +# +# Solaris Host USB Monitor Driver Configuration +# + +# +# 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 . +# +# 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 +# + +name="vboxusbmon" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp b/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp new file mode 100644 index 00000000..123682a9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp @@ -0,0 +1,391 @@ +/** $Id: tstUSBFilter.cpp $ */ +/** @file + * VirtualBox USB filter abstraction - testcase. + */ + +/* + * 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define TESTCASE "tstUSBFilter" + +#define TST_CHECK_RC(expr) do { \ + int rc = expr; \ + if (RT_FAILURE(rc)) \ + { \ + RTPrintf( TESTCASE "(%d): %Rrc - %s\n", __LINE__, rc, #expr); \ + cErrors++; \ + } \ + } while (0) + +#define TST_CHECK_EXPR(expr) do { \ + int rc = (intptr_t)(expr); \ + if (!rc) \ + { \ + RTPrintf( TESTCASE "(%d): %s -> %d\n", __LINE__, #expr, rc); \ + cErrors++; \ + } \ +} while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char g_szString64[64+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345" +}; + +static const char g_szString128[128+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" +}; + +static const char g_szString256[256+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" +}; + + +int main() +{ + unsigned cErrors = 0; + RTR3InitExeNoArguments(0); + + /* + * Basic property setting and simple matching. + */ + USBFILTER Flt1; + USBFilterInit(&Flt1, USBFILTERTYPE_CAPTURE); + /* numbers */ + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_VENDOR_ID, 0x1111, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PRODUCT_ID, 0x2222, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_PROTOCOL, 0xff, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_BUS)); + TST_CHECK_RC(USBFilterSetPresentOnly(&Flt1, USBFILTERIDX_BUS)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, false)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 1, false)); + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_PORT)); + /* strings */ + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "foobar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "foobar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "barbar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "vendor", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, "serial", true, false )); + + /* cloning */ + USBFILTER Dev; + USBFilterClone(&Dev, &Flt1); + + TST_CHECK_EXPR(USBFilterIsIdentical(&Dev, &Flt1)); + TST_CHECK_EXPR(USBFilterMatch(&Dev, &Flt1)); + + USBFilterDelete(&Flt1); + USBFilterDelete(&Dev); + + /* make a sample device */ + USBFilterInit(&Dev, USBFILTERTYPE_CAPTURE); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_VENDOR_ID, 0x1111, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_PRODUCT_ID, 0x2222, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_PROTOCOL, 0xff, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_PORT, 2, true)); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_MANUFACTURER_STR, "vendor", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_SERIAL_NUMBER_STR, "serial", true, false )); + + /* do some basic matching tests */ + USBFilterInit(&Flt1, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(!USBFilterHasAnySubstatialCriteria(&Flt1)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev) /* 100% ignore filter */); + + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 3, true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 2, true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 2, true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "no match", true, false )); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + /* string patterns */ + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*product", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "product*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*uct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*uct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*duct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*x", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*product*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*oduct*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*produc*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?*?*?***??t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?*?*?***??", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*d*t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*x*t", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_PRODUCT_STR)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + /* numeric patterns */ + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0X1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4369", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010421", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111-0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4369-4369", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010421-010421", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1110-0x1112", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4360-4370", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010420-010422", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1112-0x1110", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0x1f", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-19", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-017", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0xffff", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-65535", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-177777", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0XABCD", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0EF-0XABCD", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0X0ef-0Xabcd", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "42|1|0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "42|0x1111|1", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111|42|1", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1112|42|1", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + /* numeric patterns - interval filters */ + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "int:0x0-0xffff", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "int: 0x0 - 0xffff ", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_PRODUCT_ID, "int:0x0028-", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_DEVICE_REV, "int:-0x0045", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_PORT, "int:1,4", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_PORT, "int:( 1, 3 )", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "39-59|0x256-0x101f|0xfffff-0xf000|0x1000-0x2000", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x000256-0x0101f|0xf000-0xfffff|0x000008000-0x2000|39-59", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "| | \t \t\t| 0x256 - 0x101f | 0xf000 - 0xfeff\t| 0x1000 -\t0x6000 | 1- 0512", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "| | \t \t\t| 0x256 - 0x101f | 0xf000 - 0xfeff\t| 0x1112 -\t0x6000 | 1- 0512", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + + USBFilterDelete(&Flt1); + + /* + * string overflow + */ + struct + { + uint64_t u64Pre; + USBFILTER Flt; + uint64_t u64Post; + } sOf; + sOf.u64Pre = sOf.u64Post = UINT64_C(0x1234567887654321); + + USBFilterInit(&sOf.Flt, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + + AssertCompileMemberSize(USBFILTER, achStrTab, 256); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[0], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[1], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[3], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 0 + 1 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, "", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString256[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString256[1], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 0 + 2 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString128[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString128[1], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 3 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString64[0], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString64[0], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[4], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[4], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[3], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + + /* + * Check for a string replacement bug. + */ + USBFILTER Dev2; + USBFilterInit(&Dev2, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_VENDOR_ID, 0x19b6, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_PRODUCT_ID, 0x1024, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_REV, 0x0141, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_CLASS, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_PROTOCOL, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_PORT, 0x1, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_MANUFACTURER_STR, "Generic", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_PRODUCT_STR, "Mass Storage Device", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_MANUFACTURER_STR, "YBU1PPRS", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_VENDOR_ID) == 0x19b6); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_PRODUCT_ID) == 0x1024); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_REV) == 0x0141); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_CLASS) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_SUB_CLASS) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_PROTOCOL) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_PORT) == 1); + + + /* + * Summary. + */ + if (!cErrors) + RTPrintf(TESTCASE ": SUCCESS\n"); + else + RTPrintf(TESTCASE ": FAILURE - %d errors\n", cErrors); + return !!cErrors; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp new file mode 100644 index 00000000..0debc44c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp @@ -0,0 +1,256 @@ +/* $Id: USBInstall.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver installation, Installation 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxUSBMon" +/** Win32 Device name. */ +#define DEVICE_NAME "\\\\.\\VBoxUSBMon" +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxUSBMon" +/** Win32 Symlink name. */ +#define DEVICE_NAME_DOS L"\\DosDevices\\VBoxUSBMon" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +int usblibOsCreateService(void); + + +static DECLCALLBACK(void) vboxUsbLog(VBOXDRVCFG_LOG_SEVERITY_T enmSeverity, char *pszMsg, void *pvContext) +{ + RT_NOREF1(pvContext); + switch (enmSeverity) + { + case VBOXDRVCFG_LOG_SEVERITY_FLOW: + case VBOXDRVCFG_LOG_SEVERITY_REGULAR: + break; + case VBOXDRVCFG_LOG_SEVERITY_REL: + RTMsgInfo("%s", pszMsg); + break; + default: + break; + } +} + +static DECLCALLBACK(void) vboxUsbPanic(void *pvPanic) +{ + RT_NOREF1(pvPanic); +#ifndef DEBUG_bird + AssertFailed(); +#endif +} + + +int __cdecl main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTMsgInfo("USB installation"); + + VBoxDrvCfgLoggerSet(vboxUsbLog, NULL); + VBoxDrvCfgPanicSet(vboxUsbPanic, NULL); + + rc = usblibOsCreateService(); + if (RT_SUCCESS(rc)) + { + /* Build the path to the INF file: */ + char szInfFile[RTPATH_MAX]; + rc = RTProcGetExecutablePath(szInfFile, sizeof(szInfFile)) ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + { + RTPathStripFilename(szInfFile); + rc = RTPathAppend(szInfFile, sizeof(szInfFile), "VBoxUSB.inf"); + } + PRTUTF16 pwszInfFile = NULL; + if (RT_SUCCESS(rc)) + rc = RTStrToUtf16(szInfFile, &pwszInfFile); + if (RT_SUCCESS(rc)) + { + /* Install the INF file: */ + HRESULT hr = VBoxDrvCfgInfInstall(pwszInfFile); + if (hr == S_OK) + RTMsgInfo("Installation successful!"); + else + { + RTMsgError("Installation failed: %Rhrc", hr); + rc = VERR_GENERAL_FAILURE; + } + + RTUtf16Free(pwszInfFile); + } + else + RTMsgError("Failed to construct INF path: %Rrc", rc); + } + else + RTMsgError("Service creation failed: %Rrc", rc); + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Changes the USB driver service to specified driver path. + * + * @returns 0 on success. + * @returns < 0 on failure. + */ +int usblibOsChangeService(const char *pszDriverPath) +{ + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwLastError = GetLastError(); + int rc = RTErrConvertFromWin32(dwLastError); + AssertPtr(pszDriverPath); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed rc=%d\n", dwLastError)); + if (hSMgrCreate) + { + SC_HANDLE hService = OpenService(hSMgrCreate, + SERVICE_NAME, + GENERIC_ALL); + dwLastError = GetLastError(); + if (hService == NULL) + { + AssertMsg(hService, ("OpenService failed! LastError=%Rwa, pszDriver=%s\n", dwLastError, pszDriverPath)); + rc = RTErrConvertFromWin32(dwLastError); + } + else + { + /* We only gonna change the driver image path, the rest remains like it already is */ + if (ChangeServiceConfig(hService, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + pszDriverPath, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL)) + { + RTPrintf("Changed service config to new driver path: %s\n", pszDriverPath); + } + else + { + AssertMsg(hService, ("ChangeServiceConfig failed! LastError=%Rwa, pszDriver=%s\n", dwLastError, pszDriverPath)); + rc = RTErrConvertFromWin32(dwLastError); + } + if (hService != NULL) + CloseServiceHandle(hService); + } + + CloseServiceHandle(hSMgrCreate); + } + return rc; +} + + +/** + * Creates the service. + * + * @returns 0 on success. + * @returns < 0 on failure. + */ +int usblibOsCreateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwLastError = GetLastError(); + int rc = RTErrConvertFromWin32(dwLastError); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed rc=%d\n", dwLastError)); + if (hSMgrCreate) + { + char szDriver[RTPATH_MAX]; + rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxUSBMon.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxUSBMon.sys"); + RTPrintf("Creating USB monitor driver service with path %s ...\n", szDriver); + SC_HANDLE hService = CreateService(hSMgrCreate, + SERVICE_NAME, + "VBox USB Monitor Driver", + SERVICE_QUERY_STATUS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL); + dwLastError = GetLastError(); + if (dwLastError == ERROR_SERVICE_EXISTS) + { + RTPrintf("USB monitor driver service already exists, skipping creation.\n"); + rc = usblibOsChangeService(szDriver); + } + else + { + AssertMsg(hService, ("CreateService failed! LastError=%Rwa, szDriver=%s\n", dwLastError, szDriver)); + rc = RTErrConvertFromWin32(dwLastError); + if (hService != NULL) + CloseServiceHandle(hService); + } + } + CloseServiceHandle(hSMgrCreate); + } + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp new file mode 100644 index 00000000..3dc0d6fe --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp @@ -0,0 +1,215 @@ +/* $Id: USBUninstall.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver uninstallation. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxUSBMon" +/** Win32 Device name. */ +#define DEVICE_NAME "\\\\.\\VBoxUSBMon" +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxUSBMon" +/** Win32 Symlink name. */ +#define DEVICE_NAME_DOS L"\\DosDevices\\VBoxUSBMon" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usblibOsStopService(void); +static int usblibOsDeleteService(void); + + +static DECLCALLBACK(void) vboxUsbLog(VBOXDRVCFG_LOG_SEVERITY_T enmSeverity, char *pszMsg, void *pvContext) +{ + RT_NOREF1(pvContext); + switch (enmSeverity) + { + case VBOXDRVCFG_LOG_SEVERITY_FLOW: + case VBOXDRVCFG_LOG_SEVERITY_REGULAR: + break; + case VBOXDRVCFG_LOG_SEVERITY_REL: + RTMsgInfo("%s", pszMsg); + break; + default: + break; + } +} + +static DECLCALLBACK(void) vboxUsbPanic(void *pvPanic) +{ + RT_NOREF1(pvPanic); +#ifndef DEBUG_bird + AssertFailed(); +#endif +} + + +int __cdecl main(int argc, char **argv) +{ + RTR3InitExeNoArguments(0); + if (argc != 1) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "This utility takes no arguments\n"); + NOREF(argv); + RTMsgInfo("USB uninstallation\n"); + + VBoxDrvCfgLoggerSet(vboxUsbLog, NULL); + VBoxDrvCfgPanicSet(vboxUsbPanic, NULL); + + usblibOsStopService(); + usblibOsDeleteService(); + + HRESULT hr = VBoxDrvCfgInfUninstallAllF(L"USB", L"USB\\VID_80EE&PID_CAFE", SUOI_FORCEDELETE); + if (hr != S_OK) + return RTMsgErrorExitFailure("SetupUninstallOEMInf failed: %Rhrc\n", hr); + + RTMsgInfo("USB uninstallation succeeded!"); + return 0; +} + + +/** + * Stops a possibly running service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +static int usblibOsStopService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", GetLastError())); + 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 = 0; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + /* + * Wait for finish about 1 minute. + * It should be enough for work with driver verifier + */ + int iWait = 600; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + else + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + } + else + AssertMsgFailed(("ControlService failed with LastError=%Rwa. status=%d\n", GetLastError(), Status.dwCurrentState)); + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", GetLastError())); + CloseServiceHandle(hSMgr); + } + return rc; +} + + +/** + * Deletes the service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +static int usblibOsDeleteService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", GetLastError())); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, DELETE); + if (hService) + { + /* + * Delete the service. + */ + if (DeleteService(hService)) + rc = 0; + else + AssertMsgFailed(("DeleteService failed LastError=%Rwa\n", GetLastError())); + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", GetLastError())); + CloseServiceHandle(hSMgr); + } + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk new file mode 100644 index 00000000..4211147b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk @@ -0,0 +1,204 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Windows USB drivers. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +LIBRARIES.win += usbd +SYSMODS.win += VBoxUSB VBoxUSBMon +PROGRAMS.win += USBInstall USBUninstall USBTest +INSTALLS.win += install-infs + +# +# usbd +# +usbd_TEMPLATE = VBoxR0Drv +usbd_SOURCES = usbd/usbd.def + +# +# VBoxUSB +# +VBoxUSB_TEMPLATE = VBoxR0Drv +ifdef VBOX_SIGNING_MODE + VBoxUSB_INSTTYPE = none + VBoxUSB_DEBUG_INSTTYPE = both +endif +VBoxUSB_SDKS = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK_INCS) +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxUSB_DEFS := IN_RT_R0 IN_SUP_R0 VBOX_DBG_LOG_NAME="USBDev" +else + VBoxUSB_DEFS := IN_RT_R0 IN_SUP_R0 VBOX_DBG_LOG_NAME=\"USBDev\" +endif +VBoxUSB_LDFLAGS.x86 = -Entry:DriverEntry@8 +VBoxUSB_LDFLAGS.amd64 = -Entry:DriverEntry +VBoxUSB_SOURCES = \ + dev/VBoxUsbDev.cpp \ + dev/VBoxUsbRt.cpp \ + dev/VBoxUsbPnP.cpp \ + dev/VBoxUsbPwr.cpp \ + cmn/VBoxUsbTool.cpp \ + cmn/VBoxDrvTool.cpp \ + dev/VBoxUsbDev.rc +VBoxUSB_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(usbd_1_TARGET) + +# +# VBoxUSBMon +# +VBoxUSBMon_TEMPLATE = VBoxR0Drv +ifdef VBOX_SIGNING_MODE + VBoxUSBMon_INSTTYPE = none + VBoxUSBMon_DEBUG_INSTTYPE = both +endif +VBoxUSBMon_INCS := $(PATH_SUB_CURRENT)/.. +VBoxUSBMon_SDKS = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK_INCS) +VBoxUSBMon_DEFS = IN_RT_R0 IN_SUP_R0 NTDDI_WINNT=_NTDDI_VISTA VBOXUSBFILTERMGR_USB_SPINLOCK +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxUSBMon_DEFS += VBOX_DBG_LOG_NAME="USBMon" +else + VBoxUSBMon_DEFS += VBOX_DBG_LOG_NAME=\"USBMon\" +endif +VBoxUSBMon_LDFLAGS.x86 = -Entry:DriverEntry@8 +VBoxUSBMon_LDFLAGS.amd64 = -Entry:DriverEntry +ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUSBMon_DEFS += VBOX_USBMON_WITH_FILTER_AUTOAPPLY +endif +VBoxUSBMon_SOURCES = \ + mon/VBoxUsbMon.cpp \ + mon/VBoxUsbFlt.cpp \ + mon/VBoxUsbHook.cpp \ + cmn/VBoxUsbTool.cpp \ + cmn/VBoxDrvTool.cpp \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp \ + mon/VBoxUsbMon.rc +VBoxUSBMon_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(usbd_1_TARGET) +if1of ($(KBUILD_TYPE), debug) + VBoxUSBMon_DEFS += LOG_ENABLED VBOX_USB_WITH_VERBOSE_LOGGING +endif + +# +# Template for USBInstalls and friends. +# +TEMPLATE_VBoxUsbR3 = Template for USBInstalls, USBUninstall and USBTest +TEMPLATE_VBoxUsbR3_EXTENDS = VBoxR3Exe +TEMPLATE_VBoxUsbR3_SDKS = $(TEMPLATE_VBoxR3Exe_SDKS) ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBoxWinNewDevLib +TEMPLATE_VBoxUsbR3_CXXFLAGS = $(TEMPLATE_VBoxR3Exe_CXXFLAGS) -Gz +TEMPLATE_VBoxUsbR3_CFLAGS = $(TEMPLATE_VBoxR3Exe_CFLAGS) -Gz +TEMPLATE_VBoxUsbR3_LIBS = $(TEMPLATE_VBoxR3Exe_LIBS) \ + $(PATH_STAGE_LIB)/VBoxDrvCfgExe$(VBOX_SUFF_LIB) \ + $(LIB_RUNTIME) + +# +# USBInstall +# +USBInstall_TEMPLATE = VBoxUsbR3 +USBInstall_SOURCES = Install/USBInstall.cpp + +# +# USBUninstall +# +USBUninstall_TEMPLATE = VBoxUsbR3 +USBUninstall_SOURCES = Install/USBUninstall.cpp + +# +# USBTest +# +USBTest_TEMPLATE = VBoxUsbR3 +USBTest_DEFS = IN_USBLIB +USBTest_SOURCES = \ + testcase/USBTest.cpp \ + ../USBFilter.cpp + +# +# Install the INF files. +# +install-infs_TEMPLATE = VBoxR0DrvInfCat +install-infs_SOURCES = \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf +install-infs_CLEAN = $(install-infs_SOURCES) +install-infs_BLDDIRS = \ + $(PATH_TARGET)/VBoxUSBCat.dir \ + $(PATH_TARGET)/VBoxUSBMonCat.dir + +$(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf: $(PATH_SUB_CURRENT)/dev/VBoxUSB.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,install-infs,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + +$(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf: $(PATH_SUB_CURRENT)/mon/VBoxUSBMon.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,install-infs,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + +ifdef VBOX_SIGNING_MODE + install-infs_SOURCES += \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat=>VBoxUSB-PreW10.cat \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat=>VBoxUSBMon-PreW10.cat \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys + + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys: $$(VBoxUSB_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat: \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys + $(call MSG_TOOL,Inf2Cat,VBoxUSB-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys: $$(VBoxUSBMon_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat: \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys + $(call MSG_TOOL,Inf2Cat,VBoxUSBMon-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + +endif # signing + +# generate rules +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp new file mode 100644 index 00000000..63ed595b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp @@ -0,0 +1,247 @@ +/* $Id: VBoxDrvTool.cpp $ */ +/** @file + * Windows Driver R0 Tooling. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "VBoxDrvTool.h" + +#include +#include + +#include "../../../win/VBoxDbgLog.h" + +#define VBOXDRVTOOL_MEMTAG 'TDBV' + +static PVOID vboxDrvToolMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXDRVTOOL_MEMTAG); + Assert(pvMem); + return pvMem; +} + +static PVOID vboxDrvToolMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxDrvToolMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +static VOID vboxDrvToolMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXDRVTOOL_MEMTAG); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKeyU(OUT PHANDLE phKey, IN PUNICODE_STRING pName, IN ACCESS_MASK fAccess) +{ + OBJECT_ATTRIBUTES ObjAttr; + + InitializeObjectAttributes(&ObjAttr, pName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + return ZwOpenKey(phKey, fAccess, &ObjAttr); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKey(OUT PHANDLE phKey, IN PWCHAR pName, IN ACCESS_MASK fAccess) +{ + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + + return VBoxDrvToolRegOpenKeyU(phKey, &RtlStr, fAccess); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegCloseKey(IN HANDLE hKey) +{ + return ZwClose(hKey); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegQueryValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT PULONG pDword) +{ + struct + { + KEY_VALUE_PARTIAL_INFORMATION Info; + UCHAR Buf[32]; /* should be enough */ + } Buf; + ULONG cbBuf; + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + NTSTATUS Status = ZwQueryValueKey(hKey, + &RtlStr, + KeyValuePartialInformation, + &Buf.Info, + sizeof(Buf), + &cbBuf); + if (Status == STATUS_SUCCESS) + { + if (Buf.Info.Type == REG_DWORD) + { + Assert(Buf.Info.DataLength == 4); + *pDword = *((PULONG)Buf.Info.Data); + return STATUS_SUCCESS; + } + } + + return STATUS_INVALID_PARAMETER; +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegSetValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT ULONG val) +{ + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + return ZwSetValueKey(hKey, &RtlStr, + NULL, /* IN ULONG TitleIndex OPTIONAL, reserved */ + REG_DWORD, + &val, + sizeof(val)); +} + +static NTSTATUS vboxDrvToolIoCompletionSetEvent(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF2(pDevObj, pIrp); + PKEVENT pEvent = (PKEVENT)pvContext; + KeSetEvent(pEvent, 0, FALSE); + return STATUS_MORE_PROCESSING_REQUIRED; +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostAsync(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent) +{ + IoSetCompletionRoutine(pIrp, vboxDrvToolIoCompletionSetEvent, pEvent, TRUE, TRUE, TRUE); + return IoCallDriver(pDevObj, pIrp); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSync(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + NTSTATUS Status = VBoxDrvToolIoPostAsync(pDevObj, pIrp, &Event); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = pIrp->IoStatus.Status; + } + return Status; +} + +/* !!!NOTE: the caller MUST be the IRP owner!!! * + * !! one can not post threaded IRPs this way!! */ +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSyncWithTimeout(PDEVICE_OBJECT pDevObj, PIRP pIrp, ULONG dwTimeoutMs) +{ + KEVENT Event; + LOG(("post irp (0x%p) to DevObj(0x%p) with timeout (%u)", pIrp, pDevObj, dwTimeoutMs)); + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + NTSTATUS Status = VBoxDrvToolIoPostAsync(pDevObj, pIrp, &Event); + if (Status == STATUS_PENDING) + { + LARGE_INTEGER Interval; + PLARGE_INTEGER pInterval = NULL; + if (dwTimeoutMs != RT_INDEFINITE_WAIT) + { + Interval.QuadPart = -(int64_t) dwTimeoutMs /* ms */ * 10000; + pInterval = &Interval; + } + + Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, pInterval); + if (Status == STATUS_TIMEOUT) + { + WARN(("irp (0x%p) to DevObj(0x%p) was not completed within timeout (%u), cancelling", pIrp, pDevObj, dwTimeoutMs)); + if (!IoCancelIrp(pIrp)) + { + /* this may happen, but this is something the caller with timeout is not expecting */ + WARN(("IoCancelIrp failed")); + } + + /* wait for the IRP to complete */ + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } + else + { + ASSERT_WARN(Status == STATUS_SUCCESS, ("uunexpected Status (0x%x)", Status)); + } + + /* by this time the IRP is completed */ + Status = pIrp->IoStatus.Status; + LOG(("Pending IRP(0x%p) completed with status(0x%x)", pIrp, Status)); + } + else + { + LOG(("IRP(0x%p) completed with status(0x%x)", pIrp, Status)); + } + return Status; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolRefWaitEqual(PVBOXDRVTOOL_REF pRef, uint32_t u32Val) +{ + LARGE_INTEGER Interval; + Interval.QuadPart = -(int64_t) 2 /* ms */ * 10000; + uint32_t cRefs; + size_t loops = 0; + KTIMER kTimer; + NTSTATUS status = STATUS_SUCCESS; + + KeInitializeTimer(&kTimer); + + while ((cRefs = ASMAtomicReadU32(&pRef->cRefs)) > u32Val && loops < 256) + { + Assert(cRefs >= u32Val); + Assert(cRefs < UINT32_MAX/2); + + KeSetTimer(&kTimer, Interval, NULL); + status = KeWaitForSingleObject(&kTimer, Executive, KernelMode, false, NULL); + Assert(NT_SUCCESS(status)); + loops++; + } +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolStrCopy(PUNICODE_STRING pDst, CONST PUNICODE_STRING pSrc) +{ + USHORT cbLength = pSrc->Length + sizeof (pDst->Buffer[0]); + pDst->Buffer = (PWCHAR)vboxDrvToolMemAlloc(cbLength); + Assert(pDst->Buffer); + if (pDst->Buffer) + { + RtlMoveMemory(pDst->Buffer, pSrc->Buffer, pSrc->Length); + pDst->Buffer[pSrc->Length / sizeof (pDst->Buffer[0])] = L'\0'; + pDst->Length = pSrc->Length; + pDst->MaximumLength = cbLength; + return STATUS_SUCCESS; + } + return STATUS_NO_MEMORY; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolStrFree(PUNICODE_STRING pStr) +{ + vboxDrvToolMemFree(pStr->Buffer); +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h new file mode 100644 index 00000000..f953f8eb --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h @@ -0,0 +1,113 @@ +/* $Id: VBoxDrvTool.h $ */ +/** @file + * Windows Driver R0 Tooling. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include +#include +#include + + +RT_C_DECLS_BEGIN + +#if 0 +/* enable this in case we include this in a dll*/ +# ifdef IN_VBOXDRVTOOL +# define VBOXDRVTOOL_DECL(a_Type) DECLEXPORT(a_Type) +# else +# define VBOXDRVTOOL_DECL(a_Type) DECLIMPORT(a_Type) +# endif +#else +/*enable this in case we include this in a static lib*/ +# define VBOXDRVTOOL_DECL(a_Type) a_Type VBOXCALL +#endif + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKeyU(OUT PHANDLE phKey, IN PUNICODE_STRING pName, IN ACCESS_MASK fAccess); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKey(OUT PHANDLE phKey, IN PWCHAR pName, IN ACCESS_MASK fAccess); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegCloseKey(IN HANDLE hKey); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegQueryValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT PULONG pDword); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegSetValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT ULONG val); + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostAsync(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSync(PDEVICE_OBJECT pDevObj, PIRP pIrp); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSyncWithTimeout(PDEVICE_OBJECT pDevObj, PIRP pIrp, ULONG dwTimeoutMs); +DECLINLINE(NTSTATUS) VBoxDrvToolIoComplete(PIRP pIrp, NTSTATUS Status, ULONG ulInfo) +{ + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = ulInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + +typedef struct VBOXDRVTOOL_REF +{ + volatile uint32_t cRefs; +} VBOXDRVTOOL_REF, *PVBOXDRVTOOL_REF; + +DECLINLINE(void) VBoxDrvToolRefInit(PVBOXDRVTOOL_REF pRef) +{ + pRef->cRefs = 1; +} + +DECLINLINE(uint32_t) VBoxDrvToolRefRetain(PVBOXDRVTOOL_REF pRef) +{ + Assert(pRef->cRefs); + Assert(pRef->cRefs < UINT32_MAX / 2); + return ASMAtomicIncU32(&pRef->cRefs); +} + +DECLINLINE(uint32_t) VBoxDrvToolRefRelease(PVBOXDRVTOOL_REF pRef) +{ + uint32_t cRefs = ASMAtomicDecU32(&pRef->cRefs); + Assert(cRefs < UINT32_MAX/2); + return cRefs; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolRefWaitEqual(PVBOXDRVTOOL_REF pRef, uint32_t u32Val); + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolStrCopy(PUNICODE_STRING pDst, CONST PUNICODE_STRING pSrc); +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolStrFree(PUNICODE_STRING pStr); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h new file mode 100644 index 00000000..9bc69afb --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h @@ -0,0 +1,95 @@ +/* $Id: VBoxUsbIdc.h $ */ +/** @file + * Windows USB Proxy - Monitor Driver communication interface. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define VBOXUSBIDC_VERSION_MAJOR 1 +#define VBOXUSBIDC_VERSION_MINOR 0 + +#define VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x618, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x619, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN CTL_CODE(FILE_DEVICE_UNKNOWN, 0x61A, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STATE_CHANGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x61B, METHOD_NEITHER, FILE_WRITE_ACCESS) + +typedef struct +{ + uint32_t u32Major; + uint32_t u32Minor; +} VBOXUSBIDC_VERSION, *PVBOXUSBIDC_VERSION; + +typedef void *HVBOXUSBIDCDEV; + +/* the initial device state is USBDEVICESTATE_HELD_BY_PROXY */ +typedef struct VBOXUSBIDC_PROXY_STARTUP +{ + union + { + /* in: device PDO */ + PDEVICE_OBJECT pPDO; + /* out: device handle to be used for subsequent USBSUP_PROXY_XXX calls */ + HVBOXUSBIDCDEV hDev; + } u; +} VBOXUSBIDC_PROXY_STARTUP, *PVBOXUSBIDC_PROXY_STARTUP; + +typedef struct VBOXUSBIDC_PROXY_TEARDOWN +{ + HVBOXUSBIDCDEV hDev; +} VBOXUSBIDC_PROXY_TEARDOWN, *PVBOXUSBIDC_PROXY_TEARDOWN; + +typedef enum +{ + VBOXUSBIDC_PROXY_STATE_UNKNOWN = 0, + VBOXUSBIDC_PROXY_STATE_IDLE, + VBOXUSBIDC_PROXY_STATE_INITIAL = VBOXUSBIDC_PROXY_STATE_IDLE, + VBOXUSBIDC_PROXY_STATE_USED_BY_GUEST +} VBOXUSBIDC_PROXY_STATE; + +typedef struct VBOXUSBIDC_PROXY_STATE_CHANGE +{ + HVBOXUSBIDCDEV hDev; + VBOXUSBIDC_PROXY_STATE enmState; +} VBOXUSBIDC_PROXY_STATE_CHANGE, *PVBOXUSBIDC_PROXY_STATE_CHANGE; + +NTSTATUS VBoxUsbIdcInit(); +VOID VBoxUsbIdcTerm(); +NTSTATUS VBoxUsbIdcProxyStarted(PDEVICE_OBJECT pPDO, HVBOXUSBIDCDEV *phDev); +NTSTATUS VBoxUsbIdcProxyStopped(HVBOXUSBIDCDEV hDev); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp new file mode 100644 index 00000000..67383ea0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp @@ -0,0 +1,428 @@ +/* $Id: VBoxUsbTool.cpp $ */ +/** @file + * Windows USB R0 Tooling. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#define INITGUID +#include "VBoxUsbTool.h" +#include + +#include +#include +#include +#include +#include + +#include "../../../win/VBoxDbgLog.h" + +#define VBOXUSBTOOL_MEMTAG 'TUBV' + +static PVOID vboxUsbToolMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSBTOOL_MEMTAG); + Assert(pvMem); + return pvMem; +} + +static PVOID vboxUsbToolMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxUsbToolMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +static VOID vboxUsbToolMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSBTOOL_MEMTAG); +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAlloc(USHORT u16Function, USHORT cbSize) +{ + PURB pUrb = (PURB)vboxUsbToolMemAlloc(cbSize); + Assert(pUrb); + if (!pUrb) + return NULL; + + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAllocZ(USHORT u16Function, USHORT cbSize) +{ + PURB pUrb = (PURB)vboxUsbToolMemAllocZ(cbSize); + Assert(pUrb); + if (!pUrb) + return NULL; + + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbReinit(PURB pUrb, USHORT cbSize, USHORT u16Function) +{ + Assert(pUrb->UrbHeader.Length == cbSize); + if (pUrb->UrbHeader.Length < cbSize) + return NULL; + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolUrbFree(PURB pUrb) +{ + vboxUsbToolMemFree(pUrb); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolUrbPost(PDEVICE_OBJECT pDevObj, PURB pUrb, ULONG dwTimeoutMs) +{ + if (dwTimeoutMs == RT_INDEFINITE_WAIT) + return VBoxUsbToolIoInternalCtlSendSync(pDevObj, IOCTL_INTERNAL_USB_SUBMIT_URB, pUrb, NULL); + return VBoxUsbToolIoInternalCtlSendSyncWithTimeout(pDevObj, IOCTL_INTERNAL_USB_SUBMIT_URB, pUrb, NULL, dwTimeoutMs); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDescriptor(PDEVICE_OBJECT pDevObj, void *pvBuffer, int cbBuffer, int Type, int iIndex, int LangId, ULONG dwTimeoutMs) +{ + NTSTATUS Status; + USHORT cbUrb = sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST); + PURB pUrb = VBoxUsbToolUrbAllocZ(URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE, cbUrb); + if (!pUrb) + { + WARN(("allocating URB failed")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + PUSB_COMMON_DESCRIPTOR pCmn = (PUSB_COMMON_DESCRIPTOR)pvBuffer; + pCmn->bLength = cbBuffer; + pCmn->bDescriptorType = Type; + + pUrb->UrbHeader.Function = URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE; + pUrb->UrbHeader.Length = sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST); + pUrb->UrbControlDescriptorRequest.TransferBufferLength = cbBuffer; + pUrb->UrbControlDescriptorRequest.TransferBuffer = pvBuffer; + pUrb->UrbControlDescriptorRequest.Index = (UCHAR)iIndex; + pUrb->UrbControlDescriptorRequest.DescriptorType = (UCHAR)Type; + pUrb->UrbControlDescriptorRequest.LanguageId = (USHORT)LangId; + + Status = VBoxUsbToolUrbPost(pDevObj, pUrb, dwTimeoutMs); + ASSERT_WARN(Status == STATUS_SUCCESS, ("VBoxUsbToolUrbPost failed Status (0x%x)", Status)); + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolStringDescriptorToUnicodeString(PUSB_STRING_DESCRIPTOR pDr, PUNICODE_STRING pUnicode) +{ + /* for some reason the string dr sometimes contains a non-null terminated string + * although we zeroed up the complete descriptor buffer + * this is why RtlInitUnicodeString won't work + * we need to init the scting length based on dr length */ + pUnicode->Buffer = pDr->bString; + pUnicode->Length = pUnicode->MaximumLength = pDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetStringDescriptor(PDEVICE_OBJECT pDevObj, char *pszResult, ULONG cbResult, + int iIndex, int LangId, ULONG dwTimeoutMs) +{ + char aBuf[MAXIMUM_USB_STRING_LENGTH]; + AssertCompile(sizeof (aBuf) <= UINT8_MAX); + UCHAR cbBuf = (UCHAR)sizeof (aBuf); + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)&aBuf; + + Assert(pszResult); + *pszResult = 0; + + memset(pDr, 0, cbBuf); + pDr->bLength = cbBuf; + pDr->bDescriptorType = USB_STRING_DESCRIPTOR_TYPE; + + NTSTATUS Status = VBoxUsbToolGetDescriptor(pDevObj, pDr, cbBuf, USB_STRING_DESCRIPTOR_TYPE, iIndex, LangId, dwTimeoutMs); + if (NT_SUCCESS(Status)) + { + if (pDr->bLength >= sizeof (USB_STRING_DESCRIPTOR)) + { + int rc = RTUtf16ToUtf8Ex(pDr->bString, (pDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString)) / sizeof(RTUTF16), + &pszResult, cbResult, NULL /*pcch*/); + if (RT_SUCCESS(rc)) + { + USBLibPurgeEncoding(pszResult); + Status = STATUS_SUCCESS; + } + else + Status = STATUS_UNSUCCESSFUL; + } + else + Status = STATUS_INVALID_PARAMETER; + } + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetLangID(PDEVICE_OBJECT pDevObj, int *pLangId, ULONG dwTimeoutMs) +{ + char aBuf[MAXIMUM_USB_STRING_LENGTH]; + AssertCompile(sizeof (aBuf) <= UINT8_MAX); + UCHAR cbBuf = (UCHAR)sizeof (aBuf); + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)&aBuf; + + Assert(pLangId); + *pLangId = 0; + + memset(pDr, 0, cbBuf); + pDr->bLength = cbBuf; + pDr->bDescriptorType = USB_STRING_DESCRIPTOR_TYPE; + + NTSTATUS Status = VBoxUsbToolGetDescriptor(pDevObj, pDr, cbBuf, USB_STRING_DESCRIPTOR_TYPE, 0, 0, dwTimeoutMs); + if (NT_SUCCESS(Status)) + { + /* Just grab the first lang ID if available. In 99% cases, it will be US English (0x0409).*/ + if (pDr->bLength >= sizeof (USB_STRING_DESCRIPTOR)) + { + AssertCompile(sizeof (pDr->bString[0]) == sizeof (uint16_t)); + *pLangId = pDr->bString[0]; + Status = STATUS_SUCCESS; + } + else + { + Status = STATUS_INVALID_PARAMETER; + } + } + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDeviceSpeed(PDEVICE_OBJECT pDevObj, BOOLEAN *pbIsHigh) +{ + Assert(pbIsHigh); + *pbIsHigh = FALSE; + + PIRP pIrp = IoAllocateIrp(pDevObj->StackSize, FALSE); + Assert(pIrp); + if (!pIrp) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + USB_BUS_INTERFACE_USBDI_V1 BusIf; + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_PNP; + pSl->MinorFunction = IRP_MN_QUERY_INTERFACE; + pSl->Parameters.QueryInterface.InterfaceType = &USB_BUS_INTERFACE_USBDI_GUID; + pSl->Parameters.QueryInterface.Size = sizeof (BusIf); + pSl->Parameters.QueryInterface.Version = USB_BUSIF_USBDI_VERSION_1; + pSl->Parameters.QueryInterface.Interface = (PINTERFACE)&BusIf; + pSl->Parameters.QueryInterface.InterfaceSpecificData = NULL; + + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevObj, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + if (NT_SUCCESS(Status)) + { + *pbIsHigh = BusIf.IsDeviceHighSpeed(BusIf.BusContext); + BusIf.InterfaceDereference(BusIf.BusContext); + } + IoFreeIrp(pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolPipeClear(PDEVICE_OBJECT pDevObj, HANDLE hPipe, bool fReset) +{ + if (!hPipe) + { + Log(("Resetting the control pipe??\n")); + return STATUS_SUCCESS; + } + USHORT u16Function = fReset ? URB_FUNCTION_RESET_PIPE : URB_FUNCTION_ABORT_PIPE; + PURB pUrb = VBoxUsbToolUrbAlloc(u16Function, sizeof (struct _URB_PIPE_REQUEST)); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed!\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + pUrb->UrbPipeRequest.PipeHandle = hPipe; + pUrb->UrbPipeRequest.Reserved = 0; + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, pUrb, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status) || !USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + AssertMsgFailed((__FUNCTION__": vboxUsbToolRequest failed with %x (%x)\n", Status, pUrb->UrbHeader.Status)); + } + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolCurrentFrame(PDEVICE_OBJECT pDevObj, PIRP pIrp, PULONG piFrame) +{ + struct _URB_GET_CURRENT_FRAME_NUMBER Urb; + Urb.Hdr.Function = URB_FUNCTION_GET_CURRENT_FRAME_NUMBER; + Urb.Hdr.Length = sizeof(Urb); + Urb.FrameNumber = (ULONG)-1; + + Assert(piFrame); + *piFrame = (ULONG)-1; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; + pSl->Parameters.Others.Argument1 = (PVOID)&Urb; + pSl->Parameters.Others.Argument2 = NULL; + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, (PURB)&Urb, RT_INDEFINITE_WAIT); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + { + *piFrame = Urb.FrameNumber; + } + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolDevUnconfigure(PDEVICE_OBJECT pDevObj) +{ + USHORT cbUrb = sizeof (struct _URB_SELECT_CONFIGURATION); + PURB pUrb = VBoxUsbToolUrbAlloc(URB_FUNCTION_SELECT_CONFIGURATION, cbUrb); + Assert(pUrb); + if (!pUrb) + return STATUS_INSUFFICIENT_RESOURCES; + + UsbBuildSelectConfigurationRequest(pUrb, (USHORT)cbUrb, NULL); + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, pUrb, RT_INDEFINITE_WAIT); + Assert(NT_SUCCESS(Status)); + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(PIRP) VBoxUsbToolIoBuildAsyncInternalCtl(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2) +{ + PIRP pIrp = IoAllocateIrp(pDevObj->StackSize, FALSE); + Assert(pIrp); + if (!pIrp) + { + return NULL; + } + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = NULL; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->MinorFunction = 0; + pSl->Parameters.DeviceIoControl.IoControlCode = uCtl; + pSl->Parameters.Others.Argument1 = pvArg1; + pSl->Parameters.Others.Argument2 = pvArg2; + return pIrp; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSyncWithTimeout(PDEVICE_OBJECT pDevObj, ULONG uCtl, + void *pvArg1, void *pvArg2, ULONG dwTimeoutMs) +{ + /* since we're going to cancel the irp on timeout, we should allocate our own IRP rather than using the threaded one + * */ + PIRP pIrp = VBoxUsbToolIoBuildAsyncInternalCtl(pDevObj, uCtl, pvArg1, pvArg2); + if (!pIrp) + { + WARN(("VBoxUsbToolIoBuildAsyncInternalCtl failed")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + NTSTATUS Status = VBoxDrvToolIoPostSyncWithTimeout(pDevObj, pIrp, dwTimeoutMs); + + IoFreeIrp(pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendAsync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, + PKEVENT pEvent, PIO_STATUS_BLOCK pIoStatus) +{ + NTSTATUS Status; + PIRP pIrp; + PIO_STACK_LOCATION pSl; + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + pIrp = IoBuildDeviceIoControlRequest(uCtl, pDevObj, NULL, 0, NULL, 0, TRUE, pEvent, pIoStatus); + if (!pIrp) + { + WARN(("IoBuildDeviceIoControlRequest failed!!\n")); + pIoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + pIoStatus->Information = 0; + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Get the next stack location as that is used for the new irp */ + pSl = IoGetNextIrpStackLocation(pIrp); + pSl->Parameters.Others.Argument1 = pvArg1; + pSl->Parameters.Others.Argument2 = pvArg2; + + Status = IoCallDriver(pDevObj, pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2) +{ + IO_STATUS_BLOCK IoStatus = {0}; + KEVENT Event; + NTSTATUS Status; + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + LOG(("Sending sync Ctl pDevObj(0x%p), uCtl(0x%x), pvArg1(0x%p), pvArg2(0x%p)", pDevObj, uCtl, pvArg1, pvArg2)); + + Status = VBoxUsbToolIoInternalCtlSendAsync(pDevObj, uCtl, pvArg1, pvArg2, &Event, &IoStatus); + + if (Status == STATUS_PENDING) + { + LOG(("VBoxUsbToolIoInternalCtlSendAsync returned pending for pDevObj(0x%p)", pDevObj)); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatus.Status; + LOG(("Pending VBoxUsbToolIoInternalCtlSendAsync completed with Status (0x%x) for pDevObj(0x%p)", Status, pDevObj)); + } + else + { + LOG(("VBoxUsbToolIoInternalCtlSendAsync completed with Status (0x%x) for pDevObj(0x%p)", Status, pDevObj)); + } + + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h new file mode 100644 index 00000000..e6c6c236 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h @@ -0,0 +1,89 @@ +/* $Id: VBoxUsbTool.h $ */ +/** @file + * Windows USB R0 Tooling. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxDrvTool.h" + +RT_C_DECLS_BEGIN +#pragma warning( disable : 4200 ) +#include +#pragma warning( default : 4200 ) +#include + +RT_C_DECLS_END + +#if 0 +/* enable this in case we include this in a dll*/ +# ifdef IN_VBOXUSBTOOL +# define VBOXUSBTOOL_DECL(a_Type) DECLEXPORT(a_Type) +# else +# define VBOXUSBTOOL_DECL(a_Type) DECLIMPORT(a_Type) +# endif +#else +/*enable this in case we include this in a static lib*/ +# define VBOXUSBTOOL_DECL(a_Type) a_Type VBOXCALL +#endif + + +RT_C_DECLS_BEGIN + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAlloc(USHORT u16Function, USHORT cbSize); +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAllocZ(USHORT u16Function, USHORT cbSize); +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbReinit(PURB pUrb, USHORT cbSize, USHORT u16Function); +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolUrbFree(PURB pUrb); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolUrbPost(PDEVICE_OBJECT pDevObj, PURB pUrb, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDescriptor(PDEVICE_OBJECT pDevObj, void *pvBuffer, int cbBuffer, int Type, int iIndex, int LangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetStringDescriptor(PDEVICE_OBJECT pDevObj, char *pszResult, ULONG cbResult, int iIndex, int LangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetLangID(PDEVICE_OBJECT pDevObj, int *pLangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDeviceSpeed(PDEVICE_OBJECT pDevObj, BOOLEAN *pbIsHigh); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolPipeClear(PDEVICE_OBJECT pDevObj, HANDLE hPipe, bool fReset); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolCurrentFrame(PDEVICE_OBJECT pDevObj, PIRP pIrp, PULONG piFrame); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolDevUnconfigure(PDEVICE_OBJECT pDevObj); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendAsync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, PKEVENT pEvent, PIO_STATUS_BLOCK pIoStatus); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2); +VBOXUSBTOOL_DECL(PIRP) VBoxUsbToolIoBuildAsyncInternalCtl(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSyncWithTimeout(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolStringDescriptorToUnicodeString(PUSB_STRING_DESCRIPTOR pDr, PUNICODE_STRING pUnicode); + + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf new file mode 100644 index 00000000..9b580b98 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf @@ -0,0 +1,88 @@ +; $Id: VBoxUSB.inf $ +;; @file +; VBox host drivers - USB drivers - Win32 USB device +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[Version] +Signature="$Windows NT$" +Class=USB +ClassGUID={36FC9E60-C465-11CF-8056-444553540000} +provider=%ORACLE% +;edit-DriverVer=08/26/2008,2.00.0000 +;cat CatalogFile=VBoxUSB.cat + +[SourceDisksNames] +1=%Disk_Description%,,, + +[SourceDisksFiles] +VBoxUSB.sys = 1 + +[Manufacturer] +%ORACLE%=VBoxUSB@COMMA-NT-ARCH@ + +[VBoxUSB@DOT-NT-ARCH@] +%USB\VID_80EE&PID_CAFE.DeviceDesc%=VBoxUSB.Dev, USB\VID_80EE&PID_CAFE + +[DestinationDirs] +VBoxUSB.Files.Ext = 10,System32\Drivers + +[VBoxUSB.Dev.NT] +CopyFiles=VBoxUSB.Files.Ext + +[VBoxUSB.Dev.NT.Services] +Addservice = VBoxUSB, 0x00000002, VBoxUSB.AddService + +[VBoxUSB.AddService] +DisplayName = %VBoxUSB.SvcDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %10%\System32\Drivers\VBoxUSB.sys +AddReg = VBoxUSB.AddReg +LoadOrderGroup = Base + +[VBoxUSB.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,VBoxUSB.sys + +[VBoxUSB.Files.Ext] +VBoxUSB.sys + +;---------------------------------------------------------------; + +[Strings] +ORACLE="Oracle Corporation" +Disk_Description="VBoxUSB Installation Disk" +USB\VID_80EE&PID_CAFE.DeviceDesc="VirtualBox USB" +VBoxUSB.SvcDesc="VirtualBox USB" diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h new file mode 100644 index 00000000..57eeeafa --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h @@ -0,0 +1,103 @@ +/* $Id: VBoxUsbCmn.h $ */ +/** @file + * VBoxUsmCmn.h - USB device. Common defs + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "../cmn/VBoxDrvTool.h" +#include "../cmn/VBoxUsbTool.h" + +#include +#include + +#include + +#define VBOXUSB_CFG_IDLE_TIME_MS 5000 + +typedef struct VBOXUSBDEV_EXT *PVBOXUSBDEV_EXT; + +RT_C_DECLS_BEGIN + +#ifdef _WIN64 +#define DECLSPEC_USBIMPORT DECLSPEC_IMPORT +#else +#define DECLSPEC_USBIMPORT + +#define USBD_ParseDescriptors _USBD_ParseDescriptors +#define USBD_ParseConfigurationDescriptorEx _USBD_ParseConfigurationDescriptorEx +#define USBD_CreateConfigurationRequestEx _USBD_CreateConfigurationRequestEx +#endif + +DECLSPEC_USBIMPORT PUSB_COMMON_DESCRIPTOR +USBD_ParseDescriptors( + IN PVOID DescriptorBuffer, + IN ULONG TotalLength, + IN PVOID StartPosition, + IN LONG DescriptorType + ); + +DECLSPEC_USBIMPORT PUSB_INTERFACE_DESCRIPTOR +USBD_ParseConfigurationDescriptorEx( + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, + IN PVOID StartPosition, + IN LONG InterfaceNumber, + IN LONG AlternateSetting, + IN LONG InterfaceClass, + IN LONG InterfaceSubClass, + IN LONG InterfaceProtocol + ); + +DECLSPEC_USBIMPORT PURB +USBD_CreateConfigurationRequestEx( + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, + IN PUSBD_INTERFACE_LIST_ENTRY InterfaceList + ); + +RT_C_DECLS_END + +DECLHIDDEN(PVOID) vboxUsbMemAlloc(SIZE_T cbBytes); +DECLHIDDEN(PVOID) vboxUsbMemAllocZ(SIZE_T cbBytes); +DECLHIDDEN(VOID) vboxUsbMemFree(PVOID pvMem); + +#include "VBoxUsbRt.h" +#include "VBoxUsbPnP.h" +#include "VBoxUsbPwr.h" +#include "VBoxUsbDev.h" + + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp new file mode 100644 index 00000000..680f5d2f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp @@ -0,0 +1,357 @@ +/* $Id: VBoxUsbDev.cpp $ */ +/** @file + * VBoxUsbDev.cpp - USB device. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbCmn.h" +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXUSB_MEMTAG 'bUBV' + + + +DECLHIDDEN(PVOID) vboxUsbMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSB_MEMTAG); + Assert(pvMem); + return pvMem; +} + +DECLHIDDEN(PVOID) vboxUsbMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxUsbMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +DECLHIDDEN(VOID) vboxUsbMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSB_MEMTAG); +} + +VBOXUSB_GLOBALS g_VBoxUsbGlobals = {0}; + +static NTSTATUS vboxUsbDdiAddDevice(PDRIVER_OBJECT pDriverObject, + PDEVICE_OBJECT pPDO) +{ + PDEVICE_OBJECT pFDO = NULL; + NTSTATUS Status = IoCreateDevice(pDriverObject, + sizeof (VBOXUSBDEV_EXT), + NULL, /* IN PUNICODE_STRING pDeviceName OPTIONAL */ + FILE_DEVICE_UNKNOWN, /* IN DEVICE_TYPE DeviceType */ + FILE_AUTOGENERATED_DEVICE_NAME, /* IN ULONG DeviceCharacteristics */ + FALSE, /* IN BOOLEAN fExclusive */ + &pFDO); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pFDO->DeviceExtension; + /* init Device Object bits */ + pFDO->Flags |= DO_DIRECT_IO; + if (pPDO->Flags & DO_POWER_PAGABLE) + pFDO->Flags |= DO_POWER_PAGABLE; + + + /* now init our state bits */ + + pDevExt->cHandles = 0; + + pDevExt->pFDO = pFDO; + pDevExt->pPDO = pPDO; + pDevExt->pLowerDO = IoAttachDeviceToDeviceStack(pFDO, pPDO); + Assert(pDevExt->pLowerDO); + if (pDevExt->pLowerDO) + { + vboxUsbDdiStateInit(pDevExt); + Status = vboxUsbRtInit(pDevExt); + if (Status == STATUS_SUCCESS) + { + /* we're done! */ + pFDO->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; + } + + IoDetachDevice(pDevExt->pLowerDO); + } + else + Status = STATUS_NO_SUCH_DEVICE; + + IoDeleteDevice(pFDO); + } + + return Status; +} + +static VOID vboxUsbDdiUnload(PDRIVER_OBJECT pDriverObject) +{ + RT_NOREF1(pDriverObject); + LogRel(("VBoxUsb::DriverUnload. Built Date (%s) Time (%s)\n", __DATE__, __TIME__)); + VBoxDrvToolStrFree(&g_VBoxUsbGlobals.RegPath); + + vboxUsbRtGlobalsTerm(); + + PRTLOGGER pLogger = RTLogRelSetDefaultInstance(NULL); + if (pLogger) + { + RTLogDestroy(pLogger); + } + pLogger = RTLogSetDefaultInstance(NULL); + if (pLogger) + { + RTLogDestroy(pLogger); + } +} + +static NTSTATUS vboxUsbDispatchCreate(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + NTSTATUS Status = STATUS_INVALID_HANDLE; + do + { + if (vboxUsbPnPStateGet(pDevExt) != ENMVBOXUSB_PNPSTATE_STARTED) + { + Status = STATUS_INVALID_DEVICE_STATE; + break; + } + + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + if (!pFObj) + { + Status = STATUS_INVALID_PARAMETER; + break; + } + + pFObj->FsContext = NULL; + + if (pFObj->FileName.Length) + { + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtCreate(pDevExt, pIrp); + if (!NT_SUCCESS(Status)) + { + AssertFailed(); + break; + } + + ASMAtomicIncU32(&pDevExt->cHandles); + Status = STATUS_SUCCESS; + break; + } while (0); + + Status = VBoxDrvToolIoComplete(pIrp, Status, 0); + return Status; +} + +static NTSTATUS vboxUsbDispatchClose(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + NTSTATUS Status = STATUS_SUCCESS; +#ifdef VBOX_STRICT + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + Assert(pFObj); + Assert(!pFObj->FileName.Length); +#endif + Status = vboxUsbRtClose(pDevExt, pIrp); + if (NT_SUCCESS(Status)) + { + ASMAtomicDecU32(&pDevExt->cHandles); + } + else + { + AssertFailed(); + } + Status = VBoxDrvToolIoComplete(pIrp, Status, 0); + return Status; +} + +static NTSTATUS vboxUsbDispatchDeviceControl(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (vboxUsbDdiStateRetainIfStarted(pDevExt)) + return vboxUsbRtDispatch(pDevExt, pIrp); + return VBoxDrvToolIoComplete(pIrp, STATUS_INVALID_DEVICE_STATE, 0); +} + +static NTSTATUS vboxUsbDispatchCleanup(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + RT_NOREF1(pDeviceObject); + return VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); +} + +static NTSTATUS vboxUsbDevAccessDeviedDispatchStub(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + { + VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + return STATUS_DELETE_PENDING; + } + + NTSTATUS Status = VBoxDrvToolIoComplete(pIrp, STATUS_ACCESS_DENIED, 0); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbDispatchSystemControl(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + { + VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + return STATUS_DELETE_PENDING; + } + + IoSkipCurrentIrpStackLocation(pIrp); + + NTSTATUS Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbDispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ +#ifdef DEBUG_misha + AssertFailed(); +#endif + return vboxUsbDevAccessDeviedDispatchStub(pDeviceObject, pIrp); +} + +static NTSTATUS vboxUsbDispatchWrite(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ +#ifdef DEBUG_misha + AssertFailed(); +#endif + return vboxUsbDevAccessDeviedDispatchStub(pDeviceObject, pIrp); +} + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) +{ + LogRel(("VBoxUsb::DriverEntry. Built Date (%s) Time (%s)\n", __DATE__, __TIME__)); + + NTSTATUS Status = vboxUsbRtGlobalsInit(); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Status = VBoxDrvToolStrCopy(&g_VBoxUsbGlobals.RegPath, pRegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + g_VBoxUsbGlobals.pDrvObj = pDriverObject; + + pDriverObject->DriverExtension->AddDevice = vboxUsbDdiAddDevice; + + pDriverObject->DriverUnload = vboxUsbDdiUnload; + + pDriverObject->MajorFunction[IRP_MJ_CREATE] = vboxUsbDispatchCreate; + pDriverObject->MajorFunction[IRP_MJ_CLOSE] = vboxUsbDispatchClose; + pDriverObject->MajorFunction[IRP_MJ_READ] = vboxUsbDispatchRead; + pDriverObject->MajorFunction[IRP_MJ_WRITE] = vboxUsbDispatchWrite; + pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = vboxUsbDispatchDeviceControl; + pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = vboxUsbDispatchCleanup; + pDriverObject->MajorFunction[IRP_MJ_POWER] = vboxUsbDispatchPower; + pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = vboxUsbDispatchSystemControl; + pDriverObject->MajorFunction[IRP_MJ_PNP] = vboxUsbDispatchPnP; + + return STATUS_SUCCESS; + } + vboxUsbRtGlobalsTerm(); + } + + LogRel(("VBoxUsb::DriverEntry. failed with Status (0x%x)\n", Status)); + + return Status; +} + +#ifdef VBOX_STRICT +DECLHIDDEN(VOID) vboxUsbPnPStateGbgChange(ENMVBOXUSB_PNPSTATE enmOldState, ENMVBOXUSB_PNPSTATE enmNewState) +{ + /* *ensure the state change is valid */ + switch (enmNewState) + { + case ENMVBOXUSB_PNPSTATE_STARTED: + Assert( enmOldState == ENMVBOXUSB_PNPSTATE_START_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_STOPPED + || enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING); + break; + case ENMVBOXUSB_PNPSTATE_STOP_PENDING: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_STOPPED: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING); + break; + case ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_REMOVE_PENDING: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_REMOVED: + Assert( enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED); + break; + default: + AssertFailed(); + break; + } + +} +#endif diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h new file mode 100644 index 00000000..7dd19d86 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h @@ -0,0 +1,218 @@ +/* $Id: VBoxUsbDev.h $ */ +/** @file + * VBoxUsbDev.h - USB device. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbCmn.h" +#include +#include + +typedef struct VBOXUSB_GLOBALS +{ + PDRIVER_OBJECT pDrvObj; + UNICODE_STRING RegPath; + VBOXUSBRT_IDC RtIdc; +} VBOXUSB_GLOBALS, *PVBOXUSB_GLOBALS; + +extern VBOXUSB_GLOBALS g_VBoxUsbGlobals; + +/* pnp state decls */ +typedef enum +{ + ENMVBOXUSB_PNPSTATE_UNKNOWN = 0, + ENMVBOXUSB_PNPSTATE_START_PENDING, + ENMVBOXUSB_PNPSTATE_STARTED, + ENMVBOXUSB_PNPSTATE_STOP_PENDING, + ENMVBOXUSB_PNPSTATE_STOPPED, + ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED, + ENMVBOXUSB_PNPSTATE_REMOVE_PENDING, + ENMVBOXUSB_PNPSTATE_REMOVED, + ENMVBOXUSB_PNPSTATE_FORSEDWORD = 0x8fffffff +} ENMVBOXUSB_PNPSTATE; +AssertCompile(sizeof (ENMVBOXUSB_PNPSTATE) == sizeof (uint32_t)); + +#ifdef VBOX_STRICT +DECLHIDDEN(VOID) vboxUsbPnPStateGbgChange(ENMVBOXUSB_PNPSTATE enmOld, ENMVBOXUSB_PNPSTATE enmNew); +# define VBOXUSB_PNP_GBG_STATE_CHANGE(_old, _new) vboxUsbPnPStateGbgChange((_old), (_new)) +#else +# define VBOXUSB_PNP_GBG_STATE_CHANGE(_old, _new) do { } while (0) +#endif + + +typedef struct VBOXUSB_PNPSTATE +{ + /* Current state */ + volatile ENMVBOXUSB_PNPSTATE Curr; + /* Previous state, used to restore state info on cancell stop device */ + ENMVBOXUSB_PNPSTATE Prev; +} VBOXUSB_PNPSTATE, *PVBOXUSB_PNPSTATE; + +typedef struct VBOXUSBDEV_DDISTATE +{ + /* Lock */ + KSPIN_LOCK Lock; + VBOXDRVTOOL_REF Ref; + VBOXUSB_PNPSTATE PnPState; + VBOXUSB_PWRSTATE PwrState; + /* current dev caps */ + DEVICE_CAPABILITIES DevCaps; +} VBOXUSBDEV_DDISTATE, *PVBOXUSBDEV_DDISTATE; + +typedef struct VBOXUSBDEV_EXT +{ + PDEVICE_OBJECT pFDO; + PDEVICE_OBJECT pPDO; + PDEVICE_OBJECT pLowerDO; + + VBOXUSBDEV_DDISTATE DdiState; + + uint32_t cHandles; + + VBOXUSB_RT Rt; + +} VBOXUSBDEV_EXT, *PVBOXUSBDEV_EXT; + +/* pnp state api */ +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateGet(PVBOXUSBDEV_EXT pDevExt) +{ + return (ENMVBOXUSB_PNPSTATE)ASMAtomicUoReadU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr); +} + +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateSet(PVBOXUSBDEV_EXT pDevExt, ENMVBOXUSB_PNPSTATE enmState) +{ + KIRQL Irql; + ENMVBOXUSB_PNPSTATE enmOldState; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &Irql); + pDevExt->DdiState.PnPState.Prev = (ENMVBOXUSB_PNPSTATE)ASMAtomicUoReadU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr); + ASMAtomicWriteU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr, (uint32_t)enmState); + pDevExt->DdiState.PnPState.Curr = enmState; + enmOldState = pDevExt->DdiState.PnPState.Prev; + KeReleaseSpinLock(&pDevExt->DdiState.Lock, Irql); + VBOXUSB_PNP_GBG_STATE_CHANGE(enmOldState, enmState); + return enmState; +} + +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateRestore(PVBOXUSBDEV_EXT pDevExt) +{ + ENMVBOXUSB_PNPSTATE enmNewState, enmOldState; + KIRQL Irql; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &Irql); + enmOldState = pDevExt->DdiState.PnPState.Curr; + enmNewState = pDevExt->DdiState.PnPState.Prev; + ASMAtomicWriteU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr, (uint32_t)pDevExt->DdiState.PnPState.Prev); + KeReleaseSpinLock(&pDevExt->DdiState.Lock, Irql); + VBOXUSB_PNP_GBG_STATE_CHANGE(enmOldState, enmNewState); + Assert(enmNewState == ENMVBOXUSB_PNPSTATE_STARTED); + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING); + return enmNewState; +} + +DECLINLINE(VOID) vboxUsbPnPStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + pDevExt->DdiState.PnPState.Curr = pDevExt->DdiState.PnPState.Prev = ENMVBOXUSB_PNPSTATE_START_PENDING; +} + +DECLINLINE(VOID) vboxUsbDdiStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + KeInitializeSpinLock(&pDevExt->DdiState.Lock); + VBoxDrvToolRefInit(&pDevExt->DdiState.Ref); + vboxUsbPwrStateInit(pDevExt); + vboxUsbPnPStateInit(pDevExt); +} + +DECLINLINE(bool) vboxUsbDdiStateRetainIfStarted(PVBOXUSBDEV_EXT pDevExt) +{ + KIRQL oldIrql; + bool bRetained = true; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &oldIrql); + if (vboxUsbPnPStateGet(pDevExt) == ENMVBOXUSB_PNPSTATE_STARTED) + { + VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); + } + else + { + bRetained = false; + } + KeReleaseSpinLock(&pDevExt->DdiState.Lock, oldIrql); + return bRetained; +} + +/* if device is removed - does nothing and returns zero, + * otherwise increments a ref counter and returns the current pnp state + * NOTE: never returns ENMVBOXUSB_PNPSTATE_REMOVED + * */ +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbDdiStateRetainIfNotRemoved(PVBOXUSBDEV_EXT pDevExt) +{ + KIRQL oldIrql; + ENMVBOXUSB_PNPSTATE enmState; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &oldIrql); + enmState = vboxUsbPnPStateGet(pDevExt); + if (enmState != ENMVBOXUSB_PNPSTATE_REMOVED) + { + VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); + } + KeReleaseSpinLock(&pDevExt->DdiState.Lock, oldIrql); + return enmState != ENMVBOXUSB_PNPSTATE_REMOVED ? enmState : (ENMVBOXUSB_PNPSTATE)0; +} + +DECLINLINE(uint32_t) vboxUsbDdiStateRetain(PVBOXUSBDEV_EXT pDevExt) +{ + return VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); +} + +DECLINLINE(uint32_t) vboxUsbDdiStateRelease(PVBOXUSBDEV_EXT pDevExt) +{ + return VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); +} + +DECLINLINE(VOID) vboxUsbDdiStateReleaseAndWaitCompleted(PVBOXUSBDEV_EXT pDevExt) +{ + VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); + VBoxDrvToolRefWaitEqual(&pDevExt->DdiState.Ref, 1); +} + +DECLINLINE(VOID) vboxUsbDdiStateReleaseAndWaitRemoved(PVBOXUSBDEV_EXT pDevExt) +{ + VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); + VBoxDrvToolRefWaitEqual(&pDevExt->DdiState.Ref, 0); +} + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc new file mode 100644 index 00000000..83ccd753 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc @@ -0,0 +1,70 @@ +/* $Id: VBoxUsbDev.rc $ */ +/** @file + * VBoxUSB - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +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 USB Driver\0" + VALUE "InternalName", "VBoxUSB\0" + VALUE "OriginalFilename", "VBoxUSB.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/VBoxUSB/win/dev/VBoxUsbPnP.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp new file mode 100644 index 00000000..18a068c8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp @@ -0,0 +1,274 @@ +/* $Id: VBoxUsbPnP.cpp $ */ +/** @file + * USB PnP Handling + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "VBoxUsbCmn.h" + +static NTSTATUS vboxUsbPnPMnStartDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + IoCopyCurrentIrpStackLocationToNext(pIrp); + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbRtStart(pDevExt); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STARTED); + } + } + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STOP_PENDING); + + vboxUsbDdiStateReleaseAndWaitCompleted(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + return IoCallDriver(pDevExt->pLowerDO, pIrp); +} + +static NTSTATUS vboxUsbPnPMnStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STOPPED); + + vboxUsbRtClear(pDevExt); + + NTSTATUS Status = VBoxUsbToolDevUnconfigure(pDevExt->pLowerDO); + Assert(NT_SUCCESS(Status)); + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPnPMnCancelStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + + IoCopyCurrentIrpStackLocationToNext(pIrp); + Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + if (NT_SUCCESS(Status) && enmState == ENMVBOXUSB_PNPSTATE_STOP_PENDING) + { + vboxUsbPnPStateRestore(pDevExt); + } + + Status = STATUS_SUCCESS; + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_REMOVE_PENDING); + + vboxUsbDdiStateReleaseAndWaitCompleted(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + return IoCallDriver(pDevExt->pLowerDO, pIrp); +} + +static NTSTATUS vboxUsbPnPRmDev(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = vboxUsbRtRm(pDevExt); + Assert(Status == STATUS_SUCCESS); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + if (enmState != ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED) + { + Status = vboxUsbPnPRmDev(pDevExt); + Assert(Status == STATUS_SUCCESS); + } + + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_REMOVED); + + vboxUsbDdiStateRelease(pDevExt); + + vboxUsbDdiStateReleaseAndWaitRemoved(pDevExt); + + vboxUsbRtClear(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + IoDetachDevice(pDevExt->pLowerDO); + IoDeleteDevice(pDevExt->pFDO); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnCancelRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + IoCopyCurrentIrpStackLocationToNext(pIrp); + + Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + + if (NT_SUCCESS(Status) && + enmState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING) + { + vboxUsbPnPStateRestore(pDevExt); + } + + Status = STATUS_SUCCESS; + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnSurpriseRemoval(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED); + + NTSTATUS Status = vboxUsbPnPRmDev(pDevExt); + Assert(Status == STATUS_SUCCESS); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryCapabilities(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PDEVICE_CAPABILITIES pDevCaps = pSl->Parameters.DeviceCapabilities.Capabilities; + + if (pDevCaps->Version < 1 || pDevCaps->Size < sizeof (*pDevCaps)) + { + AssertFailed(); + /** @todo return more appropriate status ?? */ + return STATUS_UNSUCCESSFUL; + } + + pDevCaps->SurpriseRemovalOK = TRUE; + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoCopyCurrentIrpStackLocationToNext(pIrp); + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + { + pDevCaps->SurpriseRemovalOK = 1; + pDevExt->DdiState.DevCaps = *pDevCaps; + } + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnDefault(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + NTSTATUS Status; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPnP(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + return VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_START_DEVICE: + return vboxUsbPnPMnStartDevice(pDevExt, pIrp); + + case IRP_MN_QUERY_STOP_DEVICE: + return vboxUsbPnPMnQueryStopDevice(pDevExt, pIrp); + + case IRP_MN_STOP_DEVICE: + return vboxUsbPnPMnStopDevice(pDevExt, pIrp); + + case IRP_MN_CANCEL_STOP_DEVICE: + return vboxUsbPnPMnCancelStopDevice(pDevExt, pIrp); + + case IRP_MN_QUERY_REMOVE_DEVICE: + return vboxUsbPnPMnQueryRemoveDevice(pDevExt, pIrp); + + case IRP_MN_REMOVE_DEVICE: + return vboxUsbPnPMnRemoveDevice(pDevExt, pIrp); + + case IRP_MN_CANCEL_REMOVE_DEVICE: + return vboxUsbPnPMnCancelRemoveDevice(pDevExt, pIrp); + + case IRP_MN_SURPRISE_REMOVAL: + return vboxUsbPnPMnSurpriseRemoval(pDevExt, pIrp); + + case IRP_MN_QUERY_CAPABILITIES: + return vboxUsbPnPMnQueryCapabilities(pDevExt, pIrp); + + default: + return vboxUsbPnPMnDefault(pDevExt, pIrp); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h new file mode 100644 index 00000000..c1799080 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h @@ -0,0 +1,46 @@ +/* $Id: VBoxUsbPnP.h $ */ +/** @file + * USB PnP Handling + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +#include "VBoxUsbCmn.h" + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPnP(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp new file mode 100644 index 00000000..35b57159 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp @@ -0,0 +1,427 @@ +/* $Id: VBoxUsbPwr.cpp $ */ +/** @file + * USB Power state Handling + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include "VBoxUsbCmn.h" + +#include + +DECLHIDDEN(VOID) vboxUsbPwrStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + POWER_STATE PowerState; + PowerState.SystemState = PowerSystemWorking; + PowerState.DeviceState = PowerDeviceD0; + PoSetPowerState(pDevExt->pFDO, DevicePowerState, PowerState); + pDevExt->DdiState.PwrState.PowerState = PowerState; + pDevExt->DdiState.PwrState.PowerDownLevel = PowerDeviceUnspecified; +} + +static NTSTATUS vboxUsbPwrMnDefault(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + NTSTATUS Status; + PoStartNextPowerIrp(pIrp); + IoSkipCurrentIrpStackLocation(pIrp); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPwrMnPowerSequence(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); +} + +typedef struct VBOXUSB_PWRDEV_CTX +{ + PVBOXUSBDEV_EXT pDevExt; + PIRP pIrp; +} VBOXUSB_PWRDEV_CTX, *PVBOXUSB_PWRDEV_CTX; + +static VOID vboxUsbPwrIoDeviceCompletion(IN PDEVICE_OBJECT pDeviceObject, + IN UCHAR MinorFunction, + IN POWER_STATE PowerState, + IN PVOID pvContext, + IN PIO_STATUS_BLOCK pIoStatus) +{ + RT_NOREF3(pDeviceObject, MinorFunction, PowerState); + PVBOXUSB_PWRDEV_CTX pDevCtx = (PVBOXUSB_PWRDEV_CTX)pvContext; + PVBOXUSBDEV_EXT pDevExt = pDevCtx->pDevExt; + PIRP pIrp = pDevCtx->pIrp; + pIrp->IoStatus.Status = pIoStatus->Status; + pIrp->IoStatus.Information = 0; + + PoStartNextPowerIrp(pIrp); + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + vboxUsbDdiStateRelease(pDevExt); + + vboxUsbMemFree(pDevCtx); +} + +static NTSTATUS vboxUsbPwrIoRequestDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + POWER_STATE PwrState; + PwrState.SystemState = pSl->Parameters.Power.State.SystemState; + PwrState.DeviceState = pDevExt->DdiState.DevCaps.DeviceState[PwrState.SystemState]; + + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PVBOXUSB_PWRDEV_CTX pDevCtx = (PVBOXUSB_PWRDEV_CTX)vboxUsbMemAlloc(sizeof (*pDevCtx)); + Assert(pDevCtx); + if (pDevCtx) + { + pDevCtx->pDevExt = pDevExt; + pDevCtx->pIrp = pIrp; + + Status = PoRequestPowerIrp(pDevExt->pPDO, pSl->MinorFunction, PwrState, + vboxUsbPwrIoDeviceCompletion, pDevCtx, NULL); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return STATUS_MORE_PROCESSING_REQUIRED; + + vboxUsbMemFree(pDevCtx); + } + + PoStartNextPowerIrp(pIrp); + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + vboxUsbDdiStateRelease(pDevExt); + + /* the "real" Status is stored in pIrp->IoStatus.Status, + * return success here to complete the Io */ + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostSysCompletion(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF1(pDevObj); + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pvContext; + NTSTATUS Status = pIrp->IoStatus.Status; + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_SET_POWER: + pDevExt->DdiState.PwrState.PowerState.SystemState = pSl->Parameters.Power.State.SystemState; + break; + + default: + break; + } + + return vboxUsbPwrIoRequestDev(pDevExt, pIrp); + } + + PoStartNextPowerIrp(pIrp); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + IoMarkIrpPending(pIrp); + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostSysCompletion, pDevExt, TRUE, TRUE, TRUE); + NTSTATUS Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); NOREF(Status); + return STATUS_PENDING; +} + +static NTSTATUS vboxUsbPwrQueryPowerSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + /*PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + SYSTEM_POWER_STATE enmSysPState = pSl->Parameters.Power.State.SystemState;*/ + + return vboxUsbPwrIoPostSys(pDevExt, pIrp); +} + +static NTSTATUS vboxUsbPwrIoPostDevCompletion(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF1(pDevObj); + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pvContext; + + if (pIrp->PendingReturned) + IoMarkIrpPending(pIrp); + + NTSTATUS Status = pIrp->IoStatus.Status; + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_SET_POWER: + pDevExt->DdiState.PwrState.PowerState.DeviceState = pSl->Parameters.Power.State.DeviceState; + PoSetPowerState(pDevExt->pFDO, DevicePowerState, pSl->Parameters.Power.State); + break; + + default: + break; + } + } + + PoStartNextPowerIrp(pIrp); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + IoMarkIrpPending(pIrp); + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostDevCompletion, pDevExt, TRUE, TRUE, TRUE); + NTSTATUS Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); RT_NOREF_PV(Status); + return STATUS_PENDING; +} + +typedef struct VBOXUSB_IOASYNC_CTX +{ + PIO_WORKITEM pWrkItem; + PIRP pIrp; +} VBOXUSB_IOASYNC_CTX, *PVBOXUSB_IOASYNC_CTX; + +static VOID vboxUsbPwrIoWaitCompletionAndPostAsyncWorker(IN PDEVICE_OBJECT pDeviceObject, IN PVOID pvContext) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + PVBOXUSB_IOASYNC_CTX pCtx = (PVBOXUSB_IOASYNC_CTX)pvContext; + PIRP pIrp = pCtx->pIrp; + + vboxUsbPwrIoPostDev(pDevExt, pIrp); + + IoFreeWorkItem(pCtx->pWrkItem); + vboxUsbMemFree(pCtx); +} + +static NTSTATUS vboxUsbPwrIoWaitCompletionAndPostAsync(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PVBOXUSB_IOASYNC_CTX pCtx = (PVBOXUSB_IOASYNC_CTX)vboxUsbMemAlloc(sizeof (*pCtx)); + Assert(pCtx); + if (pCtx) + { + PIO_WORKITEM pWrkItem = IoAllocateWorkItem(pDevExt->pFDO); + Assert(pWrkItem); + if (pWrkItem) + { + pCtx->pWrkItem = pWrkItem; + pCtx->pIrp = pIrp; + IoMarkIrpPending(pIrp); + IoQueueWorkItem(pWrkItem, vboxUsbPwrIoWaitCompletionAndPostAsyncWorker, DelayedWorkQueue, pCtx); + return STATUS_PENDING; + } + vboxUsbMemFree(pCtx); + } + return Status; +} + +static NTSTATUS vboxUsbPwrQueryPowerDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + DEVICE_POWER_STATE enmDevPState = pSl->Parameters.Power.State.DeviceState; + NTSTATUS Status = STATUS_SUCCESS; + + if (enmDevPState >= pDevExt->DdiState.PwrState.PowerState.DeviceState) + { + Status = vboxUsbPwrIoWaitCompletionAndPostAsync(pDevExt, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return Status; + } + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + + PoStartNextPowerIrp(pIrp); + + if (NT_SUCCESS(Status)) + { + IoSkipCurrentIrpStackLocation(pIrp); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + } + else + { + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPwrMnQueryPower(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.Power.Type) + { + case SystemPowerState: + return vboxUsbPwrQueryPowerSys(pDevExt, pIrp); + + case DevicePowerState: + return vboxUsbPwrQueryPowerDev(pDevExt, pIrp); + + default: + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + + } +} + +static NTSTATUS vboxUsbPwrSetPowerSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + /*PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + SYSTEM_POWER_STATE enmSysPState = pSl->Parameters.Power.State.SystemState;*/ + + return vboxUsbPwrIoPostSys(pDevExt, pIrp); +} + +static NTSTATUS vboxUsbPwrSetPowerDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + DEVICE_POWER_STATE enmDevPState = pSl->Parameters.Power.State.DeviceState; + DEVICE_POWER_STATE enmCurDevPState = pDevExt->DdiState.PwrState.PowerState.DeviceState; + NTSTATUS Status = STATUS_SUCCESS; + + if (enmDevPState > enmCurDevPState && enmCurDevPState == PowerDeviceD0) + { + Status = vboxUsbPwrIoWaitCompletionAndPostAsync(pDevExt, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return Status; + } + + PoStartNextPowerIrp(pIrp); + + if (NT_SUCCESS(Status)) + { + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostDevCompletion, pDevExt, TRUE, TRUE, TRUE); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + } + else + { + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + vboxUsbDdiStateRelease(pDevExt); + } + + return Status; +} + + +static NTSTATUS vboxUsbPwrMnSetPower(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.Power.Type) + { + case SystemPowerState: + return vboxUsbPwrSetPowerSys(pDevExt, pIrp); + + case DevicePowerState: + return vboxUsbPwrSetPowerDev(pDevExt, pIrp); + + default: + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + } +} + +static NTSTATUS vboxUsbPwrMnWaitWake(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); +} + + +static NTSTATUS vboxUsbPwrDispatch(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + + switch (pSl->MinorFunction) + { + case IRP_MN_POWER_SEQUENCE: + return vboxUsbPwrMnPowerSequence(pDevExt, pIrp); + + case IRP_MN_QUERY_POWER: + return vboxUsbPwrMnQueryPower(pDevExt, pIrp); + + case IRP_MN_SET_POWER: + return vboxUsbPwrMnSetPower(pDevExt, pIrp); + + case IRP_MN_WAIT_WAKE: + return vboxUsbPwrMnWaitWake(pDevExt, pIrp); + + default: +// AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + } +} + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPower(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + ENMVBOXUSB_PNPSTATE enmState = vboxUsbDdiStateRetainIfNotRemoved(pDevExt); + switch (enmState) + { + case ENMVBOXUSB_PNPSTATE_REMOVED: + PoStartNextPowerIrp(pIrp); + + pIrp->IoStatus.Status = STATUS_DELETE_PENDING; + pIrp->IoStatus.Information = 0; + + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + vboxUsbDdiStateRelease(pDevExt); + + return STATUS_DELETE_PENDING; + + case ENMVBOXUSB_PNPSTATE_START_PENDING: + PoStartNextPowerIrp(pIrp); + IoSkipCurrentIrpStackLocation(pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return PoCallDriver(pDevExt->pLowerDO, pIrp); + + default: + return vboxUsbPwrDispatch(pDevExt, pIrp); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h new file mode 100644 index 00000000..792187ed --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h @@ -0,0 +1,51 @@ +/* $Id: VBoxUsbPwr.h $ */ +/** @file + * USB Power state Handling + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +typedef struct VBOXUSB_PWRSTATE +{ + POWER_STATE PowerState; + ULONG PowerDownLevel; +} VBOXUSB_PWRSTATE, *PVBOXUSB_PWRSTATE; + +DECLHIDDEN(VOID) vboxUsbPwrStateInit(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPower(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp new file mode 100644 index 00000000..f4e82a33 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp @@ -0,0 +1,1593 @@ +/* $Id: VBoxUsbRt.cpp $ */ +/** @file + * VBox USB R0 runtime + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbCmn.h" +#include "../cmn/VBoxUsbIdc.h" +#include "../cmn/VBoxUsbTool.h" + +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define _USBD_ /** @todo r=bird: What is this?? */ + +#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008 + +#define VBOXUSB_MAGIC 0xABCF1423 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXUSB_URB_CONTEXT +{ + PURB pUrb; + PMDL pMdlBuf; + PVBOXUSBDEV_EXT pDevExt; + PVOID pOut; + ULONG ulTransferType; + ULONG ulMagic; +} VBOXUSB_URB_CONTEXT, * PVBOXUSB_URB_CONTEXT; + +typedef struct VBOXUSB_SETUP +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VBOXUSB_SETUP, *PVBOXUSB_SETUP; + + + +static bool vboxUsbRtCtxSetOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + bool fRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, pFObj, NULL); + if (fRc) + LogFunc(("pDevExt (0x%x) Owner(0x%x) acquired\n", pFObj)); + else + LogFunc(("pDevExt (0x%x) Owner(0x%x) FAILED!!\n", pFObj)); + return fRc; +} + +static bool vboxUsbRtCtxReleaseOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + bool fRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, NULL, pFObj); + if (fRc) + LogFunc(("pDevExt (0x%x) Owner(0x%x) released\n", pFObj)); + else + LogFunc(("pDevExt (0x%x) Owner(0x%x) release: is NOT an owner\n", pFObj)); + return fRc; +} + +static bool vboxUsbRtCtxIsOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + PFILE_OBJECT pOwner = (PFILE_OBJECT)ASMAtomicReadPtr((void *volatile *)(&pDevExt->Rt.pOwner)); + return pOwner == pFObj; +} + +static NTSTATUS vboxUsbRtIdcSubmit(ULONG uCtl, void *pvBuffer) +{ + /* we just reuse the standard usb tooling for simplicity here */ + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(g_VBoxUsbGlobals.RtIdc.pDevice, uCtl, pvBuffer, NULL); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +static NTSTATUS vboxUsbRtIdcInit() +{ + UNICODE_STRING UniName; + RtlInitUnicodeString(&UniName, USBMON_DEVICE_NAME_NT); + NTSTATUS Status = IoGetDeviceObjectPointer(&UniName, FILE_ALL_ACCESS, &g_VBoxUsbGlobals.RtIdc.pFile, &g_VBoxUsbGlobals.RtIdc.pDevice); + if (NT_SUCCESS(Status)) + { + VBOXUSBIDC_VERSION Version; + vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION, &Version); + if (NT_SUCCESS(Status)) + { + if ( Version.u32Major == VBOXUSBIDC_VERSION_MAJOR +#if VBOXUSBIDC_VERSION_MINOR != 0 + && Version.u32Minor >= VBOXUSBIDC_VERSION_MINOR +#endif + ) + return STATUS_SUCCESS; + AssertFailed(); + } + else + { + AssertFailed(); + } + + /* this will as well dereference the dev obj */ + ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile); + } + else + { + AssertFailed(); + } + + memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc)); + return Status; +} + +static VOID vboxUsbRtIdcTerm() +{ + Assert(g_VBoxUsbGlobals.RtIdc.pFile); + Assert(g_VBoxUsbGlobals.RtIdc.pDevice); + ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile); + memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc)); +} + +static NTSTATUS vboxUsbRtIdcReportDevStart(PDEVICE_OBJECT pPDO, HVBOXUSBIDCDEV *phDev) +{ + VBOXUSBIDC_PROXY_STARTUP Start; + Start.u.pPDO = pPDO; + + *phDev = NULL; + + NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP, &Start); + Assert(Status == STATUS_SUCCESS); + if (!NT_SUCCESS(Status)) + return Status; + + *phDev = Start.u.hDev; + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtIdcReportDevStop(HVBOXUSBIDCDEV hDev) +{ + VBOXUSBIDC_PROXY_TEARDOWN Stop; + Stop.hDev = hDev; + + NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN, &Stop); + Assert(Status == STATUS_SUCCESS); + return Status; +} + + +DECLHIDDEN(NTSTATUS) vboxUsbRtGlobalsInit() +{ + return vboxUsbRtIdcInit(); +} + +DECLHIDDEN(VOID) vboxUsbRtGlobalsTerm() +{ + vboxUsbRtIdcTerm(); +} + + +DECLHIDDEN(NTSTATUS) vboxUsbRtInit(PVBOXUSBDEV_EXT pDevExt) +{ + RtlZeroMemory(&pDevExt->Rt, sizeof (pDevExt->Rt)); + NTSTATUS Status = IoRegisterDeviceInterface(pDevExt->pPDO, &GUID_CLASS_VBOXUSB, + NULL, /* IN PUNICODE_STRING ReferenceString OPTIONAL */ + &pDevExt->Rt.IfName); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbRtIdcReportDevStart(pDevExt->pPDO, &pDevExt->Rt.hMonDev); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + Assert(pDevExt->Rt.hMonDev); + return STATUS_SUCCESS; + } + + NTSTATUS tmpStatus = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE); + Assert(tmpStatus == STATUS_SUCCESS); + if (NT_SUCCESS(tmpStatus)) + { + RtlFreeUnicodeString(&pDevExt->Rt.IfName); + } + } + return Status; +} + +/** + * Free cached USB device/configuration descriptors + * + * @param pDevExt USB DevExt pointer + */ +static void vboxUsbRtFreeCachedDescriptors(PVBOXUSBDEV_EXT pDevExt) +{ + if (pDevExt->Rt.devdescr) + { + vboxUsbMemFree(pDevExt->Rt.devdescr); + pDevExt->Rt.devdescr = NULL; + } + for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i) + { + if (pDevExt->Rt.cfgdescr[i]) + { + vboxUsbMemFree(pDevExt->Rt.cfgdescr[i]); + pDevExt->Rt.cfgdescr[i] = NULL; + } + } +} + +/** + * Free per-device interface info + * + * @param pDevExt USB DevExt pointer + * @param fAbortPipes If true, also abort any open pipes + */ +static void vboxUsbRtFreeInterfaces(PVBOXUSBDEV_EXT pDevExt, BOOLEAN fAbortPipes) +{ + unsigned i; + unsigned j; + + /* + * Free old interface info + */ + if (pDevExt->Rt.pVBIfaceInfo) + { + for (i=0;iRt.uNumInterfaces;i++) + { + if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo) + { + if (fAbortPipes) + { + for (j=0; jRt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + Log(("Aborting Pipe %d handle %x address %x\n", j, + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle, + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress)); + VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle, FALSE); + } + } + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo); + } + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = NULL; + if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo); + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL; + } + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo); + pDevExt->Rt.pVBIfaceInfo = NULL; + } +} + +DECLHIDDEN(VOID) vboxUsbRtClear(PVBOXUSBDEV_EXT pDevExt) +{ + vboxUsbRtFreeCachedDescriptors(pDevExt); + vboxUsbRtFreeInterfaces(pDevExt, FALSE); +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtRm(PVBOXUSBDEV_EXT pDevExt) +{ + if (!pDevExt->Rt.IfName.Buffer) + return STATUS_SUCCESS; + + NTSTATUS Status = vboxUsbRtIdcReportDevStop(pDevExt->Rt.hMonDev); + Assert(Status == STATUS_SUCCESS); + Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + RtlFreeUnicodeString(&pDevExt->Rt.IfName); + pDevExt->Rt.IfName.Buffer = NULL; + } + return Status; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtStart(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, TRUE); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +static NTSTATUS vboxUsbRtCacheDescriptors(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; +// uint32_t uTotalLength; +// unsigned i; + + /* Read device descriptor */ + Assert(!pDevExt->Rt.devdescr); + pDevExt->Rt.devdescr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_DEVICE_DESCRIPTOR)); + if (pDevExt->Rt.devdescr) + { + memset(pDevExt->Rt.devdescr, 0, sizeof (USB_DEVICE_DESCRIPTOR)); + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.devdescr, sizeof (USB_DEVICE_DESCRIPTOR), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + Assert(pDevExt->Rt.devdescr->bNumConfigurations > 0); + PUSB_CONFIGURATION_DESCRIPTOR pDr = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_CONFIGURATION_DESCRIPTOR)); + Assert(pDr); + if (pDr) + { + UCHAR i = 0; + for (; i < pDevExt->Rt.devdescr->bNumConfigurations; ++i) + { + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof (USB_CONFIGURATION_DESCRIPTOR), USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status)) + { + break; + } + + USHORT uTotalLength = pDr->wTotalLength; + pDevExt->Rt.cfgdescr[i] = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(uTotalLength); + if (!pDevExt->Rt.cfgdescr[i]) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.cfgdescr[i], uTotalLength, USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status)) + { + break; + } + } + + vboxUsbMemFree(pDr); + + if (NT_SUCCESS(Status)) + return Status; + + /* recources will be freed in vboxUsbRtFreeCachedDescriptors below */ + } + } + + vboxUsbRtFreeCachedDescriptors(pDevExt); + } + + /* shoud be only on fail here */ + Assert(!NT_SUCCESS(Status)); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchClaimDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLAIMDEV pDev = (PUSBSUP_CLAIMDEV)pIrp->AssociatedIrp.SystemBuffer; + ULONG cbOut = 0; + NTSTATUS Status = STATUS_SUCCESS; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if ( !pDev + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pDev) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pDev)) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxSetOwner(pDevExt, pFObj)) + { + AssertFailed(); + pDev->fClaimed = false; + cbOut = sizeof (*pDev); + break; + } + + vboxUsbRtFreeCachedDescriptors(pDevExt); + Status = vboxUsbRtCacheDescriptors(pDevExt); + if (NT_SUCCESS(Status)) + { + pDev->fClaimed = true; + cbOut = sizeof (*pDev); + } + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, cbOut); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchReleaseDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + NTSTATUS Status= STATUS_SUCCESS; + + if (vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + vboxUsbRtFreeCachedDescriptors(pDevExt); + bool fRc = vboxUsbRtCtxReleaseOwner(pDevExt, pFObj); + Assert(fRc); NOREF(fRc); + } + else + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + } + + VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtGetDeviceDescription(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PUSB_DEVICE_DESCRIPTOR pDr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAllocZ(sizeof (USB_DEVICE_DESCRIPTOR)); + if (pDr) + { + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof(*pDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + pDevExt->Rt.idVendor = pDr->idVendor; + pDevExt->Rt.idProduct = pDr->idProduct; + pDevExt->Rt.bcdDevice = pDr->bcdDevice; + pDevExt->Rt.szSerial[0] = 0; + + if (pDr->iSerialNumber +#ifdef DEBUG + || pDr->iProduct || pDr->iManufacturer +#endif + ) + { + int langId; + Status = VBoxUsbToolGetLangID(pDevExt->pLowerDO, &langId, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + Status = VBoxUsbToolGetStringDescriptor(pDevExt->pLowerDO, pDevExt->Rt.szSerial, sizeof (pDevExt->Rt.szSerial), + pDr->iSerialNumber, langId, RT_INDEFINITE_WAIT); + } + else + { + Status = STATUS_SUCCESS; + } + } + } + vboxUsbMemFree(pDr); + } + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchGetDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PUSBSUP_GETDEV pDev = (PUSBSUP_GETDEV)pIrp->AssociatedIrp.SystemBuffer; + ULONG cbOut = 0; + + /* don't check for owner since this request is allowed for non-owners as well */ + NTSTATUS Status; + if ( pDev + && pSl->Parameters.DeviceIoControl.InputBufferLength == sizeof(*pDev) + && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof(*pDev)) + { + /* Even if we don't return it, we need to query the HS flag for later use. */ + Status = VBoxUsbToolGetDeviceSpeed(pDevExt->pLowerDO, &pDevExt->Rt.fIsHighSpeed); + if (NT_SUCCESS(Status)) + { + pDev->hDevice = pDevExt->Rt.hMonDev; + cbOut = sizeof (*pDev); + } + } + else + Status = STATUS_INVALID_PARAMETER; + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, cbOut); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbReset(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + NTSTATUS rcNt; + if (pFObj) + { + if (vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + if ( pIrp->AssociatedIrp.SystemBuffer == NULL + && pSl->Parameters.DeviceIoControl.InputBufferLength == 0 + && pSl->Parameters.DeviceIoControl.OutputBufferLength == 0) + { + rcNt = VBoxUsbToolIoInternalCtlSendSync(pDevExt->pLowerDO, IOCTL_INTERNAL_USB_RESET_PORT, NULL, NULL); + Assert(NT_SUCCESS(rcNt)); + } + else + { + AssertFailed(); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + AssertFailed(); + rcNt = STATUS_ACCESS_DENIED; + } + } + else + { + AssertFailed(); + rcNt = STATUS_INVALID_PARAMETER; + } + + Assert(rcNt != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, rcNt, 0); + vboxUsbDdiStateRelease(pDevExt); + return rcNt; +} + +static PUSB_CONFIGURATION_DESCRIPTOR vboxUsbRtFindConfigDesc(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration) +{ + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + + for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i) + { + if (pDevExt->Rt.cfgdescr[i]) + { + if (pDevExt->Rt.cfgdescr[i]->bConfigurationValue == uConfiguration) + { + pCfgDr = pDevExt->Rt.cfgdescr[i]; + break; + } + } + } + + return pCfgDr; +} + +static NTSTATUS vboxUsbRtSetConfig(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration) +{ + PURB pUrb = NULL; + NTSTATUS Status = STATUS_SUCCESS; + uint32_t i; + + if (!uConfiguration) + { + pUrb = VBoxUsbToolUrbAllocZ(URB_FUNCTION_SELECT_CONFIGURATION, sizeof (struct _URB_SELECT_CONFIGURATION)); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + vboxUsbRtFreeInterfaces(pDevExt, TRUE); + + pUrb->UrbSelectConfiguration.ConfigurationDescriptor = NULL; + + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle; + pDevExt->Rt.uConfigValue = uConfiguration; + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + + VBoxUsbToolUrbFree(pUrb); + + return Status; + } + +/** @todo r=bird: Need to write a script for fixing these kind of clueless use + * of AssertMsgFailed (into AssertMsgReturn). The __FUNCTION__ is just + * the topping it off - the assertion message includes function, file and + * line number. Duh! */ + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, uConfiguration); + if (!pCfgDr) + { + AssertMsgFailed((__FUNCTION__": VBoxUSBFindConfigDesc did not find cfg (%d)\n", uConfiguration)); + return STATUS_INVALID_PARAMETER; + } + + PUSBD_INTERFACE_LIST_ENTRY pIfLe = (PUSBD_INTERFACE_LIST_ENTRY)vboxUsbMemAllocZ((pCfgDr->bNumInterfaces + 1) * sizeof(USBD_INTERFACE_LIST_ENTRY)); + if (!pIfLe) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ for pIfLe failed\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + for (i = 0; i < pCfgDr->bNumInterfaces; i++) + { + pIfLe[i].InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, i, 0, -1, -1, -1); + if (!pIfLe[i].InterfaceDescriptor) + { + AssertMsgFailed((__FUNCTION__": interface %d not found\n", i)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + pIfLe[pCfgDr->bNumInterfaces].InterfaceDescriptor = NULL; + + if (NT_SUCCESS(Status)) + { + pUrb = USBD_CreateConfigurationRequestEx(pCfgDr, pIfLe); + if (pUrb) + { + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + vboxUsbRtFreeInterfaces(pDevExt, FALSE); + + pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle; + pDevExt->Rt.uConfigValue = uConfiguration; + pDevExt->Rt.uNumInterfaces = pCfgDr->bNumInterfaces; + + pDevExt->Rt.pVBIfaceInfo = (VBOXUSB_IFACE_INFO*)vboxUsbMemAllocZ(pDevExt->Rt.uNumInterfaces * sizeof (VBOXUSB_IFACE_INFO)); + if (pDevExt->Rt.pVBIfaceInfo) + { + Assert(NT_SUCCESS(Status)); + for (i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + size_t uTotalIfaceInfoLength = GET_USBD_INTERFACE_SIZE(pIfLe[i].Interface->NumberOfPipes); + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength); + if (!pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + if (pIfLe[i].Interface->NumberOfPipes > 0) + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = (VBOXUSB_PIPE_INFO *)vboxUsbMemAlloc(pIfLe[i].Interface->NumberOfPipes * sizeof(VBOXUSB_PIPE_INFO)); + if (!pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_NO_MEMORY; + break; + } + } + else + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL; + } + + RtlCopyMemory(pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo, pIfLe[i].Interface, uTotalIfaceInfoLength); + + for (ULONG j = 0; j < pIfLe[i].Interface->NumberOfPipes; j++) + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress = pIfLe[i].Interface->Pipes[j].EndpointAddress; + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].NextScheduledFrame = 0; + } + } + +// if (NT_SUCCESS(Status)) +// { +// +// } + } + else + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ failed\n")); + Status = STATUS_NO_MEMORY; + } + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + ExFreePool(pUrb); + } + else + { + AssertMsgFailed((__FUNCTION__": USBD_CreateConfigurationRequestEx failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + vboxUsbMemFree(pIfLe); + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbSetConfig(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_SET_CONFIG pCfg = (PUSBSUP_SET_CONFIG)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status = STATUS_SUCCESS; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCfg + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCfg) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtSetConfig(pDevExt, pCfg->bConfigurationValue); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtSetInterface(PVBOXUSBDEV_EXT pDevExt, uint32_t InterfaceNumber, int AlternateSetting) +{ + AssertMsgReturn(pDevExt->Rt.uConfigValue, ("Can't select an interface without an active configuration\n"), + STATUS_INVALID_PARAMETER); + AssertMsgReturn(InterfaceNumber < pDevExt->Rt.uNumInterfaces, ("InterfaceNumber %d too high!!\n", InterfaceNumber), + STATUS_INVALID_PARAMETER); + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, pDevExt->Rt.uConfigValue); + AssertMsgReturn(pCfgDr, ("configuration %d not found!!\n", pDevExt->Rt.uConfigValue), + STATUS_INVALID_PARAMETER); + PUSB_INTERFACE_DESCRIPTOR pIfDr = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, InterfaceNumber, AlternateSetting, -1, -1, -1); + AssertMsgReturn(pIfDr, ("invalid interface %d or alternate setting %d\n", InterfaceNumber, AlternateSetting), + STATUS_UNSUCCESSFUL); + + USHORT uUrbSize = GET_SELECT_INTERFACE_REQUEST_SIZE(pIfDr->bNumEndpoints); + ULONG uTotalIfaceInfoLength = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints); + NTSTATUS Status = STATUS_SUCCESS; + PURB pUrb; + PUSBD_INTERFACE_INFORMATION pNewIFInfo = NULL; + VBOXUSB_PIPE_INFO *pNewPipeInfo = NULL; + + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo) + { + /* Clear pipes associated with the interface, else Windows may hang. */ + for (ULONG i = 0; i < pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->NumberOfPipes; i++) + VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->Pipes[i].PipeHandle, FALSE); + } + + do { + /* First allocate all the structures we'll need. */ + pUrb = VBoxUsbToolUrbAllocZ(0, uUrbSize); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAllocZ failed\n")); + Status = STATUS_NO_MEMORY; + break; + } + + pNewIFInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength); + if (!pNewIFInfo) + { + AssertMsgFailed((__FUNCTION__": Failed allocating interface storage\n")); + Status = STATUS_NO_MEMORY; + break; + } + + if (pIfDr->bNumEndpoints > 0) + { + pNewPipeInfo = (VBOXUSB_PIPE_INFO *)vboxUsbMemAlloc(pIfDr->bNumEndpoints * sizeof(VBOXUSB_PIPE_INFO)); + if (!pNewPipeInfo) + { + AssertMsgFailed((__FUNCTION__": Failed allocating pipe info storage\n")); + Status = STATUS_NO_MEMORY; + break; + } + } + else + pNewPipeInfo = NULL; + + /* Now that we have all the bits, select the interface. */ + UsbBuildSelectInterfaceRequest(pUrb, uUrbSize, pDevExt->Rt.hConfiguration, InterfaceNumber, AlternateSetting); + pUrb->UrbSelectInterface.Interface.Length = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints); + + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + /* Free the old memory and put new in. */ + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo); + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo = pNewIFInfo; + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo); + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo = pNewPipeInfo; + pNewPipeInfo = NULL; pNewIFInfo = NULL; /* Don't try to free it again. */ + + USBD_INTERFACE_INFORMATION *pIfInfo = &pUrb->UrbSelectInterface.Interface; + memcpy(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo, pIfInfo, GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints)); + + Assert(pIfInfo->NumberOfPipes == pIfDr->bNumEndpoints); + for (ULONG i = 0; i < pIfInfo->NumberOfPipes; i++) + { + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].EndpointAddress = pIfInfo->Pipes[i].EndpointAddress; + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].NextScheduledFrame = 0; + } + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x) usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + } while (0); + + /* Clean up. */ + if (pUrb) + VBoxUsbToolUrbFree(pUrb); + if (pNewIFInfo) + vboxUsbMemFree(pNewIFInfo); + if (pNewPipeInfo) + vboxUsbMemFree(pNewPipeInfo); + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbSelectInterface(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_SELECT_INTERFACE pIf = (PUSBSUP_SELECT_INTERFACE)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pIf + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pIf) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtSetInterface(pDevExt, pIf->bInterfaceNumber, pIf->bAlternateSetting); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static HANDLE vboxUsbRtGetPipeHandle(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress) +{ + if (EndPointAddress == 0) + return pDevExt->Rt.hPipe0; + + for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + /* Note that bit 7 determines pipe direction, but is still significant + * because endpoints may be numbered like 0x01, 0x81, 0x02, 0x82 etc. + */ + if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress == EndPointAddress) + return pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle; + } + } + return 0; +} + +static VBOXUSB_PIPE_INFO* vboxUsbRtGetPipeInfo(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress) +{ + for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress == EndPointAddress) + return &pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j]; + } + } + return NULL; +} + + + +static NTSTATUS vboxUsbRtClearEndpoint(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress, bool fReset) +{ + NTSTATUS Status = VBoxUsbToolPipeClear(pDevExt->pLowerDO, vboxUsbRtGetPipeHandle(pDevExt, EndPointAddress), fReset); + if (!NT_SUCCESS(Status)) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolPipeClear failed Status (0x%x)\n", Status)); + } + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbClearEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCe + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, TRUE); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbAbortEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCe + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, FALSE); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtUrbSendCompletion(PDEVICE_OBJECT pDevObj, IRP *pIrp, void *pvContext) +{ + RT_NOREF1(pDevObj); + + if (!pvContext) + { + AssertMsgFailed((__FUNCTION__": context is NULL\n")); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + PVBOXUSB_URB_CONTEXT pContext = (PVBOXUSB_URB_CONTEXT)pvContext; + + if (pContext->ulMagic != VBOXUSB_MAGIC) + { + AssertMsgFailed((__FUNCTION__": Invalid context magic\n")); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + PURB pUrb = pContext->pUrb; + PMDL pMdlBuf = pContext->pMdlBuf; + PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pContext->pOut; + PVBOXUSBDEV_EXT pDevExt = pContext->pDevExt; + + if (!pUrb || !pMdlBuf || !pUrbInfo || !pDevExt) + { + AssertMsgFailed((__FUNCTION__": Invalid args\n")); + if (pDevExt) + vboxUsbDdiStateRelease(pDevExt); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + NTSTATUS Status = pIrp->IoStatus.Status; + if (Status == STATUS_SUCCESS) + { + switch(pUrb->UrbHeader.Status) + { + case USBD_STATUS_CRC: + pUrbInfo->error = USBSUP_XFER_CRC; + break; + case USBD_STATUS_SUCCESS: + pUrbInfo->error = USBSUP_XFER_OK; + break; + case USBD_STATUS_STALL_PID: + pUrbInfo->error = USBSUP_XFER_STALL; + break; + case USBD_STATUS_INVALID_URB_FUNCTION: + case USBD_STATUS_INVALID_PARAMETER: + AssertMsgFailed((__FUNCTION__": sw error, urb Status (0x%x)\n", pUrb->UrbHeader.Status)); + case USBD_STATUS_DEV_NOT_RESPONDING: + default: + pUrbInfo->error = USBSUP_XFER_DNR; + break; + } + + switch(pContext->ulTransferType) + { + case USBSUP_TRANSFER_TYPE_MSG: + pUrbInfo->len = pUrb->UrbControlTransfer.TransferBufferLength; + /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special + * the first 8 bytes of the buffer is the setup packet so the real + * data length is therefore urb->len - 8 + */ + pUrbInfo->len += sizeof (pUrb->UrbControlTransfer.SetupPacket); + + /* If a control URB was successfully completed on the default control + * pipe, stash away the handle. When submitting the URB, we don't need + * to know (and initially don't have) the handle. If we want to abort + * the default control pipe, we *have* to have a handle. This is how we + * find out what the handle is. + */ + if (!pUrbInfo->ep && (pDevExt->Rt.hPipe0 == NULL)) + { + pDevExt->Rt.hPipe0 = pUrb->UrbControlTransfer.PipeHandle; + } + + break; + case USBSUP_TRANSFER_TYPE_ISOC: + pUrbInfo->len = pUrb->UrbIsochronousTransfer.TransferBufferLength; + break; + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + if (pUrbInfo->dir == USBSUP_DIRECTION_IN && pUrbInfo->error == USBSUP_XFER_OK + && !(pUrbInfo->flags & USBSUP_FLAG_SHORT_OK) + && pUrbInfo->len > pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength + ) + { + /* If we don't use the USBD_SHORT_TRANSFER_OK flag, the returned buffer lengths are + * wrong for short transfers (always a multiple of max packet size?). So we just figure + * out if this was a data underrun on our own. + */ + pUrbInfo->error = USBSUP_XFER_UNDERRUN; + } + pUrbInfo->len = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength; + break; + default: + break; + } + } + else + { + pUrbInfo->len = 0; + + LogFunc(("URB failed Status (0x%x) urb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); +#ifdef DEBUG + switch(pContext->ulTransferType) + { + case USBSUP_TRANSFER_TYPE_MSG: + LogRel(("Msg (CTRL) length=%d\n", pUrb->UrbControlTransfer.TransferBufferLength)); + break; + case USBSUP_TRANSFER_TYPE_ISOC: + LogRel(("ISOC length=%d\n", pUrb->UrbIsochronousTransfer.TransferBufferLength)); + break; + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + LogRel(("BULK/INTR length=%d\n", pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength)); + break; + } +#endif + switch(pUrb->UrbHeader.Status) + { + case USBD_STATUS_CRC: + pUrbInfo->error = USBSUP_XFER_CRC; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_STALL_PID: + pUrbInfo->error = USBSUP_XFER_STALL; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_DEV_NOT_RESPONDING: + case USBD_STATUS_DEVICE_GONE: + pUrbInfo->error = USBSUP_XFER_DNR; + Status = STATUS_SUCCESS; + break; + case ((USBD_STATUS)0xC0010000L): // USBD_STATUS_CANCELED - too bad usbdi.h and usb.h aren't consistent! + /// @todo What the heck are we really supposed to do here? + pUrbInfo->error = USBSUP_XFER_STALL; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_BAD_START_FRAME: // This one really shouldn't happen + case USBD_STATUS_ISOCH_REQUEST_FAILED: + pUrbInfo->error = USBSUP_XFER_NAC; + Status = STATUS_SUCCESS; + break; + default: + AssertMsgFailed((__FUNCTION__": err Status (0x%x) (0x%x)\n", Status, pUrb->UrbHeader.Status)); + pUrbInfo->error = USBSUP_XFER_DNR; + Status = STATUS_SUCCESS; + break; + } + } + // For isochronous transfers, always update the individual packets + if (pContext->ulTransferType == USBSUP_TRANSFER_TYPE_ISOC) + { + Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets); + for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i) + { + Assert(pUrbInfo->aIsoPkts[i].off == pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset); + pUrbInfo->aIsoPkts[i].cb = (uint16_t)pUrb->UrbIsochronousTransfer.IsoPacket[i].Length; + switch (pUrb->UrbIsochronousTransfer.IsoPacket[i].Status) + { + case USBD_STATUS_SUCCESS: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_OK; + break; + case USBD_STATUS_NOT_ACCESSED: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_NAC; + break; + default: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_STALL; + break; + } + } + } + + MmUnlockPages(pMdlBuf); + IoFreeMdl(pMdlBuf); + + vboxUsbMemFree(pContext); + + vboxUsbDdiStateRelease(pDevExt); + + Assert(pIrp->IoStatus.Status != STATUS_IO_TIMEOUT); + pIrp->IoStatus.Information = sizeof(*pUrbInfo); + pIrp->IoStatus.Status = Status; + return STATUS_CONTINUE_COMPLETION; +} + +static NTSTATUS vboxUsbRtUrbSend(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp, PUSBSUP_URB pUrbInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + PVBOXUSB_URB_CONTEXT pContext = NULL; + PMDL pMdlBuf = NULL; + ULONG cbUrb; + + Assert(pUrbInfo); + if (pUrbInfo->type == USBSUP_TRANSFER_TYPE_ISOC) + { + Assert(pUrbInfo->numIsoPkts <= 8); + cbUrb = GET_ISO_URB_SIZE(pUrbInfo->numIsoPkts); + } + else + cbUrb = sizeof (URB); + + do + { + pContext = (PVBOXUSB_URB_CONTEXT)vboxUsbMemAllocZ(cbUrb + sizeof (VBOXUSB_URB_CONTEXT)); + if (!pContext) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + PURB pUrb = (PURB)(pContext + 1); + HANDLE hPipe = NULL; + if (pUrbInfo->ep) + { + hPipe = vboxUsbRtGetPipeHandle(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00)); + if (!hPipe) + { + AssertMsgFailed((__FUNCTION__": vboxUsbRtGetPipeHandle failed for endpoint (0x%x)\n", pUrbInfo->ep)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + pMdlBuf = IoAllocateMdl(pUrbInfo->buf, (ULONG)pUrbInfo->len, FALSE, FALSE, NULL); + if (!pMdlBuf) + { + AssertMsgFailed((__FUNCTION__": IoAllocateMdl failed for buffer (0x%p) length (%d)\n", pUrbInfo->buf, pUrbInfo->len)); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + __try + { + MmProbeAndLockPages(pMdlBuf, KernelMode, IoModifyAccess); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + Status = GetExceptionCode(); + IoFreeMdl(pMdlBuf); + pMdlBuf = NULL; + AssertMsgFailed((__FUNCTION__": Exception Code (0x%x)\n", Status)); + break; + } + + /* For some reason, passing a MDL in the URB does not work reliably. Notably + * the iPhone when used with iTunes fails. + */ + PVOID pBuffer = MmGetSystemAddressForMdlSafe(pMdlBuf, NormalPagePriority); + if (!pBuffer) + { + AssertMsgFailed((__FUNCTION__": MmGetSystemAddressForMdlSafe failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + switch (pUrbInfo->type) + { + case USBSUP_TRANSFER_TYPE_MSG: + { + pUrb->UrbHeader.Function = URB_FUNCTION_CONTROL_TRANSFER; + pUrb->UrbHeader.Length = sizeof (struct _URB_CONTROL_TRANSFER); + pUrb->UrbControlTransfer.PipeHandle = hPipe; + pUrb->UrbControlTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbControlTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + pUrb->UrbControlTransfer.UrbLink = 0; + + if (!hPipe) + pUrb->UrbControlTransfer.TransferFlags |= USBD_DEFAULT_PIPE_TRANSFER; + + /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special + * the first 8 bytes of the buffer is the setup packet so the real + * data length is therefore pUrb->len - 8 + */ + //PVBOXUSB_SETUP pSetup = (PVBOXUSB_SETUP)pUrb->UrbControlTransfer.SetupPacket; + memcpy(pUrb->UrbControlTransfer.SetupPacket, pBuffer, min(sizeof (pUrb->UrbControlTransfer.SetupPacket), pUrbInfo->len)); + + if (pUrb->UrbControlTransfer.TransferBufferLength <= sizeof (pUrb->UrbControlTransfer.SetupPacket)) + pUrb->UrbControlTransfer.TransferBufferLength = 0; + else + pUrb->UrbControlTransfer.TransferBufferLength -= sizeof (pUrb->UrbControlTransfer.SetupPacket); + + pUrb->UrbControlTransfer.TransferBuffer = (uint8_t *)pBuffer + sizeof(pUrb->UrbControlTransfer.SetupPacket); + pUrb->UrbControlTransfer.TransferBufferMDL = 0; + pUrb->UrbControlTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK; + break; + } + case USBSUP_TRANSFER_TYPE_ISOC: + { + Assert(hPipe); + VBOXUSB_PIPE_INFO *pPipeInfo = vboxUsbRtGetPipeInfo(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00)); + if (pPipeInfo == NULL) + { + /* Can happen if the isoc request comes in too early or late. */ + AssertMsgFailed((__FUNCTION__": pPipeInfo not found\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + pUrb->UrbHeader.Function = URB_FUNCTION_ISOCH_TRANSFER; + pUrb->UrbHeader.Length = (USHORT)cbUrb; + pUrb->UrbIsochronousTransfer.PipeHandle = hPipe; + pUrb->UrbIsochronousTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbIsochronousTransfer.TransferBufferMDL = 0; + pUrb->UrbIsochronousTransfer.TransferBuffer = pBuffer; + pUrb->UrbIsochronousTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + pUrb->UrbIsochronousTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK; // May be implied already + pUrb->UrbIsochronousTransfer.NumberOfPackets = pUrbInfo->numIsoPkts; + pUrb->UrbIsochronousTransfer.ErrorCount = 0; + pUrb->UrbIsochronousTransfer.UrbLink = 0; + + Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets); + for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i) + { + pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset = pUrbInfo->aIsoPkts[i].off; + pUrb->UrbIsochronousTransfer.IsoPacket[i].Length = pUrbInfo->aIsoPkts[i].cb; + } + + /* We have to schedule the URBs ourselves. There is an ASAP flag but + * that can only be reliably used after pipe creation/reset, ie. it's + * almost completely useless. + */ + ULONG iFrame, iStartFrame; + VBoxUsbToolCurrentFrame(pDevExt->pLowerDO, pIrp, &iFrame); + iFrame += 2; + iStartFrame = pPipeInfo->NextScheduledFrame; + if ((iFrame < iStartFrame) || (iStartFrame > iFrame + 512)) + iFrame = iStartFrame; + /* For full-speed devices, there must be one transfer per frame (Windows USB + * stack requirement), but URBs can contain multiple packets. For high-speed or + * faster transfers, we expect one URB per frame, regardless of the interval. + */ + if (pDevExt->Rt.devdescr->bcdUSB < 0x300 && !pDevExt->Rt.fIsHighSpeed) + pPipeInfo->NextScheduledFrame = iFrame + pUrbInfo->numIsoPkts; + else + pPipeInfo->NextScheduledFrame = iFrame + 1; + pUrb->UrbIsochronousTransfer.StartFrame = iFrame; + break; + } + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + { + Assert(pUrbInfo->dir != USBSUP_DIRECTION_SETUP); + Assert(pUrbInfo->dir == USBSUP_DIRECTION_IN || pUrbInfo->type == USBSUP_TRANSFER_TYPE_BULK); + Assert(hPipe); + + pUrb->UrbHeader.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; + pUrb->UrbHeader.Length = sizeof (struct _URB_BULK_OR_INTERRUPT_TRANSFER); + pUrb->UrbBulkOrInterruptTransfer.PipeHandle = hPipe; + pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbBulkOrInterruptTransfer.TransferBufferMDL = 0; + pUrb->UrbBulkOrInterruptTransfer.TransferBuffer = pBuffer; + pUrb->UrbBulkOrInterruptTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + + if (pUrb->UrbBulkOrInterruptTransfer.TransferFlags & USBD_TRANSFER_DIRECTION_IN) + pUrb->UrbBulkOrInterruptTransfer.TransferFlags |= (USBD_SHORT_TRANSFER_OK); + + pUrb->UrbBulkOrInterruptTransfer.UrbLink = 0; + break; + } + default: + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + if (!NT_SUCCESS(Status)) + { + break; + } + + pContext->pDevExt = pDevExt; + pContext->pMdlBuf = pMdlBuf; + pContext->pUrb = pUrb; + pContext->pOut = pUrbInfo; + pContext->ulTransferType = pUrbInfo->type; + pContext->ulMagic = VBOXUSB_MAGIC; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; + pSl->Parameters.Others.Argument1 = pUrb; + pSl->Parameters.Others.Argument2 = NULL; + + IoSetCompletionRoutine(pIrp, vboxUsbRtUrbSendCompletion, pContext, TRUE, TRUE, TRUE); + IoMarkIrpPending(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + AssertMsg(NT_SUCCESS(Status), (__FUNCTION__": IoCallDriver failed Status (0x%x)\n", Status)); + return STATUS_PENDING; + } while (0); + + Assert(!NT_SUCCESS(Status)); + + if (pMdlBuf) + { + if (pMdlBuf->MdlFlags & MDL_PAGES_LOCKED) + MmUnlockPages(pMdlBuf); + + IoFreeMdl(pMdlBuf); + } + + if (pContext) + vboxUsbMemFree(pContext); + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchSendUrb(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pUrbInfo + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pUrbInfo) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pUrbInfo)) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + return vboxUsbRtUrbSend(pDevExt, pIrp, pUrbInfo); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchIsOperational(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtDispatchGetVersion(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PUSBSUP_VERSION pVer= (PUSBSUP_VERSION)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status = STATUS_SUCCESS; + + if ( pVer + && pSl->Parameters.DeviceIoControl.InputBufferLength == 0 + && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof(*pVer)) + { + pVer->u32Major = USBDRV_MAJOR_VERSION; + pVer->u32Minor = USBDRV_MINOR_VERSION; + } + else + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + } + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, sizeof (*pVer)); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchDefault(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + VBoxDrvToolIoComplete(pIrp, STATUS_INVALID_DEVICE_REQUEST, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_INVALID_DEVICE_REQUEST; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtCreate(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + RT_NOREF1(pDevExt); + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + AssertReturn(pFObj, STATUS_INVALID_PARAMETER); + return STATUS_SUCCESS; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtClose(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + Assert(pFObj); + + vboxUsbRtCtxReleaseOwner(pDevExt, pFObj); + + return STATUS_SUCCESS; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtDispatch(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.DeviceIoControl.IoControlCode) + { + case SUPUSB_IOCTL_USB_CLAIM_DEVICE: + return vboxUsbRtDispatchClaimDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_RELEASE_DEVICE: + return vboxUsbRtDispatchReleaseDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_GET_DEVICE: + return vboxUsbRtDispatchGetDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_RESET: + return vboxUsbRtDispatchUsbReset(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_SET_CONFIG: + return vboxUsbRtDispatchUsbSetConfig(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_SELECT_INTERFACE: + return vboxUsbRtDispatchUsbSelectInterface(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_CLEAR_ENDPOINT: + return vboxUsbRtDispatchUsbClearEndpoint(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_ABORT_ENDPOINT: + return vboxUsbRtDispatchUsbAbortEndpoint(pDevExt, pIrp); + + case SUPUSB_IOCTL_SEND_URB: + return vboxUsbRtDispatchSendUrb(pDevExt, pIrp); + + case SUPUSB_IOCTL_IS_OPERATIONAL: + return vboxUsbRtDispatchIsOperational(pDevExt, pIrp); + + case SUPUSB_IOCTL_GET_VERSION: + return vboxUsbRtDispatchGetVersion(pDevExt, pIrp); + + default: + return vboxUsbRtDispatchDefault(pDevExt, pIrp); + } +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h new file mode 100644 index 00000000..c835890d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h @@ -0,0 +1,97 @@ +/* $Id: VBoxUsbRt.h $ */ +/** @file + * VBox USB R0 runtime + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbCmn.h" +#include "../cmn/VBoxUsbIdc.h" + +#define VBOXUSBRT_MAX_CFGS 4 + +typedef struct VBOXUSB_PIPE_INFO { + UCHAR EndpointAddress; + ULONG NextScheduledFrame; +} VBOXUSB_PIPE_INFO; + +typedef struct VBOXUSB_IFACE_INFO { + USBD_INTERFACE_INFORMATION *pInterfaceInfo; + VBOXUSB_PIPE_INFO *pPipeInfo; +} VBOXUSB_IFACE_INFO; + +typedef struct VBOXUSB_RT +{ + UNICODE_STRING IfName; + + HANDLE hPipe0; + HANDLE hConfiguration; + uint32_t uConfigValue; + + uint32_t uNumInterfaces; + USB_DEVICE_DESCRIPTOR *devdescr; + USB_CONFIGURATION_DESCRIPTOR *cfgdescr[VBOXUSBRT_MAX_CFGS]; + + VBOXUSB_IFACE_INFO *pVBIfaceInfo; + + uint16_t idVendor, idProduct, bcdDevice; + char szSerial[MAX_USB_SERIAL_STRING]; + BOOLEAN fIsHighSpeed; + + HVBOXUSBIDCDEV hMonDev; + PFILE_OBJECT pOwner; +} VBOXUSB_RT, *PVBOXUSB_RT; + +typedef struct VBOXUSBRT_IDC +{ + PDEVICE_OBJECT pDevice; + PFILE_OBJECT pFile; +} VBOXUSBRT_IDC, *PVBOXUSBRT_IDC; + +DECLHIDDEN(NTSTATUS) vboxUsbRtGlobalsInit(); +DECLHIDDEN(VOID) vboxUsbRtGlobalsTerm(); + +DECLHIDDEN(NTSTATUS) vboxUsbRtInit(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(VOID) vboxUsbRtClear(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbRtRm(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbRtStart(PVBOXUSBDEV_EXT pDevExt); + +DECLHIDDEN(NTSTATUS) vboxUsbRtDispatch(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); +DECLHIDDEN(NTSTATUS) vboxUsbRtCreate(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); +DECLHIDDEN(NTSTATUS) vboxUsbRtClose(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp b/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp new file mode 100644 index 00000000..817b26ed --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp @@ -0,0 +1,2221 @@ +/* $Id: VBoxUsbLib-win.cpp $ */ +/** @file + * VBox USB ring-3 Driver Interface library, Windows. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning (disable:4200) /* shuts up the empty array member warnings */ +#include +#include +#include +#include + +/* Defined in Windows 8 DDK (through usbdi.h) but we use Windows 7 DDK to build. */ +#define UsbSuperSpeed 3 + +#ifdef VBOX_WITH_NEW_USB_ENUM +# include +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _USB_INTERFACE_DESCRIPTOR2 +{ + UCHAR bLength; + UCHAR bDescriptorType; + UCHAR bInterfaceNumber; + UCHAR bAlternateSetting; + UCHAR bNumEndpoints; + UCHAR bInterfaceClass; + UCHAR bInterfaceSubClass; + UCHAR bInterfaceProtocol; + UCHAR iInterface; + USHORT wNumClasses; +} USB_INTERFACE_DESCRIPTOR2, *PUSB_INTERFACE_DESCRIPTOR2; + +typedef struct VBOXUSBGLOBALSTATE +{ + HANDLE hMonitor; + HANDLE hNotifyEvent; + HANDLE hInterruptEvent; + HANDLE hThread; + HWND hWnd; + HANDLE hTimerQueue; + HANDLE hTimer; +} VBOXUSBGLOBALSTATE, *PVBOXUSBGLOBALSTATE; + +typedef struct VBOXUSB_STRING_DR_ENTRY +{ + struct VBOXUSB_STRING_DR_ENTRY *pNext; + UCHAR iDr; + USHORT idLang; + USB_STRING_DESCRIPTOR StrDr; +} VBOXUSB_STRING_DR_ENTRY, *PVBOXUSB_STRING_DR_ENTRY; + +/** + * This represents VBoxUsb device instance + */ +typedef struct VBOXUSB_DEV +{ + struct VBOXUSB_DEV *pNext; + char szName[512]; + char szDriverRegName[512]; +} VBOXUSB_DEV, *PVBOXUSB_DEV; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static VBOXUSBGLOBALSTATE g_VBoxUsbGlobal; + + +static void usbLibVuFreeDevices(PVBOXUSB_DEV pDevInfos) +{ + while (pDevInfos) + { + PVBOXUSB_DEV pNext = pDevInfos->pNext; + RTMemFree(pDevInfos); + pDevInfos = pNext; + } +} + +/* Check that a proxied device responds the way we expect it to. */ +static int usbLibVuDeviceValidate(PVBOXUSB_DEV pVuDev) +{ + HANDLE hOut = INVALID_HANDLE_VALUE; + DWORD dwErr; + + hOut = CreateFile(pVuDev->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL); + + if (hOut == INVALID_HANDLE_VALUE) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("Failed to open `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + return VERR_GENERAL_FAILURE; + } + + USBSUP_VERSION version = {0}; + DWORD cbReturned = 0; + int rc = VERR_VERSION_MISMATCH; + + do + { + if (!DeviceIoControl(hOut, SUPUSB_IOCTL_GET_VERSION, NULL, 0,&version, sizeof(version), &cbReturned, NULL)) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSB_IOCTL_GET_VERSION failed on `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + break; + } + + if ( version.u32Major != USBDRV_MAJOR_VERSION +#if USBDRV_MINOR_VERSION != 0 + || version.u32Minor < USBDRV_MINOR_VERSION +#endif + ) + { + AssertFailed(); + LogRelFunc(("Invalid version %d:%d (%s) vs %d:%d (library)!\n", version.u32Major, version.u32Minor, pVuDev->szName, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION)); + break; + } + + if (!DeviceIoControl(hOut, SUPUSB_IOCTL_IS_OPERATIONAL, NULL, 0, NULL, NULL, &cbReturned, NULL)) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSB_IOCTL_IS_OPERATIONAL failed on `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + break; + } + + rc = VINF_SUCCESS; + } while (0); + + CloseHandle(hOut); + return rc; +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibVuDevicePopulate(PVBOXUSB_DEV pVuDev, HDEVINFO hDevInfo, PSP_DEVICE_INTERFACE_DATA pIfData) +{ + DWORD cbIfDetailData; + int rc = VINF_SUCCESS; + + SetupDiGetDeviceInterfaceDetail(hDevInfo, pIfData, + NULL, /* OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData */ + 0, /* IN DWORD DeviceInterfaceDetailDataSize */ + &cbIfDetailData, + NULL + ); + Assert(GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + PSP_DEVICE_INTERFACE_DETAIL_DATA pIfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAllocZ(cbIfDetailData); + if (!pIfDetailData) + { + AssertMsgFailed(("RTMemAllocZ failed\n")); + return VERR_OUT_OF_RESOURCES; + } + + DWORD cbDbgRequired; + SP_DEVINFO_DATA DevInfoData; + DevInfoData.cbSize = sizeof (DevInfoData); + /* the cbSize should contain the sizeof a fixed-size part according to the docs */ + pIfDetailData->cbSize = sizeof (*pIfDetailData); + do + { + if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, pIfData, + pIfDetailData, + cbIfDetailData, + &cbDbgRequired, + &DevInfoData)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetDeviceInterfaceDetail, cbRequired (%d), was (%d), dwErr (%d)\n", cbDbgRequired, cbIfDetailData, dwErr)); + rc = VERR_GENERAL_FAILURE; + break; + } + + strncpy(pVuDev->szName, pIfDetailData->DevicePath, sizeof (pVuDev->szName)); + + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfo, &DevInfoData, SPDRP_DRIVER, + NULL, /* OUT PDWORD PropertyRegDataType */ + (PBYTE)pVuDev->szDriverRegName, + sizeof (pVuDev->szDriverRegName), + &cbDbgRequired)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetDeviceRegistryPropertyA, cbRequired (%d), was (%d), dwErr (%d)\n", cbDbgRequired, sizeof (pVuDev->szDriverRegName), dwErr)); + rc = VERR_GENERAL_FAILURE; + break; + } + + rc = usbLibVuDeviceValidate(pVuDev); + LogRelFunc(("Found VBoxUSB on `%s' (rc=%d)\n", pVuDev->szName, rc)); + AssertRC(rc); + } while (0); + + RTMemFree(pIfDetailData); + return rc; +} + +static int usbLibVuGetDevices(PVBOXUSB_DEV *ppVuDevs, uint32_t *pcVuDevs) +{ + *ppVuDevs = NULL; + *pcVuDevs = 0; + + HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_CLASS_VBOXUSB, + NULL, /* IN PCTSTR Enumerator */ + NULL, /* IN HWND hwndParent */ + (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) /* IN DWORD Flags */ + ); + if (hDevInfo == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetClassDevs, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + + for (int i = 0; ; ++i) + { + SP_DEVICE_INTERFACE_DATA IfData; + IfData.cbSize = sizeof (IfData); + if (!SetupDiEnumDeviceInterfaces(hDevInfo, + NULL, /* IN PSP_DEVINFO_DATA DeviceInfoData */ + &GUID_CLASS_VBOXUSB, /* IN LPGUID InterfaceClassGuid */ + i, + &IfData)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_NO_MORE_ITEMS) + break; + + AssertMsgFailed(("SetupDiEnumDeviceInterfaces, dwErr (%u), resuming\n", dwErr)); + continue; + } + + /* we've now got the IfData */ + PVBOXUSB_DEV pVuDev = (PVBOXUSB_DEV)RTMemAllocZ(sizeof (*pVuDev)); + if (!pVuDev) + { + AssertMsgFailed(("RTMemAllocZ failed, resuming\n")); + continue; + } + + int rc = usbLibVuDevicePopulate(pVuDev, hDevInfo, &IfData); + if (!RT_SUCCESS(rc)) + { + AssertMsgFailed(("usbLibVuDevicePopulate failed, rc (%d), resuming\n", rc)); + continue; + } + + pVuDev->pNext = *ppVuDevs; + *ppVuDevs = pVuDev; + ++*pcVuDevs; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + + return VINF_SUCCESS; +} + +static int usbLibDevPopulate(PUSBDEVICE pDev, PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo, ULONG iPort, LPCSTR lpszDrvKeyName, LPCSTR lpszHubName, PVBOXUSB_STRING_DR_ENTRY pDrList) +{ + pDev->bcdUSB = pConInfo->DeviceDescriptor.bcdUSB; + pDev->bDeviceClass = pConInfo->DeviceDescriptor.bDeviceClass; + pDev->bDeviceSubClass = pConInfo->DeviceDescriptor.bDeviceSubClass; + pDev->bDeviceProtocol = pConInfo->DeviceDescriptor.bDeviceProtocol; + pDev->idVendor = pConInfo->DeviceDescriptor.idVendor; + pDev->idProduct = pConInfo->DeviceDescriptor.idProduct; + pDev->bcdDevice = pConInfo->DeviceDescriptor.bcdDevice; + pDev->bBus = 0; /** @todo figure out bBus on windows... */ + pDev->bPort = iPort; + /** @todo check which devices are used for primary input (keyboard & mouse) */ + if (!lpszDrvKeyName || *lpszDrvKeyName == 0) + pDev->enmState = USBDEVICESTATE_UNUSED; + else + pDev->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* Determine the speed the device is operating at. */ + switch (pConInfo->Speed) + { + case UsbLowSpeed: pDev->enmSpeed = USBDEVICESPEED_LOW; break; + case UsbFullSpeed: pDev->enmSpeed = USBDEVICESPEED_FULL; break; + case UsbHighSpeed: pDev->enmSpeed = USBDEVICESPEED_HIGH; break; + default: /* If we don't know, most likely it's something new. */ + case UsbSuperSpeed: pDev->enmSpeed = USBDEVICESPEED_SUPER; break; + } + /* Unfortunately USB_NODE_CONNECTION_INFORMATION_EX will not report UsbSuperSpeed, and + * it's not even defined in the Win7 DDK we use. So we go by the USB version, and + * luckily we know that USB3 must mean SuperSpeed. The USB3 spec guarantees this (9.6.1). + */ + if (pDev->bcdUSB >= 0x0300) + pDev->enmSpeed = USBDEVICESPEED_SUPER; + + int rc = RTStrAPrintf((char **)&pDev->pszAddress, "%s", lpszDrvKeyName); + if (rc < 0) + return VERR_NO_MEMORY; + pDev->pszBackend = RTStrDup("host"); + if (!pDev->pszBackend) + { + RTStrFree((char *)pDev->pszAddress); + return VERR_NO_STR_MEMORY; + } + pDev->pszHubName = RTStrDup(lpszHubName); + pDev->bNumConfigurations = 0; + pDev->u64SerialHash = 0; + + for (; pDrList; pDrList = pDrList->pNext) + { + char **ppszString = NULL; + if ( pConInfo->DeviceDescriptor.iManufacturer + && pDrList->iDr == pConInfo->DeviceDescriptor.iManufacturer) + ppszString = (char **)&pDev->pszManufacturer; + else if ( pConInfo->DeviceDescriptor.iProduct + && pDrList->iDr == pConInfo->DeviceDescriptor.iProduct) + ppszString = (char **)&pDev->pszProduct; + else if ( pConInfo->DeviceDescriptor.iSerialNumber + && pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + ppszString = (char **)&pDev->pszSerialNumber; + if (ppszString) + { + rc = RTUtf16ToUtf8((PCRTUTF16)pDrList->StrDr.bString, ppszString); + if (RT_SUCCESS(rc)) + { + Assert(*ppszString); + USBLibPurgeEncoding(*ppszString); + + if (pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + pDev->u64SerialHash = USBLibHashSerial(*ppszString); + } + else + { + AssertMsgFailed(("RTUtf16ToUtf8 failed, rc (%d), resuming\n", rc)); + *ppszString = NULL; + } + } + } + + return VINF_SUCCESS; +} +#else + +static PSP_DEVICE_INTERFACE_DETAIL_DATA usbLibGetDevDetail(HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData, PSP_DEVINFO_DATA DevInfoData); +static void *usbLibGetRegistryProperty(HDEVINFO InfoSet, const PSP_DEVINFO_DATA DevData, DWORD Property); + +/* Populate the data for a single proxied USB device. */ +static int usbLibVUsbDevicePopulate(PVBOXUSB_DEV pVuDev, HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData) +{ + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData = NULL; + SP_DEVINFO_DATA DeviceData; + LPCSTR Location; + int rc = VINF_SUCCESS; + + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + /* The interface detail includes the device path. */ + DetailData = usbLibGetDevDetail(InfoSet, InterfaceData, &DeviceData); + if (DetailData) + { + strncpy(pVuDev->szName, DetailData->DevicePath, sizeof(pVuDev->szName)); + + /* The location is used as a unique identifier for cross-referencing the two lists. */ + Location = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_DRIVER); + if (Location) + { + strncpy(pVuDev->szDriverRegName, Location, sizeof(pVuDev->szDriverRegName)); + rc = usbLibVuDeviceValidate(pVuDev); + LogRelFunc(("Found VBoxUSB on `%s' (rc=%d)\n", pVuDev->szName, rc)); + AssertRC(rc); + + RTMemFree((void *)Location); + } + else + { + /* Errors will be logged by usbLibGetRegistryProperty(). */ + rc = VERR_GENERAL_FAILURE; + } + + RTMemFree(DetailData); + } + else + { + /* Errors will be logged by usbLibGetDevDetail(). */ + rc = VERR_GENERAL_FAILURE; + } + + + return rc; +} + +/* Enumerate proxied USB devices (with VBoxUSB.sys loaded). */ +static int usbLibEnumVUsbDevices(PVBOXUSB_DEV *ppVuDevs, uint32_t *pcVuDevs) +{ + SP_DEVICE_INTERFACE_DATA InterfaceData; + HDEVINFO InfoSet; + DWORD DeviceIndex; + DWORD dwErr; + + *ppVuDevs = NULL; + *pcVuDevs = 0; + + /* Enumerate all present devices which support the GUID_CLASS_VBOXUSB interface. */ + InfoSet = SetupDiGetClassDevs(&GUID_CLASS_VBOXUSB, NULL, NULL, + (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); + if (InfoSet == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("SetupDiGetClassDevs for GUID_CLASS_VBOXUSB failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + DeviceIndex = 0; + + /* Loop over the enumerated list. */ + while (SetupDiEnumDeviceInterfaces(InfoSet, NULL, &GUID_CLASS_VBOXUSB, DeviceIndex, &InterfaceData)) + { + /* we've now got the IfData */ + PVBOXUSB_DEV pVuDev = (PVBOXUSB_DEV)RTMemAllocZ(sizeof (*pVuDev)); + if (!pVuDev) + { + AssertFailed(); + LogRelFunc(("RTMemAllocZ failed\n")); + break; + } + + int rc = usbLibVUsbDevicePopulate(pVuDev, InfoSet, &InterfaceData); + if (RT_SUCCESS(rc)) + { + pVuDev->pNext = *ppVuDevs; + *ppVuDevs = pVuDev; + ++*pcVuDevs; + } + else /* Skip this device but continue enumerating. */ + AssertMsgFailed(("usbLibVuDevicePopulate failed, rc=%d\n", rc)); + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + ++DeviceIndex; + } + + /* Paranoia. */ + dwErr = GetLastError(); + if (dwErr != ERROR_NO_MORE_ITEMS) + { + LogRelFunc(("SetupDiEnumDeviceInterfaces failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + } + + SetupDiDestroyDeviceInfoList(InfoSet); + + return VINF_SUCCESS; +} + +static uint16_t usbLibParseHexNumU16(LPCSTR *ppStr) +{ + const char *pStr = *ppStr; + char c; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 4; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + c = *pStr; + u = c >= 'A' ? c - 'A' + 10 : c - '0'; /* Hex digit to number. */ + num |= u << (12 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static int usbLibDevPopulate(PUSBDEVICE pDev, PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo, ULONG iPort, LPCSTR lpszLocation, LPCSTR lpszDrvKeyName, LPCSTR lpszHubName, PVBOXUSB_STRING_DR_ENTRY pDrList) +{ + pDev->bcdUSB = pConInfo->DeviceDescriptor.bcdUSB; + pDev->bDeviceClass = pConInfo->DeviceDescriptor.bDeviceClass; + pDev->bDeviceSubClass = pConInfo->DeviceDescriptor.bDeviceSubClass; + pDev->bDeviceProtocol = pConInfo->DeviceDescriptor.bDeviceProtocol; + pDev->idVendor = pConInfo->DeviceDescriptor.idVendor; + pDev->idProduct = pConInfo->DeviceDescriptor.idProduct; + pDev->bcdDevice = pConInfo->DeviceDescriptor.bcdDevice; + pDev->bBus = 0; /* The hub numbering is not very useful on Windows. Skip it. */ + pDev->bPort = iPort; + + /* The port path/location uniquely identifies the port. */ + pDev->pszPortPath = RTStrDup(lpszLocation); + if (!pDev->pszPortPath) + return VERR_NO_STR_MEMORY; + + /* If there is no DriverKey, the device is unused because there's no driver. */ + if (!lpszDrvKeyName || *lpszDrvKeyName == 0) + pDev->enmState = USBDEVICESTATE_UNUSED; + else + pDev->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* Determine the speed the device is operating at. */ + switch (pConInfo->Speed) + { + case UsbLowSpeed: pDev->enmSpeed = USBDEVICESPEED_LOW; break; + case UsbFullSpeed: pDev->enmSpeed = USBDEVICESPEED_FULL; break; + case UsbHighSpeed: pDev->enmSpeed = USBDEVICESPEED_HIGH; break; + default: /* If we don't know, most likely it's something new. */ + case UsbSuperSpeed: pDev->enmSpeed = USBDEVICESPEED_SUPER; break; + } + /* Unfortunately USB_NODE_CONNECTION_INFORMATION_EX will not report UsbSuperSpeed, and + * it's not even defined in the Win7 DDK we use. So we go by the USB version, and + * luckily we know that USB3 must mean SuperSpeed. The USB3 spec guarantees this (9.6.1). + */ + if (pDev->bcdUSB >= 0x0300) + pDev->enmSpeed = USBDEVICESPEED_SUPER; + + /* If there's no DriverKey, jam in an empty string to avoid NULL pointers. */ + if (!lpszDrvKeyName) + pDev->pszAddress = RTStrDup(""); + else + pDev->pszAddress = RTStrDup(lpszDrvKeyName); + + pDev->pszBackend = RTStrDup("host"); + if (!pDev->pszBackend) + { + RTStrFree((char *)pDev->pszAddress); + return VERR_NO_STR_MEMORY; + } + pDev->pszHubName = RTStrDup(lpszHubName); + pDev->bNumConfigurations = 0; + pDev->u64SerialHash = 0; + + for (; pDrList; pDrList = pDrList->pNext) + { + char **ppszString = NULL; + if ( pConInfo->DeviceDescriptor.iManufacturer + && pDrList->iDr == pConInfo->DeviceDescriptor.iManufacturer) + ppszString = (char **)&pDev->pszManufacturer; + else if ( pConInfo->DeviceDescriptor.iProduct + && pDrList->iDr == pConInfo->DeviceDescriptor.iProduct) + ppszString = (char **)&pDev->pszProduct; + else if ( pConInfo->DeviceDescriptor.iSerialNumber + && pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + ppszString = (char **)&pDev->pszSerialNumber; + if (ppszString) + { + int rc = RTUtf16ToUtf8((PCRTUTF16)pDrList->StrDr.bString, ppszString); + if (RT_SUCCESS(rc)) + { + Assert(*ppszString); + USBLibPurgeEncoding(*ppszString); + + if (pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + pDev->u64SerialHash = USBLibHashSerial(*ppszString); + } + else + { + AssertMsgFailed(("RTUtf16ToUtf8 failed, rc (%d), resuming\n", rc)); + *ppszString = NULL; + } + } + } + + return VINF_SUCCESS; +} +#endif + +static void usbLibDevStrFree(LPSTR lpszName) +{ + RTStrFree(lpszName); +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibDevStrDriverKeyGet(HANDLE hHub, ULONG iPort, LPSTR* plpszName) +{ + USB_NODE_CONNECTION_DRIVERKEY_NAME Name; + DWORD cbReturned = 0; + Name.ConnectionIndex = iPort; + *plpszName = NULL; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL)) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertMsgFailed(("DeviceIoControl 1 fail dwErr (%u)\n", GetLastError())); +#endif + return VERR_GENERAL_FAILURE; + } + + if (Name.ActualLength < sizeof (Name)) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + PUSB_NODE_CONNECTION_DRIVERKEY_NAME pName = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)RTMemAllocZ(Name.ActualLength); + if (!pName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + pName->ConnectionIndex = iPort; + if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->DriverKeyName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("DeviceIoControl 2 fail dwErr (%u)\n", dwErr)); + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pName); + return rc; +} +#endif + +static int usbLibDevStrHubNameGet(HANDLE hHub, ULONG iPort, LPSTR* plpszName) +{ + USB_NODE_CONNECTION_NAME Name; + DWORD cbReturned = 0; + Name.ConnectionIndex = iPort; + *plpszName = NULL; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL)) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + if (Name.ActualLength < sizeof (Name)) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + PUSB_NODE_CONNECTION_NAME pName = (PUSB_NODE_CONNECTION_NAME)RTMemAllocZ(Name.ActualLength); + if (!pName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + pName->ConnectionIndex = iPort; + if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->NodeName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + AssertFailed(); + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pName); + return rc; +} + +static int usbLibDevStrRootHubNameGet(HANDLE hCtl, LPSTR* plpszName) +{ + USB_ROOT_HUB_NAME HubName; + DWORD cbReturned = 0; + *plpszName = NULL; + if (!DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, &HubName, sizeof (HubName), &cbReturned, NULL)) + { + return VERR_GENERAL_FAILURE; + } + PUSB_ROOT_HUB_NAME pHubName = (PUSB_ROOT_HUB_NAME)RTMemAllocZ(HubName.ActualLength); + if (!pHubName) + return VERR_OUT_OF_RESOURCES; + + int rc = VINF_SUCCESS; + if (DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, pHubName, HubName.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pHubName->RootHubName, pHubName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pHubName); + return rc; +} + +static int usbLibDevCfgDrGet(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, PUSB_CONFIGURATION_DESCRIPTOR *ppDr) +{ + *ppDr = NULL; + + char Buf[sizeof (USB_DESCRIPTOR_REQUEST) + sizeof (USB_CONFIGURATION_DESCRIPTOR)]; + memset(&Buf, 0, sizeof (Buf)); + + PUSB_DESCRIPTOR_REQUEST pCfgDrRq = (PUSB_DESCRIPTOR_REQUEST)Buf; + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = (PUSB_CONFIGURATION_DESCRIPTOR)(Buf + sizeof (*pCfgDrRq)); + + pCfgDrRq->ConnectionIndex = iPort; + pCfgDrRq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | iDr; + pCfgDrRq->SetupPacket.wLength = (USHORT)(sizeof (USB_CONFIGURATION_DESCRIPTOR)); + DWORD cbReturned = 0; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pCfgDrRq, sizeof (Buf), + pCfgDrRq, sizeof (Buf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION #1 failed (dwErr=%u) on hub %s port %d\n", dwErr, lpcszHubName, iPort)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return VERR_GENERAL_FAILURE; + } + + if (sizeof (Buf) != cbReturned) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + if (pCfgDr->wTotalLength < sizeof (USB_CONFIGURATION_DESCRIPTOR)) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + DWORD cbRq = sizeof (USB_DESCRIPTOR_REQUEST) + pCfgDr->wTotalLength; + PUSB_DESCRIPTOR_REQUEST pRq = (PUSB_DESCRIPTOR_REQUEST)RTMemAllocZ(cbRq); + Assert(pRq); + if (!pRq) + return VERR_OUT_OF_RESOURCES; + + int rc = VERR_GENERAL_FAILURE; + do + { + PUSB_CONFIGURATION_DESCRIPTOR pDr = (PUSB_CONFIGURATION_DESCRIPTOR)(pRq + 1); + pRq->ConnectionIndex = iPort; + pRq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | iDr; + pRq->SetupPacket.wLength = (USHORT)(cbRq - sizeof (USB_DESCRIPTOR_REQUEST)); + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pRq, cbRq, + pRq, cbRq, + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION #2 failed (dwErr=%u) on hub %s port %d\n", dwErr, lpcszHubName, iPort)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + break; + } + + if (cbRq != cbReturned) + { + AssertFailed(); + break; + } + + if (pDr->wTotalLength != cbRq - sizeof (USB_DESCRIPTOR_REQUEST)) + { + AssertFailed(); + break; + } + + *ppDr = pDr; + return VINF_SUCCESS; + } while (0); + + RTMemFree(pRq); + return rc; +} + +static void usbLibDevCfgDrFree(PUSB_CONFIGURATION_DESCRIPTOR pDr) +{ + Assert(pDr); + PUSB_DESCRIPTOR_REQUEST pRq = ((PUSB_DESCRIPTOR_REQUEST)pDr)-1; + RTMemFree(pRq); +} + +static int usbLibDevStrDrEntryGet(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, USHORT idLang, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + char szBuf[sizeof (USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH]; + RT_ZERO(szBuf); + + PUSB_DESCRIPTOR_REQUEST pRq = (PUSB_DESCRIPTOR_REQUEST)szBuf; + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)(szBuf + sizeof (*pRq)); + RT_BZERO(pDr, sizeof(USB_STRING_DESCRIPTOR)); + + pRq->ConnectionIndex = iPort; + pRq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | iDr; + pRq->SetupPacket.wIndex = idLang; + pRq->SetupPacket.wLength = sizeof (szBuf) - sizeof (*pRq); + + DWORD cbReturned = 0; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pRq, sizeof (szBuf), + pRq, sizeof(szBuf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRel(("Getting USB descriptor (id %u) failed (dwErr=%u) on hub %s port %d\n", iDr, dwErr, lpcszHubName, iPort)); + return RTErrConvertFromWin32(dwErr); + } + + /* Wrong descriptor type at the requested port index? Bail out. */ + if (pDr->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE) + return VERR_NOT_FOUND; + + /* Some more sanity checks. */ + if ( (cbReturned < sizeof (*pDr) + 2) + || (!!(pDr->bLength % 2)) + || (pDr->bLength != cbReturned - sizeof(*pRq))) + { + AssertMsgFailed(("Sanity check failed for string descriptor: cbReturned=%RI32, cbDevReq=%zu, type=%RU8, len=%RU8, port=%RU32, index=%RU32, lang=%RU32\n", + cbReturned, sizeof(*pRq), pDr->bDescriptorType, pDr->bLength, iPort, iDr, idLang)); + return VERR_INVALID_PARAMETER; + } + + PVBOXUSB_STRING_DR_ENTRY pEntry = + (PVBOXUSB_STRING_DR_ENTRY)RTMemAllocZ(sizeof(VBOXUSB_STRING_DR_ENTRY) + pDr->bLength + 2); + AssertPtr(pEntry); + if (!pEntry) + return VERR_NO_MEMORY; + + pEntry->pNext = *ppList; + pEntry->iDr = iDr; + pEntry->idLang = idLang; + memcpy(&pEntry->StrDr, pDr, pDr->bLength); + + *ppList = pEntry; + + return VINF_SUCCESS; +} + +static void usbLibDevStrDrEntryFree(PVBOXUSB_STRING_DR_ENTRY pDr) +{ + RTMemFree(pDr); +} + +static void usbLibDevStrDrEntryFreeList(PVBOXUSB_STRING_DR_ENTRY pDr) +{ + while (pDr) + { + PVBOXUSB_STRING_DR_ENTRY pNext = pDr->pNext; + usbLibDevStrDrEntryFree(pDr); + pDr = pNext; + } +} + +static int usbLibDevStrDrEntryGetForLangs(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, ULONG cIdLang, const USHORT *pIdLang, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + for (ULONG i = 0; i < cIdLang; ++i) + { + usbLibDevStrDrEntryGet(hHub, lpcszHubName, iPort, iDr, pIdLang[i], ppList); + } + return VINF_SUCCESS; +} + +static int usbLibDevStrDrEntryGetAll(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, PUSB_DEVICE_DESCRIPTOR pDevDr, PUSB_CONFIGURATION_DESCRIPTOR pCfgDr, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + /* Read string descriptor zero to determine what languages are available. */ + int rc = usbLibDevStrDrEntryGet(hHub, lpcszHubName, iPort, 0, 0, ppList); + if (RT_FAILURE(rc)) + return rc; + + PUSB_STRING_DESCRIPTOR pLangStrDr = &(*ppList)->StrDr; + USHORT *pIdLang = pLangStrDr->bString; + ULONG cIdLang = (pLangStrDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString)) / sizeof (*pIdLang); + + if (pDevDr->iManufacturer) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iManufacturer, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + if (pDevDr->iProduct) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iProduct, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + if (pDevDr->iSerialNumber) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iSerialNumber, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + PUCHAR pCur = (PUCHAR)pCfgDr; + PUCHAR pEnd = pCur + pCfgDr->wTotalLength; + while (pCur + sizeof (USB_COMMON_DESCRIPTOR) <= pEnd) + { + PUSB_COMMON_DESCRIPTOR pCmnDr = (PUSB_COMMON_DESCRIPTOR)pCur; + if (pCur + pCmnDr->bLength > pEnd) + { + AssertFailed(); + break; + } + + /* This is invalid but was seen with a TerraTec Aureon 7.1 USB sound card. */ + if (!pCmnDr->bLength) + break; + + switch (pCmnDr->bDescriptorType) + { + case USB_CONFIGURATION_DESCRIPTOR_TYPE: + { + if (pCmnDr->bLength != sizeof (USB_CONFIGURATION_DESCRIPTOR)) + { + AssertFailed(); + break; + } + PUSB_CONFIGURATION_DESCRIPTOR pCurCfgDr = (PUSB_CONFIGURATION_DESCRIPTOR)pCmnDr; + if (!pCurCfgDr->iConfiguration) + break; + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pCurCfgDr->iConfiguration, cIdLang, pIdLang, ppList); + AssertRC(rc); + break; + } + case USB_INTERFACE_DESCRIPTOR_TYPE: + { + if (pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR) && pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR2)) + { + AssertFailed(); + break; + } + PUSB_INTERFACE_DESCRIPTOR pCurIfDr = (PUSB_INTERFACE_DESCRIPTOR)pCmnDr; + if (!pCurIfDr->iInterface) + break; + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pCurIfDr->iInterface, cIdLang, pIdLang, ppList); + AssertRC(rc); + break; + } + default: + break; + } + + pCur = pCur + pCmnDr->bLength; + } + + return VINF_SUCCESS; +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibDevGetHubDevices(LPCSTR lpszName, PUSBDEVICE *ppDevs, uint32_t *pcDevs); + +static int usbLibDevGetHubPortDevices(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + int rc = VINF_SUCCESS; + char Buf[sizeof (USB_NODE_CONNECTION_INFORMATION_EX) + (sizeof (USB_PIPE_INFO) * 20)]; + PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo = (PUSB_NODE_CONNECTION_INFORMATION_EX)Buf; + //PUSB_PIPE_INFO paPipeInfo = (PUSB_PIPE_INFO)(Buf + sizeof (PUSB_NODE_CONNECTION_INFORMATION_EX)); + DWORD cbReturned = 0; + memset(&Buf, 0, sizeof (Buf)); + pConInfo->ConnectionIndex = iPort; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, + pConInfo, sizeof (Buf), + pConInfo, sizeof (Buf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRel(("Getting USB connection information failed (dwErr=%u) on hub %s\n", dwErr, lpcszHubName)); + AssertMsg(dwErr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed (dwErr=%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + + if (pConInfo->ConnectionStatus != DeviceConnected) + { + /* just ignore & return success */ + return VWRN_INVALID_HANDLE; + } + + if (pConInfo->DeviceIsHub) + { + LPSTR lpszChildHubName = NULL; + rc = usbLibDevStrHubNameGet(hHub, iPort, &lpszChildHubName); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibDevGetHubDevices(lpszChildHubName, ppDevs, pcDevs); + usbLibDevStrFree(lpszChildHubName); + AssertRC(rc); + return rc; + } + /* ignore this err */ + return VINF_SUCCESS; + } + + bool fFreeNameBuf = true; + char nameEmptyBuf = '\0'; + LPSTR lpszName = NULL; + rc = usbLibDevStrDriverKeyGet(hHub, iPort, &lpszName); + Assert(!!lpszName == !!RT_SUCCESS(rc)); + if (!lpszName) + { + LogRelFunc(("No DriverKey on hub %s port %d\n", lpcszHubName, iPort)); + lpszName = &nameEmptyBuf; + fFreeNameBuf = false; + } + + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + PVBOXUSB_STRING_DR_ENTRY pList = NULL; + rc = usbLibDevCfgDrGet(hHub, lpcszHubName, iPort, 0, &pCfgDr); + if (pCfgDr) + { + rc = usbLibDevStrDrEntryGetAll(hHub, lpcszHubName, iPort, &pConInfo->DeviceDescriptor, pCfgDr, &pList); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertRC(rc); // this can fail if device suspended +#endif + } + + PUSBDEVICE pDev = (PUSBDEVICE)RTMemAllocZ(sizeof (*pDev)); + if (RT_LIKELY(pDev)) + { + rc = usbLibDevPopulate(pDev, pConInfo, iPort, lpszName, lpcszHubName, pList); + if (RT_SUCCESS(rc)) + { + pDev->pNext = *ppDevs; + *ppDevs = pDev; + ++*pcDevs; + } + else + RTMemFree(pDev); + } + else + rc = VERR_NO_MEMORY; + + if (pCfgDr) + usbLibDevCfgDrFree(pCfgDr); + if (fFreeNameBuf) + { + Assert(lpszName); + usbLibDevStrFree(lpszName); + } + if (pList) + usbLibDevStrDrEntryFreeList(pList); + + return rc; +} + +static int usbLibDevGetHubDevices(LPCSTR lpszName, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + LPSTR lpszDevName = (LPSTR)RTMemAllocZ(strlen(lpszName) + sizeof("\\\\.\\")); + HANDLE hDev = INVALID_HANDLE_VALUE; + Assert(lpszDevName); + if (!lpszDevName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + strcpy(lpszDevName, "\\\\.\\"); + strcpy(lpszDevName + sizeof("\\\\.\\") - sizeof (lpszDevName[0]), lpszName); + do + { + DWORD cbReturned = 0; + hDev = CreateFile(lpszDevName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hDev == INVALID_HANDLE_VALUE) + { + AssertFailed(); + break; + } + + USB_NODE_INFORMATION NodeInfo; + memset(&NodeInfo, 0, sizeof (NodeInfo)); + if (!DeviceIoControl(hDev, IOCTL_USB_GET_NODE_INFORMATION, + &NodeInfo, sizeof (NodeInfo), + &NodeInfo, sizeof (NodeInfo), + &cbReturned, NULL)) + { + LogRel(("Getting USB node information failed (dwErr=%u) on hub %s\n", GetLastError(), lpszName)); + AssertFailed(); + break; + } + + for (ULONG i = 1; i <= NodeInfo.u.HubInformation.HubDescriptor.bNumberOfPorts; ++i) + { + /* Just skip devices for which we failed to create the device structure. */ + usbLibDevGetHubPortDevices(hDev, lpszName, i, ppDevs, pcDevs); + } + } while (0); + + if (hDev != INVALID_HANDLE_VALUE) + CloseHandle(hDev); + + RTMemFree(lpszDevName); + + return rc; +} +#endif + +#ifdef VBOX_WITH_NEW_USB_ENUM + +/* Get a registry property for a device given its HDEVINFO + SP_DEVINFO_DATA. */ +static void *usbLibGetRegistryProperty(HDEVINFO InfoSet, const PSP_DEVINFO_DATA DevData, DWORD Property) +{ + BOOL rc; + DWORD dwReqLen; + void *PropertyData; + + /* How large a buffer do we need? */ + rc = SetupDiGetDeviceRegistryProperty(InfoSet, DevData, Property, + NULL, NULL, 0, &dwReqLen); + if (!rc && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + { + LogRelFunc(("Failed to query buffer size, error %ld\n", GetLastError())); + return NULL; + } + + PropertyData = RTMemAlloc(dwReqLen); + if (!PropertyData) + return NULL; + + /* Get the actual property data. */ + rc = SetupDiGetDeviceRegistryProperty(InfoSet, DevData, Property, + NULL, (PBYTE)PropertyData, dwReqLen, &dwReqLen); + if (!rc) + { + LogRelFunc(("Failed to get property data, error %ld\n", GetLastError())); + RTMemFree(PropertyData); + return NULL; + } + return PropertyData; +} + +/* Given a HDEVINFO and SP_DEVICE_INTERFACE_DATA, get the interface detail data and optionally device info data. */ +static PSP_DEVICE_INTERFACE_DETAIL_DATA usbLibGetDevDetail(HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData, PSP_DEVINFO_DATA DevInfoData) +{ + BOOL rc; + DWORD dwReqLen; + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData; + + rc = SetupDiGetDeviceInterfaceDetail(InfoSet, InterfaceData, NULL, 0, &dwReqLen, DevInfoData); + if (!rc && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + { + LogRelFunc(("Failed to get interface detail size, error %ld\n", GetLastError())); + return NULL; + } + + DetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAlloc(dwReqLen); + if (!DetailData) + return NULL; + + memset(DetailData, 0, dwReqLen); + DetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + rc = SetupDiGetDeviceInterfaceDetail(InfoSet, InterfaceData, DetailData, dwReqLen, &dwReqLen, DevInfoData); + if (!rc) + { + LogRelFunc(("Failed to get interface detail, error %ld\n", GetLastError())); + RTMemFree(DetailData); + } + + return DetailData; +} + +/* Given a hub's PnP device instance, find its device path (file name). */ +static LPCSTR usbLibGetHubPathFromInstanceID(LPCSTR InstanceID) +{ + HDEVINFO InfoSet; + SP_DEVICE_INTERFACE_DATA InterfaceData; + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData; + BOOL rc; + LPSTR DevicePath = NULL; + + /* Enumerate the DevInst's USB hub interface. */ + InfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_HUB, InstanceID, NULL, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (InfoSet == INVALID_HANDLE_VALUE) + { + LogRelFunc(("Failed to get interface for InstID %se, error %ld\n", InstanceID, GetLastError())); + return NULL; + } + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + rc = SetupDiEnumDeviceInterfaces(InfoSet, 0, &GUID_DEVINTERFACE_USB_HUB, 0, &InterfaceData); + if (!rc) + { + DWORD dwErr = GetLastError(); + + /* The parent device might not be a hub; that is valid, ignore such errors. */ + if (dwErr != ERROR_NO_MORE_ITEMS) + LogRelFunc(("Failed to get interface data for InstID %s, error %ld\n", InstanceID, dwErr)); + SetupDiDestroyDeviceInfoList(InfoSet); + return NULL; + } + + DetailData = usbLibGetDevDetail(InfoSet, &InterfaceData, NULL); + if (!DetailData) + { + SetupDiDestroyDeviceInfoList(InfoSet); + return NULL; + } + + /* Copy the device path out of the interface detail. */ + DevicePath = RTStrDup(DetailData->DevicePath); + RTMemFree(DetailData); + SetupDiDestroyDeviceInfoList(InfoSet); + + return DevicePath; +} + + +/* Use the Configuration Manager (CM) to get a devices's parent given its DEVINST and + * turn it into a PnP device instance ID string. + */ +static LPCSTR usbLibGetParentInstanceID(DEVINST DevInst) +{ + LPSTR InstanceID; + DEVINST ParentInst; + ULONG ulReqChars; + ULONG ulReqBytes; + CONFIGRET cr; + + /* First get the parent DEVINST. */ + cr = CM_Get_Parent(&ParentInst, DevInst, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get parent instance, error %ld\n", GetLastError())); + return NULL; + } + + /* Then convert it to the instance ID string. */ + cr = CM_Get_Device_ID_Size(&ulReqChars, ParentInst, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get device ID size (DevInst=%X), error %ld\n", DevInst, GetLastError())); + return NULL; + } + + /* CM_Get_Device_ID_Size gives us the size in characters without terminating null. */ + ulReqBytes = (ulReqChars + 1) * sizeof(char); + InstanceID = (LPSTR)RTMemAlloc(ulReqBytes); + if (!InstanceID) + return NULL; + + cr = CM_Get_Device_ID(ParentInst, InstanceID, ulReqBytes, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get device ID (DevInst=%X), error %ld\n", DevInst, GetLastError())); + RTMemFree(InstanceID); + return NULL; + } + + return InstanceID; +} + +/* Process a single USB device that's being enumerated and grab its hub-specific data. */ +static int usbLibDevGetDevice(LPCSTR lpcszHubFile, ULONG iPort, LPCSTR lpcszLocation, LPCSTR lpcszDriverKey, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + HANDLE HubDevice; + BYTE abConBuf[sizeof(USB_NODE_CONNECTION_INFORMATION_EX)]; + PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo = PUSB_NODE_CONNECTION_INFORMATION_EX(abConBuf); + int rc = VINF_SUCCESS; + DWORD cbReturned = 0; + + /* Validate inputs. */ + if ((iPort < 1) || (iPort > 255)) + { + LogRelFunc(("Port index out of range (%u)\n", iPort)); + return VERR_INVALID_PARAMETER; + } + if (!lpcszHubFile) + { + LogRelFunc(("Hub path is NULL!\n")); + return VERR_INVALID_PARAMETER; + } + if (!lpcszLocation) + { + LogRelFunc(("Location NULL!\n")); + return VERR_INVALID_PARAMETER; + } + + /* Try opening the hub file so we can send IOCTLs to it. */ + HubDevice = CreateFile(lpcszHubFile, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (HubDevice == INVALID_HANDLE_VALUE) + { + LogRelFunc(("Failed to open hub `%s' (dwErr=%u)\n", lpcszHubFile, GetLastError())); + return VERR_FILE_NOT_FOUND; + } + + /* The shenanigans with abConBuf are due to USB_NODE_CONNECTION_INFORMATION_EX + * containing a zero-sized array, triggering compiler warnings. + */ + memset(pConInfo, 0, sizeof(abConBuf)); + pConInfo->ConnectionIndex = iPort; + + /* We expect that IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX is always available + * on any supported Windows version and hardware. + * NB: IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 is Win8 and later only. + */ + if (!DeviceIoControl(HubDevice, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, + pConInfo, sizeof(abConBuf), pConInfo, sizeof(abConBuf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRel(("IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX failed (dwErr=%u) on hub %s, port %d\n", dwErr, lpcszHubFile, iPort)); + AssertMsg(dwErr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed dwErr (%u)\n", dwErr)); + CloseHandle(HubDevice); + return VERR_GENERAL_FAILURE; + } + + if (pConInfo->ConnectionStatus != DeviceConnected) + { + /* Ignore this, can't do anything with it. */ + LogFunc(("Device is not connected, skipping.\n")); + CloseHandle(HubDevice); + return VINF_SUCCESS; + } + + if (pConInfo->DeviceIsHub) + { + /* We're ignoring hubs, just skip this. */ + LogFunc(("Device is a hub, skipping.\n")); + CloseHandle(HubDevice); + return VINF_SUCCESS; + } + + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + PVBOXUSB_STRING_DR_ENTRY pList = NULL; + rc = usbLibDevCfgDrGet(HubDevice, lpcszHubFile, iPort, 0, &pCfgDr); + if (pCfgDr) + { + rc = usbLibDevStrDrEntryGetAll(HubDevice, lpcszHubFile, iPort, &pConInfo->DeviceDescriptor, pCfgDr, &pList); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertRC(rc); // this can fail if device suspended +#endif + } + + /* At this point we're done with the hub device. */ + CloseHandle(HubDevice); + + PUSBDEVICE pDev = (PUSBDEVICE)RTMemAllocZ(sizeof (*pDev)); + if (RT_LIKELY(pDev)) + { + rc = usbLibDevPopulate(pDev, pConInfo, iPort, lpcszLocation, lpcszDriverKey, lpcszHubFile, pList); + if (RT_SUCCESS(rc)) + { + pDev->pNext = *ppDevs; + *ppDevs = pDev; + ++*pcDevs; + } + else + RTMemFree(pDev); + } + else + rc = VERR_NO_MEMORY; + + if (pCfgDr) + usbLibDevCfgDrFree(pCfgDr); + if (pList) + usbLibDevStrDrEntryFreeList(pList); + + return rc; +} + + +/* + * Enumerate the USB devices in the host system. Since we do not care about the hierarchical + * structure of root hubs, other hubs, and devices, we just ask the USB PnP enumerator to + * give us all it has. This includes hubs (though not root hubs), as well as multiple child + * interfaces of multi-interface USB devices, which we filter out. It also includes USB + * devices with no driver, which is notably something we cannot get by enumerating via + * GUID_DEVINTERFACE_USB_DEVICE. + * + * This approach also saves us some trouble relative to enumerating devices via hub IOCTLs and + * then hunting through the PnP manager to find them. Instead, we look up the device's parent + * which (for devices we're interested in) is always a hub, and that allows us to obtain + * USB-specific data (descriptors, speeds, etc.) when combined with the devices PnP "address" + * (USB port on parent hub). + * + * NB: Every USB device known to the Windows PnP Manager will have a device instance ID. Typically + * it also has a DriverKey but only if it has a driver installed. Hence we ignore the DriverKey, at + * least prior to capturing (once VBoxUSB.sys is installed, a DriverKey must by definition be + * present). Also note that the device instance ID changes for captured devices since we change + * their USB VID/PID, though it is unique at any given point. + * + * The location information should be a reliable way of identifying a device and does not change + * with driver installs, capturing, etc. USB device location information is only available on + * Windows Vista and later; earlier Windows version had no reliable way of cross-referencing the + * USB IOCTL and PnP Manager data. + */ +static int usbLibEnumDevices(PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + HDEVINFO InfoSet; + DWORD DeviceIndex; + LPDWORD Address; + SP_DEVINFO_DATA DeviceData; + LPCSTR ParentInstID; + LPCSTR HubPath = NULL; + LPCSTR Location; + LPCSTR DriverKey; + + /* Ask the USB PnP enumerator for all it has. */ + InfoSet = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT); + + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + DeviceIndex = 0; + + /* Enumerate everything in the info set. */ + while (SetupDiEnumDeviceInfo(InfoSet, DeviceIndex, &DeviceData)) + { + /* Use the CM API to get the parent instance ID. */ + ParentInstID = usbLibGetParentInstanceID(DeviceData.DevInst); + + /* Now figure out the hub's file path fron the instance ID, if there is one. */ + if (ParentInstID) + HubPath = usbLibGetHubPathFromInstanceID(ParentInstID); + + /* If there's no hub interface on the parent, then this might be a child + * device of a multi-interface device. Either way, we're not interested. + */ + if (HubPath) + { + /* The location information uniquely identifies the USB device, (hub/port). */ + Location = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_LOCATION_PATHS); + + /* The software key aka DriverKey. This will be NULL for devices with no driver + * and allows us to distinguish between 'busy' (driver installed) and 'available' + * (no driver) devices. + */ + DriverKey = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_DRIVER); + + /* The device's PnP Manager "address" is the port number on the parent hub. */ + Address = (LPDWORD)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_ADDRESS); + if (Address && Location) /* NB: DriverKey may be NULL! */ + { + usbLibDevGetDevice(HubPath, *Address, Location, DriverKey, ppDevs, pcDevs); + } + RTMemFree((void *)HubPath); + + if (Location) + RTMemFree((void *)Location); + if (DriverKey) + RTMemFree((void *)DriverKey); + if (Address) + RTMemFree((void *)Address); + } + + /* Clean up after this device. */ + if (ParentInstID) + RTMemFree((void *)ParentInstID); + + ++DeviceIndex; + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + } + + if (InfoSet) + SetupDiDestroyDeviceInfoList(InfoSet); + + return VINF_SUCCESS; +} + +#else +static int usbLibDevGetDevices(PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + char CtlName[16]; + int rc = VINF_SUCCESS; + + for (int i = 0; i < 10; ++i) + { + RTStrPrintf(CtlName, sizeof(CtlName), "\\\\.\\HCD%d", i); + HANDLE hCtl = CreateFile(CtlName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hCtl != INVALID_HANDLE_VALUE) + { + char* lpszName; + rc = usbLibDevStrRootHubNameGet(hCtl, &lpszName); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibDevGetHubDevices(lpszName, ppDevs, pcDevs); + AssertRC(rc); + usbLibDevStrFree(lpszName); + } + CloseHandle(hCtl); + if (RT_FAILURE(rc)) + break; + } + } + return VINF_SUCCESS; +} +#endif + +static int usbLibMonDevicesCmp(PUSBDEVICE pDev, PVBOXUSB_DEV pDevInfo) +{ + int iDiff; + iDiff = strcmp(pDev->pszAddress, pDevInfo->szDriverRegName); + return iDiff; +} + +static int usbLibMonDevicesUpdate(PVBOXUSBGLOBALSTATE pGlobal, PUSBDEVICE pDevs, PVBOXUSB_DEV pDevInfos) +{ + + PUSBDEVICE pDevsHead = pDevs; + for (; pDevInfos; pDevInfos = pDevInfos->pNext) + { + for (pDevs = pDevsHead; pDevs; pDevs = pDevs->pNext) + { + if (usbLibMonDevicesCmp(pDevs, pDevInfos)) + continue; + + if (!pDevInfos->szDriverRegName[0]) + { + AssertFailed(); + break; + } + + USBSUP_GETDEV Dev = {0}; + HANDLE hDev = CreateFile(pDevInfos->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL); + if (hDev == INVALID_HANDLE_VALUE) + { + AssertFailed(); + break; + } + + DWORD cbReturned = 0; + if (!DeviceIoControl(hDev, SUPUSB_IOCTL_GET_DEVICE, &Dev, sizeof (Dev), &Dev, sizeof (Dev), &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + /* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */ + AsserFailed(); +#endif + LogRelFunc(("SUPUSB_IOCTL_GET_DEVICE failed on '%s' (dwErr=%u)!\n", pDevInfos->szName, dwErr)); + CloseHandle(hDev); + break; + } + + /* we must not close the handle until we request for the device state from the monitor to ensure + * the device handle returned by the device driver does not disappear */ + Assert(Dev.hDevice); + USBSUP_GETDEV_MON MonInfo; + HVBOXUSBDEVUSR hDevice = Dev.hDevice; + if (!DeviceIoControl(pGlobal->hMonitor, SUPUSBFLT_IOCTL_GET_DEVICE, &hDevice, sizeof (hDevice), &MonInfo, sizeof (MonInfo), &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + /* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */ + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_GET_DEVICE failed for '%s' (hDevice=%p, dwErr=%u)!\n", pDevInfos->szName, hDevice, dwErr)); + CloseHandle(hDev); + break; + } + + CloseHandle(hDev); + + /* success!! update device info */ + /* ensure the state returned is valid */ + Assert( MonInfo.enmState == USBDEVICESTATE_USED_BY_HOST + || MonInfo.enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE + || MonInfo.enmState == USBDEVICESTATE_UNUSED + || MonInfo.enmState == USBDEVICESTATE_HELD_BY_PROXY + || MonInfo.enmState == USBDEVICESTATE_USED_BY_GUEST); + pDevs->enmState = MonInfo.enmState; + + if (pDevs->enmState != USBDEVICESTATE_USED_BY_HOST) + { + /* only set the interface name if device can be grabbed */ + RTStrFree(pDevs->pszAltAddress); + pDevs->pszAltAddress = (char*)pDevs->pszAddress; + pDevs->pszAddress = RTStrDup(pDevInfos->szName); + } +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + else + { + /* dbg breakpoint */ + AssertFailed(); + } +#endif + + /* we've found the device, break in any way */ + break; + } + } + + return VINF_SUCCESS; +} + +static int usbLibGetDevices(PVBOXUSBGLOBALSTATE pGlobal, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + *ppDevs = NULL; + *pcDevs = 0; + + LogRelFunc(("Starting USB device enumeration\n")); +#ifdef VBOX_WITH_NEW_USB_ENUM + int rc = usbLibEnumDevices(ppDevs, pcDevs); +#else + int rc = usbLibDevGetDevices(ppDevs, pcDevs); +#endif + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + PVBOXUSB_DEV pDevInfos = NULL; + uint32_t cDevInfos = 0; +#ifdef VBOX_WITH_NEW_USB_ENUM + rc = usbLibEnumVUsbDevices(&pDevInfos, &cDevInfos); +#else + rc = usbLibVuGetDevices(&pDevInfos, &cDevInfos); +#endif + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibMonDevicesUpdate(pGlobal, *ppDevs, pDevInfos); + AssertRC(rc); + usbLibVuFreeDevices(pDevInfos); + } + + LogRelFunc(("Found %u USB devices, %u captured\n", *pcDevs, cDevInfos)); + return VINF_SUCCESS; + } + return rc; +} + +AssertCompile(INFINITE == RT_INDEFINITE_WAIT); +static int usbLibStateWaitChange(PVBOXUSBGLOBALSTATE pGlobal, RTMSINTERVAL cMillies) +{ + HANDLE ahEvents[] = {pGlobal->hNotifyEvent, pGlobal->hInterruptEvent}; + DWORD dwResult = WaitForMultipleObjects(RT_ELEMENTS(ahEvents), ahEvents, + FALSE, /* BOOL bWaitAll */ + cMillies); + + switch (dwResult) + { + case WAIT_OBJECT_0: + return VINF_SUCCESS; + case WAIT_OBJECT_0 + 1: + return VERR_INTERRUPTED; + case WAIT_TIMEOUT: + return VERR_TIMEOUT; + default: + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("WaitForMultipleObjects failed, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + } +} + +AssertCompile(RT_INDEFINITE_WAIT == INFINITE); +AssertCompile(sizeof (RTMSINTERVAL) == sizeof (DWORD)); +USBLIB_DECL(int) USBLibWaitChange(RTMSINTERVAL msWaitTimeout) +{ + return usbLibStateWaitChange(&g_VBoxUsbGlobal, msWaitTimeout); +} + +static int usbLibInterruptWaitChange(PVBOXUSBGLOBALSTATE pGlobal) +{ + BOOL fRc = SetEvent(pGlobal->hInterruptEvent); + if (!fRc) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetEvent failed, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + return VINF_SUCCESS; +} + +USBLIB_DECL(int) USBLibInterruptWaitChange(void) +{ + return usbLibInterruptWaitChange(&g_VBoxUsbGlobal); +} + +/* +USBLIB_DECL(bool) USBLibHasPendingDeviceChanges(void) +{ + int rc = USBLibWaitChange(0); + return rc == VINF_SUCCESS; +} +*/ + +USBLIB_DECL(int) USBLibGetDevices(PUSBDEVICE *ppDevices, uint32_t *pcbNumDevices) +{ + Assert(g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE); + return usbLibGetDevices(&g_VBoxUsbGlobal, ppDevices, pcbNumDevices); +} + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + USBSUP_FLTADDOUT FltAddRc; + DWORD cbReturned = 0; + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return NULL; + } + + Log(("usblibInsertFilter: Manufacturer=%s Product=%s Serial=%s\n", + USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "")); + + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_ADD_FILTER, + (LPVOID)pFilter, sizeof(*pFilter), + &FltAddRc, sizeof(FltAddRc), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_ADD_FILTER failed (dwErr=%u)!\n", dwErr)); + return NULL; + } + + if (RT_FAILURE(FltAddRc.rc)) + { + AssertFailed(); + LogRelFunc(("Adding a USB filter failed with rc=%d!\n", FltAddRc.rc)); + return NULL; + } + + LogRel(("Added USB filter (ID=%u, type=%d) for device %04X:%04X rev %04X, c/s/p %02X/%02X/%02X, Manufacturer=`%s' Product=`%s' Serial=`%s'\n", FltAddRc.uId, USBFilterGetFilterType(pFilter), + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "")); + + return (void *)FltAddRc.uId; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + uintptr_t uId; + DWORD cbReturned = 0; + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return; + } + + Log(("usblibRemoveFilter %p\n", pvId)); + + uId = (uintptr_t)pvId; + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_REMOVE_FILTER, &uId, sizeof(uId), NULL, 0,&cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_REMOVE_FILTER failed (dwErr=%u)!\n", dwErr)); + } + else + LogRel(("Removed USB filter ID=%u\n", uId)); +} + +USBLIB_DECL(int) USBLibRunFilters(void) +{ + DWORD cbReturned = 0; + + Assert(g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE); + + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_RUN_FILTERS, + NULL, 0, + NULL, 0, + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_RUN_FILTERS failed (dwErr=%u)!\n", dwErr)); + return RTErrConvertFromWin32(dwErr); + } + + return VINF_SUCCESS; +} + + +static VOID CALLBACK usbLibTimerCallback(__in PVOID lpParameter, __in BOOLEAN TimerOrWaitFired) RT_NOTHROW_DEF +{ + RT_NOREF2(lpParameter, TimerOrWaitFired); + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); +} + +static void usbLibOnDeviceChange(void) +{ + /* we're getting series of events like that especially on device re-attach + * (i.e. first for device detach and then for device attach) + * unfortunately the event does not tell us what actually happened. + * To avoid extra notifications, we delay the SetEvent via a timer + * and update the timer if additional notification comes before the timer fires + * */ + if (g_VBoxUsbGlobal.hTimer) + { + if (!DeleteTimerQueueTimer(g_VBoxUsbGlobal.hTimerQueue, g_VBoxUsbGlobal.hTimer, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(dwErr == ERROR_IO_PENDING, ("DeleteTimerQueueTimer failed, dwErr (%u)\n", dwErr)); + } + } + + if (!CreateTimerQueueTimer(&g_VBoxUsbGlobal.hTimer, g_VBoxUsbGlobal.hTimerQueue, + usbLibTimerCallback, + NULL, + 500, /* ms*/ + 0, + WT_EXECUTEONLYONCE)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateTimerQueueTimer failed, dwErr (%u)\n", dwErr)); + + /* call it directly */ + usbLibTimerCallback(NULL, FALSE); + } +} + +static LRESULT CALLBACK usbLibWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DEVICECHANGE: + if (wParam == DBT_DEVNODES_CHANGED) + { + /* we notify change any device arivals/removals on the system + * and let the client decide whether the usb change actually happened + * so far this is more clean than reporting events from the Monitor + * because monitor sees only PDO arrivals/removals, + * and by the time PDO is created, device can not + * be yet started and fully functional, + * so usblib won't be able to pick it up + * */ + + usbLibOnDeviceChange(); + } + break; + case WM_DESTROY: + { + PostQuitMessage(0); + return 0; + } + } + return DefWindowProc (hwnd, uMsg, wParam, lParam); +} + +/** @todo r=bird: Use an IPRT thread!! */ +static DWORD WINAPI usbLibMsgThreadProc(__in LPVOID lpParameter) RT_NOTHROW_DEF +{ + static LPCSTR s_szVBoxUsbWndClassName = "VBoxUsbLibClass"; + const HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); + RT_NOREF1(lpParameter); + + Assert(g_VBoxUsbGlobal.hWnd == NULL); + g_VBoxUsbGlobal.hWnd = NULL; + + /* + * Register the Window Class and create the hidden window. + */ + WNDCLASS wc; + wc.style = 0; + wc.lpfnWndProc = usbLibWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = sizeof(void *); + wc.hInstance = hInstance; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = s_szVBoxUsbWndClassName; + ATOM atomWindowClass = RegisterClass(&wc); + if (atomWindowClass != 0) + g_VBoxUsbGlobal.hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, + s_szVBoxUsbWndClassName, s_szVBoxUsbWndClassName, + WS_POPUPWINDOW, + -200, -200, 100, 100, NULL, NULL, hInstance, NULL); + else + AssertMsgFailed(("RegisterClass failed, last error %u\n", GetLastError())); + + /* + * Signal the creator thread. + */ + ASMCompilerBarrier(); + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); + + if (g_VBoxUsbGlobal.hWnd) + { + /* Make sure it's really hidden. */ + SetWindowPos(g_VBoxUsbGlobal.hWnd, HWND_TOPMOST, -200, -200, 0, 0, + SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); + + /* + * The message pump. + */ + MSG msg; + BOOL fRet; + while ((fRet = GetMessage(&msg, NULL, 0, 0)) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + Assert(fRet >= 0); + } + + if (atomWindowClass != NULL) + UnregisterClass(s_szVBoxUsbWndClassName, hInstance); + + return 0; +} + + +/** + * Initialize the USB library + * + * @returns VBox status code. + */ +USBLIB_DECL(int) USBLibInit(void) +{ + int rc = VERR_GENERAL_FAILURE; + + Log(("usbproxy: usbLibInit\n")); + + RT_ZERO(g_VBoxUsbGlobal); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + + /* + * Create the notification and interrupt event before opening the device. + */ + g_VBoxUsbGlobal.hNotifyEvent = CreateEvent(NULL, /* LPSECURITY_ATTRIBUTES lpEventAttributes */ + FALSE, /* BOOL bManualReset */ + FALSE, /* set to false since it will be initially used for notification thread startup sync */ + NULL /* LPCTSTR lpName */); + if (g_VBoxUsbGlobal.hNotifyEvent) + { + g_VBoxUsbGlobal.hInterruptEvent = CreateEvent(NULL, /* LPSECURITY_ATTRIBUTES lpEventAttributes */ + FALSE, /* BOOL bManualReset */ + FALSE, /* BOOL bInitialState */ + NULL /* LPCTSTR lpName */); + if (g_VBoxUsbGlobal.hInterruptEvent) + { + /* + * Open the USB monitor device, starting if needed. + */ + g_VBoxUsbGlobal.hMonitor = CreateFile(USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM, + NULL); + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + HRESULT hr = VBoxDrvCfgSvcStart(USBMON_SERVICE_NAME_W); + if (hr == S_OK) + { + g_VBoxUsbGlobal.hMonitor = CreateFile(USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM, + NULL); + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("CreateFile failed (dwErr=%u) for `%s'\n", dwErr, USBMON_DEVICE_NAME)); + rc = VERR_FILE_NOT_FOUND; + } + } + } + + if (g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE) + { + /* + * Check the USB monitor version. + * + * Drivers are backwards compatible within the same major + * number. We consider the minor version number this library + * is compiled with to be the minimum required by the driver. + * This is by reasoning that the library uses the full feature + * set of the driver it's written for. + */ + USBSUP_VERSION Version = {0}; + DWORD cbReturned = 0; + if (DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_GET_VERSION, + NULL, 0, + &Version, sizeof (Version), + &cbReturned, NULL)) + { + if ( Version.u32Major == USBMON_MAJOR_VERSION +#if USBMON_MINOR_VERSION != 0 + && Version.u32Minor >= USBMON_MINOR_VERSION +#endif + ) + { + /* + * We can not use USB Mon for reliable device add/remove tracking + * since once USB Mon is notified about PDO creation and/or IRP_MN_START_DEVICE, + * the function device driver may still do some initialization, which might result in + * notifying too early. + * Instead we use WM_DEVICECHANGE + DBT_DEVNODES_CHANGED to make Windows notify us about + * device arivals/removals. + * Since WM_DEVICECHANGE is a window message, create a dedicated thread to be used for WndProc and stuff. + * The thread would create a window, track windows messages and call usbLibOnDeviceChange on WM_DEVICECHANGE arrival. + * See comments in usbLibOnDeviceChange function for detail about using the timer queue. + */ + g_VBoxUsbGlobal.hTimerQueue = CreateTimerQueue(); + if (g_VBoxUsbGlobal.hTimerQueue) + { +/** @todo r=bird: Which lunatic used CreateThread here?!? + * Only the CRT uses CreateThread. */ + g_VBoxUsbGlobal.hThread = CreateThread( + NULL, /*__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, */ + 0, /*__in SIZE_T dwStackSize, */ + usbLibMsgThreadProc, /*__in LPTHREAD_START_ROUTINE lpStartAddress,*/ + NULL, /*__in_opt LPVOID lpParameter,*/ + 0, /*__in DWORD dwCreationFlags,*/ + NULL /*__out_opt LPDWORD lpThreadId*/ + ); + if (g_VBoxUsbGlobal.hThread) + { + DWORD dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hNotifyEvent, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); + if (g_VBoxUsbGlobal.hWnd) + { + /* + * We're DONE! + * + * Just ensure that the event is set so the + * first "wait change" request is processed. + */ + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); + return VINF_SUCCESS; + } + + dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hThread, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); + BOOL fRc = CloseHandle(g_VBoxUsbGlobal.hThread); NOREF(fRc); + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(fRc, ("CloseHandle for hThread failed (dwErr=%u)\n", dwErr)); + g_VBoxUsbGlobal.hThread = INVALID_HANDLE_VALUE; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateThread failed, (dwErr=%u)\n", dwErr)); + rc = VERR_GENERAL_FAILURE; + } + + DeleteTimerQueueEx(g_VBoxUsbGlobal.hTimerQueue, INVALID_HANDLE_VALUE /* see term */); + g_VBoxUsbGlobal.hTimerQueue = NULL; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateTimerQueue failed (dwErr=%u)\n", dwErr)); + } + } + else + { + LogRelFunc(("USB Monitor driver version mismatch! driver=%u.%u library=%u.%u\n", + Version.u32Major, Version.u32Minor, USBMON_MAJOR_VERSION, USBMON_MINOR_VERSION)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + rc = VERR_VERSION_MISMATCH; + } + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRelFunc(("SUPUSBFLT_IOCTL_GET_VERSION failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + rc = VERR_VERSION_MISMATCH; + } + + CloseHandle(g_VBoxUsbGlobal.hMonitor); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + } + else + { + LogRelFunc(("USB Service not found\n")); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + rc = VERR_FILE_NOT_FOUND; + } + + CloseHandle(g_VBoxUsbGlobal.hInterruptEvent); + g_VBoxUsbGlobal.hInterruptEvent = NULL; + } + else + { + AssertMsgFailed(("CreateEvent for InterruptEvent failed (dwErr=%u)\n", GetLastError())); + rc = VERR_GENERAL_FAILURE; + } + + CloseHandle(g_VBoxUsbGlobal.hNotifyEvent); + g_VBoxUsbGlobal.hNotifyEvent = NULL; + } + else + { + AssertMsgFailed(("CreateEvent for NotifyEvent failed (dwErr=%u)\n", GetLastError())); + rc = VERR_GENERAL_FAILURE; + } + + /* since main calls us even if USBLibInit fails, + * we use hMonitor == INVALID_HANDLE_VALUE as a marker to indicate whether the lib is inited */ + + Assert(RT_FAILURE(rc)); + return rc; +} + + +/** + * Terminate the USB library + * + * @returns VBox status code. + */ +USBLIB_DECL(int) USBLibTerm(void) +{ + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + Assert(g_VBoxUsbGlobal.hInterruptEvent == NULL); + Assert(g_VBoxUsbGlobal.hNotifyEvent == NULL); + return VINF_SUCCESS; + } + + BOOL fRc; + fRc = PostMessage(g_VBoxUsbGlobal.hWnd, WM_CLOSE, 0, 0); + AssertMsg(fRc, ("PostMessage for hWnd failed (dwErr=%u)\n", GetLastError())); + + if (g_VBoxUsbGlobal.hThread != NULL) + { + DWORD dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hThread, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); NOREF(dwResult); + fRc = CloseHandle(g_VBoxUsbGlobal.hThread); + AssertMsg(fRc, ("CloseHandle for hThread failed (dwErr=%u)\n", GetLastError())); + } + + if (g_VBoxUsbGlobal.hTimer) + { + fRc = DeleteTimerQueueTimer(g_VBoxUsbGlobal.hTimerQueue, g_VBoxUsbGlobal.hTimer, + INVALID_HANDLE_VALUE); /* <-- to block until the timer is completed */ + AssertMsg(fRc, ("DeleteTimerQueueTimer failed (dwErr=%u)\n", GetLastError())); + } + + if (g_VBoxUsbGlobal.hTimerQueue) + { + fRc = DeleteTimerQueueEx(g_VBoxUsbGlobal.hTimerQueue, + INVALID_HANDLE_VALUE); /* <-- to block until all timers are completed */ + AssertMsg(fRc, ("DeleteTimerQueueEx failed (dwErr=%u)\n", GetLastError())); + } + + fRc = CloseHandle(g_VBoxUsbGlobal.hMonitor); + AssertMsg(fRc, ("CloseHandle for hMonitor failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(g_VBoxUsbGlobal.hInterruptEvent); + AssertMsg(fRc, ("CloseHandle for hInterruptEvent failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hInterruptEvent = NULL; + + fRc = CloseHandle(g_VBoxUsbGlobal.hNotifyEvent); + AssertMsg(fRc, ("CloseHandle for hNotifyEvent failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hNotifyEvent = NULL; + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf new file mode 100644 index 00000000..d54bb101 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf @@ -0,0 +1,98 @@ +; $Id: VBoxUSBMon.inf $ +;; @file +; VBox USB Monitor driver - Installation file +; + +; +; Copyright (C) 2011-2023 Oracle and/or its affiliates. +; +; This file is part of VirtualBox base platform packages, as +; available from https://www.virtualbox.org. +; +; This program is free software; you can redistribute it and/or +; modify it under the terms of the GNU General Public License +; as published by the Free Software Foundation, in version 3 of the +; License. +; +; This program is distributed in the hope that it will be useful, but +; WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +; General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with this program; if not, see . +; +; The contents of this file may alternatively be used under the terms +; of the Common Development and Distribution License Version 1.0 +; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +; in the VirtualBox distribution, in which case the provisions of the +; CDDL are applicable instead of those of the GPL. +; +; You may elect to license modified versions of this file under the +; terms and conditions of either the GPL or the CDDL or both. +; +; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +; + +[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=VBoxUSBMon.cat + +[DestinationDirs] +DefaultDestDir = 12 + +[DefaultInstall@DOT-NT-ARCH@] +CopyFiles=VBoxUSBMon_CopyFiles + +[DefaultInstall@DOT-NT-ARCH@.Services] +AddService=VBoxUSBMon,0x00000002,VBoxUSBMon_Service,VBoxUSBMon_AddEventLog + +;; Cannot get this to work (same works fine for VBoxDrv): +;; [Manufacturer] +;; %ORACLE%=VBoxUSBMon@COMMA-NT-ARCH@ +;; +;; ; Models section (referenced by [Manufacturer]). +;; [VBoxUSBMon@DOT-NT-ARCH@] +;; %VBoxUSBMon.DRVDESC%=VBoxUSBMon_Install,root\VBoxUSBMon +;; +;; [VBoxUSBMon_Install@DOT-NT-ARCH@] +;; CopyFiles=VBoxUSBMon_CopyFiles +;; +;; [VBoxUSBMon_Install@DOT-NT-ARCH@.Services] +;; AddService=VBoxUSBMon,0x00000002,VBoxUSBMon_Service,VBoxUSBMon_AddEventLog + +[SourceDisksFiles] +VBoxUSBMon.sys=1 + +[SourceDisksNames] +1=%VBoxUSBMon.DSKDESC%, + +[VBoxUSBMon_CopyFiles] +VBoxUSBMon.sys + +[VBoxUSBMon_Service] +DisplayName = %VBoxUSBMon.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%\VBoxUSBMon.sys + +[VBoxUSBMon_AddEventLog] +AddReg = VBoxUSBMon_AddEventLogRegistry + +[VBoxUSBMon_AddEventLogRegistry] +HKR,,EventMessageFile,0x00020000,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,0x00010001,7 + +[Strings] +ORACLE = "Oracle Corporation" +VBoxUSBMon.SVCDESC = "VirtualBox USB Monitor Service" +VBoxUSBMon.DRVDESC = "VirtualBox USB Monitor Driver" +VBoxUSBMon.DSKDESC = "VirtualBox USB Monitor Driver Installation Disk" + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp new file mode 100644 index 00000000..23d4d088 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp @@ -0,0 +1,1763 @@ +/* $Id: VBoxUsbFlt.cpp $ */ +/** @file + * VBox USB Monitor Device Filtering functionality + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" +#include "../cmn/VBoxUsbTool.h" + +#include +#include +#include +#include +#include + +#include + +#pragma warning(disable : 4200) +#include "usbdi.h" +#pragma warning(default : 4200) +#include "usbdlib.h" +#include "VBoxUSBFilterMgr.h" +#include +#include +#include + + +/* We should be including ntifs.h but that's not as easy as it sounds. */ +extern "C" { +NTKERNELAPI PDEVICE_OBJECT IoGetDeviceAttachmentBaseRef(__in PDEVICE_OBJECT DeviceObject); +} + +/* + * state transitions: + * + * (we are not filtering this device ) + * ADDED --> UNCAPTURED ------------------------------->- + * | | + * | (we are filtering this device, | (the device is being + * | waiting for our device driver | re-plugged to perform + * | to pick it up) | capture-uncapture transition) + * |-> CAPTURING -------------------------------->|---> REPLUGGING ----- + * ^ | (device driver picked | | + * | | up the device) | (remove cased | (device is removed + * | ->---> CAPTURED ---------------------->| by "real" removal | the device info is removed form the list) + * | | |------------------->->--> REMOVED + * | | | + * |-----------<->---> USED_BY_GUEST ------->| + * | | + * |------------------------<- + * + * NOTE: the order of enums DOES MATTER!! + * Do not blindly modify!! as the code assumes the state is ordered this way. + */ +typedef enum +{ + VBOXUSBFLT_DEVSTATE_UNKNOWN = 0, + VBOXUSBFLT_DEVSTATE_REMOVED, + VBOXUSBFLT_DEVSTATE_REPLUGGING, + VBOXUSBFLT_DEVSTATE_ADDED, + VBOXUSBFLT_DEVSTATE_UNCAPTURED, + VBOXUSBFLT_DEVSTATE_CAPTURING, + VBOXUSBFLT_DEVSTATE_CAPTURED, + VBOXUSBFLT_DEVSTATE_USED_BY_GUEST, + VBOXUSBFLT_DEVSTATE_32BIT_HACK = 0x7fffffff +} VBOXUSBFLT_DEVSTATE; + +typedef struct VBOXUSBFLT_DEVICE +{ + LIST_ENTRY GlobalLe; + /* auxiliary list to be used for gathering devices to be re-plugged + * only thread that puts the device to the REPLUGGING state can use this list */ + LIST_ENTRY RepluggingLe; + /* Owning session. Each matched device has an owning session. */ + struct VBOXUSBFLTCTX *pOwner; + /* filter id - if NULL AND device has an owner - the filter is destroyed */ + uintptr_t uFltId; + /* true iff device is filtered with a one-shot filter */ + bool fIsFilterOneShot; + /* true if descriptors could not be read and only inferred from PnP Manager data */ + bool fInferredDesc; + /* The device state. If the non-owner session is requesting the state while the device is grabbed, + * the USBDEVICESTATE_USED_BY_HOST is returned. */ + VBOXUSBFLT_DEVSTATE enmState; + volatile uint32_t cRefs; + PDEVICE_OBJECT Pdo; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint16_t bPort; + uint8_t bClass; + uint8_t bSubClass; + uint8_t bProtocol; + char szSerial[MAX_USB_SERIAL_STRING]; + char szMfgName[MAX_USB_SERIAL_STRING]; + char szProduct[MAX_USB_SERIAL_STRING]; + WCHAR szLocationPath[768]; +#if 0 + char szDrvKeyName[512]; + BOOLEAN fHighSpeed; +#endif +} VBOXUSBFLT_DEVICE, *PVBOXUSBFLT_DEVICE; + +#define PVBOXUSBFLT_DEVICE_FROM_LE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, GlobalLe) ) ) +#define PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, RepluggingLe) ) ) +#define PVBOXUSBFLTCTX_FROM_LE(_pLe) ( (PVBOXUSBFLTCTX)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLTCTX, ListEntry) ) ) + +typedef struct VBOXUSBFLT_LOCK +{ + KSPIN_LOCK Lock; + KIRQL OldIrql; +} VBOXUSBFLT_LOCK, *PVBOXUSBFLT_LOCK; + +#define VBOXUSBFLT_LOCK_INIT() \ + KeInitializeSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock) +#define VBOXUSBFLT_LOCK_TERM() do { } while (0) +#define VBOXUSBFLT_LOCK_ACQUIRE() \ + KeAcquireSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, &g_VBoxUsbFltGlobals.Lock.OldIrql); +#define VBOXUSBFLT_LOCK_RELEASE() \ + KeReleaseSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, g_VBoxUsbFltGlobals.Lock.OldIrql); + + +typedef struct VBOXUSBFLT_BLDEV +{ + LIST_ENTRY ListEntry; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; +} VBOXUSBFLT_BLDEV, *PVBOXUSBFLT_BLDEV; + +#define PVBOXUSBFLT_BLDEV_FROM_LE(_pLe) ( (PVBOXUSBFLT_BLDEV)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_BLDEV, ListEntry) ) ) + +typedef struct VBOXUSBFLTGLOBALS +{ + LIST_ENTRY DeviceList; + LIST_ENTRY ContextList; + /* devices known to misbehave */ + LIST_ENTRY BlackDeviceList; + VBOXUSBFLT_LOCK Lock; + /** Flag whether to force replugging a device we can't query descirptors from. + * Short term workaround for @bugref{9479}. */ + ULONG dwForceReplugWhenDevPopulateFails; +} VBOXUSBFLTGLOBALS, *PVBOXUSBFLTGLOBALS; +static VBOXUSBFLTGLOBALS g_VBoxUsbFltGlobals; + +static bool vboxUsbFltBlDevMatchLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice) +{ + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_BLDEV pDev = PVBOXUSBFLT_BLDEV_FROM_LE(pEntry); + if (pDev->idVendor != idVendor) + continue; + if (pDev->idProduct != idProduct) + continue; + if (pDev->bcdDevice != bcdDevice) + continue; + + return true; + } + return false; +} + +static NTSTATUS vboxUsbFltBlDevAddLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice) +{ + if (vboxUsbFltBlDevMatchLocked(idVendor, idProduct, bcdDevice)) + return STATUS_SUCCESS; + PVBOXUSBFLT_BLDEV pDev = (PVBOXUSBFLT_BLDEV)VBoxUsbMonMemAllocZ(sizeof (*pDev)); + if (!pDev) + { + AssertFailed(); + return STATUS_INSUFFICIENT_RESOURCES; + } + + pDev->idVendor = idVendor; + pDev->idProduct = idProduct; + pDev->bcdDevice = bcdDevice; + InsertHeadList(&g_VBoxUsbFltGlobals.BlackDeviceList, &pDev->ListEntry); + return STATUS_SUCCESS; +} + +static void vboxUsbFltBlDevClearLocked() +{ + PLIST_ENTRY pNext; + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList; + pEntry = pNext) + { + pNext = pEntry->Flink; + VBoxUsbMonMemFree(pEntry); + } +} + +static void vboxUsbFltBlDevPopulateWithKnownLocked() +{ + /* this one halts when trying to get string descriptors from it */ + vboxUsbFltBlDevAddLocked(0x5ac, 0x921c, 0x115); +} + + +DECLINLINE(void) vboxUsbFltDevRetain(PVBOXUSBFLT_DEVICE pDevice) +{ + Assert(pDevice->cRefs); + ASMAtomicIncU32(&pDevice->cRefs); +} + +static void vboxUsbFltDevDestroy(PVBOXUSBFLT_DEVICE pDevice) +{ + Assert(!pDevice->cRefs); + Assert(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED); + VBoxUsbMonMemFree(pDevice); +} + +DECLINLINE(void) vboxUsbFltDevRelease(PVBOXUSBFLT_DEVICE pDevice) +{ + uint32_t cRefs = ASMAtomicDecU32(&pDevice->cRefs); + Assert(cRefs < UINT32_MAX/2); + if (!cRefs) + { + vboxUsbFltDevDestroy(pDevice); + } +} + +static void vboxUsbFltDevOwnerSetLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot) +{ + ASSERT_WARN(!pDevice->pOwner, ("device 0x%p has an owner(0x%p)", pDevice, pDevice->pOwner)); + ++pContext->cActiveFilters; + pDevice->pOwner = pContext; + pDevice->uFltId = uFltId; + pDevice->fIsFilterOneShot = fIsOneShot; +} + +static void vboxUsbFltDevOwnerClearLocked(PVBOXUSBFLT_DEVICE pDevice) +{ + ASSERT_WARN(pDevice->pOwner, ("no owner for device 0x%p", pDevice)); + --pDevice->pOwner->cActiveFilters; + ASSERT_WARN(pDevice->pOwner->cActiveFilters < UINT32_MAX/2, ("cActiveFilters (%d)", pDevice->pOwner->cActiveFilters)); + pDevice->pOwner = NULL; + pDevice->uFltId = 0; +} + +static void vboxUsbFltDevOwnerUpdateLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot) +{ + if (pDevice->pOwner != pContext) + { + if (pDevice->pOwner) + vboxUsbFltDevOwnerClearLocked(pDevice); + if (pContext) + vboxUsbFltDevOwnerSetLocked(pDevice, pContext, uFltId, fIsOneShot); + } + else if (pContext) + { + pDevice->uFltId = uFltId; + pDevice->fIsFilterOneShot = fIsOneShot; + } +} + +static PVBOXUSBFLT_DEVICE vboxUsbFltDevGetLocked(PDEVICE_OBJECT pPdo) +{ +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + for (PLIST_ENTRY pEntry2 = pEntry->Flink; + pEntry2 != &g_VBoxUsbFltGlobals.DeviceList; + pEntry2 = pEntry2->Flink) + { + PVBOXUSBFLT_DEVICE pDevice2 = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry2); + ASSERT_WARN( pDevice->idVendor != pDevice2->idVendor + || pDevice->idProduct != pDevice2->idProduct + || pDevice->bcdDevice != pDevice2->bcdDevice, ("duplicate devices in a list!!")); + } + } +#endif + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + ASSERT_WARN( pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST, + ("Invalid device state(%d) for device(0x%p) PDO(0x%p)", pDevice->enmState, pDevice, pDevice->Pdo)); + if (pDevice->Pdo == pPdo) + return pDevice; + } + return NULL; +} + +static NTSTATUS vboxUsbFltPdoReplug(PDEVICE_OBJECT pDo) +{ + LOG(("Replugging PDO(0x%p)", pDo)); + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_CYCLE_PORT, NULL, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("replugging PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Replugging PDO(0x%p) done with Status(0x%x)", pDo, Status)); + return Status; +} + +static bool vboxUsbFltDevCanBeCaptured(PVBOXUSBFLT_DEVICE pDevice) +{ + if (pDevice->bClass == USB_DEVICE_CLASS_HUB) + { + LOG(("device (0x%p), pdo (0x%p) is a hub, can not be captured", pDevice, pDevice->Pdo)); + return false; + } + return true; +} + +static PVBOXUSBFLTCTX vboxUsbFltDevMatchLocked(PVBOXUSBFLT_DEVICE pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot) +{ + *puId = 0; + *pfFilter = false; + *pfIsOneShot = false; + if (!vboxUsbFltDevCanBeCaptured(pDevice)) + { + LOG(("vboxUsbFltDevCanBeCaptured returned false")); + return NULL; + } + + USBFILTER DevFlt; + USBFilterInit(&DevFlt, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_VENDOR_ID, pDevice->idVendor, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PRODUCT_ID, pDevice->idProduct, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_REV, pDevice->bcdDevice, true); + + /* If we could not read a string descriptor, don't set the filter item at all. */ + if (pDevice->szMfgName[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_MANUFACTURER_STR, pDevice->szMfgName, true /*fMustBePresent*/, true /*fPurge*/); + if (pDevice->szProduct[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_PRODUCT_STR, pDevice->szProduct, true /*fMustBePresent*/, true /*fPurge*/); + if (pDevice->szSerial[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_SERIAL_NUMBER_STR, pDevice->szSerial, true /*fMustBePresent*/, true /*fPurge*/); + + /* If device descriptor had to be inferred from PnP Manager data, the class/subclass/protocol may be wrong. + * When Windows reports CompatibleIDs 'USB\Class_03&SubClass_00&Prot_00', the device descriptor might be + * reporting class 3 (HID), *or* the device descriptor might be reporting class 0 (specified by interface) + * and the device's interface reporting class 3. Ignore the class/subclass/protocol in such case, since + * we are more or less guaranteed to rely on VID/PID anyway. + * See @bugref{9479}. + */ + if (pDevice->fInferredDesc) + { + LOG(("Device descriptor was not read, only inferred; ignoring class/subclass/protocol!")); + } + else + { + LOG(("Setting filter class/subclass/protocol %02X/%02X/%02X\n", pDevice->bClass, pDevice->bSubClass, pDevice->bProtocol)); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_CLASS, pDevice->bClass, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_SUB_CLASS, pDevice->bSubClass, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_PROTOCOL, pDevice->bProtocol, true); + } + + /* If the port number looks valid, add it to the filter. */ + if (pDevice->bPort) + { + LOG(("Setting filter port %04X\n", pDevice->bPort)); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PORT, pDevice->bPort, true); + } + else + LOG(("Port number not known, ignoring!")); + + /* Run filters on the thing. */ + PVBOXUSBFLTCTX pOwner = VBoxUSBFilterMatchEx(&DevFlt, puId, fRemoveFltIfOneShot, pfFilter, pfIsOneShot); + USBFilterDelete(&DevFlt); + return pOwner; +} + +static void vboxUsbFltDevStateMarkReplugLocked(PVBOXUSBFLT_DEVICE pDevice) +{ + vboxUsbFltDevOwnerUpdateLocked(pDevice, NULL, 0, false); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REPLUGGING; +} + +static bool vboxUsbFltDevStateIsNotFiltered(PVBOXUSBFLT_DEVICE pDevice) +{ + return pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED; +} + +static bool vboxUsbFltDevStateIsFiltered(PVBOXUSBFLT_DEVICE pDevice) +{ + return pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; +} + +static uint16_t vboxUsbParseHexNumU16(WCHAR **ppStr) +{ + WCHAR *pStr = *ppStr; + WCHAR wc; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 4; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + wc = *pStr; + u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */ + num |= u << (12 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static uint8_t vboxUsbParseHexNumU8(WCHAR **ppStr) +{ + WCHAR *pStr = *ppStr; + WCHAR wc; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 2; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + wc = *pStr; + u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */ + num |= u << (4 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static bool vboxUsbParseHardwareID(WCHAR *pchIdStr, uint16_t *pVid, uint16_t *pPid, uint16_t *pRev) +{ +#define VID_PREFIX L"USB\\VID_" +#define PID_PREFIX L"&PID_" +#define REV_PREFIX L"&REV_" + + *pVid = *pPid = *pRev = 0xFFFF; + + /* The Hardware ID is in the format USB\VID_xxxx&PID_xxxx&REV_xxxx, with 'xxxx' + * being 16-bit hexadecimal numbers. The string is coming from the + * Windows PnP manager so OEMs should have no opportunity to mess it up. + */ + + if (wcsncmp(pchIdStr, VID_PREFIX, wcslen(VID_PREFIX))) + return false; + /* Point to the start of the vendor ID number and parse it. */ + pchIdStr += wcslen(VID_PREFIX); + *pVid = vboxUsbParseHexNumU16(&pchIdStr); + + if (wcsncmp(pchIdStr, PID_PREFIX, wcslen(PID_PREFIX))) + return false; + /* Point to the start of the product ID number and parse it. */ + pchIdStr += wcslen(PID_PREFIX); + *pPid = vboxUsbParseHexNumU16(&pchIdStr); + + /* The revision might not be there; the Windows documentation is not + * entirely clear if it will be always present for USB devices or not. + * If it's not there, still consider this a success. */ + if (wcsncmp(pchIdStr, REV_PREFIX, wcslen(REV_PREFIX))) + return true; + + /* Point to the start of the revision number and parse it. */ + pchIdStr += wcslen(REV_PREFIX); + *pRev = vboxUsbParseHexNumU16(&pchIdStr); + + return true; +#undef VID_PREFIX +#undef PID_PREFIX +#undef REV_PREFIX +} + +static bool vboxUsbParseCompatibleIDs(WCHAR *pchIdStr, uint8_t *pClass, uint8_t *pSubClass, uint8_t *pProt) +{ +#define CLS_PREFIX L"USB\\Class_" +#define SUB_PREFIX L"&SubClass_" +#define PRO_PREFIX L"&Prot_" + + *pClass = *pSubClass = *pProt = 0xFF; + + /* The Compatible IDs string is in the format USB\Class_xx&SubClass_xx&Prot_xx, + * with 'xx' being 8-bit hexadecimal numbers. Since this string is provided by the + * PnP manager and USB devices always report these as part of the basic USB device + * descriptor, we assume all three must be present. + */ + + if (wcsncmp(pchIdStr, CLS_PREFIX, wcslen(CLS_PREFIX))) + return false; + /* Point to the start of the device class and parse it. */ + pchIdStr += wcslen(CLS_PREFIX); + *pClass = vboxUsbParseHexNumU8(&pchIdStr); + + if (wcsncmp(pchIdStr, SUB_PREFIX, wcslen(SUB_PREFIX))) + return false; + + /* Point to the start of the subclass and parse it. */ + pchIdStr += wcslen(SUB_PREFIX); + *pSubClass = vboxUsbParseHexNumU8(&pchIdStr); + + if (wcsncmp(pchIdStr, PRO_PREFIX, wcslen(PRO_PREFIX))) + return false; + + /* Point to the start of the protocol and parse it. */ + pchIdStr += wcslen(PRO_PREFIX); + *pProt = vboxUsbParseHexNumU8(&pchIdStr); + + return true; +#undef CLS_PREFIX +#undef SUB_PREFIX +#undef PRO_PREFIX +} + +#define VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS 10000 + +static NTSTATUS vboxUsbFltDevPopulate(PVBOXUSBFLT_DEVICE pDevice, PDEVICE_OBJECT pDo /*, BOOLEAN bPopulateNonFilterProps*/) +{ + NTSTATUS Status; + USB_TOPOLOGY_ADDRESS TopoAddr; + PUSB_DEVICE_DESCRIPTOR pDevDr = 0; + ULONG ulResultLen; + DEVPROPTYPE type; + WCHAR wchPropBuf[256]; + uint16_t port; + bool rc; + + pDevice->Pdo = pDo; + + LOG(("Populating Device(0x%p) for PDO(0x%p)", pDevice, pDo)); + + pDevDr = (PUSB_DEVICE_DESCRIPTOR)VBoxUsbMonMemAllocZ(sizeof(*pDevDr)); + if (pDevDr == NULL) + { + WARN(("Failed to alloc mem for urb")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + do + { + pDevice->fInferredDesc = false; + Status = VBoxUsbToolGetDescriptor(pDo, pDevDr, sizeof(*pDevDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + uint16_t vid, pid, rev; + uint8_t cls, sub, prt; + + WARN(("getting device descriptor failed, Status (0x%x); falling back to IoGetDeviceProperty", Status)); + + /* Try falling back to IoGetDevicePropertyData. */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_HardwareIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* This just isn't our day. We have no idea what the device is. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_HardwareIds, Status (0x%x)", Status)); + break; + } + rc = vboxUsbParseHardwareID(wchPropBuf, &vid, &pid, &rev); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + break; + } + + /* Now grab the Compatible IDs to get the class/subclass/protocol. */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_CompatibleIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* We really kind of need these. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_CompatibleIds, Status (0x%x)", Status)); + break; + } + rc = vboxUsbParseCompatibleIDs(wchPropBuf, &cls, &sub, &prt); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + break; + } + + LOG(("Parsed HardwareID: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt)); + if (vid == 0xFFFF || pid == 0xFFFF) + break; + + LOG(("Successfully fell back to IoGetDeviceProperty result")); + pDevDr->idVendor = vid; + pDevDr->idProduct = pid; + pDevDr->bcdDevice = rev; + pDevDr->bDeviceClass = cls; + pDevDr->bDeviceSubClass = sub; + pDevDr->bDeviceProtocol = prt; + + /* The USB device class/subclass/protocol may not be accurate. We have to be careful when comparing + * and not take mismatches too seriously. + */ + pDevice->fInferredDesc = true; + } + + /* Query the location path. The path is purely a function of the physical device location + * and does not change if the device changes, and also does not change depending on + * whether the device is captured or not. + * NB: We ignore any additional strings and only look at the first one. + */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_LocationPaths, LOCALE_NEUTRAL, 0, sizeof(pDevice->szLocationPath), pDevice->szLocationPath, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* We do need this, but not critically. On Windows 7, we may get STATUS_OBJECT_NAME_NOT_FOUND. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_LocationPaths, Status (0x%x)", Status)); + } + else + { + LOG_STRW(pDevice->szLocationPath); + } + + // Disabled, but could be used as a fallback instead of IoGetDevicePropertyData; it should work even + // when this code is entered from the PnP IRP processing path. +#if 0 + { + HUB_DEVICE_CONFIG_INFO HubInfo; + + memset(&HubInfo, 0, sizeof(HubInfo)); + HubInfo.Version = 1; + HubInfo.Length = sizeof(HubInfo); + + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_DEVICE_CONFIG_INFO, &HubInfo, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_DEVICE_CONFIG_INFO for PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Querying hub device config info for PDO(0x%p) done with Status(0x%x)", pDo, Status)); + + if (Status == STATUS_SUCCESS) + { + uint16_t vid, pid, rev; + uint8_t cls, sub, prt; + + LOG(("Hub flags: %X\n", HubInfo.HubFlags)); + LOG_STRW(HubInfo.HardwareIds.Buffer); + LOG_STRW(HubInfo.CompatibleIds.Buffer); + if (HubInfo.DeviceDescription.Buffer) + LOG_STRW(HubInfo.DeviceDescription.Buffer); + + rc = vboxUsbParseHardwareID(HubInfo.HardwareIds.Buffer, &pid, &vid, &rev); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + } + + /* The CompatibleID the IOCTL gives is not always the same as what the PnP Manager uses + * (thanks, Microsoft). It might look like "USB\DevClass_00&SubClass_00&Prot_00" or like + * "USB\USB30_HUB". In such cases, we must consider the class/subclass/protocol + * information simply unavailable. + */ + rc = vboxUsbParseCompatibleIDs(HubInfo.CompatibleIds.Buffer, &cls, &sub, &prt); + if (!rc) + { + /* This is unfortunate but not fatal. */ + WARN(("Failed to parse Compatible ID")); + } + LOG(("Parsed HardwareID from IOCTL: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt)); + + ExFreePool(HubInfo.HardwareIds.Buffer); + ExFreePool(HubInfo.CompatibleIds.Buffer); + if (HubInfo.DeviceDescription.Buffer) + ExFreePool(HubInfo.DeviceDescription.Buffer); + } + } +#endif + + /* Query the topology address from the hub driver. This is not trivial to translate to the location + * path, but at least we can get the port number this way. + */ + memset(&TopoAddr, 0, sizeof(TopoAddr)); + Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_TOPOLOGY_ADDRESS, &TopoAddr, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_TOPOLOGY_ADDRESS for PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Querying topology address for PDO(0x%p) done with Status(0x%x)", pDo, Status)); + + port = 0; + if (Status == STATUS_SUCCESS) + { + uint16_t *pPort = &TopoAddr.RootHubPortNumber; + + /* The last non-zero port number is the one we're looking for. It might be on the + * root hub directly, or on some downstream hub. + */ + for (int i = 0; i < RT_ELEMENTS(TopoAddr.HubPortNumber) + 1; ++i) { + if (*pPort) + port = *pPort; + pPort++; + } + LOG(("PCI bus/dev/fn: %02X:%02X:%02X, parsed port: %u\n", TopoAddr.PciBusNumber, TopoAddr.PciDeviceNumber, TopoAddr.PciFunctionNumber, port)); + LOG(("RH port: %u, hub ports: %u/%u/%u/%u/%u/%u\n", TopoAddr.RootHubPortNumber, TopoAddr.HubPortNumber[0], + TopoAddr.HubPortNumber[1], TopoAddr.HubPortNumber[2], TopoAddr.HubPortNumber[3], TopoAddr.HubPortNumber[4], TopoAddr.HubPortNumber[5])); + + /* In the extremely unlikely case that the port number does not fit into 8 bits, force + * it to zero to indicate that we can't use it. + */ + if (port > 255) + port = 0; + } + + if (vboxUsbFltBlDevMatchLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)) + { + WARN(("found a known black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + Status = STATUS_UNSUCCESSFUL; + break; + } + + LOG(("Device pid=%x vid=%x rev=%x port=%x", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice, port)); + pDevice->bPort = port; + pDevice->idVendor = pDevDr->idVendor; + pDevice->idProduct = pDevDr->idProduct; + pDevice->bcdDevice = pDevDr->bcdDevice; + pDevice->bClass = pDevDr->bDeviceClass; + pDevice->bSubClass = pDevDr->bDeviceSubClass; + pDevice->bProtocol = pDevDr->bDeviceProtocol; + pDevice->szSerial[0] = 0; + pDevice->szMfgName[0] = 0; + pDevice->szProduct[0] = 0; + + /* If there are no strings, don't even try to get any string descriptors. */ + if (pDevDr->iSerialNumber || pDevDr->iManufacturer || pDevDr->iProduct) + { + int langId; + + Status = VBoxUsbToolGetLangID(pDo, &langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading language ID failed")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + } + break; + } + + if (pDevDr->iSerialNumber) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szSerial, sizeof (pDevice->szSerial), pDevDr->iSerialNumber, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading serial number failed")); + ASSERT_WARN(pDevice->szSerial[0] == '\0', ("serial is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + if (pDevDr->iManufacturer) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szMfgName, sizeof (pDevice->szMfgName), pDevDr->iManufacturer, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading manufacturer name failed")); + ASSERT_WARN(pDevice->szMfgName[0] == '\0', ("szMfgName is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + if (pDevDr->iProduct) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szProduct, sizeof (pDevice->szProduct), pDevDr->iProduct, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading product name failed")); + ASSERT_WARN(pDevice->szProduct[0] == '\0', ("szProduct is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + LOG((": strings: '%s':'%s':'%s' (lang ID %x)", + pDevice->szMfgName, pDevice->szProduct, pDevice->szSerial, langId)); + } + + LOG(("Populating Device(0x%p) for PDO(0x%p) Succeeded", pDevice, pDo)); + Status = STATUS_SUCCESS; + } while (0); + + VBoxUsbMonMemFree(pDevDr); + LOG(("Populating Device(0x%p) for PDO(0x%p) Done, Status (0x%x)", pDevice, pDo, Status)); + return Status; +} + +static bool vboxUsbFltDevCheckReplugLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext) +{ + ASSERT_WARN(pContext, ("context is NULL!")); + + LOG(("Current context is (0x%p)", pContext)); + LOG(("Current Device owner is (0x%p)", pDevice->pOwner)); + + /* check if device is already replugging */ + if (pDevice->enmState <= VBOXUSBFLT_DEVSTATE_ADDED) + { + LOG(("Device (0x%p) is already replugging, return..", pDevice)); + /* it is, do nothing */ + ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING, + ("Device (0x%p) state is NOT REPLUGGING (%d)", pDevice, pDevice->enmState)); + return false; + } + + if (pDevice->pOwner && pContext != pDevice->pOwner) + { + LOG(("Device (0x%p) is owned by another context(0x%p), current is(0x%p)", pDevice, pDevice->pOwner, pContext)); + /* this device is owned by another context, we're not allowed to do anything */ + return false; + } + + uintptr_t uId = 0; + bool bNeedReplug = false; + bool fFilter = false; + bool fIsOneShot = false; + PVBOXUSBFLTCTX pNewOwner = vboxUsbFltDevMatchLocked(pDevice, &uId, + false, /* do not remove a one-shot filter */ + &fFilter, &fIsOneShot); + LOG(("Matching Info: Filter (0x%p), NewOwner(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pNewOwner, (int)fFilter, (int)fIsOneShot)); + if (pDevice->pOwner && pNewOwner && pDevice->pOwner != pNewOwner) + { + LOG(("Matching: Device (0x%p) is requested another owner(0x%p), current is(0x%p)", pDevice, pNewOwner, pDevice->pOwner)); + /* the device is owned by another owner, we can not change the owner here */ + return false; + } + + if (!fFilter) + { + LOG(("Matching: Device (0x%p) should NOT be filtered", pDevice)); + /* the device should NOT be filtered, check the current state */ + if (vboxUsbFltDevStateIsNotFiltered(pDevice)) + { + LOG(("Device (0x%p) is NOT filtered", pDevice)); + /* no changes */ + if (fIsOneShot) + { + ASSERT_WARN(pNewOwner, ("no new owner")); + LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId)); + /* remove a one-shot filter and keep the original filter data */ + int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId); + ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc)); + if (!pDevice->pOwner) + { + LOG(("Matching: updating the one-shot owner to (0x%p), fltId(0x%p)", pNewOwner, uId)); + /* update owner for one-shot if the owner is changed (i.e. assigned) */ + vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, true); + } + else + { + LOG(("Matching: device already has owner (0x%p) assigned", pDevice->pOwner)); + } + } + else + { + LOG(("Matching: This is NOT a one-shot filter (0x%p), newOwner(0x%p)", uId, pNewOwner)); + if (pNewOwner) + { + vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, false); + } + } + } + else + { + LOG(("Device (0x%p) IS filtered", pDevice)); + /* the device is currently filtered, we should release it only if + * 1. device does not have an owner + * or + * 2. it should be released bue to a one-shot filter + * or + * 3. it is NOT grabbed by a one-shot filter */ + if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot) + { + LOG(("Matching: Need replug")); + bNeedReplug = true; + } + } + } + else + { + LOG(("Matching: Device (0x%p) SHOULD be filtered", pDevice)); + /* the device should be filtered, check the current state */ + ASSERT_WARN(uId, ("zero uid")); + ASSERT_WARN(pNewOwner, ("zero pNewOwner")); + if (vboxUsbFltDevStateIsFiltered(pDevice)) + { + LOG(("Device (0x%p) IS filtered", pDevice)); + /* the device is filtered */ + if (pNewOwner == pDevice->pOwner) + { + LOG(("Device owner match")); + /* no changes */ + if (fIsOneShot) + { + LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId)); + /* remove a one-shot filter and keep the original filter data */ + int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId); + ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc)); + } + else + { + LOG(("Matching: This is NOT a one-shot filter (0x%p), Owner(0x%p)", uId, pDevice->pOwner)); + vboxUsbFltDevOwnerUpdateLocked(pDevice, pDevice->pOwner, uId, false); + } + } + else + { + ASSERT_WARN(!pDevice->pOwner, ("device should NOT have owner")); + LOG(("Matching: Need replug")); + /* the device needs to be filtered, but the owner changes, replug needed */ + bNeedReplug = true; + } + } + else + { + /* the device is currently NOT filtered, + * we should replug it only if + * 1. device does not have an owner + * or + * 2. it should be captured due to a one-shot filter + * or + * 3. it is NOT released by a one-shot filter */ + if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot) + { + bNeedReplug = true; + LOG(("Matching: Need replug")); + } + } + } + + if (bNeedReplug) + { + LOG(("Matching: Device needs replugging, marking as such")); + vboxUsbFltDevStateMarkReplugLocked(pDevice); + } + else + { + LOG(("Matching: Device does NOT need replugging")); + } + + return bNeedReplug; +} + +static void vboxUsbFltReplugList(PLIST_ENTRY pList) +{ + PLIST_ENTRY pNext; + for (PLIST_ENTRY pEntry = pList->Flink; + pEntry != pList; + pEntry = pNext) + { + pNext = pEntry->Flink; + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(pEntry); + LOG(("replugging matched PDO(0x%p), pDevice(0x%p)", pDevice->Pdo, pDevice)); + ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED, + ("invalid state(0x%x) for device(0x%p)", pDevice->enmState, pDevice)); + + vboxUsbFltPdoReplug(pDevice->Pdo); + ObDereferenceObject(pDevice->Pdo); + vboxUsbFltDevRelease(pDevice); + } +} + +typedef struct VBOXUSBFLTCHECKWALKER +{ + PVBOXUSBFLTCTX pContext; +} VBOXUSBFLTCHECKWALKER, *PVBOXUSBFLTCHECKWALKER; + +static DECLCALLBACK(BOOLEAN) vboxUsbFltFilterCheckWalker(PFILE_OBJECT pHubFile, + PDEVICE_OBJECT pHubDo, PVOID pvContext) +{ + PVBOXUSBFLTCHECKWALKER pData = (PVBOXUSBFLTCHECKWALKER)pvContext; + PVBOXUSBFLTCTX pContext = pData->pContext; + + LOG(("Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext)); + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql)); + + PDEVICE_RELATIONS pDevRelations = NULL; + + NTSTATUS Status = VBoxUsbMonQueryBusRelations(pHubDo, pHubFile, &pDevRelations); + if (Status == STATUS_SUCCESS && pDevRelations) + { + ULONG cReplugPdos = pDevRelations->Count; + LIST_ENTRY ReplugDevList; + InitializeListHead(&ReplugDevList); + for (ULONG k = 0; k < pDevRelations->Count; ++k) + { + PDEVICE_OBJECT pDevObj; + + /* Grab the PDO+reference. We won't need the upper layer device object + * anymore, so dereference that right here, and drop the PDO ref later. + */ + pDevObj = IoGetDeviceAttachmentBaseRef(pDevRelations->Objects[k]); + LOG(("DevObj=%p, PDO=%p\n", pDevRelations->Objects[k], pDevObj)); + ObDereferenceObject(pDevRelations->Objects[k]); + pDevRelations->Objects[k] = pDevObj; + + LOG(("Found existing USB PDO 0x%p", pDevObj)); + VBOXUSBFLT_LOCK_ACQUIRE(); + PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pDevObj); + if (pDevice) + { + LOG(("Found existing device info (0x%p) for PDO 0x%p", pDevice, pDevObj)); + bool bReplug = vboxUsbFltDevCheckReplugLocked(pDevice, pContext); + if (bReplug) + { + LOG(("Replug needed for device (0x%p)", pDevice)); + InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe); + vboxUsbFltDevRetain(pDevice); + /* do not dereference object since we will use it later */ + } + else + { + LOG(("Replug NOT needed for device (0x%p)", pDevice)); + ObDereferenceObject(pDevObj); + } + + VBOXUSBFLT_LOCK_RELEASE(); + + pDevRelations->Objects[k] = NULL; + --cReplugPdos; + ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos(%d) state broken", cReplugPdos)); + continue; + } + VBOXUSBFLT_LOCK_RELEASE(); + + LOG(("NO device info found for PDO 0x%p", pDevObj)); + VBOXUSBFLT_DEVICE Device; + Status = vboxUsbFltDevPopulate(&Device, pDevObj /*, FALSE /* only need filter properties */); + if (NT_SUCCESS(Status)) + { + uintptr_t uId = 0; + bool fFilter = false; + bool fIsOneShot = false; + VBOXUSBFLT_LOCK_ACQUIRE(); + PVBOXUSBFLTCTX pCtx = vboxUsbFltDevMatchLocked(&Device, &uId, + false, /* do not remove a one-shot filter */ + &fFilter, &fIsOneShot); + VBOXUSBFLT_LOCK_RELEASE(); + NOREF(pCtx); + LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot)); + if (fFilter) + { + LOG(("Matching: This device SHOULD be filtered")); + /* this device needs to be filtered, but it's not, + * leave the PDO in array to issue a replug request for it + * later on */ + continue; + } + } + else + { + WARN(("vboxUsbFltDevPopulate for PDO 0x%p failed with Status 0x%x", pDevObj, Status)); + if ( Status == STATUS_CANCELLED + && g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails) + { + /* + * This can happen if the device got suspended and is in D3 state where we can't query any strings. + * There is no known way to set the power state of the device, especially if there is no driver attached yet. + * The sledgehammer approach is to just replug the device to force it out of suspend, see bugref @{9479}. + */ + continue; + } + } + + LOG(("Matching: This device should NOT be filtered")); + /* this device should not be filtered, and it's not */ + ObDereferenceObject(pDevObj); + pDevRelations->Objects[k] = NULL; + --cReplugPdos; + ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos is %d", cReplugPdos)); + } + + LOG(("(%d) non-matched PDOs to be replugged", cReplugPdos)); + + if (cReplugPdos) + { + for (ULONG k = 0; k < pDevRelations->Count; ++k) + { + if (!pDevRelations->Objects[k]) + continue; + + Status = vboxUsbFltPdoReplug(pDevRelations->Objects[k]); + ASSERT_WARN(Status == STATUS_SUCCESS, ("vboxUsbFltPdoReplug failed! Status(0x%x)", Status)); + ObDereferenceObject(pDevRelations->Objects[k]); + if (!--cReplugPdos) + break; + } + + ASSERT_WARN(!cReplugPdos, ("cReplugPdos reached zero!")); + } + + vboxUsbFltReplugList(&ReplugDevList); + + ExFreePool(pDevRelations); + } + else + { + WARN(("VBoxUsbMonQueryBusRelations failed for hub DO(0x%p), Status(0x%x), pDevRelations(0x%p)", + pHubDo, Status, pDevRelations)); + } + + LOG(("Done Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext)); + + return TRUE; +} + +NTSTATUS VBoxUsbFltFilterCheck(PVBOXUSBFLTCTX pContext) +{ + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql)); + + LOG(("Running filters, Context (0x%p)..", pContext)); + + VBOXUSBFLTCHECKWALKER Data; + Data.pContext = pContext; + vboxUsbMonHubDevWalk(vboxUsbFltFilterCheckWalker, &Data); + + LOG(("DONE Running filters, Context (0x%p)", pContext)); + + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltClose(PVBOXUSBFLTCTX pContext) +{ + LOG(("Closing context(0x%p)", pContext)); + LIST_ENTRY ReplugDevList; + InitializeListHead(&ReplugDevList); + + ASSERT_WARN(pContext, ("null context")); + + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("irql==(%d)", Irql)); + + VBOXUSBFLT_LOCK_ACQUIRE(); + + pContext->bRemoved = TRUE; + RemoveEntryList(&pContext->ListEntry); + + LOG(("removing owner filters")); + /* now re-arrange the filters */ + /* 1. remove filters */ + VBoxUSBFilterRemoveOwner(pContext); + + LOG(("enumerating devices..")); + /* 2. check if there are devices owned */ + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + if (pDevice->pOwner != pContext) + continue; + + LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)", + pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + + vboxUsbFltDevOwnerClearLocked(pDevice); + + if (vboxUsbFltDevCheckReplugLocked(pDevice, pContext)) + { + LOG(("device needs replug")); + InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe); + /* retain to ensure the device is not removed before we issue a replug */ + vboxUsbFltDevRetain(pDevice); + /* keep the PDO alive */ + ObReferenceObject(pDevice->Pdo); + } + else + { + LOG(("device does NOT need replug")); + } + } + + VBOXUSBFLT_LOCK_RELEASE(); + + /* this should replug all devices that were either skipped or grabbed due to the context's */ + vboxUsbFltReplugList(&ReplugDevList); + + LOG(("SUCCESS done context(0x%p)", pContext)); + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltCreate(PVBOXUSBFLTCTX pContext) +{ + LOG(("Creating context(0x%p)", pContext)); + memset(pContext, 0, sizeof (*pContext)); + pContext->Process = RTProcSelf(); + VBOXUSBFLT_LOCK_ACQUIRE(); + InsertHeadList(&g_VBoxUsbFltGlobals.ContextList, &pContext->ListEntry); + VBOXUSBFLT_LOCK_RELEASE(); + LOG(("SUCCESS context(0x%p)", pContext)); + return STATUS_SUCCESS; +} + +int VBoxUsbFltAdd(PVBOXUSBFLTCTX pContext, PUSBFILTER pFilter, uintptr_t *pId) +{ + LOG(("adding filter, Context (0x%p)..", pContext)); + *pId = 0; + /* LOG the filter details. */ + LOG((__FUNCTION__": %s %s %s", + USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "", + USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x Type%#x", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT), + USBFilterGetFilterType(pFilter))); +#endif + + /* We can't get the bus/port numbers. Ignore them while matching. */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PORT, false); + + /* We may not be able to reconstruct the class/subclass/protocol if we aren't able to + * read the device descriptor. Don't require these to be present. See also the fInferredDesc flag. + */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_CLASS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_PROTOCOL, false); + + /* We may also be unable to read string descriptors. Often the userland can't read the + * string descriptors either because the device is in a low-power state, but it can happen + * that the userland gets lucky and reads the strings, but by the time we get to read them + * they're inaccessible due to power management. So, don't require the strings to be present. + */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_MANUFACTURER_STR, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PRODUCT_STR, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR, false); + + uintptr_t uId = 0; + VBOXUSBFLT_LOCK_ACQUIRE(); + /* Add the filter. */ + int rc = VBoxUSBFilterAdd(pFilter, pContext, &uId); + VBOXUSBFLT_LOCK_RELEASE(); + if (RT_SUCCESS(rc)) + { + LOG(("ADDED filter id 0x%p", uId)); + ASSERT_WARN(uId, ("uid is NULL")); +#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUsbFltFilterCheck(); +#endif + } + else + { + WARN(("VBoxUSBFilterAdd failed rc (%d)", rc)); + ASSERT_WARN(!uId, ("uid is not NULL")); + } + + *pId = uId; + return rc; +} + +int VBoxUsbFltRemove(PVBOXUSBFLTCTX pContext, uintptr_t uId) +{ + LOG(("removing filter id(0x%p), Context (0x%p)..", pContext, uId)); + Assert(uId); + + VBOXUSBFLT_LOCK_ACQUIRE(); + int rc = VBoxUSBFilterRemove(pContext, uId); + if (!RT_SUCCESS(rc)) + { + WARN(("VBoxUSBFilterRemove failed rc (%d)", rc)); + VBOXUSBFLT_LOCK_RELEASE(); + return rc; + } + + LOG(("enumerating devices..")); + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + if (pDevice->fIsFilterOneShot) + { + ASSERT_WARN(!pDevice->uFltId, ("oneshot filter on device(0x%p): unexpected uFltId(%d)", pDevice, pDevice->uFltId)); + } + + if (pDevice->uFltId != uId) + continue; + + ASSERT_WARN(pDevice->pOwner == pContext, ("Device(0x%p) owner(0x%p) not match to (0x%p)", pDevice, pDevice->pOwner, pContext)); + if (pDevice->pOwner != pContext) + continue; + + LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)", + pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot)); + ASSERT_WARN(!pDevice->fIsFilterOneShot, ("device(0x%p) is filtered with a oneshot filter", pDevice)); + pDevice->uFltId = 0; + /* clear the fIsFilterOneShot flag to ensure the device is replugged on the next VBoxUsbFltFilterCheck call */ + pDevice->fIsFilterOneShot = false; + } + VBOXUSBFLT_LOCK_RELEASE(); + + LOG(("done enumerating devices")); + + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUsbFltFilterCheck(); +#endif + } + return rc; +} + +static USBDEVICESTATE vboxUsbDevGetUserState(PVBOXUSBFLTCTX pContext, PVBOXUSBFLT_DEVICE pDevice) +{ + if (vboxUsbFltDevStateIsNotFiltered(pDevice)) + return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* the device is filtered, or replugging */ + if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING) + { + ASSERT_WARN(!pDevice->pOwner, ("replugging device(0x%p) still has an owner(0x%p)", pDevice, pDevice->pOwner)); + ASSERT_WARN(!pDevice->uFltId, ("replugging device(0x%p) still has filter(0x%p)", pDevice, pDevice->uFltId)); + /* no user state for this, we should not return it tu the user */ + return USBDEVICESTATE_USED_BY_HOST; + } + + /* the device is filtered, if owner differs from the context, return as USED_BY_HOST */ + ASSERT_WARN(pDevice->pOwner, ("device(0x%p) has noowner", pDevice)); + /* the id can be null if a filter is removed */ +// Assert(pDevice->uFltId); + + if (pDevice->pOwner != pContext) + { + LOG(("Device owner differs from the current context, returning used by host")); + return USBDEVICESTATE_USED_BY_HOST; + } + + switch (pDevice->enmState) + { + case VBOXUSBFLT_DEVSTATE_UNCAPTURED: + case VBOXUSBFLT_DEVSTATE_CAPTURING: + return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + case VBOXUSBFLT_DEVSTATE_CAPTURED: + return USBDEVICESTATE_HELD_BY_PROXY; + case VBOXUSBFLT_DEVSTATE_USED_BY_GUEST: + return USBDEVICESTATE_USED_BY_GUEST; + default: + WARN(("unexpected device state(%d) for device(0x%p)", pDevice->enmState, pDevice)); + return USBDEVICESTATE_UNSUPPORTED; + } +} + +NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo) +{ + if (!hDevice) + return STATUS_INVALID_PARAMETER; + + memset (pInfo, 0, sizeof (*pInfo)); + VBOXUSBFLT_LOCK_ACQUIRE(); + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED); + Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED); + + if (pDevice != hDevice) + continue; + + USBDEVICESTATE enmUsrState = vboxUsbDevGetUserState(pContext, pDevice); + pInfo->enmState = enmUsrState; + VBOXUSBFLT_LOCK_RELEASE(); + return STATUS_SUCCESS; + } + + VBOXUSBFLT_LOCK_RELEASE(); + + /* We should not get this far with valid input. */ + return STATUS_INVALID_PARAMETER; +} + +NTSTATUS VBoxUsbFltPdoAdd(PDEVICE_OBJECT pPdo, BOOLEAN *pbFiltered) +{ + *pbFiltered = FALSE; + PVBOXUSBFLT_DEVICE pDevice; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + /* first check if device is in the a already */ + VBOXUSBFLT_LOCK_ACQUIRE(); + pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + { + LOG(("found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + *pbFiltered = pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + return STATUS_SUCCESS; + } + VBOXUSBFLT_LOCK_RELEASE(); + pDevice = (PVBOXUSBFLT_DEVICE)VBoxUsbMonMemAllocZ(sizeof (*pDevice)); + if (!pDevice) + { + WARN(("VBoxUsbMonMemAllocZ failed")); + ObDereferenceObject(pPdo); + return STATUS_NO_MEMORY; + } + + pDevice->enmState = VBOXUSBFLT_DEVSTATE_ADDED; + pDevice->cRefs = 1; + NTSTATUS Status = vboxUsbFltDevPopulate(pDevice, pPdo /* , TRUE /* need all props */); + if (!NT_SUCCESS(Status)) + { + WARN(("vboxUsbFltDevPopulate failed, Status 0x%x", Status)); + ObDereferenceObject(pPdo); + VBoxUsbMonMemFree(pDevice); + return Status; + } + + uintptr_t uId; + bool fFilter = false; + bool fIsOneShot = false; + PVBOXUSBFLTCTX pCtx; + PVBOXUSBFLT_DEVICE pTmpDev; + VBOXUSBFLT_LOCK_ACQUIRE(); + /* (paranoia) re-check the device is still not here */ + pTmpDev = vboxUsbFltDevGetLocked(pPdo); + + /* Drop the PDO ref, now we won't need it anymore. */ + ObDereferenceObject(pPdo); + + if (pTmpDev) + { + LOG(("second try: found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("second try: VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("second try: VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + *pbFiltered = pTmpDev->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; + VBOXUSBFLT_LOCK_RELEASE(); + VBoxUsbMonMemFree(pDevice); + return STATUS_SUCCESS; + } + + LOG(("Created Device 0x%p for PDO 0x%p", pDevice, pPdo)); + + pCtx = vboxUsbFltDevMatchLocked(pDevice, &uId, + true, /* remove a one-shot filter */ + &fFilter, &fIsOneShot); + LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot)); + if (fFilter) + { + LOG(("Created Device 0x%p should be filtered", pDevice)); + ASSERT_WARN(pCtx, ("zero ctx")); + ASSERT_WARN(uId, ("zero uId")); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING; + } + else + { + LOG(("Created Device 0x%p should NOT be filtered", pDevice)); + ASSERT_WARN(!uId == !pCtx, ("invalid uid(0x%p) - ctx(0x%p) pair", uId, pCtx)); /* either both zero or both not */ + pDevice->enmState = VBOXUSBFLT_DEVSTATE_UNCAPTURED; + } + + if (pCtx) + vboxUsbFltDevOwnerSetLocked(pDevice, pCtx, fIsOneShot ? 0 : uId, fIsOneShot); + + InsertHeadList(&g_VBoxUsbFltGlobals.DeviceList, &pDevice->GlobalLe); + + /* do not need to signal anything here - + * going to do that once the proxy device object starts */ + VBOXUSBFLT_LOCK_RELEASE(); + + *pbFiltered = fFilter; + + return STATUS_SUCCESS; +} + +BOOLEAN VBoxUsbFltPdoIsFiltered(PDEVICE_OBJECT pPdo) +{ + VBOXUSBFLT_DEVSTATE enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + VBOXUSBFLT_LOCK_ACQUIRE(); + + PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + enmState = pDevice->enmState; + + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + + return enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; +} + +NTSTATUS VBoxUsbFltPdoRemove(PDEVICE_OBJECT pPdo) +{ + PVBOXUSBFLT_DEVICE pDevice; + VBOXUSBFLT_DEVSTATE enmOldState; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + VBOXUSBFLT_LOCK_ACQUIRE(); + pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + { + RemoveEntryList(&pDevice->GlobalLe); + enmOldState = pDevice->enmState; + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + } + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + if (pDevice) + vboxUsbFltDevRelease(pDevice); + return STATUS_SUCCESS; +} + +HVBOXUSBFLTDEV VBoxUsbFltProxyStarted(PDEVICE_OBJECT pPdo) +{ + PVBOXUSBFLT_DEVICE pDevice; + VBOXUSBFLT_LOCK_ACQUIRE(); + + /* NB: The USB proxy (VBoxUSB.sys) passes us the real PDO, not anything above that. */ + pDevice = vboxUsbFltDevGetLocked(pPdo); + /* + * Prevent a host crash when vboxUsbFltDevGetLocked fails to locate the matching PDO + * in g_VBoxUsbFltGlobals.DeviceList (see @bugref{6509}). + */ + if (pDevice == NULL) + { + WARN(("failed to get device for PDO(0x%p)", pPdo)); + } + else if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING) + { + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURED; + LOG(("The proxy notified proxy start for the captured device 0x%p", pDevice)); + vboxUsbFltDevRetain(pDevice); + } + else + { + WARN(("invalid state, %d", pDevice->enmState)); + pDevice = NULL; + } + VBOXUSBFLT_LOCK_RELEASE(); + return pDevice; +} + +void VBoxUsbFltProxyStopped(HVBOXUSBFLTDEV hDev) +{ + PVBOXUSBFLT_DEVICE pDevice = (PVBOXUSBFLT_DEVICE)hDev; + /* + * Prevent a host crash when VBoxUsbFltProxyStarted fails, returning NULL. + * See @bugref{6509}. + */ + if (pDevice == NULL) + { + WARN(("VBoxUsbFltProxyStopped called with NULL device pointer")); + return; + } + VBOXUSBFLT_LOCK_ACQUIRE(); + if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST) + { + /* this is due to devie was physically removed */ + LOG(("The proxy notified proxy stop for the captured device 0x%p, current state %d", pDevice, pDevice->enmState)); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING; + } + else + { + if (pDevice->enmState != VBOXUSBFLT_DEVSTATE_REPLUGGING) + { + WARN(("invalid state, %d", pDevice->enmState)); + } + } + VBOXUSBFLT_LOCK_RELEASE(); + + vboxUsbFltDevRelease(pDevice); +} + + +static NTSTATUS vboxUsbFltRegKeyQuery(PWSTR ValueName, ULONG ValueType, PVOID ValueData, ULONG ValueLength, PVOID Context, PVOID EntryContext) +{ + NTSTATUS Status = STATUS_SUCCESS; + + RT_NOREF(ValueName, Context); + if ( ValueType == REG_DWORD + && ValueLength == sizeof(ULONG)) + *(ULONG *)EntryContext = *(ULONG *)ValueData; + else + Status = STATUS_OBJECT_TYPE_MISMATCH; + + return Status; +} + + +NTSTATUS VBoxUsbFltInit() +{ + int rc = VBoxUSBFilterInit(); + if (RT_FAILURE(rc)) + { + WARN(("VBoxUSBFilterInit failed, rc (%d)", rc)); + return STATUS_UNSUCCESSFUL; + } + + memset(&g_VBoxUsbFltGlobals, 0, sizeof (g_VBoxUsbFltGlobals)); + InitializeListHead(&g_VBoxUsbFltGlobals.DeviceList); + InitializeListHead(&g_VBoxUsbFltGlobals.ContextList); + InitializeListHead(&g_VBoxUsbFltGlobals.BlackDeviceList); + vboxUsbFltBlDevPopulateWithKnownLocked(); + VBOXUSBFLT_LOCK_INIT(); + + /* + * Check whether the setting to force replugging USB devices when + * querying string descriptors fail is set in the registry, + * see @bugref{9479}. + */ + RTL_QUERY_REGISTRY_TABLE aParams[] = + { + {vboxUsbFltRegKeyQuery, 0, L"ForceReplugWhenDevPopulateFails", &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, REG_DWORD, &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, sizeof(ULONG) }, + { NULL, 0, NULL, NULL, 0, 0, 0 } + }; + UNICODE_STRING UnicodePath = RTL_CONSTANT_STRING(L"\\VBoxUSB"); + + NTSTATUS Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, UnicodePath.Buffer, &aParams[0], NULL, NULL); + if (Status == STATUS_SUCCESS) + { + if (g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails) + LOG(("Forcing replug of USB devices where querying the descriptors fail\n")); + } + else + LOG(("RtlQueryRegistryValues() -> %#x, assuming defaults\n", Status)); + + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltTerm() +{ + bool bBusy = false; + VBOXUSBFLT_LOCK_ACQUIRE(); + do + { + if (!IsListEmpty(&g_VBoxUsbFltGlobals.ContextList)) + { + AssertFailed(); + bBusy = true; + break; + } + + PLIST_ENTRY pNext = NULL; + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pNext) + { + pNext = pEntry->Flink; + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + Assert(!pDevice->uFltId); + Assert(!pDevice->pOwner); + if (pDevice->cRefs != 1) + { + AssertFailed(); + bBusy = true; + break; + } + } + } while (0); + + VBOXUSBFLT_LOCK_RELEASE() + + if (bBusy) + { + return STATUS_DEVICE_BUSY; + } + + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink) + { + RemoveEntryList(pEntry); + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + vboxUsbFltDevRelease(pDevice); + } + + vboxUsbFltBlDevClearLocked(); + + VBOXUSBFLT_LOCK_TERM(); + + VBoxUSBFilterTerm(); + + return STATUS_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h new file mode 100644 index 00000000..2e8c7501 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h @@ -0,0 +1,75 @@ +/* $Id: VBoxUsbFlt.h $ */ +/** @file + * VBox USB Monitor Device Filtering functionality + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbMon.h" +#include + +#include + +typedef struct VBOXUSBFLTCTX +{ + LIST_ENTRY ListEntry; + RTPROCESS Process; // Purely informational, no function? + uint32_t cActiveFilters; + BOOLEAN bRemoved; // For debugging only? +} VBOXUSBFLTCTX, *PVBOXUSBFLTCTX; + +NTSTATUS VBoxUsbFltInit(); +NTSTATUS VBoxUsbFltTerm(); +NTSTATUS VBoxUsbFltCreate(PVBOXUSBFLTCTX pContext); +NTSTATUS VBoxUsbFltClose(PVBOXUSBFLTCTX pContext); +int VBoxUsbFltAdd(PVBOXUSBFLTCTX pContext, PUSBFILTER pFilter, uintptr_t *pId); +int VBoxUsbFltRemove(PVBOXUSBFLTCTX pContext, uintptr_t uId); +NTSTATUS VBoxUsbFltFilterCheck(PVBOXUSBFLTCTX pContext); + +NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo); + +typedef void* HVBOXUSBFLTDEV; +HVBOXUSBFLTDEV VBoxUsbFltProxyStarted(PDEVICE_OBJECT pPdo); +void VBoxUsbFltProxyStopped(HVBOXUSBFLTDEV hDev); + +NTSTATUS VBoxUsbFltPdoAdd(PDEVICE_OBJECT pPdo, BOOLEAN *pbFiltered); +NTSTATUS VBoxUsbFltPdoRemove(PDEVICE_OBJECT pPdo); +BOOLEAN VBoxUsbFltPdoIsFiltered(PDEVICE_OBJECT pPdo); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp new file mode 100644 index 00000000..83dc8de9 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp @@ -0,0 +1,218 @@ +/* $Id: VBoxUsbHook.cpp $ */ +/** @file + * Driver Dispatch Table Hooking API + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXUSBHOOK_MEMTAG 'HUBV' + + +NTSTATUS VBoxUsbHookInstall(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (pHook->fIsInstalled) + { + WARN(("hook is marked installed, returning failure")); + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_UNSUCCESSFUL; + } + + pHook->pfnOldHandler = (PDRIVER_DISPATCH)InterlockedExchangePointer((PVOID*)&pHook->pDrvObj->MajorFunction[pHook->iMjFunction], pHook->pfnHook); + Assert(pHook->pfnOldHandler); + Assert(pHook->pfnHook != pHook->pfnOldHandler); + pHook->fIsInstalled = TRUE; + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_SUCCESS; + +} +NTSTATUS VBoxUsbHookUninstall(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (!pHook->fIsInstalled) + { + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_SUCCESS; + } + + PDRIVER_DISPATCH pfnOldVal = (PDRIVER_DISPATCH)InterlockedCompareExchangePointer((PVOID*)&pHook->pDrvObj->MajorFunction[pHook->iMjFunction], pHook->pfnOldHandler, pHook->pfnHook); + Assert(pfnOldVal == pHook->pfnHook); + if (pfnOldVal != pHook->pfnHook) + { + AssertMsgFailed(("unhook failed!!!\n")); + /* this is bad! this could happen if someone else has chained another hook, + * or (which is even worse) restored the "initial" entry value it saved when doing a hooking before us + * return the failure and don't do anything else + * the best thing to do if this happens is to leave everything as is + * and to prevent the driver from being unloaded to ensure no one references our unloaded hook routine */ + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_UNSUCCESSFUL; + } + + pHook->fIsInstalled = FALSE; + KeReleaseSpinLock(&pHook->Lock, Irql); + + /* wait for the current handlers to exit */ + VBoxDrvToolRefWaitEqual(&pHook->HookRef, 1); + + return STATUS_SUCCESS; +} + +BOOLEAN VBoxUsbHookIsInstalled(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + BOOLEAN fIsInstalled; + KeAcquireSpinLock(&pHook->Lock, &Irql); + fIsInstalled = pHook->fIsInstalled; + KeReleaseSpinLock(&pHook->Lock, Irql); + return fIsInstalled; +} + +VOID VBoxUsbHookInit(PVBOXUSBHOOK_ENTRY pHook, PDRIVER_OBJECT pDrvObj, UCHAR iMjFunction, PDRIVER_DISPATCH pfnHook) +{ + Assert(pDrvObj); + Assert(iMjFunction <= IRP_MJ_MAXIMUM_FUNCTION); + Assert(pfnHook); + memset(pHook, 0, sizeof (*pHook)); + InitializeListHead(&pHook->RequestList); + KeInitializeSpinLock(&pHook->Lock); + VBoxDrvToolRefInit(&pHook->HookRef); + pHook->pDrvObj = pDrvObj; + pHook->iMjFunction = iMjFunction; + pHook->pfnHook = pfnHook; + Assert(!pHook->pfnOldHandler); + Assert(!pHook->fIsInstalled); + +} + +static void vboxUsbHookRequestRegisterCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest) +{ + Assert(pfnCompletion); + Assert(pRequest); + Assert(pDevObj); + Assert(pIrp); + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + memset(pRequest, 0, sizeof (*pRequest)); + pRequest->pHook = pHook; + pRequest->OldLocation = *pSl; + pRequest->pDevObj = pDevObj; + pRequest->pIrp = pIrp; + pRequest->bCompletionStopped = FALSE; + pSl->CompletionRoutine = pfnCompletion; + pSl->Context = pRequest; + pSl->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL; + + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + InsertTailList(&pHook->RequestList, &pRequest->ListEntry); + KeReleaseSpinLock(&pHook->Lock, oldIrql); +} + +NTSTATUS VBoxUsbHookRequestPassDownHookCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest) +{ + Assert(pfnCompletion); + vboxUsbHookRequestRegisterCompletion(pHook, pDevObj, pIrp, pfnCompletion, pRequest); + return pHook->pfnOldHandler(pDevObj, pIrp); +} + +NTSTATUS VBoxUsbHookRequestPassDownHookSkip(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + return pHook->pfnOldHandler(pDevObj, pIrp); +} + +NTSTATUS VBoxUsbHookRequestMoreProcessingRequired(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, + PVBOXUSBHOOK_REQUEST pRequest) +{ + RT_NOREF3(pHook, pDevObj, pIrp); + Assert(!pRequest->bCompletionStopped); + pRequest->bCompletionStopped = TRUE; + return STATUS_MORE_PROCESSING_REQUIRED; +} + +NTSTATUS VBoxUsbHookRequestComplete(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest) +{ + NTSTATUS Status = STATUS_SUCCESS; + + if (pRequest->OldLocation.CompletionRoutine && pRequest->OldLocation.Control) + { + Status = pRequest->OldLocation.CompletionRoutine(pDevObj, pIrp, pRequest->OldLocation.Context); + } + + if (Status != STATUS_MORE_PROCESSING_REQUIRED) + { + if (pRequest->bCompletionStopped) + { + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + } + /* + * else - in case driver returned STATUS_MORE_PROCESSING_REQUIRED, + * it will call IoCompleteRequest itself + */ + + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + RemoveEntryList(&pRequest->ListEntry); + KeReleaseSpinLock(&pHook->Lock, oldIrql); + return Status; +} + +#define PVBOXUSBHOOK_REQUEST_FROM_LE(_pLe) ( (PVBOXUSBHOOK_REQUEST)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBHOOK_REQUEST, ListEntry) ) ) + +VOID VBoxUsbHookVerifyCompletion(PVBOXUSBHOOK_ENTRY pHook, PVBOXUSBHOOK_REQUEST pRequest, PIRP pIrp) +{ + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + for (PLIST_ENTRY pLe = pHook->RequestList.Flink; pLe != &pHook->RequestList; pLe = pLe->Flink) + { + PVBOXUSBHOOK_REQUEST pCur = PVBOXUSBHOOK_REQUEST_FROM_LE(pLe); + if (pCur != pRequest) + continue; + if (pCur->pIrp != pIrp) + continue; + WARN(("found pending IRP(0x%p) when it should not be", pIrp)); + } + KeReleaseSpinLock(&pHook->Lock, oldIrql); + +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h new file mode 100644 index 00000000..f9207297 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h @@ -0,0 +1,96 @@ +/* $Id: VBoxUsbHook.h $ */ +/** @file + * Driver Dispatch Table Hooking API impl + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbMon.h" + +typedef struct VBOXUSBHOOK_ENTRY +{ + LIST_ENTRY RequestList; + KSPIN_LOCK Lock; + BOOLEAN fIsInstalled; + PDRIVER_DISPATCH pfnOldHandler; + VBOXDRVTOOL_REF HookRef; + PDRIVER_OBJECT pDrvObj; + UCHAR iMjFunction; + PDRIVER_DISPATCH pfnHook; +} VBOXUSBHOOK_ENTRY, *PVBOXUSBHOOK_ENTRY; + +typedef struct VBOXUSBHOOK_REQUEST +{ + LIST_ENTRY ListEntry; + PVBOXUSBHOOK_ENTRY pHook; + IO_STACK_LOCATION OldLocation; + PDEVICE_OBJECT pDevObj; + PIRP pIrp; + BOOLEAN bCompletionStopped; +} VBOXUSBHOOK_REQUEST, *PVBOXUSBHOOK_REQUEST; + +DECLINLINE(BOOLEAN) VBoxUsbHookRetain(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (!pHook->fIsInstalled) + { + KeReleaseSpinLock(&pHook->Lock, Irql); + return FALSE; + } + + VBoxDrvToolRefRetain(&pHook->HookRef); + KeReleaseSpinLock(&pHook->Lock, Irql); + return TRUE; +} + +DECLINLINE(VOID) VBoxUsbHookRelease(PVBOXUSBHOOK_ENTRY pHook) +{ + VBoxDrvToolRefRelease(&pHook->HookRef); +} + +VOID VBoxUsbHookInit(PVBOXUSBHOOK_ENTRY pHook, PDRIVER_OBJECT pDrvObj, UCHAR iMjFunction, PDRIVER_DISPATCH pfnHook); +NTSTATUS VBoxUsbHookInstall(PVBOXUSBHOOK_ENTRY pHook); +NTSTATUS VBoxUsbHookUninstall(PVBOXUSBHOOK_ENTRY pHook); +BOOLEAN VBoxUsbHookIsInstalled(PVBOXUSBHOOK_ENTRY pHook); +NTSTATUS VBoxUsbHookRequestPassDownHookCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest); +NTSTATUS VBoxUsbHookRequestPassDownHookSkip(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp); +NTSTATUS VBoxUsbHookRequestMoreProcessingRequired(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest); +NTSTATUS VBoxUsbHookRequestComplete(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest); +VOID VBoxUsbHookVerifyCompletion(PVBOXUSBHOOK_ENTRY pHook, PVBOXUSBHOOK_REQUEST pRequest, PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp new file mode 100644 index 00000000..349893bd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp @@ -0,0 +1,1568 @@ +/* $Id: VBoxUsbMon.cpp $ */ +/** @file + * VBox USB Monitor + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/* + * + * Theory of Operation + * - or - + * The Document I Wish The Original Author Had Written + * + * + * The USB Monitor (VBoxUSBMon.sys) serves to capture and uncapture USB + * devices. Its job is to ensure that the USB proxy (VBoxUSB.sys) gets installed + * for captured devices and removed again when not needed, restoring the regular + * driver (if any). + * + * The USB Monitor does not handle any actual USB traffic; that is the role of + * VBoxUSB.sys, the USB proxy. A typical solution for installing such USB proxy + * is using a filter driver, but that approach was rejected because filter drivers + * cannot be dynamically added and removed. What VBoxUSBMon does instead is hook + * into the dispatch routine of the bus driver, i.e. USB hub driver, and alter + * the PnP information returned by the bus driver. + * + * The key functionality for capturing is cycling a USB port (which causes a USB + * device reset and triggers re-enumeration in the Windows USB driver stack), and + * then modifying IRP_MN_QUERY_ID / BusQueryHardwareIDs and related requests so + * that they return the synthetic USB VID/PID that VBoxUSB.sys handles rather than + * the true hardware VID/PID. That causes Windows to install VBoxUSB.sys for the + * device. + * + * Uncapturing again cycles the USB port but returns unmodified hardware IDs, + * causing Windows to load the normal driver for the device. + * + * Identifying devices to capture or release (uncapture) is done through USB filters, + * a cross-platform concept which matches USB device based on their VID/PID, class, + * and other criteria. + * + * There is an IOCTL interface for adding/removing USB filters and applying them. + * The IOCTLs are normally issued by VBoxSVC. + * + * USB devices are enumerated by finding all USB hubs (GUID_DEVINTERFACE_USB_HUB) + * and querying their child devices (i.e. USB devices or other hubs) by sending + * IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations. This is done when + * applying existing filters. + * + * Newly arrived USB devices are intercepted early in their PnP enumeration + * through the hooked bus driver dispatch routine. Devices which satisty the + * filter matching criteria are morphed (see above) such that VBoxUSB.sys loads + * for them before any default driver does. + * + * There is an IDC interface to VBoxUSB.sys which allows the USB proxy to report + * that it's installed for a given USB device, and also report when the USB proxy + * is unloaded (typically caused by either unplugging the device or uncapturing + * and cycling the port). VBoxUSBMon.sys relies on these IDC calls to track + * captured devices and be informed when VBoxUSB.sys unloads. + * + * Windows 8+ complicates the USB Monitor's life by automatically putting some + * USB devices to a low-power state where they are unable to respond to any USB + * requests and VBoxUSBMon can't read any of their descriptors (note that in + * userland, the device descriptor can always be read, but string descriptors + * can't). Such devices' USB VID/PID/revision is recovered using the Windows + * PnP Manager from their DevicePropertyHardwareID, but their USB class/subclass + * and protocol unfortunately cannot be unambiguously recovered from their + * DevicePropertyCompatibleIDs. + * + * Filter drivers add another complication. With filter drivers in place, the + * device objects returned by the BusRelations query (or passing through the PnP + * hooks) may not be PDOs but rather filter DOs higher in the stack. To avoid + * confusion, we flatten the references to their base, i.e. the real PDO, which + * should remain the same for the lifetime of a device. Note that VBoxUSB.sys + * always passes its own PDO in the proxy startup IOCTL. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" +#include "../cmn/VBoxUsbIdc.h" +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXUSBMON_MEMTAG 'MUBV' + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXUSBMONINS +{ + void * pvDummy; +} VBOXUSBMONINS, *PVBOXUSBMONINS; + +typedef struct VBOXUSBMONCTX +{ + VBOXUSBFLTCTX FltCtx; +} VBOXUSBMONCTX, *PVBOXUSBMONCTX; + +typedef struct VBOXUSBHUB_PNPHOOK +{ + VBOXUSBHOOK_ENTRY Hook; + bool fUninitFailed; +} VBOXUSBHUB_PNPHOOK, *PVBOXUSBHUB_PNPHOOK; + +typedef struct VBOXUSBHUB_PNPHOOK_COMPLETION +{ + VBOXUSBHOOK_REQUEST Rq; +} VBOXUSBHUB_PNPHOOK_COMPLETION, *PVBOXUSBHUB_PNPHOOK_COMPLETION; + +#define VBOXUSBMON_MAXDRIVERS 5 +typedef struct VBOXUSB_PNPDRIVER +{ + PDRIVER_OBJECT DriverObject; + VBOXUSBHUB_PNPHOOK UsbHubPnPHook; + PDRIVER_DISPATCH pfnHookStub; +} VBOXUSB_PNPDRIVER, *PVBOXUSB_PNPDRIVER; + +typedef struct VBOXUSBMONGLOBALS +{ + PDEVICE_OBJECT pDevObj; + VBOXUSB_PNPDRIVER pDrivers[VBOXUSBMON_MAXDRIVERS]; + KEVENT OpenSynchEvent; + IO_REMOVE_LOCK RmLock; + uint32_t cOpens; + volatile LONG ulPreventUnloadOn; + PFILE_OBJECT pPreventUnloadFileObj; +} VBOXUSBMONGLOBALS, *PVBOXUSBMONGLOBALS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static VBOXUSBMONGLOBALS g_VBoxUsbMonGlobals; + +/* + * Note: Must match the VID & PID in the USB driver .inf file!! + */ +/* + BusQueryDeviceID USB\Vid_80EE&Pid_CAFE + BusQueryInstanceID 2 + BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE&Rev_0100 + BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE + BusQueryCompatibleIDs USB\Class_ff&SubClass_00&Prot_00 + BusQueryCompatibleIDs USB\Class_ff&SubClass_00 + BusQueryCompatibleIDs USB\Class_ff +*/ + +static WCHAR const g_szBusQueryDeviceId[] = L"USB\\Vid_80EE&Pid_CAFE"; +static WCHAR const g_szBusQueryHardwareIDs[] = L"USB\\Vid_80EE&Pid_CAFE&Rev_0100\0USB\\Vid_80EE&Pid_CAFE\0\0"; +static WCHAR const g_szBusQueryCompatibleIDs[] = L"USB\\Class_ff&SubClass_00&Prot_00\0USB\\Class_ff&SubClass_00\0USB\\Class_ff\0\0"; +static WCHAR const g_szDeviceTextDescription[] = L"VirtualBox USB"; + + + +PVOID VBoxUsbMonMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSBMON_MEMTAG); + Assert(pvMem); + return pvMem; +} + +PVOID VBoxUsbMonMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = VBoxUsbMonMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +VOID VBoxUsbMonMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSBMON_MEMTAG); +} + +#define VBOXUSBDBG_STRCASE(_t) \ + case _t: return #_t +#define VBOXUSBDBG_STRCASE_UNKNOWN(_v) \ + default: LOG((__FUNCTION__": Unknown Value (0n%d), (0x%x)", _v, _v)); return "Unknown" + +/* These minor code are semi-undocumented. */ +#ifndef IRP_MN_QUERY_LEGACY_BUS_INFORMATION +#define IRP_MN_QUERY_LEGACY_BUS_INFORMATION 0x18 +#endif +#ifndef IRP_MN_DEVICE_ENUMERATED +#define IRP_MN_DEVICE_ENUMERATED 0x19 +#endif + +static const char* vboxUsbDbgStrPnPMn(UCHAR uMn) +{ + switch (uMn) + { + VBOXUSBDBG_STRCASE(IRP_MN_START_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_CANCEL_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_CANCEL_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_DEVICE_RELATIONS); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_INTERFACE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_CAPABILITIES); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_RESOURCES); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_RESOURCE_REQUIREMENTS); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_DEVICE_TEXT); + VBOXUSBDBG_STRCASE(IRP_MN_FILTER_RESOURCE_REQUIREMENTS); + VBOXUSBDBG_STRCASE(IRP_MN_READ_CONFIG); + VBOXUSBDBG_STRCASE(IRP_MN_WRITE_CONFIG); + VBOXUSBDBG_STRCASE(IRP_MN_EJECT); + VBOXUSBDBG_STRCASE(IRP_MN_SET_LOCK); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_ID); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_PNP_DEVICE_STATE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_BUS_INFORMATION); + VBOXUSBDBG_STRCASE(IRP_MN_DEVICE_USAGE_NOTIFICATION); + VBOXUSBDBG_STRCASE(IRP_MN_SURPRISE_REMOVAL); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_LEGACY_BUS_INFORMATION); + VBOXUSBDBG_STRCASE(IRP_MN_DEVICE_ENUMERATED); + VBOXUSBDBG_STRCASE_UNKNOWN(uMn); + } +} + +/** + * Send IRP_MN_QUERY_DEVICE_RELATIONS + * + * @returns NT Status + * @param pDevObj USB device pointer + * @param pFileObj Valid file object pointer + * @param pDevRelations Pointer to DEVICE_RELATIONS pointer (out) + */ +NTSTATUS VBoxUsbMonQueryBusRelations(PDEVICE_OBJECT pDevObj, PFILE_OBJECT pFileObj, PDEVICE_RELATIONS *pDevRelations) +{ + IO_STATUS_BLOCK IoStatus; + KEVENT Event; + NTSTATUS Status; + PIRP pIrp; + PIO_STACK_LOCATION pSl; + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + Assert(pDevRelations); + *pDevRelations = NULL; + + pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, pDevObj, NULL, 0, NULL, &Event, &IoStatus); + if (!pIrp) + { + WARN(("IoBuildDeviceIoControlRequest failed!!")); + return STATUS_INSUFFICIENT_RESOURCES; + } + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_PNP; + pSl->MinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS; + pSl->Parameters.QueryDeviceRelations.Type = BusRelations; + pSl->FileObject = pFileObj; + + Status = IoCallDriver(pDevObj, pIrp); + if (Status == STATUS_PENDING) + { + LOG(("IoCallDriver returned STATUS_PENDING!!")); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatus.Status; + } + + if (Status == STATUS_SUCCESS) + { + PDEVICE_RELATIONS pRel = (PDEVICE_RELATIONS)IoStatus.Information; + LOG(("pRel = %p", pRel)); + if (RT_VALID_PTR(pRel)) + *pDevRelations = pRel; + else + { + WARN(("Invalid pointer %p", pRel)); + } + } + else + { + WARN(("IRP_MN_QUERY_DEVICE_RELATIONS failed Status(0x%x)", Status)); + } + + LOG(("IoCallDriver returned %x", Status)); + return Status; +} + +VOID vboxUsbMonHubDevWalk(PFNVBOXUSBMONDEVWALKER pfnWalker, PVOID pvWalker) +{ + NTSTATUS Status = STATUS_UNSUCCESSFUL; + PWSTR szwHubList; + Status = IoGetDeviceInterfaces(&GUID_DEVINTERFACE_USB_HUB, NULL, 0, &szwHubList); + if (Status != STATUS_SUCCESS) + { + LOG(("IoGetDeviceInterfaces failed with %d\n", Status)); + return; + } + if (szwHubList) + { + UNICODE_STRING UnicodeName; + PDEVICE_OBJECT pHubDevObj; + PFILE_OBJECT pHubFileObj; + PWSTR szwHubName = szwHubList; + while (*szwHubName != UNICODE_NULL) + { + RtlInitUnicodeString(&UnicodeName, szwHubName); + Status = IoGetDeviceObjectPointer(&UnicodeName, FILE_READ_DATA, &pHubFileObj, &pHubDevObj); + if (Status == STATUS_SUCCESS) + { + /* We could not log hub name here. + * It is the paged memory and we cannot use it in logger cause it increases the IRQL + */ + LOG(("IoGetDeviceObjectPointer returned %p %p", pHubDevObj, pHubFileObj)); + if (!pfnWalker(pHubFileObj, pHubDevObj, pvWalker)) + { + LOG(("the walker said to stop")); + ObDereferenceObject(pHubFileObj); + break; + } + + LOG(("going forward..")); + ObDereferenceObject(pHubFileObj); + } + szwHubName += wcslen(szwHubName) + 1; + } + ExFreePool(szwHubList); + } +} + +/* NOTE: the stack location data is not the "actual" IRP stack location, + * but a copy being preserved on the IRP way down. + * See the note in VBoxUsbPnPCompletion for detail */ +static NTSTATUS vboxUsbMonHandlePnPIoctl(PDEVICE_OBJECT pDevObj, PIO_STACK_LOCATION pSl, PIO_STATUS_BLOCK pIoStatus) +{ + LOG(("IRQL = %d", KeGetCurrentIrql())); + switch(pSl->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_TEXT: + { + LOG(("IRP_MN_QUERY_DEVICE_TEXT: pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS) + { + WCHAR *pId = (WCHAR *)pIoStatus->Information; + if (RT_VALID_PTR(pId)) + { + KIRQL Iqrl = KeGetCurrentIrql(); + /* IRQL should be always passive here */ + ASSERT_WARN(Iqrl == PASSIVE_LEVEL, ("irql is not PASSIVE")); + switch (pSl->Parameters.QueryDeviceText.DeviceTextType) + { + case DeviceTextLocationInformation: + LOG(("DeviceTextLocationInformation")); + LOG_STRW(pId); + break; + + case DeviceTextDescription: + LOG(("DeviceTextDescription")); + LOG_STRW(pId); + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + { + LOG(("PDO (0x%p) is filtered", pDevObj)); + WCHAR *pId2 = (WCHAR *)ExAllocatePool(PagedPool, sizeof(g_szDeviceTextDescription)); + AssertBreak(pId2); + memcpy(pId2, g_szDeviceTextDescription, sizeof(g_szDeviceTextDescription)); + LOG(("NEW szDeviceTextDescription")); + LOG_STRW(pId2); + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId2; + } + else + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + break; + default: + LOG(("DeviceText %d", pSl->Parameters.QueryDeviceText.DeviceTextType)); + break; + } + } + else + LOG(("Invalid pointer %p", pId)); + } + break; + } + + case IRP_MN_QUERY_ID: + { + LOG(("IRP_MN_QUERY_ID: Irp->pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS && pDevObj) + { + WCHAR *pId = (WCHAR *)pIoStatus->Information; +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + WCHAR *pTmp; +#endif + if (RT_VALID_PTR(pId)) + { + KIRQL Iqrl = KeGetCurrentIrql(); + /* IRQL should be always passive here */ + ASSERT_WARN(Iqrl == PASSIVE_LEVEL, ("irql is not PASSIVE")); + + switch (pSl->Parameters.QueryId.IdType) + { + case BusQueryInstanceID: + LOG(("BusQueryInstanceID")); + LOG_STRW(pId); + break; + + case BusQueryDeviceID: + { + LOG(("BusQueryDeviceID")); + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(g_szBusQueryDeviceId)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + + BOOLEAN bFiltered = FALSE; + NTSTATUS Status = VBoxUsbFltPdoAdd(pDevObj, &bFiltered); + if (Status != STATUS_SUCCESS || !bFiltered) + { + if (Status == STATUS_SUCCESS) + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + else + { + WARN(("VBoxUsbFltPdoAdd for PDO (0x%p) failed Status 0x%x", pDevObj, Status)); + } + ExFreePool(pId); + break; + } + + LOG(("PDO (0x%p) is filtered", pDevObj)); + ExFreePool((PVOID)pIoStatus->Information); + memcpy(pId, g_szBusQueryDeviceId, sizeof(g_szBusQueryDeviceId)); + pIoStatus->Information = (ULONG_PTR)pId; + break; + } + case BusQueryHardwareIDs: + { + LOG(("BusQueryHardwareIDs")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + while (*pId) //MULTI_SZ + { + LOG_STRW(pId); + while (*pId) pId++; + pId++; + } +#endif + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(g_szBusQueryHardwareIDs)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + + BOOLEAN bFiltered = FALSE; + NTSTATUS Status = VBoxUsbFltPdoAdd(pDevObj, &bFiltered); + if (Status != STATUS_SUCCESS || !bFiltered) + { + if (Status == STATUS_SUCCESS) + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + else + { + WARN(("VBoxUsbFltPdoAdd for PDO (0x%p) failed Status 0x%x", pDevObj, Status)); + } + ExFreePool(pId); + break; + } + + LOG(("PDO (0x%p) is filtered", pDevObj)); + + memcpy(pId, g_szBusQueryHardwareIDs, sizeof(g_szBusQueryHardwareIDs)); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("NEW BusQueryHardwareIDs")); + pTmp = pId; + while (*pTmp) //MULTI_SZ + { + + LOG_STRW(pTmp); + while (*pTmp) pTmp++; + pTmp++; + } +#endif + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId; + break; + } + case BusQueryCompatibleIDs: + LOG(("BusQueryCompatibleIDs")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + while (*pId) //MULTI_SZ + { + LOG_STRW(pId); + while (*pId) pId++; + pId++; + } +#endif + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + { + LOG(("PDO (0x%p) is filtered", pDevObj)); + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(g_szBusQueryCompatibleIDs)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + memcpy(pId, g_szBusQueryCompatibleIDs, sizeof(g_szBusQueryCompatibleIDs)); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("NEW BusQueryCompatibleIDs")); + pTmp = pId; + while (*pTmp) //MULTI_SZ + { + LOG_STRW(pTmp); + while (*pTmp) pTmp++; + pTmp++; + } +#endif + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId; + } + else + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + break; + + default: + /** @todo r=bird: handle BusQueryContainerID and whatever else we might see */ + break; + } + } + else + { + LOG(("Invalid pointer %p", pId)); + } + } + break; + } + +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + switch(pSl->Parameters.QueryDeviceRelations.Type) + { + case BusRelations: + LOG(("BusRelations")); + + if (pIoStatus->Status == STATUS_SUCCESS) + { + PDEVICE_RELATIONS pRel = (PDEVICE_RELATIONS)pIoStatus->Information; + LOG(("pRel = %p", pRel)); + if (RT_VALID_PTR(pRel)) + { + for (unsigned i=0;iCount;i++) + { + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + LOG(("New PDO %p", pRel->Objects[i])); + } + } + else + LOG(("Invalid pointer %p", pRel)); + } + break; + case TargetDeviceRelation: + LOG(("TargetDeviceRelation")); + break; + case RemovalRelations: + LOG(("RemovalRelations")); + break; + case EjectionRelations: + LOG(("EjectionRelations")); + break; + default: + LOG(("QueryDeviceRelations.Type=%d", pSl->Parameters.QueryDeviceRelations.Type)); + } + break; + } + + case IRP_MN_QUERY_CAPABILITIES: + { + LOG(("IRP_MN_QUERY_CAPABILITIES: pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS) + { + PDEVICE_CAPABILITIES pCaps = pSl->Parameters.DeviceCapabilities.Capabilities; + if (RT_VALID_PTR(pCaps)) + { + LOG(("Caps.SilentInstall = %d", pCaps->SilentInstall)); + LOG(("Caps.UniqueID = %d", pCaps->UniqueID )); + LOG(("Caps.Address = %d", pCaps->Address )); + LOG(("Caps.UINumber = %d", pCaps->UINumber )); + } + else + LOG(("Invalid pointer %p", pCaps)); + } + break; + } + + default: + break; +#endif + } /*switch */ + + LOG(("Done returns %x (IRQL = %d)", pIoStatus->Status, KeGetCurrentIrql())); + return pIoStatus->Status; +} + +NTSTATUS _stdcall VBoxUsbPnPCompletion(DEVICE_OBJECT *pDevObj, IRP *pIrp, void *pvContext) +{ + LOG(("Completion PDO(0x%p), IRP(0x%p), Status(0x%x)", pDevObj, pIrp, pIrp->IoStatus.Status)); + ASSERT_WARN(pvContext, ("zero context")); + + PVBOXUSBHOOK_REQUEST pRequest = (PVBOXUSBHOOK_REQUEST)pvContext; + /* NOTE: despite a regular IRP processing the stack location in our completion + * differs from those of the PnP hook since the hook is invoked in the "context" of the calle, + * while the completion is in the "coller" context in terms of IRP, + * so the completion stack location is one level "up" here. + * + * Moreover we CAN NOT access irp stack location in the completion because we might not have one at all + * in case the hooked driver is at the top of the irp call stack + * + * This is why we use the stack location we saved on IRP way down. + * */ + PIO_STACK_LOCATION pSl = &pRequest->OldLocation; + ASSERT_WARN(pIrp == pRequest->pIrp, ("completed IRP(0x%x) not match request IRP(0x%x)", pIrp, pRequest->pIrp)); + /* NOTE: we can not rely on pDevObj passed in IoCompletion since it may be zero + * in case IRP was created with extra stack locations and the caller did not initialize + * the IO_STACK_LOCATION::DeviceObject */ + DEVICE_OBJECT *pRealDevObj = pRequest->pDevObj; +// Assert(!pDevObj || pDevObj == pRealDevObj); +// Assert(pSl->DeviceObject == pDevObj); + + switch(pSl->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_TEXT: + case IRP_MN_QUERY_ID: +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + case IRP_MN_QUERY_DEVICE_RELATIONS: + case IRP_MN_QUERY_CAPABILITIES: +#endif + if (NT_SUCCESS(pIrp->IoStatus.Status)) + { + vboxUsbMonHandlePnPIoctl(pRealDevObj, pSl, &pIrp->IoStatus); + } + else + { + ASSERT_WARN(pIrp->IoStatus.Status == STATUS_NOT_SUPPORTED, ("Irp failed with status(0x%x)", pIrp->IoStatus.Status)); + } + break; + + case IRP_MN_SURPRISE_REMOVAL: + case IRP_MN_REMOVE_DEVICE: + if (NT_SUCCESS(pIrp->IoStatus.Status)) + { + VBoxUsbFltPdoRemove(pRealDevObj); + } + else + { + AssertFailed(); + } + break; + + /* These two IRPs are received when the PnP subsystem has determined the id of the newly arrived device */ + /* IRP_MN_START_DEVICE only arrives if it's a USB device of a known class or with a present host driver */ + case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: + case IRP_MN_QUERY_RESOURCES: + /* There used to be code to support SUPUSBFLT_IOCTL_SET_NOTIFY_EVENT but it was not reliable. */ + + default: + break; + } + + LOG(("<==PnP: Mn(%s), PDO(0x%p), IRP(0x%p), Status(0x%x), Sl PDO(0x%p), Compl PDO(0x%p)", + vboxUsbDbgStrPnPMn(pSl->MinorFunction), + pRealDevObj, pIrp, pIrp->IoStatus.Status, + pSl->DeviceObject, pDevObj)); +#ifdef DEBUG_misha + NTSTATUS tmpStatus = pIrp->IoStatus.Status; +#endif + PVBOXUSBHOOK_ENTRY pHook = pRequest->pHook; + NTSTATUS Status = VBoxUsbHookRequestComplete(pHook, pDevObj, pIrp, pRequest); + VBoxUsbMonMemFree(pRequest); +#ifdef DEBUG_misha + if (Status != STATUS_MORE_PROCESSING_REQUIRED) + { + Assert(pIrp->IoStatus.Status == tmpStatus); + } +#endif + VBoxUsbHookRelease(pHook); + return Status; +} + +/** + * Device PnP hook + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS vboxUsbMonPnPHook(IN PVBOXUSBHOOK_ENTRY pHook, IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + LOG(("==>PnP: Mn(%s), PDO(0x%p), IRP(0x%p), Status(0x%x)", vboxUsbDbgStrPnPMn(IoGetCurrentIrpStackLocation(pIrp)->MinorFunction), pDevObj, pIrp, pIrp->IoStatus.Status)); + + if (!VBoxUsbHookRetain(pHook)) + { + WARN(("VBoxUsbHookRetain failed")); + return VBoxUsbHookRequestPassDownHookSkip(pHook, pDevObj, pIrp); + } + + PVBOXUSBHUB_PNPHOOK_COMPLETION pCompletion = (PVBOXUSBHUB_PNPHOOK_COMPLETION)VBoxUsbMonMemAlloc(sizeof (*pCompletion)); + if (!pCompletion) + { + WARN(("VBoxUsbMonMemAlloc failed")); + VBoxUsbHookRelease(pHook); + pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return STATUS_INSUFFICIENT_RESOURCES; + } + + NTSTATUS Status = VBoxUsbHookRequestPassDownHookCompletion(pHook, pDevObj, pIrp, VBoxUsbPnPCompletion, &pCompletion->Rq); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + if (Status != STATUS_PENDING) + { + LOG(("Request completed, Status(0x%x)", Status)); + VBoxUsbHookVerifyCompletion(pHook, &pCompletion->Rq, pIrp); + } + else + { + LOG(("Request pending")); + } +#endif + return Status; +} + +/** + * Device PnP hook stubs. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +#define VBOX_PNPHOOKSTUB(n) NTSTATUS _stdcall VBoxUsbMonPnPHook##n(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) \ +{ \ + return vboxUsbMonPnPHook(&g_VBoxUsbMonGlobals.pDrivers[n].UsbHubPnPHook.Hook, pDevObj, pIrp); \ +} + +#define VBOX_PNPHOOKSTUB_INIT(n) g_VBoxUsbMonGlobals.pDrivers[n].pfnHookStub = VBoxUsbMonPnPHook##n + +VBOX_PNPHOOKSTUB(0) +VBOX_PNPHOOKSTUB(1) +VBOX_PNPHOOKSTUB(2) +VBOX_PNPHOOKSTUB(3) +VBOX_PNPHOOKSTUB(4) +AssertCompile(VBOXUSBMON_MAXDRIVERS == 5); + +typedef struct VBOXUSBMONHOOKDRIVERWALKER +{ + PDRIVER_OBJECT pDrvObj; +} VBOXUSBMONHOOKDRIVERWALKER, *PVBOXUSBMONHOOKDRIVERWALKER; + +/** + * Logs an error to the system event log. + * + * @param ErrCode Error to report to event log. + * @param ReturnedStatus Error that was reported by the driver to the caller. + * @param uErrId Unique error id representing the location in the driver. + * @param cbDumpData Number of bytes at pDumpData. + * @param pDumpData Pointer to data that will be added to the message (see 'details' tab). + * + * NB: We only use IoLogMsg.dll as the message file, limiting + * ErrCode to status codes and messages defined in ntiologc.h + */ +static void vboxUsbMonLogError(NTSTATUS ErrCode, NTSTATUS ReturnedStatus, ULONG uErrId, USHORT cbDumpData, PVOID pDumpData) +{ + PIO_ERROR_LOG_PACKET pErrEntry; + + + /* Truncate dumps that do not fit into IO_ERROR_LOG_PACKET. */ + if (FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData > ERROR_LOG_MAXIMUM_SIZE) + cbDumpData = ERROR_LOG_MAXIMUM_SIZE - FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData); + + pErrEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(g_VBoxUsbMonGlobals.pDevObj, + FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData); + if (pErrEntry) + { + uint8_t *pDump = (uint8_t *)pErrEntry->DumpData; + if (cbDumpData) + memcpy(pDump, pDumpData, cbDumpData); + pErrEntry->MajorFunctionCode = 0; + pErrEntry->RetryCount = 0; + pErrEntry->DumpDataSize = cbDumpData; + pErrEntry->NumberOfStrings = 0; + pErrEntry->StringOffset = 0; + pErrEntry->ErrorCode = ErrCode; + pErrEntry->UniqueErrorValue = uErrId; + pErrEntry->FinalStatus = ReturnedStatus; + pErrEntry->IoControlCode = 0; + IoWriteErrorLogEntry(pErrEntry); + } + else + { + LOG(("Failed to allocate error log entry (cb=%d)\n", FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData)); + } +} + +static DECLCALLBACK(BOOLEAN) vboxUsbMonHookDrvObjWalker(PFILE_OBJECT pHubFile, PDEVICE_OBJECT pHubDo, PVOID pvContext) +{ + RT_NOREF2(pHubFile, pvContext); + PDRIVER_OBJECT pDrvObj = pHubDo->DriverObject; + + /* First we try to figure out if we are already hooked to this driver. */ + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + if (pDrvObj == g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + LOG(("Found %p at pDrivers[%d]\n", pDrvObj, i)); + /* We've already hooked to this one -- nothing to do. */ + return TRUE; + } + /* We are not hooked yet, find an empty slot. */ + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + { + if (!g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + /* Found an emtpy slot, use it. */ + g_VBoxUsbMonGlobals.pDrivers[i].DriverObject = pDrvObj; + ObReferenceObject(pDrvObj); + LOG(("pDrivers[%d] = %p, installing the hook...\n", i, pDrvObj)); + VBoxUsbHookInit(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook, + pDrvObj, + IRP_MJ_PNP, + g_VBoxUsbMonGlobals.pDrivers[i].pfnHookStub); + VBoxUsbHookInstall(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook); + return TRUE; /* Must continue to find all drivers. */ + } + if (pDrvObj == g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + LOG(("Found %p at pDrivers[%d]\n", pDrvObj, i)); + /* We've already hooked to this one -- nothing to do. */ + return TRUE; + } + } + /* No empty slots! No reason to continue. */ + LOG(("No empty slots!\n")); + ANSI_STRING ansiDrvName; + NTSTATUS Status = RtlUnicodeStringToAnsiString(&ansiDrvName, &pDrvObj->DriverName, true); + if (Status != STATUS_SUCCESS) + { + ansiDrvName.Length = 0; + LOG(("RtlUnicodeStringToAnsiString failed with 0x%x", Status)); + } + vboxUsbMonLogError(IO_ERR_INSUFFICIENT_RESOURCES, STATUS_SUCCESS, 1, ansiDrvName.Length, ansiDrvName.Buffer); + if (Status == STATUS_SUCCESS) + RtlFreeAnsiString(&ansiDrvName); + return FALSE; +} + +/** + * Finds all USB drivers in the system and installs hooks if haven't done already. + */ +static NTSTATUS vboxUsbMonInstallAllHooks() +{ + vboxUsbMonHubDevWalk(vboxUsbMonHookDrvObjWalker, NULL); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbMonHookCheckInit() +{ + static bool fIsHookInited = false; + if (fIsHookInited) + { + LOG(("hook inited already, success")); + return STATUS_SUCCESS; + } + return vboxUsbMonInstallAllHooks(); +} + +static NTSTATUS vboxUsbMonHookInstall() +{ + /* Nothing to do here as we have already installed all hooks in vboxUsbMonHookCheckInit(). */ + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbMonHookUninstall() +{ +#ifdef VBOXUSBMON_DBG_NO_PNPHOOK + return STATUS_SUCCESS; +#else + NTSTATUS Status = STATUS_SUCCESS; + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + { + if (g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + Assert(g_VBoxUsbMonGlobals.pDrivers[i].DriverObject == g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook.pDrvObj); + LOG(("Unhooking from %p...\n", g_VBoxUsbMonGlobals.pDrivers[i].DriverObject)); + Status = VBoxUsbHookUninstall(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook); + if (!NT_SUCCESS(Status)) + { + /* + * We failed to uninstall the hook, so we keep the reference to the driver + * in order to prevent another driver re-using this slot because we are + * going to mark this hook as fUninitFailed. + */ + //AssertMsgFailed(("usbhub pnp unhook failed, setting the fUninitFailed flag, the current value of fUninitFailed (%d)", g_VBoxUsbMonGlobals.UsbHubPnPHook.fUninitFailed)); + LOG(("usbhub pnp unhook failed, setting the fUninitFailed flag, the current value of fUninitFailed (%d)", g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.fUninitFailed)); + g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.fUninitFailed = true; + } + else + { + /* The hook was removed successfully, now we can forget about this driver. */ + ObDereferenceObject(g_VBoxUsbMonGlobals.pDrivers[i].DriverObject); + g_VBoxUsbMonGlobals.pDrivers[i].DriverObject = NULL; + } + } + } + return Status; +#endif +} + + +static NTSTATUS vboxUsbMonCheckTermStuff() +{ + NTSTATUS Status = KeWaitForSingleObject(&g_VBoxUsbMonGlobals.OpenSynchEvent, + Executive, KernelMode, + FALSE, /* BOOLEAN Alertable */ + NULL /* IN PLARGE_INTEGER Timeout */ + ); + AssertRelease(Status == STATUS_SUCCESS); + + do + { + if (--g_VBoxUsbMonGlobals.cOpens) + break; + + Status = vboxUsbMonHookUninstall(); + + NTSTATUS tmpStatus = VBoxUsbFltTerm(); + if (!NT_SUCCESS(tmpStatus)) + { + /* this means a driver state is screwed up, KeBugCheckEx here ? */ + AssertReleaseFailed(); + } + } while (0); + + KeSetEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, 0, FALSE); + + return Status; +} + +static NTSTATUS vboxUsbMonCheckInitStuff() +{ + NTSTATUS Status = KeWaitForSingleObject(&g_VBoxUsbMonGlobals.OpenSynchEvent, + Executive, KernelMode, + FALSE, /* BOOLEAN Alertable */ + NULL /* IN PLARGE_INTEGER Timeout */ + ); + if (Status == STATUS_SUCCESS) + { + do + { + if (g_VBoxUsbMonGlobals.cOpens++) + { + LOG(("opens: %d, success", g_VBoxUsbMonGlobals.cOpens)); + break; + } + + Status = VBoxUsbFltInit(); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbMonHookCheckInit(); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbMonHookInstall(); + if (NT_SUCCESS(Status)) + { + Status = STATUS_SUCCESS; + LOG(("succeded!!")); + break; + } + else + { + WARN(("vboxUsbMonHookInstall failed, Status (0x%x)", Status)); + } + } + else + { + WARN(("vboxUsbMonHookCheckInit failed, Status (0x%x)", Status)); + } + VBoxUsbFltTerm(); + } + else + { + WARN(("VBoxUsbFltInit failed, Status (0x%x)", Status)); + } + + --g_VBoxUsbMonGlobals.cOpens; + Assert(!g_VBoxUsbMonGlobals.cOpens); + } while (0); + + KeSetEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, 0, FALSE); + } + else + { + WARN(("KeWaitForSingleObject failed, Status (0x%x)", Status)); + } + return Status; +} + +static NTSTATUS vboxUsbMonContextCreate(PVBOXUSBMONCTX *ppCtx) +{ + NTSTATUS Status; + *ppCtx = NULL; + PVBOXUSBMONCTX pFileCtx = (PVBOXUSBMONCTX)VBoxUsbMonMemAllocZ(sizeof (*pFileCtx)); + if (pFileCtx) + { + Status = vboxUsbMonCheckInitStuff(); + if (Status == STATUS_SUCCESS) + { + Status = VBoxUsbFltCreate(&pFileCtx->FltCtx); + if (Status == STATUS_SUCCESS) + { + *ppCtx = pFileCtx; + LOG(("succeeded!!")); + return STATUS_SUCCESS; + } + else + { + WARN(("VBoxUsbFltCreate failed")); + } + vboxUsbMonCheckTermStuff(); + } + else + { + WARN(("vboxUsbMonCheckInitStuff failed")); + } + VBoxUsbMonMemFree(pFileCtx); + } + else + { + WARN(("VBoxUsbMonMemAllocZ failed")); + Status = STATUS_NO_MEMORY; + } + + return Status; +} + +static NTSTATUS vboxUsbMonContextClose(PVBOXUSBMONCTX pCtx) +{ + NTSTATUS Status = VBoxUsbFltClose(&pCtx->FltCtx); + if (Status == STATUS_SUCCESS) + { + Status = vboxUsbMonCheckTermStuff(); + Assert(Status == STATUS_SUCCESS); + /* ignore the failure */ + VBoxUsbMonMemFree(pCtx); + } + + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + Assert(pFileObj->FsContext); + PVBOXUSBMONCTX pCtx = (PVBOXUSBMONCTX)pFileObj->FsContext; + + LOG(("VBoxUsbMonClose")); + + NTSTATUS Status = vboxUsbMonContextClose(pCtx); + if (Status != STATUS_SUCCESS) + { + WARN(("vboxUsbMonContextClose failed, Status (0x%x), prevent unload", Status)); + if (!InterlockedExchange(&g_VBoxUsbMonGlobals.ulPreventUnloadOn, 1)) + { + LOGREL(("ulPreventUnloadOn not set, preventing unload")); + UNICODE_STRING UniName; + PDEVICE_OBJECT pTmpDevObj; + RtlInitUnicodeString(&UniName, USBMON_DEVICE_NAME_NT); + NTSTATUS tmpStatus = IoGetDeviceObjectPointer(&UniName, FILE_ALL_ACCESS, &g_VBoxUsbMonGlobals.pPreventUnloadFileObj, &pTmpDevObj); + AssertRelease(NT_SUCCESS(tmpStatus)); + AssertRelease(pTmpDevObj == pDevObj); + } + else + { + WARN(("ulPreventUnloadOn already set")); + } + LOG(("success!!")); + Status = STATUS_SUCCESS; + } + pFileObj->FsContext = NULL; + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + + +static NTSTATUS _stdcall VBoxUsbMonCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + NTSTATUS Status; + + LOG(("VBoxUSBMonCreate")); + + if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) + { + WARN(("trying to open as a directory")); + pIrp->IoStatus.Status = STATUS_NOT_A_DIRECTORY; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return STATUS_NOT_A_DIRECTORY; + } + + pFileObj->FsContext = NULL; + PVBOXUSBMONCTX pCtx = NULL; + Status = vboxUsbMonContextCreate(&pCtx); + if (Status == STATUS_SUCCESS) + { + Assert(pCtx); + pFileObj->FsContext = pCtx; + } + else + { + WARN(("vboxUsbMonContextCreate failed Status (0x%x)", Status)); + } + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + +static int VBoxUsbMonFltAdd(PVBOXUSBMONCTX pContext, PUSBFILTER pFilter, uintptr_t *pId) +{ +#ifdef VBOXUSBMON_DBG_NO_FILTERS + static uintptr_t idDummy = 1; + *pId = idDummy; + ++idDummy; + return VINF_SUCCESS; +#else + int rc = VBoxUsbFltAdd(&pContext->FltCtx, pFilter, pId); + return rc; +#endif +} + +static int VBoxUsbMonFltRemove(PVBOXUSBMONCTX pContext, uintptr_t uId) +{ +#ifdef VBOXUSBMON_DBG_NO_FILTERS + return VINF_SUCCESS; +#else + int rc = VBoxUsbFltRemove(&pContext->FltCtx, uId); + return rc; +#endif +} + +static NTSTATUS VBoxUsbMonRunFilters(PVBOXUSBMONCTX pContext) +{ + NTSTATUS Status = VBoxUsbFltFilterCheck(&pContext->FltCtx); + return Status; +} + +static NTSTATUS VBoxUsbMonGetDevice(PVBOXUSBMONCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo) +{ + NTSTATUS Status = VBoxUsbFltGetDevice(&pContext->FltCtx, hDevice, pInfo); + return Status; +} + +static NTSTATUS vboxUsbMonIoctlDispatch(PVBOXUSBMONCTX pContext, ULONG Ctl, PVOID pvBuffer, ULONG cbInBuffer, + ULONG cbOutBuffer, ULONG_PTR *pInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG_PTR Info = 0; + switch (Ctl) + { + case SUPUSBFLT_IOCTL_GET_VERSION: + { + PUSBSUP_VERSION pOut = (PUSBSUP_VERSION)pvBuffer; + + LOG(("SUPUSBFLT_IOCTL_GET_VERSION")); + if (!pvBuffer || cbOutBuffer != sizeof(*pOut) || cbInBuffer != 0) + { + WARN(("SUPUSBFLT_IOCTL_GET_VERSION: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, 0, cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + pOut->u32Major = USBMON_MAJOR_VERSION; + pOut->u32Minor = USBMON_MINOR_VERSION; + Info = sizeof (*pOut); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_ADD_FILTER: + { + PUSBFILTER pFilter = (PUSBFILTER)pvBuffer; + PUSBSUP_FLTADDOUT pOut = (PUSBSUP_FLTADDOUT)pvBuffer; + uintptr_t uId = 0; + int rc; + if (RT_UNLIKELY(!pvBuffer || cbInBuffer != sizeof (*pFilter) || cbOutBuffer != sizeof (*pOut))) + { + WARN(("SUPUSBFLT_IOCTL_ADD_FILTER: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, sizeof (*pFilter), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + + rc = VBoxUsbMonFltAdd(pContext, pFilter, &uId); + pOut->rc = rc; + pOut->uId = uId; + Info = sizeof (*pOut); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_REMOVE_FILTER: + { + uintptr_t *pIn = (uintptr_t *)pvBuffer; + int *pRc = (int *)pvBuffer; + + if (!pvBuffer || cbInBuffer != sizeof (*pIn) || (cbOutBuffer && cbOutBuffer != sizeof (*pRc))) + { + WARN(("SUPUSBFLT_IOCTL_REMOVE_FILTER: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, sizeof (*pIn), cbOutBuffer, 0)); + Status = STATUS_INVALID_PARAMETER; + break; + } + LOG(("SUPUSBFLT_IOCTL_REMOVE_FILTER %x", *pIn)); + int rc = VBoxUsbMonFltRemove(pContext, *pIn); + if (cbOutBuffer) + { + /* we've validated that already */ + Assert(cbOutBuffer == (ULONG)*pRc); + *pRc = rc; + Info = sizeof (*pRc); + } + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_RUN_FILTERS: + { + if (pvBuffer || cbInBuffer || cbOutBuffer) + { + WARN(("SUPUSBFLT_IOCTL_RUN_FILTERS: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, 0, cbOutBuffer, 0)); + Status = STATUS_INVALID_PARAMETER; + break; + } + LOG(("SUPUSBFLT_IOCTL_RUN_FILTERS ")); + Status = VBoxUsbMonRunFilters(pContext); + ASSERT_WARN(Status != STATUS_PENDING, ("status pending!")); + break; + } + + case SUPUSBFLT_IOCTL_GET_DEVICE: + { + HVBOXUSBDEVUSR hDevice; + PUSBSUP_GETDEV_MON pOut = (PUSBSUP_GETDEV_MON)pvBuffer; + if (!pvBuffer || cbInBuffer != sizeof (hDevice) || cbOutBuffer < sizeof (*pOut)) + { + WARN(("SUPUSBFLT_IOCTL_GET_DEVICE: Invalid input/output sizes! cbIn=%d expected %d. cbOut=%d expected >= %d.", + cbInBuffer, sizeof (hDevice), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + hDevice = *(HVBOXUSBDEVUSR*)pvBuffer; + if (!hDevice) + { + WARN(("SUPUSBFLT_IOCTL_GET_DEVICE: hDevice is NULL!", + cbInBuffer, sizeof (hDevice), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = VBoxUsbMonGetDevice(pContext, hDevice, pOut); + + if (NT_SUCCESS(Status)) + { + Info = sizeof (*pOut); + } + else + { + WARN(("VBoxUsbMonGetDevice fail 0x%x", Status)); + } + break; + } + + default: + WARN(("Unknown code 0x%x", Ctl)); + Status = STATUS_INVALID_PARAMETER; + break; + } + + ASSERT_WARN(Status != STATUS_PENDING, ("Status pending!")); + + *pInfo = Info; + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + ULONG_PTR Info = 0; + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pSl->FileObject; + Assert(pFileObj); + Assert(pFileObj->FsContext); + PVBOXUSBMONCTX pCtx = (PVBOXUSBMONCTX)pFileObj->FsContext; + Assert(pCtx); + Status = vboxUsbMonIoctlDispatch(pCtx, + pSl->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, + pSl->Parameters.DeviceIoControl.InputBufferLength, + pSl->Parameters.DeviceIoControl.OutputBufferLength, + &Info); + ASSERT_WARN(Status != STATUS_PENDING, ("Status pending")); + + IoReleaseRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + } + else + { + WARN(("IoAcquireRemoveLock failed Status (0x%x)", Status)); + } + + pIrp->IoStatus.Information = Info; + pIrp->IoStatus.Status = Status; + IoCompleteRequest (pIrp, IO_NO_INCREMENT); + return Status; +} + +static NTSTATUS vboxUsbMonInternalIoctlDispatch(ULONG Ctl, PVOID pvBuffer, ULONG_PTR *pInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + *pInfo = 0; + switch (Ctl) + { + case VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION: + { + PVBOXUSBIDC_VERSION pOut = (PVBOXUSBIDC_VERSION)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + pOut->u32Major = VBOXUSBIDC_VERSION_MAJOR; + pOut->u32Minor = VBOXUSBIDC_VERSION_MINOR; + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP: + { + PVBOXUSBIDC_PROXY_STARTUP pOut = (PVBOXUSBIDC_PROXY_STARTUP)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + PDEVICE_OBJECT pDevObj = pOut->u.pPDO; + pOut->u.hDev = VBoxUsbFltProxyStarted(pDevObj); + + /* If we couldn't find the PDO in our list, that's a real problem and + * the capturing will not really work. Log an error. + */ + if (!pOut->u.hDev) + vboxUsbMonLogError(IO_ERR_DRIVER_ERROR, STATUS_SUCCESS, 2, sizeof("INTERNAL_IOCTL_PROXY_STARTUP"), "INTERNAL_IOCTL_PROXY_STARTUP"); + + ASSERT_WARN(pOut->u.hDev, ("zero hDev")); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN: + { + PVBOXUSBIDC_PROXY_TEARDOWN pOut = (PVBOXUSBIDC_PROXY_TEARDOWN)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + ASSERT_WARN(pOut->hDev, ("zero hDev")); + VBoxUsbFltProxyStopped(pOut->hDev); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + default: + { + WARN(("Unknown code 0x%x", Ctl)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + ULONG_PTR Info = 0; + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + Status = vboxUsbMonInternalIoctlDispatch(pSl->Parameters.DeviceIoControl.IoControlCode, + pSl->Parameters.Others.Argument1, + &Info); + Assert(Status != STATUS_PENDING); + + IoReleaseRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + } + + pIrp->IoStatus.Information = Info; + pIrp->IoStatus.Status = Status; + IoCompleteRequest (pIrp, IO_NO_INCREMENT); + return Status; +} + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +static void _stdcall VBoxUsbMonUnload(PDRIVER_OBJECT pDrvObj) +{ + RT_NOREF1(pDrvObj); + LOG(("VBoxUSBMonUnload pDrvObj (0x%p)", pDrvObj)); + + IoReleaseRemoveLockAndWait(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + + Assert(!g_VBoxUsbMonGlobals.cOpens); + + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, USBMON_DEVICE_NAME_DOS); + IoDeleteSymbolicLink(&DosName); + + IoDeleteDevice(g_VBoxUsbMonGlobals.pDevObj); + + /* cleanup the logger */ + PRTLOGGER pLogger = RTLogRelSetDefaultInstance(NULL); + if (pLogger) + RTLogDestroy(pLogger); + pLogger = RTLogSetDefaultInstance(NULL); + if (pLogger) + RTLogDestroy(pLogger); +} + +RT_C_DECLS_BEGIN +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +RT_C_DECLS_END + +/** + * 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); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + RTLogGroupSettings(0, "+default.e.l.f.l2.l3"); + RTLogDestinations(0, "debugger"); +#endif + + LOGREL(("Built %s %s", __DATE__, __TIME__)); + + memset (&g_VBoxUsbMonGlobals, 0, sizeof (g_VBoxUsbMonGlobals)); + + VBOX_PNPHOOKSTUB_INIT(0); + VBOX_PNPHOOKSTUB_INIT(1); + VBOX_PNPHOOKSTUB_INIT(2); + VBOX_PNPHOOKSTUB_INIT(3); + VBOX_PNPHOOKSTUB_INIT(4); + AssertCompile(VBOXUSBMON_MAXDRIVERS == 5); + + KeInitializeEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, SynchronizationEvent, TRUE /* signaled */); + IoInitializeRemoveLock(&g_VBoxUsbMonGlobals.RmLock, VBOXUSBMON_MEMTAG, 1, 100); + UNICODE_STRING DevName; + PDEVICE_OBJECT pDevObj; + /* create the device */ + RtlInitUnicodeString(&DevName, USBMON_DEVICE_NAME_NT); + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + if (NT_SUCCESS(Status)) + { + Status = IoCreateDevice(pDrvObj, sizeof (VBOXUSBMONINS), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj); + if (NT_SUCCESS(Status)) + { + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, USBMON_DEVICE_NAME_DOS); + Status = IoCreateSymbolicLink(&DosName, &DevName); + if (NT_SUCCESS(Status)) + { + PVBOXUSBMONINS pDevExt = (PVBOXUSBMONINS)pDevObj->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); + + pDrvObj->DriverUnload = VBoxUsbMonUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxUsbMonCreate; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxUsbMonClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxUsbMonDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxUsbMonInternalDeviceControl; + + g_VBoxUsbMonGlobals.pDevObj = pDevObj; + LOG(("VBoxUSBMon::DriverEntry returning STATUS_SUCCESS")); + return STATUS_SUCCESS; + } + IoDeleteDevice(pDevObj); + } + IoReleaseRemoveLockAndWait(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + } + + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h new file mode 100644 index 00000000..e67c6856 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h @@ -0,0 +1,77 @@ +/* $Id: VBoxUsbMon.h $ */ +/** @file + * VBox USB Monitor + */ +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef DEBUG +/* disables filters */ +//#define VBOXUSBMON_DBG_NO_FILTERS +/* disables pnp hooking */ +//#define VBOXUSBMON_DBG_NO_PNPHOOK +#endif + +#include "../../../win/VBoxDbgLog.h" +#include "../cmn/VBoxDrvTool.h" +#include "../cmn/VBoxUsbTool.h" + +#include "VBoxUsbHook.h" +#include "VBoxUsbFlt.h" + +PVOID VBoxUsbMonMemAlloc(SIZE_T cbBytes); +PVOID VBoxUsbMonMemAllocZ(SIZE_T cbBytes); +VOID VBoxUsbMonMemFree(PVOID pvMem); + +NTSTATUS VBoxUsbMonGetDescriptor(PDEVICE_OBJECT pDevObj, void *buffer, int size, int type, int index, int language_id); +NTSTATUS VBoxUsbMonQueryBusRelations(PDEVICE_OBJECT pDevObj, PFILE_OBJECT pFileObj, PDEVICE_RELATIONS *pDevRelations); + +void vboxUsbDbgPrintUnicodeString(PUNICODE_STRING pUnicodeString); + +typedef DECLCALLBACKTYPE(BOOLEAN, FNVBOXUSBMONDEVWALKER,(PFILE_OBJECT pHubFile, PDEVICE_OBJECT pHubDo, PVOID pvContext)); +typedef FNVBOXUSBMONDEVWALKER *PFNVBOXUSBMONDEVWALKER; + +VOID vboxUsbMonHubDevWalk(PFNVBOXUSBMONDEVWALKER pfnWalker, PVOID pvWalker); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc new file mode 100644 index 00000000..f22b3dd8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc @@ -0,0 +1,70 @@ +/* $Id: VBoxUsbMon.rc $ */ +/** @file + * VBoxUSBMon - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#include +#include + +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 USB Monitor Driver\0" + VALUE "InternalName", "VBoxUSBMon\0" + VALUE "OriginalFilename", "VBoxUSBMon.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/VBoxUSB/win/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp b/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp new file mode 100644 index 00000000..6b75c07f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp @@ -0,0 +1,384 @@ +/* $Id: USBTest.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver 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 . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Handle to the open device. */ +static HANDLE g_hUSBMonitor = INVALID_HANDLE_VALUE; +/** Flags whether or not we started the service. */ +static bool g_fStartedService = false; + + +/** + * Attempts to start the service, creating it if necessary. + * + * @returns 0 on success. + * @returns -1 on failure. + * @param fRetry Indicates retry call. + */ +int usbMonStartService(void) +{ + HRESULT hr = VBoxDrvCfgSvcStart(USBMON_SERVICE_NAME_W); + if (hr != S_OK) + { + AssertMsgFailed(("couldn't start service, hr (0x%x)\n", hr)); + return -1; + } + return 0; +} + +/** + * Stops a possibly running service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +int usbMonStopService(void) +{ + RTPrintf("usbMonStopService\n"); + + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", GetLastError())); + if (hSMgr) + { + SC_HANDLE hService = OpenServiceW(hSMgr, USBMON_SERVICE_NAME_W, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (hService) + { + /* + * Stop the service. + */ + SERVICE_STATUS Status; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + 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 = 0; + else + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + } + else + AssertMsgFailed(("ControlService failed with LastError=%Rwa. status=%d\n", GetLastError(), Status.dwCurrentState)); + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", GetLastError())); + CloseServiceHandle(hSMgr); + } + return rc; +} + +/** + * Release specified USB device to the host. + * + * @returns VBox status code + * @param usVendorId Vendor id + * @param usProductId Product id + * @param usRevision Revision + */ +int usbMonReleaseDevice(USHORT usVendorId, USHORT usProductId, USHORT usRevision) +{ + USBSUP_RELEASE release; + DWORD cbReturned = 0; + + RTPrintf("usbLibReleaseDevice %x %x %x\n", usVendorId, usProductId, usRevision); + + release.usVendorId = usVendorId; + release.usProductId = usProductId; + release.usRevision = usRevision; + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_RELEASE_DEVICE, &release, sizeof(release), NULL, 0, &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + + return VINF_SUCCESS; +} + + +/** + * Add USB device filter + * + * @returns VBox status code. + * @param usVendorId Vendor id + * @param usProductId Product id + * @param usRevision Revision + * @param ppID Pointer to filter id + */ +int usbMonInsertFilter(USHORT usVendorId, USHORT usProductId, USHORT usRevision, void **ppID) +{ + USBFILTER filter; + USBSUP_FLTADDOUT flt_add; + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + RTPrintf("usblibInsertFilter %04X %04X %04X\n", usVendorId, usProductId, usRevision); + + USBFilterInit(&filter, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&filter, USBFILTERIDX_VENDOR_ID, usVendorId, true); + USBFilterSetNumExact(&filter, USBFILTERIDX_PRODUCT_ID, usProductId, true); + USBFilterSetNumExact(&filter, USBFILTERIDX_DEVICE_REV, usRevision, true); + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_ADD_FILTER, &filter, sizeof(filter), &flt_add, sizeof(flt_add), &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + *ppID = (void *)flt_add.uId; + return VINF_SUCCESS; +} + +/** + * Applies existing filters to currently plugged-in USB devices + * + * @returns VBox status code. + */ +int usbMonRunFilters(void) +{ + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_RUN_FILTERS, NULL, 0, NULL, 0, &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + return VINF_SUCCESS; +} + +/** + * Remove USB device filter + * + * @returns VBox status code. + * @param aID Filter id + */ +int usbMonRemoveFilter (void *aID) +{ + uintptr_t uId; + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + RTPrintf("usblibRemoveFilter %p\n", aID); + + uId = (uintptr_t)aID; + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_REMOVE_FILTER, &uId, sizeof(uId), NULL, 0,&cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + return VINF_SUCCESS; +} + +/** + * Initialize the USB monitor + * + * @returns VBox status code. + */ +int usbMonitorInit() +{ + int rc; + USBSUP_VERSION version = {0}; + DWORD cbReturned; + + RTPrintf("usbproxy: usbLibInit\n"); + + g_hUSBMonitor = CreateFile (USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM, + NULL); // No template file + + if (g_hUSBMonitor == INVALID_HANDLE_VALUE) + { + usbMonStartService(); + + g_hUSBMonitor = CreateFile (USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM, + NULL); // No template file + + if (g_hUSBMonitor == INVALID_HANDLE_VALUE) + { + /* AssertFailed(); */ + RTPrintf("usbproxy: Unable to open filter driver!! (rc=%lu)\n", GetLastError()); + rc = VERR_FILE_NOT_FOUND; + goto failure; + } + } + + /* + * Check the version + */ + cbReturned = 0; + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_GET_VERSION, NULL, 0,&version, sizeof(version), &cbReturned, NULL)) + { + RTPrintf("usbproxy: Unable to query filter version!! (rc=%lu)\n", GetLastError()); + rc = VERR_VERSION_MISMATCH; + goto failure; + } + + if ( version.u32Major != USBMON_MAJOR_VERSION +#if USBMON_MINOR_VERSION != 0 + || version.u32Minor < USBMON_MINOR_VERSION +#endif + ) + { + RTPrintf("usbproxy: Filter driver version mismatch!!\n"); + rc = VERR_VERSION_MISMATCH; + goto failure; + } + + return VINF_SUCCESS; + +failure: + if (g_hUSBMonitor != INVALID_HANDLE_VALUE) + { + CloseHandle(g_hUSBMonitor); + g_hUSBMonitor = INVALID_HANDLE_VALUE; + } + return rc; +} + + + +/** + * Terminate the USB monitor + * + * @returns VBox status code. + */ +int usbMonitorTerm() +{ + if (g_hUSBMonitor != INVALID_HANDLE_VALUE) + { + CloseHandle(g_hUSBMonitor); + g_hUSBMonitor = INVALID_HANDLE_VALUE; + } + /* + * If we started the service we might consider stopping it too. + * + * Since this won't work unless the process starting it is the + * last user we might wanna skip this... + */ + if (g_fStartedService) + { + usbMonStopService(); + g_fStartedService = false; + } + + return VINF_SUCCESS; +} + + +int __cdecl main(int argc, char **argv) +{ + int rc; + int c; + RT_NOREF2(argc, argv); + + RTPrintf("USB test\n"); + + rc = usbMonitorInit(); + AssertRC(rc); + + void *pId1, *pId2, *pId3; + + usbMonInsertFilter(0x0529, 0x0514, 0x0100, &pId1); + usbMonInsertFilter(0x0A16, 0x2499, 0x0100, &pId2); + usbMonInsertFilter(0x80EE, 0x0030, 0x0110, &pId3); + + RTPrintf("Waiting to capture devices... enter 'r' to run filters\n"); + c = RTStrmGetCh(g_pStdIn); + if (c == 'r') + { + usbMonRunFilters(); + RTPrintf("Waiting to capture devices...\n"); + RTStrmGetCh(g_pStdIn); /* eat the '\n' */ + RTStrmGetCh(g_pStdIn); /* wait for more input */ + } + + RTPrintf("Releasing device\n"); + usbMonReleaseDevice(0xA16, 0x2499, 0x100); + + usbMonRemoveFilter(pId1); + usbMonRemoveFilter(pId2); + usbMonRemoveFilter(pId3); + + rc = usbMonitorTerm(); + + return 0; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def b/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def new file mode 100644 index 00000000..d3a618ab --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def @@ -0,0 +1,74 @@ +; $Id: usbd.def $ +;; @file +; Export 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 . +; +; 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 +; + +LIBRARY USBD.SYS + +EXPORTS +USBD_AllocateDeviceName +USBD_CalculateUsbBandwidth +USBD_CompleteRequest +USBD_CreateConfigurationRequest +USBD_CreateDevice +USBD_Debug_GetHeap +USBD_Debug_LogEntry +USBD_Debug_RetHeap +USBD_Dispatch +USBD_FreeDeviceMutex +USBD_FreeDeviceName +USBD_GetDeviceInformation +USBD_GetInterfaceLength +USBD_GetPdoRegistryParameter +USBD_GetSuspendPowerState +USBD_GetUSBDIVersion +USBD_InitializeDevice +USBD_MakePdoName +USBD_ParseConfigurationDescriptor +USBD_QueryBusTime +USBD_RegisterHcDeviceCapabilities +USBD_RegisterHcFilter +USBD_RegisterHostController +USBD_RemoveDevice +USBD_RestoreDevice +USBD_SetSuspendPowerState +USBD_WaitDeviceMutex +_USBD_CreateConfigurationRequestEx@8 +_USBD_ParseDescriptors@16 +_USBD_ParseConfigurationDescriptorEx@28 + +; win64 strips the _stdcall postfix +USBD_CreateConfigurationRequestEx +USBD_ParseConfigurationDescriptorEx +USBD_ParseDescriptors diff --git a/src/VBox/HostDrivers/adpctl/Makefile.kmk b/src/VBox/HostDrivers/adpctl/Makefile.kmk new file mode 100644 index 00000000..7156fbf7 --- /dev/null +++ b/src/VBox/HostDrivers/adpctl/Makefile.kmk @@ -0,0 +1,44 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for VBoxAdpCtl +# +# VBoxAdpCtl is a tool for configuring vboxnetX adapters. +# + +# +# 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 . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +PROGRAMS += VBoxNetAdpCtl +### Another template? We must *not* set RPATH! +ifneq ($(KBUILD_TYPE),debug) + VBoxNetAdpCtl_TEMPLATE = VBoxR3SetUidToRoot + VBoxNetAdpCtl_LIBS += $(LIB_RUNTIME) +else + VBoxNetAdpCtl_TEMPLATE = VBoxR3Static +endif +VBoxNetAdpCtl_SOURCES = VBoxNetAdpCtl.cpp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp b/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp new file mode 100644 index 00000000..5433c969 --- /dev/null +++ b/src/VBox/HostDrivers/adpctl/VBoxNetAdpCtl.cpp @@ -0,0 +1,1178 @@ +/* $Id: VBoxNetAdpCtl.cpp $ */ +/** @file + * Apps - VBoxAdpCtl, Configuration tool for vboxnetX adapters. + */ + +/* + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef RT_OS_LINUX +# include +# include +# include +/* Older versions of ethtool.h rely on these: */ +typedef unsigned long long u64; +typedef __uint32_t u32; +typedef __uint16_t u16; +typedef __uint8_t u8; +# include /* for INT_MAX */ +# include +# include +#endif +#ifdef RT_OS_SOLARIS +# include +#endif + +/** @todo Error codes must be moved to some header file */ +#define ADPCTLERR_BAD_NAME 2 +#define ADPCTLERR_NO_CTL_DEV 3 +#define ADPCTLERR_IOCTL_FAILED 4 +#define ADPCTLERR_SOCKET_FAILED 5 + +/** @todo These are duplicates from src/VBox/HostDrivers/VBoxNetAdp/VBoxNetAdpInternal.h */ +#define VBOXNETADP_CTL_DEV_NAME "/dev/vboxnetctl" +#define VBOXNETADP_MAX_INSTANCES 128 +#define VBOXNETADP_NAME "vboxnet" +#define VBOXNETADP_MAX_NAME_LEN 32 +#define VBOXNETADP_CTL_ADD _IOWR('v', 1, VBOXNETADPREQ) +#define VBOXNETADP_CTL_REMOVE _IOW('v', 2, VBOXNETADPREQ) +typedef struct VBoxNetAdpReq +{ + char szName[VBOXNETADP_MAX_NAME_LEN]; +} VBOXNETADPREQ; +typedef VBOXNETADPREQ *PVBOXNETADPREQ; + +#define VBOXADPCTL_IFCONFIG_PATH1 "/sbin/ifconfig" +#define VBOXADPCTL_IFCONFIG_PATH2 "/bin/ifconfig" + +bool verbose; +bool dry_run; + + +static int usage(void) +{ + fprintf(stderr, "Usage: VBoxNetAdpCtl
([netmask
] | remove)\n"); + fprintf(stderr, " | VBoxNetAdpCtl [] add\n"); + fprintf(stderr, " | VBoxNetAdpCtl remove\n"); + return EXIT_FAILURE; +} + + +/* + * A wrapper on standard list that provides '<<' operator for adding several list members in a single + * line dynamically. For example: "CmdList(arg1) << arg2 << arg3" produces a list with three members. + */ +class CmdList +{ +public: + /** Creates an empty list. */ + CmdList() {}; + /** Creates a list with a single member. */ + CmdList(const char *pcszCommand) { m_list.push_back(pcszCommand); }; + /** Provides access to the underlying standard list. */ + const std::list& getList(void) const { return m_list; }; + /** Adds a member to the list. */ + CmdList& operator<<(const char *pcszArgument); +private: + std::listm_list; +}; + +CmdList& CmdList::operator<<(const char *pcszArgument) +{ + m_list.push_back(pcszArgument); + return *this; +} + +/** Simple helper to distinguish IPv4 and IPv6 addresses. */ +inline bool isAddrV6(const char *pcszAddress) +{ + return !!(strchr(pcszAddress, ':')); +} + + +/********************************************************************************************************************************* +* Generic address commands. * +*********************************************************************************************************************************/ + +/** + * The base class for all address manipulation commands. While being an abstract class, + * it provides a generic implementation of 'set' and 'remove' methods, which rely on + * pure virtual methods like 'addV4' and 'removeV4' to perform actual command execution. + */ +class AddressCommand +{ +public: + AddressCommand() : m_pszPath(0) {}; + virtual ~AddressCommand() {}; + + /** returns true if underlying command (executable) is present in the system. */ + bool isAvailable(void) + { struct stat s; return (!stat(m_pszPath, &s) && S_ISREG(s.st_mode)); }; + + /* + * Someday we may want to support several IP addresses per adapter, but for now we + * have 'set' method only, which replaces all addresses with the one specifed. + * + * virtual int add(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + */ + /** replace existing address(es) */ + virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0); + /** remove address */ + virtual int remove(const char *pcszAdapter, const char *pcszAddress); +protected: + /** IPv4-specific handler used by generic implementation of 'set' method if 'setV4' is not supported. */ + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv6-specific handler used by generic implementation of 'set' method. */ + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv4-specific handler used by generic implementation of 'set' method. */ + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) = 0; + /** IPv4-specific handler used by generic implementation of 'remove' method. */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) = 0; + /** IPv6-specific handler used by generic implementation of 'remove' method. */ + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) = 0; + /** Composes the argument list of command that obtains all addresses assigned to the adapter. */ + virtual CmdList getShowCommand(const char *pcszAdapter) const = 0; + + /** Prepares an array of C strings needed for 'exec' call. */ + char * const * allocArgv(const CmdList& commandList); + /** Hides process creation details. To be used in derived classes. */ + int execute(CmdList& commandList); + + /** A path to executable command. */ + const char *m_pszPath; +private: + /** Removes all previously asssigned addresses of a particular protocol family. */ + int removeAddresses(const char *pcszAdapter, const char *pcszFamily); +}; + +/* + * A generic implementation of 'ifconfig' command for all platforms. + */ +class CmdIfconfig : public AddressCommand +{ +public: + CmdIfconfig() + { + struct stat s; + if ( !stat(VBOXADPCTL_IFCONFIG_PATH1, &s) + && S_ISREG(s.st_mode)) + m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH1; + else + m_pszPath = (char*)VBOXADPCTL_IFCONFIG_PATH2; + }; + +protected: + /** Returns platform-specific subcommand to add an address. */ + virtual const char *addCmdArg(void) const = 0; + /** Returns platform-specific subcommand to remove an address. */ + virtual const char *delCmdArg(void) const = 0; + virtual CmdList getShowCommand(const char *pcszAdapter) const + { return CmdList(pcszAdapter); }; + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); }; + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + return execute(CmdList(pcszAdapter) << "inet6" << addCmdArg() << pcszAddress); + NOREF(pcszNetmask); + }; + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + if (!pcszNetmask) + return execute(CmdList(pcszAdapter) << pcszAddress); + return execute(CmdList(pcszAdapter) << pcszAddress << "netmask" << pcszNetmask); + }; + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << delCmdArg() << pcszAddress); }; + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << "inet6" << delCmdArg() << pcszAddress); }; +}; + + +/********************************************************************************************************************************* +* Platform-specific commands * +*********************************************************************************************************************************/ + +class CmdIfconfigLinux : public CmdIfconfig +{ +protected: + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList(pcszAdapter) << "0.0.0.0"); NOREF(pcszAddress); }; + virtual const char *addCmdArg(void) const { return "add"; }; + virtual const char *delCmdArg(void) const { return "del"; }; +}; + +class CmdIfconfigDarwin : public CmdIfconfig +{ +protected: + virtual const char *addCmdArg(void) const { return "add"; }; + virtual const char *delCmdArg(void) const { return "delete"; }; +}; + +class CmdIfconfigSolaris : public CmdIfconfig +{ +public: + virtual int set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + const char *pcszFamily = isAddrV6(pcszAddress) ? "inet6" : "inet"; + int status; + + status = execute(CmdList(pcszAdapter) << pcszFamily); + if (status != EXIT_SUCCESS) + status = execute(CmdList(pcszAdapter) << pcszFamily << "plumb" << "up"); + if (status != EXIT_SUCCESS) + return status; + + return CmdIfconfig::set(pcszAdapter, pcszAddress, pcszNetmask); + }; +protected: + /* We can umplumb IPv4 interfaces only! */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { + int rc = CmdIfconfig::removeV4(pcszAdapter, pcszAddress); + + /** @todo Do we really need to unplumb inet here? */ + execute(CmdList(pcszAdapter) << "inet" << "unplumb"); + return rc; + }; + virtual const char *addCmdArg(void) const { return "addif"; }; + virtual const char *delCmdArg(void) const { return "removeif"; }; +}; + + +#ifdef RT_OS_LINUX +/* + * Helper class to incapsulate IPv4 address conversion. + * + * Note that this class relies on NetworkAddress to have been used for + * checking validity of IP addresses prior calling any methods of this + * class. + */ +class AddressIPv4 +{ +public: + AddressIPv4(const char *pcszAddress, const char *pcszNetmask = 0) + { + m_Prefix = 0; + memset(&m_Address, 0, sizeof(m_Address)); + + if (pcszNetmask) + m_Prefix = maskToPrefix(pcszNetmask); + else + { + /* + * Since guessing network mask is probably futile we simply use 24, + * as it matches our defaults. When non-default values are used + * providing a proper netmask is up to the user. + */ + m_Prefix = 24; + } + int rc = RTNetStrToIPv4Addr(pcszAddress, &m_Address); + AssertRCReturnVoid(rc); + snprintf(m_szAddressAndMask, sizeof(m_szAddressAndMask), "%s/%d", pcszAddress, m_Prefix); + deriveBroadcast(&m_Address, m_Prefix); + } + const char *getBroadcast() const { return m_szBroadcast; }; + const char *getAddressAndMask() const { return m_szAddressAndMask; }; +private: + int maskToPrefix(const char *pcszNetmask); + void deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int uPrefix); + + int m_Prefix; + RTNETADDRIPV4 m_Address; + char m_szAddressAndMask[INET_ADDRSTRLEN + 3]; /* e.g. 192.168.56.101/24 */ + char m_szBroadcast[INET_ADDRSTRLEN]; +}; + +int AddressIPv4::maskToPrefix(const char *pcszNetmask) +{ + RTNETADDRIPV4 mask; + int prefix = 0; + + int rc = RTNetStrToIPv4Addr(pcszNetmask, &mask); + AssertRCReturn(rc, 0); + rc = RTNetMaskToPrefixIPv4(&mask, &prefix); + AssertRCReturn(rc, 0); + + return prefix; +} + +void AddressIPv4::deriveBroadcast(PCRTNETADDRIPV4 pcAddress, int iPrefix) +{ + RTNETADDRIPV4 mask, broadcast; + int rc = RTNetPrefixToMaskIPv4(iPrefix, &mask); + AssertRCReturnVoid(rc); + broadcast.au32[0] = (pcAddress->au32[0] & mask.au32[0]) | ~mask.au32[0]; + inet_ntop(AF_INET, broadcast.au32, m_szBroadcast, sizeof(m_szBroadcast)); +} + + +/* + * Linux-specific implementation of 'ip' command, as other platforms do not support it. + */ +class CmdIpLinux : public AddressCommand +{ +public: + CmdIpLinux() { m_pszPath = "/sbin/ip"; }; + /** + * IPv4 and IPv6 syntax is the same, so we override `remove` instead of implementing + * family-specific commands. It would be easier to use the same body in both + * 'removeV4' and 'removeV6', so we override 'remove' to illustrate how to do common + * implementation. + */ + virtual int remove(const char *pcszAdapter, const char *pcszAddress) + { return execute(CmdList("addr") << "del" << pcszAddress << "dev" << pcszAdapter); }; +protected: + virtual int addV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + AddressIPv4 addr(pcszAddress, pcszNetmask); + bringUp(pcszAdapter); + return execute(CmdList("addr") << "add" << addr.getAddressAndMask() << + "broadcast" << addr.getBroadcast() << "dev" << pcszAdapter); + }; + virtual int addV6(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { + bringUp(pcszAdapter); + return execute(CmdList("addr") << "add" << pcszAddress << "dev" << pcszAdapter); + NOREF(pcszNetmask); + }; + /** + * Our command does not support 'replacing' addresses. Reporting this fact to generic implementation + * of 'set' causes it to remove all assigned addresses, then 'add' the new one. + */ + virtual int setV4(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask = 0) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); NOREF(pcszNetmask); }; + /** We use family-agnostic command syntax. See 'remove' above. */ + virtual int removeV4(const char *pcszAdapter, const char *pcszAddress) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); }; + /** We use family-agnostic command syntax. See 'remove' above. */ + virtual int removeV6(const char *pcszAdapter, const char *pcszAddress) + { return ENOTSUP; NOREF(pcszAdapter); NOREF(pcszAddress); }; + virtual CmdList getShowCommand(const char *pcszAdapter) const + { return CmdList("addr") << "show" << "dev" << pcszAdapter; }; +private: + /** Brings up the adapter */ + void bringUp(const char *pcszAdapter) + { execute(CmdList("link") << "set" << "dev" << pcszAdapter << "up"); }; +}; +#endif /* RT_OS_LINUX */ + + +/********************************************************************************************************************************* +* Generic address command implementations * +*********************************************************************************************************************************/ + +int AddressCommand::set(const char *pcszAdapter, const char *pcszAddress, const char *pcszNetmask) +{ + if (isAddrV6(pcszAddress)) + { + removeAddresses(pcszAdapter, "inet6"); + return addV6(pcszAdapter, pcszAddress, pcszNetmask); + } + int rc = setV4(pcszAdapter, pcszAddress, pcszNetmask); + if (rc == ENOTSUP) + { + removeAddresses(pcszAdapter, "inet"); + rc = addV4(pcszAdapter, pcszAddress, pcszNetmask); + } + return rc; +} + +int AddressCommand::remove(const char *pcszAdapter, const char *pcszAddress) +{ + if (isAddrV6(pcszAddress)) + return removeV6(pcszAdapter, pcszAddress); + return removeV4(pcszAdapter, pcszAddress); +} + +/* + * Allocate an array of exec arguments. In addition to arguments provided + * we need to include the full path to the executable as well as "terminating" + * null pointer marking the end of the array. + */ +char * const * AddressCommand::allocArgv(const CmdList& list) +{ + int i = 0; + std::list::const_iterator it; + const char **argv = (const char **)calloc(list.getList().size() + 2, sizeof(const char *)); + if (argv) + { + argv[i++] = m_pszPath; + for (it = list.getList().begin(); it != list.getList().end(); ++it) + argv[i++] = *it; + argv[i++] = NULL; + } + return (char * const*)argv; +} + +int AddressCommand::execute(CmdList& list) +{ + char * const pEnv[] = { (char*)"LC_ALL=C", NULL }; + char * const* argv = allocArgv(list); + if (argv == NULL) + return EXIT_FAILURE; + + if (verbose) + { + const char *sep = ""; + for (const char * const *pArg = argv; *pArg != NULL; ++pArg) + { + printf("%s%s", sep, *pArg); + sep = " "; + } + printf("\n"); + } + if (dry_run) + { + free((void *)argv); + return EXIT_SUCCESS; + } + + int rc = EXIT_FAILURE; /* o/~ hope for the best, expect the worst */ + pid_t childPid = fork(); + switch (childPid) + { + case -1: /* Something went wrong. */ + perror("fork"); + break; + + case 0: /* Child process. */ + if (execve(argv[0], argv, pEnv) == -1) + { + perror("execve"); + exit(EXIT_FAILURE); + /* NOTREACHED */ + } + break; + + default: /* Parent process. */ + { + int status; + pid_t waited = waitpid(childPid, &status, 0); + if (waited == childPid) /* likely*/ + { + if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) + rc = EXIT_SUCCESS; + } + else if (waited == (pid_t)-1) + { + perror("waitpid"); + } + else + { + /* should never happen */ + fprintf(stderr, "waitpid: unexpected pid %lld\n", + (long long int)waited); + } + break; + } + } + + free((void*)argv); + return rc; +} + +#define MAX_ADDRESSES 128 +#define MAX_ADDRLEN 64 + +int AddressCommand::removeAddresses(const char *pcszAdapter, const char *pcszFamily) +{ + char szBuf[1024]; + char aszAddresses[MAX_ADDRESSES][MAX_ADDRLEN]; + int rc = EXIT_SUCCESS; + int fds[2]; + + memset(aszAddresses, 0, sizeof(aszAddresses)); + + rc = pipe(fds); + if (rc < 0) + return errno; + + pid_t pid = fork(); + if (pid < 0) + return errno; + + if (pid == 0) + { + /* child */ + close(fds[0]); + close(STDOUT_FILENO); + rc = dup2(fds[1], STDOUT_FILENO); + if (rc >= 0) + { + char * const * argv = allocArgv(getShowCommand(pcszAdapter)); + char * const envp[] = { (char*)"LC_ALL=C", NULL }; + + if (execve(argv[0], argv, envp) == -1) + { + free((void *)argv); + return errno; + } + + free((void *)argv); + } + return rc; + } + + /* parent */ + close(fds[1]); + FILE *fp = fdopen(fds[0], "r"); + if (!fp) + return false; + + int cAddrs; + for (cAddrs = 0; cAddrs < MAX_ADDRESSES && fgets(szBuf, sizeof(szBuf), fp);) + { + int cbSkipWS = strspn(szBuf, " \t"); + char *pszWord = strtok(szBuf + cbSkipWS, " "); + /* We are concerned with particular family address lines only. */ + if (!pszWord || strcmp(pszWord, pcszFamily)) + continue; + + pszWord = strtok(NULL, " "); + + /* Skip "addr:" word if present. */ + if (pszWord && !strcmp(pszWord, "addr:")) + pszWord = strtok(NULL, " "); + + /* Skip link-local address lines. */ + if (!pszWord || !strncmp(pszWord, "fe80", 4)) + continue; + strncpy(aszAddresses[cAddrs++], pszWord, MAX_ADDRLEN-1); + } + fclose(fp); + + for (int i = 0; i < cAddrs && rc == EXIT_SUCCESS; i++) + rc = remove(pcszAdapter, aszAddresses[i]); + + return rc; +} + + +/********************************************************************************************************************************* +* Adapter creation/removal implementations * +*********************************************************************************************************************************/ + +/* + * A generic implementation of adapter creation/removal ioctl calls. + */ +class Adapter +{ +public: + int add(char *pszNameInOut); + int remove(const char *pcszName); + int checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut); +protected: + virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq); +}; + +/* + * Solaris does not support dynamic creation/removal of adapters. + */ +class AdapterSolaris : public Adapter +{ +protected: + virtual int doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq) + { return 1 /*ENOTSUP*/; NOREF(iCmd); NOREF(pReq); }; +}; + +#if defined(RT_OS_LINUX) +/* + * Linux implementation provides a 'workaround' to obtain adapter speed. + */ +class AdapterLinux : public Adapter +{ +public: + int getSpeed(const char *pszName, unsigned *puSpeed); +}; + +int AdapterLinux::getSpeed(const char *pszName, unsigned *puSpeed) +{ + struct ifreq IfReq; + struct ethtool_value EthToolVal; + struct ethtool_cmd EthToolReq; + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "speed for %s: ", pszName); + perror("VBoxNetAdpCtl: failed to open control socket"); + return ADPCTLERR_SOCKET_FAILED; + } + /* Get link status first. */ + memset(&EthToolVal, 0, sizeof(EthToolVal)); + memset(&IfReq, 0, sizeof(IfReq)); + snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName); + + EthToolVal.cmd = ETHTOOL_GLINK; + IfReq.ifr_data = (caddr_t)&EthToolVal; + int rc = ioctl(fd, SIOCETHTOOL, &IfReq); + if (rc == 0) + { + if (EthToolVal.data) + { + memset(&IfReq, 0, sizeof(IfReq)); + snprintf(IfReq.ifr_name, sizeof(IfReq.ifr_name), "%s", pszName); + EthToolReq.cmd = ETHTOOL_GSET; + IfReq.ifr_data = (caddr_t)&EthToolReq; + rc = ioctl(fd, SIOCETHTOOL, &IfReq); + if (rc == 0) + { + *puSpeed = EthToolReq.speed; + } + else + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "speed for %s: ", pszName); + perror("VBoxNetAdpCtl: ioctl failed"); + rc = ADPCTLERR_IOCTL_FAILED; + } + } + else + *puSpeed = 0; + } + else + { + fprintf(stderr, "VBoxNetAdpCtl: Error while retrieving link " + "status for %s: ", pszName); + perror("VBoxNetAdpCtl: ioctl failed"); + rc = ADPCTLERR_IOCTL_FAILED; + } + + close(fd); + return rc; +} +#endif /* defined(RT_OS_LINUX) */ + +int Adapter::add(char *pszName /* in/out */) +{ + VBOXNETADPREQ Req; + memset(&Req, '\0', sizeof(Req)); + snprintf(Req.szName, sizeof(Req.szName), "%s", pszName); + int rc = doIOCtl(VBOXNETADP_CTL_ADD, &Req); + if (rc == 0) + strncpy(pszName, Req.szName, VBOXNETADP_MAX_NAME_LEN); + return rc; +} + +int Adapter::remove(const char *pcszName) +{ + VBOXNETADPREQ Req; + memset(&Req, '\0', sizeof(Req)); + snprintf(Req.szName, sizeof(Req.szName), "%s", pcszName); + return doIOCtl(VBOXNETADP_CTL_REMOVE, &Req); +} + +int Adapter::checkName(const char *pcszNameIn, char *pszNameOut, size_t cbNameOut) +{ + int iAdapterIndex = -1; + + if ( strlen(pcszNameIn) >= VBOXNETADP_MAX_NAME_LEN + || sscanf(pcszNameIn, "vboxnet%d", &iAdapterIndex) != 1 + || iAdapterIndex < 0 || iAdapterIndex >= VBOXNETADP_MAX_INSTANCES ) + { + fprintf(stderr, "VBoxNetAdpCtl: Setting configuration for '%s' is not supported.\n", pcszNameIn); + return ADPCTLERR_BAD_NAME; + } + snprintf(pszNameOut, cbNameOut, "vboxnet%d", iAdapterIndex); + if (strcmp(pszNameOut, pcszNameIn)) + { + fprintf(stderr, "VBoxNetAdpCtl: Invalid adapter name '%s'.\n", pcszNameIn); + return ADPCTLERR_BAD_NAME; + } + + return 0; +} + +int Adapter::doIOCtl(unsigned long iCmd, VBOXNETADPREQ *pReq) +{ + int fd = open(VBOXNETADP_CTL_DEV_NAME, O_RDWR); + if (fd == -1) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ", + iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding", + pReq->szName[0] ? pReq->szName : "new interface"); + perror("failed to open " VBOXNETADP_CTL_DEV_NAME); + return ADPCTLERR_NO_CTL_DEV; + } + + int rc = ioctl(fd, iCmd, pReq); + if (rc == -1) + { + fprintf(stderr, "VBoxNetAdpCtl: Error while %s %s: ", + iCmd == VBOXNETADP_CTL_REMOVE ? "removing" : "adding", + pReq->szName[0] ? pReq->szName : "new interface"); + perror("VBoxNetAdpCtl: ioctl failed for " VBOXNETADP_CTL_DEV_NAME); + rc = ADPCTLERR_IOCTL_FAILED; + } + + close(fd); + + return rc; +} + + +/********************************************************************************************************************************* +* Global config file implementation * +*********************************************************************************************************************************/ + +#define VBOX_GLOBAL_NETWORK_CONFIG_PATH "/etc/vbox/networks.conf" +#define VBOXNET_DEFAULT_IPV4MASK "255.255.255.0" + +class NetworkAddress +{ + public: + bool isValidString(const char *pcszNetwork); + bool isValid() { return m_fValid; }; + virtual bool matches(const char *pcszNetwork) = 0; + virtual const char *defaultNetwork() = 0; + protected: + bool m_fValid; +}; + +bool NetworkAddress::isValidString(const char *pcszNetwork) +{ + RTNETADDRIPV4 addrv4; + RTNETADDRIPV6 addrv6; + int prefix; + int rc = RTNetStrToIPv4Cidr(pcszNetwork, &addrv4, &prefix); + if (RT_SUCCESS(rc)) + return true; + rc = RTNetStrToIPv6Cidr(pcszNetwork, &addrv6, &prefix); + return RT_SUCCESS(rc); +} + +class NetworkAddressIPv4 : public NetworkAddress +{ + public: + NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask = VBOXNET_DEFAULT_IPV4MASK); + virtual bool matches(const char *pcszNetwork); + virtual const char *defaultNetwork() { return "192.168.56.1/21"; }; /* Matches defaults in VBox/Main/include/netif.h, see @bugref{10077}. */ + + protected: + bool isValidUnicastAddress(PCRTNETADDRIPV4 address); + + private: + RTNETADDRIPV4 m_address; + int m_prefix; +}; + +NetworkAddressIPv4::NetworkAddressIPv4(const char *pcszIpAddress, const char *pcszNetMask) +{ + int rc = RTNetStrToIPv4Addr(pcszIpAddress, &m_address); + if (RT_SUCCESS(rc)) + { + RTNETADDRIPV4 mask; + rc = RTNetStrToIPv4Addr(pcszNetMask, &mask); + if (RT_FAILURE(rc)) + m_fValid = false; + else + rc = RTNetMaskToPrefixIPv4(&mask, &m_prefix); + } +#if 0 /* cmd.set() does not support CIDR syntax */ + else + rc = RTNetStrToIPv4Cidr(pcszIpAddress, &m_address, &m_prefix); +#endif + m_fValid = RT_SUCCESS(rc) && isValidUnicastAddress(&m_address); +} + +bool NetworkAddressIPv4::isValidUnicastAddress(PCRTNETADDRIPV4 address) +{ + /* Multicast addresses are not allowed. */ + if ((address->au8[0] & 0xF0) == 0xE0) + return false; + + /* Broadcast address is not allowed. */ + if (address->au32[0] == 0xFFFFFFFF) /* Endianess does not matter in this particual case. */ + return false; + + /* Loopback addresses are not allowed. */ + if ((address->au8[0] & 0xFF) == 0x7F) + return false; + + return true; +} + +bool NetworkAddressIPv4::matches(const char *pcszNetwork) +{ + RTNETADDRIPV4 allowedNet, allowedMask; + int allowedPrefix; + int rc = RTNetStrToIPv4Cidr(pcszNetwork, &allowedNet, &allowedPrefix); + if (RT_SUCCESS(rc)) + rc = RTNetPrefixToMaskIPv4(allowedPrefix, &allowedMask); + if (RT_FAILURE(rc)) + return false; + return m_prefix >= allowedPrefix && (m_address.au32[0] & allowedMask.au32[0]) == (allowedNet.au32[0] & allowedMask.au32[0]); +} + +class NetworkAddressIPv6 : public NetworkAddress +{ + public: + NetworkAddressIPv6(const char *pcszIpAddress); + virtual bool matches(const char *pcszNetwork); + virtual const char *defaultNetwork() { return "FE80::/10"; }; + private: + RTNETADDRIPV6 m_address; + int m_prefix; +}; + +NetworkAddressIPv6::NetworkAddressIPv6(const char *pcszIpAddress) +{ + int rc = RTNetStrToIPv6Cidr(pcszIpAddress, &m_address, &m_prefix); + m_fValid = RT_SUCCESS(rc); +} + +bool NetworkAddressIPv6::matches(const char *pcszNetwork) +{ + RTNETADDRIPV6 allowedNet, allowedMask; + int allowedPrefix; + int rc = RTNetStrToIPv6Cidr(pcszNetwork, &allowedNet, &allowedPrefix); + if (RT_SUCCESS(rc)) + rc = RTNetPrefixToMaskIPv6(allowedPrefix, &allowedMask); + if (RT_FAILURE(rc)) + return false; + RTUINT128U u128Provided, u128Allowed; + return m_prefix >= allowedPrefix + && RTUInt128Compare(RTUInt128And(&u128Provided, &m_address, &allowedMask), RTUInt128And(&u128Allowed, &allowedNet, &allowedMask)) == 0; +} + + +class GlobalNetworkPermissionsConfig +{ + public: + bool forbids(const char *pcszIpAddress); /* address or address with mask in cidr */ + bool forbids(const char *pcszIpAddress, const char *pcszNetMask); + + private: + bool forbids(NetworkAddress& address); +}; + +bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress) +{ + NetworkAddressIPv6 addrv6(pcszIpAddress); + + if (addrv6.isValid()) + return forbids(addrv6); + + NetworkAddressIPv4 addrv4(pcszIpAddress); + + if (addrv4.isValid()) + return forbids(addrv4); + + fprintf(stderr, "Error: invalid address '%s'\n", pcszIpAddress); + return true; +} + +bool GlobalNetworkPermissionsConfig::forbids(const char *pcszIpAddress, const char *pcszNetMask) +{ + NetworkAddressIPv4 addrv4(pcszIpAddress, pcszNetMask); + + if (addrv4.isValid()) + return forbids(addrv4); + + fprintf(stderr, "Error: invalid address '%s' with mask '%s'\n", pcszIpAddress, pcszNetMask); + return true; +} + +bool GlobalNetworkPermissionsConfig::forbids(NetworkAddress& address) +{ + FILE *fp = fopen(VBOX_GLOBAL_NETWORK_CONFIG_PATH, "r"); + if (!fp) + { + if (verbose) + fprintf(stderr, "Info: matching against default '%s' => %s\n", address.defaultNetwork(), + address.matches(address.defaultNetwork()) ? "MATCH" : "no match"); + return !address.matches(address.defaultNetwork()); + } + + char *pszToken, szLine[1024]; + for (int line = 1; fgets(szLine, sizeof(szLine), fp); ++line) + { + pszToken = strtok(szLine, " \t\n"); + /* Skip anything except '*' lines */ + if (pszToken == NULL || strcmp("*", pszToken)) + continue; + /* Match the specified address against each network */ + while ((pszToken = strtok(NULL, " \t\n")) != NULL) + { + if (!address.isValidString(pszToken)) + { + fprintf(stderr, "Warning: %s(%d) invalid network '%s'\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken); + continue; + } + if (verbose) + fprintf(stderr, "Info: %s(%d) matching against '%s' => %s\n", VBOX_GLOBAL_NETWORK_CONFIG_PATH, line, pszToken, + address.matches(pszToken) ? "MATCH" : "no match"); + if (address.matches(pszToken)) + return false; + } + } + fclose(fp); + return true; +} + + +/********************************************************************************************************************************* +* Main logic, argument parsing, etc. * +*********************************************************************************************************************************/ + +#if defined(RT_OS_LINUX) +static CmdIfconfigLinux g_ifconfig; +static AdapterLinux g_adapter; +#elif defined(RT_OS_SOLARIS) +static CmdIfconfigSolaris g_ifconfig; +static AdapterSolaris g_adapter; +#else +static CmdIfconfigDarwin g_ifconfig; +static Adapter g_adapter; +#endif + +static AddressCommand& chooseAddressCommand() +{ +#if defined(RT_OS_LINUX) + static CmdIpLinux g_ip; + if (g_ip.isAvailable()) + return g_ip; +#endif + return g_ifconfig; +} + +int main(int argc, char *argv[]) +{ + char szAdapterName[VBOXNETADP_MAX_NAME_LEN]; + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + + AddressCommand& cmd = chooseAddressCommand(); + + + static const struct option options[] = { + { "dry-run", no_argument, NULL, 'n' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + int ch; + while ((ch = getopt_long(argc, argv, "nv", options, NULL)) != -1) + { + switch (ch) + { + case 'n': + dry_run = true; + verbose = true; + break; + + case 'v': + verbose = true; + break; + + case '?': + default: + return usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + return usage(); + + + /* + * VBoxNetAdpCtl add + */ + if (strcmp(argv[0], "add") == 0) + { + if (argc > 1) /* extraneous args */ + return usage(); + + /* Create a new interface, print its name. */ + *szAdapterName = '\0'; + rc = g_adapter.add(szAdapterName); + if (rc == EXIT_SUCCESS) + puts(szAdapterName); + + return rc; + } + + + /* + * All other variants are of the form: + * VBoxNetAdpCtl if0 ...action... + */ + const char * const ifname = argv[0]; + const char * const action = argv[1]; + if (argc < 2) + return usage(); + + +#ifdef RT_OS_LINUX + /* + * VBoxNetAdpCtl iface42 speed + * + * This ugly hack is needed for retrieving the link speed on + * pre-2.6.33 kernels (see @bugref{6345}). + * + * This variant is used with any interface, not just host-only. + */ + if (strcmp(action, "speed") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + if (strlen(ifname) >= IFNAMSIZ) + { + fprintf(stderr, "Interface name too long\n"); + return EXIT_FAILURE; + } + + unsigned uSpeed = 0; + rc = g_adapter.getSpeed(ifname, &uSpeed); + if (rc == EXIT_SUCCESS) + printf("%u", uSpeed); + + return rc; + } +#endif /* RT_OS_LINUX */ + + + /* + * The rest of the actions only operate on host-only interfaces. + */ + /** @todo Why the code below uses both ifname and szAdapterName? */ + rc = g_adapter.checkName(ifname, szAdapterName, sizeof(szAdapterName)); + if (rc != EXIT_SUCCESS) + return rc; + + + /* + * VBoxNetAdpCtl vboxnetN remove + */ + if (strcmp(action, "remove") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + /* Remove an existing interface */ + return g_adapter.remove(ifname); + } + + /* + * VBoxNetAdpCtl vboxnetN add + */ + if (strcmp(action, "add") == 0) + { + if (argc > 2) /* extraneous args */ + return usage(); + + /* Create an interface with the given name, print its name. */ + rc = g_adapter.add(szAdapterName); + if (rc == EXIT_SUCCESS) + puts(szAdapterName); + + return rc; + } + + + /* + * The rest of the actions are of the form + * VBoxNetAdpCtl vboxnetN $addr [...] + * + * Use the argument after the address to select the action. + */ + /** @todo Do early verification of addr format here? */ + const char * const addr = argv[1]; + const char * const keyword = argv[2]; + + GlobalNetworkPermissionsConfig config; + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 + */ + if (keyword == NULL) + { + if (config.forbids(addr)) + { + fprintf(stderr, "Error: permission denied\n"); + return -VERR_ACCESS_DENIED; + } + + return cmd.set(ifname, addr); + } + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 netmask 255.255.255.0 + */ + if (strcmp(keyword, "netmask") == 0) + { + if (argc != 4) /* too few or too many args */ + return usage(); + + const char * const mask = argv[3]; + if (config.forbids(addr, mask)) + { + fprintf(stderr, "Error: permission denied\n"); + return -VERR_ACCESS_DENIED; + } + + return cmd.set(ifname, addr, mask); + } + + /* + * VBoxNetAdpCtl vboxnetN 1.2.3.4 remove + */ + if (strcmp(keyword, "remove") == 0) + { + if (argc > 3) /* extraneous args */ + return usage(); + + return cmd.remove(ifname, addr); + } + + return usage(); +} diff --git a/src/VBox/HostDrivers/darwin/Makefile.kmk b/src/VBox/HostDrivers/darwin/Makefile.kmk new file mode 100644 index 00000000..47938915 --- /dev/null +++ b/src/VBox/HostDrivers/darwin/Makefile.kmk @@ -0,0 +1,49 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-makefile for the darwin host driver helper scripts. +# + +# +# 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 . +# +# 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 + +ifdef VBOX_WITH_VBOXDRV + INSTALLS += HostDrivers-darwin-sh + HostDrivers-darwin-sh_INST = $(INST_DIST) + HostDrivers-darwin-sh_INSTTYPE = stage + HostDrivers-darwin-sh_EXEC_SOURCES = \ + loadall.sh +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/darwin/VBoxNetSend.h b/src/VBox/HostDrivers/darwin/VBoxNetSend.h new file mode 100644 index 00000000..4585a255 --- /dev/null +++ b/src/VBox/HostDrivers/darwin/VBoxNetSend.h @@ -0,0 +1,132 @@ +/* $Id: VBoxNetSend.h $ */ +/** @file + * A place to share code and definitions between VBoxNetAdp and VBoxNetFlt host drivers. + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +/** @todo move this to src/VBox/HostDrivers/darwin as a .cpp file. */ + +#ifndef VBOX_INCLUDED_SRC_darwin_VBoxNetSend_h +#define VBOX_INCLUDED_SRC_darwin_VBoxNetSend_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#if defined(RT_OS_DARWIN) + +# include +# include +# include + +# include +# if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 /* The 10.15 SDK has a slightly butchered API deprecation attempt. */ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmacro-redefined" /* Each header redefines __NKE_API_DEPRECATED. */ +# pragma clang diagnostic ignored "-Wmissing-declarations" /* Misplaced __NKE_API_DEPRECATED; in kpi_mbuf.h. */ +# include +# include +# include +# pragma clang diagnostic pop +# else /* < 10.15 */ +# include +RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */ +# include +RT_C_DECLS_END +# include +# endif /* < 10.15 */ + + +RT_C_DECLS_BEGIN + +# if defined(IN_RING0) + +/** + * Constructs and submits a dummy packet to ifnet_input(). + * + * This is a workaround for "stuck dock icon" issue. When the first packet goes + * through the interface DLIL grabs a reference to the thread that submits the + * packet and holds it until the interface is destroyed. By submitting this + * dummy we make DLIL grab the thread of a non-GUI process. + * + * Most of this function was copied from vboxNetFltDarwinMBufFromSG(). + * + * @returns VBox status code. + * @param pIfNet The interface that will hold the reference to the calling + * thread. We submit dummy as if it was coming from this interface. + */ +DECLINLINE(int) VBoxNetSendDummy(ifnet_t pIfNet) +{ + int rc = VINF_SUCCESS; + + size_t cbTotal = 50; /* No Ethernet header */ + mbuf_how_t How = MBUF_WAITOK; + mbuf_t pPkt = NULL; + errno_t err = mbuf_allocpacket(How, cbTotal, NULL, &pPkt); + if (!err) + { + /* Skip zero sized memory buffers (paranoia). */ + mbuf_t pCur = pPkt; + while (pCur && !mbuf_maxlen(pCur)) + pCur = mbuf_next(pCur); + Assert(pCur); + + /* Set the required packet header attributes. */ + mbuf_pkthdr_setlen(pPkt, cbTotal); + mbuf_pkthdr_setheader(pPkt, mbuf_data(pCur)); + + mbuf_setlen(pCur, cbTotal); + memset(mbuf_data(pCur), 0, cbTotal); + + mbuf_pkthdr_setrcvif(pPkt, pIfNet); /* will crash without this. */ + + err = ifnet_input(pIfNet, pPkt, NULL); + if (err) + { + rc = RTErrConvertFromErrno(err); + mbuf_freem(pPkt); + } + } + else + rc = RTErrConvertFromErrno(err); + + return rc; +} + +# endif /* IN_RING0 */ + +RT_C_DECLS_END + +#endif /* RT_OS_DARWIN */ + +#endif /* !VBOX_INCLUDED_SRC_darwin_VBoxNetSend_h */ + diff --git a/src/VBox/HostDrivers/darwin/loadall.sh b/src/VBox/HostDrivers/darwin/loadall.sh new file mode 100755 index 00000000..3333afd7 --- /dev/null +++ b/src/VBox/HostDrivers/darwin/loadall.sh @@ -0,0 +1,48 @@ +#!/bin/bash +## @file +# For development, loads all the host drivers. +# + +# +# 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 . +# +# 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 +# + +MY_DIR=`dirname "$0"` +MY_DIR=`cd "${MY_DIR}" && pwd` +if [ ! -d "${MY_DIR}" ]; then + echo "Cannot find ${MY_DIR} or it's not a directory..." + exit 1; +fi + +set -e +"${MY_DIR}/load.sh" "$*" +"${MY_DIR}/loadnetflt.sh" "$*" +"${MY_DIR}/loadnetadp.sh" "$*" + diff --git a/src/VBox/HostDrivers/freebsd/Makefile b/src/VBox/HostDrivers/freebsd/Makefile new file mode 100644 index 00000000..215ac52b --- /dev/null +++ b/src/VBox/HostDrivers/freebsd/Makefile @@ -0,0 +1,94 @@ +# +# Makefile for the VirtualBox FreeBSD Host Drivers. +# + +# +# 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 . +# +# 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 +# + +KBUILD_VERBOSE = + +all: + @echo "=== Building 'vboxdrv' module ===" + @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxdrv + @cp vboxdrv/vboxdrv.ko . + @echo + @if [ -d vboxnetflt ]; then \ + if [ -f vboxdrv/Module.symvers ]; then \ + cp vboxdrv/Module.symvers vboxnetflt; \ + fi; \ + echo "=== Building 'vboxnetflt' module ==="; \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetflt; \ + cp vboxnetflt/vboxnetflt.ko .; \ + echo; \ + fi + @if [ -d vboxnetadp ]; then \ + if [ -f vboxdrv/Module.symvers ]; then \ + cp vboxdrv/Module.symvers vboxnetadp; \ + fi; \ + echo "=== Building 'vboxnetadp' module ==="; \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetadp; \ + cp vboxnetadp/vboxnetadp.ko .; \ + echo; \ + fi + + +install: + @$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxdrv install + @if [ -d vboxnetflt ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetflt install; \ + fi + @if [ -d vboxnetadp ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetadp install; \ + fi + +clean: + @$(MAKE) -C vboxdrv clean + @if [ -d vboxnetflt ]; then \ + $(MAKE) -C vboxnetflt clean; \ + fi + @if [ -d vboxnetadp ]; then \ + $(MAKE) -C vboxnetadp clean; \ + fi + rm -f vboxdrv.ko vboxnetflt.ko vboxnetadp.ko + +load: + @for module in vboxnetadp vboxnetflt vboxdrv; do \ + if kldstat -n "$$module" >/dev/null; then \ + echo "Removing previously installed $$module module"; \ + /sbin/kldunload $$module; \ + fi; \ + done + @for module in vboxdrv vboxnetflt vboxnetadp; do \ + if test -f $$module.ko; then \ + echo "Installing $$module module"; \ + /sbin/kldload ./$$module.ko; \ + fi; \ + done diff --git a/src/VBox/HostDrivers/freebsd/Makefile.kmk b/src/VBox/HostDrivers/freebsd/Makefile.kmk new file mode 100644 index 00000000..9c9bf4d2 --- /dev/null +++ b/src/VBox/HostDrivers/freebsd/Makefile.kmk @@ -0,0 +1,45 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-makefile for the FreeBSD host driver helper scripts. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +INSTALLS += HostDrivers-scripts +HostDrivers-scripts_INST = bin/src/ +HostDrivers-scripts_SOURCES = Makefile + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/linux/Makefile b/src/VBox/HostDrivers/linux/Makefile new file mode 100644 index 00000000..c32cb136 --- /dev/null +++ b/src/VBox/HostDrivers/linux/Makefile @@ -0,0 +1,248 @@ +# +# Makefile for the VirtualBox Linux Host Drivers. +# + +# +# 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 . +# +# 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 +# + +ifneq ($(KERNELRELEASE),) + +# Building from kBuild (make -C M=`pwd`), +# or inside a kernel source tree. + +obj-m = vboxdrv/ + ifneq ($(wildcard $(CURDIR)/vboxnetflt/Makefile),) +obj-m += vboxnetflt/ + endif + ifneq ($(wildcard $(CURDIR)/vboxnetadp/Makefile),) +obj-m += vboxnetadp/ + endif + ifneq ($(wildcard $(CURDIR)/vboxpci/Makefile),) +obj-m += vboxpci/ + endif + +else # ! KERNELRELEASE + +# convenience Makefile without KERNELRELEASE + +ifndef SUDO + ifneq ($(shell id -u),0) +SUDO := $(firstword $(wildcard /usr/bin/sudo /bin/sudo /usr/sbin/sudo)) + else +SUDO := + endif +endif + +KBUILD_VERBOSE ?= +.PHONY: all install clean check unload load \ + vboxdrv vboxnetflt vboxnetadp vboxpci \ + install-vboxdrv install-vboxnetflt install-vboxnetadp install-vboxpci \ + clean-vboxdrv clean-vboxnetflt clean-vboxnetadp clean-vboxpci + +all: vboxdrv vboxnetflt vboxnetadp vboxpci + +# We want to build on Linux 2.6.18 and later kernels. +KERN_VER ?= $(shell uname -r) + ifneq ($(filter-out 1.% 2.0.% 2.1.% 2.2.% 2.3.% 2.4.% 2.5.%,$(KERN_VER)),) + +vboxdrv: + @echo "=== Building 'vboxdrv' module ===" + +@$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxdrv + @cp vboxdrv/vboxdrv.ko . + @echo + +vboxnetflt: vboxdrv + +@if [ -d vboxnetflt ]; then \ + if [ -f vboxdrv/Module.symvers ]; then \ + cp vboxdrv/Module.symvers vboxnetflt; \ + fi; \ + echo "=== Building 'vboxnetflt' module ==="; \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) KBUILD_EXTRA_SYMBOLS=$(abspath vboxnetflt/Module.symvers) -C vboxnetflt || exit 1; \ + cp vboxnetflt/vboxnetflt.ko .; \ + echo; \ + fi + +vboxnetadp: vboxdrv + +@if [ -d vboxnetadp ]; then \ + if [ -f vboxdrv/Module.symvers ]; then \ + cp vboxdrv/Module.symvers vboxnetadp; \ + fi; \ + echo "=== Building 'vboxnetadp' module ==="; \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) KBUILD_EXTRA_SYMBOLS=$(abspath vboxnetadp/Module.symvers) -C vboxnetadp || exit 1; \ + cp vboxnetadp/vboxnetadp.ko .; \ + echo; \ + fi + +vboxpci: vboxdrv + +@if [ -d vboxpci ]; then \ + if [ -f vboxdrv/Module.symvers ]; then \ + cp vboxdrv/Module.symvers vboxpci; \ + fi; \ + echo "=== Building 'vboxpci' module ==="; \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) KBUILD_EXTRA_SYMBOLS=$(abspath vboxpci/Module.symvers) -C vboxpci || exit 1; \ + cp vboxpci/vboxpci.ko .; \ + echo; \ + fi + +install-vboxdrv: + +@$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxdrv install + +install-vboxnetflt: + +@if [ -d vboxnetflt ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetflt install; \ + fi + +install-vboxnetadp: + +@if [ -d vboxnetadp ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxnetadp install; \ + fi + +install-vboxpci: + +@if [ -d vboxpci ]; then \ + $(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxpci install; \ + fi + +install: install-vboxdrv install-vboxnetflt install-vboxnetadp install-vboxpci + +# Look for wrapper modules, sorting them so vmmr0 is first. +VBOX_WRAPPER_DIRS := $(notdir $(wildcard $(CURDIR)/vbox_*)) + ifneq ($(VBOX_WRAPPER_DIRS),) +VBOX_WRAPPER_DIRS := $(filter vbox_vmmr0,$(VBOX_WRAPPER_DIRS)) $(sort $(filter-out vbox_vmmr0,$(VBOX_WRAPPER_DIRS))) + endif + define wrapper_template +$(wrapper): $(subst $(wrapper),,vbox_vmmr0) + +$$(MAKE) KBUILD_VERBOSE=$$(KBUILD_VERBOSE) -C $(wrapper)/ + +load-$(wrapper): $(subst load-$(wrapper),,load-vbox_vmmr0) + @if ! grep -q "^$(wrapper) " /proc/modules; then \ + echo "Loading $(wrapper)..."; \ + $(SUDO) /sbin/insmod $(wrapper)/$(wrapper).ko; \ + else \ + echo "Skipping loading $(wrapper) module (already loaded)."; \ + fi + +unload-$(wrapper): + @if grep -q "^$(wrapper) " /proc/modules; then \ + echo "Unloading $(wrapper)..."; \ + $(SUDO) /sbin/rmmod $(wrapper)/$(wrapper).ko; \ + fi + endef +$(foreach wrapper,$(VBOX_WRAPPER_DIRS), $(eval $(wrapper_template))) + +wrappers: $(VBOX_WRAPPER_DIRS) +wrappers-load: $(addprefix load-,$(VBOX_WRAPPER_DIRS)) +wrappers-unload: + @for module in $(filter-out vbox_vmmr0,$(VBOX_WRAPPER_DIRS)) $(filter vbox_vmmr0,$(VBOX_WRAPPER_DIRS)); \ + do \ + if grep -q "^$${module} " /proc/modules; then \ + echo "Unloading $${module}..."; \ + $(SUDO) /sbin/rmmod "$${module}"; \ + fi \ + done +wrappers-reload: wrappers-unload + +$(MAKE) -f $(lastword $(MAKEFILE_LIST)) --no-print-directory wrappers-load + +buildid: + @for module in $(foreach module,vboxdrv vboxnetflt vboxnetadp $(VBOX_WRAPPER_DIRS),$(module)/$(module).ko); \ + do \ + buildid=`readelf -n $${module} | sed -ne 's/^.*Build ID: *\([[:xdigit:]][[:xdigit:]]\)\(.*\)$$/\1\/\2/p' `; \ + if [ -n "$${buildid}" ]; then \ + mkdir -p ~/.debug/.build-id/`dirname $${buildid}`; \ + ln -sfn -- "$(CURDIR)/$${module}" ~/.debug/.build-id/$${buildid}; \ + else \ + echo "warning: No build ID for $${module}"; \ + fi \ + done + +.PHONY: wrappers wrappers-load wrappers-unload buildid $(VBOX_WRAPPER_DIRS) \ + $(addprefix load-,$(VBOX_WRAPPER_DIRS)) $(addprefix unload-,$(VBOX_WRAPPER_DIRS)) + + else # Too old: + +VBOX_WRAPPER_DIRS := + +vboxdrv: +vboxnetflt: +vboxnetadp: +vboxpci: +install: +wrappers: +wrappers-load: +wrappers-unload: +buildid: + + endif + +clean-vboxdrv: + +@$(MAKE) -C vboxdrv clean + rm -rf vboxdrv.ko + +clean-vboxnetflt: + +@if [ -d vboxnetflt ]; then \ + $(MAKE) -C vboxnetflt clean; \ + fi + rm -rf vboxnetflt.ko + +clean-vboxnetadp: + +@if [ -d vboxnetadp ]; then \ + $(MAKE) -C vboxnetadp clean; \ + fi + rm -rf vboxnetadp.ko + +clean-vboxpci: + +@if [ -d vboxpci ]; then \ + $(MAKE) -C vboxpci clean; \ + fi + rm -f vboxpci.ko + +clean: clean-vboxdrv clean-vboxnetflt clean-vboxnetadp clean-vboxpci + +check: + +@$(MAKE) KBUILD_VERBOSE=$(KBUILD_VERBOSE) -C vboxdrv check + +unload: + @for module in vboxpci vboxnetadp vboxnetflt vboxdrv; do \ + if grep "^$$module " /proc/modules >/dev/null; then \ + echo "Removing previously installed $$module module"; \ + $(SUDO) /sbin/rmmod $$module; \ + fi; \ + done + +load: unload + @for module in vboxdrv vboxnetflt vboxnetadp vboxpci; do \ + if test -f $$module.ko; then \ + echo "Installing $$module module"; \ + $(SUDO) /sbin/insmod $$module.ko; \ + fi; \ + done + +endif # ! KERNELRELEASE + diff --git a/src/VBox/HostDrivers/linux/Makefile.kmk b/src/VBox/HostDrivers/linux/Makefile.kmk new file mode 100644 index 00000000..3d012c47 --- /dev/null +++ b/src/VBox/HostDrivers/linux/Makefile.kmk @@ -0,0 +1,50 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-makefile for the Linux host driver helper scripts. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +INSTALLS += HostDrivers-scripts +HostDrivers-scripts_INST = $(INST_DIST) +HostDrivers-scripts_SOURCES = \ + Makefile=>src/Makefile +HostDrivers-scripts_EXEC_SOURCES = \ + loadall.sh \ + load.sh \ + build_in_tmp=>src/build_in_tmp + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/linux/build_in_tmp b/src/VBox/HostDrivers/linux/build_in_tmp new file mode 100755 index 00000000..e70c9b04 --- /dev/null +++ b/src/VBox/HostDrivers/linux/build_in_tmp @@ -0,0 +1,104 @@ +#!/bin/sh +# $Id: build_in_tmp $ +## @file +# Script to build a kernel module in /tmp. +# +# Useful if the module sources are installed in read-only directory. +# + +# +# 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 . +# +# 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 +# + +# find a unique temp directory +num=0 +while true; do + tmpdir="/tmp/vbox.$num" + if mkdir -m 0755 "$tmpdir" 2> /dev/null; then + break + fi + num=`expr $num + 1` + if [ $num -gt 200 ]; then + echo "Could not find a valid tmp directory" + exit 1 + fi +done + +# Guest optimal number of make jobs. +MAKE_JOBS=`grep vendor_id /proc/cpuinfo | wc -l` +if [ "${MAKE_JOBS}" -le "0" ]; then MAKE_JOBS=1; fi + +# Parse our arguments, anything we don't grok is for make. +while true; do + if [ "$1" = "--save-module-symvers" ]; then + shift + SAVE_MOD_SYMVERS="$1" + shift + elif [ "$1" = "--use-module-symvers" ]; then + shift + USE_MOD_SYMVERS="$1" + shift + elif [ "$1" = "--module-source" ]; then + shift + MODULE_SOURCE="$1" + shift + else + break + fi +done + +# copy +if [ -n "$MODULE_SOURCE" ]; then + cp -a "$MODULE_SOURCE"/* $tmpdir/ +else + cp -a ${0%/*}/* $tmpdir/ +fi +if [ -n "$USE_MOD_SYMVERS" ]; then + cp $USE_MOD_SYMVERS $tmpdir/Module.symvers + MAKE_EXTRAOPTS="KBUILD_EXTRA_SYMBOLS=$tmpdir/Module.symvers" +fi + +# make, cleanup if success +cd "$tmpdir" +if make CONFIG_MODULE_COMPRESS_GZIP= CONFIG_MODULE_COMPRESS_XZ= CONFIG_MODULE_COMPRESS_ZSTD= "-j`echo ${MAKE_JOBS}`" "$@" ${MAKE_EXTRAOPTS}; then # strip leading space from "MAKE_JOBS" + if [ -n "$SAVE_MOD_SYMVERS" ]; then + if [ -f Module.symvers ]; then + cp -f Module.symvers $SAVE_MOD_SYMVERS + else + cat /dev/null > $SAVE_MOD_SYMVERS + fi + fi + rm -rf $tmpdir + exit 0 +fi + +# failure +rm -rf $tmpdir +exit 1 diff --git a/src/VBox/HostDrivers/linux/export_modules.sh b/src/VBox/HostDrivers/linux/export_modules.sh new file mode 100755 index 00000000..11c49d47 --- /dev/null +++ b/src/VBox/HostDrivers/linux/export_modules.sh @@ -0,0 +1,258 @@ +#!/bin/sh +# $Id$ +## @file +# Create a tar archive containing the sources of the vboxdrv kernel module. +# + +# +# 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 . +# +# 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 +# + +# The below is GNU-specific. See VBox.sh for the longer Solaris/OS X version. +TARGET=`readlink -e -- "${0}"` || exit 1 +MY_DIR="${TARGET%/[!/]*}" + +# What this script does: +usage_msg="\ +Usage: `basename ${0}` --file |--folder \ + [--override-svn-rev ] [--extra-version-string ] [--without-hardening] + +Exports the VirtualBox host kernel modules to the tar.gz archive or folder in \ +, optionally adjusting the Make files to build them without hardening. + +Examples: + `basename ${0}` --file /tmp/vboxhost.tar.gz + `basename ${0}` --folder /tmp/tmpdir --without-hardening" + +usage() +{ + case "${1}" in + 0) + echo "${usage_msg}" | fold -s -w80 ;; + *) + echo "${usage_msg}" | fold -s -w80 >&2 ;; + esac + exit "${1}" +} + +fail() +{ + echo "${1}" | fold -s -w80 >&2 + exit 1 +} + +unset FILE FOLDER +VBOX_WITH_HARDENING=1 +while test -n "${1}"; do + case "${1}" in + --file) + FILE="${2}" + shift 2 ;; + --folder) + FOLDER="${2}" + shift 2 ;; + --override-svn-rev) + OVERRIDE_SVN_REV="${2}" + shift 2 ;; + --extra-version-string) + EXTRA_VERSION_STRING="${2}" + shift 2 ;; + --without-hardening) + unset VBOX_WITH_HARDENING + shift ;; + -h|--help) + usage 0 ;; + *) + echo "Unknown parameter ${1}" >&2 + usage 1 ;; + esac +done +test -z "$FILE" || test -z "$FOLDER" || + fail "Only one of --file and --folder may be used" +test -n "$FILE" || test -n "$FOLDER" || usage 1 + +if test -n "$FOLDER"; then + PATH_TMP="$FOLDER" +else + PATH_TMP="`cd \`dirname $FILE\`; pwd`/.vbox_modules" + FILE_OUT="`cd \`dirname $FILE\`; pwd`/`basename $FILE`" +fi +PATH_OUT=$PATH_TMP +PATH_ROOT="`cd ${MY_DIR}/../../../..; pwd`" +PATH_LOG=/tmp/vbox-export-host.log +PATH_LINUX="$PATH_ROOT/src/VBox/HostDrivers/linux" +PATH_VBOXDRV="$PATH_ROOT/src/VBox/HostDrivers/Support" +PATH_VBOXNET="$PATH_ROOT/src/VBox/HostDrivers/VBoxNetFlt" +PATH_VBOXADP="$PATH_ROOT/src/VBox/HostDrivers/VBoxNetAdp" +PATH_VBOXPCI="$PATH_ROOT/src/VBox/HostDrivers/VBoxPci" + +VBOX_VERSION_MAJOR=`sed -e "s/^ *VBOX_VERSION_MAJOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk` +VBOX_VERSION_MINOR=`sed -e "s/^ *VBOX_VERSION_MINOR *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk` +VBOX_VERSION_BUILD=`sed -e "s/^ *VBOX_VERSION_BUILD *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk` +VBOX_VERSION_STRING=$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD +VBOX_VERSION_BUILD=`sed -e "s/^ *VBOX_VERSION_BUILD *= \+\([0-9]\+\)/\1/;t;d" $PATH_ROOT/Version.kmk` +VBOX_SVN_CONFIG_REV=`sed -e 's/^ *VBOX_SVN_REV_CONFIG_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Config.kmk` +VBOX_SVN_VERSION_REV=`sed -e 's/^ *VBOX_SVN_REV_VERSION_FALLBACK *:= \+\$(patsubst *%:,, *\$Rev: *\([0-9]\+\) *\$ *) */\1/;t;d' $PATH_ROOT/Version.kmk` + +if [ -n "$OVERRIDE_SVN_REV" ]; then + VBOX_SVN_REV=$OVERRIDE_SVN_REV +elif [ "$VBOX_SVN_CONFIG_REV" -gt "$VBOX_SVN_VERSION_REV" ]; then + VBOX_SVN_REV=$VBOX_SVN_CONFIG_REV +else + VBOX_SVN_REV=$VBOX_SVN_VERSION_REV +fi +VBOX_VENDOR=`sed -e 's/^ *VBOX_VENDOR *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk` +VBOX_VENDOR_SHORT=`sed -e 's/^ *VBOX_VENDOR_SHORT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk` +VBOX_PRODUCT=`sed -e 's/^ *VBOX_PRODUCT *= \+\(.\+\)/\1/;t;d' $PATH_ROOT/Config.kmk` +VBOX_C_YEAR=`date +%Y` +VBOX_WITH_PCI_PASSTHROUGH=`sed -e '/^ *VBOX_WITH_PCI_PASSTHROUGH *[:]\?= */!d' -e 's/ *#.*$//' -e 's/^.*= *//' $PATH_ROOT/Config.kmk` + +. $PATH_VBOXDRV/linux/files_vboxdrv +. $PATH_VBOXNET/linux/files_vboxnetflt +. $PATH_VBOXADP/linux/files_vboxnetadp +if [ "${VBOX_WITH_PCI_PASSTHROUGH}" = "1" ]; then + . $PATH_VBOXPCI/linux/files_vboxpci +fi + +# Temporary path for creating the modules, will be removed later +rm -rf "$PATH_TMP" +mkdir $PATH_TMP || exit 1 + +# Create auto-generated version file, needed by all modules +echo "#ifndef ___version_generated_h___" > $PATH_TMP/version-generated.h +echo "#define ___version_generated_h___" >> $PATH_TMP/version-generated.h +echo "" >> $PATH_TMP/version-generated.h +echo "#define VBOX_VERSION_MAJOR $VBOX_VERSION_MAJOR" >> $PATH_TMP/version-generated.h +echo "#define VBOX_VERSION_MINOR $VBOX_VERSION_MINOR" >> $PATH_TMP/version-generated.h +echo "#define VBOX_VERSION_BUILD $VBOX_VERSION_BUILD" >> $PATH_TMP/version-generated.h +echo "#define VBOX_VERSION_STRING_RAW \"$VBOX_VERSION_MAJOR.$VBOX_VERSION_MINOR.$VBOX_VERSION_BUILD\"" >> $PATH_TMP/version-generated.h +echo "#define VBOX_VERSION_STRING \"$VBOX_VERSION_STRING\"" >> $PATH_TMP/version-generated.h +echo "#define VBOX_API_VERSION_STRING \"${VBOX_VERSION_MAJOR}_${VBOX_VERSION_MINOR}\"" >> $PATH_TMP/version-generated.h +[ -n "$EXTRA_VERSION_STRING" ] && echo "#define VBOX_EXTRA_VERSION_STRING \" ${EXTRA_VERSION_STRING}\"" >> $PATH_TMP/version-generated.h +echo "#define VBOX_PRIVATE_BUILD_DESC \"Private build with export_modules\"" >> $PATH_TMP/version-generated.h +echo "" >> $PATH_TMP/version-generated.h +echo "#endif" >> $PATH_TMP/version-generated.h + +# Create auto-generated revision file, needed by all modules +echo "#ifndef __revision_generated_h__" > $PATH_TMP/revision-generated.h +echo "#define __revision_generated_h__" >> $PATH_TMP/revision-generated.h +echo "" >> $PATH_TMP/revision-generated.h +echo "#define VBOX_SVN_REV $VBOX_SVN_REV" >> $PATH_TMP/revision-generated.h +echo "" >> $PATH_TMP/revision-generated.h +echo "#endif" >> $PATH_TMP/revision-generated.h + +# Create auto-generated product file, needed by all modules +echo "#ifndef ___product_generated_h___" > $PATH_TMP/product-generated.h +echo "#define ___product_generated_h___" >> $PATH_TMP/product-generated.h +echo "" >> $PATH_TMP/product-generated.h +echo "#define VBOX_VENDOR \"$VBOX_VENDOR\"" >> $PATH_TMP/product-generated.h +echo "#define VBOX_VENDOR_SHORT \"$VBOX_VENDOR_SHORT\"" >> $PATH_TMP/product-generated.h +echo "" >> $PATH_TMP/product-generated.h +echo "#define VBOX_PRODUCT \"$VBOX_PRODUCT\"" >> $PATH_TMP/product-generated.h +echo "#define VBOX_C_YEAR \"$VBOX_C_YEAR\"" >> $PATH_TMP/product-generated.h +echo "" >> $PATH_TMP/product-generated.h +echo "#endif" >> $PATH_TMP/product-generated.h + +# vboxdrv (VirtualBox host kernel module) +mkdir $PATH_TMP/vboxdrv || exit 1 +for f in $FILES_VBOXDRV_NOBIN; do + install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_TMP/vboxdrv/`echo $f|cut -d'>' -f2`" +done +for f in $FILES_VBOXDRV_BIN; do + install -D -m 0755 `echo $f|cut -d'=' -f1` "$PATH_TMP/vboxdrv/`echo $f|cut -d'>' -f2`" +done +if [ -n "$VBOX_WITH_HARDENING" ]; then + sed -e "s;VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV;;g" \ + -e "s;IPRT_WITH_EFLAGS_AC_PRESERVING;;g" \ + < $PATH_VBOXDRV/linux/Makefile > $PATH_TMP/vboxdrv/Makefile +else + sed -e "s;VBOX_WITH_HARDENING;;g" \ + -e "s;VBOX_WITH_EFLAGS_AC_SET_IN_VBOXDRV;;g" \ + -e "s;IPRT_WITH_EFLAGS_AC_PRESERVING;;g" \ + < $PATH_VBOXDRV/linux/Makefile > $PATH_TMP/vboxdrv/Makefile +fi + +# vboxnetflt (VirtualBox netfilter kernel module) +mkdir $PATH_TMP/vboxnetflt || exit 1 +for f in $VBOX_VBOXNETFLT_SOURCES; do + install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_TMP/vboxnetflt/`echo $f|cut -d'>' -f2`" +done +if [ -n "$VBOX_WITH_HARDENING" ]; then + cat $PATH_VBOXNET/linux/Makefile > $PATH_TMP/vboxnetflt/Makefile +else + sed -e "s;VBOX_WITH_HARDENING;;g" < $PATH_VBOXNET/linux/Makefile > $PATH_TMP/vboxnetflt/Makefile +fi + +# vboxnetadp (VirtualBox network adapter kernel module) +mkdir $PATH_TMP/vboxnetadp || exit 1 +for f in $VBOX_VBOXNETADP_SOURCES; do + install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_TMP/vboxnetadp/`echo $f|cut -d'>' -f2`" +done +if [ -n "$VBOX_WITH_HARDENING" ]; then + cat $PATH_VBOXADP/linux/Makefile > $PATH_TMP/vboxnetadp/Makefile +else + sed -e "s;VBOX_WITH_HARDENING;;g" < $PATH_VBOXADP/linux/Makefile > $PATH_TMP/vboxnetadp/Makefile +fi + +# vboxpci (VirtualBox host PCI access kernel module) +if [ "${VBOX_WITH_PCI_PASSTHROUGH}" = "1" ]; then + mkdir $PATH_TMP/vboxpci || exit 1 + for f in $VBOX_VBOXPCI_SOURCES; do + install -D -m 0644 `echo $f|cut -d'=' -f1` "$PATH_TMP/vboxpci/`echo $f|cut -d'>' -f2`" + done + if [ -n "$VBOX_WITH_HARDENING" ]; then + cat $PATH_VBOXPCI/linux/Makefile > $PATH_TMP/vboxpci/Makefile + else + sed -e "s;VBOX_WITH_HARDENING;;g" < $PATH_VBOXPCI/linux/Makefile > $PATH_TMP/vboxpci/Makefile + fi +fi + +install -D -m 0644 $PATH_LINUX/Makefile $PATH_TMP/Makefile +install -D -m 0755 $PATH_LINUX/build_in_tmp $PATH_TMP/build_in_tmp + +# Only temporary, omit from archive +rm $PATH_TMP/version-generated.h +rm $PATH_TMP/revision-generated.h +rm $PATH_TMP/product-generated.h + +# If we are exporting to a folder then stop now. +test -z "$FOLDER" || exit 0 + +# Do a test build +echo Doing a test build, this may take a while. +make -C $PATH_TMP > $PATH_LOG 2>&1 && + make -C $PATH_TMP clean >> $PATH_LOG 2>&1 || + echo "Warning: test build failed. Please check $PATH_LOG" + +# Create the archive +tar -czf $FILE_OUT -C $PATH_TMP . || exit 1 + +# Remove the temporary directory +rm -r $PATH_TMP diff --git a/src/VBox/HostDrivers/linux/load.sh b/src/VBox/HostDrivers/linux/load.sh new file mode 100755 index 00000000..866951c4 --- /dev/null +++ b/src/VBox/HostDrivers/linux/load.sh @@ -0,0 +1,65 @@ +#!/bin/bash +## @file +# For development, builds and loads all the host drivers. +# + +# +# 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 . +# +# 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 +# + +TARGET=`readlink -e -- "${0}"` || exit 1 # The GNU-specific way. +MY_DIR="${TARGET%/[!/]*}" + +set -e + +# Parse parameters. +OPT_UNLOAD_ONLY= +if [ ${#} -ge 1 -a "${1}" = "-u" ]; then + OPT_UNLOAD_ONLY=yes + shift +fi +if [ ${#} -ge 1 -a '(' "${1}" = "-h" -o "${1}" = "--help" ')' ]; then + echo "usage: load.sh [-u] [make arguments]" + exit 1 +fi + +# Unload, but keep the udev rules. +sudo "${MY_DIR}/vboxdrv.sh" stop + +if [ -z "${OPT_UNLOAD_ONLY}" ]; then + # Build and load. + MAKE_JOBS=`grep vendor_id /proc/cpuinfo | wc -l` + if [ "${MAKE_JOBS}" -le "0" ]; then MAKE_JOBS=1; fi + make "-j${MAKE_JOBS}" -C "${MY_DIR}/src/vboxdrv" "$@" + + echo "Installing SUPDrv (aka VBoxDrv/vboxdrv)" + sudo /sbin/insmod "${MY_DIR}/src/vboxdrv/vboxdrv.ko" +fi + diff --git a/src/VBox/HostDrivers/linux/loadall.sh b/src/VBox/HostDrivers/linux/loadall.sh new file mode 100755 index 00000000..7d7d80a0 --- /dev/null +++ b/src/VBox/HostDrivers/linux/loadall.sh @@ -0,0 +1,85 @@ +#!/bin/bash +## @file +# For development, builds and loads all the host drivers. +# + +# +# 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 . +# +# 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 +# + +TARGET=`readlink -e -- "${0}"` || exit 1 # The GNU-specific way. +MY_DIR="${TARGET%/[!/]*}" +MY_GROUPNAME="vboxusers" + +# +# vboxusers membership check. +# +if ! (id -Gn | grep -w ${MY_GROUPNAME}); then + echo "loadall.sh: you're not a member of vboxusers..."; + # Create the group. + if ! getent group ${MY_GROUPNAME}; then + set -e + sudo groupadd -r -f ${MY_GROUPNAME} + set +e + fi + # Add ourselves. + MY_USER=`id -un` + if [ -z "${MY_USER}" ]; then echo "loadall.sh: cannot figure user name"; exit 1; fi + sudo usermod -a -G ${MY_GROUPNAME} "${MY_USER}"; + + # Require relogon. + echo "loadall.sh: You must log on again to load the new group membership." + exit 1 +fi + +# +# Normal action. +# +if [ ${#} -eq 0 ]; then + if [ -f "${MY_DIR}/src/vboxdrv/vboxdrv.ko" ]; then + echo "Cleaning build folder." + make -C "${MY_DIR}/src" clean > /dev/null + fi + sudo "${MY_DIR}/vboxdrv.sh" setup + +# +# Unload and clean up when '-u' is given. +# +elif [ ${#} -eq 1 -a "x${1}" = x-u ]; then + sudo "${MY_DIR}/vboxdrv.sh" cleanup + +# +# Invalid syntax. +# +else + echo "Usage: loadall.sh [-u]" + exit 1 +fi + diff --git a/src/VBox/HostDrivers/win/Makefile.kmk b/src/VBox/HostDrivers/win/Makefile.kmk new file mode 100644 index 00000000..ed68e39a --- /dev/null +++ b/src/VBox/HostDrivers/win/Makefile.kmk @@ -0,0 +1,66 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for Windows driver tooling lib. +# + +# +# Copyright (C) 2011-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox distribution, in which case the provisions of the +# CDDL are applicable instead of those of the GPL. +# +# You may elect to license modified versions of this file under the +# terms and conditions of either the GPL or the CDDL or both. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# For the installer helper DLL. +LIBRARIES += VBoxDrvCfgSharedStatic +VBoxDrvCfgSharedStatic_TEMPLATE := VBoxR3StaticDllNoAsan +VBoxDrvCfgSharedStatic_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) +VBoxDrvCfgSharedStatic_DEFS := _WIN32_WINNT=0x0501 _UNICODE UNICODE +VBoxDrvCfgSharedStatic_SOURCES := cfg/VBoxDrvCfg.cpp + +# For the driver installer/uninstaller executables. +LIBRARIES += VBoxDrvCfgExe +VBoxDrvCfgExe_TEMPLATE := VBoxR3Exe +VBoxDrvCfgExe_EXTENDS := VBoxDrvCfgSharedStatic + +# For the regular VBox DLLs. +LIBRARIES += VBoxDrvCfgDll +VBoxDrvCfgDll_TEMPLATE := VBoxR3Dll +VBoxDrvCfgDll_EXTENDS := VBoxDrvCfgSharedStatic + + +INSTALLS += HostDrivers-scripts +HostDrivers-scripts_INST = $(INST_DIST) +HostDrivers-scripts_EXEC_SOURCES = \ + loadall.cmd \ + load.cmd + +# generate rules +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/win/VBoxDbgLog.h b/src/VBox/HostDrivers/win/VBoxDbgLog.h new file mode 100644 index 00000000..2a299e91 --- /dev/null +++ b/src/VBox/HostDrivers/win/VBoxDbgLog.h @@ -0,0 +1,150 @@ +/* $Id: VBoxDbgLog.h $ */ +/** @file + * Logging helper + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_win_VBoxDbgLog_h +#define VBOX_INCLUDED_SRC_win_VBoxDbgLog_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef VBOX_DBG_LOG_NAME +# error VBOX_DBG_LOG_NAME should be defined! +#endif + +/* Uncomment to show file/line info in the log */ +/*#define VBOX_DBG_LOG_SHOWLINEINFO*/ + +#define VBOX_DBG_LOG_PREFIX_FMT VBOX_DBG_LOG_NAME"::"LOG_FN_FMT": " +#define VBOX_DBG_LOG_PREFIX_PARMS __PRETTY_FUNCTION__ + +#ifdef VBOX_DBG_LOG_SHOWLINEINFO +# define VBOX_DBG_LOG_SUFFIX_FMT " (%s:%d)\n" +# define VBOX_DBG_LOG_SUFFIX_PARMS ,__FILE__, __LINE__ +#else +# define VBOX_DBG_LOG_SUFFIX_FMT "\n" +# define VBOX_DBG_LOG_SUFFIX_PARMS +#endif + +#ifdef DEBUG_misha +# define BP_WARN() AssertFailed() +#else +# define BP_WARN() do { } while (0) +#endif + +#define _LOGMSG_EXACT(_logger, _a) \ + do \ + { \ + _logger(_a); \ + } while (0) + +#define _LOGMSG(_logger, _a) \ + do \ + { \ + _logger((VBOX_DBG_LOG_PREFIX_FMT, VBOX_DBG_LOG_PREFIX_PARMS)); \ + _logger(_a); \ + _logger((VBOX_DBG_LOG_SUFFIX_FMT VBOX_DBG_LOG_SUFFIX_PARMS)); \ + } while (0) + +/* we can not print paged strings to RT logger, do it this way */ +#define _LOGMSG_STR(_logger, _a, _f) do {\ + int _i = 0; \ + _logger(("\"")); \ + for (;(_a)[_i];++_i) { \ + _logger(("%"_f, (_a)[_i])); \ + }\ + _logger(("\"\n")); \ + } while (0) + +#define _LOGMSG_USTR(_logger, _a) do {\ + int _i = 0; \ + _logger(("\"")); \ + for (;_i<(_a)->Length/2;++_i) { \ + _logger(("%c", (_a)->Buffer[_i])); \ + }\ + _logger(("\"\n")); \ + } while (0) + +#define WARN_NOBP(_a) \ + do \ + { \ + Log((VBOX_DBG_LOG_PREFIX_FMT"WARNING! ", VBOX_DBG_LOG_PREFIX_PARMS)); \ + Log(_a); \ + Log((VBOX_DBG_LOG_SUFFIX_FMT VBOX_DBG_LOG_SUFFIX_PARMS)); \ + } while (0) + +#define WARN(_a) \ + do \ + { \ + WARN_NOBP(_a); \ + BP_WARN(); \ + } while (0) + +#define ASSERT_WARN(_a, _w) do {\ + if(!(_a)) { \ + WARN(_w); \ + }\ + } while (0) + +#define LOG(_a) _LOGMSG(Log, _a) +#define LOGREL(_a) _LOGMSG(LogRel, _a) +#define LOGF(_a) _LOGMSG(LogFlow, _a) +#define LOGF_ENTER() LOGF(("ENTER")) +#define LOGF_LEAVE() LOGF(("LEAVE")) +#define LOG_EXACT(_a) _LOGMSG_EXACT(Log, _a) +#define LOGREL_EXACT(_a) _LOGMSG_EXACT(LogRel, _a) +/* we can not print paged strings to RT logger, do it this way */ +#define LOG_STRA(_a) do {\ + _LOGMSG_STR(Log, _a, "c"); \ + } while (0) +#define LOG_STRW(_a) do {\ + _LOGMSG_STR(Log, _a, "c"); \ + } while (0) +#define LOG_USTR(_a) do {\ + _LOGMSG_USTR(Log, _a); \ + } while (0) +#define LOGREL_STRA(_a) do {\ + _LOGMSG_STR(LogRel, _a, "c"); \ + } while (0) +#define LOGREL_STRW(_a) do {\ + _LOGMSG_STR(LogRel, _a, "c"); \ + } while (0) +#define LOGREL_USTR(_a) do {\ + _LOGMSG_USTR(LogRel, _a); \ + } while (0) + + +#endif /* !VBOX_INCLUDED_SRC_win_VBoxDbgLog_h */ + diff --git a/src/VBox/HostDrivers/win/cfg/Makefile.kup b/src/VBox/HostDrivers/win/cfg/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/HostDrivers/win/cfg/VBoxDrvCfg.cpp b/src/VBox/HostDrivers/win/cfg/VBoxDrvCfg.cpp new file mode 100644 index 00000000..8a3d9764 --- /dev/null +++ b/src/VBox/HostDrivers/win/cfg/VBoxDrvCfg.cpp @@ -0,0 +1,1006 @@ +/* $Id: VBoxDrvCfg.cpp $ */ +/** @file + * VBoxDrvCfg.cpp - Windows Driver Manipulation API implementation. + * + * @note This is EXTREMELY BADLY documented code. Please help improve by + * adding comments whenever you've got a chance! + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static PFNVBOXDRVCFGLOG g_pfnVBoxDrvCfgLog; +static void *g_pvVBoxDrvCfgLog; + +static PFNVBOXDRVCFGPANIC g_pfnVBoxDrvCfgPanic; +static void *g_pvVBoxDrvCfgPanic; + + +VBOXDRVCFG_DECL(void) VBoxDrvCfgLoggerSet(PFNVBOXDRVCFGLOG pfnLog, void *pvLog) +{ + g_pfnVBoxDrvCfgLog = pfnLog; + g_pvVBoxDrvCfgLog = pvLog; +} + +VBOXDRVCFG_DECL(void) VBoxDrvCfgPanicSet(PFNVBOXDRVCFGPANIC pfnPanic, void *pvPanic) +{ + g_pfnVBoxDrvCfgPanic = pfnPanic; + g_pvVBoxDrvCfgPanic = pvPanic; +} + +static void vboxDrvCfgLogRel(const char *pszFormat, ...) +{ + PFNVBOXDRVCFGLOG pfnLog = g_pfnVBoxDrvCfgLog; + void *pvLog = g_pvVBoxDrvCfgLog; + if (pfnLog) + { + char szBuffer[4096]; + va_list va; + va_start(va, pszFormat); + RTStrPrintfV(szBuffer, RT_ELEMENTS(szBuffer), pszFormat, va); + va_end(va); + pfnLog(VBOXDRVCFG_LOG_SEVERITY_REL, szBuffer, pvLog); + } +} + +static void vboxDrvCfgLogRegular(const char *pszFormat, ...) +{ + PFNVBOXDRVCFGLOG pfnLog = g_pfnVBoxDrvCfgLog; + void *pvLog = g_pvVBoxDrvCfgLog; + if (pfnLog) + { + char szBuffer[4096]; + va_list va; + va_start(va, pszFormat); + RTStrPrintfV(szBuffer, RT_ELEMENTS(szBuffer), pszFormat, va); + va_end(va); + pfnLog(VBOXDRVCFG_LOG_SEVERITY_REGULAR, szBuffer, pvLog); + } +} + +static void vboxDrvCfgLogFlow(const char *pszFormat, ...) +{ + PFNVBOXDRVCFGLOG pfnLog = g_pfnVBoxDrvCfgLog; + void *pvLog = g_pvVBoxDrvCfgLog; + if (pfnLog) + { + char szBuffer[4096]; + va_list va; + va_start(va, pszFormat); + RTStrPrintfV(szBuffer, RT_ELEMENTS(szBuffer), pszFormat, va); + va_end(va); + pfnLog(VBOXDRVCFG_LOG_SEVERITY_FLOW, szBuffer, pvLog); + } +} + +static void vboxDrvCfgPanic(void) +{ + PFNVBOXDRVCFGPANIC pfnPanic = g_pfnVBoxDrvCfgPanic; + void *pvPanic = g_pvVBoxDrvCfgPanic; + if (pfnPanic) + pfnPanic(pvPanic); +} + +/* we do not use IPRT Logging because the lib is used in host installer and needs to + * post its msgs to MSI logger */ +#define NonStandardLogCrap(_m) do { vboxDrvCfgLogRegular _m ; } while (0) +#define NonStandardLogFlowCrap(_m) do { vboxDrvCfgLogFlow _m ; } while (0) +#define NonStandardLogRelCrap(_m) do { vboxDrvCfgLogRel _m ; } while (0) +#define NonStandardAssertFailed() vboxDrvCfgPanic() +#define NonStandardAssert(_m) do { \ + if (RT_UNLIKELY(!(_m))) { vboxDrvCfgPanic(); } \ + } while (0) + + +/** + * This is a simple string vector class. + * + * @note Is is _NOT_ a list as the name could lead you to believe, but a vector. + */ +class VBoxDrvCfgStringList +{ +public: + VBoxDrvCfgStringList(size_t a_cElements); + ~VBoxDrvCfgStringList(); + + HRESULT add(LPWSTR pStr); + + size_t size() { return m_cUsed; } + + LPWSTR get(size_t i) { return i < m_cUsed ? m_paStrings[i] : NULL; } +private: + HRESULT grow(size_t a_cNew); + + /** Array of strings. */ + LPWSTR *m_paStrings; + size_t m_cAllocated; + size_t m_cUsed; +}; + +VBoxDrvCfgStringList::VBoxDrvCfgStringList(size_t a_cElements) +{ + m_paStrings = (LPWSTR *)RTMemAllocZ(sizeof(m_paStrings[0]) * a_cElements); + m_cAllocated = a_cElements; + m_cUsed = 0; +} + +VBoxDrvCfgStringList::~VBoxDrvCfgStringList() +{ + if (!m_cAllocated) + return; + + for (size_t i = 0; i < m_cUsed; ++i) + RTMemFree(m_paStrings[i]); + RTMemFree(m_paStrings); + m_paStrings = NULL; + m_cAllocated = 0; + m_cUsed = 0; +} + +HRESULT VBoxDrvCfgStringList::add(LPWSTR pStr) +{ + if (m_cUsed == m_cAllocated) + { + int hrc = grow(m_cAllocated + 16); + if (SUCCEEDED(hrc)) + return hrc; + } + LPWSTR str = (LPWSTR)RTMemDup(pStr, (RTUtf16Len(pStr) + 1) * sizeof(m_paStrings[0][0])); + if (!str) + return E_OUTOFMEMORY; + m_paStrings[m_cUsed] = str; + ++m_cUsed; + return S_OK; +} + +HRESULT VBoxDrvCfgStringList::grow(size_t a_cNew) +{ + NonStandardAssert(a_cNew >= m_cUsed); + if (a_cNew < m_cUsed) + return E_FAIL; + void *pvNew = RTMemReallocZ(m_paStrings, m_cUsed * sizeof(m_paStrings[0]), a_cNew * sizeof(m_paStrings[0])); + if (!pvNew) + return E_OUTOFMEMORY; + m_paStrings = (LPWSTR *)pvNew; + m_cAllocated = a_cNew; + return S_OK; +} + +/* + * inf file manipulation API + */ +typedef bool (*PFNVBOXNETCFG_ENUMERATION_CALLBACK_T)(LPCWSTR lpszFileName, PVOID pContext); + +typedef struct INF_INFO_T +{ + LPCWSTR pwszClassName; + LPCWSTR pwszPnPId; +} INF_INFO_T, *PINF_INFO_T; + +typedef struct INFENUM_CONTEXT_T +{ + INF_INFO_T InfInfo; + DWORD fFlags; + HRESULT hrc; +} INFENUM_CONTEXT_T, *PINFENUM_CONTEXT_T; + +static HRESULT vboxDrvCfgInfQueryContext(HINF hInf, LPCWSTR pwszSection, LPCWSTR pwszKey, PINFCONTEXT pCtx) +{ + if (!SetupFindFirstLineW(hInf, pwszSection, pwszKey, pCtx)) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__ ": SetupFindFirstLine failed WinEr (%Rwc) for Section(%ls), Key(%ls)\n", + dwErr, pwszSection, pwszKey)); + return HRESULT_FROM_WIN32(dwErr); + } + return S_OK; +} + +static HRESULT vboxDrvCfgInfQueryKeyValue(PINFCONTEXT pCtx, DWORD iValue, LPWSTR *ppwszValue, PDWORD pcwcValue) +{ + *ppwszValue = NULL; + if (pcwcValue) + *pcwcValue = 0; + + DWORD cwcValue; + if (!SetupGetStringFieldW(pCtx, iValue, NULL, 0, &cwcValue)) + { + DWORD dwErr = GetLastError(); +// NonStandardAssert(dwErr == ERROR_INSUFFICIENT_BUFFER); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + { + NonStandardLogFlowCrap((__FUNCTION__ ": SetupGetStringField failed WinEr (%Rwc) for iValue(%d)\n", dwErr, iValue)); + return HRESULT_FROM_WIN32(dwErr); + } + } + + LPWSTR pwszValue = (LPWSTR)RTMemAlloc(cwcValue * sizeof(pwszValue[0])); + NonStandardAssert(pwszValue); + if (!pwszValue) + { + NonStandardLogRelCrap((__FUNCTION__ ": SetCoTaskMemAlloc failed to alloc mem of size (%d), for iValue(%d)\n", + cwcValue * sizeof(pwszValue[0]), iValue)); + return E_FAIL; + } + + if (!SetupGetStringFieldW(pCtx, iValue, pwszValue, cwcValue, &cwcValue)) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__ ": SetupGetStringField failed WinEr (%Rwc) for iValue(%d)\n", dwErr, iValue)); + NonStandardAssertFailed(); + RTMemFree(pwszValue); + return HRESULT_FROM_WIN32(dwErr); + } + + *ppwszValue = pwszValue; + if (pcwcValue) + *pcwcValue = cwcValue; + return S_OK; +} + +#if defined(RT_ARCH_AMD64) +# define VBOXDRVCFG_ARCHSTR "amd64" +#else +# define VBOXDRVCFG_ARCHSTR "x86" +#endif + +static HRESULT vboxDrvCfgInfQueryModelsSectionName(HINF hInf, LPWSTR *ppwszValue, PDWORD pcwcValue) +{ + *ppwszValue = NULL; + if (pcwcValue) + *pcwcValue = 0; + + INFCONTEXT InfCtx; + HRESULT hrc = vboxDrvCfgInfQueryContext(hInf, L"Manufacturer", NULL, &InfCtx); + if (hrc != S_OK) + { + NonStandardLogCrap((__FUNCTION__ ": vboxDrvCfgInfQueryContext for Manufacturer failed, hrc=0x%x\n", hrc)); + return hrc; + } + + LPWSTR pwszModels; + DWORD cwcModels; + hrc = vboxDrvCfgInfQueryKeyValue(&InfCtx, 1, &pwszModels, &cwcModels); + if (hrc != S_OK) + { + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgRegQueryKeyValue 1 for Manufacturer failed, hrc=0x%x\n", hrc)); + return hrc; + } + + LPWSTR pwszPlatform = NULL; + DWORD cwcPlatform = 0; + bool fArch = false; + bool fNt = false; + + LPWSTR pwszPlatformCur; + DWORD cwcPlatformCur; + for (DWORD i = 2; (hrc = vboxDrvCfgInfQueryKeyValue(&InfCtx, i, &pwszPlatformCur, &cwcPlatformCur)) == S_OK; ++i) + { + if (RTUtf16ICmpAscii(pwszPlatformCur, "NT" VBOXDRVCFG_ARCHSTR) == 0) + fArch = true; + else + { + if (fNt || RTUtf16ICmpAscii(pwszPlatformCur, "NT") != 0) + { + RTMemFree(pwszPlatformCur); + pwszPlatformCur = NULL; + continue; + } + fNt = true; + } + + cwcPlatform = cwcPlatformCur; + if (pwszPlatform) + RTMemFree(pwszPlatform); + pwszPlatform = pwszPlatformCur; + pwszPlatformCur = NULL; + } + + hrc = S_OK; + + LPWSTR pwszResult = NULL; + DWORD cwcResult = 0; + if (pwszPlatform) + { + pwszResult = (LPWSTR)RTMemAlloc((cwcModels + cwcPlatform) * sizeof(pwszResult[0])); + if (pwszResult) + { + memcpy(pwszResult, pwszModels, (cwcModels - 1) * sizeof(pwszResult[0])); + pwszResult[cwcModels - 1] = L'.'; + memcpy(&pwszResult[cwcModels], pwszPlatform, cwcPlatform * sizeof(pwszResult[0])); + cwcResult = cwcModels + cwcPlatform; + } + else + hrc = E_OUTOFMEMORY; + } + else + { + pwszResult = pwszModels; + cwcResult = cwcModels; + pwszModels = NULL; + } + + if (pwszModels) + RTMemFree(pwszModels); + if (pwszPlatform) + RTMemFree(pwszPlatform); + + if (hrc == S_OK) + { + *ppwszValue = pwszResult; + if (pcwcValue) + *pcwcValue = cwcResult; + } + + return hrc; +} + +static HRESULT vboxDrvCfgInfQueryFirstPnPId(HINF hInf, LPWSTR *ppwszPnPId) +{ + *ppwszPnPId = NULL; + + LPWSTR pwszModels; + HRESULT hrc = vboxDrvCfgInfQueryModelsSectionName(hInf, &pwszModels, NULL); + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgInfQueryModelsSectionName returned pwszModels = (%ls)", pwszModels)); + if (hrc != S_OK) + { + NonStandardLogCrap((__FUNCTION__ ": vboxDrvCfgRegQueryKeyValue for Manufacturer failed, hrc=0x%x\n", hrc)); + return hrc; + } + + LPWSTR pwszPnPId = NULL; + INFCONTEXT InfCtx; + hrc = vboxDrvCfgInfQueryContext(hInf, pwszModels, NULL, &InfCtx); + if (hrc == S_OK) + { + hrc = vboxDrvCfgInfQueryKeyValue(&InfCtx, 2, &pwszPnPId, NULL); + if (hrc == S_OK) + { + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgRegQueryKeyValue for models (%ls) returned pwszPnPId (%ls)\n", pwszModels, pwszPnPId)); + *ppwszPnPId = pwszPnPId; + } + else + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgRegQueryKeyValue for models (%ls) failed, hrc=0x%x\n", pwszModels, hrc)); + } + else + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgInfQueryContext for models (%ls) failed, hrc=0x%x\n", pwszModels, hrc)); + + RTMemFree(pwszModels); + return hrc; +} + +static bool vboxDrvCfgInfEnumerationCallback(LPCWSTR pwszFileName, PVOID pCtxt) +{ + PINFENUM_CONTEXT_T pContext = (PINFENUM_CONTEXT_T)pCtxt; + NonStandardLogRelCrap((__FUNCTION__": pwszFileName (%ls)\n", pwszFileName)); + NonStandardLogRelCrap((__FUNCTION__ ": pContext->InfInfo.pwszClassName = (%ls)\n", pContext->InfInfo.pwszClassName)); + HINF hInf = SetupOpenInfFileW(pwszFileName, pContext->InfInfo.pwszClassName, INF_STYLE_WIN4, NULL /*__in PUINT ErrorLine */); + if (hInf == INVALID_HANDLE_VALUE) + { + DWORD const dwErr = GetLastError(); +// NonStandardAssert(dwErr == ERROR_CLASS_MISMATCH); + if (dwErr != ERROR_CLASS_MISMATCH) + NonStandardLogCrap((__FUNCTION__ ": SetupOpenInfFileW err dwErr=%u\n", dwErr)); + else + NonStandardLogCrap((__FUNCTION__ ": dwErr == ERROR_CLASS_MISMATCH\n")); + return true; + } + + LPWSTR pwszPnPId; + HRESULT hrc = vboxDrvCfgInfQueryFirstPnPId(hInf, &pwszPnPId); + NonStandardLogRelCrap((__FUNCTION__ ": vboxDrvCfgInfQueryFirstPnPId returned pwszPnPId = (%ls)\n", pwszPnPId)); + NonStandardLogRelCrap((__FUNCTION__ ": pContext->InfInfo.pwszPnPId = (%ls)\n", pContext->InfInfo.pwszPnPId)); + if (hrc == S_OK) + { + if (!RTUtf16ICmp(pContext->InfInfo.pwszPnPId, pwszPnPId)) + { + /** @todo bird/2020-09-01: See the following during uninstallation with + * windbg attached (see DllMain trick): + * + * ModLoad: 00007ffa`73c20000 00007ffa`73c4f000 C:\WINDOWS\SYSTEM32\drvsetup.dll + * (1b238.1b254): Access violation - code c0000005 (first chance) + * First chance exceptions are reported before any exception handling. + * This exception may be expected and handled. + * KERNELBASE!WaitForMultipleObjectsEx+0x9e: + * 00007ffa`8247cb6e 458b74fd00 mov r14d,dword ptr [r13+rdi*8] ds:00000000`00000010=???????? + * 0:006> k + * # Child-SP RetAddr Call Site + * 00 00000099`6e4fe7a0 00007ffa`73c2df46 KERNELBASE!WaitForMultipleObjectsEx+0x9e + * 01 00000099`6e4fea90 00007ffa`73c32ec2 drvsetup!pSetupStringTableEnum+0x3e + * 02 00000099`6e4feae0 00007ffa`73c2ae9d drvsetup!DrvUtilsUpdateInfoEnumDriverInfs+0x8e + * 03 00000099`6e4feb20 00007ffa`73c2b1cc drvsetup!DrvSetupUninstallDriverInternal+0x211 + * 04 00000099`6e4febe0 00007ffa`83eb09d7 drvsetup!pDrvSetupUninstallDriver+0xfc + * 05 00000099`6e4fec30 00007ffa`83eb06a0 SETUPAPI!pSetupUninstallOEMInf+0x26b + * 06 00000099`6e4fef00 00007ffa`57a39fb7 SETUPAPI!SetupUninstallOEMInfW+0x170 + * 07 00000099`6e4ff190 00007ffa`57a3ae0c MSID039!vboxDrvCfgInfEnumerationCallback+0xf7 [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 445] + * 08 00000099`6e4ff1c0 00007ffa`57a321e6 MSID039!VBoxDrvCfgInfUninstallAllSetupDi+0xfc [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 653] + * 09 (Inline Function) --------`-------- MSID039!_removeHostOnlyInterfaces+0x6c [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1523] + * 0a 00000099`6e4ff240 00007ffa`610f59d3 MSID039!RemoveHostOnlyInterfaces+0x76 [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1545] + * 0b 00000099`6e4ff270 00007ffa`610d80ac msi!CallCustomDllEntrypoint+0x2b + * 0c 00000099`6e4ff2e0 00007ffa`84567034 msi!CMsiCustomAction::CustomActionThread+0x34c + * 0d 00000099`6e4ff8d0 00007ffa`849a2651 KERNEL32!BaseThreadInitThunk+0x14 + * 0e 00000099`6e4ff900 00000000`00000000 ntdll!RtlUserThreadStart+0x21 + * 0:006> r + * rax=000000996e114000 rbx=0000000000000002 rcx=0000000000000002 + * rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000 + * rip=00007ffa8247cb6e rsp=000000996e4fe7a0 rbp=0000000000000004 + * r8=0000000000000000 r9=00000000ffffffff r10=0000000000000000 + * r11=0000000000000246 r12=00000000ffffffff r13=0000000000000010 + * r14=00007ffa73c32e00 r15=0000000000000001 + * iopl=0 nv up ei ng nz ac pe cy + * cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010293 + * KERNELBASE!WaitForMultipleObjectsEx+0x9e: + * 00007ffa`8247cb6e 458b74fd00 mov r14d,dword ptr [r13+rdi*8] ds:00000000`00000010=???????? + * + * Happens with the filter driver too: + * + * (1b238.1b7e0): Access violation - code c0000005 (first chance) + * First chance exceptions are reported before any exception handling. + * This exception may be expected and handled. + * KERNELBASE!WaitForMultipleObjectsEx+0x9e: + * 00007ffa`8247cb6e 458b74fd00 mov r14d,dword ptr [r13+rdi*8] ds:00000000`00000010=???????? + * 0:006> k + * # Child-SP RetAddr Call Site + * 00 00000099`6e4fe8c0 00007ffa`6558df46 KERNELBASE!WaitForMultipleObjectsEx+0x9e + * 01 00000099`6e4febb0 00007ffa`65592ec2 drvsetup!pSetupStringTableEnum+0x3e + * 02 00000099`6e4fec00 00007ffa`6558ae9d drvsetup!DrvUtilsUpdateInfoEnumDriverInfs+0x8e + * 03 00000099`6e4fec40 00007ffa`6558b1cc drvsetup!DrvSetupUninstallDriverInternal+0x211 + * 04 00000099`6e4fed00 00007ffa`83eb09d7 drvsetup!pDrvSetupUninstallDriver+0xfc + * 05 00000099`6e4fed50 00007ffa`83eb06a0 SETUPAPI!pSetupUninstallOEMInf+0x26b + * 06 00000099`6e4ff020 00007ffa`57a39fb7 SETUPAPI!SetupUninstallOEMInfW+0x170 + * 07 00000099`6e4ff2b0 00007ffa`57a3abaf MSI398C!vboxDrvCfgInfEnumerationCallback+0xf7 [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 445] + * 08 (Inline Function) --------`-------- MSI398C!vboxDrvCfgEnumFiles+0x4f [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 670] + * 09 00000099`6e4ff2e0 00007ffa`57a3792e MSI398C!VBoxDrvCfgInfUninstallAllF+0xdf [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 723] + * 0a 00000099`6e4ff7b0 00007ffa`57a33411 MSI398C!vboxNetCfgWinNetLwfUninstall+0x9e [E:\vbox\svn\trunk\src\VBox\HostDrivers\VBoxNetFlt\win\cfg\VBoxNetCfg.cpp @ 2249] + * 0b 00000099`6e4ff7e0 00007ffa`57a3263d MSI398C!_uninstallNetLwf+0x71 [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1206] + * 0c 00000099`6e4ff810 00007ffa`610f59d3 MSI398C!UninstallNetFlt+0xd [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1124] + * 0d 00000099`6e4ff840 00007ffa`610d80ac msi!CallCustomDllEntrypoint+0x2b + * 0e 00000099`6e4ff8b0 00007ffa`84567034 msi!CMsiCustomAction::CustomActionThread+0x34c + * 0f 00000099`6e4ffea0 00007ffa`849a2651 KERNEL32!BaseThreadInitThunk+0x14 + * 10 00000099`6e4ffed0 00000000`00000000 ntdll!RtlUserThreadStart+0x21 + * 0:006> r + * rax=000000996e114000 rbx=0000000000000002 rcx=0000000000000002 + * rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000 + * rip=00007ffa8247cb6e rsp=000000996e4fe8c0 rbp=0000000000000004 + * r8=0000000000000000 r9=00000000ffffffff r10=0000000000000000 + * r11=0000000000000246 r12=00000000ffffffff r13=0000000000000010 + * r14=00007ffa65592e00 r15=0000000000000000 + * iopl=0 nv up ei ng nz ac pe cy + * cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010293 + * KERNELBASE!WaitForMultipleObjectsEx+0x9e: + * 00007ffa`8247cb6e 458b74fd00 mov r14d,dword ptr [r13+rdi*8] ds:00000000`00000010=???????? + * + * BUGBUG + */ +#if 0 + if (!SetupUninstallOEMInfW(pwszFileName, pContext->fFlags, /* could be SUOI_FORCEDELETE */ NULL /* Reserved */)) +#else /* Just in case the API doesn't catch it itself (seems it does on w10/19044). */ + BOOL fRc = TRUE; + __try + { + fRc = SetupUninstallOEMInfW(pwszFileName, pContext->fFlags, /* could be SUOI_FORCEDELETE */ NULL /* Reserved */); + } + __except(hrc = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) + { + NonStandardLogRelCrap((__FUNCTION__ ": SetupUninstallOEMInf raised an exception: %#x\n", hrc)); + hrc = E_ABORT; + } + if (!fRc) +#endif + { + DWORD const dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__ ": SetupUninstallOEMInf failed for file (%ls), dwErr=%u\n", pwszFileName, dwErr)); + NonStandardAssertFailed(); + hrc = HRESULT_FROM_WIN32( dwErr ); + } + } + + RTMemFree(pwszPnPId); + } + else + NonStandardLogCrap((__FUNCTION__ ": vboxDrvCfgInfQueryFirstPnPId failed, hrc=0x%x\n", hrc)); + + SetupCloseInfFile(hInf); + return true; +} + + +#define VBOXDRVCFG_S_INFEXISTS (HRESULT_FROM_WIN32(ERROR_FILE_EXISTS)) + +static HRESULT vboxDrvCfgInfCopyEx(IN LPCWSTR pwszInfPath, IN DWORD fCopyStyle, OUT LPWSTR pwszDstName, IN DWORD cwcDstName, + OUT PDWORD pcwcDstNameRet, OUT LPWSTR *pwszDstNameComponent) +{ + /* Extract the director from pwszInfPath */ + size_t cchPath = RTUtf16Len(pwszInfPath); + while (cchPath > 0 && !RTPATH_IS_SEP(pwszInfPath[cchPath - 1])) + cchPath--; + + WCHAR *pwszMediaLocation = (WCHAR *)alloca(((cchPath) + 1) * sizeof(pwszMediaLocation[0])); + memcpy(pwszMediaLocation, pwszInfPath, cchPath * sizeof(pwszMediaLocation[0])); + pwszMediaLocation[cchPath] = '\0'; + + + if (!SetupCopyOEMInfW(pwszInfPath, pwszMediaLocation, SPOST_PATH, fCopyStyle, + pwszDstName, cwcDstName, pcwcDstNameRet, pwszDstNameComponent)) + { + DWORD const dwErr = GetLastError(); + HRESULT hrc = HRESULT_FROM_WIN32(dwErr); + if (fCopyStyle != SP_COPY_REPLACEONLY || hrc != VBOXDRVCFG_S_INFEXISTS) + NonStandardLogRelCrap((__FUNCTION__ ": SetupCopyOEMInf fail dwErr=%u\n", dwErr)); + return hrc; + } + + return S_OK; +} + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgInfInstall(IN LPCWSTR pwszInfPath) +{ + return vboxDrvCfgInfCopyEx(pwszInfPath, 0 /*fCopyStyle*/, NULL /*pwszDstName*/, 0 /*cwcDstName*/, + NULL /*pcwcDstNameRet*/, NULL /*pwszDstNameComponent*/); +} + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgInfUninstall(IN LPCWSTR pwszInfPath, DWORD fFlags) +{ + WCHAR wszDstInfName[MAX_PATH]; + DWORD cwcDword = RT_ELEMENTS(wszDstInfName); + HRESULT hrc = vboxDrvCfgInfCopyEx(pwszInfPath, SP_COPY_REPLACEONLY, wszDstInfName, cwcDword, &cwcDword, NULL); + if (hrc == VBOXDRVCFG_S_INFEXISTS) + { + if (!SetupUninstallOEMInfW(wszDstInfName, fFlags, NULL /*Reserved*/)) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__ ": SetupUninstallOEMInf failed for file (%ls), oem(%ls), dwErr=%u\n", + pwszInfPath, wszDstInfName, dwErr)); + NonStandardAssertFailed(); + return HRESULT_FROM_WIN32(dwErr); + } + } + return S_OK; +} + + +static HRESULT vboxDrvCfgCollectInfsSetupDi(const GUID *pGuid, LPCWSTR pwszPnPId, VBoxDrvCfgStringList &a_rList) +{ + DWORD dwErrRet = ERROR_SUCCESS; + HDEVINFO hDevInfo = SetupDiCreateDeviceInfoList(pGuid, /*ClassGuid*/ NULL /*hwndParent*/); + if (hDevInfo != INVALID_HANDLE_VALUE) + { + /** @todo bird/2020-09-01: seeing this during uninstall when windbg is + * attached to msiexec.exe (see trick in DllMain): + * + * (1b238.1b254): Access violation - code c0000005 (first chance) + * First chance exceptions are reported before any exception handling. + * This exception may be expected and handled. + * SETUPAPI!SpSignVerifyInfFile+0x246: + * 00007ffa`83e3ee3e 663907 cmp word ptr [rdi],ax ds:00000000`00000000=???? + * 0:006> k + * # Child-SP RetAddr Call Site + * 00 00000099`6e4f8340 00007ffa`83e1e765 SETUPAPI!SpSignVerifyInfFile+0x246 + * 01 00000099`6e4f8420 00007ffa`83e9ebfd SETUPAPI!DrvSearchCallback+0x1155 + * 02 00000099`6e4f9380 00007ffa`83e9eed3 SETUPAPI!InfCacheSearchDirectory+0x469 + * 03 00000099`6e4f98b0 00007ffa`83e9f454 SETUPAPI!InfCacheSearchDirectoryRecursive+0xcf + * 04 00000099`6e4f9fe0 00007ffa`83e9da10 SETUPAPI!InfCacheSearchPath+0x1a0 + * 05 00000099`6e4fa2b0 00007ffa`83e262a2 SETUPAPI!EnumDrvInfsInDirPathList+0x560 + * 06 00000099`6e4fa3f0 00007ffa`57a39a21 SETUPAPI!SetupDiBuildDriverInfoList+0x1242 + * 07 00000099`6e4fab10 00007ffa`57a3ad6e MSID039!vboxDrvCfgCollectInfsSetupDi+0x71 [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 526] + * 08 00000099`6e4ff1c0 00007ffa`57a321e6 MSID039!VBoxDrvCfgInfUninstallAllSetupDi+0x5e [E:\vbox\svn\trunk\src\VBox\HostDrivers\win\cfg\VBoxDrvCfg.cpp @ 633] + * 09 (Inline Function) --------`-------- MSID039!_removeHostOnlyInterfaces+0x6c [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1523] + * 0a 00000099`6e4ff240 00007ffa`610f59d3 MSID039!RemoveHostOnlyInterfaces+0x76 [E:\vbox\svn\trunk\src\VBox\Installer\win\InstallHelper\VBoxInstallHelper.cpp @ 1545] + * 0b 00000099`6e4ff270 00007ffa`610d80ac msi!CallCustomDllEntrypoint+0x2b + * 0c 00000099`6e4ff2e0 00007ffa`84567034 msi!CMsiCustomAction::CustomActionThread+0x34c + * 0d 00000099`6e4ff8d0 00007ffa`849a2651 KERNEL32!BaseThreadInitThunk+0x14 + * 0e 00000099`6e4ff900 00000000`00000000 ntdll!RtlUserThreadStart+0x21 + * 0:006> r + * rax=0000000000000000 rbx=0000000000000490 rcx=aa222a2675da0000 + * rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000 + * rip=00007ffa83e3ee3e rsp=000000996e4f8340 rbp=000000996e4f9480 + * r8=0000000000050004 r9=00007ffa83ef5418 r10=0000000000008000 + * r11=000000996e4f76f0 r12=000000996e4f84c8 r13=0000000000000000 + * r14=000000996e4f88d0 r15=0000000000000000 + * iopl=0 nv up ei pl nz ac pe cy + * cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010213 + * SETUPAPI!SpSignVerifyInfFile+0x246: + * 00007ffa`83e3ee3e 663907 cmp word ptr [rdi],ax ds:00000000`00000000=???? + */ +#if 0 + if (SetupDiBuildDriverInfoList(hDevInfo, NULL /*DeviceInfoData*/, SPDIT_CLASSDRIVER)) +#else /* Just in case the API doesn't catch it itself (seems it does on w10/19044). */ + BOOL fRc = FALSE; + DWORD uXcpt = 0; + __try + { + fRc = SetupDiBuildDriverInfoList(hDevInfo, NULL /*DeviceInfoData*/, SPDIT_CLASSDRIVER); + } + __except(uXcpt = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) + { + NonStandardLogRelCrap((__FUNCTION__ ": SetupDiBuildDriverInfoList raised an exception: %#x\n", uXcpt)); + } + if (fRc) +#endif + { + SP_DRVINFO_DATA DrvInfo; + DrvInfo.cbSize = sizeof(SP_DRVINFO_DATA); + + union + { + SP_DRVINFO_DETAIL_DATA_W s; + uint8_t ab[16384]; + } DrvDetail; + + /* Ensure zero terminated buffer: */ + DrvDetail.ab[sizeof(DrvDetail) - 1] = '\0'; + DrvDetail.ab[sizeof(DrvDetail) - 2] = '\0'; + + for (DWORD i = 0; dwErrRet == ERROR_SUCCESS; i++) + { + if (SetupDiEnumDriverInfo(hDevInfo, NULL /*DeviceInfoData*/, SPDIT_CLASSDRIVER /*DriverType*/, + i /*MemberIndex*/, &DrvInfo /*DriverInfoData*/)) + { + DWORD dwReq = 0; + DrvDetail.s.cbSize = sizeof(SP_DRVINFO_DETAIL_DATA); + if (SetupDiGetDriverInfoDetail(hDevInfo, NULL /*DeviceInfoData*/, &DrvInfo, + &DrvDetail.s, sizeof(DrvDetail) - 2 /*our terminator*/, &dwReq)) + { + for (WCHAR *pwszHwId = DrvDetail.s.HardwareID; + *pwszHwId != '\0' && (uintptr_t)pwszHwId < (uintptr_t)&DrvDetail.ab[sizeof(DrvDetail)]; + pwszHwId += RTUtf16Len(pwszHwId) + 1) + { + if (RTUtf16ICmp(pwszHwId, pwszPnPId) == 0) + { + NonStandardAssert(DrvDetail.s.InfFileName[0]); + if (DrvDetail.s.InfFileName[0]) + { + HRESULT hrc = a_rList.add(DrvDetail.s.InfFileName); + NonStandardLogRelCrap((__FUNCTION__": %ls added to list (%#x)", DrvDetail.s.InfFileName, hrc)); + if (hrc != S_OK) + { + dwErrRet = ERROR_OUTOFMEMORY; + break; + } + } + } + } + } + else + { + DWORD dwErr2 = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": SetupDiGetDriverInfoDetail fail dwErr=%u, size(%d)", dwErr2, dwReq)); +// NonStandardAssertFailed(); + } + } + else + { + DWORD dwErr2 = GetLastError(); + if (dwErr2 == ERROR_NO_MORE_ITEMS) + { + NonStandardLogRelCrap((__FUNCTION__": dwErr == ERROR_NO_MORE_ITEMS -> search was finished ")); + break; + } + NonStandardAssertFailed(); + } + } + + SetupDiDestroyDriverInfoList(hDevInfo, NULL /*DeviceInfoData*/, SPDIT_CLASSDRIVER); + } + else + { + dwErrRet = GetLastError(); + NonStandardAssertFailed(); + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + } + else + { + dwErrRet = GetLastError(); + NonStandardAssertFailed(); + } + + return HRESULT_FROM_WIN32(dwErrRet); +} + +#if 0 +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgInit() +{ + int rc = RTR3InitDll(0); + if (rc != VINF_SUCCESS) + { + NonStandardLogRelCrap(("Could not init IPRT!, rc (%d)\n", rc)); + return E_FAIL; + } + + return S_OK; +} + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgTerm() +{ + return S_OK; +} +#endif + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgInfUninstallAllSetupDi(IN const GUID *pGuidClass, IN LPCWSTR pwszClassName, + IN LPCWSTR pwszPnPId, IN DWORD fFlags) +{ + VBoxDrvCfgStringList list(128); + HRESULT hrc = vboxDrvCfgCollectInfsSetupDi(pGuidClass, pwszPnPId, list); + NonStandardLogRelCrap((__FUNCTION__": vboxDrvCfgCollectInfsSetupDi returned %d devices with PnPId %ls and class name %ls", + list.size(), pwszPnPId, pwszClassName)); + if (hrc == S_OK) + { + INFENUM_CONTEXT_T Context; + Context.InfInfo.pwszClassName = pwszClassName; + Context.InfInfo.pwszPnPId = pwszPnPId; + Context.fFlags = fFlags; + Context.hrc = S_OK; + size_t const cItems = list.size(); + for (size_t i = 0; i < cItems; ++i) + { + LPCWSTR pwszInf = list.get(i); + + /* Find the start of the filename: */ + size_t offFilename = RTUtf16Len(pwszInf); + while (offFilename > 0 && !RTPATH_IS_SEP(pwszInf[offFilename - 1])) + offFilename--; + + vboxDrvCfgInfEnumerationCallback(&pwszInf[offFilename], &Context); + NonStandardLogRelCrap((__FUNCTION__": inf = %ls\n", pwszInf)); + } + } + return hrc; +} + +static HRESULT vboxDrvCfgEnumFiles(LPCWSTR pwszDirAndPattern, PFNVBOXNETCFG_ENUMERATION_CALLBACK_T pfnCallback, PVOID pContext) +{ + HRESULT hrc = S_OK; + + WIN32_FIND_DATAW Data; + RT_ZERO(Data); + HANDLE hEnum = FindFirstFileW(pwszDirAndPattern, &Data); + if (hEnum != INVALID_HANDLE_VALUE) + { + for (;;) + { + if (!pfnCallback(Data.cFileName, pContext)) + break; + + /* next iteration */ + RT_ZERO(Data); + BOOL fNext = FindNextFile(hEnum, &Data); + if (!fNext) + { + DWORD dwErr = GetLastError(); + if (dwErr != ERROR_NO_MORE_FILES) + { + NonStandardLogRelCrap((__FUNCTION__": FindNextFile fail dwErr=%u\n", dwErr)); + NonStandardAssertFailed(); + hrc = HRESULT_FROM_WIN32(dwErr); + } + break; + } + } + + FindClose(hEnum); + } + else + { + DWORD dwErr = GetLastError(); + if (dwErr != ERROR_NO_MORE_FILES) + { + NonStandardLogRelCrap((__FUNCTION__": FindFirstFile fail dwErr=%u\n", dwErr)); + NonStandardAssertFailed(); + hrc = HRESULT_FROM_WIN32(dwErr); + } + } + + return hrc; +} + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgInfUninstallAllF(LPCWSTR pwszClassName, LPCWSTR pwszPnPId, DWORD fFlags) +{ + static WCHAR const s_wszFilter[] = L"\\inf\\oem*.inf"; + + HRESULT hrc; + WCHAR wszInfDirPath[MAX_PATH]; + UINT cwcInput = RT_ELEMENTS(wszInfDirPath) - RT_ELEMENTS(s_wszFilter); + UINT cwcWindows = GetSystemWindowsDirectoryW(wszInfDirPath, cwcInput); + if (cwcWindows > 0 && cwcWindows < cwcInput) + { + RTUtf16Copy(&wszInfDirPath[cwcWindows], RT_ELEMENTS(wszInfDirPath) - cwcWindows, s_wszFilter); + + INFENUM_CONTEXT_T Context; + Context.InfInfo.pwszClassName = pwszClassName; + Context.InfInfo.pwszPnPId = pwszPnPId; + Context.fFlags = fFlags; + Context.hrc = S_OK; + NonStandardLogRelCrap((__FUNCTION__": Calling vboxDrvCfgEnumFiles(wszInfDirPath, vboxDrvCfgInfEnumerationCallback, &Context)")); + hrc = vboxDrvCfgEnumFiles(wszInfDirPath, vboxDrvCfgInfEnumerationCallback, &Context); + NonStandardAssert(hrc == S_OK); + if (hrc == S_OK) + hrc = Context.hrc; + else + NonStandardLogRelCrap((__FUNCTION__": vboxDrvCfgEnumFiles failed, hrc=0x%x\n", hrc)); + } + else + { + NonStandardLogRelCrap((__FUNCTION__": GetSystemWindowsDirectory failed, cwcWindows=%u lasterr=%u\n", cwcWindows, GetLastError())); + NonStandardAssertFailed(); + hrc = E_FAIL; + } + + return hrc; + +} + +/* time intervals in milliseconds */ +/* max time to wait for the service to startup */ +#define VBOXDRVCFG_SVC_WAITSTART_TIME 10000 +/* sleep time before service status polls */ +#define VBOXDRVCFG_SVC_WAITSTART_TIME_PERIOD 100 +/* number of service start polls */ +#define VBOXDRVCFG_SVC_WAITSTART_RETRIES (VBOXDRVCFG_SVC_WAITSTART_TIME/VBOXDRVCFG_SVC_WAITSTART_TIME_PERIOD) + +VBOXDRVCFG_DECL(HRESULT) VBoxDrvCfgSvcStart(LPCWSTR pwszSvcName) +{ + SC_HANDLE hMgr = OpenSCManagerW(NULL, NULL, SERVICE_QUERY_STATUS | SERVICE_START); + if (hMgr == NULL) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": OpenSCManager failed, dwErr=%u\n", dwErr)); + return HRESULT_FROM_WIN32(dwErr); + } + + HRESULT hrc = S_OK; + SC_HANDLE hSvc = OpenServiceW(hMgr, pwszSvcName, SERVICE_QUERY_STATUS | SERVICE_START); + if (hSvc) + { + SERVICE_STATUS Status; + BOOL fRc = QueryServiceStatus(hSvc, &Status); + if (fRc) + { + if (Status.dwCurrentState != SERVICE_RUNNING && Status.dwCurrentState != SERVICE_START_PENDING) + { + NonStandardLogRelCrap(("Starting service (%ls)\n", pwszSvcName)); + + fRc = StartService(hSvc, 0, NULL); + if (!fRc) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": StartService failed dwErr=%u\n", dwErr)); + hrc = HRESULT_FROM_WIN32(dwErr); + } + } + + if (fRc) + { + fRc = QueryServiceStatus(hSvc, &Status); + if (fRc) + { + if (Status.dwCurrentState == SERVICE_START_PENDING) + for (size_t i = 0; i < VBOXDRVCFG_SVC_WAITSTART_RETRIES; ++i) + { + Sleep(VBOXDRVCFG_SVC_WAITSTART_TIME_PERIOD); + fRc = QueryServiceStatus(hSvc, &Status); + if (!fRc) + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": QueryServiceStatus failed dwErr=%u\n", dwErr)); + hrc = HRESULT_FROM_WIN32(dwErr); + break; + } + if (Status.dwCurrentState != SERVICE_START_PENDING) + break; + } + + if (hrc != S_OK || Status.dwCurrentState != SERVICE_RUNNING) + { + NonStandardLogRelCrap((__FUNCTION__": Failed to start the service\n")); + hrc = E_FAIL; + } + } + else + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": QueryServiceStatus failed dwErr=%u\n", dwErr)); + hrc = HRESULT_FROM_WIN32(dwErr); + } + } + } + else + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": QueryServiceStatus failed dwErr=%u\n", dwErr)); + hrc = HRESULT_FROM_WIN32(dwErr); + } + + CloseServiceHandle(hSvc); + } + else + { + DWORD dwErr = GetLastError(); + NonStandardLogRelCrap((__FUNCTION__": OpenServiceW failed, dwErr=%u\n", dwErr)); + hrc = HRESULT_FROM_WIN32(dwErr); + } + + CloseServiceHandle(hMgr); + return hrc; +} + + +HRESULT VBoxDrvCfgDrvUpdate(LPCWSTR pszwHwId, LPCWSTR psxwInf, BOOL *pfRebootRequired) +{ + if (pfRebootRequired) + *pfRebootRequired = FALSE; + + WCHAR wszInfFullPath[MAX_PATH]; + DWORD dwChars = GetFullPathNameW(psxwInf, MAX_PATH, wszInfFullPath, NULL /*lpFilePart*/); + if (!dwChars || dwChars >= MAX_PATH) + { + NonStandardLogCrap(("GetFullPathNameW failed, dwErr=%u, dwChars=%ld\n", GetLastError(), dwChars)); + return E_INVALIDARG; + } + + BOOL fRebootRequired = FALSE; + if (!UpdateDriverForPlugAndPlayDevicesW(NULL /*hwndParent*/, pszwHwId, wszInfFullPath, INSTALLFLAG_FORCE, &fRebootRequired)) + { + DWORD dwErr = GetLastError(); + NonStandardLogCrap(("UpdateDriverForPlugAndPlayDevicesW failed, dwErr=%u\n", dwErr)); + return HRESULT_FROM_WIN32(dwErr); + } + + if (fRebootRequired) + NonStandardLogCrap(("!!Driver Update: REBOOT REQUIRED!!\n")); + + if (pfRebootRequired) + *pfRebootRequired = fRebootRequired; + + return S_OK; +} + diff --git a/src/VBox/HostDrivers/win/load.cmd b/src/VBox/HostDrivers/win/load.cmd new file mode 100644 index 00000000..3015c787 --- /dev/null +++ b/src/VBox/HostDrivers/win/load.cmd @@ -0,0 +1,117 @@ +@echo off +rem $Id: load.cmd $ +rem rem @file +rem Windows NT batch script for loading the support driver. +rem + +rem +rem Copyright (C) 2009-2023 Oracle and/or its affiliates. +rem +rem This file is part of VirtualBox base platform packages, as +rem available from https://www.virtualbox.org. +rem +rem This program is free software; you can redistribute it and/or +rem modify it under the terms of the GNU General Public License +rem as published by the Free Software Foundation, in version 3 of the +rem License. +rem +rem This program is distributed in the hope that it will be useful, but +rem WITHOUT ANY WARRANTY; without even the implied warranty of +rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +rem General Public License for more details. +rem +rem You should have received a copy of the GNU General Public License +rem along with this program; if not, see . +rem +rem The contents of this file may alternatively be used under the terms +rem of the Common Development and Distribution License Version 1.0 +rem (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +rem in the VirtualBox distribution, in which case the provisions of the +rem CDDL are applicable instead of those of the GPL. +rem +rem You may elect to license modified versions of this file under the +rem terms and conditions of either the GPL or the CDDL or both. +rem +rem SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +rem + + +setlocal ENABLEEXTENSIONS +setlocal ENABLEDELAYEDEXPANSION +setlocal + + +rem +rem find the directory we're in. +rem +set MY_DIR=%~dp0 +if exist "%MY_DIR%\load.cmd" goto dir_okay +echo load.cmd: failed to find load.sh in "%~dp0". +goto end + +:dir_okay +rem +rem We don't use the driver files directly any more because of win10 keeping the open, +rem so create an alternative directory for the binaries. +rem +set MY_ALTDIR=%MY_DIR%\..\LoadedDrivers +if not exist "%MY_ALTDIR%" mkdir "%MY_ALTDIR%" + +rem +rem Display device states. +rem +for %%i in (VBoxNetAdp VBoxNetAdp6 VBoxNetFlt VBoxNetLwf VBoxUSBMon VBoxUSB VBoxSup) do ( + set type= + for /f "usebackq tokens=*" %%f in (`sc query %%i`) do (set xxx=%%f&&if "!xxx:~0,5!" =="STATE" set type=!xxx!) + for /f "usebackq tokens=2 delims=:" %%f in ('!type!') do set type=%%f + if "!type!x" == "x" set type= not configured, probably + echo load.sh: %%i -!type! +) + +rem +rem Copy uninstallers and installers and VBoxRT into the dir: +rem +echo ** +echo ** Copying installers and uninstallers into %MY_ALTDIR%... +echo ** +set MY_FAILED=no +for %%i in (NetAdpUninstall.exe NetAdp6Uninstall.exe USBUninstall.exe NetFltUninstall.exe NetLwfUninstall.exe SUPUninstall.exe ^ + NetAdpInstall.exe NetAdp6Install.exe USBInstall.exe NetFltInstall.exe NetLwfInstall.exe SUPInstall.exe ^ + VBoxRT.dll) do if exist "%MY_DIR%\%%i" (copy "%MY_DIR%\%%i" "%MY_ALTDIR%\%%i" || set MY_FAILED=yes) +if "%MY_FAILED%" == "yes" goto end + +rem +rem Unload the drivers. +rem +echo ** +echo ** Unloading drivers... +echo ** +for %%i in (NetAdpUninstall.exe NetAdp6Uninstall.exe USBUninstall.exe NetFltUninstall.exe NetLwfUninstall.exe SUPUninstall.exe) do ( + if exist "%MY_ALTDIR%\%%i" (echo ** Running %%i...&& "%MY_ALTDIR%\%%i") +) + +rem +rem Copy the driver files into the directory now that they no longer should be in use and can be overwritten. +rem +echo ** +echo ** Copying drivers into %MY_ALTDIR%... +echo ** +set MY_FAILED=no +for %%i in (VBoxSup.sys VBoxSup.inf VBoxSup.cat) do if exist "%MY_DIR%\%%i" (copy "%MY_DIR%\%%i" "%MY_ALTDIR%\%%i" || set MY_FAILED=yes) +if "%MY_FAILED%" == "yes" goto end + +rem +rem Invoke the installer if asked to do so. +rem +if "%1%" == "-u" goto end +if "%1%" == "--uninstall" goto end +echo ** +echo ** Loading drivers... +echo ** +for %%i in (SUPInstall.exe) do "%MY_ALTDIR%\%%i" || goto end + +:end +endlocal +endlocal +endlocal + diff --git a/src/VBox/HostDrivers/win/loadall.cmd b/src/VBox/HostDrivers/win/loadall.cmd new file mode 100644 index 00000000..593907bc --- /dev/null +++ b/src/VBox/HostDrivers/win/loadall.cmd @@ -0,0 +1,142 @@ +@echo off +rem $Id: loadall.cmd $ +rem rem @file +rem Windows NT batch script for loading the host drivers. +rem + +rem +rem Copyright (C) 2009-2023 Oracle and/or its affiliates. +rem +rem This file is part of VirtualBox base platform packages, as +rem available from https://www.virtualbox.org. +rem +rem This program is free software; you can redistribute it and/or +rem modify it under the terms of the GNU General Public License +rem as published by the Free Software Foundation, in version 3 of the +rem License. +rem +rem This program is distributed in the hope that it will be useful, but +rem WITHOUT ANY WARRANTY; without even the implied warranty of +rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +rem General Public License for more details. +rem +rem You should have received a copy of the GNU General Public License +rem along with this program; if not, see . +rem +rem The contents of this file may alternatively be used under the terms +rem of the Common Development and Distribution License Version 1.0 +rem (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +rem in the VirtualBox distribution, in which case the provisions of the +rem CDDL are applicable instead of those of the GPL. +rem +rem You may elect to license modified versions of this file under the +rem terms and conditions of either the GPL or the CDDL or both. +rem +rem SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +rem + + +setlocal ENABLEEXTENSIONS +setlocal ENABLEDELAYEDEXPANSION +setlocal + + +rem +rem find the directory we're in. +rem +set MY_DIR=%~dp0 +if exist "%MY_DIR%\load.cmd" goto dir_okay +echo load.cmd: failed to find load.sh in "%~dp0". +goto end + +:dir_okay +rem +rem We don't use the driver files directly any more because of win10 keeping the open, +rem so create an alternative directory for the binaries. +rem +set MY_ALTDIR=%MY_DIR%\..\LoadedDrivers +if not exist "%MY_ALTDIR%" mkdir "%MY_ALTDIR%" + +rem +rem Display device states. +rem +for %%i in (VBoxNetAdp VBoxNetAdp6 VBoxNetFlt VBoxNetLwf VBoxUSBMon VBoxUSB VBoxSup) do ( + set type= + for /f "usebackq tokens=*" %%f in (`sc query %%i`) do (set xxx=%%f&&if "!xxx:~0,5!" =="STATE" set type=!xxx!) + for /f "usebackq tokens=2 delims=:" %%f in ('!type!') do set type=%%f + if "!type!x" == "x" set type= not configured, probably + echo load.sh: %%i -!type! +) + +rem +rem Copy uninstallers and installers and VBoxRT into the dir: +rem +echo ** +echo ** Copying installers and uninstallers into %MY_ALTDIR%... +echo ** +set MY_FAILED=no +for %%i in (NetAdpUninstall.exe NetAdp6Uninstall.exe USBUninstall.exe NetFltUninstall.exe NetLwfUninstall.exe SUPUninstall.exe ^ + NetAdpInstall.exe NetAdp6Install.exe USBInstall.exe NetFltInstall.exe NetLwfInstall.exe SUPInstall.exe ^ + VBoxRT.dll) do if exist "%MY_DIR%\%%i" (copy "%MY_DIR%\%%i" "%MY_ALTDIR%\%%i" || set MY_FAILED=yes) +if "%MY_FAILED%" == "yes" goto end + +rem +rem Unload the drivers. +rem +echo ** +echo ** Unloading drivers... +echo ** +for %%i in (NetAdpUninstall.exe NetAdp6Uninstall.exe USBUninstall.exe NetFltUninstall.exe NetLwfUninstall.exe SUPUninstall.exe) do ( + if exist "%MY_ALTDIR%\%%i" (echo * Running %%i...&& "%MY_ALTDIR%\%%i") +) + +rem +rem Copy the driver files into the directory now that they no longer should be in use and can be overwritten. +rem +echo ** +echo ** Copying drivers into %MY_ALTDIR%... +echo ** +set MY_FAILED=no +for %%i in (^ + VBoxSup.sys VBoxSup.inf VBoxSup.cat VBoxSup-PreW10.cat ^ + VBoxNetAdp.sys VBoxNetAdp.inf VBoxNetAdp.cat ^ + VBoxNetAdp6.sys VBoxNetAdp6.inf VBoxNetAdp6.cat VBoxNetAdp6-PreW10.cat ^ + VBoxNetFlt.sys VBoxNetFlt.inf VBoxNetFlt.cat VBoxNetFltNobj.dll ^ + VBoxNetFltM.inf ^ + VBoxNetLwf.sys VBoxNetLwf.inf VBoxNetLwf.cat VBoxNetLwf-PreW10.cat ^ + VBoxUSB.sys VBoxUSB.inf VBoxUSB.cat VBoxUSB-PreW10.cat ^ + VBoxUSBMon.sys VBoxUSBMon.inf VBoxUSBMon.cat VBoxUSBMon-PreW10.cat ) ^ +do if exist "%MY_DIR%\%%i" (copy "%MY_DIR%\%%i" "%MY_ALTDIR%\%%i" || set MY_FAILED=yes) +if "%MY_FAILED%" == "yes" goto end + +rem +rem Invoke the installer if asked to do so. +rem +if "%1%" == "-u" goto end +if "%1%" == "--uninstall" goto end + +set MY_VER= +for /f "usebackq delims=[] tokens=2" %%f in (`cmd /c ver`) do set MY_VER=%%f +for /f "usebackq tokens=2" %%f in ('%MY_VER%') do set MY_VER=%%f +for /f "usebackq delims=. tokens=1" %%f in ('%MY_VER%') do set MY_VER=%%f +set MY_VER=0000%MY_VER% +set MY_VER=%MY_VER:~-2% + +echo ** +echo ** Loading drivers (for windows version %MY_VER%)... +echo ** + +if "%MY_VER%" GEQ "06" ( + set MY_INSTALLERS=SUPInstall.exe USBInstall.exe NetLwfInstall.exe NetAdp6Install.exe +) else ( + set MY_INSTALLERS=SUPInstall.exe USBInstall.exe NetFltInstall.exe + rem NetAdpInstall.exe; - busted +) +for %%i in (%MY_INSTALLERS%) do (echo * Running %%i...&& "%MY_ALTDIR%\%%i" || (echo loadall.cmd: %%i failed&& goto end)) + +echo * loadall.cmd completed successfully. +:end +endlocal +endlocal +endlocal + -- cgit v1.2.3