diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 49fd62eccdee..83ebd70f0765 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -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" ) @@ -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]()) } diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index 3a36154bffa6..ec5864b1e7ba 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -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{ ID: "github.com/aquasecurity/go-module@v1.0.0", Name: "github.com/aquasecurity/go-module", @@ -572,7 +586,7 @@ repositories: Sources: []vex.Source{ { 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", args: args{ @@ -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