From f6f3830e4cf5826a7151e4bcec799b5f892d5f78 Mon Sep 17 00:00:00 2001 From: Hank Donnay Date: Mon, 5 Jan 2026 16:47:25 -0600 Subject: [PATCH] rpmver: move to internal rpm version package Signed-off-by: Hank Donnay Change-Id: I5f0bdbae3d18c9e87be919712d86d91f7be3ad35 --- aws/matcher.go | 22 +++-------------- go.mod | 1 - go.sum | 2 -- oracle/matcher.go | 25 ++++++-------------- photon/matcher.go | 25 ++++++-------------- pkg/rhctag/version.go | 55 +++++++++++++++++++++++++++++-------------- rhel/matcher.go | 21 ++--------------- rhel/rhcc/matcher.go | 16 +++++++++---- suse/matcher.go | 25 ++++++-------------- 9 files changed, 75 insertions(+), 117 deletions(-) diff --git a/aws/matcher.go b/aws/matcher.go index 3207bab29..20db63cf1 100644 --- a/aws/matcher.go +++ b/aws/matcher.go @@ -3,9 +3,8 @@ package aws import ( "context" - version "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpm" "github.com/quay/claircore/libvuln/driver" ) @@ -43,21 +42,6 @@ func (*Matcher) Query() []driver.MatchConstraint { } } -func (*Matcher) Vulnerable(_ context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { - pkgVer := version.NewVersion(record.Package.Version) - var vulnVer version.Version - // Assume the vulnerability record we have is for the last known vulnerable - // version, so greater versions aren't vulnerable. - cmp := func(i int) bool { return i != version.GREATER } - // But if it's explicitly marked as a fixed-in version, it's only vulnerable - // if less than that version. - if vuln.FixedInVersion != "" { - vulnVer = version.NewVersion(vuln.FixedInVersion) - cmp = func(i int) bool { return i == version.LESS } - } else { - // If a vulnerability doesn't have FixedInVersion, assume it is unfixed. - vulnVer = version.NewVersion("65535:0") - } - // compare version and architecture - return cmp(pkgVer.Compare(vulnVer)) && vuln.ArchOperation.Cmp(record.Package.Arch, vuln.Package.Arch), nil +func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { + return rpm.MatchVulnerable(ctx, record, vuln) } diff --git a/go.mod b/go.mod index f4fc3bb99..adf929e75 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/klauspost/compress v1.18.4 github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d - github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 github.com/package-url/packageurl-go v0.1.4 github.com/prometheus/client_golang v1.23.2 github.com/quay/claircore/toolkit v1.4.0 diff --git a/go.sum b/go.sum index 7ecb4c2bf..874cf1277 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f h1:GvCU5GX github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f/go.mod h1:q59u9px8b7UTj0nIjEjvmTWekazka6xIt6Uogz5Dm+8= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4Kn3JPupAwwWuo4AzYp16P0OyLO9d7OnMZc/c= github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao= -github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936 h1:HDjRqotkViMNcGMGicb7cgxklx8OwnjtCBmyWEqrRvM= -github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936/go.mod h1:i4sF0l1fFnY1aiw08QQSwVAFxHEm311Me3WsU/X7nL0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/oracle/matcher.go b/oracle/matcher.go index 555df7f65..a43f16125 100644 --- a/oracle/matcher.go +++ b/oracle/matcher.go @@ -3,9 +3,8 @@ package oracle import ( "context" - version "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpm" "github.com/quay/claircore/libvuln/driver" ) @@ -14,17 +13,17 @@ const ( OSReleaseName = "Oracle Linux Server" ) -// Matcher implements driver.Matcher +// Matcher is an Oracle Linux matcher. type Matcher struct{} var _ driver.Matcher = (*Matcher)(nil) -// Name implements driver.Matcher +// Name implements [driver.Matcher]. func (*Matcher) Name() string { return "oracle" } -// Filter implements driver.Matcher +// Filter implements [driver.Matcher]. func (*Matcher) Filter(record *claircore.IndexRecord) bool { if record.Distribution == nil { return false @@ -40,7 +39,7 @@ func (*Matcher) Filter(record *claircore.IndexRecord) bool { } } -// Query implements driver.Matcher +// Query implements [driver.Matcher]. func (*Matcher) Query() []driver.MatchConstraint { return []driver.MatchConstraint{ driver.DistributionDID, @@ -49,17 +48,7 @@ func (*Matcher) Query() []driver.MatchConstraint { } } -// Vulnerable implements driver.Matcher +// Vulnerable implements [driver.Matcher]. func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { - pkgVer, vulnVer := version.NewVersion(record.Package.Version), version.NewVersion(vuln.Package.Version) - // Assume the vulnerability record we have is for the last known vulnerable - // version, so greater versions aren't vulnerable. - cmp := func(i int) bool { return i != version.GREATER } - // But if it's explicitly marked as a fixed-in version, it't only vulnerable - // if less than that version. - if vuln.FixedInVersion != "" { - vulnVer = version.NewVersion(vuln.FixedInVersion) - cmp = func(i int) bool { return i == version.LESS } - } - return cmp(pkgVer.Compare(vulnVer)) && vuln.ArchOperation.Cmp(record.Package.Arch, vuln.Package.Arch), nil + return rpm.MatchVulnerable(ctx, record, vuln) } diff --git a/photon/matcher.go b/photon/matcher.go index 13fdeb411..bfdacc6d8 100644 --- a/photon/matcher.go +++ b/photon/matcher.go @@ -3,29 +3,28 @@ package photon import ( "context" - version "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpm" "github.com/quay/claircore/libvuln/driver" ) -// Matcher implements driver.Matcher. +// Matcher implements [driver.Matcher]. type Matcher struct{} var _ driver.Matcher = (*Matcher)(nil) -// Name implements driver.Matcher. +// Name implements [driver.Matcher]. func (*Matcher) Name() string { return "photon" } -// Filter implements driver.Matcher. +// Filter implements [driver.Matcher]. func (*Matcher) Filter(record *claircore.IndexRecord) bool { return record.Distribution != nil && record.Distribution.DID == "photon" } -// Query implements driver.Matcher. +// Query implements [driver.Matcher]. func (*Matcher) Query() []driver.MatchConstraint { return []driver.MatchConstraint{ driver.DistributionDID, @@ -34,17 +33,7 @@ func (*Matcher) Query() []driver.MatchConstraint { } } -// Vulnerable implements driver.Matcher. +// Vulnerable implements [driver.Matcher]. func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { - pkgVer, vulnVer := version.NewVersion(record.Package.Version), version.NewVersion(vuln.Package.Version) - // Assume the vulnerability record we have is for the last known vulnerable - // version, so greater versions aren't vulnerable. - cmp := func(i int) bool { return i != version.GREATER } - // But if it's explicitly marked as a fixed-in version, it't only vulnerable - // if less than that version. - if vuln.FixedInVersion != "" { - vulnVer = version.NewVersion(vuln.FixedInVersion) - cmp = func(i int) bool { return i == version.LESS } - } - return cmp(pkgVer.Compare(vulnVer)), nil + return rpm.MatchVulnerable(ctx, record, vuln) } diff --git a/pkg/rhctag/version.go b/pkg/rhctag/version.go index 7b93b1d2d..7fe93cd53 100644 --- a/pkg/rhctag/version.go +++ b/pkg/rhctag/version.go @@ -3,33 +3,39 @@ package rhctag import ( + "errors" "fmt" "math" "strconv" "strings" - rpmVersion "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpmver" ) -// Allows extracting the Major and Minor versions so that we don't compare container tags from different minor versions. +// Version allows extracting the "major" and "minor" versions so that we don't compare container tags from different minor versions. +// // This is a workaround for another problem where not all minor releases of containers have a unique CPE. -// Take for example ocs4/rook-ceph-rhel8-operator, it shipped at least 2 minor releases into the same container repository. -// Both those major versions use the same CPE "cpe:/a:redhat:openshift_container_storage:4" -// Here are 2 example tags which fixed CVE-2020-8565 -// 4.7 minor: 4.7-140.49a6fcf.release_4.7 -// 4.8 minor: 4.8-167.9a9db5f.release_4.8 - -// This class also handles container tags which have a 'v' prefix, for example openshift4/ose-metering-hive -// 4.6 minor: v4.6.0-202112140546.p0.g8b9da97.assembly.stream -// 4.7 minor: v4.7.0-202112140553.p0.g091bb99.assembly.stream +// +// For example, take "ocs4/rook-ceph-rhel8-operator": it shipped at least 2 minor releases into the same container repository. +// Both those major versions use the same CPE: "cpe:/a:redhat:openshift_container_storage:4". +// Here are 2 example tags which fixed CVE-2020-8565: +// - 4.7-140.49a6fcf.release_4.7 +// - 4.8-167.9a9db5f.release_4.8 +// +// This type also handles container tags which have a 'v' prefix (e.g. "openshift4/ose-metering-hive"). +// - v4.6.0-202112140546.p0.g8b9da97.assembly.stream +// - v4.7.0-202112140553.p0.g091bb99.assembly.stream type Version struct { Original string Major int Minor int } +// Version turns the reciever into a [claircore.Version]. +// +// "Min" controls whether the returned version is the minimum within the minor +// series or the maximum. func (v *Version) Version(min bool) (c claircore.Version) { const ( major = 0 @@ -64,7 +70,7 @@ func upToDot(s string) (value int, remainder string, err error) { if err == nil { return value, remainder, nil } - return value, remainder, fmt.Errorf("Could not parse %s as an int", s) + return value, remainder, fmt.Errorf("could not parse %q as int", s) } // Parse attempts to extract a Red Hat container registry tag version string @@ -75,7 +81,7 @@ func Parse(s string) (v Version, err error) { // remove the leading "v" prefix canonical = s[1:] } - //strip revision + // strip revision dashIndex := strings.Index(canonical, "-") if dashIndex > 0 { canonical = canonical[:dashIndex] @@ -105,13 +111,26 @@ func (v *Version) MinorStart() (start Version) { return start } +// Compare is a comparison for the provided [Version]s. func (v *Version) Compare(x *Version) int { - thisRpmVersion := rpmVersion.NewVersion(v.Original) - otherRpmVersion := rpmVersion.NewVersion(x.Original) - return thisRpmVersion.Compare(otherRpmVersion) + a, aErr := rpmver.Parse(v.toEVR()) + b, bErr := rpmver.Parse(x.toEVR()) + if err := errors.Join(aErr, bErr); err != nil { + panic(fmt.Errorf("unable to compare versions: %w", err)) + } + return rpmver.Compare(&a, &b) +} + +func (v *Version) toEVR() string { + s := v.Original + s = strings.TrimPrefix(s, "v") + if !strings.Contains(s, "-") { + s = s + "-0" + } + return s } -// Versions implements sort.Interface. +// Versions implements [sort.Interface]. type Versions []Version func (vs Versions) Len() int { diff --git a/rhel/matcher.go b/rhel/matcher.go index e5dfd3687..58b937a45 100644 --- a/rhel/matcher.go +++ b/rhel/matcher.go @@ -5,9 +5,8 @@ import ( "log/slog" "strings" - version "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpm" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/toolkit/types/cpe" ) @@ -79,21 +78,5 @@ func (m *Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, return false, nil } - // TODO(hank) Switch to the [rpmver] package. - pkgVer := version.NewVersion(record.Package.Version) - var vulnVer version.Version - // Assume the vulnerability record we have is for the last known vulnerable - // version, so greater versions aren't vulnerable. - cmp := func(i int) bool { return i != version.GREATER } - // But if it's explicitly marked as a fixed-in version, it's only vulnerable - // if less than that version. - if vuln.FixedInVersion != "" { - vulnVer = version.NewVersion(vuln.FixedInVersion) - cmp = func(i int) bool { return i == version.LESS } - } else { - // If a vulnerability doesn't have FixedInVersion, assume it is unfixed. - vulnVer = version.NewVersion("65535:0") - } - // compare version and architecture - return cmp(pkgVer.Compare(vulnVer)) && vuln.ArchOperation.Cmp(record.Package.Arch, vuln.Package.Arch), nil + return rpm.MatchVulnerable(ctx, record, vuln) } diff --git a/rhel/rhcc/matcher.go b/rhel/rhcc/matcher.go index 1b156b61b..542b4a07b 100644 --- a/rhel/rhcc/matcher.go +++ b/rhel/rhcc/matcher.go @@ -2,11 +2,11 @@ package rhcc import ( "context" + "fmt" "log/slog" - rpmVersion "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpmver" "github.com/quay/claircore/libvuln/driver" "github.com/quay/claircore/rhel" "github.com/quay/claircore/toolkit/types/cpe" @@ -50,9 +50,17 @@ func (*matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, v return false, nil } } - pkgVer, fixedInVer := rpmVersion.NewVersion(record.Package.Version), rpmVersion.NewVersion(vuln.FixedInVersion) + slog.DebugContext(ctx, "comparing versions", "record", record.Package.Version, "vulnerability", vuln.FixedInVersion) - return pkgVer.LessThan(fixedInVer), nil + pkgVer, err := rpmver.Parse(record.Package.Version) + if err != nil { + return false, fmt.Errorf("rhcc: unable to parse version %q: %w", record.Package.Version, err) + } + fixedVer, err := rpmver.Parse(vuln.FixedInVersion) + if err != nil { + return false, fmt.Errorf("rhcc: unable to parse version %q: %w", vuln.FixedInVersion, err) + } + return rpmver.Compare(&pkgVer, &fixedVer) == -1, nil } // Implement version filtering to have the database only return results for the diff --git a/suse/matcher.go b/suse/matcher.go index 4cdd8b88c..cd7dce6e2 100644 --- a/suse/matcher.go +++ b/suse/matcher.go @@ -4,9 +4,8 @@ import ( "context" "slices" - version "github.com/knqyf263/go-rpm-version" - "github.com/quay/claircore" + "github.com/quay/claircore/internal/rpm" "github.com/quay/claircore/libvuln/driver" ) @@ -15,17 +14,17 @@ var ( OSReleaseNames = []string{"SLES", "openSUSE Leap"} ) -// Matcher implements driver.Matcher +// Matcher implements [driver.Matcher] type Matcher struct{} var _ driver.Matcher = (*Matcher)(nil) -// Name implements driver.Matcher +// Name implements [driver.Matcher] func (*Matcher) Name() string { return "suse" } -// Filter implements driver.Matcher +// Filter implements [driver.Matcher] func (*Matcher) Filter(record *claircore.IndexRecord) bool { if record.Distribution == nil { return false @@ -41,7 +40,7 @@ func (*Matcher) Filter(record *claircore.IndexRecord) bool { } } -// Query implements driver.Matcher +// Query implements [driver.Matcher] func (*Matcher) Query() []driver.MatchConstraint { return []driver.MatchConstraint{ driver.DistributionDID, @@ -50,17 +49,7 @@ func (*Matcher) Query() []driver.MatchConstraint { } } -// Vulnerable implements driver.Matcher +// Vulnerable implements [driver.Matcher] func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) { - pkgVer, vulnVer := version.NewVersion(record.Package.Version), version.NewVersion(vuln.Package.Version) - // Assume the vulnerability record we have is for the last known vulnerable - // version, so greater versions aren't vulnerable. - cmp := func(i int) bool { return i != version.GREATER } - // But if it's explicitly marked as a fixed-in version, it't only vulnerable - // if less than that version. - if vuln.FixedInVersion != "" { - vulnVer = version.NewVersion(vuln.FixedInVersion) - cmp = func(i int) bool { return i == version.LESS } - } - return cmp(pkgVer.Compare(vulnVer)) && vuln.ArchOperation.Cmp(record.Package.Arch, vuln.Package.Arch), nil + return rpm.MatchVulnerable(ctx, record, vuln) }