Skip to content

Security: Fix Local privilege escalation via DLL hijack#46145

Draft
vanzue wants to merge 11 commits intomainfrom
dev/vanzue/fix-sec
Draft

Security: Fix Local privilege escalation via DLL hijack#46145
vanzue wants to merge 11 commits intomainfrom
dev/vanzue/fix-sec

Conversation

@vanzue
Copy link
Contributor

@vanzue vanzue commented Mar 16, 2026

Summary of the Pull Request

Attack vector:

  1. user install per machine installer
  2. Open an elevated command prompt and verify the newly added PowerToys PATH entry
  3. Inspect the ACL on the DSCModules directory an observe that the "Authenticated Users" group have inherited Modify permissions
  4. Log in as a low-privileged (non-admin) user and confirm that you can create or modify files in C:\PowerToys\DSCModules. This confirms that a non-admin user can plant arbitrary DLLs in a system PATH directory.
  5. The attacker identifies a DLL that a privileged process (e.g., a system service or an application running as a different, higher-privileged user) attempts to load via the standard DLL search order. The attacker crafts a malicious DLL with the same name and places it in C:\PowerToys\DSCModules.

The fix is to:

  • Hardening the PowerToys install leaf directory for per-machine custom installs using the effective DACL model of Program Files, with inheritance protection enabled to prevent permissive ACLs from parent directories such as C:\ from propagating.

  • During upgrade or repair, we will also reapply the hardened ACLs to existing install contents so that previously insecure custom installs are corrected, not just newly installed instances.

PR Checklist

  • Closes: #xxx
  • Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected
  • Tests: Added/updated and all pass
  • Localization: All end-user-facing strings can be localized
  • Dev docs: Added/updated
  • New binaries: Added on the required places
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

image Normal user don't have write access any more to the folder when deploy local machine version installer

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens per-machine custom install locations to mitigate a local privilege escalation vector where permissive inherited ACLs allow non-admin users to plant DLLs in a directory that is added to PATH.

Changes:

  • Adds a new deferred, non-impersonated MSI custom action to secure the install folder ACL for per-machine installs outside Program Files.
  • Implements ACL templating based on Program Files and reapplies hardened ACLs across the existing install tree during upgrade/repair.
  • Wires the new custom action into the WiX installer sequencing for per-machine scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
installer/PowerToysSetupVNext/Product.wxs Schedules the new SecureInstallFolderAcl custom action and its property setup for per-machine installs.
installer/PowerToysSetupCustomActionsVNext/pch.h Adds ACL API header include needed for the new security logic.
installer/PowerToysSetupCustomActionsVNext/CustomAction.def Exports the new custom action entrypoint for MSI to invoke.
installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp Implements the install-folder ACL hardening + recursive re-ACL logic.

Comment on lines +286 to +323
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.");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path parent = normalized.parent_path();
if (parent.empty() || parent == normalized.root_path())
{
return false;
}

I'm not sure whether this needs to be called out, so this is a great review, and will enforce something other than the suggest

vanzue and others added 4 commits March 16, 2026 11:32
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@vanzue vanzue marked this pull request as draft March 16, 2026 06:30
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this require hundreds of lines of custom action code? does WiX not support ACLs?

@github-actions

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants