From 1f5314302b3c8bc1977aed79df1d05c52608f382 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Mon, 3 Jan 2022 15:35:14 -0600 Subject: Don't assume Exe packages with Burn protocol are bundles. Related to #3693 --- src/burn/engine/bundlepackageengine.cpp | 460 ++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 src/burn/engine/bundlepackageengine.cpp (limited to 'src/burn/engine/bundlepackageengine.cpp') diff --git a/src/burn/engine/bundlepackageengine.cpp b/src/burn/engine/bundlepackageengine.cpp new file mode 100644 index 00000000..10022b6a --- /dev/null +++ b/src/burn/engine/bundlepackageengine.cpp @@ -0,0 +1,460 @@ +// 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" + + + +// function definitions + +extern "C" void BundlePackageEnginePackageUninitialize( + __in BURN_PACKAGE* pPackage + ) +{ + ReleaseStr(pPackage->Bundle.sczInstallArguments); + ReleaseStr(pPackage->Bundle.sczRepairArguments); + ReleaseStr(pPackage->Bundle.sczUninstallArguments); + ReleaseStr(pPackage->Bundle.sczIgnoreDependencies); + ReleaseMem(pPackage->Bundle.rgExitCodes); + + // free command-line arguments + if (pPackage->Bundle.rgCommandLineArguments) + { + for (DWORD i = 0; i < pPackage->Bundle.cCommandLineArguments; ++i) + { + ExeEngineCommandLineArgumentUninitialize(pPackage->Bundle.rgCommandLineArguments + i); + } + MemFree(pPackage->Bundle.rgCommandLineArguments); + } + + // clear struct + memset(&pPackage->Bundle, 0, sizeof(pPackage->Bundle)); +} + +// +// PlanCalculate - calculates the execute and rollback state for the requested package state. +// +extern "C" HRESULT BundlePackageEnginePlanCalculatePackage( + __in BURN_PACKAGE* pPackage + ) +{ + HRESULT hr = S_OK; + BOOTSTRAPPER_ACTION_STATE execute = BOOTSTRAPPER_ACTION_STATE_NONE; + BOOTSTRAPPER_ACTION_STATE rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + + // execute action + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: + execute = pPackage->Bundle.fPseudoBundle ? BOOTSTRAPPER_ACTION_STATE_INSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = pPackage->Bundle.fRepairable ? BOOTSTRAPPER_ACTION_STATE_REPAIR : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_CACHE: + execute = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: + execute = BOOTSTRAPPER_ACTION_STATE_UNINSTALL; + break; + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + execute = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + default: + execute = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package current state: %d.", pPackage->currentState); + } + + // Calculate the rollback action if there is an execute action. + if (BOOTSTRAPPER_ACTION_STATE_NONE != execute) + { + switch (pPackage->currentState) + { + case BOOTSTRAPPER_PACKAGE_STATE_PRESENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_INSTALL; + break; + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + case BOOTSTRAPPER_PACKAGE_STATE_ABSENT: + switch (pPackage->requested) + { + case BOOTSTRAPPER_REQUEST_STATE_PRESENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_REPAIR: + rollback = pPackage->fUninstallable ? BOOTSTRAPPER_ACTION_STATE_UNINSTALL : BOOTSTRAPPER_ACTION_STATE_NONE; + break; + case BOOTSTRAPPER_REQUEST_STATE_FORCE_ABSENT: __fallthrough; + case BOOTSTRAPPER_REQUEST_STATE_ABSENT: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + default: + rollback = BOOTSTRAPPER_ACTION_STATE_NONE; + break; + } + break; + + default: + hr = E_INVALIDARG; + ExitOnRootFailure(hr, "Invalid package expected state."); + } + } + + // return values + pPackage->execute = execute; + pPackage->rollback = rollback; + +LExit: + return hr; +} + +// +// PlanAdd - adds the calculated execute and rollback actions for the package. +// +extern "C" HRESULT BundlePackageEnginePlanAddRelatedBundle( + __in_opt DWORD *pdwInsertSequence, + __in BURN_RELATED_BUNDLE* pRelatedBundle, + __in BURN_PLAN* pPlan, + __in BURN_LOGGING* pLog, + __in BURN_VARIABLES* pVariables + ) +{ + HRESULT hr = S_OK; + BURN_EXECUTE_ACTION* pAction = NULL; + BURN_PACKAGE* pPackage = &pRelatedBundle->package; + + hr = DependencyPlanPackage(pdwInsertSequence, pPackage, pPlan); + ExitOnFailure(hr, "Failed to plan package dependency actions."); + + // add execute action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->execute) + { + if (pdwInsertSequence) + { + hr = PlanInsertExecuteAction(*pdwInsertSequence, pPlan, &pAction); + ExitOnFailure(hr, "Failed to insert execute action."); + } + else + { + hr = PlanAppendExecuteAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append execute action."); + } + + pAction->type = BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE; + pAction->relatedBundle.pRelatedBundle = pRelatedBundle; + pAction->relatedBundle.action = pPackage->execute; + + if (pPackage->Bundle.sczIgnoreDependencies) + { + hr = StrAllocString(&pAction->relatedBundle.sczIgnoreDependencies, pPackage->Bundle.sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + + if (pPackage->Bundle.wzAncestors) + { + hr = StrAllocString(&pAction->relatedBundle.sczAncestors, pPackage->Bundle.wzAncestors, 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + + if (pPackage->Bundle.wzEngineWorkingDirectory) + { + hr = StrAllocString(&pAction->relatedBundle.sczEngineWorkingDirectory, pPackage->Bundle.wzEngineWorkingDirectory, 0); + ExitOnFailure(hr, "Failed to allocate the custom working directory."); + } + + LoggingSetPackageVariable(pPackage, NULL, FALSE, pLog, pVariables, NULL); // ignore errors. + } + + // add rollback action + if (BOOTSTRAPPER_ACTION_STATE_NONE != pPackage->rollback) + { + hr = PlanAppendRollbackAction(pPlan, &pAction); + ExitOnFailure(hr, "Failed to append rollback action."); + + pAction->type = BURN_EXECUTE_ACTION_TYPE_RELATED_BUNDLE; + pAction->relatedBundle.pRelatedBundle = pRelatedBundle; + pAction->relatedBundle.action = pPackage->rollback; + + if (pPackage->Bundle.sczIgnoreDependencies) + { + hr = StrAllocString(&pAction->relatedBundle.sczIgnoreDependencies, pPackage->Bundle.sczIgnoreDependencies, 0); + ExitOnFailure(hr, "Failed to allocate the list of dependencies to ignore."); + } + + if (pPackage->Bundle.wzAncestors) + { + hr = StrAllocString(&pAction->relatedBundle.sczAncestors, pPackage->Bundle.wzAncestors, 0); + ExitOnFailure(hr, "Failed to allocate the list of ancestors."); + } + + if (pPackage->Bundle.wzEngineWorkingDirectory) + { + hr = StrAllocString(&pAction->relatedBundle.sczEngineWorkingDirectory, pPackage->Bundle.wzEngineWorkingDirectory, 0); + ExitOnFailure(hr, "Failed to allocate the custom working directory."); + } + + LoggingSetPackageVariable(pPackage, NULL, TRUE, pLog, pVariables, NULL); // ignore errors. + } + +LExit: + return hr; +} + +extern "C" HRESULT BundlePackageEngineExecuteRelatedBundle( + __in BURN_EXECUTE_ACTION* pExecuteAction, + __in BURN_CACHE* pCache, + __in BURN_VARIABLES* pVariables, + __in BOOL fRollback, + __in PFN_GENERICMESSAGEHANDLER pfnGenericMessageHandler, + __in LPVOID pvContext, + __out BOOTSTRAPPER_APPLY_RESTART* pRestart + ) +{ + HRESULT hr = S_OK; + int nResult = IDNOACTION; + LPCWSTR wzArguments = NULL; + LPWSTR sczArguments = NULL; + LPWSTR sczArgumentsFormatted = NULL; + LPWSTR sczArgumentsObfuscated = NULL; + LPWSTR sczCachedDirectory = NULL; + LPWSTR sczExecutablePath = NULL; + LPWSTR sczCommand = NULL; + LPWSTR sczCommandObfuscated = NULL; + HANDLE hExecutableFile = INVALID_HANDLE_VALUE; + STARTUPINFOW si = { }; + PROCESS_INFORMATION pi = { }; + DWORD dwExitCode = 0; + GENERIC_EXECUTE_MESSAGE message = { }; + BOOTSTRAPPER_ACTION_STATE action = pExecuteAction->relatedBundle.action; + BURN_RELATED_BUNDLE* pRelatedBundle = pExecuteAction->relatedBundle.pRelatedBundle; + BOOTSTRAPPER_RELATION_TYPE relationType = pRelatedBundle->relationType; + BURN_PACKAGE* pPackage = &pRelatedBundle->package; + BURN_PAYLOAD* pPackagePayload = pPackage->payloads.rgItems[0].pPayload; + LPCWSTR wzRelationTypeCommandLine = CoreRelationTypeToCommandLineString(relationType); + LPCWSTR wzOperationCommandLine = NULL; + BOOL fRunEmbedded = pPackage->Bundle.fSupportsBurnProtocol; + + // get cached executable path + hr = CacheGetCompletedPath(pCache, pPackage->fPerMachine, pPackage->sczCacheId, &sczCachedDirectory); + ExitOnFailure(hr, "Failed to get cached path for package: %ls", pPackage->sczId); + + // Best effort to set the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, sczCachedDirectory, TRUE, FALSE); + VariableSetNumeric(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, action, TRUE); + + hr = PathConcat(sczCachedDirectory, pPackagePayload->sczFilePath, &sczExecutablePath); + ExitOnFailure(hr, "Failed to build executable path."); + + // pick arguments + switch (action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + wzArguments = pPackage->Bundle.sczInstallArguments; + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + wzOperationCommandLine = L"-uninstall"; + wzArguments = pPackage->Bundle.sczUninstallArguments; + break; + + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + wzOperationCommandLine = L"-repair"; + wzArguments = pPackage->Bundle.sczRepairArguments; + break; + + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid Bundle package action: %d.", action); + } + + // now add optional arguments + if (wzArguments && *wzArguments) + { + hr = StrAllocString(&sczArguments, wzArguments, 0); + ExitOnFailure(hr, "Failed to copy package arguments."); + } + + for (DWORD i = 0; i < pPackage->Bundle.cCommandLineArguments; ++i) + { + BURN_EXE_COMMAND_LINE_ARGUMENT* commandLineArgument = &pPackage->Bundle.rgCommandLineArguments[i]; + BOOL fCondition = FALSE; + + hr = ConditionEvaluate(pVariables, commandLineArgument->sczCondition, &fCondition); + ExitOnFailure(hr, "Failed to evaluate bundle package command-line condition."); + + if (fCondition) + { + if (sczArguments) + { + hr = StrAllocConcat(&sczArguments, L" ", 0); + ExitOnFailure(hr, "Failed to separate command-line arguments."); + } + + switch (action) + { + case BOOTSTRAPPER_ACTION_STATE_INSTALL: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczInstallArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for install."); + break; + + case BOOTSTRAPPER_ACTION_STATE_UNINSTALL: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczUninstallArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for uninstall."); + break; + + case BOOTSTRAPPER_ACTION_STATE_REPAIR: + hr = StrAllocConcat(&sczArguments, commandLineArgument->sczRepairArgument, 0); + ExitOnFailure(hr, "Failed to get command-line argument for repair."); + break; + + default: + hr = E_INVALIDARG; + ExitOnFailure(hr, "Invalid Bundle package action: %d.", action); + } + } + } + + // build command + AppAppendCommandLineArgument(&sczCommand, sczExecutablePath); + ExitOnFailure(hr, "Failed to create executable command."); + + if (!fRunEmbedded) + { + hr = StrAllocConcat(&sczCommand, L" -quiet", 0); + ExitOnFailure(hr, "Failed to append quiet argument."); + } + + if (wzOperationCommandLine) + { + hr = StrAllocConcatFormatted(&sczCommand, L" %ls", wzOperationCommandLine); + ExitOnFailure(hr, "Failed to append operation argument."); + } + + if (wzRelationTypeCommandLine) + { + hr = StrAllocConcatFormatted(&sczCommand, L" -%ls", wzRelationTypeCommandLine); + ExitOnFailure(hr, "Failed to append relation type argument."); + } + + // Add the list of dependencies to ignore, if any, to the burn command line. + if (pExecuteAction->relatedBundle.sczIgnoreDependencies) + { + hr = StrAllocConcatFormatted(&sczCommand, L" -%ls=%ls", BURN_COMMANDLINE_SWITCH_IGNOREDEPENDENCIES, pExecuteAction->relatedBundle.sczIgnoreDependencies); + ExitOnFailure(hr, "Failed to append the list of dependencies to ignore to the command line."); + } + + // Add the list of ancestors, if any, to the burn command line. + if (pExecuteAction->relatedBundle.sczAncestors) + { + hr = StrAllocConcatFormatted(&sczCommand, L" -%ls=%ls", BURN_COMMANDLINE_SWITCH_ANCESTORS, pExecuteAction->relatedBundle.sczAncestors); + ExitOnFailure(hr, "Failed to append the list of ancestors to the command line."); + } + + hr = CoreAppendEngineWorkingDirectoryToCommandLine(pExecuteAction->relatedBundle.sczEngineWorkingDirectory, &sczCommand, NULL); + ExitOnFailure(hr, "Failed to append the custom working directory to the bundlepackage command line."); + + hr = CoreAppendFileHandleSelfToCommandLine(sczExecutablePath, &hExecutableFile, &sczCommand, NULL); + ExitOnFailure(hr, "Failed to append %ls", BURN_COMMANDLINE_SWITCH_FILEHANDLE_SELF); + + // Always add user supplied arguments last. + if (sczArguments && *sczArguments) + { + hr = VariableFormatString(pVariables, sczArguments, &sczArgumentsFormatted, NULL); + ExitOnFailure(hr, "Failed to format argument string."); + + hr = VariableFormatStringObfuscated(pVariables, sczArguments, &sczArgumentsObfuscated, NULL); + ExitOnFailure(hr, "Failed to format obfuscated argument string."); + + hr = StrAllocFormatted(&sczCommandObfuscated, L"%ls %ls", sczCommand, sczArgumentsObfuscated); + ExitOnFailure(hr, "Failed to copy obfuscated formatted arguments."); + + hr = StrAllocConcatFormattedSecure(&sczCommand, L" %ls", sczArgumentsFormatted); + ExitOnFailure(hr, "Failed to copy formatted arguments."); + } + + // Log before we add the secret pipe name and client token for embedded processes. + LogId(REPORT_STANDARD, MSG_APPLYING_PACKAGE, LoggingRollbackOrExecute(fRollback), pPackage->sczId, LoggingActionStateToString(action), sczExecutablePath, sczCommandObfuscated); + + if (fRunEmbedded) + { + hr = EmbeddedRunBundle(sczExecutablePath, sczCommand, pfnGenericMessageHandler, pvContext, &dwExitCode); + ExitOnFailure(hr, "Failed to run bundle as embedded from path: %ls", sczExecutablePath); + } + else // create and wait for the executable process while sending fake progress to allow cancel. + { + // Make the cache location of the executable the current directory to help those executables + // that expect stuff to be relative to them. + si.cb = sizeof(si); + if (!::CreateProcessW(sczExecutablePath, sczCommand, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, sczCachedDirectory, &si, &pi)) + { + ExitWithLastError(hr, "Failed to CreateProcess on path: %ls", sczExecutablePath); + } + + do + { + message.type = GENERIC_EXECUTE_MESSAGE_PROGRESS; + message.dwUIHint = MB_OKCANCEL; + message.progress.dwPercentage = 50; + nResult = pfnGenericMessageHandler(&message, pvContext); + hr = (IDOK == nResult || IDNOACTION == nResult) ? S_OK : IDCANCEL == nResult ? HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) : HRESULT_FROM_WIN32(ERROR_INSTALL_FAILURE); + ExitOnRootFailure(hr, "Bootstrapper application aborted during BUNDLE progress."); + + hr = ProcWaitForCompletion(pi.hProcess, 500, &dwExitCode); + if (HRESULT_FROM_WIN32(WAIT_TIMEOUT) != hr) + { + ExitOnFailure(hr, "Failed to wait for executable to complete: %ls", sczExecutablePath); + } + } while (HRESULT_FROM_WIN32(WAIT_TIMEOUT) == hr); + } + + hr = ExeEngineHandleExitCode(pPackage->Bundle.rgExitCodes, pPackage->Bundle.cExitCodes, dwExitCode, pRestart); + ExitOnRootFailure(hr, "Process returned error: 0x%x", dwExitCode); + +LExit: + StrSecureZeroFreeString(sczArguments); + StrSecureZeroFreeString(sczArgumentsFormatted); + ReleaseStr(sczArgumentsObfuscated); + ReleaseStr(sczCachedDirectory); + ReleaseStr(sczExecutablePath); + StrSecureZeroFreeString(sczCommand); + ReleaseStr(sczCommandObfuscated); + + ReleaseHandle(pi.hThread); + ReleaseHandle(pi.hProcess); + ReleaseFileHandle(hExecutableFile); + + // Best effort to clear the execute package cache folder and action variables. + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_CACHE_FOLDER, NULL, TRUE, FALSE); + VariableSetString(pVariables, BURN_BUNDLE_EXECUTE_PACKAGE_ACTION, NULL, TRUE, FALSE); + + return hr; +} -- cgit v1.2.3-55-g6feb