Skip to content

Commit

Permalink
fix(dotnet): don't include non-runtime libraries into report for `*.d…
Browse files Browse the repository at this point in the history
…eps.json` files (#7039)
  • Loading branch information
DmitriyLewen authored Jul 22, 2024
1 parent b76a725 commit 5bc662b
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 21 deletions.
3 changes: 3 additions & 0 deletions docs/docs/coverage/language/dotnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ The following table provides an outline of the features Trivy offers.
## *.deps.json
Trivy parses `*.deps.json` files. Trivy currently excludes dev dependencies from the report.

!!! note
Trivy only includes runtime dependencies in the report.

## packages.config
Trivy only finds dependency names and versions from `packages.config` files. To build dependency graph, it is better to use `packages.lock.json` files.

Expand Down
6 changes: 4 additions & 2 deletions integration/testdata/dotnet.json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
"Type": "dotnet-core",
"Packages": [
{
"ID": "Newtonsoft.Json/9.0.1",
"Name": "Newtonsoft.Json",
"Identifier": {
"PURL": "pkg:nuget/Newtonsoft.Json@9.0.1",
"UID": "19955f480b8a6340"
"UID": "e678401f5d07418a"
},
"Version": "9.0.1",
"Layer": {},
Expand All @@ -40,10 +41,11 @@
"Vulnerabilities": [
{
"VulnerabilityID": "GHSA-5crp-9r3c-p9vr",
"PkgID": "Newtonsoft.Json/9.0.1",
"PkgName": "Newtonsoft.Json",
"PkgIdentifier": {
"PURL": "pkg:nuget/Newtonsoft.Json@9.0.1",
"UID": "19955f480b8a6340"
"UID": "e678401f5d07418a"
},
"InstalledVersion": "9.0.1",
"FixedVersion": "13.0.1",
Expand Down
3 changes: 2 additions & 1 deletion pkg/dependency/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ func ID(ltype types.LangType, name, version string) string {

sep := "@"
switch ltype {
case types.Conan:
// cf. https://github.com/dotnet/sdk/blob/529132850841a6bcfce96799262ce688e3851875/documentation/specs/runtime-configuration-file.md#targets-section-depsjson
case types.Conan, types.DotNetCore:
sep = "/"
case types.GoModule, types.GoBinary:
// Return a module ID according the Go way.
Expand Down
68 changes: 58 additions & 10 deletions pkg/dependency/parser/dotnet/core_deps/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,51 @@ package core_deps

import (
"io"
"sort"
"strings"
"sync"

"github.com/liamg/jfather"
"github.com/samber/lo"
"golang.org/x/xerrors"

"github.com/aquasecurity/trivy/pkg/dependency"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)

type dotNetDependencies struct {
Libraries map[string]dotNetLibrary `json:"libraries"`
RuntimeTarget RuntimeTarget `json:"runtimeTarget"`
Targets map[string]map[string]TargetLib `json:"targets"`
}

type dotNetLibrary struct {
Type string `json:"type"`
StartLine int
EndLine int
}

type RuntimeTarget struct {
Name string `json:"name"`
}

type TargetLib struct {
Runtime any `json:"runtime"`
RuntimeTargets any `json:"runtimeTargets"`
Native any `json:"native"`
}

type Parser struct {
logger *log.Logger
once sync.Once
}

func NewParser() *Parser {
return &Parser{
logger: log.WithPrefix("dotnet"),
once: sync.Once{},
}
}

Expand All @@ -29,11 +57,11 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
if err != nil {
return nil, nil, xerrors.Errorf("read error: %w", err)
}
if err := jfather.Unmarshal(input, &depsFile); err != nil {
if err = jfather.Unmarshal(input, &depsFile); err != nil {
return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err)
}

var pkgs []ftypes.Package
var pkgs ftypes.Packages
for nameVer, lib := range depsFile.Libraries {
if !strings.EqualFold(lib.Type, "package") {
continue
Expand All @@ -46,7 +74,20 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
continue
}

// Take target libraries for RuntimeTarget
if targetLibs, ok := depsFile.Targets[depsFile.RuntimeTarget.Name]; !ok {
// If the target is not found, take all dependencies
p.once.Do(func() {
p.logger.Debug("Unable to find `Target` for Runtime Target Name. All dependencies from `libraries` section will be included in the report", log.String("Runtime Target Name", depsFile.RuntimeTarget.Name))
})
} else if !p.isRuntimeLibrary(targetLibs, nameVer) {
// Skip non-runtime libraries
// cf. https://github.com/aquasecurity/trivy/pull/7039#discussion_r1674566823
continue
}

pkgs = append(pkgs, ftypes.Package{
ID: dependency.ID(ftypes.DotNetCore, split[0], split[1]),
Name: split[0],
Version: split[1],
Locations: []ftypes.Location{
Expand All @@ -58,17 +99,24 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
})
}

sort.Sort(pkgs)
return pkgs, nil, nil
}

type dotNetDependencies struct {
Libraries map[string]dotNetLibrary `json:"libraries"`
}

type dotNetLibrary struct {
Type string `json:"type"`
StartLine int
EndLine int
// isRuntimeLibrary returns true if library contains `runtime`, `runtimeTarget` or `native` sections, or if the library is missing from `targetLibs`.
// See https://github.com/aquasecurity/trivy/discussions/4282#discussioncomment-8830365 for more details.
func (p *Parser) isRuntimeLibrary(targetLibs map[string]TargetLib, library string) bool {
lib, ok := targetLibs[library]
// Selected target doesn't contain library
// Mark these libraries as runtime to avoid mistaken omission
if !ok {
p.once.Do(func() {
p.logger.Debug("Unable to determine that this is runtime library. Library not found in `Target` section.", log.String("Library", library))
})
return true
}
// Check that `runtime`, `runtimeTarget` and `native` sections are not empty
return !lo.IsEmpty(lib)
}

// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
Expand Down
68 changes: 60 additions & 8 deletions pkg/dependency/parser/dotnet/core_deps/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package core_deps

import (
"os"
"path"
"sort"
"testing"

Expand All @@ -13,29 +12,82 @@ import (
)

func TestParse(t *testing.T) {
vectors := []struct {
tests := []struct {
name string
file string // Test input file
want []ftypes.Package
wantErr string
}{
{
file: "testdata/ExampleApp1.deps.json",
name: "happy path",
file: "testdata/happy.deps.json",
want: []ftypes.Package{
{Name: "Newtonsoft.Json", Version: "13.0.1", Locations: []ftypes.Location{{StartLine: 33, EndLine: 39}}},
{
ID: "Newtonsoft.Json/13.0.1",
Name: "Newtonsoft.Json",
Version: "13.0.1",
Locations: []ftypes.Location{
{
StartLine: 33,
EndLine: 39,
},
},
},
},
},
{
file: "testdata/NoLibraries.deps.json",
name: "happy path with skipped libs",
file: "testdata/without-runtime.deps.json",
want: []ftypes.Package{
{
ID: "JsonDiffPatch/2.0.61",
Name: "JsonDiffPatch",
Version: "2.0.61",
Locations: []ftypes.Location{
{
StartLine: 66,
EndLine: 72,
},
},
},
{
ID: "Libuv/1.9.1",
Name: "Libuv",
Version: "1.9.1",
Locations: []ftypes.Location{
{
StartLine: 73,
EndLine: 79,
},
},
},
{
ID: "System.Collections.Immutable/1.3.0",
Name: "System.Collections.Immutable",
Version: "1.3.0",
Locations: []ftypes.Location{
{
StartLine: 101,
EndLine: 107,
},
},
},
},
},
{
name: "happy path without libs",
file: "testdata/no-libraries.deps.json",
want: nil,
},
{
file: "testdata/InvalidJson.deps.json",
name: "sad path",
file: "testdata/invalid.deps.json",
wantErr: "failed to decode .deps.json file: EOF",
},
}

for _, tt := range vectors {
t.Run(path.Base(tt.file), func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.file)
require.NoError(t, err)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v6.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v6.0": {
"hello2/1.0.0": {
"dependencies": {
"JsonDiffPatch": "2.0.61"
},
"runtime": {
"hello2.dll": {}
}
},
"JsonDiffPatch/2.0.61": {
"dependencies": {
"Microsoft.NETCore.App": "1.1.2"
},
"runtime": {
"lib/netcoreapp1.1/JsonDiffPatch.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"Libuv/1.9.1": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
},
"runtimeTargets": {
"runtimes/debian-x64/native/libuv.so": {
"rid": "debian-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
},
"runtimes/fedora-x64/native/libuv.so": {
"rid": "fedora-x64",
"assetType": "native",
"fileVersion": "0.0.0.0"
}
}
},
"Microsoft.NETCore.App/1.1.2": {
"dependencies": {
"Libuv": "1.9.1",
"System.Collections.Immutable": "1.3.0"
}
},
"Microsoft.NETCore.Platforms/1.1.0": {},
"NETStandard.Library/1.6.0": {
"dependencies": {
"System.Net.Http": "4.1.0"
}
},
"System.Net.Http/4.1.0": {}
}
},
"libraries": {
"hello2/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"JsonDiffPatch/2.0.61": {
"type": "package",
"serviceable": true,
"sha512": "sha512-nZ4QtcU3jR+CBT69qcJBvCcWi5uKgPRrrvSMm4V8Z76ljJ/MFo1P55qXk/nQY0q0WC4v94m5qH4SDhovFfci+Q==",
"path": "jsondiffpatch/2.0.61",
"hashPath": "jsondiffpatch.2.0.61.nupkg.sha512"
},
"Libuv/1.9.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uqX2Frwf9PW8MaY7PRNY6HM5BpW1D8oj1EdqzrmbEFD5nH63Yat3aEjN/tws6Tw6Fk7LwmLBvtUh32tTeTaHiA==",
"path": "libuv/1.9.1",
"hashPath": "libuv.1.9.1.nupkg.sha512"
},
"Microsoft.NETCore.App/1.1.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fcN0Ob6rjY7Zu0770cA5l9wRJvj7+ltJPPdryUidejkkhao+y2AYrtezBTlP9nCSFXLmYR9BtaknORT17x8reA==",
"path": "microsoft.netcore.app/1.1.2",
"hashPath": "microsoft.netcore.app.1.1.2.nupkg.sha512"
},
"Microsoft.NETCore.Platforms/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
"path": "microsoft.netcore.platforms/1.1.0",
"hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
},
"NETStandard.Library/1.6.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ypsCvIdCZ4IoYASJHt6tF2fMo7N30NLgV1EbmC+snO490OMl9FvVxmumw14rhReWU3j3g7BYudG6YCrchwHJlA==",
"path": "netstandard.library/1.6.0",
"hashPath": "netstandard.library.1.6.0.nupkg.sha512"
},
"System.Collections.Immutable/1.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zukBRPUuNxwy9m4TGWLxKAnoiMc9+B+8VXeXVyPiBPvOd7yLgAlZ1DlsRWJjMx4VsvhhF2+6q6kO2GRbPja6hA==",
"path": "system.collections.immutable/1.3.0",
"hashPath": "system.collections.immutable.1.3.0.nupkg.sha512"
},
"System.Net.Http/4.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ULq9g3SOPVuupt+Y3U+A37coXzdNisB1neFCSKzBwo182u0RDddKJF8I5+HfyXqK6OhJPgeoAwWXrbiUXuRDsg==",
"path": "system.net.http/4.1.0",
"hashPath": "system.net.http.4.1.0.nupkg.sha512"
}
}
}
1 change: 1 addition & 0 deletions pkg/fanal/analyzer/language/dotnet/deps/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Test_depsLibraryAnalyzer_Analyze(t *testing.T) {
FilePath: "testdata/datacollector.deps.json",
Packages: types.Packages{
{
ID: "Newtonsoft.Json/9.0.1",
Name: "Newtonsoft.Json",
Version: "9.0.1",
Locations: []types.Location{
Expand Down

0 comments on commit 5bc662b

Please sign in to comment.