Skip to content

Commit

Permalink
fix: only warn missing CPEs if CPEs wanted (#1710)
Browse files Browse the repository at this point in the history
Previously, a warning would be logged about missing CPEs even when
processing packages for which CPEs would never be matched on. Instead,
return a specific error when attempting to match by CPEs on a package
with no CPEs.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
  • Loading branch information
willmurphyscode committed Feb 12, 2024
1 parent ba0cc19 commit 6b38079
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 7 deletions.
3 changes: 3 additions & 0 deletions grype/matcher/apk/matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,9 @@ func TestDistroMatchBySourceIndirection(t *testing.T) {
Name: "musl",
},
},
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:musl-utils:musl-utils:*:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
}

vulnFound, err := vulnerability.NewVulnerability(secDbVuln)
Expand Down
6 changes: 1 addition & 5 deletions grype/pkg/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,18 @@ func FromCollection(catalog *pkg.Collection, config SynthesisConfig) []Package {

func FromPackages(syftpkgs []pkg.Package, config SynthesisConfig) []Package {
var pkgs []Package
var missingCPEs bool
for _, p := range syftpkgs {
if len(p.CPEs) == 0 {
// For SPDX (or any format, really) we may have no CPEs
if config.GenerateMissingCPEs {
p.CPEs = cpes.Generate(p)
} else {
log.Debugf("no CPEs for package: %s", p)
missingCPEs = true
}
}
pkgs = append(pkgs, New(p))
}
if missingCPEs {
log.Warnf("some package(s) are missing CPEs. This may result in missing vulnerabilities. You may autogenerate these using: --add-cpes-if-none")
}

return pkgs
}

Expand Down
8 changes: 8 additions & 0 deletions grype/search/cpe.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package search

import (
"errors"
"fmt"
"sort"
"strings"
Expand Down Expand Up @@ -81,10 +82,17 @@ func alpineCPEComparableVersion(version string) string {
return cpeComparableVersion
}

var ErrEmptyCPEMatch = errors.New("attempted CPE match against package with no CPEs")

// ByPackageCPE retrieves all vulnerabilities that match the generated CPE
func ByPackageCPE(store vulnerability.ProviderByCPE, d *distro.Distro, p pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
// we attempt to merge match details within the same matcher when searching by CPEs, in this way there are fewer duplicated match
// objects (and fewer duplicated match details).

// Warn the user if they are matching by CPE, but there are no CPEs available.
if len(p.CPEs) == 0 {
return nil, ErrEmptyCPEMatch
}
matchesByFingerprint := make(map[match.Fingerprint]match.Match)
for _, c := range p.CPEs {
// prefer the CPE version, but if npt specified use the package version
Expand Down
23 changes: 22 additions & 1 deletion grype/search/cpe_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package search

import (
"errors"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -161,6 +162,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
name string
p pkg.Package
expected []match.Match
wantErr require.ErrorAssertionFunc
}{
{
name: "match from range",
Expand Down Expand Up @@ -361,6 +363,9 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
Version: "4.0.1",
Language: syftPkg.Ruby,
Type: syftPkg.GemPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:no_match:no_match:0.9.9:*:*:*:*:*:*:*", cpe.GeneratedSource),
},
},
expected: []match.Match{},
},
Expand Down Expand Up @@ -683,14 +688,30 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
},
},
},
{
name: "package without CPEs returns error",
p: pkg.Package{
Name: "some-package",
},
expected: nil,
wantErr: func(t require.TestingT, err error, i ...interface{}) {
if !errors.Is(err, ErrEmptyCPEMatch) {
t.Errorf("expected %v but got %v", ErrEmptyCPEMatch, err)
t.FailNow()
}
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
p, err := db.NewVulnerabilityProvider(newMockStore())
require.NoError(t, err)
actual, err := ByPackageCPE(p, nil, test.p, matcher)
assert.NoError(t, err)
if test.wantErr == nil {
test.wantErr = require.NoError
}
test.wantErr(t, err)
assertMatchesUsingIDsForVulnerabilities(t, test.expected, actual)
for idx, e := range test.expected {
if d := cmp.Diff(e.Details, actual[idx].Details); d != "" {
Expand Down
7 changes: 6 additions & 1 deletion grype/search/criteria.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package search

import (
"errors"

"github.com/anchore/grype/grype/distro"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
Expand All @@ -25,7 +27,10 @@ func ByCriteria(store vulnerability.Provider, d *distro.Distro, p pkg.Package, u
switch c {
case ByCPE:
m, err := ByPackageCPE(store, d, p, upstreamMatcher)
if err != nil {
if errors.Is(err, ErrEmptyCPEMatch) {
log.Warnf("attempted CPE search on %s, which has no CPEs. Consider re-running with --add-cpes-if-none", p.Name)
continue
} else if err != nil {
log.Warnf("could not match by package CPE (package=%+v): %v", p, err)
continue
}
Expand Down

0 comments on commit 6b38079

Please sign in to comment.