Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions grype/matcher/internal/cpe.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,8 @@ func MatchPackageByCPEs(provider vulnerability.Provider, p pkg.Package, upstream

format := pkg.VersionFormat(p)

if format == version.JVMFormat {
searchVersion = transformJvmVersion(searchVersion, c.Attributes.Update)
}
// Apply version and update field combination for all package types
searchVersion = combineVersionAndUpdate(searchVersion, c.Attributes.Update, format)

var verObj *version.Version
var err error
Expand Down Expand Up @@ -111,6 +110,27 @@ func transformJvmVersion(searchVersion, updateCpeField string) string {
return searchVersion
}

func combineVersionAndUpdate(searchVersion, updateCpeField string, format version.Format) string {
// Handle empty or wildcard update fields
if updateCpeField == "" || updateCpeField == wfn.NA || updateCpeField == wfn.Any {
return searchVersion
}

// Don't combine if the version is empty or a wildcard - these should remain as is
if searchVersion == "" || searchVersion == wfn.NA || searchVersion == wfn.Any {
return searchVersion
}

// Special handling for JVM packages
if format == version.JVMFormat {
return transformJvmVersion(searchVersion, updateCpeField)
}

// For other packages, combine version and update field directly
// This handles cases like NTP where version="4.2.8" and update="p18" should become "4.2.8p18"
return fmt.Sprintf("%s%s", searchVersion, updateCpeField)
}

func addNewMatch(matchesByFingerprint map[match.Fingerprint]match.Match, vuln vulnerability.Vulnerability, p pkg.Package, searchVersion *version.Version, upstreamMatcher match.MatcherType, searchedByCPE cpe.CPE) {
candidateMatch := match.Match{

Expand Down Expand Up @@ -205,11 +225,8 @@ func filterCPEsByVersion(pkgVersion *version.Version, allCPEs []cpe.CPE) (matche

ver := c.Attributes.Version

if pkgVersion.Format == version.JVMFormat {
if c.Attributes.Update != wfn.Any && c.Attributes.Update != wfn.NA {
ver = transformJvmVersion(ver, c.Attributes.Update)
}
}
// Apply version and update field combination for all package types
ver = combineVersionAndUpdate(ver, c.Attributes.Update, pkgVersion.Format)

constraint, err := version.GetConstraint(ver, pkgVersion.Format)
if err != nil {
Expand Down
95 changes: 95 additions & 0 deletions grype/matcher/internal/cpe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,101 @@ func TestFilterCPEsByVersion(t *testing.T) {
}
}

func TestCombineVersionAndUpdate(t *testing.T) {
tests := []struct {
name string
searchVersion string
updateCpeField string
format version.Format
expectedVersion string
}{
{
name: "non-JVM package with update field (NTP example)",
searchVersion: "4.2.8",
updateCpeField: "p18",
format: version.UnknownFormat,
expectedVersion: "4.2.8p18",
},
{
name: "non-JVM package without update field",
searchVersion: "1.2.3",
updateCpeField: "",
format: version.UnknownFormat,
expectedVersion: "1.2.3",
},
{
name: "non-JVM package with Any update field",
searchVersion: "1.2.3",
updateCpeField: "",
format: version.UnknownFormat,
expectedVersion: "1.2.3",
},
{
name: "non-JVM package with NA update field",
searchVersion: "1.2.3",
updateCpeField: "-",
format: version.UnknownFormat,
expectedVersion: "1.2.3",
},
{
name: "JVM package with update field",
searchVersion: "1.8.0",
updateCpeField: "update400",
format: version.JVMFormat,
expectedVersion: "1.8.0_400",
},
{
name: "JVM package without update field (Any)",
searchVersion: "1.8.0",
updateCpeField: "",
format: version.JVMFormat,
expectedVersion: "1.8.0",
},
{
name: "JVM package without update field (NA)",
searchVersion: "1.8.0",
updateCpeField: "-",
format: version.JVMFormat,
expectedVersion: "1.8.0",
},
{
name: "semantic package with patch update",
searchVersion: "2.4.1",
updateCpeField: "rc1",
format: version.SemanticFormat,
expectedVersion: "2.4.1rc1",
},
{
name: "debian package with update",
searchVersion: "1.0.0",
updateCpeField: "deb1",
format: version.DebFormat,
expectedVersion: "1.0.0deb1",
},
{
name: "non-JVM package with wildcard version should not combine",
searchVersion: "",
updateCpeField: "update123",
format: version.UnknownFormat,
expectedVersion: "",
},
{
name: "non-JVM package with NA version should not combine",
searchVersion: "-",
updateCpeField: "update123",
format: version.UnknownFormat,
expectedVersion: "-",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := combineVersionAndUpdate(test.searchVersion, test.updateCpeField, test.format)
assert.Equal(t, test.expectedVersion, result)
})
}
}

func TestAddMatchDetails(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading