From 3324b35192dc28571a862f44c0cfbdeafc737870 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Mon, 20 Nov 2023 16:27:38 -0500 Subject: [PATCH 01/14] correctly failing test Signed-off-by: Will Murphy --- .../v5/transformers/nvd/unique_pkg_test.go | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index beb050fc..b30a858c 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -17,6 +17,9 @@ func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { } func TestFindUniquePkgs(t *testing.T) { + boolPtr := func(b bool) *bool { + return &b + } tests := []struct { name string nodes []nvd.Node @@ -226,6 +229,85 @@ func TestFindUniquePkgs(t *testing.T) { }, }), }, + { + name: "cpe with multiple platforms", + nodes: []nvd.Node{ + { + Negate: boolPtr(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + }, + }, + { + Negate: boolPtr(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + MatchCriteriaID: "902B8056-9E37-443B-8905-8AA93E2447FB", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + MatchCriteriaID: "3D94DA3B-FA74-4526-A0A0-A872684598C6", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + MatchCriteriaID: "DEECE5FC-CACF-4496-A3E7-164736409252", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + MatchCriteriaID: "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73", + Vulnerable: false, + }, + { + Criteria: "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + MatchCriteriaID: "FA6FEEC2-9F11-4643-8827-749718254FED", + Vulnerable: false, + }, + }, + }, + }, + expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"), + }, + }), + }, } for _, test := range tests { From 6b2524fdd338a0239c9cddb8e6901c4d63174090 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 21 Nov 2023 08:47:50 -0500 Subject: [PATCH 02/14] WIP: passes manual test; very messy; breaks othe things Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 104 ++++++++++++++---- .../v5/transformers/nvd/unique_pkg_test.go | 62 ++++++++++- 2 files changed, 145 insertions(+), 21 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 46612f22..35ad6c80 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -6,7 +6,6 @@ import ( "github.com/umisama/go-cpe" - "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/process/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) @@ -63,18 +62,76 @@ func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { return set } +func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) []*pkgCandidate { + nodes := c.Nodes + var result []*pkgCandidate + /* + Turn a configuration like this: + (AND (redis <= 6.2 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) + Into a configuration like this: + (OR (AND redis <= 6.1 debian:8) (AND redis <= 6.1 debian:9) (AND redis <= 6.1 ubuntu:19) (AND redis <= 6.1 ubuntu:20)) + */ + if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { + matches := nodes[1].CpeMatch + applicationNode := nodes[0].CpeMatch[0] + for _, maybePlatform := range matches { + // TODO: I'm overwriting a pointer or something? + // Something stupid is happening. Every time this loop steps, I'm overwriting + // the zeroth member of the set. + platform := maybePlatform.Criteria + candidate, err := newPkgCandidate(applicationNode, &platform) + if err != nil || candidate == nil { + continue + } + set.Add(*candidate, nodes[0].CpeMatch[0]) + } + + } + return result +} + func determinePlatformCPEAndNodes(c nvd.Configuration) (*string, []nvd.Node) { var platformCPE *string nodes := c.Nodes // Only retrieve a platform CPE in very specific cases + // WILL - I need to figure out what these if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { - if len(nodes[1].CpeMatch) == 1 && !nodes[1].CpeMatch[0].Vulnerable { + if len(nodes[1].CpeMatch) == 1 && !nodes[1].CpeMatch[0].Vulnerable { // WILL: this is false for the record in question + // Here's what I think is happening: + // Right now, if there is _exactly one_ platform + // we set that platform on the vuln record we emit + // but if there is more than one, we just punt + // and say, "who knows; lots of platforms." + // instead, we should figure out how to or together the platforms + // and emit the fewest number of rows that covers all cases. + // + /* + + TODAY: + sqlite> select id, package_name, namespace, package_qualifiers, version_constraint from vulnerability where id like 'CVE-2022-0543'; + id package_name namespace package_qualifiers version_constraint + ------------- ------------ ------------------------------------ ------------------ --------------------- + CVE-2022-0543 redis nvd:cpe + + This should really look more like: + + sqlite> select id, package_name, namespace, package_qualifiers, version_constraint from vulnerability where id like 'CVE-2022-0543'; + id package_name namespace package_qualifiers version_constraint + ------------- ------------ ------------------------------------ ------------------ --------------------- + CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"}] + CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"}] + CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"}] + CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"}] + CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"}] + */ platformCPE = &nodes[1].CpeMatch[0].Criteria nodes = []nvd.Node{nodes[0]} } } + // This needs to return multiple nodes? + return platformCPE, nodes } @@ -83,23 +140,32 @@ func _findUniquePkgs(set uniquePkgTracker, c nvd.Configuration) { return } - platformCPE, nodes := determinePlatformCPEAndNodes(c) - - for _, node := range nodes { - for _, match := range node.CpeMatch { - candidate, err := newPkgCandidate(match, platformCPE) - if err != nil { - // Do not halt all execution because of being unable to create - // a PkgCandidate. This can happen when a CPE is invalid which - // could avoid creating a database - log.Debugf("unable processing uniquePkg: %v", err) - continue - } - if candidate != nil { - set.Add(*candidate, match) - } - } - } + platformPackageCandidates(set, c) + + //platformCPE, nodes := determinePlatformCPEAndNodes(c) + // + //// TODO: this needs to loop also the other way; + //// we need to be able to represent a single package + //// that has multiple platforms, + //// probably by + //for _, node := range nodes { + // for _, match := range node.CpeMatch { + // candidate, err := newPkgCandidate(match, platformCPE) + // if err != nil { + // // Do not halt all execution because of being unable to create + // // a PkgCandidate. This can happen when a CPE is invalid which + // // could avoid creating a database + // log.Debugf("unable processing uniquePkg: %v", err) + // continue + // } + // if candidate != nil { + // set.Add(*candidate, match) + // } + // } + //} + //for _, u := range platformPackageCandidates(c) { + // set.Add(*) + //} } func buildConstraints(matches []nvd.CpeMatch) string { diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index b30a858c..2e0c4548 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -1,6 +1,8 @@ package nvd import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" "github.com/sergi/go-diff/diffmatchpatch" @@ -20,9 +22,13 @@ func TestFindUniquePkgs(t *testing.T) { boolPtr := func(b bool) *bool { return &b } + operatorRef := func(o nvd.Operator) *nvd.Operator { + return &o + } tests := []struct { name string nodes []nvd.Node + operator *nvd.Operator expected uniquePkgTracker }{ { @@ -230,7 +236,8 @@ func TestFindUniquePkgs(t *testing.T) { }), }, { - name: "cpe with multiple platforms", + name: "cpe with multiple platforms", + operator: operatorRef(nvd.And), nodes: []nvd.Node{ { Negate: boolPtr(false), @@ -312,7 +319,7 @@ func TestFindUniquePkgs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes}) + actual := findUniquePkgs(nvd.Configuration{Nodes: test.nodes, Operator: test.operator}) missing, extra := test.expected.Diff(actual) if len(missing) != 0 { for _, c := range missing { @@ -433,3 +440,54 @@ func TestBuildConstraints(t *testing.T) { } } + +func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { + candidates := []pkgCandidate{ + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"), + }, + { + Product: "redis", + Vendor: "redis", + TargetSoftware: ANY, + PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"), + }, + } + cpeMatch := nvd.CpeMatch{ + Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + } + applicationNode := nvd.CpeMatch{ + Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + MatchCriteriaID: "some-uuid", + Vulnerable: true, + } + tracker := newUniquePkgTracker() + for _, c := range candidates { + candidate, err := newPkgCandidate(applicationNode, c.PlatformCPE) + require.NoError(t, err) + tracker.Add(*candidate, cpeMatch) + } + assert.Len(t, tracker, len(candidates)) +} From 240908e78b2d27c216159b0a3805a74dcb622530 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 21 Nov 2023 09:09:06 -0500 Subject: [PATCH 03/14] units pass Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/transform.go | 4 +- pkg/process/v5/transformers/nvd/unique_pkg.go | 66 +++++++++---------- .../v5/transformers/nvd/unique_pkg_test.go | 26 ++++---- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/transform.go b/pkg/process/v5/transformers/nvd/transform.go index c77ffd35..72d92bbd 100644 --- a/pkg/process/v5/transformers/nvd/transform.go +++ b/pkg/process/v5/transformers/nvd/transform.go @@ -46,10 +46,10 @@ func Transform(vulnerability unmarshal.NVDVulnerability) ([]data.Entry, error) { cpes.Add(grypeNamespace.Resolver().Normalize(m.Criteria)) } - if p.PlatformCPE != nil { + if p.PlatformCPE != "" { qualifiers = []qualifier.Qualifier{platformcpe.Qualifier{ Kind: "platform-cpe", - CPE: *p.PlatformCPE, + CPE: p.PlatformCPE, }} } diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 35ad6c80..ad40af87 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -4,10 +4,10 @@ import ( "fmt" "strings" - "github.com/umisama/go-cpe" - + "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/process/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" + "github.com/umisama/go-cpe" ) const ( @@ -19,18 +19,18 @@ type pkgCandidate struct { Product string Vendor string TargetSoftware string - PlatformCPE *string + PlatformCPE string } func (p pkgCandidate) String() string { - if p.PlatformCPE == nil { + if p.PlatformCPE == "" { return fmt.Sprintf("%s|%s|%s", p.Vendor, p.Product, p.TargetSoftware) } - return fmt.Sprintf("%s|%s|%s|%s", p.Vendor, p.Product, p.TargetSoftware, *p.PlatformCPE) + return fmt.Sprintf("%s|%s|%s|%s", p.Vendor, p.Product, p.TargetSoftware, p.PlatformCPE) } -func newPkgCandidate(match nvd.CpeMatch, platformCPE *string) (*pkgCandidate, error) { +func newPkgCandidate(match nvd.CpeMatch, platformCPE string) (*pkgCandidate, error) { // we are only interested in packages that are vulnerable (not related to secondary match conditioning) if !match.Vulnerable { return nil, nil @@ -62,16 +62,16 @@ func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { return set } -func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) []*pkgCandidate { +func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { nodes := c.Nodes - var result []*pkgCandidate + result := false /* Turn a configuration like this: (AND (redis <= 6.2 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) Into a configuration like this: (OR (AND redis <= 6.1 debian:8) (AND redis <= 6.1 debian:9) (AND redis <= 6.1 ubuntu:19) (AND redis <= 6.1 ubuntu:20)) */ - if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { + if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And && len(nodes[0].CpeMatch) == 1 { matches := nodes[1].CpeMatch applicationNode := nodes[0].CpeMatch[0] for _, maybePlatform := range matches { @@ -79,19 +79,20 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) []*pkg // Something stupid is happening. Every time this loop steps, I'm overwriting // the zeroth member of the set. platform := maybePlatform.Criteria - candidate, err := newPkgCandidate(applicationNode, &platform) + candidate, err := newPkgCandidate(applicationNode, platform) if err != nil || candidate == nil { continue } set.Add(*candidate, nodes[0].CpeMatch[0]) + result = true } } return result } -func determinePlatformCPEAndNodes(c nvd.Configuration) (*string, []nvd.Node) { - var platformCPE *string +func determinePlatformCPEAndNodes(c nvd.Configuration) (string, []nvd.Node) { + var platformCPE string nodes := c.Nodes // Only retrieve a platform CPE in very specific cases @@ -125,7 +126,7 @@ func determinePlatformCPEAndNodes(c nvd.Configuration) (*string, []nvd.Node) { CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"}] CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"}] */ - platformCPE = &nodes[1].CpeMatch[0].Criteria + platformCPE = nodes[1].CpeMatch[0].Criteria nodes = []nvd.Node{nodes[0]} } } @@ -140,32 +141,31 @@ func _findUniquePkgs(set uniquePkgTracker, c nvd.Configuration) { return } - platformPackageCandidates(set, c) + if platformPackageCandidates(set, c) { + return + } - //platformCPE, nodes := determinePlatformCPEAndNodes(c) + platformCPE, nodes := determinePlatformCPEAndNodes(c) // //// TODO: this needs to loop also the other way; //// we need to be able to represent a single package //// that has multiple platforms, //// probably by - //for _, node := range nodes { - // for _, match := range node.CpeMatch { - // candidate, err := newPkgCandidate(match, platformCPE) - // if err != nil { - // // Do not halt all execution because of being unable to create - // // a PkgCandidate. This can happen when a CPE is invalid which - // // could avoid creating a database - // log.Debugf("unable processing uniquePkg: %v", err) - // continue - // } - // if candidate != nil { - // set.Add(*candidate, match) - // } - // } - //} - //for _, u := range platformPackageCandidates(c) { - // set.Add(*) - //} + for _, node := range nodes { + for _, match := range node.CpeMatch { + candidate, err := newPkgCandidate(match, platformCPE) + if err != nil { + // Do not halt all execution because of being unable to create + // a PkgCandidate. This can happen when a CPE is invalid which + // could avoid creating a database + log.Debugf("unable processing uniquePkg: %v", err) + continue + } + if candidate != nil { + set.Add(*candidate, match) + } + } + } } func buildConstraints(matches []nvd.CpeMatch) string { diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index 2e0c4548..fb209cae 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -1,9 +1,10 @@ package nvd import ( + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" "github.com/sergi/go-diff/diffmatchpatch" @@ -282,36 +283,37 @@ func TestFindUniquePkgs(t *testing.T) { }, }, }, + // TODO: why is this adding nils? expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"), + PlatformCPE: "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"), + PlatformCPE: "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", }, }), }, @@ -334,7 +336,6 @@ func TestFindUniquePkgs(t *testing.T) { } }) } - } func strRef(s string) *string { @@ -438,7 +439,6 @@ func TestBuildConstraints(t *testing.T) { } }) } - } func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { @@ -447,31 +447,31 @@ func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"), + PlatformCPE: "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"), + PlatformCPE: "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", }, { Product: "redis", Vendor: "redis", TargetSoftware: ANY, - PlatformCPE: strRef("cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"), + PlatformCPE: "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", }, } cpeMatch := nvd.CpeMatch{ From c8fc7dbc7d0eb337b764ad27be73a6eae079f1e3 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Thu, 23 Nov 2023 06:39:03 -0500 Subject: [PATCH 04/14] add transform test for new platform code Signed-off-by: Will Murphy --- .../nvd/test-fixtures/cve-2020-10729.json | 166 +++++++++++++++ .../nvd/test-fixtures/cve-2022-0543.json | 183 +++++++++++++++++ .../v5/transformers/nvd/transform_test.go | 192 ++++++++++++++++++ 3 files changed, 541 insertions(+) create mode 100644 pkg/process/v5/transformers/nvd/test-fixtures/cve-2020-10729.json create mode 100644 pkg/process/v5/transformers/nvd/test-fixtures/cve-2022-0543.json diff --git a/pkg/process/v5/transformers/nvd/test-fixtures/cve-2020-10729.json b/pkg/process/v5/transformers/nvd/test-fixtures/cve-2020-10729.json new file mode 100644 index 00000000..89677497 --- /dev/null +++ b/pkg/process/v5/transformers/nvd/test-fixtures/cve-2020-10729.json @@ -0,0 +1,166 @@ +{ + "cve": { + "id": "CVE-2020-10729", + "sourceIdentifier": "secalert@redhat.com", + "published": "2021-05-27T19:15:07.880", + "lastModified": "2021-12-10T19:57:06.357", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "A flaw was found in the use of insufficiently random values in Ansible. Two random password lookups of the same length generate the equal value as the template caching action for the same file since no re-evaluation happens. The highest threat from this vulnerability would be that all passwords are exposed at once for the file. This flaw affects Ansible Engine versions before 2.9.6." + }, + { + "lang": "es", + "value": "Se encontró un fallo en el uso de valores insuficientemente aleatorios en Ansible. Dos búsquedas de contraseñas aleatorias de la misma longitud generan el mismo valor que la acción de almacenamiento en caché de la plantilla para el mismo archivo, ya que no se realiza una reevaluación. La mayor amenaza de esta vulnerabilidad sería que todas las contraseñas estén expuestas a la vez para el archivo. Este fallo afecta a Ansible Engine versiones anteriores a 2.9.6" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + "attackVector": "LOCAL", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 5.5, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 1.8, + "impactScore": 3.6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:L/AC:L/Au:N/C:P/I:N/A:N", + "accessVector": "LOCAL", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "PARTIAL", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 2.1 + }, + "baseSeverity": "LOW", + "exploitabilityScore": 3.9, + "impactScore": 2.9, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-330" + } + ] + }, + { + "source": "secalert@redhat.com", + "type": "Secondary", + "description": [ + { + "lang": "en", + "value": "CWE-330" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*", + "versionEndExcluding": "2.9.6", + "matchCriteriaId": "EDFA8005-6FBE-4032-A499-608B7FA34F56" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:redhat:enterprise_linux:7.0:*:*:*:*:*:*:*", + "matchCriteriaId": "142AD0DD-4CF3-4D74-9442-459CE3347E3A" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:redhat:enterprise_linux:8.0:*:*:*:*:*:*:*", + "matchCriteriaId": "F4CFF558-3C47-480D-A2F0-BABF26042943" + } + ] + } + ] + }, + { + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://bugzilla.redhat.com/show_bug.cgi?id=1831089", + "source": "secalert@redhat.com", + "tags": [ + "Issue Tracking", + "Vendor Advisory" + ] + }, + { + "url": "https://github.com/ansible/ansible/issues/34144", + "source": "secalert@redhat.com", + "tags": [ + "Exploit", + "Issue Tracking", + "Third Party Advisory" + ] + }, + { + "url": "https://www.debian.org/security/2021/dsa-4950", + "source": "secalert@redhat.com", + "tags": [ + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v5/transformers/nvd/test-fixtures/cve-2022-0543.json b/pkg/process/v5/transformers/nvd/test-fixtures/cve-2022-0543.json new file mode 100644 index 00000000..09d5c187 --- /dev/null +++ b/pkg/process/v5/transformers/nvd/test-fixtures/cve-2022-0543.json @@ -0,0 +1,183 @@ +{ + "cve": { + "id": "CVE-2022-0543", + "sourceIdentifier": "security@debian.org", + "published": "2022-02-18T20:15:17.583", + "lastModified": "2023-09-29T15:55:24.533", + "vulnStatus": "Analyzed", + "cisaExploitAdd": "2022-03-28", + "cisaActionDue": "2022-04-18", + "cisaRequiredAction": "Apply updates per vendor instructions.", + "cisaVulnerabilityName": "Debian-specific Redis Server Lua Sandbox Escape Vulnerability", + "descriptions": [ + { + "lang": "en", + "value": "It was discovered, that redis, a persistent key-value database, due to a packaging issue, is prone to a (Debian-specific) Lua sandbox escape, which could result in remote code execution." + }, + { + "lang": "es", + "value": "Se ha detectado que redis, una base de datos persistente de valores clave, debido a un problema de empaquetado, es propenso a un escape del sandbox de Lua (específico de Debian), que podría resultar en una ejecución de código remota" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "NONE", + "userInteraction": "NONE", + "scope": "CHANGED", + "confidentialityImpact": "HIGH", + "integrityImpact": "HIGH", + "availabilityImpact": "HIGH", + "baseScore": 10, + "baseSeverity": "CRITICAL" + }, + "exploitabilityScore": 3.9, + "impactScore": 6 + } + ], + "cvssMetricV2": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "2.0", + "vectorString": "AV:N/AC:L/Au:N/C:C/I:C/A:C", + "accessVector": "NETWORK", + "accessComplexity": "LOW", + "authentication": "NONE", + "confidentialityImpact": "COMPLETE", + "integrityImpact": "COMPLETE", + "availabilityImpact": "COMPLETE", + "baseScore": 10 + }, + "baseSeverity": "HIGH", + "exploitabilityScore": 10, + "impactScore": 10, + "acInsufInfo": false, + "obtainAllPrivilege": false, + "obtainUserPrivilege": false, + "obtainOtherPrivilege": false, + "userInteractionRequired": false + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-862" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + "matchCriteriaId": "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + "matchCriteriaId": "902B8056-9E37-443B-8905-8AA93E2447FB" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + "matchCriteriaId": "3D94DA3B-FA74-4526-A0A0-A872684598C6" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + "matchCriteriaId": "DEECE5FC-CACF-4496-A3E7-164736409252" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + "matchCriteriaId": "07B237A9-69A3-4A9C-9DA0-4E06BD37AE73" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + "matchCriteriaId": "FA6FEEC2-9F11-4643-8827-749718254FED" + } + ] + } + ] + } + ], + "references": [ + { + "url": "http://packetstormsecurity.com/files/166885/Redis-Lua-Sandbox-Escape.html", + "source": "security@debian.org", + "tags": [ + "Exploit", + "Third Party Advisory", + "VDB Entry" + ] + }, + { + "url": "https://bugs.debian.org/1005787", + "source": "security@debian.org", + "tags": [ + "Issue Tracking", + "Patch", + "Third Party Advisory" + ] + }, + { + "url": "https://lists.debian.org/debian-security-announce/2022/msg00048.html", + "source": "security@debian.org", + "tags": [ + "Mailing List", + "Third Party Advisory" + ] + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220331-0004/", + "source": "security@debian.org", + "tags": [ + "Third Party Advisory" + ] + }, + { + "url": "https://www.debian.org/security/2022/dsa-5081", + "source": "security@debian.org", + "tags": [ + "Mailing List", + "Third Party Advisory" + ] + }, + { + "url": "https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce", + "source": "security@debian.org", + "tags": [ + "Third Party Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v5/transformers/nvd/transform_test.go b/pkg/process/v5/transformers/nvd/transform_test.go index f7f03621..a4bb9e32 100644 --- a/pkg/process/v5/transformers/nvd/transform_test.go +++ b/pkg/process/v5/transformers/nvd/transform_test.go @@ -324,6 +324,198 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { }, }, }, + { + name: "CVE-2022-0543 multiple platforms", + numEntries: 1, + fixture: "test-fixtures/cve-2022-0543.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2022-0543", + PackageName: "redis", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*", + }}, + VersionConstraint: "", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + { + ID: "CVE-2022-0543", + PackageName: "redis", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*", + }}, + VersionConstraint: "", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + { + ID: "CVE-2022-0543", + PackageName: "redis", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*", + }}, + VersionConstraint: "", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + { + ID: "CVE-2022-0543", + PackageName: "redis", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*", + }}, + VersionConstraint: "", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + { + ID: "CVE-2022-0543", + PackageName: "redis", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*", + }}, + VersionConstraint: "", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2022-0543", + Namespace: "nvd:cpe", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2022-0543", + RecordSource: "nvdv2:nvdv2:cves", + Severity: "Critical", + URLs: []string{ + "http://packetstormsecurity.com/files/166885/Redis-Lua-Sandbox-Escape.html", + "https://bugs.debian.org/1005787", + "https://lists.debian.org/debian-security-announce/2022/msg00048.html", + "https://security.netapp.com/advisory/ntap-20220331-0004/", + "https://www.debian.org/security/2022/dsa-5081", + "https://www.ubercomp.com/posts/2022-01-20_redis_on_debian_rce", + }, + Description: "It was discovered, that redis, a persistent key-value database, due to a packaging issue, is prone to a (Debian-specific) Lua sandbox escape, which could result in remote code execution.", + Cvss: []grypeDB.Cvss{ + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(10, 10, 10), + Vector: "AV:N/AC:L/Au:N/C:C/I:C/A:C", + Version: "2.0", + Source: "nvd@nist.gov", + Type: "Primary", + }, + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(10, 3.9, 6), + Vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", + Version: "3.1", + Source: "nvd@nist.gov", + Type: "Primary", + }, + }, + }, + }, + { + name: "CVE-2020-10729 multiple platforms omitted top level config", + numEntries: 1, + fixture: "test-fixtures/cve-2020-10729.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2020-10729", + PackageName: "ansible_engine", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:redhat:enterprise_linux:7.0:*:*:*:*:*:*:*", + }}, + VersionConstraint: "< 2.9.6", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + { + ID: "CVE-2020-10729", + PackageName: "ansible_engine", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:redhat:enterprise_linux:8.0:*:*:*:*:*:*:*", + }}, + VersionConstraint: "< 2.9.6", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:redhat:ansible_engine:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{State: "unknown"}, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2020-10729", + Namespace: "nvd:cpe", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2020-10729", + RecordSource: "nvdv2:nvdv2:cves", + Severity: "Medium", + URLs: []string{ + "https://bugzilla.redhat.com/show_bug.cgi?id=1831089", + "https://github.com/ansible/ansible/issues/34144", + "https://www.debian.org/security/2021/dsa-4950", + }, + Description: "A flaw was found in the use of insufficiently random values in Ansible. Two random password lookups of the same length generate the equal value as the template caching action for the same file since no re-evaluation happens. The highest threat from this vulnerability would be that all passwords are exposed at once for the file. This flaw affects Ansible Engine versions before 2.9.6.", + Cvss: []grypeDB.Cvss{ + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics( + 2.1, + 3.9, + 2.9, + ), + Vector: "AV:L/AC:L/Au:N/C:P/I:N/A:N", + Version: "2.0", + Source: "nvd@nist.gov", + Type: "Primary", + }, + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics( + 5.5, + 1.8, + 3.6, + ), + Vector: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N", + Version: "3.1", + Source: "nvd@nist.gov", + Type: "Primary", + }, + }, + }, + }, } for _, test := range tests { From fcff4115288a8be56c14010b0ee65ab262f922a9 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Thu, 23 Nov 2023 06:42:15 -0500 Subject: [PATCH 05/14] lint-fix Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 4 ++-- pkg/process/v5/transformers/nvd/unique_pkg_test.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index ad40af87..488ecdf0 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -4,10 +4,11 @@ import ( "fmt" "strings" + "github.com/umisama/go-cpe" + "github.com/anchore/grype-db/internal/log" "github.com/anchore/grype-db/pkg/process/common" "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" - "github.com/umisama/go-cpe" ) const ( @@ -86,7 +87,6 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { set.Add(*candidate, nodes[0].CpeMatch[0]) result = true } - } return result } diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index fb209cae..576cb504 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -3,11 +3,10 @@ package nvd import ( "testing" + "github.com/sergi/go-diff/diffmatchpatch" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/sergi/go-diff/diffmatchpatch" - "github.com/anchore/grype-db/pkg/provider/unmarshal/nvd" ) From d32cfdfd518ab07a3634952c2dd75c8a3520592a Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 24 Nov 2023 07:40:47 -0500 Subject: [PATCH 06/14] handle either order on nodes Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 46 +++++++++++++------ .../v5/transformers/nvd/unique_pkg_test.go | 23 +++++----- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 488ecdf0..15df44f0 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -68,24 +68,42 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { result := false /* Turn a configuration like this: - (AND (redis <= 6.2 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) + (AND (redis <= 6.1 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) Into a configuration like this: (OR (AND redis <= 6.1 debian:8) (AND redis <= 6.1 debian:9) (AND redis <= 6.1 ubuntu:19) (AND redis <= 6.1 ubuntu:20)) */ - if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And && len(nodes[0].CpeMatch) == 1 { - matches := nodes[1].CpeMatch - applicationNode := nodes[0].CpeMatch[0] - for _, maybePlatform := range matches { - // TODO: I'm overwriting a pointer or something? - // Something stupid is happening. Every time this loop steps, I'm overwriting - // the zeroth member of the set. - platform := maybePlatform.Criteria - candidate, err := newPkgCandidate(applicationNode, platform) - if err != nil || candidate == nil { - continue + if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { + var platformsNode nvd.Node + var applicationNode nvd.Node + for _, n := range nodes { + for _, c := range n.CpeMatch { + if strings.HasPrefix(c.Criteria, "cpe:2.3:a") { + applicationNode = n + break + } + if strings.HasPrefix(c.Criteria, "cpe:2.3:o") { + platformsNode = n + break + } + } + } + if platformsNode.Operator != nvd.Or || len(platformsNode.CpeMatch) < 2 { + return false + } + if applicationNode.Operator != nvd.Or { + return false + } + matches := platformsNode.CpeMatch + for _, application := range applicationNode.CpeMatch { + for _, maybePlatform := range matches { + platform := maybePlatform.Criteria + candidate, err := newPkgCandidate(application, platform) + if err != nil || candidate == nil { + continue + } + set.Add(*candidate, application) + result = true } - set.Add(*candidate, nodes[0].CpeMatch[0]) - result = true } } return result diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index 576cb504..d6bef72c 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -239,17 +239,6 @@ func TestFindUniquePkgs(t *testing.T) { name: "cpe with multiple platforms", operator: operatorRef(nvd.And), nodes: []nvd.Node{ - { - Negate: boolPtr(false), - Operator: nvd.Or, - CpeMatch: []nvd.CpeMatch{ - { - Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", - MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", - Vulnerable: true, - }, - }, - }, { Negate: boolPtr(false), Operator: nvd.Or, @@ -281,8 +270,18 @@ func TestFindUniquePkgs(t *testing.T) { }, }, }, + { + Negate: boolPtr(false), + Operator: nvd.Or, + CpeMatch: []nvd.CpeMatch{ + { + Criteria: "cpe:2.3:a:redis:redis:-:*:*:*:*:*:*:*", + MatchCriteriaID: "5EBE5E1C-C881-4A76-9E36-4FB7C48427E6", + Vulnerable: true, + }, + }, + }, }, - // TODO: why is this adding nils? expected: newUniquePkgTrackerFromSlice([]pkgCandidate{ { Product: "redis", From 0318e9957a282b8627360a9a45c9032503e81a74 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Mon, 27 Nov 2023 07:31:11 -0500 Subject: [PATCH 07/14] clean up logging and comments Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 44 +++---------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 15df44f0..944a10c8 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -98,7 +98,11 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { for _, maybePlatform := range matches { platform := maybePlatform.Criteria candidate, err := newPkgCandidate(application, platform) - if err != nil || candidate == nil { + if err != nil { + log.Debugf("unable processing uniquePkg with multiple platforms: %v", err) + continue + } + if candidate == nil { continue } set.Add(*candidate, application) @@ -113,44 +117,13 @@ func determinePlatformCPEAndNodes(c nvd.Configuration) (string, []nvd.Node) { var platformCPE string nodes := c.Nodes - // Only retrieve a platform CPE in very specific cases - // WILL - I need to figure out what these if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { - if len(nodes[1].CpeMatch) == 1 && !nodes[1].CpeMatch[0].Vulnerable { // WILL: this is false for the record in question - // Here's what I think is happening: - // Right now, if there is _exactly one_ platform - // we set that platform on the vuln record we emit - // but if there is more than one, we just punt - // and say, "who knows; lots of platforms." - // instead, we should figure out how to or together the platforms - // and emit the fewest number of rows that covers all cases. - // - /* - - TODAY: - sqlite> select id, package_name, namespace, package_qualifiers, version_constraint from vulnerability where id like 'CVE-2022-0543'; - id package_name namespace package_qualifiers version_constraint - ------------- ------------ ------------------------------------ ------------------ --------------------- - CVE-2022-0543 redis nvd:cpe - - This should really look more like: - - sqlite> select id, package_name, namespace, package_qualifiers, version_constraint from vulnerability where id like 'CVE-2022-0543'; - id package_name namespace package_qualifiers version_constraint - ------------- ------------ ------------------------------------ ------------------ --------------------- - CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:canonical:ubuntu_linux:20.04:*:*:*:lts:*:*:*"}] - CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:canonical:ubuntu_linux:21.10:*:*:*:-:*:*:*"}] - CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:9.0:*:*:*:*:*:*:*"}] - CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:10.0:*:*:*:*:*:*:*"}] - CVE-2022-0543 redis nvd:cpe [{"kind":"platform-cpe","cpe":"cpe:2.3:o:debian:debian_linux:11.0:*:*:*:*:*:*:*"}] - */ + if len(nodes[1].CpeMatch) == 1 && !nodes[1].CpeMatch[0].Vulnerable { platformCPE = nodes[1].CpeMatch[0].Criteria nodes = []nvd.Node{nodes[0]} } } - // This needs to return multiple nodes? - return platformCPE, nodes } @@ -164,11 +137,6 @@ func _findUniquePkgs(set uniquePkgTracker, c nvd.Configuration) { } platformCPE, nodes := determinePlatformCPEAndNodes(c) - // - //// TODO: this needs to loop also the other way; - //// we need to be able to represent a single package - //// that has multiple platforms, - //// probably by for _, node := range nodes { for _, match := range node.CpeMatch { candidate, err := newPkgCandidate(match, platformCPE) From da4a2cdcbb3b8cad820e1f48493608a133cc799e Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Mon, 27 Nov 2023 07:47:31 -0500 Subject: [PATCH 08/14] linter suppress Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 944a10c8..9b88091e 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -63,6 +63,7 @@ func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { return set } +// nolint:gocognit func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { nodes := c.Nodes result := false From 1ea65fd1a46285bc25a6b658ae496784e4904466 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 28 Nov 2023 09:51:58 -0500 Subject: [PATCH 09/14] refactor: address formatting comments Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 9b88091e..42093bf6 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -63,57 +63,67 @@ func findUniquePkgs(cfgs ...nvd.Configuration) uniquePkgTracker { return set } -// nolint:gocognit func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { nodes := c.Nodes - result := false /* Turn a configuration like this: (AND (redis <= 6.1 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) Into a configuration like this: (OR (AND redis <= 6.1 debian:8) (AND redis <= 6.1 debian:9) (AND redis <= 6.1 ubuntu:19) (AND redis <= 6.1 ubuntu:20)) */ - if len(nodes) == 2 && c.Operator != nil && *c.Operator == nvd.And { - var platformsNode nvd.Node - var applicationNode nvd.Node - for _, n := range nodes { - for _, c := range n.CpeMatch { - if strings.HasPrefix(c.Criteria, "cpe:2.3:a") { - applicationNode = n - break - } - if strings.HasPrefix(c.Criteria, "cpe:2.3:o") { - platformsNode = n - break - } + if len(nodes) != 2 || c.Operator == nil || *c.Operator != nvd.And { + return false + } + var platformsNode nvd.Node + var applicationNode nvd.Node + for _, n := range nodes { + for _, c := range n.CpeMatch { + if isApplicationCPE(c) { + applicationNode = n + break + } + if isPlatformCPE(c) { + platformsNode = n + break } } - if platformsNode.Operator != nvd.Or || len(platformsNode.CpeMatch) < 2 { - return false - } - if applicationNode.Operator != nvd.Or { - return false - } - matches := platformsNode.CpeMatch - for _, application := range applicationNode.CpeMatch { - for _, maybePlatform := range matches { - platform := maybePlatform.Criteria - candidate, err := newPkgCandidate(application, platform) - if err != nil { - log.Debugf("unable processing uniquePkg with multiple platforms: %v", err) - continue - } - if candidate == nil { - continue - } - set.Add(*candidate, application) - result = true + } + if platformsNode.Operator != nvd.Or || len(platformsNode.CpeMatch) < 2 { + return false + } + if applicationNode.Operator != nvd.Or { + return false + } + result := false + matches := platformsNode.CpeMatch + for _, application := range applicationNode.CpeMatch { + for _, maybePlatform := range matches { + platform := maybePlatform.Criteria + candidate, err := newPkgCandidate(application, platform) + if err != nil { + log.Debugf("unable processing uniquePkg with multiple platforms: %v", err) + continue + } + if candidate == nil { + continue } + set.Add(*candidate, application) + result = true } } return result } +func isApplicationCPE(c nvd.CpeMatch) bool { + parts := strings.Split(c.Criteria, ":") + return len(parts) >= 3 && parts[0] == "cpe" && parts[2] == "a" +} + +func isPlatformCPE(c nvd.CpeMatch) bool { + parts := strings.Split(c.Criteria, ":") + return len(parts) >= 3 && parts[0] == "cpe" && parts[2] == "o" +} + func determinePlatformCPEAndNodes(c nvd.Configuration) (string, []nvd.Node) { var platformCPE string nodes := c.Nodes From f3d2951bdeba10b48c34455476771392aac3223a Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 1 Dec 2023 08:18:53 -0500 Subject: [PATCH 10/14] update comment to use cpes Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 42093bf6..b6fed32a 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -67,9 +67,19 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { nodes := c.Nodes /* Turn a configuration like this: - (AND (redis <= 6.1 (OR debian:8 debian:9 ubuntu:19 ubuntu:20)) + (AND + (OR (cpe:2.3:a:redis:...whatever) (cpe:2.3.:something:...whatever) + (OR (cpe:2.3:o:debian:9....) (cpe:2.3:o:ubuntu:22..)) + ) Into a configuration like this: - (OR (AND redis <= 6.1 debian:8) (AND redis <= 6.1 debian:9) (AND redis <= 6.1 ubuntu:19) (AND redis <= 6.1 ubuntu:20)) + (OR + (AND (cpe:2.3:a:redis:...whatever) (cpe:2.3:o:debian:9...)) + (AND (cpe:2.3:a:redis:...whatever) (cpe:2.3:o:ubuntu:22...)) + (AND (cpe:2.3:a:something:...whatever) (cpe:2.3:o:debian:9...)) + (AND (cpe:2.3:a:something:...whatever) (cpe:2.3:o:ubuntu:22...)) + ) + Because in schema v5, rows in Grype DB can only have zero or one platform CPE + constraint. */ if len(nodes) != 2 || c.Operator == nil || *c.Operator != nvd.And { return false From 98e9e4f2e6422351ad9e73d6f15a210dfb241eaa Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Mon, 4 Dec 2023 11:13:05 -0500 Subject: [PATCH 11/14] Account for applications that are platforms For example, Redis or OpenShift might be coded as a type "a" CPE (for "application"), but might be a platform (displayed in the "running on or with" section of the NVD UI). For these, consider them platforms and emit a platform CPE. Signed-off-by: Will Murphy --- ...ltiple-platforms-with-application-cpe.json | 142 ++++++++++++++++++ .../v5/transformers/nvd/transform_test.go | 71 +++++++++ pkg/process/v5/transformers/nvd/unique_pkg.go | 34 +++-- 3 files changed, 232 insertions(+), 15 deletions(-) create mode 100644 pkg/process/v5/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json diff --git a/pkg/process/v5/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json b/pkg/process/v5/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json new file mode 100644 index 00000000..40c0fa56 --- /dev/null +++ b/pkg/process/v5/transformers/nvd/test-fixtures/multiple-platforms-with-application-cpe.json @@ -0,0 +1,142 @@ +{ + "cve": { + "id": "CVE-2023-38733", + "sourceIdentifier": "psirt@us.ibm.com", + "published": "2023-08-22T22:15:08.460", + "lastModified": "2023-08-26T02:25:42.957", + "vulnStatus": "Analyzed", + "descriptions": [ + { + "lang": "en", + "value": "\nIBM Robotic Process Automation 21.0.0 through 21.0.7.1 and 23.0.0 through 23.0.1 server could allow an authenticated user to view sensitive information from installation logs. IBM X-Force Id: 262293.\n\n" + } + ], + "metrics": { + "cvssMetricV31": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "LOW", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 4.3, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 2.8, + "impactScore": 1.4 + }, + { + "source": "psirt@us.ibm.com", + "type": "Secondary", + "cvssData": { + "version": "3.1", + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + "attackVector": "NETWORK", + "attackComplexity": "LOW", + "privilegesRequired": "LOW", + "userInteraction": "NONE", + "scope": "UNCHANGED", + "confidentialityImpact": "LOW", + "integrityImpact": "NONE", + "availabilityImpact": "NONE", + "baseScore": 4.3, + "baseSeverity": "MEDIUM" + }, + "exploitabilityScore": 2.8, + "impactScore": 1.4 + } + ] + }, + "weaknesses": [ + { + "source": "nvd@nist.gov", + "type": "Primary", + "description": [ + { + "lang": "en", + "value": "CWE-532" + } + ] + }, + { + "source": "psirt@us.ibm.com", + "type": "Secondary", + "description": [ + { + "lang": "en", + "value": "CWE-532" + } + ] + } + ], + "configurations": [ + { + "operator": "AND", + "nodes": [ + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": true, + "criteria": "cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*", + "versionStartIncluding": "21.0.0", + "versionEndIncluding": "21.0.7.3", + "matchCriteriaId": "DDF503DD-23DC-4B22-8873-BE94BF0F1CD1" + }, + { + "vulnerable": true, + "criteria": "cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*", + "versionStartIncluding": "23.0.0", + "versionEndIncluding": "23.0.3", + "matchCriteriaId": "F513AA2B-F457-408B-8D5F-EBE657439000" + } + ] + }, + { + "operator": "OR", + "negate": false, + "cpeMatch": [ + { + "vulnerable": false, + "criteria": "cpe:2.3:a:redhat:openshift:-:*:*:*:*:*:*:*", + "matchCriteriaId": "F08E234C-BDCF-4B41-87B9-96BD5578CBBF" + }, + { + "vulnerable": false, + "criteria": "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + "matchCriteriaId": "A2572D17-1DE6-457B-99CC-64AFD54487EA" + } + ] + } + ] + } + ], + "references": [ + { + "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/262293", + "source": "psirt@us.ibm.com", + "tags": [ + "VDB Entry", + "Vendor Advisory" + ] + }, + { + "url": "https://www.ibm.com/support/pages/node/7028223", + "source": "psirt@us.ibm.com", + "tags": [ + "Patch", + "Vendor Advisory" + ] + } + ] + } +} diff --git a/pkg/process/v5/transformers/nvd/transform_test.go b/pkg/process/v5/transformers/nvd/transform_test.go index a4bb9e32..493d10b7 100644 --- a/pkg/process/v5/transformers/nvd/transform_test.go +++ b/pkg/process/v5/transformers/nvd/transform_test.go @@ -516,6 +516,77 @@ func TestParseAllNVDVulnerabilityEntries(t *testing.T) { }, }, }, + { + name: "multiple platforms some are application", + numEntries: 2, + fixture: "test-fixtures/multiple-platforms-with-application-cpe.json", + vulns: []grypeDB.Vulnerability{ + { + ID: "CVE-2023-38733", + PackageName: "robotic_process_automation", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:a:redhat:openshift:-:*:*:*:*:*:*:*", + }}, + VersionConstraint: ">= 21.0.0, <= 21.0.7.3 || >= 23.0.0, <= 23.0.3", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{ + State: "unknown", + }, + Advisories: nil, + }, + { + ID: "CVE-2023-38733", + PackageName: "robotic_process_automation", + Namespace: "nvd:cpe", + PackageQualifiers: []qualifier.Qualifier{platformcpe.Qualifier{ + Kind: "platform-cpe", + CPE: "cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*", + }}, + VersionConstraint: ">= 21.0.0, <= 21.0.7.3 || >= 23.0.0, <= 23.0.3", + VersionFormat: "unknown", + CPEs: []string{"cpe:2.3:a:ibm:robotic_process_automation:*:*:*:*:*:*:*:*"}, + RelatedVulnerabilities: nil, + Fix: grypeDB.Fix{ + State: "unknown", + }, + Advisories: nil, + }, + }, + metadata: grypeDB.VulnerabilityMetadata{ + ID: "CVE-2023-38733", + Namespace: "nvd:cpe", + DataSource: "https://nvd.nist.gov/vuln/detail/CVE-2023-38733", + RecordSource: "nvdv2:nvdv2:cves", + Severity: "Medium", + URLs: []string{ + "https://exchange.xforce.ibmcloud.com/vulnerabilities/262293", + "https://www.ibm.com/support/pages/node/7028223", + }, + Description: "\nIBM Robotic Process Automation 21.0.0 through 21.0.7.1 and 23.0.0 through 23.0.1 server could allow an authenticated user to view sensitive information from installation logs. IBM X-Force Id: 262293.\n\n", + Cvss: []grypeDB.Cvss{ + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(4.3, 2.8, 1.4), + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + Version: "3.1", + Source: "nvd@nist.gov", + Type: "Primary", + }, + { + VendorMetadata: nil, + Metrics: grypeDB.NewCvssMetrics(4.3, 2.8, 1.4), + Vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N", + Version: "3.1", + Source: "psirt@us.ibm.com", + Type: "Secondary", + }, + }, + }, + }, } for _, test := range tests { diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index b6fed32a..6594fe06 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -87,15 +87,11 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { var platformsNode nvd.Node var applicationNode nvd.Node for _, n := range nodes { - for _, c := range n.CpeMatch { - if isApplicationCPE(c) { - applicationNode = n - break - } - if isPlatformCPE(c) { - platformsNode = n - break - } + if allCPEsVulnerable(n) { + applicationNode = n + } + if noCPEsVulnerable(n) { + platformsNode = n } } if platformsNode.Operator != nvd.Or || len(platformsNode.CpeMatch) < 2 { @@ -124,14 +120,22 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { return result } -func isApplicationCPE(c nvd.CpeMatch) bool { - parts := strings.Split(c.Criteria, ":") - return len(parts) >= 3 && parts[0] == "cpe" && parts[2] == "a" +func allCPEsVulnerable(node nvd.Node) bool { + for _, c := range node.CpeMatch { + if !c.Vulnerable { + return false + } + } + return true } -func isPlatformCPE(c nvd.CpeMatch) bool { - parts := strings.Split(c.Criteria, ":") - return len(parts) >= 3 && parts[0] == "cpe" && parts[2] == "o" +func noCPEsVulnerable(node nvd.Node) bool { + for _, c := range node.CpeMatch { + if c.Vulnerable { + return false + } + } + return true } func determinePlatformCPEAndNodes(c nvd.Configuration) (string, []nvd.Node) { From 6121268e48b963720d4476edb883b5bc6394e2e9 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Mon, 4 Dec 2023 17:06:24 -0500 Subject: [PATCH 12/14] chore: additional tests for new multi-platform-cpe logic Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg.go | 13 ++ .../v5/transformers/nvd/unique_pkg_test.go | 148 ++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg.go b/pkg/process/v5/transformers/nvd/unique_pkg.go index 6594fe06..7e01c019 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg.go @@ -87,6 +87,9 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { var platformsNode nvd.Node var applicationNode nvd.Node for _, n := range nodes { + if anyHardwareCPEPresent(n) { + return false + } if allCPEsVulnerable(n) { applicationNode = n } @@ -120,6 +123,16 @@ func platformPackageCandidates(set uniquePkgTracker, c nvd.Configuration) bool { return result } +func anyHardwareCPEPresent(n nvd.Node) bool { + for _, c := range n.CpeMatch { + parts := strings.Split(c.Criteria, ":") + if len(parts) < 3 || parts[2] == "h" { + return true + } + } + return false +} + func allCPEsVulnerable(node nvd.Node) bool { for _, c := range node.CpeMatch { if !c.Vulnerable { diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index d6bef72c..bae77d06 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -3,6 +3,7 @@ package nvd import ( "testing" + "github.com/google/go-cmp/cmp" "github.com/sergi/go-diff/diffmatchpatch" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -489,3 +490,150 @@ func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { } assert.Len(t, tracker, len(candidates)) } + +func TestPlatformPackageCandidates(t *testing.T) { + // TODO: guard against `h` type CPEs getting in + type testCase struct { + name string + config nvd.Configuration + wantChanged bool + wantSet uniquePkgTracker + } + tests := []testCase{ + { + name: "application X platform", + config: nvd.Configuration{ + Negate: nil, + Nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:other-platform:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + }, + Operator: opRef(nvd.And), + }, + wantChanged: true, + wantSet: newUniquePkgTrackerFromSlice( + []pkgCandidate{ + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*"), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*"), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, "cpe:2.3:o:some-vendor:other-platform:*:*:*:*:*:*:*:*"), + mustNewPackage(t, nvd.CpeMatch{ + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, "cpe:2.3:o:some-vendor:other-platform:*:*:*:*:*:*:*:*"), + }, + ), + }, + { + name: "top-level OR is excluded", + config: nvd.Configuration{ + Operator: opRef(nvd.Or), + }, + wantChanged: false, + wantSet: newUniquePkgTracker(), + }, + { + name: "top-level nil op is excluded", + config: nvd.Configuration{ + Operator: nil, + }, + wantChanged: false, + }, + { + name: "single hardware node results in exclusion", + config: nvd.Configuration{ + Negate: nil, + Nodes: []nvd.Node{ + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:some-app:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: true, + Criteria: "cpe:2.3:a:some-vendor:other-app:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + { + CpeMatch: []nvd.CpeMatch{ + { + Vulnerable: false, + Criteria: "cpe:2.3:o:some-vendor:some-platform:*:*:*:*:*:*:*:*", + }, + { + Vulnerable: false, + Criteria: "cpe:2.3:h:some-vendor:some-device:*:*:*:*:*:*:*:*", + }, + }, + Negate: nil, + Operator: nvd.Or, + }, + }, + Operator: opRef(nvd.And), + }, + wantChanged: false, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + set := newUniquePkgTracker() + result := platformPackageCandidates(set, tc.config) + assert.Equal(t, result, tc.wantChanged) + if tc.wantSet == nil { + tc.wantSet = newUniquePkgTracker() + } + if diff := cmp.Diff(tc.wantSet.All(), set.All()); diff != "" { + t.Errorf("unexpected diff (-want +got)\n%s", diff) + } + }) + + } +} + +func opRef(op nvd.Operator) *nvd.Operator { + return &op +} + +func mustNewPackage(t *testing.T, match nvd.CpeMatch, platformCPE string) pkgCandidate { + p, err := newPkgCandidate(match, platformCPE) + require.NoError(t, err) + return *p +} From ade46ce3f5a4cf9c46ea7753a23695fc830efdc0 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 5 Dec 2023 10:41:53 -0500 Subject: [PATCH 13/14] chore: consolidate test helper functions Signed-off-by: Will Murphy --- .../v5/transformers/nvd/unique_pkg_test.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index bae77d06..86e996d8 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -20,12 +20,6 @@ func newUniquePkgTrackerFromSlice(candidates []pkgCandidate) uniquePkgTracker { } func TestFindUniquePkgs(t *testing.T) { - boolPtr := func(b bool) *bool { - return &b - } - operatorRef := func(o nvd.Operator) *nvd.Operator { - return &o - } tests := []struct { name string nodes []nvd.Node @@ -238,10 +232,10 @@ func TestFindUniquePkgs(t *testing.T) { }, { name: "cpe with multiple platforms", - operator: operatorRef(nvd.And), + operator: opRef(nvd.And), nodes: []nvd.Node{ { - Negate: boolPtr(false), + Negate: boolRef(false), Operator: nvd.Or, CpeMatch: []nvd.CpeMatch{ { @@ -272,7 +266,7 @@ func TestFindUniquePkgs(t *testing.T) { }, }, { - Negate: boolPtr(false), + Negate: boolRef(false), Operator: nvd.Or, CpeMatch: []nvd.CpeMatch{ { @@ -632,6 +626,10 @@ func opRef(op nvd.Operator) *nvd.Operator { return &op } +func boolRef(b bool) *bool { + return &b +} + func mustNewPackage(t *testing.T, match nvd.CpeMatch, platformCPE string) pkgCandidate { p, err := newPkgCandidate(match, platformCPE) require.NoError(t, err) From 11c9a1b4e05c6687157ee653366599fe4f767643 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 5 Dec 2023 11:18:29 -0500 Subject: [PATCH 14/14] chore: remove done todo Signed-off-by: Will Murphy --- pkg/process/v5/transformers/nvd/unique_pkg_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/process/v5/transformers/nvd/unique_pkg_test.go b/pkg/process/v5/transformers/nvd/unique_pkg_test.go index 86e996d8..a684ef90 100644 --- a/pkg/process/v5/transformers/nvd/unique_pkg_test.go +++ b/pkg/process/v5/transformers/nvd/unique_pkg_test.go @@ -486,7 +486,6 @@ func Test_UniquePackageTrackerHandlesOnlyPlatformDiff(t *testing.T) { } func TestPlatformPackageCandidates(t *testing.T) { - // TODO: guard against `h` type CPEs getting in type testCase struct { name string config nvd.Configuration