Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,271 @@ HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring &installationDir)
return hr;
}

static std::optional<std::wstring> GetKnownFolderPath(REFKNOWNFOLDERID folderId)
{
PWSTR pathBlockPtr = nullptr;
if (FAILED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, nullptr, &pathBlockPtr)))
{
return std::nullopt;
}

std::wstring path{ pathBlockPtr };
CoTaskMemFree(pathBlockPtr);
return path;
}

static std::wstring NormalizePathForComparison(const std::filesystem::path& path)
{
std::error_code errorCode;
auto normalizedPath = std::filesystem::weakly_canonical(path, errorCode);
if (errorCode)
{
normalizedPath = path.lexically_normal();
}

auto value = normalizedPath.native();
while (!value.empty() && (value.back() == L'\\' || value.back() == L'/'))
{
value.pop_back();
}

return value;
}

static bool IsSameOrUnderPath(const std::wstring& candidatePath, const std::wstring& rootPath)
{
if (candidatePath.size() < rootPath.size())
{
return false;
}

if (_wcsnicmp(candidatePath.c_str(), rootPath.c_str(), rootPath.size()) != 0)
{
return false;
}

return candidatePath.size() == rootPath.size() || candidatePath[rootPath.size()] == L'\\' || candidatePath[rootPath.size()] == L'/';
}

