From de25eb90439101ac40660013109c60ffeba190ed Mon Sep 17 00:00:00 2001 From: Weston Steimel Date: Wed, 8 Mar 2023 10:14:42 +0000 Subject: [PATCH] fix: correct APK CPE version comparison logic Previously, the -r{buildindex} suffix of APK package versions were treated as pre-release versions per the fuzzy matcher logic; however, these should be treated as equivalent to the release version for the purposes of collecting CPE-based matches for APK packages. We may want to make a similar change in syft to generate cleaner CPE versions for APK packages, but making the change in grype corrects behaviour for previously-generated SBOMs as well. Signed-off-by: Weston Steimel --- grype/matcher/apk/matcher.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/grype/matcher/apk/matcher.go b/grype/matcher/apk/matcher.go index 2fb8bc98b8a4..2db14cfaddf2 100644 --- a/grype/matcher/apk/matcher.go +++ b/grype/matcher/apk/matcher.go @@ -2,6 +2,7 @@ package apk import ( "fmt" + "strings" "github.com/anchore/grype/grype/distro" "github.com/anchore/grype/grype/match" @@ -45,7 +46,8 @@ func (m *Matcher) Match(store vulnerability.Provider, d *distro.Distro, p pkg.Pa 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, p, m.Type()) + cpePackage := cpeComparablePackage(p) + cpeMatches, err := search.ByPackageCPE(store, cpePackage, m.Type()) if err != nil { return nil, err } @@ -101,6 +103,36 @@ cveLoop: return finalCpeMatches, nil } +func cpeComparablePackage(p pkg.Package) pkg.Package { + // clean the alpine package version so that it compares correctly with the CPE version comparison logic + // alpine versions are suffixed with -r{buildindex}; however, if left intact CPE comparison logic will + // incorrectly treat these as a pre-release. In actuality, we just want to treat 1.2.3-r21 as equivalent to + // 1.2.3 for purposes of CPE-based matching since the alpine fix should filter out any cases where a later + // build fixes something that was vulnerable in 1.2.3 + components := strings.Split(p.Version, "-r") + cpeComparableVersion := p.Version + + if len(components) == 2 { + cpeComparableVersion = components[0] + } + + if cpeComparableVersion == p.Version { + return p + } + + t := p + t.Version = cpeComparableVersion + t.CPEs = nil + t.CPEs = append(t.CPEs, p.CPEs...) + + for index, cpe := range t.CPEs { + cpe.Version = cpeComparableVersion + t.CPEs[index] = cpe + } + + return t +} + 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)