From bf58a14625f2a04cc281f5408f0be0acd49960b4 Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Thu, 25 May 2023 16:28:45 -0400 Subject: [PATCH 1/5] fix: update alpine matchers to no longer search by package distro we only want to match on cpe without sec db fixes Signed-off-by: Christopher Phillips --- grype/matcher/apk/matcher.go | 47 ++++-------------------------------- 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/grype/matcher/apk/matcher.go b/grype/matcher/apk/matcher.go index 2dc1145248f..1cd2f92b08d 100644 --- a/grype/matcher/apk/matcher.go +++ b/grype/matcher/apk/matcher.go @@ -27,11 +27,11 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa var matches = make([]match.Match, 0) // direct matches with package - directMatches, err := m.findApkPackage(store, d, p) + cpeMatches, err := m.cpeMatchesWithoutSecDBFixes(store, d, p) if err != nil { return nil, err } - matches = append(matches, directMatches...) + matches = append(matches, cpeMatches...) // indirect matches with package source indirectMatches, err := m.matchBySourceIndirection(store, d, p) @@ -101,21 +101,6 @@ cveLoop: return finalCpeMatches, nil } -func deduplicateMatches(secDBMatches, cpeMatches []match.Match) (matches []match.Match) { - // add additional unique matches from CPE source that is unique from the SecDB matches - secDBMatchesByID := matchesByID(secDBMatches) - cpeMatchesByID := matchesByID(cpeMatches) - for id, cpeMatchesForID := range cpeMatchesByID { - // by this point all matches have been verified to be vulnerable within the given package version relative to the vulnerability source. - // now we will add unique CPE candidates that were not found in secdb. - if _, exists := secDBMatchesByID[id]; !exists { - // add the new CPE-based record (e.g. NVD) since it was not found in secDB - matches = append(matches, cpeMatchesForID...) - } - } - return matches -} - func matchesByID(matches []match.Match) map[string][]match.Match { var results = make(map[string][]match.Match) for _, secDBMatch := range matches { @@ -133,36 +118,14 @@ func vulnerabilitiesByID(vulns []vulnerability.Vulnerability) map[string][]vulne return results } -func (m *Matcher) findApkPackage(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) { - // find Alpine SecDB matches for the given package name and version - secDBMatches, err := search.ByPackageDistro(store, d, p, m.Type()) - if err != nil { - return nil, err - } - - cpeMatches, err := m.cpeMatchesWithoutSecDBFixes(store, d, p) - if err != nil { - return nil, err - } - - var matches []match.Match - - // keep all secdb matches, as this is an authoritative source - matches = append(matches, secDBMatches...) - - // keep only unique CPE matches - matches = append(matches, deduplicateMatches(secDBMatches, cpeMatches)...) - - return matches, nil -} - func (m *Matcher) matchBySourceIndirection(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) { var matches []match.Match for _, indirectPackage := range pkg.UpstreamPackages(p) { - indirectMatches, err := m.findApkPackage(store, d, indirectPackage) + // direct matches with package + indirectMatches, err := m.cpeMatchesWithoutSecDBFixes(store, d, indirectPackage) if err != nil { - return nil, fmt.Errorf("failed to find vulnerabilities for apk upstream source package: %w", err) + return nil, err } matches = append(matches, indirectMatches...) } From b92dcab76c05fabe2ba8559584786a6e4451ea2e Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 23 Oct 2023 15:07:40 -0400 Subject: [PATCH 2/5] test: update unit tests with matcher changes Signed-off-by: Christopher Phillips --- grype/matcher/apk/matcher.go | 52 +++--- grype/matcher/apk/matcher_test.go | 274 +++++------------------------- 2 files changed, 74 insertions(+), 252 deletions(-) diff --git a/grype/matcher/apk/matcher.go b/grype/matcher/apk/matcher.go index 1cd2f92b08d..bb295c1d652 100644 --- a/grype/matcher/apk/matcher.go +++ b/grype/matcher/apk/matcher.go @@ -1,17 +1,31 @@ package apk import ( - "fmt" - "github.com/anchore/grype/grype/distro" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/search" - "github.com/anchore/grype/grype/version" "github.com/anchore/grype/grype/vulnerability" syftPkg "github.com/anchore/syft/syft/pkg" ) +// Matcher behaves a little differently here than its other implementations. +// Secdb provides a negative match to the NVD matches meaning it can only be +// used to turn off a vulnerability. The contraint is a lie. Only the "fixed_in_versions" +// Column shows the true match to turn off... +// +//Example.... +/* +----------------------------- +Package Match in NVD: +zlib: v1.2.3-r2 | CVE X — affected versions: < v1.4.2 + +Secdb data shows +zlib: v1.2.3-r2 fixes CVE X + +Expected result: +Match is not reported because of the Secdb fix +*/ type Matcher struct { } @@ -43,6 +57,7 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa return matches, nil } +// compares NVD matches against secdb fixes for a given distro func (m *Matcher) cpeMatchesWithoutSecDBFixes(store vulnerability.Provider, d *distro.Distro, p pkg.Package) ([]match.Match, error) { // find CPE-indexed vulnerability matches specific to the given package name and version cpeMatches, err := search.ByPackageCPE(store, d, p, m.Type()) @@ -52,26 +67,22 @@ func (m *Matcher) cpeMatchesWithoutSecDBFixes(store vulnerability.Provider, d *d cpeMatchesByID := matchesByID(cpeMatches) - // remove cpe matches where there is an entry in the secDB for the particular package-vulnerability pairing, and the - // installed package version is >= the fixed in version for the secDB record. - secDBVulnerabilities, err := store.GetByDistro(d, p) + // get all secDB fixes for the provided distro + secDBVulnFixes, err := store.GetByDistro(d, p) if err != nil { return nil, err } - secDBVulnerabilitiesByID := vulnerabilitiesByID(secDBVulnerabilities) - - verObj, err := version.NewVersionFromPkg(p) - if err != nil { - return nil, fmt.Errorf("matcher failed to parse version pkg='%s' ver='%s': %w", p.Name, p.Version, err) - } + secDBFixesByID := fixesByID(secDBVulnFixes) + // remove cpe matches where there is an entry in the secDB for the particular package-vulnerability pairing + //and the installed package version should match the fixed in version for the secDB record. var finalCpeMatches []match.Match cveLoop: for id, cpeMatchesForID := range cpeMatchesByID { // check to see if there is a secdb entry for this ID (CVE) - secDBVulnerabilitiesForID, exists := secDBVulnerabilitiesByID[id] + secDBFixForID, exists := secDBFixesByID[id] if !exists { // does not exist in secdb, so the CPE record(s) should be added to the final results finalCpeMatches = append(finalCpeMatches, cpeMatchesForID...) @@ -79,16 +90,19 @@ cveLoop: } // there is a secdb entry... - for _, vuln := range secDBVulnerabilitiesForID { + for _, vuln := range secDBFixForID { // ...is there a fixed in entry? (should always be yes) if len(vuln.Fix.Versions) == 0 { continue } // ...is the current package vulnerable? - vulnerable, err := vuln.Constraint.Satisfied(verObj) - if err != nil { - return nil, err + var vulnerable bool + for _, fixedVersion := range vuln.Fix.Versions { + if fixedVersion == p.Version { + vulnerable = false + break + } } if vulnerable { @@ -109,9 +123,9 @@ func matchesByID(matches []match.Match) map[string][]match.Match { return results } -func vulnerabilitiesByID(vulns []vulnerability.Vulnerability) map[string][]vulnerability.Vulnerability { +func fixesByID(vulnFixes []vulnerability.Vulnerability) map[string][]vulnerability.Vulnerability { var results = make(map[string][]vulnerability.Vulnerability) - for _, vuln := range vulns { + for _, vuln := range vulnFixes { results[vuln.ID] = append(results[vuln.ID], vuln) } diff --git a/grype/matcher/apk/matcher_test.go b/grype/matcher/apk/matcher_test.go index 569b94a07fb..6f88e6d75d0 100644 --- a/grype/matcher/apk/matcher_test.go +++ b/grype/matcher/apk/matcher_test.go @@ -50,85 +50,14 @@ func (s *mockStore) GetVulnerabilityNamespaces() ([]string, error) { return keys, nil } -func TestSecDBOnlyMatch(t *testing.T) { - - secDbVuln := grypeDB.Vulnerability{ - // ID doesn't match - this is the key for comparison in the matcher - ID: "CVE-2020-2", - VersionConstraint: "<= 0.9.11", - VersionFormat: "apk", - Namespace: "secdb:distro:alpine:3.12", - } - store := mockStore{ - backend: map[string]map[string][]grypeDB.Vulnerability{ - "secdb:distro:alpine:3.12": { - "libvncserver": []grypeDB.Vulnerability{secDbVuln}, - }, - }, - } - - provider, err := db.NewVulnerabilityProvider(&store) - require.NoError(t, err) - - m := Matcher{} - d, err := distro.New(distro.Alpine, "3.12.0", "") - if err != nil { - t.Fatalf("failed to create a new distro: %+v", err) - } - - p := pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "libvncserver", - Version: "0.9.9", - Type: syftPkg.ApkPkg, - CPEs: []cpe.CPE{ - cpe.Must("cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"), - }, - } - - vulnFound, err := vulnerability.NewVulnerability(secDbVuln) - assert.NoError(t, err) - - expected := []match.Match{ - { - - Vulnerability: *vulnFound, - Package: p, - Details: []match.Detail{ - { - Type: match.ExactDirectMatch, - Confidence: 1.0, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": d.Type.String(), - "version": d.RawVersion, - }, - "package": map[string]string{ - "name": "libvncserver", - "version": "0.9.9", - }, - "namespace": "secdb:distro:alpine:3.12", - }, - Found: map[string]interface{}{ - "versionConstraint": vulnFound.Constraint.String(), - "vulnerabilityID": "CVE-2020-2", - }, - Matcher: match.ApkMatcher, - }, - }, - }, - } - - actual, err := m.Match(provider, d, p) - assert.NoError(t, err) - - assertMatches(t, expected, actual) -} - -func TestBothSecdbAndNvdMatches(t *testing.T) { +// TODO include case where fixed version don't match and it's not fixed +func TestSecdbFixesNvdMatches(t *testing.T) { // NVD and Alpine's secDB both have the same CVE ID for the package + // NVD represents the presence of a vulnerability + // SECDB data represents that for a given version that CVE has been fixed nvdVuln := grypeDB.Vulnerability{ ID: "CVE-2020-1", + PackageName: "libvncserver", VersionConstraint: "<= 0.9.11", VersionFormat: "unknown", CPEs: []string{`cpe:2.3:a:lib_vnc_project-\(server\):libvncserver:*:*:*:*:*:*:*:*`}, @@ -136,11 +65,12 @@ func TestBothSecdbAndNvdMatches(t *testing.T) { } secDbVuln := grypeDB.Vulnerability{ - // ID *does* match - this is the key for comparison in the matcher - ID: "CVE-2020-1", - VersionConstraint: "<= 0.9.11", - VersionFormat: "apk", - Namespace: "secdb:distro:alpine:3.12", + ID: "CVE-2020-1", + Fix: grypeDB.Fix{ + Versions: []string{"0.9.11"}, + }, + VersionFormat: "apk", + Namespace: "distro:alpine:3.12", } store := mockStore{ backend: map[string]map[string][]grypeDB.Vulnerability{ @@ -157,7 +87,7 @@ func TestBothSecdbAndNvdMatches(t *testing.T) { require.NoError(t, err) m := Matcher{} - d, err := distro.New(distro.Alpine, "3.12.0", "") + d, err := distro.New(distro.Alpine, "3.12", "") if err != nil { t.Fatalf("failed to create a new distro: %+v", err) } @@ -165,46 +95,17 @@ func TestBothSecdbAndNvdMatches(t *testing.T) { p := pkg.Package{ ID: pkg.ID(uuid.NewString()), Name: "libvncserver", - Version: "0.9.9", + Version: "0.9.11", // has to match the fixed version of secDB to be turned off Type: syftPkg.ApkPkg, CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"), }, } - // ensure the SECDB record is preferred over the NVD record - vulnFound, err := vulnerability.NewVulnerability(secDbVuln) assert.NoError(t, err) - expected := []match.Match{ - { - - Vulnerability: *vulnFound, - Package: p, - Details: []match.Detail{ - { - Type: match.ExactDirectMatch, - Confidence: 1.0, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": d.Type.String(), - "version": d.RawVersion, - }, - "package": map[string]string{ - "name": "libvncserver", - "version": "0.9.9", - }, - "namespace": "secdb:distro:alpine:3.12", - }, - Found: map[string]interface{}{ - "versionConstraint": vulnFound.Constraint.String(), - "vulnerabilityID": "CVE-2020-1", - }, - Matcher: match.ApkMatcher, - }, - }, - }, - } + // We expect the secdb entry to remove the match + expected := []match.Match{} actual, err := m.Match(provider, d, p) assert.NoError(t, err) @@ -212,7 +113,8 @@ func TestBothSecdbAndNvdMatches(t *testing.T) { assertMatches(t, expected, actual) } -func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) { +// TODO include case where fixed version don't match and it's not fixed +func TestNvdMatches_DifferentPackageName_Removed(t *testing.T) { // NVD and Alpine's secDB both have the same CVE ID for the package nvdVuln := grypeDB.Vulnerability{ ID: "CVE-2020-1", @@ -225,10 +127,12 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) { secDbVuln := grypeDB.Vulnerability{ // ID *does* match - this is the key for comparison in the matcher - ID: "CVE-2020-1", - VersionConstraint: "<= 0.9.11", - VersionFormat: "apk", - Namespace: "secdb:distro:alpine:3.12", + ID: "CVE-2020-1", + Fix: grypeDB.Fix{ + Versions: []string{"0.9.11"}, + }, + VersionFormat: "apk", + Namespace: "secdb:distro:alpine:3.12", } store := mockStore{ backend: map[string]map[string][]grypeDB.Vulnerability{ @@ -252,7 +156,7 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) { p := pkg.Package{ ID: pkg.ID(uuid.NewString()), Name: "libvncserver", - Version: "0.9.9", + Version: "0.9.11", Type: syftPkg.ApkPkg, CPEs: []cpe.CPE{ // Note: the product name is NOT the same as the package name @@ -260,39 +164,12 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) { }, } - // ensure the SECDB record is preferred over the NVD record - vulnFound, err := vulnerability.NewVulnerability(secDbVuln) + // when a related vulnerability fix is found in secdb + // ensure the SECDB record is included with the NVD record + _, err = vulnerability.NewVulnerability(secDbVuln) assert.NoError(t, err) - expected := []match.Match{ - { - - Vulnerability: *vulnFound, - Package: p, - Details: []match.Detail{ - { - Type: match.ExactDirectMatch, - Confidence: 1.0, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": d.Type.String(), - "version": d.RawVersion, - }, - "package": map[string]string{ - "name": "libvncserver", - "version": "0.9.9", - }, - "namespace": "secdb:distro:alpine:3.12", - }, - Found: map[string]interface{}{ - "versionConstraint": vulnFound.Constraint.String(), - "vulnerabilityID": "CVE-2020-1", - }, - Matcher: match.ApkMatcher, - }, - }, - }, - } + expected := []match.Match{} actual, err := m.Match(provider, d, p) assert.NoError(t, err) @@ -451,7 +328,7 @@ func TestNvdMatchesProperVersionFiltering(t *testing.T) { assertMatches(t, expected, actual) } -func TestNvdMatchesWithSecDBFix(t *testing.T) { +func TestNvdMatchesRemovedWithSecDBFix(t *testing.T) { nvdVuln := grypeDB.Vulnerability{ ID: "CVE-2020-1", VersionConstraint: "> 0.9.0, < 0.10.0", // note: this is not normal NVD configuration, but has the desired effect of a "wide net" for vulnerable indication @@ -461,9 +338,13 @@ func TestNvdMatchesWithSecDBFix(t *testing.T) { } secDbVuln := grypeDB.Vulnerability{ - ID: "CVE-2020-1", - VersionConstraint: "< 0.9.11", // note: this does NOT include 0.9.11, so NVD and SecDB mismatch here... secDB should trump in this case - VersionFormat: "apk", + // ID *does* match - this is the key for comparison in the matcher + ID: "CVE-2020-1", + Fix: grypeDB.Fix{ + Versions: []string{"0.9.11"}, + }, + VersionFormat: "apk", + Namespace: "secdb:distro:alpine:3.12", } store := mockStore{ @@ -515,8 +396,11 @@ func TestNvdMatchesNoConstraintWithSecDBFix(t *testing.T) { secDbVuln := grypeDB.Vulnerability{ ID: "CVE-2020-1", VersionConstraint: "< 0.9.11", - VersionFormat: "apk", - Namespace: "secdb:distro:alpine:3.12", + Fix: grypeDB.Fix{ + Versions: []string{"0.9.11"}, + }, + VersionFormat: "apk", + Namespace: "secdb:distro:alpine:3.12", } store := mockStore{ @@ -556,82 +440,6 @@ func TestNvdMatchesNoConstraintWithSecDBFix(t *testing.T) { assertMatches(t, expected, actual) } -func TestDistroMatchBySourceIndirection(t *testing.T) { - - secDbVuln := grypeDB.Vulnerability{ - // ID doesn't match - this is the key for comparison in the matcher - ID: "CVE-2020-2", - VersionConstraint: "<= 1.3.3-r0", - VersionFormat: "apk", - Namespace: "secdb:distro:alpine:3.12", - } - store := mockStore{ - backend: map[string]map[string][]grypeDB.Vulnerability{ - "secdb:distro:alpine:3.12": { - "musl": []grypeDB.Vulnerability{secDbVuln}, - }, - }, - } - - provider, err := db.NewVulnerabilityProvider(&store) - require.NoError(t, err) - - m := Matcher{} - d, err := distro.New(distro.Alpine, "3.12.0", "") - if err != nil { - t.Fatalf("failed to create a new distro: %+v", err) - } - p := pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "musl-utils", - Version: "1.3.2-r0", - Type: syftPkg.ApkPkg, - Upstreams: []pkg.UpstreamPackage{ - { - Name: "musl", - }, - }, - } - - vulnFound, err := vulnerability.NewVulnerability(secDbVuln) - assert.NoError(t, err) - - expected := []match.Match{ - { - - Vulnerability: *vulnFound, - Package: p, - Details: []match.Detail{ - { - Type: match.ExactIndirectMatch, - Confidence: 1.0, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": d.Type.String(), - "version": d.RawVersion, - }, - "package": map[string]string{ - "name": "musl", - "version": p.Version, - }, - "namespace": "secdb:distro:alpine:3.12", - }, - Found: map[string]interface{}{ - "versionConstraint": vulnFound.Constraint.String(), - "vulnerabilityID": "CVE-2020-2", - }, - Matcher: match.ApkMatcher, - }, - }, - }, - } - - actual, err := m.Match(provider, d, p) - assert.NoError(t, err) - - assertMatches(t, expected, actual) -} - func TestNVDMatchBySourceIndirection(t *testing.T) { nvdVuln := grypeDB.Vulnerability{ ID: "CVE-2020-1", From 6b475e4d0d3f03c3fcb800daf8b2e2c69fac8cdb Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 23 Oct 2023 15:25:39 -0400 Subject: [PATCH 3/5] chore: SA tooling Signed-off-by: Christopher Phillips --- grype/matcher/apk/matcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grype/matcher/apk/matcher.go b/grype/matcher/apk/matcher.go index bb295c1d652..54a0dab6445 100644 --- a/grype/matcher/apk/matcher.go +++ b/grype/matcher/apk/matcher.go @@ -14,7 +14,7 @@ import ( // used to turn off a vulnerability. The contraint is a lie. Only the "fixed_in_versions" // Column shows the true match to turn off... // -//Example.... +// Example.... /* ----------------------------- Package Match in NVD: @@ -76,7 +76,7 @@ func (m *Matcher) cpeMatchesWithoutSecDBFixes(store vulnerability.Provider, d *d secDBFixesByID := fixesByID(secDBVulnFixes) // remove cpe matches where there is an entry in the secDB for the particular package-vulnerability pairing - //and the installed package version should match the fixed in version for the secDB record. + // and the installed package version should match the fixed in version for the secDB record. var finalCpeMatches []match.Match cveLoop: From 2ba653dc50bc26b28b7a1867fba6071824a682fb Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 23 Oct 2023 16:20:45 -0400 Subject: [PATCH 4/5] test: update integration tests with new match strategy Signed-off-by: Christopher Phillips --- grype/matcher/apk/matcher.go | 4 +- grype/vulnerability/vulnerability.go | 6 ++- test/integration/db_mock_test.go | 11 ++++- test/integration/match_by_image_test.go | 57 +++++++++---------------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/grype/matcher/apk/matcher.go b/grype/matcher/apk/matcher.go index 54a0dab6445..bb9fd97f3de 100644 --- a/grype/matcher/apk/matcher.go +++ b/grype/matcher/apk/matcher.go @@ -97,8 +97,10 @@ cveLoop: } // ...is the current package vulnerable? - var vulnerable bool + vulnerable := true for _, fixedVersion := range vuln.Fix.Versions { + // we found that the packages version is the same + // as the fixed version for the given CVE in secdb if fixedVersion == p.Version { vulnerable = false break diff --git a/grype/vulnerability/vulnerability.go b/grype/vulnerability/vulnerability.go index 25d9f0fcbec..254d9f16ab0 100644 --- a/grype/vulnerability/vulnerability.go +++ b/grype/vulnerability/vulnerability.go @@ -53,12 +53,16 @@ func NewVulnerability(vuln grypeDB.Vulnerability) (*Vulnerability, error) { Namespace: r.Namespace, }) } + cpes := []cpe.CPE{} + for _, c := range vuln.CPEs { + cpes = append(cpes, cpe.Must(c)) + } return &Vulnerability{ Constraint: constraint, ID: vuln.ID, - CPEs: make([]cpe.CPE, 0), Namespace: vuln.Namespace, + CPEs: cpes, PackageQualifiers: pkgQualifiers, Fix: Fix{ Versions: vuln.Fix.Versions, diff --git a/test/integration/db_mock_test.go b/test/integration/db_mock_test.go index 6466488872e..328b6a93d70 100644 --- a/test/integration/db_mock_test.go +++ b/test/integration/db_mock_test.go @@ -48,7 +48,9 @@ func newMockDbStore() *mockStore { ID: "CVE-alpine-libvncserver", VersionConstraint: "< 0.9.10", VersionFormat: "unknown", - CPEs: []string{"cpe:2.3:a:lib_vnc_project-(server):libvncserver:*:*:*:*:*:*:*:*"}, + CPEs: []string{ + "cpe:2.3:a:libvncserver:libvncserver:*:*:*:*:*:*:*:*", + }, }, }, "my-package": []grypeDB.Vulnerability{ @@ -71,7 +73,12 @@ func newMockDbStore() *mockStore { { ID: "CVE-alpine-libvncserver", VersionConstraint: "< 0.9.10", - VersionFormat: "unknown", + Fix: grypeDB.Fix{ + Versions: []string{ + "0.9.10", + }, + }, + VersionFormat: "unknown", }, }, }, diff --git a/test/integration/match_by_image_test.go b/test/integration/match_by_image_test.go index 60ef75800de..47e0215d7da 100644 --- a/test/integration/match_by_image_test.go +++ b/test/integration/match_by_image_test.go @@ -15,6 +15,7 @@ import ( "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/matcher" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/search" "github.com/anchore/grype/grype/store" "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" @@ -34,56 +35,40 @@ func addAlpineMatches(t *testing.T, theSource source.Source, catalog *syftPkg.Co t.Fatalf("problem with upstream syft cataloger (alpine)") } thePkg := pkg.New(packages[0]) - theVuln := theStore.backend["alpine:distro:alpine:3.12"][thePkg.Name][0] + theVuln := theStore.backend["nvd:cpe"][thePkg.Name][0] vulnObj, err := vulnerability.NewVulnerability(theVuln) require.NoError(t, err) theResult.Add(match.Match{ - // note: we are matching on the secdb record, not NVD primarily - + // note: we should not be directly matching on the secdb record + // we should match on NVD and only removing the match if secdb informs us to + // this match should be reported since the mock store has a fix for 0.9.10 + // this pacakge has a version of 0.9.9 Vulnerability: *vulnObj, Package: thePkg, Details: []match.Detail{ { // note: the input pURL has an upstream reference (redundant) - Type: "exact-indirect-match", - SearchedBy: map[string]any{ - "distro": map[string]string{ - "type": "alpine", - "version": "3.12.0", + Type: match.CPEMatch, + SearchedBy: search.CPEParameters{ + Namespace: "nvd:cpe", + CPEs: []string{ + "cpe:2.3:a:libvncserver:libvncserver:0.9.9:*:*:*:*:*:*:*", }, - "namespace": "alpine:distro:alpine:3.12", - "package": map[string]string{ - "name": "libvncserver", - "version": "0.9.9", + Package: search.CPEPackageParameter{ + Name: "libvncserver", + Version: "0.9.9", }, }, - Found: map[string]any{ - "versionConstraint": "< 0.9.10 (unknown)", - "vulnerabilityID": "CVE-alpine-libvncserver", - }, - Matcher: "apk-matcher", - Confidence: 1, - }, - { - Type: match.ExactDirectMatch, - Confidence: 1.0, - SearchedBy: map[string]interface{}{ - "distro": map[string]string{ - "type": "alpine", - "version": "3.12.0", - }, - "namespace": "alpine:distro:alpine:3.12", - "package": map[string]string{ - "name": "libvncserver", - "version": "0.9.9", + Found: search.CPEResult{ + VersionConstraint: "< 0.9.10 (unknown)", + VulnerabilityID: "CVE-alpine-libvncserver", + CPEs: []string{ + "cpe:2.3:a:libvncserver:libvncserver:*:*:*:*:*:*:*:*", }, }, - Found: map[string]interface{}{ - "versionConstraint": "< 0.9.10 (unknown)", - "vulnerabilityID": vulnObj.ID, - }, - Matcher: match.ApkMatcher, + Matcher: "apk-matcher", + Confidence: 0.9, }, }, }) From bec1b978d9b2923ced1292041cae4110a69d84cd Mon Sep 17 00:00:00 2001 From: Christopher Phillips Date: Mon, 23 Oct 2023 16:41:07 -0400 Subject: [PATCH 5/5] test: update matcher test to reflect latest CPE match directive Signed-off-by: Christopher Phillips --- grype/matcher/apk/matcher_test.go | 153 ++++++++++++++++++++---------- 1 file changed, 101 insertions(+), 52 deletions(-) diff --git a/grype/matcher/apk/matcher_test.go b/grype/matcher/apk/matcher_test.go index 6f88e6d75d0..78fe14f1dc3 100644 --- a/grype/matcher/apk/matcher_test.go +++ b/grype/matcher/apk/matcher_test.go @@ -50,70 +50,119 @@ func (s *mockStore) GetVulnerabilityNamespaces() ([]string, error) { return keys, nil } -// TODO include case where fixed version don't match and it's not fixed func TestSecdbFixesNvdMatches(t *testing.T) { - // NVD and Alpine's secDB both have the same CVE ID for the package - // NVD represents the presence of a vulnerability - // SECDB data represents that for a given version that CVE has been fixed - nvdVuln := grypeDB.Vulnerability{ - ID: "CVE-2020-1", - PackageName: "libvncserver", - VersionConstraint: "<= 0.9.11", - VersionFormat: "unknown", - CPEs: []string{`cpe:2.3:a:lib_vnc_project-\(server\):libvncserver:*:*:*:*:*:*:*:*`}, - Namespace: "nvd:cpe", - } - - secDbVuln := grypeDB.Vulnerability{ - ID: "CVE-2020-1", - Fix: grypeDB.Fix{ - Versions: []string{"0.9.11"}, + tests := []struct { + name string + packageVersion string + fixedVersion string + expectedMatchs bool + }{ + { + name: "SecDB eliminates an nvd match given the same package and fixed versions", + packageVersion: "0.9.11", + fixedVersion: "0.9.11", + expectedMatchs: false, }, - VersionFormat: "apk", - Namespace: "distro:alpine:3.12", - } - store := mockStore{ - backend: map[string]map[string][]grypeDB.Vulnerability{ - "nvd:cpe": { - "libvncserver": []grypeDB.Vulnerability{nvdVuln}, - }, - "secdb:distro:alpine:3.12": { - "libvncserver": []grypeDB.Vulnerability{secDbVuln}, - }, + { + name: "SecDB retains an nvd match given the different package and fixed versions", + packageVersion: "0.9.10", + fixedVersion: "0.9.11", + expectedMatchs: true, }, } - provider, err := db.NewVulnerabilityProvider(&store) - require.NoError(t, err) - - m := Matcher{} - d, err := distro.New(distro.Alpine, "3.12", "") - if err != nil { - t.Fatalf("failed to create a new distro: %+v", err) - } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + // NVD and Alpine's secDB both have the same CVE ID for the package + // NVD represents the presence of a vulnerability + // SECDB data represents that for a given version that CVE has been fixed + nvdVuln := grypeDB.Vulnerability{ + ID: "CVE-2020-1", + PackageName: "libvncserver", + VersionConstraint: "<= 0.9.11", + VersionFormat: "unknown", + CPEs: []string{`cpe:2.3:a:lib_vnc_project-\(server\):libvncserver:*:*:*:*:*:*:*:*`}, + Namespace: "nvd:cpe", + } + + secDbFix := grypeDB.Vulnerability{ + ID: "CVE-2020-1", + Fix: grypeDB.Fix{ + Versions: []string{tc.fixedVersion}, + }, + VersionFormat: "apk", + Namespace: "distro:alpine:3.12", + } - p := pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "libvncserver", - Version: "0.9.11", // has to match the fixed version of secDB to be turned off - Type: syftPkg.ApkPkg, - CPEs: []cpe.CPE{ - cpe.Must("cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"), - }, - } + nvdFoundVuln, err := vulnerability.NewVulnerability(nvdVuln) - assert.NoError(t, err) + store := mockStore{ + backend: map[string]map[string][]grypeDB.Vulnerability{ + "nvd:cpe": { + "libvncserver": []grypeDB.Vulnerability{nvdVuln}, + }, + "secdb:distro:alpine:3.12": { + "libvncserver": []grypeDB.Vulnerability{secDbFix}, + }, + }, + } + + provider, err := db.NewVulnerabilityProvider(&store) + require.NoError(t, err) + + m := Matcher{} + d, err := distro.New(distro.Alpine, "3.12", "") + if err != nil { + t.Fatalf("failed to create a new distro: %+v", err) + } + + p := pkg.Package{ + ID: pkg.ID(uuid.NewString()), + Name: "libvncserver", + Version: tc.packageVersion, + Type: syftPkg.ApkPkg, + CPEs: []cpe.CPE{ + cpe.Must("cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"), + }, + } + + assert.NoError(t, err) + matches := []match.Match{} + if tc.expectedMatchs { + matches = append(matches, match.Match{ + Vulnerability: *nvdFoundVuln, + Package: p, + Details: []match.Detail{ + { + Type: match.CPEMatch, + Confidence: 0.9, + SearchedBy: search.CPEParameters{ + Namespace: "nvd:cpe", + CPEs: []string{"cpe:2.3:a:*:libvncserver:0.9.9:*:*:*:*:*:*:*"}, + Package: search.CPEPackageParameter{Name: "libvncserver", Version: "0.9.10"}, + }, + Found: search.CPEResult{ + VulnerabilityID: "CVE-2020-1", + VersionConstraint: "<= 0.9.11 (unknown)", + CPEs: []string{ + "cpe:2.3:a:lib_vnc_project-(server):libvncserver:*:*:*:*:*:*:*:*", + }, + }, + Matcher: match.ApkMatcher, + }, + }}) + } - // We expect the secdb entry to remove the match - expected := []match.Match{} + actual, err := m.Match(provider, d, p) + assert.NoError(t, err) - actual, err := m.Match(provider, d, p) - assert.NoError(t, err) + assertMatches(t, matches, actual) + }) + } - assertMatches(t, expected, actual) } -// TODO include case where fixed version don't match and it's not fixed func TestNvdMatches_DifferentPackageName_Removed(t *testing.T) { // NVD and Alpine's secDB both have the same CVE ID for the package nvdVuln := grypeDB.Vulnerability{