Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
99d427d
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 2, 2026
086d85f
Harden machine-wide workload state
dsplaisted Sep 6, 2026
aa8f540
Fix nullable warnings in trusted path validation
dsplaisted Sep 7, 2026
82f6ebd
Fix nullable workload ID test data
dsplaisted Sep 8, 2026
ad83582
Preserve intentional null workload ID tests
dsplaisted Sep 8, 2026
7a3a339
Validate directory permissions before checking reparse points
dsplaisted Sep 9, 2026
cc2cc81
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 10, 2026
557dd5b
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 11, 2026
82e2fca
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 11, 2026
fa0de6a
Merged PR 64999: 9.0.1xx - Validate correct permissions on Program Da…
dsplaisted Sep 11, 2026
4ccb3a2
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 12, 2026
9811446
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 12, 2026
014ef55
Merge commit 'ac1db92a24770acb2b2fa806e391b2d219f08859'
Sep 14, 2026
13c30dd
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 15, 2026
4e45988
Merged PR 64811: [internal/release/9.0.1xx] Update dependencies from …
Sep 15, 2026
1ec3137
Merge commit 'aedcf87f3496627f5c181c554643aba4dfd37b63'
Sep 15, 2026
dccdb47
Merged PR 65302: [internal/release/9.0.1xx] Update dependencies from …
Sep 16, 2026
fcb46f9
Merged PR 65348: [internal/release/9.0.1xx] Update dependencies from …
Sep 16, 2026
c270825
Merged PR 65369: [internal/release/9.0.1xx] Update dependencies from …
Sep 16, 2026
d5624c4
Merged PR 65395: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
b49bf3a
Merged PR 65386: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
ba62d80
Merged PR 65381: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
44c0484
Merged PR 65444: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
f31037d
Merged PR 65448: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
7da4ccc
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 17, 2026
48b8b98
Merged PR 65462: [internal/release/9.0.1xx] Update dependencies from …
Sep 17, 2026
483a2d4
Merged PR 65253: Honor package source mapping during workload integri…
marcpopMSFT Sep 17, 2026
c27de17
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 17, 2026
15a3527
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 18, 2026
6f7450d
Merged PR 65470: [internal/release/9.0.1xx] Update dependencies from …
Sep 18, 2026
309e07d
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 18, 2026
fb5980f
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 19, 2026
339ad2b
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 21, 2026
5618cc1
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 21, 2026
4f62b01
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 22, 2026
e554fb4
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 22, 2026
94ed3ba
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 22, 2026
a09b93c
Merged PR 65546: [internal/release/9.0.1xx] Update dependencies from …
Sep 22, 2026
bea4c0d
Merged PR 65466: [internal/release/9.0.1xx] Update dependencies from …
Sep 22, 2026
973aa95
Merged PR 65582: [internal/release/9.0.1xx] Update dependencies from …
Sep 22, 2026
00157d0
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 22, 2026
70d63c8
Merged PR 65668: [internal/release/9.0.1xx] Update dependencies from …
Sep 23, 2026
c91eaf5
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 24, 2026
ebeae46
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 24, 2026
60abfdd
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 24, 2026
a1fde41
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 25, 2026
1782b57
Update dependencies from https://dev.azure.com/dnceng/internal/_git/d…
dotnet-bot Sep 25, 2026
ae5ab5b
Merged PR 65774: [internal/release/9.0.1xx] Update dependencies from …
Sep 25, 2026
60f9f95
Merged PR 65802: [internal/release/9.0.1xx] Update dependencies from …
Sep 25, 2026
c1ba25f
Merged PR 65801: [internal/release/9.0.1xx] Update dependencies from …
Sep 25, 2026
2e7b40d
Merged PR 65793: [internal/release/9.0.1xx] Update dependencies from …
Sep 25, 2026
78f338b
Merged PR 66126: [internal/release/9.0.1xx] Update dependencies from …
Oct 5, 2026
b3ef699
Merged PR 66171: [internal/release/9.0.1xx] Update dependencies from …
Oct 5, 2026
17951ad
Merged PR 66173: [internal/release/9.0.1xx] Update dependencies from …
Oct 5, 2026
2251da5
Merge commit '17951ad7c6650fd455de1cec69436c11e68d1c3c' into internal…
dotnet-bot Oct 13, 2026
f79bb24
Fix invalid workload name test on Unix
mthalman Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,24 @@
<!-- Begin: Package sources from dotnet-aspire -->
<!-- End: Package sources from dotnet-aspire -->
<!-- Begin: Package sources from dotnet-emsdk -->
<add key="darc-pub-dotnet-emsdk-2b4c425" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-emsdk-2b4c4258/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-emsdk -->
<!-- Begin: Package sources from dotnet-aspnetcore -->
<add key="darc-int-dotnet-aspnetcore-b4d87e3" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-aspnetcore-b4d87e3b/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-aspnetcore -->
<!-- Begin: Package sources from DotNet-msbuild-Trusted -->
<add key="darc-pub-DotNet-msbuild-Trusted-2a6130a" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-DotNet-msbuild-Trusted-2a6130a2/nuget/v3/index.json" />
<!-- End: Package sources from DotNet-msbuild-Trusted -->
<!-- Begin: Package sources from dotnet-roslyn-analyzers -->
<!-- End: Package sources from dotnet-roslyn-analyzers -->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-d280e4f" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-runtime-d280e4fb/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-runtime -->
<!-- Begin: Package sources from dotnet-templating -->
<add key="darc-int-dotnet-templating-80d3eb2" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-templating-80d3eb2c/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-templating -->
<!-- Begin: Package sources from dotnet-windowsdesktop -->
<add key="darc-int-dotnet-windowsdesktop-f476308" value="https://pkgs.dev.azure.com/dnceng/internal/_packaging/darc-int-dotnet-windowsdesktop-f476308c/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-windowsdesktop -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" />
Expand All @@ -61,14 +66,18 @@
<disabledPackageSources>
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-templating -->
<add key="darc-int-dotnet-templating-80d3eb2" value="true" />
<!-- End: Package sources from dotnet-templating -->
<!-- Begin: Package sources from dotnet-aspnetcore -->
<add key="darc-int-dotnet-aspnetcore-b4d87e3" value="true" />
<!-- Begin: Package sources from DotNet-msbuild-Trusted -->
<!-- End: Package sources from DotNet-msbuild-Trusted -->
<!-- End: Package sources from dotnet-aspnetcore -->
<!-- Begin: Package sources from dotnet-runtime -->
<add key="darc-int-dotnet-runtime-d280e4f" value="true" />
<!-- End: Package sources from dotnet-runtime -->
<!-- Begin: Package sources from dotnet-windowsdesktop -->
<add key="darc-int-dotnet-windowsdesktop-f476308" value="true" />
<!-- End: Package sources from dotnet-windowsdesktop -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
</disabledPackageSources>
Expand Down
274 changes: 137 additions & 137 deletions eng/Version.Details.xml

