From 9af6ee7031536ccb91fc2eb2f8521a0ac286db2c Mon Sep 17 00:00:00 2001 From: Nir Bar Date: Thu, 3 Apr 2025 09:26:20 +0300 Subject: Support launching rundll32.exe as a safe executable if the dll it loads is in a secure location --- src/burn/test/BurnUnitTest/ApprovedExeTest.cpp | 312 +++++++++++++++++++++ src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj | 1 + .../test/BurnUnitTest/BurnUnitTest.vcxproj.filters | 5 +- src/burn/test/BurnUnitTest/precomp.h | 1 + 4 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/burn/test/BurnUnitTest/ApprovedExeTest.cpp (limited to 'src/burn/test/BurnUnitTest') diff --git a/src/burn/test/BurnUnitTest/ApprovedExeTest.cpp b/src/burn/test/BurnUnitTest/ApprovedExeTest.cpp new file mode 100644 index 00000000..da51f1f8 --- /dev/null +++ b/src/burn/test/BurnUnitTest/ApprovedExeTest.cpp @@ -0,0 +1,312 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +namespace Microsoft +{ +namespace Tools +{ +namespace WindowsInstallerXml +{ +namespace Test +{ +namespace Bootstrapper +{ + using namespace System; + using namespace System::IO; + using namespace Xunit; + + public ref class ApprovedExeTest : BurnUnitTest + { + public: + ApprovedExeTest(BurnTestFixture^ fixture) : BurnUnitTest(fixture) + { + } + + [Fact] + void ApprovedExesVerifyPFilesTest() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + + hr = VariableGetString(&variables, L"ProgramFilesFolder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable ProgramFilesFolder."); + + hr = PathConcat(scz, L"a.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 0, NULL); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFilesFolder"); + Assert::True((hr == S_OK), "Path under ProgramFilesFolder was expected to be safe"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + + [Fact] + void ApprovedExesVerifyPFilesWithRelativeTest() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + cache.fPerMachineCacheRootVerified = TRUE; + cache.fOriginalPerMachineCacheRootVerified = TRUE; + + hr = VariableGetString(&variables, L"ProgramFilesFolder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable ProgramFilesFolder."); + + hr = PathConcat(scz, L"..\\a.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 0, NULL); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFilesFolder"); + Assert::True((hr == S_FALSE), "Path pretending to be under ProgramFilesFolder was expected to be unsafe"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + + [Fact] + void ApprovedExesVerifyPFiles64Test() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + + hr = VariableGetString(&variables, L"ProgramFiles64Folder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable ProgramFiles64Folder."); + + hr = PathConcat(scz, L"a.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 0, NULL); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFiles64Folder"); + Assert::True((hr == S_OK), "Path under ProgramFiles64Folder was expected to be safe"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + + [Fact] + void ApprovedExesVerifySys64FolderTest() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + cache.fPerMachineCacheRootVerified = TRUE; + cache.fOriginalPerMachineCacheRootVerified = TRUE; + + hr = VariableGetString(&variables, L"System64Folder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable System64Folder."); + + hr = PathConcat(scz, L"a.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 0, NULL); + NativeAssert::Succeeded(hr, "Failed to test secure location under System64Folder"); + Assert::True((hr == S_FALSE), "Path under System64Folder was expected to be unsafe"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + + [Fact] + void ApprovedExesVerifySys64Rundll32UnsafeTest() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + LPWSTR szArgs = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + cache.fPerMachineCacheRootVerified = TRUE; + cache.fOriginalPerMachineCacheRootVerified = TRUE; + + hr = VariableGetString(&variables, L"System64Folder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable System64Folder."); + + hr = PathConcat(scz, L"rundll32.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 1, const_cast(&scz2)); + NativeAssert::Succeeded(hr, "Failed to test secure location under System64Folder"); + Assert::True((hr == S_FALSE), "Path under System64Folder was expected to be unsafe for rundll32 target"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + ReleaseStr(szArgs); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + + [Fact] + void ApprovedExesVerifySys64Rundll32SafeTest() + { + HRESULT hr = S_OK; + BURN_CACHE cache = { }; + BURN_ENGINE_COMMAND internalCommand = { }; + BURN_VARIABLES variables = { }; + LPWSTR scz = NULL; + LPWSTR scz2 = NULL; + LPWSTR scz3 = NULL; + + try + { + hr = VariableInitialize(&variables); + NativeAssert::Succeeded(hr, L"Failed to initialize variables."); + + hr = CacheInitialize(&cache, &internalCommand); + NativeAssert::Succeeded(hr, "Failed to initialize cache."); + cache.fPerMachineCacheRootVerified = TRUE; + cache.fOriginalPerMachineCacheRootVerified = TRUE; + + // System64Folder + hr = VariableGetString(&variables, L"System64Folder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable System64Folder."); + + hr = PathConcat(scz, L"rundll32.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = VariableGetString(&variables, L"ProgramFiles64Folder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable ProgramFiles64Folder."); + + hr = PathConcat(scz, L"a.dll", &scz3); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 1, const_cast(&scz3)); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFiles64Folder for System64Folder/rundll32 target"); + Assert::True((hr == S_OK), "Path under ProgramFiles64Folder was expected to be safe for System64Folder/rundll32 target"); + + hr = PathConcat(scz, L"a.dll,somthing else", &scz3); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 1, const_cast(&scz3)); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFiles64Folder for rundll32 target"); + Assert::True((hr == S_OK), "Path under ProgramFiles64Folder was expected to be safe for System64Folder/rundll32 target"); + + // SystemFolder + hr = VariableGetString(&variables, L"SystemFolder", &scz); + NativeAssert::Succeeded(hr, "Failed to get variable System64Folder."); + + hr = PathConcat(scz, L"rundll32.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 1, const_cast(&scz3)); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFiles64Folder for SystemFolder/rundll32 target"); + Assert::True((hr == S_OK), "Path under ProgramFiles64Folder was expected to be safe for SystemFolder/rundll32 target"); + + // Sysnative + hr = PathSystemWindowsSubdirectory(L"SysNative\\", &scz); + NativeAssert::Succeeded(hr, "Failed to get SysNative Folder."); + + hr = PathConcat(scz, L"rundll32.exe", &scz2); + NativeAssert::Succeeded(hr, "Failed to combine paths"); + + hr = ApprovedExesVerifySecureLocation(&cache, &variables, scz2, 1, const_cast(&scz3)); + NativeAssert::Succeeded(hr, "Failed to test secure location under ProgramFiles64Folder for Sysnative/rundll32 target"); + Assert::True((hr == S_OK), "Path under ProgramFiles64Folder was expected to be safe for Sysnative/rundll32 target"); + } + finally + { + ReleaseStr(internalCommand.sczEngineWorkingDirectory); + ReleaseStr(scz); + ReleaseStr(scz2); + ReleaseStr(scz3); + + CacheUninitialize(&cache); + VariablesUninitialize(&variables); + } + } + }; +} +} +} +} +} diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj index 331d237b..63d089e4 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj @@ -45,6 +45,7 @@ + diff --git a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters index 82725436..59aea6e9 100644 --- a/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters +++ b/src/burn/test/BurnUnitTest/BurnUnitTest.vcxproj.filters @@ -66,6 +66,9 @@ Source Files + + Source Files + @@ -95,4 +98,4 @@ Resource Files - \ No newline at end of file + diff --git a/src/burn/test/BurnUnitTest/precomp.h b/src/burn/test/BurnUnitTest/precomp.h index f07f5968..ec6fb7d1 100644 --- a/src/burn/test/BurnUnitTest/precomp.h +++ b/src/burn/test/BurnUnitTest/precomp.h @@ -74,6 +74,7 @@ #include "splashscreen.h" #include "detect.h" #include "externalengine.h" +#include "approvedexe.h" #include "engine.version.h" -- cgit v1.2.3-55-g6feb