static HRESULT GetPathDacl(const std::wstring& path, PACL* dacl, PSECURITY_DESCRIPTOR* securityDescriptor)
{
const auto result = GetNamedSecurityInfoW(
path.c_str(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
dacl,
nullptr,
securityDescriptor);

return result == ERROR_SUCCESS ? S_OK : HRESULT_FROM_WIN32(result);
}

static HRESULT SetProtectedPathDacl(const std::wstring& path, PACL dacl)
{
const auto result = SetNamedSecurityInfoW(
const_cast<LPWSTR>(path.c_str()),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
dacl,
nullptr);

return result == ERROR_SUCCESS ? S_OK : HRESULT_FROM_WIN32(result);
}

static HRESULT CreateAclTemplateFile(const std::filesystem::path& path)
{
HANDLE fileHandle = CreateFileW(
path.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_TEMPORARY,
nullptr);

if (fileHandle == INVALID_HANDLE_VALUE)
{
return HRESULT_FROM_WIN32(GetLastError());
}

CloseHandle(fileHandle);
return S_OK;
}

static HRESULT ReAclExistingInstallTree(
const std::filesystem::path& installFolder,
const std::filesystem::path& templateFolder,
PACL directoryDacl,
PACL fileDacl)
{
const auto normalizedTemplateFolder = NormalizePathForComparison(templateFolder);

std::error_code errorCode;
std::filesystem::recursive_directory_iterator end{};
for (std::filesystem::recursive_directory_iterator iterator(installFolder, std::filesystem::directory_options::skip_permission_denied, errorCode);
iterator != end;
iterator.increment(errorCode))
{
if (errorCode)
{
return HRESULT_FROM_WIN32(errorCode.value());
}

const auto normalizedEntryPath = NormalizePathForComparison(iterator->path());
if (IsSameOrUnderPath(normalizedEntryPath, normalizedTemplateFolder))
{
iterator.disable_recursion_pending();
continue;
}

const auto status = iterator->symlink_status(errorCode);
if (errorCode)
{
return HRESULT_FROM_WIN32(errorCode.value());
}

if (std::filesystem::is_symlink(status))
{
iterator.disable_recursion_pending();
continue;
}

HRESULT hr = S_OK;
if (std::filesystem::is_directory(status))
{
hr = SetProtectedPathDacl(iterator->path().native(), directoryDacl);
}
else if (std::filesystem::is_regular_file(status))
{
hr = SetProtectedPathDacl(iterator->path().native(), fileDacl);
}

if (FAILED(hr))
{
return hr;
}
}

return S_OK;
}

UINT __stdcall SecureInstallFolderAclCA(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
std::wstring installationFolder;
std::filesystem::path installFolderPath;
std::filesystem::path templateFolderPath;
std::filesystem::path templateFilePath;
std::wstring normalizedInstallFolder;
std::optional<std::wstring> programFilesPath;
bool isUnderProgramFiles = false;

PSECURITY_DESCRIPTOR programFilesSecurityDescriptor = nullptr;
PACL programFilesDacl = nullptr;
PSECURITY_DESCRIPTOR templateDirectorySecurityDescriptor = nullptr;
PACL templateDirectoryDacl = nullptr;
PSECURITY_DESCRIPTOR templateFileSecurityDescriptor = nullptr;
PACL templateFileDacl = nullptr;

hr = WcaInitialize(hInstall, "SecureInstallFolderAclCA");
ExitOnFailure(hr, "Failed to initialize");

hr = getInstallFolder(hInstall, installationFolder);
ExitOnFailure(hr, "Failed to get install folder.");

installFolderPath = std::filesystem::path(installationFolder);
normalizedInstallFolder = NormalizePathForComparison(installFolderPath);

programFilesPath = GetKnownFolderPath(FOLDERID_ProgramFiles);
if (!programFilesPath)
{
hr = E_FAIL;
ExitOnFailure(hr, "Failed to resolve Program Files folder.");
}

isUnderProgramFiles = IsSameOrUnderPath(normalizedInstallFolder, NormalizePathForComparison(*programFilesPath));
if (const auto programFilesX86Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86))
{
isUnderProgramFiles = isUnderProgramFiles || IsSameOrUnderPath(normalizedInstallFolder, NormalizePathForComparison(*programFilesX86Path));
}

if (isUnderProgramFiles)
{
WcaLog(LOGMSG_STANDARD, "SecureInstallFolderAclCA: Install folder already resides under Program Files. No ACL changes required.");
goto LExit;
}

{
std::error_code errorCode;
std::filesystem::create_directories(installFolderPath, errorCode);
if (errorCode)
{
hr = HRESULT_FROM_WIN32(errorCode.value());
ExitOnFailure(hr, "Failed to create install folder before ACL hardening.");
}
}

hr = GetPathDacl(*programFilesPath, &programFilesDacl, &programFilesSecurityDescriptor);
ExitOnFailure(hr, "Failed to read Program Files DACL.");

hr = SetProtectedPathDacl(installFolderPath.native(), programFilesDacl);
ExitOnFailure(hr, "Failed to apply Program Files DACL to install folder.");

templateFolderPath = installFolderPath / L".powertoys_acl_template";
{
std::error_code errorCode;
std::filesystem::remove_all(templateFolderPath, errorCode);
errorCode.clear();
std::filesystem::create_directory(templateFolderPath, errorCode);
if (errorCode)
{
hr = HRESULT_FROM_WIN32(errorCode.value());
ExitOnFailure(hr, "Failed to create ACL template folder.");
}
}

templateFilePath = templateFolderPath / L"template.bin";
hr = CreateAclTemplateFile(templateFilePath);
ExitOnFailure(hr, "Failed to create ACL template file.");

hr = GetPathDacl(templateFolderPath.native(), &templateDirectoryDacl, &templateDirectorySecurityDescriptor);
ExitOnFailure(hr, "Failed to read ACL template directory DACL.");

hr = GetPathDacl(templateFilePath.native(), &templateFileDacl, &templateFileSecurityDescriptor);
ExitOnFailure(hr, "Failed to read ACL template file DACL.");

hr = ReAclExistingInstallTree(installFolderPath, templateFolderPath, templateDirectoryDacl, templateFileDacl);
ExitOnFailure(hr, "Failed to harden install folder contents.");

LExit:
if (!templateFolderPath.empty())
{
std::error_code errorCode;
std::filesystem::remove_all(templateFolderPath, errorCode);
}

if (programFilesSecurityDescriptor)
{
LocalFree(programFilesSecurityDescriptor);
}
if (templateDirectorySecurityDescriptor)
{
LocalFree(templateDirectorySecurityDescriptor);
}
if (templateFileSecurityDescriptor)
{
LocalFree(templateFileSecurityDescriptor);
}

er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}

