diff --git a/syft/pkg/cataloger/dotnet/cataloger_test.go b/syft/pkg/cataloger/dotnet/cataloger_test.go index 68d63cbfab8..9abca7ed6a7 100644 --- a/syft/pkg/cataloger/dotnet/cataloger_test.go +++ b/syft/pkg/cataloger/dotnet/cataloger_test.go @@ -1008,6 +1008,7 @@ func TestCataloger(t *testing.T) { "Serilog.Sinks.Console @ 4.0.1 (/app/helloworld.deps.json)", "helloworld @ 1.0.0 (/app/helloworld.deps.json)", "runtime.linux-x64.Microsoft.NETCore.App @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json)", + "runtime.linux-x64.Microsoft.NETCore.DotNetHostPolicy @ 2.2.8 (/usr/share/dotnet/shared/Microsoft.NETCore.App/2.2.8/Microsoft.NETCore.App.deps.json)", // a compile target reference }, expectedRels: []string{ "Serilog @ 2.10.0 (/app/helloworld.deps.json) [dependency-of] Serilog.Sinks.Console @ 4.0.1 (/app/helloworld.deps.json)", @@ -1035,7 +1036,7 @@ func TestCataloger(t *testing.T) { } } -func TestDotnetDepsCataloger_problemCases(t *testing.T) { +func TestDotnetDepsCataloger_regressions(t *testing.T) { cases := []struct { name string fixture string @@ -1072,6 +1073,20 @@ func TestDotnetDepsCataloger_problemCases(t *testing.T) { pkgtest.AssertPackagesEqualIgnoreLayers(t, expected, actual) }, }, + { + name: "compile target reference", + fixture: "image-net8-compile-target", + cataloger: NewDotnetDepsBinaryCataloger(DefaultCatalogerConfig()), + assertion: func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) { + // ensure we find the DotNetNuke.Core package (which is using the compile target reference) + for _, p := range pkgs { + if p.Name == "DotNetNuke.Core" { + return + } + } + t.Error("expected to find DotNetNuke.Core package") + }, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { diff --git a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go index bba07858d1f..0b38c5c9a07 100644 --- a/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go +++ b/syft/pkg/cataloger/dotnet/deps_binary_cataloger.go @@ -204,6 +204,14 @@ func attachAssociatedExecutables(dep *logicalDepsJSON, pe logicalPE) bool { continue } + if targetPath, ok := p.CompilePathsByRelativeDLLPath[relativeDllPath]; ok { + pe.TargetPath = targetPath + p.Executables = append(p.Executables, pe) + dep.PackagesByNameVersion[key] = p // update the map with the modified package + found = true + continue + } + if p.NativePaths.Has(relativeDllPath) { pe.TargetPath = relativeDllPath p.Executables = append(p.Executables, pe) @@ -265,7 +273,7 @@ func packagesFromLogicalDepsJSON(doc logicalDepsJSON, config CatalogerConfig) (* continue } - claimsDLLs := len(lp.RuntimePathsByRelativeDLLPath) > 0 || len(lp.ResourcePathsByRelativeDLLPath) > 0 + claimsDLLs := len(lp.RuntimePathsByRelativeDLLPath) > 0 || len(lp.ResourcePathsByRelativeDLLPath) > 0 || len(lp.CompilePathsByRelativeDLLPath) > 0 || len(lp.NativePaths.List()) > 0 if config.DepPackagesMustClaimDLL && !claimsDLLs { if config.RelaxDLLClaimsWhenBundlingDetected && !doc.BundlingDetected || !config.RelaxDLLClaimsWhenBundlingDetected { diff --git a/syft/pkg/cataloger/dotnet/deps_json.go b/syft/pkg/cataloger/dotnet/deps_json.go index f7add21f66f..dc9c0db1883 100644 --- a/syft/pkg/cataloger/dotnet/deps_json.go +++ b/syft/pkg/cataloger/dotnet/deps_json.go @@ -26,9 +26,50 @@ type depsTarget struct { Dependencies map[string]string `json:"dependencies"` Runtime map[string]map[string]string `json:"runtime"` Resources map[string]map[string]string `json:"resources"` + Compile map[string]map[string]string `json:"compile"` Native map[string]map[string]string `json:"native"` } +func (t depsTarget) nativePaths() *strset.Set { + results := strset.New() + for path := range t.Native { + results.Add(path) + } + return results +} + +func (t depsTarget) compilePaths() map[string]string { + result := make(map[string]string) + for path := range t.Compile { + trimmedPath := trimLibPrefix(path) + if _, exists := result[trimmedPath]; exists { + continue + } + result[trimmedPath] = path + } + return result +} + +func (t depsTarget) resourcePaths() map[string]string { + result := make(map[string]string) + for path := range t.Resources { + trimmedPath := trimLibPrefix(path) + if _, exists := result[trimmedPath]; exists { + continue + } + result[trimmedPath] = path + } + return result +} + +func (t depsTarget) runtimePaths() map[string]string { + result := make(map[string]string) + for path := range t.Runtime { + result[trimLibPrefix(path)] = path + } + return result +} + type depsLibrary struct { Type string `json:"type"` Path string `json:"path"` @@ -51,6 +92,10 @@ type logicalDepsJSONPackage struct { // to the target path as described in the deps.json target entry under "resource". ResourcePathsByRelativeDLLPath map[string]string + // CompilePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file + // to the target path as described in the deps.json target entry under "compile". + CompilePathsByRelativeDLLPath map[string]string + // NativePathsByRelativeDLLPath is a map of the relative path to the DLL relative to the deps.json file // to the target path as described in the deps.json target entry under "native". These should not have // any runtime references to trim from the front of the path. @@ -133,31 +178,15 @@ func getLogicalDepsJSON(deps depsJSON) logicalDepsJSON { if ok { lib = &l } - runtimePaths := make(map[string]string) - for path := range target.Runtime { - runtimePaths[trimLibPrefix(path)] = path - } - resourcePaths := make(map[string]string) - for path := range target.Resources { - trimmedPath := trimLibPrefix(path) - if _, exists := resourcePaths[trimmedPath]; exists { - continue - } - resourcePaths[trimmedPath] = path - } - - nativePaths := strset.New() - for path := range target.Native { - nativePaths.Add(path) - } p := &logicalDepsJSONPackage{ NameVersion: libName, Library: lib, Targets: &target, - RuntimePathsByRelativeDLLPath: runtimePaths, - ResourcePathsByRelativeDLLPath: resourcePaths, - NativePaths: nativePaths, + RuntimePathsByRelativeDLLPath: target.runtimePaths(), + ResourcePathsByRelativeDLLPath: target.resourcePaths(), + CompilePathsByRelativeDLLPath: target.compilePaths(), + NativePaths: target.nativePaths(), } packageMap[libName] = p nameVersions.Add(libName) diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/.gitignore b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/.gitignore new file mode 100644 index 00000000000..b0b8376dc8f --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/.gitignore @@ -0,0 +1,2 @@ +/app +/extract.sh \ No newline at end of file diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/Dockerfile b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/Dockerfile new file mode 100644 index 00000000000..2770bd6bb80 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine@sha256:3f93439f47fea888d94e6e228d0d0de841f4122ef46f8bfd04f8bd78cbce7ddb AS build +ARG RUNTIME=win-x64 +WORKDIR /src + +COPY src/helloworld.csproj . +RUN dotnet restore -r $RUNTIME + +COPY src/*.cs . + +RUN dotnet publish -c Release --no-restore -o /app + +# note: we're not using a runtime here to make testing easier... so you cannot run this container and expect it to work +# we do this to keep the test assertions small since the don't want to include the several other runtime packages +FROM busybox:latest + +WORKDIR /app + +COPY --from=build /app . diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/Program.cs b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/Program.cs new file mode 100644 index 00000000000..752abf31b48 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace HelloWorld +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/helloworld.csproj b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/helloworld.csproj new file mode 100644 index 00000000000..62c56622533 --- /dev/null +++ b/syft/pkg/cataloger/dotnet/test-fixtures/image-net8-compile-target/src/helloworld.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + \ No newline at end of file