Skip to content

Commit

Permalink
fix(oracle-oval): Support multiple ELSAs per CVE (#221)
Browse files Browse the repository at this point in the history
Co-authored-by: DmitriyLewen <[email protected]>
  • Loading branch information
bpfoster and DmitriyLewen authored Nov 20, 2024
1 parent 6242a39 commit 333d808
Show file tree
Hide file tree
Showing 13 changed files with 2,002 additions and 41 deletions.
181 changes: 159 additions & 22 deletions pkg/vulnsrc/oracle-oval/oracle-oval.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand Down Expand Up @@ -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]

Expand All @@ -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
}
Expand All @@ -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
Expand All @@ -156,21 +168,116 @@ 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, ok := fixedVers[NormalPackageFlavor]
// To keep the previous logic - use the ksplice/fips version if the normal flavor doesn't exist.
if !ok {
fixedVersion = versions[0]
}

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)
Expand All @@ -183,7 +290,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)
}
}
Expand All @@ -192,10 +299,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 (This code can be deleted after Dec 19th, 2024)
// 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
}

Expand All @@ -211,11 +344,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(),
})
}

Expand All @@ -234,7 +367,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 {
Expand Down
Loading

0 comments on commit 333d808

Please sign in to comment.