diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index 7ae9dbf4c8b..0ce19261973 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -66,6 +66,7 @@ func (m *VulnerabilityMatcher) FindMatches(pkgs []pkg.Package, context pkg.Conte progressMonitor := trackMatcher(len(pkgs)) defer func() { + progressMonitor.Ignored.Set(int64(len(ignoredMatches))) progressMonitor.SetCompleted() if err != nil { progressMonitor.MatchesDiscovered.SetError(err) @@ -116,10 +117,25 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con // regresses the results (relative to the already applied ignore rules). Why do we additionally apply // the ignore rules before normalizing? In case the user has a rule that ignores a non-normalized // vulnerability ID, we wantMatches to ensure that the rule is honored. + originalIgnoredMatches := ignoredMatches matches, ignoredMatches = m.applyIgnoreRules(normalizedMatches) + ignoredMatches = m.mergeIgnoredMatches(originalIgnoredMatches, ignoredMatches) } - return &matches, ignoredMatches, err + return &matches, ignoredMatches, nil +} + +func (m *VulnerabilityMatcher) mergeIgnoredMatches(allIgnoredMatches ...[]match.IgnoredMatch) []match.IgnoredMatch { + var out []match.IgnoredMatch + for _, ignoredMatches := range allIgnoredMatches { + for _, ignored := range ignoredMatches { + if m.NormalizeByCVE { + ignored.Match = m.normalizeByCVE(ignored.Match) + } + out = append(out, ignored) + } + } + return out } func (m *VulnerabilityMatcher) searchDBForMatches( diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go index 8ddc98279b3..9f1d7373647 100644 --- a/grype/vulnerability_matcher_test.go +++ b/grype/vulnerability_matcher_test.go @@ -8,10 +8,13 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wagoodman/go-partybus" "github.com/anchore/grype/grype/db" grypeDB "github.com/anchore/grype/grype/db/v5" "github.com/anchore/grype/grype/distro" + "github.com/anchore/grype/grype/event" + "github.com/anchore/grype/grype/event/monitor" "github.com/anchore/grype/grype/grypeerr" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/matcher" @@ -23,6 +26,7 @@ import ( "github.com/anchore/grype/grype/version" "github.com/anchore/grype/grype/vex" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/grype/internal/bus" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" @@ -785,6 +789,48 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { }, ), wantErr: nil, + wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat), + ID: "CVE-2014-fake-3", + Namespace: "nvd:cpe", + CPEs: []cpe.CPE{}, + PackageQualifiers: []qualifier.Qualifier{}, + Advisories: []vulnerability.Advisory{}, + RelatedVulnerabilities: []vulnerability.Reference{ + { + ID: "GHSA-2014-fake-3", + Namespace: "github:language:ruby", + }, + }, + }, + Package: activerecordPkg, + Details: match.Details{ + { + Type: match.ExactDirectMatch, + SearchedBy: map[string]any{ + "language": "ruby", + "namespace": "github:language:ruby", + "package": map[string]string{"name": "activerecord", "version": "3.7.5"}, + }, + Found: map[string]any{ + "versionConstraint": "< 3.7.6 (unknown)", + "vulnerabilityID": "GHSA-2014-fake-3", + }, + Matcher: "ruby-gem-matcher", + Confidence: 1, + }, + }, + }, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Vulnerability: "GHSA-2014-fake-3", + }, + }, + }, + }, }, { name: "normalize by cve -- ignore CVE", @@ -812,6 +858,51 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { }, wantMatches: match.NewMatches(), wantIgnoredMatches: []match.IgnoredMatch{ + { + Match: match.Match{ + Vulnerability: vulnerability.Vulnerability{ + Constraint: version.MustGetConstraint("< 3.7.6", version.UnknownFormat), + ID: "CVE-2014-fake-3", + Namespace: "nvd:cpe", + CPEs: []cpe.CPE{ + mustCPE("cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"), + }, + PackageQualifiers: []qualifier.Qualifier{}, + Advisories: []vulnerability.Advisory{}, + RelatedVulnerabilities: nil, + }, + Package: activerecordPkg, + Details: match.Details{ + { + Type: match.CPEMatch, + SearchedBy: search.CPEParameters{ + Namespace: "nvd:cpe", + CPEs: []string{ + "cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*", + }, + Package: search.CPEPackageParameter{ + Name: "activerecord", + Version: "3.7.5", + }, + }, + Found: search.CPEResult{ + VulnerabilityID: "CVE-2014-fake-3", + VersionConstraint: "< 3.7.6 (unknown)", + CPEs: []string{ + "cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*", + }, + }, + Matcher: "ruby-gem-matcher", + Confidence: 0.9, + }, + }, + }, + AppliedIgnoreRules: []match.IgnoreRule{ + { + Vulnerability: "CVE-2014-fake-3", + }, + }, + }, { AppliedIgnoreRules: []match.IgnoreRule{ { @@ -969,6 +1060,11 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { NormalizeByCVE: tt.fields.NormalizeByCVE, VexProcessor: tt.fields.VexProcessor, } + + listener := &busListener{} + bus.Set(listener) + defer bus.Set(nil) + actualMatches, actualIgnoreMatches, err := m.FindMatches(tt.args.pkgs, tt.args.context) if tt.wantErr != nil { require.ErrorIs(t, err, tt.wantErr) @@ -992,6 +1088,9 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { if d := cmp.Diff(tt.wantIgnoredMatches, actualIgnoreMatches, opts...); d != "" { t.Errorf("FindMatches() ignored matches mismatch [ha!] (-want +got):\n%s", d) } + + // validate the bus-reported ignored counts are accurate + require.Equal(t, int64(len(tt.wantIgnoredMatches)), listener.matching.Ignored.Current()) }) } } @@ -1281,3 +1380,17 @@ func Test_filterMatchesUsingDistroFalsePositives(t *testing.T) { }) } } + +type busListener struct { + matching monitor.Matching +} + +func (b *busListener) Publish(e partybus.Event) { + if e.Type == event.VulnerabilityScanningStarted { + if m, ok := e.Value.(monitor.Matching); ok { + b.matching = m + } + } +} + +var _ partybus.Publisher = (*busListener)(nil)