Skip to content

Commit c25b662

Browse files
authored
Improved manifest validations for MSI and Windows Feature names (#6169)
## Change Adds some additional validation for MSI switches and Windows Feature names: 1. MSI switches are checked during full manifest validation in the same way that they are checked when we attempt to use the MSI APIs rather than msiexec. The API is used for fully all silent installs of MSIs, so we now ensure that those will work (6 total manifests in winget-pkgs were found that needed fixes). 2. Windows Feature names in dependencies are validated to be { alphanumeric, `-`, `_` }. This is done both during full manifest validation and at runtime. A PowerShell script (tools/ManifestValidation/Invoke-ManifestValidation.ps1) is added that will run `wingetdev validate` against a directory recursively and generate a report. There is support for resuming in the middle of the run. This would probably perform much better if it were updated to use the WinGetUtil API, but that can be a future project.
1 parent 35663a9 commit c25b662

28 files changed

Lines changed: 856 additions & 30 deletions

.github/actions/spelling/expect.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ ACCESSDENIED
77
ACCESSTOKEN
88
acl
99
adjacents
10+
adminproperties
1011
adml
1112
admx
1213
AFAIK
@@ -98,6 +99,7 @@ COMGLB
9899
commandline
99100
compressapi
100101
concurrencysal
102+
Consolas
101103
constexpr
102104
contactsupport
103105
contentfiles
@@ -279,6 +281,7 @@ Kaido
279281
KNOWNFOLDERID
280282
kool
281283
ktf
284+
LASTEXITCODE
282285
LCID
283286
learnxinyminutes
284287
LEBOM
@@ -347,6 +350,7 @@ msdownload
347350
msft
348351
msftrubengu
349352
MSIHASH
353+
msinewinstance
350354
MSIXHASH
351355
MSIXSTRM
352356
msstore
@@ -510,6 +514,7 @@ sddl
510514
secureobject
511515
securestring
512516
seekp
517+
Segoe
513518
seof
514519
servercert
515520
servercertificate

src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ namespace AppInstaller::CLI::Workflow
175175
{
176176
AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]");
177177
}
178+
else if (result == E_INVALIDARG)
179+
{
180+
AICLI_LOG(Core, Warning, << "Invalid Windows Feature name [" << featureName << "]");
181+
enableFeaturesFailed = true;
182+
featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl;
183+
}
178184
else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE
179185
{
180186
AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist");

src/AppInstallerCLICore/Workflows/InstallFlow.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,8 @@ namespace AppInstaller::CLI::Workflow
6161

6262
bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall)
6363
{
64-
switch (type)
65-
{
66-
case InstallerTypeEnum::Msi:
67-
case InstallerTypeEnum::Wix:
68-
return isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI);
69-
default:
70-
return false;
71-
}
64+
return DoesInstallerTypeUseMsiProperties(type) &&
65+
(isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI));
7266
}
7367

7468
bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg)

src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,12 @@ namespace AppInstaller::CLI::Workflow
509509

