Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly match JVM version ranges #2114

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ bin/
/.task

# changelog generation
CHANGELOG.md
VERSION
/CHANGELOG.md
/VERSION

# IDE configuration
.vscode/
Expand Down
2 changes: 2 additions & 0 deletions cmd/grype/cli/options/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "github.com/anchore/clio"
// matchConfig contains all matching-related configuration options available to the user via the application config.
type matchConfig struct {
Java matcherConfig `yaml:"java" json:"java" mapstructure:"java"` // settings for the java matcher
JVM matcherConfig `yaml:"jvm" json:"jvm" mapstructure:"jvm"` // settings for the jvm matcher
Dotnet matcherConfig `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"` // settings for the dotnet matcher
Golang golangConfig `yaml:"golang" json:"golang" mapstructure:"golang"` // settings for the golang matcher
Javascript matcherConfig `yaml:"javascript" json:"javascript" mapstructure:"javascript"` // settings for the javascript matcher
Expand Down Expand Up @@ -43,6 +44,7 @@ func defaultMatchConfig() matchConfig {
dontUseCpe := matcherConfig{UseCPEs: false}
return matchConfig{
Java: dontUseCpe,
JVM: useCpe,
Dotnet: dontUseCpe,
Golang: defaultGolangConfig(),
Javascript: dontUseCpe,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f
github.com/anchore/stereoscope v0.0.3
github.com/anchore/syft v1.12.2
github.com/anchore/syft v1.12.3-0.20240918171143-1629043d7a0d
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/bmatcuk/doublestar/v2 v2.0.4
github.com/charmbracelet/bubbletea v1.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f h1:B/E9ixK
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
github.com/anchore/stereoscope v0.0.3 h1:JRPHySy8S6P+Ff3IDiQ29ap1i8/laUQxDk9K1eFh/2U=
github.com/anchore/stereoscope v0.0.3/go.mod h1:5DJheGPjVRsSqegTB24Zi6SCHnYQnA519yeIG+RG+I4=
github.com/anchore/syft v1.12.2 h1:K5YXJ2Ox4C3+Q+rA4jDpsLAoYNd27RMfinvY2JmbEiM=
github.com/anchore/syft v1.12.2/go.mod h1:xFMGMFmhWTK0CJvaKwz6OPVgRdcyCkl7QO/3O/JAXI0=
github.com/anchore/syft v1.12.3-0.20240918171143-1629043d7a0d h1:Pl1fOL2fNaT6kXpDXOhGSYT+hG9Mgur/PSxMnd1Z+yc=
github.com/anchore/syft v1.12.3-0.20240918171143-1629043d7a0d/go.mod h1:xFMGMFmhWTK0CJvaKwz6OPVgRdcyCkl7QO/3O/JAXI0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
Expand Down
2 changes: 2 additions & 0 deletions grype/cpe/cpe.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ func NewSlice(cpeStrs ...string) ([]cpe.CPE, error) {
func MatchWithoutVersion(c cpe.CPE, candidates []cpe.CPE) []cpe.CPE {
matches := make([]cpe.CPE, 0)
a := wfn.Attributes(c.Attributes)
a.Update = wfn.Any
for _, candidate := range candidates {
canCopy := wfn.Attributes(candidate.Attributes)
canCopy.Update = wfn.Any
if a.MatchWithoutVersion(&canCopy) {
matches = append(matches, candidate)
}
Expand Down
2 changes: 1 addition & 1 deletion grype/internal/packagemetadata/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import "github.com/anchore/grype/grype/pkg"

// AllTypes returns a list of all pkg metadata types that grype supports (that are represented in the pkg.Package.Metadata field).
func AllTypes() []any {
return []any{pkg.ApkMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.JavaMetadata{}, pkg.RpmMetadata{}}
return []any{pkg.ApkMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.JavaMetadata{}, pkg.JavaVMInstallationMetadata{}, pkg.RpmMetadata{}}
}
11 changes: 6 additions & 5 deletions grype/internal/packagemetadata/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import (
// not the same it may be important to select different names. This design decision has been deferred, for now
// the same metadata types that have been used in the past should be used here.
var jsonNameFromType = map[reflect.Type][]string{
reflect.TypeOf(pkg.ApkMetadata{}): nameList("ApkMetadata"),
reflect.TypeOf(pkg.GolangBinMetadata{}): nameList("GolangBinMetadata"),
reflect.TypeOf(pkg.GolangModMetadata{}): nameList("GolangModMetadata"),
reflect.TypeOf(pkg.JavaMetadata{}): nameList("JavaMetadata"),
reflect.TypeOf(pkg.RpmMetadata{}): nameList("RpmMetadata"),
reflect.TypeOf(pkg.ApkMetadata{}): nameList("ApkMetadata"),
reflect.TypeOf(pkg.GolangBinMetadata{}): nameList("GolangBinMetadata"),
reflect.TypeOf(pkg.GolangModMetadata{}): nameList("GolangModMetadata"),
reflect.TypeOf(pkg.JavaMetadata{}): nameList("JavaMetadata"),
reflect.TypeOf(pkg.RpmMetadata{}): nameList("RpmMetadata"),
reflect.TypeOf(pkg.JavaVMInstallationMetadata{}): nameList("JavaVMInstallationMetadata"),
}

//nolint:unparam
Expand Down
5 changes: 5 additions & 0 deletions grype/internal/packagemetadata/names_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func TestReflectTypeFromJSONName(t *testing.T) {
lookup: "RpmMetadata",
wantRecord: reflect.TypeOf(pkg.RpmMetadata{}),
},
{
name: "JavaVMInstallationMetadata lookup",
lookup: "JavaVMInstallationMetadata",
wantRecord: reflect.TypeOf(pkg.JavaVMInstallationMetadata{}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
153 changes: 153 additions & 0 deletions grype/matcher/stock/matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package stock

import (
"testing"

"github.com/google/uuid"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/grype/grype/distro"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/version"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/syft/syft/cpe"
syftPkg "github.com/anchore/syft/syft/pkg"
)

func TestMatcher_JVMPackage(t *testing.T) {
p := pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "java_se",
Version: "1.8.0_400",
Type: syftPkg.BinaryPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update400:*:*:*:*:*:*", cpe.DeclaredSource),
},
}
matcher := Matcher{
cfg: MatcherConfig{
UseCPEs: true,
},
}
store := newMockProvider()
actual, err := matcher.Match(store, nil, p)
require.NoError(t, err)

foundCVEs := strset.New()
for _, v := range actual {
foundCVEs.Add(v.Vulnerability.ID)

require.NotEmpty(t, v.Details)
for _, d := range v.Details {
assert.Equal(t, match.CPEMatch, d.Type, "indirect match not indicated")
assert.Equal(t, matcher.Type(), d.Matcher, "failed to capture matcher type")
}
assert.Equal(t, p.Name, v.Package.Name, "failed to capture original package name")
}

expected := strset.New(
"CVE-2024-20919-real",
"CVE-2024-20919-underscore",
"CVE-2024-20919-bonkers-format",
"CVE-2024-20919-post-jep223",
)

for _, id := range expected.List() {
if !foundCVEs.Has(id) {
t.Errorf("missing CVE: %s", id)
}
}

extra := strset.Difference(foundCVEs, expected)

for _, id := range extra.List() {
t.Errorf("unexpected CVE: %s", id)
}

if t.Failed() {
t.Logf("discovered CVES: %d", foundCVEs.Size())
for _, id := range foundCVEs.List() {
t.Logf(" - %s", id)
}
}
}

func newMockProvider() *mockProvider {
mp := mockProvider{
data: make(map[syftPkg.Language]map[string][]vulnerability.Vulnerability),
}

mp.populateData()

return &mp
}

type mockProvider struct {
data map[syftPkg.Language]map[string][]vulnerability.Vulnerability
}

func (mp *mockProvider) Get(_, _ string) ([]vulnerability.Vulnerability, error) {
// TODO implement me
panic("not implemented")
}

func (mp *mockProvider) populateData() {

// derived from vuln data found on CVE-2024-20919
hit := "< 1.8.0_401 || >= 1.9-ea, < 8.0.401 || >= 9-ea, < 11.0.22 || >= 12-ea, < 17.0.10 || >= 18-ea, < 21.0.2"

mp.data["nvd:cpe"] = map[string][]vulnerability.Vulnerability{
"java_se": {
{
// positive cases
Constraint: version.MustGetConstraint(hit, version.JVMFormat),
ID: "CVE-2024-20919-real",
},
{
// positive cases
Constraint: version.MustGetConstraint("< 22.22.22", version.UnknownFormat),
ID: "CVE-2024-20919-bonkers-format",
},
{
// positive cases
Constraint: version.MustGetConstraint(hit, version.JVMFormat),
ID: "CVE-2024-20919-underscore",
},
{
// negative case
Constraint: version.MustGetConstraint("< 1.8.0_399 || >= 1.9-ea, < 8.0.399 || >= 9-ea, < 11.0.22 || >= 12-ea, < 17.0.10 || >= 18-ea, < 21.0.2", version.JVMFormat),
ID: "CVE-FAKE-bad-update",
},
{
// positive case
Constraint: version.MustGetConstraint("< 8.0.401", version.JVMFormat),
ID: "CVE-2024-20919-post-jep223",
},
{
// negative case
Constraint: version.MustGetConstraint("< 8.0.399", version.JVMFormat),
ID: "CVE-FAKE-bad-range-post-jep223",
},
{
// negative case
Constraint: version.MustGetConstraint("< 7.0.0", version.JVMFormat),
ID: "CVE-FAKE-bad-range-post-jep223",
},
},
}
}

func (mp *mockProvider) GetByCPE(p cpe.CPE) ([]vulnerability.Vulnerability, error) {
return mp.data["nvd:cpe"][p.Attributes.Product], nil
}

func (mp *mockProvider) GetByDistro(d *distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
return []vulnerability.Vulnerability{}, nil
}

func (mp *mockProvider) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]vulnerability.Vulnerability, error) {
return mp.data[l][p.Name], nil
}
37 changes: 37 additions & 0 deletions grype/pkg/java_metadata.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package pkg

import (
"github.com/scylladb/go-set/strset"

"github.com/anchore/syft/syft/pkg"
)

type JavaMetadata struct {
VirtualPath string `json:"virtualPath"`
PomArtifactID string `json:"pomArtifactID"`
Expand All @@ -12,3 +18,34 @@ type Digest struct {
Algorithm string `json:"algorithm"`
Value string `json:"value"`
}

type JavaVMInstallationMetadata struct {
Release JavaVMReleaseMetadata `json:"release,omitempty"`
}

type JavaVMReleaseMetadata struct {
JavaRuntimeVersion string `json:"javaRuntimeVersion,omitempty"`
JavaVersion string `json:"javaVersion,omitempty"`
FullVersion string `json:"fullVersion,omitempty"`
SemanticVersion string `json:"semanticVersion,omitempty"`
}

func IsJvmPackage(p Package) bool {
if _, ok := p.Metadata.(JavaVMInstallationMetadata); ok {
return true
}

if p.Type == pkg.BinaryPkg {
if HasJvmPackageName(p.Name) {
return true
}
}

return false
}

var jvmIndications = strset.New("java_se", "jre", "jdk", "zulu", "openjdk", "java", "java/jre", "java/jdk")

func HasJvmPackageName(name string) bool {
return jvmIndications.Has(name)
}
Loading
Loading