Skip to content
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
22 changes: 3 additions & 19 deletions aws/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
25 changes: 7 additions & 18 deletions oracle/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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)
}
25 changes: 7 additions & 18 deletions photon/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
}
55 changes: 37 additions & 18 deletions pkg/rhctag/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 2 additions & 19 deletions rhel/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
}
16 changes: 12 additions & 4 deletions rhel/rhcc/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
25 changes: 7 additions & 18 deletions suse/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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)
}