Skip to content

Commit

Permalink
Implement archive malware scan for local manifests (#2566)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft authored Oct 7, 2022
1 parent 8d0996f commit 1dfa17c
Show file tree
Hide file tree
Showing 21 changed files with 2,629 additions and 18 deletions.
2,064 changes: 2,064 additions & 0 deletions NOTICE.txt

Large diffs are not rendered by default.

52 changes: 44 additions & 8 deletions src/AppInstallerCLI.sln
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{
..\templates\e2e-test.template.yml = ..\templates\e2e-test.template.yml
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PureLib", "PureLib\PureLib.vcxproj", "{BB14D603-F44E-4415-8770-BF3E13F4C17F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -913,31 +915,31 @@ Global
{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x64.Build.0 = Release|x64
{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x86.ActiveCfg = Release|Win32
{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.TestRelease|x86.Build.0 = Release|Win32
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|Any CPU.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|Any CPU.ActiveCfg = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM.ActiveCfg = Debug|ARM
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.Build.0 = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.ActiveCfg = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.Build.0 = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.ActiveCfg = Debug|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.Build.0 = Debug|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|Any CPU.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|Any CPU.ActiveCfg = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM.ActiveCfg = Debug|ARM
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM64.Build.0 = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x64.ActiveCfg = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x86.ActiveCfg = Debug|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x86.Build.0 = Debug|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|Any CPU.ActiveCfg = Release|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM.ActiveCfg = Release|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|Any CPU.ActiveCfg = Release|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM.ActiveCfg = Release|ARM
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.ActiveCfg = Release|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.Build.0 = Release|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.ActiveCfg = Release|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.Build.0 = Release|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.ActiveCfg = Release|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.Build.0 = Release|x86
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|Any CPU.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|ARM.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|Any CPU.ActiveCfg = Debug|x64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|ARM.ActiveCfg = Debug|ARM
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|ARM64.ActiveCfg = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|ARM64.Build.0 = Debug|ARM64
{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.TestRelease|x64.ActiveCfg = Release|x64
Expand Down Expand Up @@ -1026,6 +1028,39 @@ Global
{787EC629-C0FB-4BA9-9746-4A82CD06B73E}.TestRelease|x64.Build.0 = Release|x64
{787EC629-C0FB-4BA9-9746-4A82CD06B73E}.TestRelease|x86.ActiveCfg = Debug|Win32
{787EC629-C0FB-4BA9-9746-4A82CD06B73E}.TestRelease|x86.Build.0 = Debug|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|Any CPU.ActiveCfg = Debug|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|ARM.ActiveCfg = Debug|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|ARM.Build.0 = Debug|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|ARM64.ActiveCfg = Debug|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|ARM64.Build.0 = Debug|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|x64.ActiveCfg = Debug|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|x64.Build.0 = Debug|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|x86.ActiveCfg = Debug|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Debug|x86.Build.0 = Debug|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Fuzzing|Any CPU.ActiveCfg = Debug|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Fuzzing|ARM.ActiveCfg = Debug|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Fuzzing|ARM64.ActiveCfg = Debug|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Fuzzing|x64.ActiveCfg = Debug|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Fuzzing|x86.ActiveCfg = Debug|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|Any CPU.ActiveCfg = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|ARM.ActiveCfg = Release|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|ARM.Build.0 = Release|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|ARM64.ActiveCfg = Release|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|ARM64.Build.0 = Release|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|x64.ActiveCfg = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|x64.Build.0 = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|x86.ActiveCfg = Release|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.Release|x86.Build.0 = Release|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|Any CPU.ActiveCfg = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|Any CPU.Build.0 = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|ARM.ActiveCfg = Release|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|ARM.Build.0 = Release|ARM
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|ARM64.ActiveCfg = Release|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|ARM64.Build.0 = Release|ARM64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|x64.ActiveCfg = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|x64.Build.0 = Release|x64
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|x86.ActiveCfg = Release|Win32
{BB14D603-F44E-4415-8770-BF3E13F4C17F}.TestRelease|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -1050,6 +1085,7 @@ Global
{31ED69A8-5310-45A9-953F-56C351D2C3E1} = {60618CAC-2995-4DF9-9914-45C6FC02C995}
{787EC629-C0FB-4BA9-9746-4A82CD06B73E} = {60618CAC-2995-4DF9-9914-45C6FC02C995}
{8E43F982-40D5-4DF1-9044-C08047B5F43B} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7}
{BB14D603-F44E-4415-8770-BF3E13F4C17F} = {60618CAC-2995-4DF9-9914-45C6FC02C995}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857}
Expand Down
2 changes: 2 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription);
WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled);
WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan);
WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases);
WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands);
Expand Down
28 changes: 28 additions & 0 deletions src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,34 @@ namespace AppInstaller::CLI::Workflow
constexpr std::wstring_view s_Extracted = L"extracted";
}

