From df8cb75d2db5a98cff0e3c000dcc0e8488558f68 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Wed, 27 Sep 2023 09:37:20 -0400 Subject: [PATCH 1/4] add failing test Signed-off-by: Will Murphy --- grype/vulnerability_matcher_test.go | 147 ++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go index 0e856170698..a43286be58e 100644 --- a/grype/vulnerability_matcher_test.go +++ b/grype/vulnerability_matcher_test.go @@ -78,6 +78,21 @@ func (d *mockStore) stub() { }, } + 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": { @@ -97,6 +112,28 @@ func (d *mockStore) stub() { }, }, } + + 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": { { @@ -309,6 +346,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/wireshark-cli@4.0.6-2.amzn2023.0.1?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 @@ -401,6 +446,108 @@ 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]any{ + "type": "amazonlinux", + "version": "2023", + }, + "namespace": "amazon:distro:amazonlinux:2023", + "package": map[string]any{ + "name": "wireshark", + "version": "4.0.6-2.amzn2023.0.1", + }, + }, + Found: map[string]any{ + "versionConstraint": "< 4.0.8-2.amzn2023.0.1 (rpm)", + "vulnerabilityID": "ALAS-2023-348", + }, + }, + { + Type: match.ExactDirectMatch, + Matcher: "rpm-matcher", + SearchedBy: map[string]any{ + "distro": map[string]any{ + "type": "amazonlinux", + "version": "2023", + }, + "namespace": "amazon:distro:amazonlinux:2023", + "package": map[string]any{ + "name": "wireshark-cli", + "version": "1:4.0.6-2.amzn2023.0.1", + }, + }, + Found: map[string]any{ + "versionConstraint": "< 4.0.8-2.amzn2023.0.1 (rpm)", + "vulnerabilityID": "ALAS-2023-348", + }, + }, + }, + }, + ), + wantIgnoredMatches: nil, + wantErr: nil, + }, + { name: "fail on severity threshold", fields: fields{ From 57472fb467b3f828d3256d0d4cf87a3e348da3de Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 29 Sep 2023 07:24:20 -0400 Subject: [PATCH 2/4] WIP: new test for feature passes Signed-off-by: Will Murphy --- grype/pkg/package.go | 18 ++++++++++++++ grype/vulnerability_matcher.go | 38 ++++++++++++++++++++++++++++- grype/vulnerability_matcher_test.go | 26 +++----------------- 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/grype/pkg/package.go b/grype/pkg/package.go index 5b65015a86d..043b637ffb1 100644 --- a/grype/pkg/package.go +++ b/grype/pkg/package.go @@ -7,11 +7,13 @@ 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" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" + cpes "github.com/anchore/syft/syft/pkg/cataloger/common/cpe" ) @@ -102,6 +104,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 { diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index 83c3799cd3c..2c9abb7eaa2 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -93,7 +93,15 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con var ignoredMatches []match.IgnoredMatch log.Trace("finding matches against DB") - matches := m.searchDBForMatches(context.Distro, pkgs, progressMonitor) + // TODO: split pkgs by distro + packagesByDistro := groupPackagesByDistro(context.Distro, pkgs) + matches := match.NewMatches() + for d, ps := range packagesByDistro { + release := linuxReleaseFromDistroString(d, context.Distro) + toAdd := m.searchDBForMatches(release, ps, progressMonitor) + matches.Add(toAdd.Sorted()...) + } + //matches := m.searchDBForMatches(context.Distro, pkgs, progressMonitor) matches, ignoredMatches = m.applyIgnoreRules(matches) @@ -118,6 +126,33 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con return &matches, ignoredMatches, err } +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 + packageDistro = detectedDistro.String() + } + packagesByDistro[packageDistro] = append(packagesByDistro[packageDistro], p) + } + + return packagesByDistro +} + func (m *VulnerabilityMatcher) searchDBForMatches( release *linux.Release, packages []pkg.Package, @@ -135,6 +170,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() } } diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go index a43286be58e..f2bd35e75c5 100644 --- a/grype/vulnerability_matcher_test.go +++ b/grype/vulnerability_matcher_test.go @@ -507,39 +507,21 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { Type: match.ExactDirectMatch, Matcher: "rpm-matcher", SearchedBy: map[string]any{ - "distro": map[string]any{ + "distro": map[string]string{ "type": "amazonlinux", "version": "2023", }, "namespace": "amazon:distro:amazonlinux:2023", - "package": map[string]any{ - "name": "wireshark", - "version": "4.0.6-2.amzn2023.0.1", - }, - }, - Found: map[string]any{ - "versionConstraint": "< 4.0.8-2.amzn2023.0.1 (rpm)", - "vulnerabilityID": "ALAS-2023-348", - }, - }, - { - Type: match.ExactDirectMatch, - Matcher: "rpm-matcher", - SearchedBy: map[string]any{ - "distro": map[string]any{ - "type": "amazonlinux", - "version": "2023", - }, - "namespace": "amazon:distro:amazonlinux:2023", - "package": map[string]any{ + "package": map[string]string{ "name": "wireshark-cli", - "version": "1:4.0.6-2.amzn2023.0.1", + "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, }, }, }, From 3966a499d2d53397f933a6fd2886c080de7dc31e Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 29 Sep 2023 07:35:19 -0400 Subject: [PATCH 3/4] appease linter Signed-off-by: Will Murphy --- grype/pkg/package.go | 1 - grype/vulnerability_matcher.go | 1 - grype/vulnerability_matcher_test.go | 1 - 3 files changed, 3 deletions(-) diff --git a/grype/pkg/package.go b/grype/pkg/package.go index 043b637ffb1..ef51d737398 100644 --- a/grype/pkg/package.go +++ b/grype/pkg/package.go @@ -13,7 +13,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - cpes "github.com/anchore/syft/syft/pkg/cataloger/common/cpe" ) diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index 2c9abb7eaa2..a48c0e24d40 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -101,7 +101,6 @@ func (m *VulnerabilityMatcher) findDBMatches(pkgs []pkg.Package, context pkg.Con toAdd := m.searchDBForMatches(release, ps, progressMonitor) matches.Add(toAdd.Sorted()...) } - //matches := m.searchDBForMatches(context.Distro, pkgs, progressMonitor) matches, ignoredMatches = m.applyIgnoreRules(matches) diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go index f2bd35e75c5..a4815220ba1 100644 --- a/grype/vulnerability_matcher_test.go +++ b/grype/vulnerability_matcher_test.go @@ -529,7 +529,6 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) { wantIgnoredMatches: nil, wantErr: nil, }, - { name: "fail on severity threshold", fields: fields{ From dbefe9528425047e335cf86d97ee28bcad1fece7 Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Fri, 29 Sep 2023 08:01:46 -0400 Subject: [PATCH 4/4] track one more TODO Signed-off-by: Will Murphy --- grype/vulnerability_matcher.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/grype/vulnerability_matcher.go b/grype/vulnerability_matcher.go index a48c0e24d40..7f12113fd75 100644 --- a/grype/vulnerability_matcher.go +++ b/grype/vulnerability_matcher.go @@ -144,7 +144,12 @@ func groupPackagesByDistro(detectedDistro *linux.Release, pkgs []pkg.Package) ma // If we don't have a distro from the PURL // assume that the packages was installed from the distro // detected on the source - packageDistro = detectedDistro.String() + // 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) }