BOOL IsLocalSystem()
{
HANDLE hToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ EXPORTS
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA
UnsetAdvancedPasteAPIKeyCA
SecureInstallFolderAclCA
CleanImageResizerRuntimeRegistryCA
CleanFileLocksmithRuntimeRegistryCA
CleanPowerRenameRuntimeRegistryCA
Expand Down
1 change: 1 addition & 0 deletions installer/PowerToysSetupCustomActionsVNext/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#define DPSAPI_VERSION 1
// Windows Header Files:
#include <windows.h>
#include <Aclapi.h>
#include <newdev.h>
#include <strsafe.h>
#include <msiquery.h>
Expand Down
14 changes: 14 additions & 0 deletions installer/PowerToysSetupVNext/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@
<Custom Action="SetUninstallCommandNotFoundParam" Before="UninstallCommandNotFound" />
<Custom Action="SetUpgradeCommandNotFoundParam" Before="UpgradeCommandNotFound" />
<Custom Action="SetApplyModulesRegistryChangeSetsParam" Before="ApplyModulesRegistryChangeSets" />
<?if $(var.PerUser) = "false" ?>
<Custom Action="SetSecureInstallFolderAclParam" Before="SecureInstallFolderAcl" />
<?endif?>
<Custom Action="SetInstallPackageIdentityMSIXParam" Before="InstallPackageIdentityMSIX" />

<?if $(var.PerUser) = "true" ?>
Expand All @@ -120,6 +123,9 @@

<Custom Action="SetUnApplyModulesRegistryChangeSetsParam" Before="UnApplyModulesRegistryChangeSets" />
<Custom Action="CheckGPO" After="InstallInitialize" Condition="NOT Installed" />
<?if $(var.PerUser) = "false" ?>
<Custom Action="SecureInstallFolderAcl" After="CreateFolders" Condition="NOT REMOVE" />
<?endif?>
<Custom Action="SetBundleInstallLocationData" Before="SetBundleInstallLocation" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="SetBundleInstallLocation" After="InstallFiles" Condition="NOT Installed OR WIX_UPGRADE_DETECTED" />
<Custom Action="ApplyModulesRegistryChangeSets" After="InstallFiles" Condition="NOT Installed" />
Expand Down Expand Up @@ -181,6 +187,10 @@

<CustomAction Id="SetUnApplyModulesRegistryChangeSetsParam" Property="UnApplyModulesRegistryChangeSets" Value="[INSTALLFOLDER]" />

<?if $(var.PerUser) = "false" ?>
<CustomAction Id="SetSecureInstallFolderAclParam" Property="SecureInstallFolderAcl" Value="[INSTALLFOLDER]" />
<?endif?>

<CustomAction Id="SetInstallDSCModuleParam" Property="InstallDSCModule" Value="[INSTALLFOLDER]" />

<CustomAction Id="SetUninstallCommandNotFoundParam" Property="UninstallCommandNotFound" Value="[INSTALLFOLDER]" />
Expand Down Expand Up @@ -245,6 +255,10 @@

<CustomAction Id="ApplyModulesRegistryChangeSets" Return="check" Impersonate="yes" Execute="deferred" DllEntry="ApplyModulesRegistryChangeSetsCA" BinaryRef="PTCustomActions" />

<?if $(var.PerUser) = "false" ?>
<CustomAction Id="SecureInstallFolderAcl" Return="check" Impersonate="no" Execute="deferred" DllEntry="SecureInstallFolderAclCA" BinaryRef="PTCustomActions" />
<?endif?>

<CustomAction Id="UnApplyModulesRegistryChangeSets" Return="check" Impersonate="yes" Execute="deferred" DllEntry="UnApplyModulesRegistryChangeSetsCA" BinaryRef="PTCustomActions" />

<CustomAction Id="UnRegisterContextMenuPackages" Return="ignore" Impersonate="yes" Execute="deferred" DllEntry="UnRegisterContextMenuPackagesCA" BinaryRef="PTCustomActions" />
Expand Down
Loading