Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions src/AppInstallerCLITests/Filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ TEST_CASE("VerifySymlink", "[filesystem]")
REQUIRE(VerifySymlink(symlinkPath, testFilePath));
REQUIRE_FALSE(VerifySymlink(symlinkPath, "badPath"));

// Verify case-insensitive comparison works (Windows paths are case-insensitive).
std::filesystem::path testFilePathUpperCase = basePath / "TESTFILE.TXT";
REQUIRE(VerifySymlink(symlinkPath, testFilePathUpperCase));

std::filesystem::remove(testFilePath);

// Ensure that symlink existence does not check the target
Expand Down
8 changes: 8 additions & 0 deletions src/AppInstallerCLITests/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ int main(int argc, char** argv)
{
init_apartment();

// Enable ProcessRedirectionTrustPolicy to match the MSIX packaged process context in which
// WinGet runs in production. This policy causes Windows to block traversal of user-created
// symlinks (e.g. via weakly_canonical), so enabling it here surfaces bugs that would only
// manifest in the real product environment. The policy is one-way; it cannot be reversed.
PROCESS_MITIGATION_REDIRECTION_TRUST_POLICY redirectionPolicy{};
redirectionPolicy.EnforceRedirectionTrust = 1;
SetProcessMitigationPolicy(ProcessRedirectionTrustPolicy, &redirectionPolicy, sizeof(redirectionPolicy));

bool hasSetTestDataBasePath = false;
bool waitBeforeReturn = false;
bool keepSQLLogging = false;
Expand Down
17 changes: 15 additions & 2 deletions src/AppInstallerSharedLib/Filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,21 @@ namespace AppInstaller::Filesystem

bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target)
{
const std::filesystem::path& symlinkTargetPath = std::filesystem::weakly_canonical(symlink);
return symlinkTargetPath == std::filesystem::weakly_canonical(target);
// Use read_symlink to get the symlink's recorded target without traversing the filesystem
// chain. weakly_canonical would follow the symlink, which is blocked by
// ProcessRedirectionTrustPolicy (inherited from the MSIX packaged process context on
// newer Windows builds) when the symlink was created by a non-elevated process.
const std::filesystem::path symlinkTarget = std::filesystem::read_symlink(symlink);

// If the recorded target is relative, resolve it against the symlink's parent directory.
const std::filesystem::path resolvedTarget = symlinkTarget.is_absolute() ?
symlinkTarget : (symlink.parent_path() / symlinkTarget);

// Windows paths are case-insensitive. Use lexically_normal to resolve . and .. without
// filesystem access, then compare case-insensitively.
return Utility::ICUCaseInsensitiveEquals(
resolvedTarget.lexically_normal().u8string(),
target.lexically_normal().u8string());
}

void AppendExtension(std::filesystem::path& target, const std::string& value)
Expand Down
Loading