Skip to content

Commit

Permalink
Add experimental NuGet detector for framework and dev dependencies (#…
Browse files Browse the repository at this point in the history
…1285)

* Add experimental NuGet detector for framework and dev dependencies

This PR does 3 things.

1. Adds `TargetFramework` to NuGet package references.  This can be useful when querying component data to understand if components are used in a place where a vulnerability applies.
2. Adds _framework package_ handling.  The .NET SDK will do [conflict resolution](https://github.com/dotnet/sdk/tree/main/src/Tasks/Common/ConflictResolution) and drop assets from packages that overlap with the framework.  NuGet is planning to do the same NuGet/Home#7344 but until then, it's beneficial to have component detection duplicate some of this logic.  When a package is identified as overlapping with the framework we'll treat it as a Development Dependency so that it might be auto-dismissed.
  - .NETFramework projects do not get this - .NETFramework does not participate in conflict resolution by default.  Even when enabled framework assemblies can be bypassed using bindingRedirects, or avoiding references to them.  Due this fragility it's not safe to apply framework package rules to .NETFramework.
  - packages.config usage is also excluded since it precludes SDK conflict resolution (and is also only used on .NETFramework projects).
3. Recognizes `ExcludeAssets="Runtime"` usage as a Development Dependencies, also any packages which don't contribute to "runtime" will be developement dependencies.

I reused _Development Dependency_ rather than plumbing a new concept.
I only mapped data for the `Microsoft.NETCore.App` - the default shared framework.  We could consider doing the same for `Microsoft.ASPNETCore.App` and `Microsoft.WindowsDesktop.App` but we'd need to plumb the reference information out of the assets file - currently that's not read and I'm not aware of a supported NuGet API for reading it (though it is present under `project/frameworks/<framework>/frameworkReferences/<name>`
.NET Core 1.x has no data since it was packages itself.  I have a fallback for future frameworks to read the data from the targeting packs.

* Address feedback
  • Loading branch information
ericstj authored Oct 31, 2024
1 parent a0e1520 commit c2546fa
Show file tree
Hide file tree
Showing 21 changed files with 1,304 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Microsoft.ComponentDetection.Contracts.TypedComponent;

using System.Collections.Generic;
using PackageUrl;

public class NuGetComponent : TypedComponent
Expand All @@ -22,6 +23,8 @@ public NuGetComponent(string name, string version, string[] authors = null)

public string[] Authors { get; set; }

public ISet<string> TargetFrameworks { get; set; } = new HashSet<string>();

public override ComponentType Type => ComponentType.NuGet;

public override string Id => $"{this.Name} {this.Version} - {this.Type}";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
namespace Microsoft.ComponentDetection.Detectors.NuGet;

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using global::NuGet.Frameworks;
using global::NuGet.Versioning;

/// <summary>
/// Represents a set of packages that are provided by a specific framework.
/// At the moment this only represents the packages that are provided by the Microsoft.NETCore.App framework.
/// We could extend this to represent the packages provided by other frameworks like Microsoft.AspNetCore.App and Microsoft.WindowsDesktop.App.
/// </summary>
internal sealed partial class FrameworkPackages : IEnumerable<KeyValuePair<string, NuGetVersion>>, IEnumerable
{
private static readonly Dictionary<NuGetFramework, FrameworkPackages> FrameworkPackagesByFramework = [];

static FrameworkPackages()
{
AddPackages(NETStandard20.Instance);
AddPackages(NETStandard21.Instance);
AddPackages(NETCoreApp20.Instance);
AddPackages(NETCoreApp21.Instance);
AddPackages(NETCoreApp22.Instance);
AddPackages(NETCoreApp30.Instance);
AddPackages(NETCoreApp31.Instance);
AddPackages(NETCoreApp50.Instance);
AddPackages(NETCoreApp60.Instance);
AddPackages(NETCoreApp70.Instance);
AddPackages(NETCoreApp80.Instance);
AddPackages(NETCoreApp90.Instance);

static void AddPackages(FrameworkPackages packages) => FrameworkPackagesByFramework[packages.Framework] = packages;
}

public FrameworkPackages(NuGetFramework framework) => this.Framework = framework;

public FrameworkPackages(NuGetFramework framework, FrameworkPackages frameworkPackages)
: this(framework) => this.Packages = new(frameworkPackages.Packages);

public NuGetFramework Framework { get; }

public Dictionary<string, NuGetVersion> Packages { get; } = new Dictionary<string, NuGetVersion>(StringComparer.OrdinalIgnoreCase);

public static FrameworkPackages GetFrameworkPackages(NuGetFramework framework)
{
if (FrameworkPackagesByFramework.TryGetValue(framework, out var frameworkPackages))
{
return frameworkPackages;
}

// if we didn't predefine the package overrides, load them from the targeting pack
// we might just leave this out since in future frameworks we'll have this functionality built into NuGet.
var frameworkPackagesFromPack = LoadFrameworkPackagesFromPack(framework);

return FrameworkPackagesByFramework[framework] = frameworkPackagesFromPack ?? new FrameworkPackages(framework);
}

private static FrameworkPackages LoadFrameworkPackagesFromPack(NuGetFramework framework)
{
if (framework is null || framework.Framework != FrameworkConstants.FrameworkIdentifiers.NetCoreApp)
{
return null;
}

// packs location : %ProgramFiles%\dotnet\packs
var packsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "packs", "Microsoft.NETCore.App.Ref");
if (!Directory.Exists(packsFolder))
{
return null;
}

var packVersionPattern = $"{framework.Version.Major}.{framework.Version.Minor}.*";
var packDirectories = Directory.GetDirectories(packsFolder, packVersionPattern);
var packageOverridesFile = packDirectories
.Select(d => (Overrides: Path.Combine(d, "data", "PackageOverrides.txt"), Version: ParseVersion(Path.GetFileName(d))))
.Where(d => File.Exists(d.Overrides))
.OrderByDescending(d => d.Version)
.FirstOrDefault().Overrides;

if (packageOverridesFile == null)
{
// we should also try to grab them from the user's package folder - they'll be in one location or the other.
return null;
}

// Adapted from https://github.com/dotnet/sdk/blob/c3a8f72c3a5491c693ff8e49e7406136a12c3040/src/Tasks/Common/ConflictResolution/PackageOverride.cs#L52-L68
var frameworkPackages = new FrameworkPackages(framework);
var packageOverrides = File.ReadAllLines(packageOverridesFile);

foreach (var packageOverride in packageOverrides)
{
var packageOverrideParts = packageOverride.Trim().Split('|');

if (packageOverrideParts.Length == 2)
{
var packageId = packageOverrideParts[0];
var packageVersion = ParseVersion(packageOverrideParts[1]);

frameworkPackages.Packages[packageId] = packageVersion;
}
}

return frameworkPackages;

static NuGetVersion ParseVersion(string versionString) => NuGetVersion.TryParse(versionString, out var version) ? version : null;
}

private void Add(string id, string version)
{
// intentionally redirect to indexer to allow for overwrite
this.Packages[id] = NuGetVersion.Parse(version);
}

public bool IsAFrameworkComponent(string id, NuGetVersion version) => this.Packages.TryGetValue(id, out var frameworkPackageVersion) && frameworkPackageVersion >= version;

IEnumerator<KeyValuePair<string, NuGetVersion>> IEnumerable<KeyValuePair<string, NuGetVersion>>.GetEnumerator() => this.Packages.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace Microsoft.ComponentDetection.Detectors.NuGet;

using global::NuGet.Frameworks;

/// <summary>
/// Framework packages for net5.0.
/// </summary>
internal partial class FrameworkPackages
{
internal static class NETCoreApp50
{
internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net5.0"), NETCoreApp31.Instance)
{
{ "Microsoft.CSharp", "4.7.0" },
{ "runtime.debian.8-x64.runtime.native.System", "4.3.0" },
{ "runtime.debian.8-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.debian.8-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.debian.8-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.debian.8-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.fedora.23-x64.runtime.native.System", "4.3.0" },
{ "runtime.fedora.23-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.fedora.23-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.fedora.23-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.fedora.24-x64.runtime.native.System", "4.3.0" },
{ "runtime.fedora.24-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.fedora.24-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.fedora.24-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.opensuse.13.2-x64.runtime.native.System", "4.3.0" },
{ "runtime.opensuse.13.2-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.opensuse.13.2-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.opensuse.13.2-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.opensuse.42.1-x64.runtime.native.System", "4.3.0" },
{ "runtime.opensuse.42.1-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.opensuse.42.1-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.opensuse.42.1-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.osx.10.10-x64.runtime.native.System", "4.3.0" },
{ "runtime.osx.10.10-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.osx.10.10-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.osx.10.10-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.rhel.7-x64.runtime.native.System", "4.3.0" },
{ "runtime.rhel.7-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.rhel.7-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.rhel.7-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.ubuntu.14.04-x64.runtime.native.System", "4.3.0" },
{ "runtime.ubuntu.14.04-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.ubuntu.14.04-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.ubuntu.16.04-x64.runtime.native.System", "4.3.0" },
{ "runtime.ubuntu.16.04-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.ubuntu.16.04-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "runtime.ubuntu.16.10-x64.runtime.native.System", "4.3.0" },
{ "runtime.ubuntu.16.10-x64.runtime.native.System.IO.Compression", "4.3.0" },
{ "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Http", "4.3.0" },
{ "runtime.ubuntu.16.10-x64.runtime.native.System.Net.Security", "4.3.0" },
{ "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography", "4.3.0" },
{ "System.Buffers", "4.5.1" },
{ "System.Collections.Immutable", "5.0.0" },
{ "System.ComponentModel.Annotations", "5.0.0" },
{ "System.Diagnostics.DiagnosticSource", "5.0.0" },
{ "System.Formats.Asn1", "5.0.0" },
{ "System.Net.Http.Json", "5.0.0" },
{ "System.Reflection.DispatchProxy", "4.7.1" },
{ "System.Reflection.Metadata", "5.0.0" },
{ "System.Runtime.CompilerServices.Unsafe", "5.0.0" },
{ "System.Text.Encoding.CodePages", "5.0.0" },
{ "System.Text.Encodings.Web", "5.0.0" },
{ "System.Text.Json", "5.0.0" },
{ "System.Threading.Channels", "5.0.0" },
{ "System.Threading.Tasks.Dataflow", "5.0.0" },
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Microsoft.ComponentDetection.Detectors.NuGet;

using global::NuGet.Frameworks;

/// <summary>
/// Framework packages for net6.0.
/// </summary>
internal partial class FrameworkPackages
{
internal static class NETCoreApp60
{
internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net6.0"), NETCoreApp50.Instance)
{
{ "Microsoft.Win32.Registry", "5.0.0" },
{ "System.Collections.Immutable", "6.0.0" },
{ "System.Diagnostics.DiagnosticSource", "6.0.1" },
{ "System.Formats.Asn1", "6.0.1" },
{ "System.IO.FileSystem.AccessControl", "5.0.0" },
{ "System.IO.Pipes.AccessControl", "5.0.0" },
{ "System.Net.Http.Json", "6.0.1" },
{ "System.Reflection.Metadata", "6.0.1" },
{ "System.Runtime.CompilerServices.Unsafe", "6.0.0" },
{ "System.Security.AccessControl", "6.0.1" },
{ "System.Security.Cryptography.Cng", "5.0.0" },
{ "System.Security.Cryptography.OpenSsl", "5.0.0" },
{ "System.Security.Principal.Windows", "5.0.0" },
{ "System.Text.Encoding.CodePages", "6.0.0" },
{ "System.Text.Encodings.Web", "6.0.0" },
{ "System.Text.Json", "6.0.9" },
{ "System.Threading.Channels", "6.0.0" },
{ "System.Threading.Tasks.Dataflow", "6.0.0" },
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Microsoft.ComponentDetection.Detectors.NuGet;

using global::NuGet.Frameworks;

/// <summary>
/// Framework packages for net7.0.
/// </summary>
internal partial class FrameworkPackages
{
internal static class NETCoreApp70
{
internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net7.0"), NETCoreApp60.Instance)
{
{ "System.Collections.Immutable", "7.0.0" },
{ "System.Diagnostics.DiagnosticSource", "7.0.2" },
{ "System.Formats.Asn1", "7.0.0" },
{ "System.Net.Http.Json", "7.0.1" },
{ "System.Reflection.Metadata", "7.0.2" },
{ "System.Text.Encoding.CodePages", "7.0.0" },
{ "System.Text.Encodings.Web", "7.0.0" },
{ "System.Text.Json", "7.0.4" },
{ "System.Threading.Channels", "7.0.0" },
{ "System.Threading.Tasks.Dataflow", "7.0.0" },
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Microsoft.ComponentDetection.Detectors.NuGet;

using global::NuGet.Frameworks;

/// <summary>
/// Framework packages for net8.0.
/// </summary>
internal partial class FrameworkPackages
{
internal static class NETCoreApp80
{
internal static FrameworkPackages Instance { get; } = new(NuGetFramework.Parse("net8.0"), NETCoreApp70.Instance)
{
{ "System.Collections.Immutable", "8.0.0" },
{ "System.Diagnostics.DiagnosticSource", "8.0.1" },
{ "System.Formats.Asn1", "8.0.1" },
{ "System.Net.Http.Json", "8.0.0" },
{ "System.Reflection.Metadata", "8.0.0" },
{ "System.Text.Encoding.CodePages", "8.0.0" },
{ "System.Text.Encodings.Web", "8.0.0" },
{ "System.Text.Json", "8.0.4" },
{ "System.Threading.Channels", "8.0.0" },
{ "System.Threading.Tasks.Dataflow", "8.0.1" },
};
}
}
Loading

0 comments on commit c2546fa

Please sign in to comment.