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

Feat/grype 86 distro from purl #1530

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
17 changes: 17 additions & 0 deletions grype/pkg/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/anchore/grype/internal/log"
"github.com/anchore/grype/internal/stringutil"
packageurl "github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
Expand Down Expand Up @@ -100,6 +101,22 @@ func (p Package) String() string {
return fmt.Sprintf("Pkg(type=%s, name=%s, version=%s, upstreams=%d)", p.Type, p.Name, p.Version, len(p.Upstreams))
}

func (p Package) DistroFragmentFromPURL() string {
if p.PURL == "" {
return ""
}
purl, err := packageurl.FromString(p.PURL)
if err != nil {
return ""
}
for k, v := range purl.Qualifiers.Map() {
if k == "distro" && v != "" {
return v
}
}
return ""
}

func removePackagesByOverlap(catalog *pkg.Collection, relationships []artifact.Relationship, distro *linux.Release) *pkg.Collection {
byOverlap := map[artifact.ID]artifact.Relationship{}
for _, r := range relationships {
Expand Down
50 changes: 45 additions & 5 deletions grype/vulnerability_matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,16 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con
var ignoredMatches []match.IgnoredMatch

log.Trace("finding matches against DB")
matches, err := m.searchDBForMatches(context.Distro, pkgs, progressMonitor)
if err != nil {
return nil, nil, fmt.Errorf("unable to find matches in DB: %w", err)
// TODO: split pkgs by distro
packagesByDistro := groupPackagesByDistro(context.Distro, pkgs)
matches := match.NewMatches()
for d, ps := range packagesByDistro {
release := linuxReleaseFromDistroString(d, context.Distro)
toAdd, err := m.searchDBForMatches(release, ps, progressMonitor)
if err != nil {
return nil, nil, fmt.Errorf("unable to find matches in DB: %w", err)
}
matches.Add(toAdd.Sorted()...)
}

matches, ignoredMatches = m.applyIgnoreRules(matches)
Expand All @@ -115,10 +122,42 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con
}

if m.FailSeverity != nil && HasSeverityAtOrAbove(m.Store, *m.FailSeverity, matches) {
err = grypeerr.ErrAboveSeverityThreshold
return &matches, ignoredMatches, grypeerr.ErrAboveSeverityThreshold
}

return &matches, ignoredMatches, nil
}

func linuxReleaseFromDistroString(d string, detected *linux.Release) *linux.Release {
parts := strings.SplitN(d, "-", 2)
if len(parts) == 2 {
return &linux.Release{
ID: parts[0],
Version: parts[1],
}
}
return detected
}

func groupPackagesByDistro(detectedDistro *linux.Release, pkgs []pkg.Package) map[string][]pkg.Package {
packagesByDistro := make(map[string][]pkg.Package)
for _, p := range pkgs {
packageDistro := p.DistroFragmentFromPURL()
if packageDistro == "" {
// If we don't have a distro from the PURL
// assume that the packages was installed from the distro
// detected on the source
// detectedDistro.String() never has the same format as
// the distro fragment from the PURL
// TODO: which counts on a coincidence
if detectedDistro != nil {
packageDistro = detectedDistro.String()
}
}
packagesByDistro[packageDistro] = append(packagesByDistro[packageDistro], p)
}

return &matches, ignoredMatches, err
return packagesByDistro
}

func (m *VulnerabilityMatcher) searchDBForMatches(
Expand All @@ -138,6 +177,7 @@ func (m *VulnerabilityMatcher) searchDBForMatches(
}
if d != nil && d.Disabled() {
log.Warnf("unsupported linux distribution: %s", d.Name())
// TODO: unsupported distro with supported packages should be allowed
return match.NewMatches(), nil
}
}
Expand Down
128 changes: 128 additions & 0 deletions grype/vulnerability_matcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,21 @@ func defaultStubFn(d *mockStore) {
},
}

d.metadata["ALAS-2023-351"] = map[string]*grypeDB.VulnerabilityMetadata{
"amazon:distro:amazonlinux:2023": {
ID: "ALAS-2023-351",
Namespace: "amazon:distro:amazonlinux:2023",
Severity: "medium",
},
}
d.metadata["ALAS-2023-348"] = map[string]*grypeDB.VulnerabilityMetadata{
"amazon:distro:amazonlinux:2023": {
ID: "ALAS-2023-348",
Namespace: "amazon:distro:amazonlinux:2023",
Severity: "medium",
},
}

