44 "errors"
55 "fmt"
66
7+ "github.com/anchore/grype/grype/distro"
78 "github.com/anchore/grype/grype/match"
89 "github.com/anchore/grype/grype/matcher/internal"
910 "github.com/anchore/grype/grype/pkg"
@@ -35,6 +36,22 @@ func NewApkMatcher(cfg MatcherConfig) *Matcher {
3536 return & Matcher {cfg : cfg }
3637}
3738
39+ // useUpstreamForPackage returns whether origin/upstream lookups should be performed
40+ // for this package. Alpine always requires upstream lookups regardless of the config flag —
41+ // it uses secdb-style advisories keyed by origin package name, so disabling lookups would
42+ // silently miss vulnerabilities. OSV-based distros with per-sub-package entries (e.g.
43+ // Chainguard, Wolfi) can disable upstream lookups via UseUpstreamMatcher=false to avoid
44+ // false positives from origin-level entries applying to unaffected sub-packages.
45+ //
46+ // TODO: if Alpine ever publishes per-sub-package OSV advisories, this hardcoded override
47+ // should be removed and Alpine should respect the flag like other distros.
48+ func (m * Matcher ) useUpstreamForPackage (p pkg.Package ) bool {
49+ if p .Distro != nil && p .Distro .Type == distro .Alpine {
50+ return true
51+ }
52+ return m .cfg .UseUpstreamMatcher
53+ }
54+
3855func (m * Matcher ) PackageTypes () []syftPkg.Type {
3956 return []syftPkg.Type {syftPkg .ApkPkg }
4057}
@@ -53,71 +70,68 @@ func (m *Matcher) Match(store vulnerability.Provider, p pkg.Package) ([]match.Ma
5370 }
5471 matches = append (matches , directMatches ... )
5572
56- // indirect matches, via package's origin package
57- if m .cfg .UseUpstreamMatcher {
58- // full upstream matching: consult advisory for origin package name
73+ // For secdb-style advisories that lack per-sub-package granularity, vulnerabilities are
74+ // keyed under the origin/source package name rather than the individual sub-package.
75+ // This lookup propagates those matches to the installed sub-package. When using an OSV-based
76+ // advisory that has per-sub-package entries (e.g. Chainguard/Wolfi), this can be disabled
77+ // (UseUpstreamMatcher=false) to avoid false positives from origin-level entries applying to
78+ // unaffected sub-packages. Alpine is always exempt — see useUpstreamForPackage.
79+ if m .useUpstreamForPackage (p ) {
5980 indirectMatches , err := m .findMatchesForOriginPackage (store , p )
6081 if err != nil {
6182 return nil , nil , err
6283 }
6384 matches = append (matches , indirectMatches ... )
64- } else {
65- // CPE-only upstream matching: use origin's CPEs to find NVD vulns, but
66- // filter against the direct sub-package's advisory (not the origin's)
67- indirectMatches , err := m .findCPEMatchesForOriginPackages (store , p )
68- if err != nil {
69- return nil , nil , err
70- }
71- matches = append (matches , indirectMatches ... )
7285 }
7386
7487 // APK sources are also able to NAK vulnerabilities, so we want to return these as explicit ignores in order
7588 // to allow rules later to use these to ignore "the same" vulnerability found in "the same" locations
76- naks , err := m .findNaksForPackage (store , p , m . cfg . UseUpstreamMatcher )
89+ naks , err := m .findNaksForPackage (store , p )
7790
7891 return matches , naks , err
7992}
8093
8194//nolint:funlen,gocognit
82- // cpeMatchesWithoutSecDBFixes finds CPE-indexed vulnerability matches for cpePkg (using its
83- // CPEs for NVD lookup) but filters out matches already fixed according to the advisory for
84- // secdbPkg. Normally cpePkg == secdbPkg, but when UseUpstreamMatcher is false, cpePkg is the
85- // origin package (rewritten CPEs for NVD lookup) while secdbPkg is the direct sub-package
86- // (whose advisory holds the authoritative fix/NAK data).
87- func (m * Matcher ) cpeMatchesWithoutSecDBFixes (provider vulnerability.Provider , cpePkg pkg.Package , secdbPkg pkg.Package ) ([]match.Match , error ) {
88- // find CPE-indexed vulnerability matches using cpePkg's CPEs
89- cpeMatches , err := internal .MatchPackageByCPEs (provider , cpePkg , m .Type ())
95+ func (m * Matcher ) cpeMatchesWithoutSecDBFixes (provider vulnerability.Provider , p pkg.Package ) ([]match.Match , error ) {
96+ // find CPE-indexed vulnerability matches specific to the given package name and version
97+ cpeMatches , err := internal .MatchPackageByCPEs (provider , p , m .Type ())
9098 if err != nil {
91- log .WithFields ("package" , cpePkg .Name , "error" , err ).Debug ("failed to find CPE matches for package" )
99+ log .WithFields ("package" , p .Name , "error" , err ).Debug ("failed to find CPE matches for package" )
92100 }
93- if secdbPkg .Distro == nil {
101+ if p .Distro == nil {
94102 return cpeMatches , nil
95103 }
96104
97105 cpeMatchesByID := matchesByID (cpeMatches )
98106
99- // remove cpe matches where there is an entry in the advisory for secdbPkg indicating the
100- // installed version is already fixed.
107+ // Suppress CPE matches that the distro advisory has already marked as fixed.
108+ // When UseUpstreamMatcher is false we only consult the direct package's advisory here,
109+ // not the origin's. This means a CPE match won't be suppressed based on an origin-level
110+ // fix — a deliberate tradeoff: a sub-package with its own CPEs may produce a false
111+ // positive if the origin advisory already has the fix and version numbers are shared.
112+ // In practice this is uncommon since APK sub-packages rarely have independent CPEs in NVD.
101113 secDBVulnerabilities , err := provider .FindVulnerabilities (
102- search .ByPackageName (secdbPkg .Name ),
103- search .ByDistro (* secdbPkg .Distro ))
114+ search .ByPackageName (p .Name ),
115+ search .ByDistro (* p .Distro ))
104116 if err != nil {
105117 return nil , err
106118 }
107119
108- for _ , upstreamPkg := range pkg .UpstreamPackages (secdbPkg ) {
109- secDBVulnerabilitiesForUpstream , err := provider .FindVulnerabilities (
110- search .ByPackageName (upstreamPkg .Name ),
111- search .ByDistro (* upstreamPkg .Distro ))
112- if err != nil {
113- return nil , err
120+ if m .useUpstreamForPackage (p ) {
121+ for _ , upstreamPkg := range pkg .UpstreamPackages (p ) {
122+ secDBVulnerabilitiesForUpstream , err := provider .FindVulnerabilities (
123+ search .ByPackageName (upstreamPkg .Name ),
124+ search .ByDistro (* upstreamPkg .Distro ))
125+ if err != nil {
126+ return nil , err
127+ }
128+ secDBVulnerabilities = append (secDBVulnerabilities , secDBVulnerabilitiesForUpstream ... )
114129 }
115- secDBVulnerabilities = append (secDBVulnerabilities , secDBVulnerabilitiesForUpstream ... )
116130 }
117131
118132 secDBVulnerabilitiesByID := vulnerabilitiesByID (secDBVulnerabilities )
119133
120- verObj := version .New (secdbPkg .Version , pkg .VersionFormat (secdbPkg ))
134+ verObj := version .New (p .Version , pkg .VersionFormat (p ))
121135
122136 var finalCpeMatches []match.Match
123137
@@ -204,7 +218,7 @@ func (m *Matcher) findMatchesForPackage(store vulnerability.Provider, p pkg.Pack
204218 }
205219
206220 // TODO: are there other errors that we should handle here that causes this to short circuit
207- cpeMatches , err := m .cpeMatchesWithoutSecDBFixes (store , p , p )
221+ cpeMatches , err := m .cpeMatchesWithoutSecDBFixes (store , p )
208222 if err != nil && ! errors .Is (err , internal .ErrEmptyCPEMatch ) {
209223 return nil , err
210224 }
@@ -238,35 +252,14 @@ func (m *Matcher) findMatchesForOriginPackage(store vulnerability.Provider, cata
238252 return matches , nil
239253}
240254
241- // findCPEMatchesForOriginPackages is used when UseUpstreamMatcher is false. It still performs
242- // CPE/NVD lookups using origin-rewritten CPEs (so NVD vulns keyed under the origin name are
243- // found), but advisory filtering uses the direct sub-package's advisory rather than the
244- // origin's. This supports distro advisories that are keyed per sub-package rather than per
245- // origin package.
246- func (m * Matcher ) findCPEMatchesForOriginPackages (store vulnerability.Provider , catalogPkg pkg.Package ) ([]match.Match , error ) {
247- var matches []match.Match
248-
249- for _ , indirectPackage := range pkg .UpstreamPackages (catalogPkg ) {
250- // cpePkg = origin (rewritten CPEs for NVD), secdbPkg = direct sub-package (advisory filtering)
251- cpeMatches , err := m .cpeMatchesWithoutSecDBFixes (store , indirectPackage , catalogPkg )
252- if err != nil && ! errors .Is (err , internal .ErrEmptyCPEMatch ) {
253- return nil , fmt .Errorf ("failed to find CPE vulnerabilities for apk upstream source package: %w" , err )
254- }
255- matches = append (matches , cpeMatches ... )
256- }
257-
258- match .ConvertToIndirectMatches (matches , catalogPkg )
259- return matches , nil
260- }
261-
262255// NAK entries are those reported as explicitly not vulnerable by the upstream provider,
263256// for example this entry is present in the v5 database:
264257// 312891,CVE-2020-7224,openvpn,alpine:distro:alpine:3.10,,< 0,apk,,"[{""id"":""CVE-2020-7224"",""namespace"":""nvd:cpe""}]","[""0""]",fixed,
265258// which indicates, for the alpine:3.10 distro, package openvpn is not vulnerable to CVE-2020-7224
266259// we want to report these NAK entries as match.IgnoredMatch, to allow for later processing to create ignore rules
267260// based on packages which overlap by location, such as a python binary found in addition to the python APK entry --
268261// we want to NAK this vulnerability for BOTH packages
269- func (m * Matcher ) findNaksForPackage (provider vulnerability.Provider , p pkg.Package , useUpstreamMatcher bool ) ([]match.IgnoreFilter , error ) {
262+ func (m * Matcher ) findNaksForPackage (provider vulnerability.Provider , p pkg.Package ) ([]match.IgnoreFilter , error ) {
270263 if p .Distro == nil {
271264 return nil , nil
272265 }
@@ -282,7 +275,7 @@ func (m *Matcher) findNaksForPackage(provider vulnerability.Provider, p pkg.Pack
282275 }
283276
284277 // append all the upstream naks
285- if useUpstreamMatcher {
278+ if m . useUpstreamForPackage ( p ) {
286279 for _ , upstreamPkg := range pkg .UpstreamPackages (p ) {
287280 upstreamNaks , err := provider .FindVulnerabilities (
288281 search .ByDistro (* upstreamPkg .Distro ),
0 commit comments