Skip to content
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

Merged
merged 19 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
171 changes: 143 additions & 28 deletions pkg/vulnsrc/oracle-oval/oracle-oval.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ func (vs VulnSrc) save(ovals []OracleOVAL) error {
}

func (vs VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error {
advisories := make(map[string]map[string]map[string]Advisory)
bpfoster marked this conversation as resolved.
Show resolved Hide resolved
for _, platform := range targetPlatforms {
advisories[platform] = make(map[string]map[string]Advisory)
}

vulnerabilityDetails := make(map[string]types.VulnerabilityDetail)

for _, oval := range ovals {
elsaID := strings.Split(oval.Title, ":")[0]

Expand All @@ -110,50 +117,143 @@ func (vs VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error {
return xerrors.Errorf("failed to put data source: %w", err)
}

advisory := types.Advisory{
FixedVersion: affectedPkg.Package.FixedVersion,
platformAdvisories := advisories[platformName]

packageAdvisories, exists := platformAdvisories[affectedPkg.Package.Name]
if !exists {
packageAdvisories = make(map[string]Advisory)
platformAdvisories[affectedPkg.Package.Name] = packageAdvisories
}

for _, vulnID := range vulnIDs {
if err := vs.dbc.PutAdvisoryDetail(tx, vulnID, affectedPkg.Package.Name, []string{platformName}, advisory); err != nil {
return xerrors.Errorf("failed to save Oracle Linux OVAL: %w", err)
cveAdvisory, cveAdvisoryExists := packageAdvisories[vulnID]
if !cveAdvisoryExists {
cveAdvisory = Advisory{
Entries: []Entry{},
}
}

found := false
for i, entry := range cveAdvisory.Entries {
entryFlavor := getFlavor(entry.FixedVersion)
affectedFlavor := getFlavor(affectedPkg.Package.FixedVersion)

if entryFlavor == affectedFlavor {
found = true
// This fixed version is newer than the previously found fixed version
if version.NewVersion(entry.FixedVersion).Compare(version.NewVersion(affectedPkg.Package.FixedVersion)) < 0 {
bpfoster marked this conversation as resolved.
Show resolved Hide resolved
cveAdvisory.Entries[i].FixedVersion = affectedPkg.Package.FixedVersion
}

// Add the ELSA ID to the vendor ID list
if !ustrings.InSlice(elsaID, entry.VendorIDs) {
cveAdvisory.Entries[i].VendorIDs = append(entry.VendorIDs, elsaID)
}
}
}

if !found {
cveAdvisory.Entries = append(cveAdvisory.Entries, Entry{
FixedVersion: affectedPkg.Package.FixedVersion,
VendorIDs: []string{elsaID},
})
bpfoster marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

var references []string
for _, ref := range oval.References {
references = append(references, ref.URI)
packageAdvisories[vulnID] = cveAdvisory
}
}

// Collect vulnerability details - references and severity
// A CVE can be present in multiple ELSAs. Collect all the applicable references as we process them, later when done we'll insert
// the references.
for _, vulnID := range vulnIDs {
vuln := types.VulnerabilityDetail{
Description: oval.Description,
References: referencesFromContains(references, []string{elsaID, vulnID}),
Title: oval.Title,
Severity: severityFromThreat(oval.Severity),
convertedSeverity := severityFromThreat(oval.Severity)

vulDetails, found := vulnerabilityDetails[vulnID]
if !found {
vulDetails = types.VulnerabilityDetail{
References: []string{},
Severity: convertedSeverity,
}
bpfoster marked this conversation as resolved.
Show resolved Hide resolved
}

if err := vs.dbc.PutVulnerabilityDetail(tx, vulnID, source.ID, vuln); err != nil {
return xerrors.Errorf("failed to save Oracle Linux OVAL vulnerability: %w", err)
// If multple ELSAs for the same CVE have differing severities, use the highest one
if convertedSeverity > vulDetails.Severity {
vulDetails.Severity = convertedSeverity
}

for _, ref := range oval.References {
if referencesFromContains(ref.URI, []string{elsaID, vulnID}) && !ustrings.InSlice(ref.URI, vulDetails.References) {
vulDetails.References = append(vulDetails.References, ref.URI)
}
}
vulnerabilityDetails[vulnID] = vulDetails
}
}

// Now that we've processed all the reports, we can save the vulnerability and advisory information
for vulnID, details := range vulnerabilityDetails {
if err := vs.dbc.PutVulnerabilityID(tx, vulnID); err != nil {
return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
}

if err := vs.dbc.PutVulnerabilityDetail(tx, vulnID, source.ID, details); err != nil {
return xerrors.Errorf("failed to save Oracle Linux OVAL vulnerability: %w", err)
}
}

// for optimization
if err := vs.dbc.PutVulnerabilityID(tx, vulnID); err != nil {
return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
for platformName, cveEntries := range advisories {
for packageName, packageEntry := range cveEntries {
for cveId, advisory := range packageEntry {
if err := vs.dbc.PutAdvisoryDetail(tx, cveId, packageName, []string{platformName}, advisory); err != nil {
return xerrors.Errorf("failed to save Oracle Linux OVAL: %w", err)
}
}
}
}

return nil

}

func (vs VulnSrc) Get(release string, pkgName string) ([]types.Advisory, error) {
bucket := fmt.Sprintf(platformFormat, release)
advisories, err := vs.dbc.GetAdvisories(bucket, pkgName)
rawAdvisories, err := vs.dbc.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 {
if len(v.Content) == 0 {
continue
}

var adv Advisory
if err = json.Unmarshal(v.Content, &adv); err != nil {
return nil, xerrors.Errorf("failed to unmarshal advisory JSON: %w", err)
}

for _, entry := range adv.Entries {
advisory := types.Advisory{
FixedVersion: entry.FixedVersion,
VulnerabilityID: vulnID,
VendorIDs: entry.VendorIDs,
}

if v.Source != (types.DataSource{}) {
advisory.DataSource = &types.DataSource{
ID: v.Source.ID,
Name: v.Source.Name,
URL: v.Source.URL,
}
}

advisories = append(advisories, advisory)
}

}

return advisories, nil
}

Expand Down Expand Up @@ -183,16 +283,31 @@ func walkOracle(cri Criteria, osVer string, pkgs []AffectedPackage) []AffectedPa
return pkgs
}

func referencesFromContains(sources []string, matches []string) []string {
references := []string{}
for _, s := range sources {
for _, m := range matches {
if strings.Contains(s, m) {
references = append(references, s)
func referencesFromContains(source string, matches []string) bool {
for _, m := range matches {
if strings.Contains(source, m) {
return true
}
}
return false
}

// Determine the "flavor" of the package:
// - "normal"
// - FIPS validated
// - ksplice userspace. there can be "ksplice1" and "ksplice2"
func getFlavor(version string) string {
if strings.HasSuffix(strings.ToLower(version), "_fips") {
return "fips"
} else {
subs := strings.Split(strings.ToLower(version), ".")
for _, s := range subs {
if strings.HasPrefix(s, "ksplice") {
return s
}
}
return ""
}
return ustrings.Unique(references)
}

func severityFromThreat(sev string) types.Severity {
Expand Down
Loading