// VULNERABILITIES ///////////////////////////////////////////////////////////////////////////
d.vulnerabilities["debian:distro:debian:8"] = map[string][]grypeDB.Vulnerability{
"neutron": {
Expand All @@ -146,6 +161,28 @@ func defaultStubFn(d *mockStore) {
},
},
}

d.vulnerabilities["amazon:distro:amazonlinux:2023"] = map[string][]grypeDB.Vulnerability{
"libtiff-tools": {
{
PackageName: "libtiff-tools",
Namespace: "amazon:distro:amazonlinux:2023",
VersionConstraint: "< 4.4.0-4.amzn2023.0.14",
ID: "ALAS-2023-351",
VersionFormat: "rpm",
},
},
"wireshark-cli": {
{
ID: "ALAS-2023-348",
PackageName: "wireshark-cli",
VersionConstraint: "< 4.0.8-2.amzn2023.0.1",
VersionFormat: "rpm",
Namespace: "amazon:distro:amazonlinux:2023",
},
},
}

d.vulnerabilities["github:language:ruby"] = map[string][]grypeDB.Vulnerability{
"activerecord": {
{
Expand Down Expand Up @@ -321,6 +358,14 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
Language: syftPkg.Ruby,
}

wiresharkPackage := pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "wireshark-cli",
Version: "4.0.6-2.amzn2023.0.1",
Type: syftPkg.RpmPkg,
PURL: "pkg:rpm/amzn/[email protected]?arch=aarch64&epoch=1&upstream=wireshark-4.0.6-2.amzn2023.0.1.src.rpm&distro=amzn-2023",
}

type fields struct {
Store store.Store
Matchers []matcher.Matcher
Expand Down Expand Up @@ -413,6 +458,89 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
wantIgnoredMatches: nil,
wantErr: nil,
},
{
name: "matches from multiple distros: rpm on deb8",
fields: fields{
Store: str,
Matchers: matcher.NewDefaultMatchers(matcher.Config{}),
},
args: args{
pkgs: []pkg.Package{
neutron2013Pkg,
wiresharkPackage,
},
context: pkg.Context{
Distro: &linux.Release{
ID: "debian",
VersionID: "8",
},
},
},
wantMatches: match.NewMatches(
match.Match{
Vulnerability: vulnerability.Vulnerability{
Constraint: version.MustGetConstraint("< 2014.1.3-6", version.DebFormat),
ID: "CVE-2014-fake-1",
Namespace: "debian:distro:debian:8",
PackageQualifiers: []qualifier.Qualifier{},
CPEs: []cpe.CPE{},
Advisories: []vulnerability.Advisory{},
},
Package: neutron2013Pkg,
Details: match.Details{
{
Type: match.ExactDirectMatch,
SearchedBy: map[string]any{
"distro": map[string]string{"type": "debian", "version": "8"},
"namespace": "debian:distro:debian:8",
"package": map[string]string{"name": "neutron", "version": "2013.1.1-1"},
},
Found: map[string]any{
"versionConstraint": "< 2014.1.3-6 (deb)",
"vulnerabilityID": "CVE-2014-fake-1",
},
Matcher: "dpkg-matcher",
Confidence: 1,
},
},
},
match.Match{
Vulnerability: vulnerability.Vulnerability{
Constraint: version.MustGetConstraint("", version.RpmFormat),
PackageQualifiers: []qualifier.Qualifier{},
CPEs: []cpe.CPE{},
ID: "ALAS-2023-348",
Namespace: "amazon:distro:amazonlinux:2023",
Advisories: []vulnerability.Advisory{},
},
Package: wiresharkPackage,
Details: match.Details{
{
Type: match.ExactDirectMatch,
Matcher: "rpm-matcher",
SearchedBy: map[string]any{
"distro": map[string]string{
"type": "amazonlinux",
"version": "2023",
},
"namespace": "amazon:distro:amazonlinux:2023",
"package": map[string]string{
"name": "wireshark-cli",
"version": "0:4.0.6-2.amzn2023.0.1",
},
},
Found: map[string]any{
"versionConstraint": "< 4.0.8-2.amzn2023.0.1 (rpm)",
"vulnerabilityID": "ALAS-2023-348",
},
Confidence: 1.0,
},
},
},
),
wantIgnoredMatches: nil,
wantErr: nil,
},
{
name: "fail on severity threshold",
fields: fields{
Expand Down
Loading