@joebigelow / wix / commits / 22c5aeb4

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>