510510
void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const
511511
{
512+
if (!Utility::IsValidWindowsFeaturePattern(m_featureName))
513+
{
514+
context.Add<Execution::Data::OperationReturnCode>(static_cast<DWORD>(E_INVALIDARG));
515+
return;
516+
}
517+
512518
Utility::LocIndView locIndFeatureName{ m_featureName };
513519

514520
std::optional<DWORD> doesFeatureExistResult = DoesWindowsFeatureExist(context, m_featureName);

src/AppInstallerCLITests/AppInstallerCLITests.vcxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,18 @@
10731073
<CopyFileToFolders Include="TestData\ManifestV1_28-PowerShellDSC.yaml">
10741074
<DeploymentContent>true</DeploymentContent>
10751075
</CopyFileToFolders>
1076+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidWindowsFeatureName.yaml">
1077+
<DeploymentContent>true</DeploymentContent>
1078+
</CopyFileToFolders>
1079+
<CopyFileToFolders Include="TestData\Manifest-Bad-BlockedMsiProperty.yaml">
1080+
<DeploymentContent>true</DeploymentContent>
1081+
</CopyFileToFolders>
1082+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidMsiSwitches.yaml">
1083+
<DeploymentContent>true</DeploymentContent>
1084+
</CopyFileToFolders>
1085+
<CopyFileToFolders Include="TestData\Manifest-Bad-NetworkAddressInSwitches.yaml">
1086+
<DeploymentContent>true</DeploymentContent>
1087+
</CopyFileToFolders>
10761088
</ItemGroup>
10771089
<ItemGroup>
10781090
<ProjectReference Include="..\AppInstallerCLICore\AppInstallerCLICore.vcxproj">

src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,5 +1140,17 @@
11401140
<CopyFileToFolders Include="TestData\ManifestV1_28-Singleton.yaml">
11411141
<Filter>TestData</Filter>
11421142
</CopyFileToFolders>
1143+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidWindowsFeatureName.yaml">
1144+
<Filter>TestData</Filter>
1145+
</CopyFileToFolders>
1146+
<CopyFileToFolders Include="TestData\Manifest-Bad-BlockedMsiProperty.yaml">
1147+
<Filter>TestData</Filter>
1148+
</CopyFileToFolders>
1149+
<CopyFileToFolders Include="TestData\Manifest-Bad-InvalidMsiSwitches.yaml">
1150+
<Filter>TestData</Filter>
1151+
</CopyFileToFolders>
1152+
<CopyFileToFolders Include="TestData\Manifest-Bad-NetworkAddressInSwitches.yaml">
1153+
<Filter>TestData</Filter>
1154+
</CopyFileToFolders>
11431155
</ItemGroup>
11441156
</Project>

src/AppInstallerCLITests/InstallDependenciesFlow.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "pch.h"
44
#include "WorkflowCommon.h"
55
#include "DependenciesTestSource.h"
6+
#include <AppInstallerRuntime.h>
67
#include <Commands/InstallCommand.h>
78
#include <Commands/COMCommand.h>
89
#include <Workflows/DependenciesFlow.h>
@@ -306,6 +307,42 @@ TEST_CASE("InstallFlow_Dependencies_COM", "[InstallFlow][workflow][dependencies]
306307
REQUIRE(installationOrder.at(2) == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies");
307308
}
308309

310+
void InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(std::string_view featureName)
311+
{
312+
std::ostringstream installOutput;
313+
TestContext context{ installOutput, std::cin };
314+
315+
context << ShellExecuteEnableWindowsFeature(featureName);
316+
317+
INFO(installOutput.str());
318+
319+
REQUIRE(context.Contains(Execution::Data::OperationReturnCode));
320+
REQUIRE(context.Get<Execution::Data::OperationReturnCode>() == E_INVALIDARG);
321+
}
322+
323+
TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Extras", "[InstallFlow][workflow][dependencies][111981]")
324+
{
325+
TempFile potentialLogFile("dism-log", ".log");
326+
std::string featureName = "MediaPlayback /LogPath:";
327+
featureName.append(potentialLogFile.GetPath().u8string());
328+
329+
InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName);
330+
331+
REQUIRE(!std::filesystem::exists(potentialLogFile));
332+
}
333+
334+
TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Quoted", "[InstallFlow][workflow][dependencies][111981]")
335+
{
336+
TempFile potentialLogFile("dism-log", ".log");
337+
std::string featureName = "\"MediaPlayback /LogPath:";
338+
featureName.append(potentialLogFile.GetPath().u8string());
339+
featureName.append("\"");
340+
341+
InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName);
342+
343+
REQUIRE(!std::filesystem::exists(potentialLogFile));
344+
}
345+
309346
// TODO:
310347
// add dependencies for installer tests to DependenciesTestSource (or a new one)
311348
// add tests for min version dependency solving

src/AppInstallerCLITests/Strings.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,28 @@ TEST_CASE("ConvertControlCodesToPictures", "[strings]")
354354

355355
REQUIRE(ConvertControlCodesToPictures(allCodes) == ConvertToUTF8(allPictures));
356356
}
357+
358+
TEST_CASE("IsValidWindowsFeaturePattern_AllFound_True", "[strings][111981]")
359+
{
360+
for (const auto& name : {
361+
"IIS-ODBCLogging",
362+
"NetFx3",
363+
"SMB1Protocol",
364+
})
365+
{
366+
INFO(name);
367+
REQUIRE(IsValidWindowsFeaturePattern(name));
368+
}
369+
}
370+
371+
TEST_CASE("IsValidWindowsFeaturePattern_Bad_False", "[strings][111981]")
372+
{
373+
for (const auto& name : {
374+
"MediaPlayback /LogPath:C:\\file.txt",
375+
"\"MediaPlayback /LogPath:C:\\file.txt\"",
376+
})
377+
{
378+
INFO(name);
379+
REQUIRE(!IsValidWindowsFeaturePattern(name));
380+
}
381+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Installer with a blocked MSI property in a switch value
2+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json
3+
4+
PackageIdentifier: AppInstallerCliTest.TestMsiInstaller
5+
PackageVersion: 1.0.0.0
6+
PackageLocale: en-US
7+
PackageName: AppInstaller Test MSI Installer
8+
ShortDescription: AppInstaller Test MSI Installer
9+
Publisher: Microsoft Corporation
10+
License: Test
11+
InstallerType: msi
12+
Installers:
13+
- Architecture: x64
14+
InstallerUrl: https://ThisIsNotUsed
15+
InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea
16+
InstallerSwitches:
17+
Silent: TRANSFORMS=evil.mst
18+
ManifestType: singleton
19+
ManifestVersion: 1.0.0
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Installer with unparseable MSI switch arguments
2+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json
3+
4+
PackageIdentifier: AppInstallerCliTest.TestMsiInstaller
5+
PackageVersion: 1.0.0.0
6+
PackageLocale: en-US
7+
PackageName: AppInstaller Test MSI Installer
8+
ShortDescription: AppInstaller Test MSI Installer
9+
Publisher: Microsoft Corporation
10+
License: Test
11+
InstallerType: msi
12+
Installers:
13+
- Architecture: x64
14+
InstallerUrl: https://ThisIsNotUsed
15+
InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea
16+
InstallerSwitches:
17+
Silent: '@INVALID'
18+
ManifestType: singleton
19+
ManifestVersion: 1.0.0

0 commit comments

Comments
 (0)