microsoft / microsoft/winget-cli

WinGet download hash mismatch cache issue

Open
#5,334 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Command-Download Issue-Feature
Dominant language
C++
Stars
26.4k
Forks
1.8k
Avg merge
1d 11h
Merged PRs (30d)
15

Description

Brief description of your issue

When running a winget download command, if there is a hash mismatch between the manifest's installer SHA256 and the downloaded file’s SHA256, the 'temporary' downloaded installer file (which has a GUID as its name with no extension) remains in the temp directory.
Ex: C:\Users\USERNAME\AppData\Local\Temp\WinGet\Corsair.iCUE.5.5.23.96\ec9d3ed6c05a94dd3e551f6a170136a3e931f659c64bac82885cd88a05d1656c

Note: I was able to resolve the issue by modifying VerifyInstallerHash() inside DownloadFlow.cpp to call RemoveInstallerFile on the file's path during hash failures. Here's my solution. Even though I'm sure y'all will find a more elegant solution, I wanted to give you an idea of how to fix it.

    void VerifyInstallerHash(Execution::Context& context)
    {
        const auto& [expectedHash, downloadResult] = context.Get<Execution::Data::DownloadHashInfo>();

        if (!std::equal(
            expectedHash.begin(),
            expectedHash.end(),
            downloadResult.Sha256Hash.begin()))
        {
            bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride);

            const auto& manifest = context.Get<Execution::Data::Manifest>();
            Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, expectedHash, downloadResult.Sha256Hash, overrideHashMismatch, downloadResult.SizeInBytes, downloadResult.ContentType);

            auto installerPath = GetInstallerBaseDownloadPath(context);
            auto installerFilename = GetInstallerPreHashValidationFileName(context);
            std::filesystem::path failedInstallerLocation = installerPath / installerFilename;

            if (downloadResult.SizeInBytes == 0)
            {
                context.Reporter.Error() << Resource::String::InstallerZeroByteFile << std::endl;
                AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE);
            }

            // If running as admin, do not allow the user to override the hash failure.
            if (Runtime::IsRunningAsAdmin())
            {
                context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl;
                RemoveInstallerFile(failedInstallerLocation);
            }
            else if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::InstallerHashOverride))
            {
                context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl;
                RemoveInstallerFile(failedInstallerLocation);
            }
            else if (overrideHashMismatch)
            {
                context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl;
                return;
            }
            else
            {
                context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl;
                RemoveInstallerFile(failedInstallerLocation);
            }

            AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH);
        }
        else
        {
            AICLI_LOG(CLI, Info, << "Installer hash verified");
            context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl;

            context.SetFlags(Execution::ContextFlag::InstallerHashMatched);

            if (context.Contains(Execution::Data::PackageVersion) &&
                context.Get<Execution::Data::PackageVersion>()->GetSource() &&
                WI_IsFlagSet(context.Get<Execution::Data::PackageVersion>()->GetSource().GetDetails().TrustLevel, SourceTrustLevel::Trusted))
            {
                context.SetFlags(Execution::ContextFlag::InstallerTrusted);
            }
        }
    }
Steps to reproduce

Run a winget download for a package that has a hash mismatch. Currently, the package Corsair.iCUE.5.5.23.96 has a hash mismatch so I'd recommend using that package to test this.

Here is the command I ran exactly:
winget download --id "Corsair.iCUE.5" --source "winget"

Expected behavior

The "temporary" installer file should be deleted on exit of program.

Actual behavior

The "temporary" installer file stays permanently until manually cleaned by user.

Environment
Windows Package Manager v1.10.340
Copyright (c) Microsoft Corporation. All rights reserved.

Windows: Windows.Desktop v10.0.26100.3476
System Architecture: X64
Package: Microsoft.DesktopAppInstaller v1.25.340.0

Winget Directories
-----------------------------------------------------------------------------------------------------------------------
Logs                               %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\Diag…
User Settings                      %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\sett…
Portable Links Directory (User)    %LOCALAPPDATA%\Microsoft\WinGet\Links
Portable Links Directory (Machine) C:\Program Files\WinGet\Links
Portable Package Root (User)       %LOCALAPPDATA%\Microsoft\WinGet\Packages
Portable Package Root              C:\Program Files\WinGet\Packages
Portable Package Root (x86)        C:\Program Files (x86)\WinGet\Packages
Installer Downloads                %USERPROFILE%\Downloads
Configuration Modules              %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules

Links
---------------------------------------------------------------------------
Privacy Statement   https://aka.ms/winget-privacy
License Agreement   https://aka.ms/winget-license
Third Party Notices https://aka.ms/winget-3rdPartyNotice
Homepage            https://aka.ms/winget
Windows Store Terms https://www.microsoft.com/en-us/storedocs/terms-of-sale

Admin Setting                             State
--------------------------------------------------
LocalManifestFiles                        Disabled
BypassCertificatePinningForMicrosoftStore Disabled
InstallerHashOverride                     Disabled
LocalArchiveMalwareScanOverride           Disabled
ProxyCommandLineOptions                   Disabled
DefaultProxy                              Disabled

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in DownloadFlow.cpp at VerifyInstallerHash() and inspect how the failed installer path is handled after a hash mismatch. Reproduce the issue with winget download --id "Corsair.iCUE.5" --source "winget", then verify that the temporary GUID-named installer file is removed when the command exits.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
cli, operating-systems
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.