-
Notifications
You must be signed in to change notification settings - Fork 151
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
fix(oracle-oval): Support multiple ELSAs per CVE #221
Changes from 16 commits
aa6ed6d
1dbc8c6
9059075
f4bb50a
63e4055
3c13c83
c46983e
57dbc86
7cdb661
54b2c0b
377b227
d6c0edd
695da90
cd98708
ed3cdbf
a135499
cb14132
6557f9c
95d6d93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -10,13 +10,14 @@ import ( | |||||
"strings" | ||||||
|
||||||
version "github.com/knqyf263/go-rpm-version" | ||||||
"github.com/samber/lo" | ||||||
bolt "go.etcd.io/bbolt" | ||||||
"golang.org/x/exp/maps" | ||||||
"golang.org/x/xerrors" | ||||||
|
||||||
"github.com/aquasecurity/trivy-db/pkg/db" | ||||||
"github.com/aquasecurity/trivy-db/pkg/types" | ||||||
"github.com/aquasecurity/trivy-db/pkg/utils" | ||||||
ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings" | ||||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" | ||||||
) | ||||||
|
||||||
|
@@ -34,10 +35,10 @@ var ( | |||||
) | ||||||
|
||||||
type PutInput struct { | ||||||
VulnID string // CVE-ID or ELSA-ID | ||||||
Vuln types.VulnerabilityDetail // vulnerability detail such as CVSS and description | ||||||
Advisories map[AffectedPackage]types.Advisory // pkg => advisory | ||||||
OVAL OracleOVAL // for extensibility, not used in trivy-db | ||||||
VulnID string // CVE-ID or ELSA-ID | ||||||
Vuln types.VulnerabilityDetail // vulnerability detail such as CVSS and description | ||||||
Advisories map[Package]types.Advisories // pkg => advisories | ||||||
OVALs []OracleOVAL // for extensibility, not used in trivy-db | ||||||
} | ||||||
|
||||||
type DB interface { | ||||||
|
@@ -111,6 +112,8 @@ func (vs *VulnSrc) put(ovals []OracleOVAL) error { | |||||
} | ||||||
|
||||||
func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { | ||||||
// CVE -> PutInput | ||||||
putInputs := make(map[string]PutInput) | ||||||
for _, oval := range ovals { | ||||||
elsaID := strings.Split(oval.Title, ":")[0] | ||||||
|
||||||
|
@@ -122,14 +125,14 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { | |||||
vulnIDs = append(vulnIDs, elsaID) | ||||||
} | ||||||
|
||||||
advisories := map[AffectedPackage]types.Advisory{} | ||||||
advisories := map[Package]types.Advisories{} | ||||||
affectedPkgs := walkOracle(oval.Criteria, "", []AffectedPackage{}) | ||||||
for _, affectedPkg := range affectedPkgs { | ||||||
if affectedPkg.Package.Name == "" { | ||||||
continue | ||||||
} | ||||||
|
||||||
platformName := affectedPkg.PlatformName() | ||||||
platformName := affectedPkg.Package.PlatformName() | ||||||
if !slices.Contains(targetPlatforms, platformName) { | ||||||
continue | ||||||
} | ||||||
|
@@ -138,9 +141,18 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { | |||||
return xerrors.Errorf("failed to put data source: %w", err) | ||||||
} | ||||||
|
||||||
advisories[affectedPkg] = types.Advisory{ | ||||||
FixedVersion: affectedPkg.Package.FixedVersion, | ||||||
advs := types.Advisories{ | ||||||
Entries: []types.Advisory{ | ||||||
{ | ||||||
FixedVersion: affectedPkg.FixedVersion, | ||||||
}, | ||||||
}, | ||||||
} | ||||||
if savedAdvs, ok := advisories[affectedPkg.Package]; ok { | ||||||
advs.Entries = append(advs.Entries, savedAdvs.Entries...) | ||||||
} | ||||||
advisories[affectedPkg.Package] = advs | ||||||
|
||||||
} | ||||||
|
||||||
var references []string | ||||||
|
@@ -156,21 +168,112 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { | |||||
Severity: severityFromThreat(oval.Severity), | ||||||
} | ||||||
|
||||||
err := vs.Put(tx, PutInput{ | ||||||
input := PutInput{ | ||||||
VulnID: vulnID, | ||||||
Vuln: vuln, | ||||||
Advisories: advisories, | ||||||
OVAL: oval, | ||||||
}) | ||||||
if err != nil { | ||||||
return xerrors.Errorf("db put error: %w", err) | ||||||
Advisories: maps.Clone(advisories), | ||||||
OVALs: []OracleOVAL{oval}, | ||||||
} | ||||||
|
||||||
if savedInput, ok := putInputs[input.VulnID]; ok { | ||||||
input.OVALs = append(input.OVALs, savedInput.OVALs...) | ||||||
|
||||||
for inputPkg, inputAdvs := range input.Advisories { | ||||||
if savedPkgAdvs, pkgFound := savedInput.Advisories[inputPkg]; pkgFound { | ||||||
inputAdvs.Entries = append(savedPkgAdvs.Entries, inputAdvs.Entries...) | ||||||
} | ||||||
savedInput.Advisories[inputPkg] = inputAdvs | ||||||
} | ||||||
input.Advisories = savedInput.Advisories | ||||||
} | ||||||
putInputs[input.VulnID] = input | ||||||
} | ||||||
} | ||||||
|
||||||
for _, input := range putInputs { | ||||||
for pkg, advs := range input.Advisories { | ||||||
input.Advisories[pkg] = resolveAdvisoriesEntries(advs) | ||||||
} | ||||||
|
||||||
err := vs.Put(tx, input) | ||||||
if err != nil { | ||||||
return xerrors.Errorf("db put error: %w", err) | ||||||
} | ||||||
} | ||||||
|
||||||
return nil | ||||||
} | ||||||
|
||||||
// resolveAdvisoriesEntries removes entries with the same fixedVersion. | ||||||
// Additionally, it only selects the latest fixedVersion for each flavor. | ||||||
func resolveAdvisoriesEntries(advisories types.Advisories) types.Advisories { | ||||||
fixedVersions := lo.Map(advisories.Entries, func(entry types.Advisory, _ int) string { | ||||||
return entry.FixedVersion | ||||||
}) | ||||||
fixedVer, resolvedVers := resolveVersions(fixedVersions) | ||||||
entries := lo.Map(resolvedVers, func(ver string, _ int) types.Advisory { | ||||||
return types.Advisory{ | ||||||
FixedVersion: ver, | ||||||
} | ||||||
}) | ||||||
return types.Advisories{ | ||||||
FixedVersion: fixedVer, | ||||||
Entries: entries, | ||||||
} | ||||||
} | ||||||
|
||||||
// resolveVersions removes duplicates and returns normal flavor + only one version for each flavor. | ||||||
func resolveVersions(vers []string) (string, []string) { | ||||||
vers = lo.Uniq(vers) | ||||||
|
||||||
fixedVers := make(map[PkgFlavor]string) | ||||||
for _, ver := range vers { | ||||||
flavor := PackageFlavor(ver) | ||||||
if savedVer, ok := fixedVers[flavor]; ok { | ||||||
v := version.NewVersion(ver) | ||||||
sv := version.NewVersion(savedVer) | ||||||
if v.LessThan(sv) { | ||||||
ver = savedVer | ||||||
} | ||||||
} | ||||||
fixedVers[flavor] = ver | ||||||
} | ||||||
|
||||||
versions := lo.Values(fixedVers) | ||||||
slices.Sort(versions) | ||||||
|
||||||
fixedVersion := fixedVers[NormalPackageFlavor] | ||||||
|
||||||
return fixedVersion, versions | ||||||
} | ||||||
|
||||||
type PkgFlavor string | ||||||
|
||||||
const ( | ||||||
NormalPackageFlavor PkgFlavor = "normal" | ||||||
FipsPackageFlavor PkgFlavor = "fips" | ||||||
KsplicePackageFlavor PkgFlavor = "ksplice" | ||||||
) | ||||||
|
||||||
// PackageFlavor determinants the package "flavor" based on its version string | ||||||
// - normal | ||||||
// - FIPS validated | ||||||
// - ksplice userspace | ||||||
func PackageFlavor(version string) PkgFlavor { | ||||||
version = strings.ToLower(version) | ||||||
if strings.HasSuffix(version, "_fips") { | ||||||
return FipsPackageFlavor | ||||||
} | ||||||
|
||||||
subs := strings.Split(version, ".") | ||||||
for _, s := range subs { | ||||||
if strings.HasPrefix(s, "ksplice") { | ||||||
return KsplicePackageFlavor | ||||||
} | ||||||
} | ||||||
return NormalPackageFlavor | ||||||
} | ||||||
|
||||||
func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { | ||||||
if err := o.PutVulnerabilityDetail(tx, input.VulnID, source.ID, input.Vuln); err != nil { | ||||||
return xerrors.Errorf("failed to save Oracle Linux OVAL vulnerability: %w", err) | ||||||
|
@@ -183,7 +286,7 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { | |||||
|
||||||
for pkg, advisory := range input.Advisories { | ||||||
platformName := pkg.PlatformName() | ||||||
if err := o.PutAdvisoryDetail(tx, input.VulnID, pkg.Package.Name, []string{platformName}, advisory); err != nil { | ||||||
if err := o.PutAdvisoryDetail(tx, input.VulnID, pkg.Name, []string{platformName}, advisory); err != nil { | ||||||
return xerrors.Errorf("failed to save Oracle Linux advisory: %w", err) | ||||||
} | ||||||
} | ||||||
|
@@ -192,10 +295,36 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { | |||||
|
||||||
func (o *Oracle) Get(release string, pkgName string) ([]types.Advisory, error) { | ||||||
bucket := fmt.Sprintf(platformFormat, release) | ||||||
advisories, err := o.GetAdvisories(bucket, pkgName) | ||||||
rawAdvisories, err := o.ForEachAdvisory([]string{bucket}, pkgName) | ||||||
if err != nil { | ||||||
return nil, xerrors.Errorf("failed to get Oracle Linux advisories: %w", err) | ||||||
return nil, xerrors.Errorf("unable to iterate advisories: %w", err) | ||||||
} | ||||||
var advisories []types.Advisory | ||||||
for vulnID, v := range rawAdvisories { | ||||||
var adv types.Advisories | ||||||
if err = json.Unmarshal(v.Content, &adv); err != nil { | ||||||
return nil, xerrors.Errorf("failed to unmarshal advisory JSON: %w", err) | ||||||
} | ||||||
|
||||||
// For backward compatibility | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed only when users keep using old databases, right? I don't think people use the old database for a month. We can set a deadline.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right. |
||||||
// The old trivy-db has no entries, but has fixed versions and custom fields. | ||||||
if len(adv.Entries) == 0 { | ||||||
advisories = append(advisories, types.Advisory{ | ||||||
VulnerabilityID: vulnID, | ||||||
FixedVersion: adv.FixedVersion, | ||||||
DataSource: &v.Source, | ||||||
Custom: adv.Custom, | ||||||
}) | ||||||
continue | ||||||
} | ||||||
|
||||||
for _, entry := range adv.Entries { | ||||||
entry.VulnerabilityID = vulnID | ||||||
entry.DataSource = &v.Source | ||||||
advisories = append(advisories, entry) | ||||||
} | ||||||
} | ||||||
|
||||||
return advisories, nil | ||||||
} | ||||||
|
||||||
|
@@ -211,11 +340,11 @@ func walkOracle(cri Criteria, osVer string, pkgs []AffectedPackage) []AffectedPa | |||||
} | ||||||
|
||||||
pkgs = append(pkgs, AffectedPackage{ | ||||||
OSVer: osVer, | ||||||
Package: Package{ | ||||||
Name: ss[0], | ||||||
FixedVersion: version.NewVersion(ss[1]).String(), | ||||||
Name: ss[0], | ||||||
OSVer: osVer, | ||||||
}, | ||||||
FixedVersion: version.NewVersion(ss[1]).String(), | ||||||
}) | ||||||
} | ||||||
|
||||||
|
@@ -234,7 +363,11 @@ func referencesFromContains(sources []string, matches []string) []string { | |||||
} | ||||||
} | ||||||
} | ||||||
return ustrings.Unique(references) | ||||||
|
||||||
references = lo.Uniq(references) | ||||||
slices.Sort(references) | ||||||
|
||||||
return references | ||||||
} | ||||||
|
||||||
func severityFromThreat(sev string) types.Severity { | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this case.
Should we use
fips
orksplice
version here?example - https://linux.oracle.com/errata/ELSA-2016-3515.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current DB contains fixed versions for
fips
orksplice
in this case, right? If so, we should keep it for compatibility.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not entirely correct (which is why this PR was created), but it is only needed for backward compatibility, so let's leave the previous logic
Updated in cb14132