From e8b371e61084b621cde660b6288e24bf4f902109 Mon Sep 17 00:00:00 2001 From: thatipelli santhosh Date: Sun, 12 Jan 2025 21:31:46 +0530 Subject: [PATCH 1/4] feat: update Oracle tracker to store advisory ID (ELSA ID) in database - Modify Oracle tracker to include logic for saving advisory IDs (ELSA IDs) in the database. --- pkg/vulnsrc/oracle-oval/oracle-oval.go | 72 ++++++++++++++------- pkg/vulnsrc/oracle-oval/oracle-oval_test.go | 60 +++++++++++++++++ 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval.go b/pkg/vulnsrc/oracle-oval/oracle-oval.go index af8f0f28..936c140c 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval.go @@ -152,6 +152,7 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { { FixedVersion: affectedPkg.FixedVersion, Arches: []string{affectedPkg.Arch}, + VendorIDs: []string{elsaID}, }, }, } @@ -216,32 +217,40 @@ type archFlavor struct { Flavor PkgFlavor } -// mergeAdvisoriesEntries merges advisories by picking the latest version for each arch+flavor. -// There may be multiple advisories that fix the same vulnerability, possibly providing multiple fixed versions. -// In this case, we need to determine the "latest" version, which is now defined as the highest (greatest) version number. +// mergeAdvisoriesEntries merges advisories by selecting the latest version for each combination of architecture and flavor. +// The "latest" version is determined as the highest (greatest) version number for each (arch, flavor) pair. // -// Additionally, we choose a single fixed version for backward compatibility, which is derived from -// the highest normal flavor version on the x86_64 architecture if available. +// The process is as follows: +// 1. Select the latest version for each (arch, flavor) combination. +// 2. Aggregate architectures for each selected version into a single entry, grouping them by their version. +// 3. Build the final list of advisories, sorted by version in ascending order. +// +// Additionally, for backward compatibility, we determine a single primary fixed version. +// This is derived from the highest normal flavor version on the x86_64 architecture if available. func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { // Step 1: Select the latest version per (arch, flavor) latestVersions := selectLatestVersions(advisories) - // Step 2: Aggregate arches by their chosen version - versionToArches := make(map[string][]string) + // Step 2: Aggregate architectures by their chosen version + versionToArches := make(map[string]types.Advisory) for k, v := range latestVersions { - versionToArches[v] = append(versionToArches[v], k.Arch) + adv := versionToArches[v.FixedVersion] + adv.VendorIDs = v.VendorIDs + adv.Arches = append(adv.Arches, k.Arch) + versionToArches[v.FixedVersion] = adv // Save the modified value back to the map } // Step 3: Build final entries, sorted by version - entries := lo.MapToSlice(versionToArches, func(ver string, arches []string) types.Advisory { - sort.Strings(arches) + entries := lo.MapToSlice(versionToArches, func(ver string, arches types.Advisory) types.Advisory { + sort.Strings(arches.Arches) // Ensure architectures are sorted for consistency return types.Advisory{ FixedVersion: ver, - Arches: arches, + Arches: arches.Arches, + VendorIDs: arches.VendorIDs, } }) sort.Slice(entries, func(i, j int) bool { - return entries[i].FixedVersion < entries[j].FixedVersion // Sorting lexicographically + return entries[i].FixedVersion < entries[j].FixedVersion // Sort lexicographically }) return types.Advisories{ @@ -250,9 +259,11 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { } } -// selectLatestVersions selects the latest (highest) version per (arch, flavor) -func selectLatestVersions(advisories types.Advisories) map[archFlavor]string { - latestVersions := make(map[archFlavor]string) // key: archFlavor -> highest fixedVersion +// selectLatestVersions identifies and selects the highest (latest) version for each unique combination of architecture and flavor. +// Each advisory entry contains a fixed version, a set of architectures, and associated vendor IDs. +// This function ensures only the highest version for a given (arch, flavor) pair is retained. +func selectLatestVersions(advisories types.Advisories) map[archFlavor]types.Advisory { + latestVersions := make(map[archFlavor]types.Advisory) // key: archFlavor -> highest fixedVersion for _, entry := range advisories.Entries { if len(entry.Arches) == 0 || entry.FixedVersion == "" { continue @@ -265,26 +276,39 @@ func selectLatestVersions(advisories types.Advisories) map[archFlavor]string { } currentVer := version.NewVersion(entry.FixedVersion) - if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing)) { + if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing.FixedVersion)) { // Keep the higher (latest) version - latestVersions[key] = entry.FixedVersion + latestVersions[key] = types.Advisory{ + FixedVersion: entry.FixedVersion, + VendorIDs: entry.VendorIDs, + Arches: entry.Arches, + } } } return latestVersions } -// determinePrimaryFixedVersion determines primary fixed version for backward compatibility -// It is chosen as the highest normal flavor version on x86_64 if any exist. -// If no normal flavor version exists, the maximum version in lexical order is chosen. -func determinePrimaryFixedVersion(latestVersions map[archFlavor]string) string { +// determinePrimaryFixedVersion determines the primary fixed version for backward compatibility. +// The primary version is selected as follows: +// 1. If there is a normal flavor version available for the "x86_64" architecture, the highest version among these is chosen. +// 2. If no normal flavor version exists, the function falls back to the maximum version across all advisories in lexical order. +// +// This ensures backward compatibility by preferring the most relevant and common version for typical use cases. +func determinePrimaryFixedVersion(latestVersions map[archFlavor]types.Advisory) string { + // Check for the highest version of the normal flavor on the "x86_64" architecture primaryFixedVersion := latestVersions[archFlavor{ Arch: "x86_64", Flavor: NormalPackageFlavor, }] - if primaryFixedVersion == "" { - primaryFixedVersion = lo.Max(lo.Values(latestVersions)) // Chose the maximum value in lexical order for idempotency + + // If no normal flavor version exists, choose the maximum version lexicographically + if primaryFixedVersion.FixedVersion == "" { + primaryFixedVersion = lo.MaxBy(lo.Values(latestVersions), func(a, b types.Advisory) bool { + return a.FixedVersion > b.FixedVersion + }) } - return primaryFixedVersion + + return primaryFixedVersion.FixedVersion } type PkgFlavor string diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go index 710b8d13..04e18963 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go @@ -50,6 +50,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2007-0057", + }, }, }, }, @@ -64,6 +67,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2007-0057", + }, }, }, }, @@ -78,6 +84,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2007-0057", + }, }, }, }, @@ -92,6 +101,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2007-0057", + }, }, }, }, @@ -160,6 +172,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -174,6 +189,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -188,6 +206,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -202,6 +223,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -216,6 +240,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -230,6 +257,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -244,6 +274,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -258,6 +291,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2019-4510", + }, }, }, }, @@ -320,18 +356,27 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, + VendorIDs: []string{ + "ELSA-2022-9221", + }, }, { FixedVersion: "3.6.16-4.el8", Arches: []string{ "aarch64", }, + VendorIDs: []string{ + "ELSA-2021-4451", + }, }, { FixedVersion: "3.6.16-5.el8", Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2021-4451", + }, }, }, }, @@ -347,6 +392,9 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, + VendorIDs: []string{ + "ELSA-2021-4451", + }, }, }, }, @@ -393,6 +441,9 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, + VendorIDs: []string{ + "ELSA-2021-9362", + }, }, }, }, @@ -408,6 +459,9 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, + VendorIDs: []string{ + "ELSA-2021-9362", + }, }, }, }, @@ -452,6 +506,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2021-9344", + }, }, }, }, @@ -495,6 +552,9 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, + VendorIDs: []string{ + "ELSA-2007-0057", + }, }, }, }, From efe1488a5fdf52f5a54e5a7c036f5be9017523c8 Mon Sep 17 00:00:00 2001 From: thatipelli santhosh Date: Thu, 23 Jan 2025 21:47:26 +0530 Subject: [PATCH 2/4] Fix - handling of same fix version with different elsa ids and include elsa ids for aqua storage,remove them before saving into oss db --- pkg/vulnsrc/oracle-oval/oracle-oval.go | 59 ++++++++-- pkg/vulnsrc/oracle-oval/oracle-oval_test.go | 109 ++++++++---------- .../oval/oracle/2021/ELSA-2021-9280.json | 74 ++++++++++++ .../oval/oracle/2021/ELSA-2021-9344.json | 74 ++++++++++++ 4 files changed, 245 insertions(+), 71 deletions(-) create mode 100644 pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json create mode 100644 pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval.go b/pkg/vulnsrc/oracle-oval/oracle-oval.go index 936c140c..9369c5b3 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval.go @@ -146,7 +146,8 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { if err := vs.PutDataSource(tx, platformName, source); err != nil { return xerrors.Errorf("failed to put data source: %w", err) } - + // The ELSA IDs are included in the input for Aqua storage, but OSS does not require them. + // Therefore, the VendorIDs will be removed before saving to the database. advs := types.Advisories{ Entries: []types.Advisory{ { @@ -232,21 +233,39 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { latestVersions := selectLatestVersions(advisories) // Step 2: Aggregate architectures by their chosen version - versionToArches := make(map[string]types.Advisory) + type versionKey struct { + FixedVersion string + VendorID string + } + versionToAdvisories := make(map[versionKey]types.Advisory) for k, v := range latestVersions { - adv := versionToArches[v.FixedVersion] - adv.VendorIDs = v.VendorIDs - adv.Arches = append(adv.Arches, k.Arch) - versionToArches[v.FixedVersion] = adv // Save the modified value back to the map + key := versionKey{ + FixedVersion: v.FixedVersion, + VendorID: strings.Join(v.VendorIDs, ","), // Combine VendorIDs as key + } + + adv, exists := versionToAdvisories[key] + if !exists { + adv = types.Advisory{ + FixedVersion: v.FixedVersion, + Arches: []string{}, + VendorIDs: v.VendorIDs, + } + } + + // Append unique architectures + adv.Arches = appendUnique(adv.Arches, k.Arch) + + versionToAdvisories[key] = adv } // Step 3: Build final entries, sorted by version - entries := lo.MapToSlice(versionToArches, func(ver string, arches types.Advisory) types.Advisory { - sort.Strings(arches.Arches) // Ensure architectures are sorted for consistency + entries := lo.MapToSlice(versionToAdvisories, func(_ versionKey, adv types.Advisory) types.Advisory { + sort.Strings(adv.Arches) // Ensure architectures are sorted for consistency return types.Advisory{ - FixedVersion: ver, - Arches: arches.Arches, - VendorIDs: arches.VendorIDs, + FixedVersion: adv.FixedVersion, + Arches: adv.Arches, + VendorIDs: adv.VendorIDs, } }) sort.Slice(entries, func(i, j int) bool { @@ -349,6 +368,9 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { } for pkg, advisory := range input.Advisories { + for i := range advisory.Entries { + advisory.Entries[i].VendorIDs = nil // Exclude VendorIDs from being saved + } platformName := pkg.PlatformName() 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) @@ -455,3 +477,18 @@ func severityFromThreat(sev string) types.Severity { } return types.SeverityUnknown } + +// appendUnique appends unique elements from src to dest. +func appendUnique(dest []string, src ...string) []string { + existing := make(map[string]bool) + for _, d := range dest { + existing[d] = true + } + for _, s := range src { + if !existing[s] { + dest = append(dest, s) + existing[s] = true + } + } + return dest +} diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go index 04e18963..b52bc1ca 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go @@ -50,9 +50,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2007-0057", - }, }, }, }, @@ -67,9 +64,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2007-0057", - }, }, }, }, @@ -84,9 +78,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2007-0057", - }, }, }, }, @@ -101,9 +92,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2007-0057", - }, }, }, }, @@ -172,9 +160,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -189,9 +174,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -206,9 +188,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -223,9 +202,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -240,9 +216,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -257,9 +230,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -274,9 +244,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -291,9 +258,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2019-4510", - }, }, }, }, @@ -356,27 +320,18 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, - VendorIDs: []string{ - "ELSA-2022-9221", - }, }, { FixedVersion: "3.6.16-4.el8", Arches: []string{ "aarch64", }, - VendorIDs: []string{ - "ELSA-2021-4451", - }, }, { FixedVersion: "3.6.16-5.el8", Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2021-4451", - }, }, }, }, @@ -392,9 +347,6 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, - VendorIDs: []string{ - "ELSA-2021-4451", - }, }, }, }, @@ -441,9 +393,6 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, - VendorIDs: []string{ - "ELSA-2021-9362", - }, }, }, }, @@ -459,9 +408,6 @@ func TestVulnSrc_Update(t *testing.T) { "aarch64", "x86_64", }, - VendorIDs: []string{ - "ELSA-2021-9362", - }, }, }, }, @@ -506,9 +452,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2021-9344", - }, }, }, }, @@ -552,9 +495,6 @@ func TestVulnSrc_Update(t *testing.T) { Arches: []string{ "x86_64", }, - VendorIDs: []string{ - "ELSA-2007-0057", - }, }, }, }, @@ -608,6 +548,55 @@ func TestVulnSrc_Update(t *testing.T) { dir: filepath.Join("testdata", "sad"), wantErr: "failed to decode Oracle Linux OVAL JSON", }, + { + name: "same fix version with different ELSA ids and architectures", + dir: filepath.Join("testdata", "multiple_elsa_ids_same_fix_version"), + wantValues: []vulnsrctest.WantValues{ + { + Key: []string{"data-source", "Oracle Linux 8"}, + Value: types.DataSource{ + ID: vulnerability.OracleOVAL, + Name: "Oracle Linux OVAL definitions", + URL: "https://linux.oracle.com/security/oval/", + }, + }, + { + Key: []string{"advisory-detail", "CVE-2016-10228", "Oracle Linux 8", "glibc"}, + Value: types.Advisories{ + FixedVersion: "2:2.28-151.0.1.ksplice1.el8", + Entries: []types.Advisory{ + { + FixedVersion: "2:2.28-151.0.1.ksplice1.el8", + Arches: []string{ + "x86_64", + }, + }, + { + FixedVersion: "2:2.28-151.0.1.ksplice1.el8", + Arches: []string{ + "i386", + }, + }, + }, + }, + }, + { + Key: []string{"vulnerability-detail", "CVE-2016-10228", "oracle-oval"}, + Value: types.VulnerabilityDetail{ + Title: "ELSA-2021-9344: glibc security update (IMPORTANT)", + References: []string{ + "https://linux.oracle.com/cve/CVE-2016-10228.html", + "https://linux.oracle.com/errata/ELSA-2021-9344.html", + }, + Severity: types.SeverityHigh, + }, + }, + { + Key: []string{"vulnerability-id", "CVE-2016-10228"}, + Value: map[string]interface{}{}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json b/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json new file mode 100644 index 00000000..4c4fbea6 --- /dev/null +++ b/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json @@ -0,0 +1,74 @@ +{ + "Title": "ELSA-2021-9280: glibc security update (IMPORTANT)", + "Description": "", + "Platform": [ + "Oracle Linux 8" + ], + "References": [ + { + "Source": "elsa", + "URI": "https://linux.oracle.com/errata/ELSA-2021-9280.html", + "ID": "ELSA-2021-9280" + }, + { + "Source": "CVE", + "URI": "https://linux.oracle.com/cve/CVE-2016-10228.html", + "ID": "CVE-2016-10228" + } + ], + "Criteria": { + "Operator": "AND", + "Criterias": [ + { + "Operator": "OR", + "Criterias": [ + { + "Operator": "AND", + "Criterias": [ + { + "Operator": "OR", + "Criterias": [ + { + "Operator": "AND", + "Criterias": null, + "Criterions": [ + { + "Comment": "glibc is earlier than 2:2.28-151.0.1.ksplice1.el8" + }, + { + "Comment": "glibc is signed with the Oracle Linux 8 key" + }, + { + "Comment": "glibc is ksplice-based" + } + ] + } + ], + "Criterions": null + } + ], + "Criterions": [ + { + "Comment": "Oracle Linux arch is x86_64" + } + ] + } + ], + "Criterions": null + } + ], + "Criterions": [ + { + "Comment": "Oracle Linux 8 is installed" + } + ] + }, + "Severity": "IMPORTANT", + "Cves": [ + { + "Impact": "", + "Href": "https://linux.oracle.com/cve/CVE-2016-10228.html", + "ID": "CVE-2016-10228" + } + ] +} \ No newline at end of file diff --git a/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json b/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json new file mode 100644 index 00000000..0b31a2c4 --- /dev/null +++ b/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json @@ -0,0 +1,74 @@ +{ + "Title": "ELSA-2021-9344: glibc security update (IMPORTANT)", + "Description": "", + "Platform": [ + "Oracle Linux 8" + ], + "References": [ + { + "Source": "elsa", + "URI": "https://linux.oracle.com/errata/ELSA-2021-9344.html", + "ID": "ELSA-2021-9280" + }, + { + "Source": "CVE", + "URI": "https://linux.oracle.com/cve/CVE-2016-10228.html", + "ID": "CVE-2016-10228" + } + ], + "Criteria": { + "Operator": "AND", + "Criterias": [ + { + "Operator": "OR", + "Criterias": [ + { + "Operator": "AND", + "Criterias": [ + { + "Operator": "OR", + "Criterias": [ + { + "Operator": "AND", + "Criterias": null, + "Criterions": [ + { + "Comment": "glibc is earlier than 2:2.28-151.0.1.ksplice1.el8" + }, + { + "Comment": "glibc is signed with the Oracle Linux 8 key" + }, + { + "Comment": "glibc is ksplice-based" + } + ] + } + ], + "Criterions": null + } + ], + "Criterions": [ + { + "Comment": "Oracle Linux arch is i386" + } + ] + } + ], + "Criterions": null + } + ], + "Criterions": [ + { + "Comment": "Oracle Linux 8 is installed" + } + ] + }, + "Severity": "IMPORTANT", + "Cves": [ + { + "Impact": "", + "Href": "https://linux.oracle.com/cve/CVE-2016-10229.html", + "ID": "CVE-2016-10228" + } + ] +} \ No newline at end of file From 4c8abbe4d2698ce180ea01502ca127cff6f2f5fd Mon Sep 17 00:00:00 2001 From: thatipelli santhosh Date: Tue, 11 Feb 2025 17:56:59 +0530 Subject: [PATCH 3/4] Updated with DmitriyLewen changes --- pkg/vulnsrc/oracle-oval/oracle-oval.go | 160 +++++++++----------- pkg/vulnsrc/oracle-oval/oracle-oval_test.go | 7 +- 2 files changed, 74 insertions(+), 93 deletions(-) diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval.go b/pkg/vulnsrc/oracle-oval/oracle-oval.go index 9ec709c0..ce1bc4fe 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval.go @@ -149,14 +149,17 @@ func (vs *VulnSrc) commit(tx *bolt.Tx, ovals []OracleOVAL) error { if err := vs.PutDataSource(tx, platformName, source); err != nil { return oops.With("platform", platformName).Wrapf(err, "failed to put data source") } - // The ELSA IDs are included in the input for Aqua storage, but OSS does not require them. - // Therefore, the VendorIDs will be removed before saving to the database. + advs := types.Advisories{ Entries: []types.Advisory{ { FixedVersion: affectedPkg.FixedVersion, - Arches: []string{affectedPkg.Arch}, - VendorIDs: []string{elsaID}, + Arches: []string{ + affectedPkg.Arch, + }, + VendorIDs: []string{ + elsaID, + }, }, }, } @@ -219,58 +222,40 @@ type archFlavor struct { Flavor PkgFlavor } -// mergeAdvisoriesEntries merges advisories by selecting the latest version for each combination of architecture and flavor. -// The "latest" version is determined as the highest (greatest) version number for each (arch, flavor) pair. -// -// The process is as follows: -// 1. Select the latest version for each (arch, flavor) combination. -// 2. Aggregate architectures for each selected version into a single entry, grouping them by their version. -// 3. Build the final list of advisories, sorted by version in ascending order. +type versionVendorID struct { + Version string + VendorID string +} + +// mergeAdvisoriesEntries merges advisories by picking the latest version for each arch+flavor. +// There may be multiple advisories that fix the same vulnerability, possibly providing multiple fixed versions. +// In this case, we need to determine the "latest" version, which is now defined as the highest (greatest) version number. // -// Additionally, for backward compatibility, we determine a single primary fixed version. -// This is derived from the highest normal flavor version on the x86_64 architecture if available. +// Additionally, we choose a single fixed version for backward compatibility, which is derived from +// the highest normal flavor version on the x86_64 architecture if available. func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { // Step 1: Select the latest version per (arch, flavor) latestVersions := selectLatestVersions(advisories) - // Step 2: Aggregate architectures by their chosen version - type versionKey struct { - FixedVersion string - VendorID string - } - versionToAdvisories := make(map[versionKey]types.Advisory) + // Step 2: Aggregate arches by their chosen version + versionToArches := make(map[versionVendorID][]string) for k, v := range latestVersions { - key := versionKey{ - FixedVersion: v.FixedVersion, - VendorID: strings.Join(v.VendorIDs, ","), // Combine VendorIDs as key - } - - adv, exists := versionToAdvisories[key] - if !exists { - adv = types.Advisory{ - FixedVersion: v.FixedVersion, - Arches: []string{}, - VendorIDs: v.VendorIDs, - } - } - - // Append unique architectures - adv.Arches = appendUnique(adv.Arches, k.Arch) - - versionToAdvisories[key] = adv + versionToArches[v] = append(versionToArches[v], k.Arch) } // Step 3: Build final entries, sorted by version - entries := lo.MapToSlice(versionToAdvisories, func(_ versionKey, adv types.Advisory) types.Advisory { - sort.Strings(adv.Arches) // Ensure architectures are sorted for consistency + entries := lo.MapToSlice(versionToArches, func(ver versionVendorID, arches []string) types.Advisory { + sort.Strings(arches) return types.Advisory{ - FixedVersion: adv.FixedVersion, - Arches: adv.Arches, - VendorIDs: adv.VendorIDs, + FixedVersion: ver.Version, + Arches: arches, + VendorIDs: []string{ + ver.VendorID, + }, } }) sort.Slice(entries, func(i, j int) bool { - return entries[i].FixedVersion < entries[j].FixedVersion // Sort lexicographically + return entries[i].FixedVersion < entries[j].FixedVersion // Sorting lexicographically }) return types.Advisories{ @@ -279,16 +264,15 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { } } -// selectLatestVersions identifies and selects the highest (latest) version for each unique combination of architecture and flavor. -// Each advisory entry contains a fixed version, a set of architectures, and associated vendor IDs. -// This function ensures only the highest version for a given (arch, flavor) pair is retained. -func selectLatestVersions(advisories types.Advisories) map[archFlavor]types.Advisory { - latestVersions := make(map[archFlavor]types.Advisory) // key: archFlavor -> highest fixedVersion +// selectLatestVersions selects the latest (highest) version per (arch, flavor) +func selectLatestVersions(advisories types.Advisories) map[archFlavor]versionVendorID { + latestVersions := make(map[archFlavor]versionVendorID) // key: archFlavor -> highest fixedVersion for _, entry := range advisories.Entries { if len(entry.Arches) == 0 || entry.FixedVersion == "" { continue } - arch := entry.Arches[0] // Before merging `arches`, it always contains only 1 arch + arch := entry.Arches[0] // Before merging `arches`, it always contains only 1 arch + vendorID := entry.VendorIDs[0] // Before merging `VendorIDs`, it always contains only 1 elsaID flavor := PackageFlavor(entry.FixedVersion) key := archFlavor{ Arch: arch, @@ -296,39 +280,32 @@ func selectLatestVersions(advisories types.Advisories) map[archFlavor]types.Advi } currentVer := version.NewVersion(entry.FixedVersion) - if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing.FixedVersion)) { + if existing, ok := latestVersions[key]; !ok || currentVer.GreaterThan(version.NewVersion(existing.Version)) { // Keep the higher (latest) version - latestVersions[key] = types.Advisory{ - FixedVersion: entry.FixedVersion, - VendorIDs: entry.VendorIDs, - Arches: entry.Arches, + latestVersions[key] = versionVendorID{ + Version: entry.FixedVersion, + VendorID: vendorID, } } } return latestVersions } -// determinePrimaryFixedVersion determines the primary fixed version for backward compatibility. -// The primary version is selected as follows: -// 1. If there is a normal flavor version available for the "x86_64" architecture, the highest version among these is chosen. -// 2. If no normal flavor version exists, the function falls back to the maximum version across all advisories in lexical order. -// -// This ensures backward compatibility by preferring the most relevant and common version for typical use cases. -func determinePrimaryFixedVersion(latestVersions map[archFlavor]types.Advisory) string { - // Check for the highest version of the normal flavor on the "x86_64" architecture +// determinePrimaryFixedVersion determines primary fixed version for backward compatibility +// It is chosen as the highest normal flavor version on x86_64 if any exist. +// If no normal flavor version exists, the maximum version in lexical order is chosen. +func determinePrimaryFixedVersion(latestVersions map[archFlavor]versionVendorID) string { primaryFixedVersion := latestVersions[archFlavor{ Arch: "x86_64", Flavor: NormalPackageFlavor, }] - - // If no normal flavor version exists, choose the maximum version lexicographically - if primaryFixedVersion.FixedVersion == "" { - primaryFixedVersion = lo.MaxBy(lo.Values(latestVersions), func(a, b types.Advisory) bool { - return a.FixedVersion > b.FixedVersion + if primaryFixedVersion.Version == "" { + vers := lo.MapToSlice(latestVersions, func(_ archFlavor, v versionVendorID) string { + return v.Version }) + primaryFixedVersion.Version = lo.Max(vers) // Chose the maximum value in lexical order for idempotency } - - return primaryFixedVersion.FixedVersion + return primaryFixedVersion.Version } type PkgFlavor string @@ -370,9 +347,7 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { } for pkg, advisory := range input.Advisories { - for i := range advisory.Entries { - advisory.Entries[i].VendorIDs = nil // Exclude VendorIDs from being saved - } + advisory = removeVendorIDs(advisory) platformName := pkg.PlatformName() if err := o.PutAdvisoryDetail(tx, input.VulnID, pkg.Name, []string{platformName}, advisory); err != nil { return eb.With("package_name", pkg.Name).With("bucket_name", platformName).Wrapf(err, "failed to save advisory") @@ -381,6 +356,32 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { return nil } +// removeVendorIDs removes VendorID from recommendations + merges arches for recommendations (by fixedVersion). +// This is needed to save space in OSS trivy-db. +// But Aqua storage requires this information. +func removeVendorIDs(advs types.Advisories) types.Advisories { + versionToArches := make(map[string][]string) // fixed version -> arches + for _, entry := range advs.Entries { + entry.VendorIDs = nil + versionToArches[entry.FixedVersion] = append(versionToArches[entry.FixedVersion], entry.Arches...) + } + + // Step 3: Build final entries, sorted by version + entries := lo.MapToSlice(versionToArches, func(ver string, arches []string) types.Advisory { + sort.Strings(arches) + return types.Advisory{ + FixedVersion: ver, + Arches: arches, + } + }) + sort.Slice(entries, func(i, j int) bool { + return entries[i].FixedVersion < entries[j].FixedVersion // Sorting lexicographically + }) + + advs.Entries = entries + return advs +} + func (o *Oracle) Get(release, pkgName, arch string) ([]types.Advisory, error) { eb := oops.In("oracle").Tags("oval").With("release", release).With("package_name", pkgName).With("arch", arch) bucket := fmt.Sprintf(platformFormat, release) @@ -480,18 +481,3 @@ func severityFromThreat(sev string) types.Severity { } return types.SeverityUnknown } - -// appendUnique appends unique elements from src to dest. -func appendUnique(dest []string, src ...string) []string { - existing := make(map[string]bool) - for _, d := range dest { - existing[d] = true - } - for _, s := range src { - if !existing[s] { - dest = append(dest, s) - existing[s] = true - } - } - return dest -} diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go index d96c3bad..e3314acd 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go @@ -565,16 +565,11 @@ func TestVulnSrc_Update(t *testing.T) { Value: types.Advisories{ FixedVersion: "2:2.28-151.0.1.ksplice1.el8", Entries: []types.Advisory{ - { - FixedVersion: "2:2.28-151.0.1.ksplice1.el8", - Arches: []string{ - "x86_64", - }, - }, { FixedVersion: "2:2.28-151.0.1.ksplice1.el8", Arches: []string{ "i386", + "x86_64", }, }, }, From 47d4c6ca71d4261e331332b1209a09d780316765 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 12 Feb 2025 11:33:38 +0600 Subject: [PATCH 4/4] refactor --- pkg/vulnsrc/oracle-oval/oracle-oval.go | 8 +++----- pkg/vulnsrc/oracle-oval/oracle-oval_test.go | 2 +- .../vuln-list/oval/oracle/2021/ELSA-2021-9280.json | 0 .../vuln-list/oval/oracle/2021/ELSA-2021-9344.json | 0 4 files changed, 4 insertions(+), 6 deletions(-) rename pkg/vulnsrc/oracle-oval/testdata/{multiple_elsa_ids_same_fix_version => multiple-elsa-ids-same-fix-version}/vuln-list/oval/oracle/2021/ELSA-2021-9280.json (100%) rename pkg/vulnsrc/oracle-oval/testdata/{multiple_elsa_ids_same_fix_version => multiple-elsa-ids-same-fix-version}/vuln-list/oval/oracle/2021/ELSA-2021-9344.json (100%) diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval.go b/pkg/vulnsrc/oracle-oval/oracle-oval.go index ce1bc4fe..27c1b7d7 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval.go @@ -227,7 +227,7 @@ type versionVendorID struct { VendorID string } -// mergeAdvisoriesEntries merges advisories by picking the latest version for each arch+flavor. +// mergeAdvisoriesEntries merges advisories by picking the latest version (with vendorID) for each arch+flavor. // There may be multiple advisories that fix the same vulnerability, possibly providing multiple fixed versions. // In this case, we need to determine the "latest" version, which is now defined as the highest (greatest) version number. // @@ -237,7 +237,7 @@ func mergeAdvisoriesEntries(advisories types.Advisories) types.Advisories { // Step 1: Select the latest version per (arch, flavor) latestVersions := selectLatestVersions(advisories) - // Step 2: Aggregate arches by their chosen version + // Step 2: Aggregate arches by their chosen version + vendorID versionToArches := make(map[versionVendorID][]string) for k, v := range latestVersions { versionToArches[v] = append(versionToArches[v], k.Arch) @@ -356,17 +356,15 @@ func (o *Oracle) Put(tx *bolt.Tx, input PutInput) error { return nil } -// removeVendorIDs removes VendorID from recommendations + merges arches for recommendations (by fixedVersion). +// removeVendorIDs removes VendorID from advisories + merges arches for advisories (by fixedVersion). // This is needed to save space in OSS trivy-db. // But Aqua storage requires this information. func removeVendorIDs(advs types.Advisories) types.Advisories { versionToArches := make(map[string][]string) // fixed version -> arches for _, entry := range advs.Entries { - entry.VendorIDs = nil versionToArches[entry.FixedVersion] = append(versionToArches[entry.FixedVersion], entry.Arches...) } - // Step 3: Build final entries, sorted by version entries := lo.MapToSlice(versionToArches, func(ver string, arches []string) types.Advisory { sort.Strings(arches) return types.Advisory{ diff --git a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go index e3314acd..7ac65d8c 100644 --- a/pkg/vulnsrc/oracle-oval/oracle-oval_test.go +++ b/pkg/vulnsrc/oracle-oval/oracle-oval_test.go @@ -550,7 +550,7 @@ func TestVulnSrc_Update(t *testing.T) { }, { name: "same fix version with different ELSA ids and architectures", - dir: filepath.Join("testdata", "multiple_elsa_ids_same_fix_version"), + dir: filepath.Join("testdata", "multiple-elsa-ids-same-fix-version"), wantValues: []vulnsrctest.WantValues{ { Key: []string{"data-source", "Oracle Linux 8"}, diff --git a/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json b/pkg/vulnsrc/oracle-oval/testdata/multiple-elsa-ids-same-fix-version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json similarity index 100% rename from pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json rename to pkg/vulnsrc/oracle-oval/testdata/multiple-elsa-ids-same-fix-version/vuln-list/oval/oracle/2021/ELSA-2021-9280.json diff --git a/pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json b/pkg/vulnsrc/oracle-oval/testdata/multiple-elsa-ids-same-fix-version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json similarity index 100% rename from pkg/vulnsrc/oracle-oval/testdata/multiple_elsa_ids_same_fix_version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json rename to pkg/vulnsrc/oracle-oval/testdata/multiple-elsa-ids-same-fix-version/vuln-list/oval/oracle/2021/ELSA-2021-9344.json