void ScanArchiveFromLocalManifest(Execution::Context& context)
{
if (context.Args.Contains(Execution::Args::Type::Manifest))
{
bool scanResult = Archive::ScanZipFile(context.Get<Execution::Data::InstallerPath>());

if (scanResult)
{
AICLI_LOG(CLI, Info, << "Archive malware scan passed");
}
else
{
// TODO: replace with proper --force argument when available.
if (context.Args.Contains(Execution::Args::Type::HashOverride))
{
AICLI_LOG(CLI, Warning, << "Archive malware scan failed; proceeding due to --force override");
context.Reporter.Warn() << Resource::String::ArchiveFailedMalwareScanOverridden << std::endl;
}
else
{
AICLI_LOG(CLI, Error, << "Archive malware scan failed");
context.Reporter.Error() << Resource::String::ArchiveFailedMalwareScan << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED);
}
}
}
}

void ExtractFilesFromArchive(Execution::Context& context)
{
const auto& installerPath = context.Get<Execution::Data::InstallerPath>();
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Workflows/ArchiveFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

namespace AppInstaller::CLI::Workflow
{
// Scans the archive file if downloaded from a local manifest
// Required Args: None
// Inputs: InstallerPath
// Outputs: None
void ScanArchiveFromLocalManifest(Execution::Context& context);

// Extracts the files from an archive
// Required Args: None
// Inputs: InstallerPath
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Workflows/InstallFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ namespace AppInstaller::CLI::Workflow
void ArchiveInstall(Execution::Context& context)
{
context <<
ScanArchiveFromLocalManifest <<
ExtractFilesFromArchive <<
VerifyAndSetNestedInstaller <<
ExecuteInstallerForType(context.Get<Execution::Data::Installer>().value().NestedInstallerType);
Expand Down
8 changes: 8 additions & 0 deletions src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1441,4 +1441,12 @@ Please specify one of them using the `--source` option to proceed.</value>
<data name="ExtractingArchive" xml:space="preserve">
<value>Extracting archive...</value>
</data>
<data name="ArchiveFailedMalwareScan" xml:space="preserve">
<value>Archive scan detected malware; to override this check use --force</value>
<comment>{Locked="--force"}</comment>
</data>
<data name="ArchiveFailedMalwareScanOverridden" xml:space="preserve">
<value>Archive scan detected malware; proceeding due to --force</value>
<comment>{Locked="--force"}</comment>
</data>
</root>
3 changes: 3 additions & 0 deletions src/AppInstallerCLITests/AppInstallerCLITests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,9 @@
<ProjectReference Include="..\YamlCppLib\YamlCppLib.vcxproj">
<Project>{8bb94bb8-374f-4294-bca1-c7811514a6b7}</Project>
</ProjectReference>
<ProjectReference Include="..\PureLib\PureLib.vcxproj">
<Project>{bb14d603-f44e-4415-8770-bf3e13f4c17f}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
Expand Down
9 changes: 9 additions & 0 deletions src/AppInstallerCLITests/Archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,13 @@ TEST_CASE("Extract_ZipArchive", "[archive]")
std::filesystem::path expectedPath = tempDirectoryPath / "test.txt";
REQUIRE(SUCCEEDED(hr));
REQUIRE(std::filesystem::exists(expectedPath));
}

TEST_CASE("Scan_ZipArchive", "[archive]")
{
TestDataFile testZip(s_ZipFile);

const auto& testZipPath = testZip.GetPath();
bool result = ScanZipFile(testZipPath);
REQUIRE(result);
}
5 changes: 5 additions & 0 deletions src/AppInstallerCLITests/TestHooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ namespace AppInstaller
{
void TestHook_SetCreateSymlinkResult_Override(bool* status);
}

namespace Archive
{
void TestHook_SetScanArchiveResult_Override(bool* status);
}
}
68 changes: 68 additions & 0 deletions src/AppInstallerCLITests/WorkFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <winget/LocIndependent.h>
#include <winget/ManifestYamlParser.h>
#include <winget/PathVariable.h>
#include <winget/Archive.h>
#include <Resources.h>
#include <AppInstallerFileLogger.h>
#include <Commands/ValidateCommand.h>
Expand Down Expand Up @@ -1088,6 +1089,9 @@ TEST_CASE("InstallFlow_Zip_Exe", "[InstallFlow][workflow]")
OverrideForVerifyAndSetNestedInstaller(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string());

bool overrideArchiveScanResult = true;
AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&overrideArchiveScanResult);