Large diffs are not rendered by default.

116 changes: 58 additions & 58 deletions eng/Versions.props

Large diffs are not rendered by default.

14 changes: 9 additions & 5 deletions src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#if !DOT_NET_BUILD_FROM_SOURCE
using Microsoft.DotNet.Installer.Windows.Security;
#endif
using Microsoft.DotNet;
using Microsoft.DotNet.Workloads.Workload;
using Newtonsoft.Json;

Expand Down Expand Up @@ -45,12 +46,12 @@ public void CachePayload(string packageId, string packageVersion, string manifes
{
// Validate that packageId and packageVersion do not contain path traversal characters
// to prevent an IPC client from constructing paths outside the package cache.
if (!WindowsUtils.ValidatePathComponent(packageId))
if (!PathComponentUtilities.IsValidPathComponent(packageId))
{
throw new ArgumentException($"Invalid package ID: {packageId}");
}

if (!WindowsUtils.ValidatePathComponent(packageVersion))
if (!PathComponentUtilities.IsValidPathComponent(packageVersion))
{
throw new ArgumentException($"Invalid package version: {packageVersion}");
}
Expand All @@ -77,8 +78,11 @@ public void CachePayload(string packageId, string packageVersion, string manifes
{
string packageDirectory = GetPackageDirectory(packageId, packageVersion);

// Delete the package directory and create a new one that's secure. If all the files were properly
// cached, the client would not request this action.
if (TrustedMachinePath.IsUnderRoot(packageDirectory, TrustedMachinePath.ProgramDataDotnetRoot))
{
SecurityUtils.EnsureSecureProgramDataRoot();
}

if (Directory.Exists(packageDirectory))
{
Directory.Delete(packageDirectory, recursive: true);
Expand Down Expand Up @@ -171,7 +175,7 @@ public bool TryGetMsiPathFromPackageData(string packageDataPath, out string msiP
/// Verifies that an MSI package contains an Authenticode signature that terminates in a trusted Microsoft root certificate.
/// </summary>
/// <param name="msiPath">The path of the MSI to verify.</param>
private void VerifyPackageSignature(string msiPath)
internal void VerifyPackageSignature(string msiPath)
{
if (VerifySignatures)
{
Expand Down
23 changes: 23 additions & 0 deletions src/Cli/dotnet/Installer/Windows/SecurityUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ namespace Microsoft.DotNet.Installer.Windows
[SupportedOSPlatform("windows")]
internal static class SecurityUtils
{
public static readonly string ProgramDataDotnetRoot = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"dotnet");

/// <summary>
/// Default inheritance to apply to directory ACLs.
/// </summary>
Expand Down Expand Up @@ -71,6 +75,11 @@ internal static class SecurityUtils
/// <param name="path">The path of the directory to create.</param>
public static void CreateSecureDirectory(string path)
{
if (TrustedMachinePath.IsUnderRoot(path, ProgramDataDotnetRoot))
{
EnsureSecureProgramDataRoot();
}

if (!Directory.Exists(path))
{
DirectorySecurity ds = new();
Expand All @@ -79,6 +88,20 @@ public static void CreateSecureDirectory(string path)
}
}

public static void EnsureSecureProgramDataRoot()
{
if (Directory.Exists(ProgramDataDotnetRoot))
{
TrustedMachinePath.ValidateDirectory(ProgramDataDotnetRoot);
}
else
{
DirectorySecurity ds = new();
SetDirectoryAccessRules(ds);
ds.CreateDirectory(ProgramDataDotnetRoot);
}
}

/// <summary>
/// Moves a file from one location to another if the destination file does not already exist and
/// configure its permissions.
Expand Down
121 changes: 121 additions & 0 deletions src/Cli/dotnet/Installer/Windows/TrustedMachinePath.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;
using System.Security.AccessControl;
using System.Security.Principal;

namespace Microsoft.DotNet.Installer.Windows
{
/// <summary>
/// Establishes that the machine-wide dotnet directory can be trusted by elevated code.
/// </summary>
#if NET
[SupportedOSPlatform("windows")]
#endif
internal static class TrustedMachinePath
{
private static readonly SecurityIdentifier s_administratorsSid =
new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null);
private static readonly SecurityIdentifier s_localSystemSid =
new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
private static readonly HashSet<SecurityIdentifier> s_trustedSids = new HashSet<SecurityIdentifier>
{
s_administratorsSid,
s_localSystemSid,
};

private const FileSystemRights ModificationRights =
FileSystemRights.WriteData |
FileSystemRights.AppendData |
FileSystemRights.WriteExtendedAttributes |
FileSystemRights.WriteAttributes |
FileSystemRights.Delete |
FileSystemRights.DeleteSubdirectoriesAndFiles |
FileSystemRights.ChangePermissions |
FileSystemRights.TakeOwnership;

public static string ProgramDataDotnetRoot { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"dotnet");

public static bool IsUnderRoot(string path, string root)
{
string fullPath = Path.GetFullPath(path);
string fullRoot = Path.GetFullPath(root).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

return fullPath.Equals(fullRoot, StringComparison.OrdinalIgnoreCase) ||
fullPath.StartsWith(fullRoot + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase);
}

public static void ValidateProgramDataRoot()
{
if (Directory.Exists(ProgramDataDotnetRoot))
{
ValidateDirectory(ProgramDataDotnetRoot);
}
}

public static void ValidateDirectory(string path)
{
Validate(new DirectoryInfo(path).GetAccessControl(AccessControlSections.Access | AccessControlSections.Owner), path);

if ((File.GetAttributes(path) & FileAttributes.ReparsePoint) != 0)
{
ThrowUntrusted(path);
}
}

public static void Validate(FileSystemSecurity security, string path)
{
if (security.GetOwner(typeof(SecurityIdentifier)) is not SecurityIdentifier owner ||
!s_trustedSids.Contains(owner))
{
ThrowUntrusted(path);
}

AuthorizationRuleCollection rules = security.GetAccessRules(
includeExplicit: true,
includeInherited: true,
typeof(SecurityIdentifier));
Dictionary<SecurityIdentifier, FileSystemRights> trustedRights = new Dictionary<SecurityIdentifier, FileSystemRights>();

foreach (FileSystemAccessRule rule in rules)
{
if (rule.AccessControlType == AccessControlType.Deny)
{
continue;
}

if (rule.IdentityReference is not SecurityIdentifier sid)
{
ThrowUntrusted(path);
continue;
}

if (s_trustedSids.Contains(sid))
{
trustedRights.TryGetValue(sid, out FileSystemRights existingRights);
trustedRights[sid] = existingRights | rule.FileSystemRights;
}
else if ((rule.FileSystemRights & ModificationRights) != 0)
{
ThrowUntrusted(path);
}
}

foreach (SecurityIdentifier trustedSid in s_trustedSids)
{
if (!trustedRights.TryGetValue(trustedSid, out FileSystemRights rights) ||
(rights & FileSystemRights.FullControl) != FileSystemRights.FullControl)
{
ThrowUntrusted(path);
}
}
}

private static void ThrowUntrusted(string path) =>
throw new InvalidDataException(
$"'{path}' has unexpected permissions and cannot be trusted. Delete '{path}' and retry.");
}
}
14 changes: 3 additions & 11 deletions src/Cli/dotnet/Installer/Windows/WindowsUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.DotNet;
using Microsoft.DotNet.Cli.Telemetry;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Win32;
Expand Down Expand Up @@ -283,17 +284,8 @@ public static bool ValidatePackagePath(string packagePath, string cacheRoot)
/// </summary>
/// <param name="component">The path component to validate.</param>
/// <returns><see langword="true"/> if the component is safe to use in <see cref="Path.Combine"/>; otherwise <see langword="false"/>.</returns>
public static bool ValidatePathComponent(string component)
{
if (string.IsNullOrWhiteSpace(component))
{
return false;
}

return !component.Contains(Path.DirectorySeparatorChar)
&& !component.Contains(Path.AltDirectorySeparatorChar)
&& !component.Contains("..");
}
public static bool ValidatePathComponent(string component) =>
PathComponentUtilities.IsValidPathComponent(component);

/// <summary>
/// Validates that the specified path, after canonicalization, is the expected root directory itself
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private List<PackageSource> LoadDefaultSources(PackageId packageId, PackageSourc
{
throw new NuGetPackageInstallerException(string.Format(LocalizableStrings.FailedToFindSourceUnderPackageSourceMapping, packageId));
}
defaultSources = defaultSources.Where(source => sources.Contains(source.Name)).ToList();
defaultSources = defaultSources.Where(source => sources.Contains(source.Name, StringComparer.OrdinalIgnoreCase)).ToList();
if (defaultSources.Count == 0)
{
throw new NuGetPackageInstallerException(string.Format(LocalizableStrings.FailedToMapSourceUnderPackageSourceMapping, packageId));
Expand Down
26 changes: 25 additions & 1 deletion src/Cli/dotnet/commands/dotnet-workload/InstallStateContents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.Text.Json;
using System.Text.Json.Serialization;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Installer.Windows;

#pragma warning disable CS8632

Expand Down Expand Up @@ -33,7 +35,29 @@ public static InstallStateContents FromString(string contents)

public static InstallStateContents FromPath(string path)
{
return File.Exists(path) ? FromString(File.ReadAllText(path)) : new InstallStateContents();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return FromPath(path, TrustedMachinePath.ProgramDataDotnetRoot);
}

return FromPath(path, programDataDotnetRoot: null);
}

internal static InstallStateContents FromPath(string path, string? programDataDotnetRoot)
{
if (!File.Exists(path))
{
return new InstallStateContents();
}

if (programDataDotnetRoot is not null &&
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
TrustedMachinePath.IsUnderRoot(path, programDataDotnetRoot))
{
TrustedMachinePath.ValidateDirectory(programDataDotnetRoot);
}

return FromString(File.ReadAllText(path));
}

public override string ToString()
Expand Down
15 changes: 15 additions & 0 deletions src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.DotNet.Cli.NuGetPackageDownloader;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.Extensions.EnvironmentAbstractions;
using Microsoft.NET.Sdk.WorkloadManifestReader;
using NuGet.Common;

namespace Microsoft.DotNet.Workloads.Workload
Expand All @@ -15,6 +16,20 @@ namespace Microsoft.DotNet.Workloads.Workload
/// </summary>
internal abstract class WorkloadCommandBase : CommandBase
{
protected static WorkloadId ParseWorkloadId(string workloadId)
{
try
{
return new WorkloadId(workloadId);
}
catch (ArgumentException)
{
throw new GracefulException(
string.Format(Install.LocalizableStrings.WorkloadNotRecognized, workloadId),
isUserError: false);
}
}

/// <summary>
/// The package downloader to use for acquiring NuGet packages.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public static void RunFirstUseCheck(IReporter reporter)
var packageDownloader = new NuGetPackageDownloader(
tempPackagesDirectory,
verboseLogger: new NullLogger(),
verifySignatures: verifySignatures);
verifySignatures: verifySignatures,
shouldUsePackageSourceMapping: true);

var installer = WorkloadInstallerFactory.GetWorkloadInstaller(
reporter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ protected uint InstallMsi(string packagePath, string logFile)

if (IsElevated)
{
SecurityUtils.EnsureSecureProgramDataRoot();
Cache.VerifyPackageSignature(packagePath);
ConfigureInstall(logFile);
string installProperties = InstallProperties.Create(InstallProperties.SystemComponent,
InstallProperties.FastInstall, InstallProperties.SuppressReboot,
Expand Down
Loading
Loading