Enforce Windows Installer limits
Fixes 702
Rob Mensching committed
Jun 1, 2022 at 13:35 UTC
22c5aeb47a1a3a6f6d5cc1ca93f2d668362b05e5
5 files changed
+184
src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+11
@@ -345,6 +345,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
345
}
346
}
347
348
+ if (SectionType.Product == section.Type)
349
+ {
350
+ var command = new ValidateWindowsInstallerProductConstraints(this.Messaging, section);
351
+ command.Execute();
352
+
353
+ if (this.Messaging.EncounteredError)
354
+ {
355
+ return null;
356
+ }
357
+ }
358
+
359
// Set generated component guids and validate all guids.
360
{
361
var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateWindowsInstallerProductConstraints.cs
new
+78
@@ -0,0 +1,78 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolset.Core.WindowsInstaller.Bind
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+ using System.Linq;
9
+ using WixToolset.Data;
10
+ using WixToolset.Data.Symbols;
11
+ using WixToolset.Extensibility.Data;
12
+ using WixToolset.Extensibility.Services;
13
+
14
+ /// <summary>
15
+ /// Set the guids for components with generatable guids and validate all are appropriately unique.
16
+ /// </summary>
17
+ internal class ValidateWindowsInstallerProductConstraints
18
+ {
19
+ private const int MaximumAllowedComponentsInMsi = 65536;
20
+ private const int MaximumAllowedFeatureDepthInMsi = 16;
21
+
22
+ internal ValidateWindowsInstallerProductConstraints(IMessaging messaging, IntermediateSection section)
23
+ {
24
+ this.Messaging = messaging;
25
+ this.Section = section;
26
+ }
27
+
28
+ private IMessaging Messaging { get; }
29
+
30
+ private IntermediateSection Section { get; }
31
+
32
+ public void Execute()
33
+ {
34
+ var componentCount = this.Section.Symbols.OfType<ComponentSymbol>().Count();
35
+ var featuresWithParent = this.Section.Symbols.OfType<FeatureSymbol>().ToDictionary(f => f.Id.Id, f => f.ParentFeatureRef);
36
+ var featuresWithDepth = new Dictionary<string, int>();
37
+
38
+ if (componentCount > MaximumAllowedComponentsInMsi)
39
+ {
40
+ this.Messaging.Write(WindowsInstallerBackendErrors.ExceededMaximumAllowedComponentsInMsi(MaximumAllowedComponentsInMsi, componentCount));
41
+ }
42
+
43
+ foreach (var featureSymbol in this.Section.Symbols.OfType<FeatureSymbol>())
44
+ {
45
+ var featureDepth = CalculateFeaturesDepth(featureSymbol.Id.Id, featuresWithParent, featuresWithDepth);
46
+
47
+ if (featureDepth > MaximumAllowedFeatureDepthInMsi)
48
+ {
49
+ this.Messaging.Write(WindowsInstallerBackendErrors.ExceededMaximumAllowedFeatureDepthInMsi(featureSymbol.SourceLineNumbers, MaximumAllowedFeatureDepthInMsi, featureSymbol.Id.Id, featureDepth));
50
+ }
51
+ }
52
+ }
53
+
54
+ private static int CalculateFeaturesDepth(string id, Dictionary<string, string> featuresWithParent, Dictionary<string, int> featuresWithDepth)
55
+ {
56
+ if (featuresWithDepth.TryGetValue(id, out var featureDepth))
57
+ {
58
+ return featureDepth;
59
+ }
60
+
61
+ var parentId = featuresWithParent[id];
62
+ if (!String.IsNullOrEmpty(parentId))
63
+ {
64
+ var parentDepth = CalculateFeaturesDepth(parentId, featuresWithParent, featuresWithDepth);
65
+
66
+ featureDepth = parentDepth + 1;
67
+ }
68
+ else
69
+ {
70
+ featureDepth = 1;
71
+ }
72
+
73
+ featuresWithDepth.Add(id, featureDepth);
74
+
75
+ return featureDepth;
76
+ }
77
+ }
78
+}
src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs
+12
@@ -14,6 +14,16 @@ namespace WixToolset.Core.WindowsInstaller
14
return Message(sourceLineNumbers, Ids.CannotLoadWixoutAsTransform, "Could not load wixout file as a transform{1}", additionalDetail);
15
}
16
17
+ internal static Message ExceededMaximumAllowedComponentsInMsi(int maximumAllowedComponentsInMsi, int componentCount)
18
+ {
19
+ return Message(null, Ids.ExceededMaximumAllowedComponentsInMsi, "Maximum number of Components allowed in an MSI was exceeded. An MSI cannot contain more than {0} Components. The MSI contains {1} Components.", maximumAllowedComponentsInMsi, componentCount);
20
+ }
21
+
22
+ internal static Message ExceededMaximumAllowedFeatureDepthInMsi(SourceLineNumber sourceLineNumbers, int maximumAllowedFeatureDepthInMsi, string featureId, int featureDepth)
23
+ {
24
+ return Message(sourceLineNumbers, Ids.ExceededMaximumAllowedFeatureDepthInMsi, "Maximum depth of the Feature tree allowed in an MSI was exceeded. An MSI does not support a Feature tree with depth greater than {0}. The Feature '{1}' is at depth {2}.", maximumAllowedFeatureDepthInMsi, featureId, featureDepth);
25
+ }
26
+
27
public static Message InvalidModuleVersion(SourceLineNumber originalLineNumber, string version)
28
{
29
return Message(originalLineNumber, Ids.InvalidModuleVersion, "The Module/@Version was not be able to be used as a four-part version. A valid four-part version has a max value of \"65535.65535.65535.65535\" and must be all numeric.", version);
@@ -28,6 +38,8 @@ namespace WixToolset.Core.WindowsInstaller
38
{
39
CannotLoadWixoutAsTransform = 7500,
40
InvalidModuleVersion = 7501,
41
+ ExceededMaximumAllowedComponentsInMsi = 7502,
42
+ ExceededMaximumAllowedFeatureDepthInMsi = 7503,
43
} // last available is 7999. 8000 is BurnBackendErrors.
44
}
45
}
src/wix/test/WixToolsetTest.CoreIntegration/FeatureFixture.cs
+33
@@ -32,6 +32,8 @@ namespace WixToolsetTest.CoreIntegration
32
"-o", msiPath
33
});
34
35
+ Assert.Equal(267, result.ExitCode);
36
+
37
var errors = result.Messages.Where(m => m.Level == MessageLevel.Error);
38
Assert.Equal(new[]
39
{
@@ -39,5 +41,36 @@ namespace WixToolsetTest.CoreIntegration
41
}, errors.Select(e => e.Id).ToArray());
42
}
43
}
44
+
45
+ [Fact]
46
+ public void CannotBuildMsiWithTooLargeFeatureDepth()
47
+ {
48
+ var folder = TestData.Get(@"TestData");
49
+
50
+ using (var fs = new DisposableFileSystem())
51
+ {
52
+ var baseFolder = fs.GetFolder();
53
+ var intermediateFolder = Path.Combine(baseFolder, "obj");
54
+ var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
55
+
56
+ var result = WixRunner.Execute(new[]
57
+ {
58
+ "build",
59
+ Path.Combine(folder, "Feature", "PackageWithExcessiveFeatureDepth.wxs"),
60
+ "-bindpath", Path.Combine(folder, "SingleFile", "data"),
61
+ "-intermediateFolder", intermediateFolder,
62
+ "-o", msiPath
63
+ });
64
+
65
+ Assert.Equal(7503, result.ExitCode);
66
+
67
+ var errors = result.Messages.Where(m => m.Level == MessageLevel.Error);
68
+ Assert.Equal(new[]
69
+ {
70
+ 7503
71
+ }, errors.Select(e => e.Id).ToArray());
72
+ Assert.Equal("Maximum depth of the Feature tree allowed in an MSI was exceeded. An MSI does not support a Feature tree with depth greater than 16. The Feature 'Depth17' is at depth 17.", errors.Single().ToString());
73
+ }
74
+ }
75
}
76
}
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Feature/PackageWithExcessiveFeatureDepth.wxs
new
+50
@@ -0,0 +1,50 @@
1
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2
+ <Package Name="PackageWithExcessiveFeatureDepth" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127">
3
+ <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
4
+
5
+ <Feature Id="Depth1">
6
+ <Feature Id="Depth2">
7
+ <Feature Id="Depth3">
8
+ <Feature Id="Depth4">
9
+ <Feature Id="Depth5">
10
+ <Feature Id="Depth6">
11
+ <Feature Id="Depth7">
12
+ <Feature Id="Depth8">
13
+ <Feature Id="Depth9">
14
+ <Feature Id="Depth10">
15
+ <Feature Id="Depth11">
16
+ <Feature Id="Depth12">
17
+ <Feature Id="Depth13">
18
+ <Feature Id="Depth14">
19
+ <Feature Id="Depth15">
20
+ <Feature Id="Depth16">
21
+ <Feature Id="Depth17">
22
+ <Component Directory="INSTALLFOLDER">
23
+ <File Source="test.txt" />
24
+ </Component>
25
+ </Feature>
26
+ </Feature>
27
+ </Feature>
28
+ </Feature>
29
+ </Feature>
30
+ </Feature>
31
+ </Feature>
32
+ </Feature>
33
+ </Feature>
34
+ </Feature>
35
+ </Feature>
36
+ </Feature>
37
+ </Feature>
38
+ </Feature>
39
+ </Feature>
40
+ </Feature>
41
+ </Feature>
42
+ </Package>
43
+
44
+
45
+ <Fragment>
46
+ <StandardDirectory Id="ProgramFiles6432Folder">
47
+ <Directory Id="INSTALLFOLDER" Name="PackageMissingFeatureComponentMapping" />
48
+ </StandardDirectory>
49
+ </Fragment>
50
+</Wix>