InstallCommand install({});
install.Execute(context);
INFO(installOutput.str());
Expand Down Expand Up @@ -1115,6 +1119,9 @@ TEST_CASE("InstallFlow_Zip_BadRelativePath", "[InstallFlow][workflow]")
OverrideForExtractInstallerFromArchive(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string());

bool overrideArchiveScanResult = true;
AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&overrideArchiveScanResult);

InstallCommand install({});
install.Execute(context);
INFO(installOutput.str());
Expand Down Expand Up @@ -1192,6 +1199,67 @@ TEST_CASE("InstallFlow_Zip_MultipleNonPortableNestedInstallers", "[InstallFlow][
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultipleNonPortableNestedInstallersSpecified).get()) != std::string::npos);
}

TEST_CASE("InstallFlow_Zip_ArchiveScanFailed", "[InstallFlow][workflow]")
{
TestCommon::TempFile installResultPath("TestExeInstalled.txt");
TestCommon::TestUserSettings testSettings;
testSettings.Set<Setting::EFZipInstall>(true);

std::ostringstream installOutput;
TestContext context{ installOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();
OverrideForShellExecute(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string());

bool overrideArchiveScanResult = false;
AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&overrideArchiveScanResult);

InstallCommand install({});
install.Execute(context);
INFO(installOutput.str());

REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED);

// Verify Installer was not called
REQUIRE(!std::filesystem::exists(installResultPath.GetPath()));
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos);
}

TEST_CASE("InstallFlow_Zip_ArchiveScanOverride", "[InstallFlow][workflow]")
{
TestCommon::TempFile installResultPath("TestExeInstalled.txt");
TestCommon::TestUserSettings testSettings;
testSettings.Set<Setting::EFZipInstall>(true);

std::ostringstream installOutput;
TestContext context{ installOutput, std::cin };
auto previousThreadGlobals = context.SetForCurrentThread();
OverrideForShellExecute(context);
OverrideForExtractInstallerFromArchive(context);
OverrideForVerifyAndSetNestedInstaller(context);
context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string());
context.Args.AddArg(Execution::Args::Type::HashOverride);

bool overrideArchiveScanResult = false;
AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&overrideArchiveScanResult);

InstallCommand install({});
install.Execute(context);
INFO(installOutput.str());

// Verify override message is displayed to the user.
REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScanOverridden).get()) != std::string::npos);

// Verify Installer is called and parameters are passed in.
REQUIRE(std::filesystem::exists(installResultPath.GetPath()));
std::ifstream installResultFile(installResultPath.GetPath());
REQUIRE(installResultFile.is_open());
std::string installResultStr;
std::getline(installResultFile, installResultStr);
REQUIRE(installResultStr.find("/custom") != std::string::npos);
REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos);
}

TEST_CASE("ExtractInstallerFromArchive_InvalidZip", "[InstallFlow][workflow]")
{
std::ostringstream installOutput;
Expand Down
Loading

0 comments on commit 1dfa17c

Please sign in to comment.