mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
fix(vex): use a separate visited set for each DFS path (#9760)
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/aquasecurity/trivy/pkg/log"
|
"github.com/aquasecurity/trivy/pkg/log"
|
||||||
"github.com/aquasecurity/trivy/pkg/sbom/core"
|
"github.com/aquasecurity/trivy/pkg/sbom/core"
|
||||||
sbomio "github.com/aquasecurity/trivy/pkg/sbom/io"
|
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/types"
|
||||||
"github.com/aquasecurity/trivy/pkg/uuid"
|
"github.com/aquasecurity/trivy/pkg/uuid"
|
||||||
)
|
)
|
||||||
@@ -181,35 +182,38 @@ func reachRoot(leaf *core.Component, components map[uuid.UUID]*core.Component, p
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
visited := make(map[uuid.UUID]bool)
|
|
||||||
|
|
||||||
// Use Depth First Search (DFS)
|
// Use Depth First Search (DFS)
|
||||||
var dfs func(c *core.Component) bool
|
var dfs func(c *core.Component, visited set.Set[uuid.UUID]) bool
|
||||||
dfs = func(c *core.Component) bool {
|
dfs = func(c *core.Component, visited set.Set[uuid.UUID]) bool {
|
||||||
|
|
||||||
// Call the function with the current component and the leaf component
|
// Call the function with the current component and the leaf component
|
||||||
switch {
|
switch {
|
||||||
case notAffected(c, leaf):
|
case notAffected(c, leaf):
|
||||||
return false
|
return false
|
||||||
case c.Root:
|
case c.Root:
|
||||||
return true
|
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.
|
// 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.
|
// 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.
|
// In this case, so as not to filter out all the vulnerabilities accidentally, return true for fail-safe.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
visited[c.ID()] = true
|
visited.Append(c.ID())
|
||||||
for _, parent := range parents[c.ID()] {
|
for _, parent := range parents[c.ID()] {
|
||||||
if visited[parent] {
|
if visited.Contains(parent) {
|
||||||
continue
|
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 true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return dfs(leaf)
|
return dfs(leaf, set.New[uuid.UUID]())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,20 @@ var (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
baseFiles2Package = ftypes.Package{
|
||||||
|
ID: "base-files2@5.3",
|
||||||
|
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{
|
goModulePackage = ftypes.Package{
|
||||||
ID: "github.com/aquasecurity/go-module@v1.0.0",
|
ID: "github.com/aquasecurity/go-module@v1.0.0",
|
||||||
Name: "github.com/aquasecurity/go-module",
|
Name: "github.com/aquasecurity/go-module",
|
||||||
@@ -572,7 +586,7 @@ repositories:
|
|||||||
Sources: []vex.Source{
|
Sources: []vex.Source{
|
||||||
{
|
{
|
||||||
Type: vex.TypeFile,
|
Type: vex.TypeFile,
|
||||||
FilePath: "testdata/openvex-multiple.json",
|
FilePath: "testdata/openvex-oci.json",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -585,6 +599,37 @@ repositories:
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "check one parent from multiple dependency paths",
|
||||||
|
args: args{
|
||||||
|
// - oci:debian?tag=12
|
||||||
|
// - pkg:deb/debian/base-files@5.3
|
||||||
|
// - pkg:deb/debian/bash@5.3
|
||||||
|
// - pkg:deb/debian/base-files2@5.3
|
||||||
|
// - pkg:deb/debian/bash@5.3
|
||||||
|
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",
|
name: "unknown format",
|
||||||
args: args{
|
args: args{
|
||||||
@@ -705,6 +750,26 @@ func bashResult(result types.Result) types.Result {
|
|||||||
return 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 {
|
func infinityLoopOSPackagesResult(result types.Result) types.Result {
|
||||||
result.Type = ftypes.Debian
|
result.Type = ftypes.Debian
|
||||||
result.Class = types.ClassOSPkg
|
result.Class = types.ClassOSPkg
|
||||||
|
|||||||
Reference in New Issue
Block a user