Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 13 additions & 9 deletions pkg/vex/vex.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/sbom/core"
sbomio "github.com/aquasecurity/trivy/pkg/sbom/io"
"github.com/aquasecurity/trivy/pkg/set"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/uuid"
)
Expand Down Expand Up @@ -181,35 +182,38 @@ func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, p
return false
}

visited := make(map[uuid.UUID]bool)

// Use Depth First Search (DFS)
var dfs func(c *core.Component) bool
dfs = func(c *core.Component) bool {
var dfs func(c *core.Component, visited set.Set[uuid.UUID]) bool
dfs = func(c *core.Component, visited set.Set[uuid.UUID]) bool {

// Call the function with the current component and the leaf component
switch {
case notAffected(c, leaf):
return false
case c.Root:
return true
case lo.Every(lo.Keys(visited), parents[c.ID()]):
case set.New[uuid.UUID](parents[c.ID()]...).Difference(visited).Size() == 0:
// Should never go here, since all components except the root must have at least one parent and be related to the root component.
// If it does, it means the component tree is not connected due to a bug in the SBOM generation.
// In this case, so as not to filter out all the vulnerabilities accidentally, return true for fail-safe.
return true
}

visited[c.ID()] = true
visited.Append(c.ID())
for _, parent := range parents[c.ID()] {
if visited[parent] {
if visited.Contains(parent) {
continue
}
if dfs(components[parent]) {

// Each DFS path needs its own visited set,
// to avoid false positives in other paths
newVisited := visited.Clone()
if dfs(components[parent], newVisited) {
return true
}
}
return false
}

return dfs(leaf)
return dfs(leaf, set.New[uuid.UUID]())
}
67 changes: 66 additions & 1 deletion pkg/vex/vex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ var (
},
},
}
baseFiles2Package = ftypes.Package{
ID: "[email protected]",
Name: "base-files2",
Version: "5.3",
Identifier: ftypes.PkgIdentifier{
UID: "08",
PURL: &packageurl.PackageURL{
Type: packageurl.TypeDebian,
Namespace: "debian",
Name: "base-files2",
Version: "5.3",
},
},
}
goModulePackage = ftypes.Package{
ID: "github.com/aquasecurity/[email protected]",
Name: "github.com/aquasecurity/go-module",
Expand Down Expand Up @@ -572,7 +586,7 @@ repositories:
Sources: []vex.Source{
{
Type: vex.TypeFile,
FilePath: "testdata/openvex-multiple.json",
FilePath: "testdata/openvex-oci.json",
},
},
},
Expand All @@ -585,6 +599,37 @@ repositories:
}),
}),
},
{
name: "check one parent from multiple dependency paths",
args: args{
// - oci:debian?tag=12
// - pkg:deb/debian/[email protected]
// - pkg:deb/debian/[email protected]
// - pkg:deb/debian/[email protected]
// - pkg:deb/debian/[email protected]
Comment on lines +603 to +609
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case looks like multiple parents ([email protected] and [email protected]) from a single dependency ([email protected]). Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.
But oci:debian?tag=12 is the parent for both [email protected] and [email protected].

For this test case there are two dependency paths:

  1. pkg:deb/debian/[email protected] -> pkg:deb/debian/[email protected] -> oci:debian?tag=12
  2. pkg:deb/debian/[email protected] -> pkg:deb/debian/[email protected] -> oci:debian?tag=12

After the first path, we mark oci:debian?tag=12 as visited.
When we process pkg:deb/debian/[email protected], we see that oci:debian?tag=12 is already visited and return true.

report: imageReport([]types.Result{
bashPackagesResult(types.Result{
Vulnerabilities: []types.DetectedVulnerability{
vuln3,
},
}),
}),
opts: vex.Options{
Sources: []vex.Source{
{
Type: vex.TypeFile,
FilePath: "testdata/openvex-oci.json",
},
},
},
},
want: imageReport([]types.Result{
bashPackagesResult(types.Result{
Vulnerabilities: []types.DetectedVulnerability{},
ModifiedFindings: []types.ModifiedFinding{modifiedFinding(vuln3, vulnerableCodeNotInExecutePath, "testdata/openvex-oci.json")},
}),
}),
},
{
name: "unknown format",
args: args{
Expand Down Expand Up @@ -705,6 +750,26 @@ func bashResult(result types.Result) types.Result {
return result
}

func bashPackagesResult(result types.Result) types.Result {
result.Type = ftypes.Debian
result.Class = types.ClassOSPkg

bashPkg := clonePackage(bashPackage)
baseFilesPkg := clonePackage(baseFilesPackage)
baseFiles2Pkg := clonePackage(baseFiles2Package)

baseFilesPkg.DependsOn = []string{bashPkg.ID}
baseFiles2Pkg.DependsOn = []string{bashPkg.ID}

result.Packages = []ftypes.Package{
bashPkg,
baseFilesPkg,
baseFiles2Pkg,
}

return result
}

func infinityLoopOSPackagesResult(result types.Result) types.Result {
result.Type = ftypes.Debian
result.Class = types.ClassOSPkg
Expand Down