diff mbox series

[v2] cve-update-nvd2-native: Use maximum CVSS score from all sources

Message ID 20260121100832.448281-1-hetpat@cisco.com
State New
Headers show
Series [v2] cve-update-nvd2-native: Use maximum CVSS score from all sources | expand

Commit Message

From: Het Patel <hetpat@cisco.com>

The CVE check system was incorrectly reporting lower CVSS scores when
multiple scoring sources were available in the NVD database. This
occurred because the code only extracted the first element from the
CVSSv2, CVSSv3, and CVSSv4 metrics arrays, which could be a Secondary
source with a lower score instead of the Primary source with the
actual severity score.

This fix iterates through all available sources and takes the maximum
CVSS score to ensure the highest severity is reported.

Fixes [YOCTO #15931]

Signed-off-by: Het Patel <hetpat@cisco.com>
---
 .../meta/cve-update-nvd2-native.bb            | 55 +++++++++++++------
 1 file changed, 39 insertions(+), 16 deletions(-)

Comments

Paul Barker Jan. 21, 2026, 2:46 p.m. UTC | #1
On Wed, 2026-01-21 at 02:08 -0800, Het Patel via lists.openembedded.org
wrote:
> From: Het Patel <hetpat@cisco.com>
> 
> The CVE check system was incorrectly reporting lower CVSS scores when
> multiple scoring sources were available in the NVD database. This
> occurred because the code only extracted the first element from the
> CVSSv2, CVSSv3, and CVSSv4 metrics arrays, which could be a Secondary
> source with a lower score instead of the Primary source with the
> actual severity score.
> 
> This fix iterates through all available sources and takes the maximum
> CVSS score to ensure the highest severity is reported.
> 
> Fixes [YOCTO #15931]
> 
> Signed-off-by: Het Patel <hetpat@cisco.com>

Het,

Thanks for the patch!

Why is the highest CVSS score the correct one to take? I think the
commit message needs updating to answer this or point us to a relevant
reference.

Best regards,
Hi,

Thank you for the feedback. The following justifications, derived from official CVSS guidance and NVD standards, clarify why selecting the maximum score is the appropriate approach.

The National Vulnerability Database (NVD) often provides multiple scores for a single CVE. These are categorized as "Primary" (NVD’s own analysis) and "Secondary" (provided by vendors or other organizations). Because different sources may interpret the vulnerability’s impact differently based on product-specific contexts or information availability, these scores frequently diverge.
According to official documentation from FIRST (Forum of Incident Response and Security Teams), the governing body for CVSS, the "reasonable worst-case" assessment is the standard when multiple valid scores exist:


  1.
CVSS v4.0 User Guide: The guidance explicitly states: "In situations where multiple CVSS-B scores are applicable but only one is provided, the highest CVSS-B score must be utilized."


  1.
CVSS SIG (Special Interest Group): Historical guidance from the CVSS SIG recommends generating a score for each potential exploitation path and "assigning the vulnerability the highest of these scores."


  1.
Risk Assessment Principle: While CVSS v3.1 and v4.0 emphasize that the Base Score represents intrinsic characteristics, the standard practice for automated scanners and reporting tools is to report the highest severity to ensure that users are alerted to the maximum potential risk before they apply their own environmental or temporal filters.

By iterating through all sources and selecting the maximum score, we ensure that the cve-check system does not inadvertently report a lower score that might mask the actual severity of the vulnerability.

References:
[1] https://www.first.org/cvss/v4.0/user-guide
[2] https://www.first.org/cvss/v2/minutes/cvss-meeting-minutes-06202006.pdf
[3] https://www.first.org/cvss/v3.1/user-guide

Regards.
diff mbox series

Patch

diff --git a/meta/recipes-core/meta/cve-update-nvd2-native.bb b/meta/recipes-core/meta/cve-update-nvd2-native.bb
index 8c8148dd92..41c34ba0d0 100644
--- a/meta/recipes-core/meta/cve-update-nvd2-native.bb
+++ b/meta/recipes-core/meta/cve-update-nvd2-native.bb
@@ -350,32 +350,55 @@  def update_db(conn, elt):
         if desc['lang'] == 'en':
             cveDesc = desc['value']
     date = elt['cve']['lastModified']
+
+    # Extract maximum CVSS scores from all sources (Primary and Secondary)
+    cvssv2 = 0.0
     try:
-        accessVector = elt['cve']['metrics']['cvssMetricV2'][0]['cvssData']['accessVector']
-        vectorString = elt['cve']['metrics']['cvssMetricV2'][0]['cvssData']['vectorString']
-        cvssv2 = elt['cve']['metrics']['cvssMetricV2'][0]['cvssData']['baseScore']
+        # Iterate through all cvssMetricV2 entries and find the maximum score
+        for metric in elt['cve']['metrics']['cvssMetricV2']:
+            score = metric['cvssData']['baseScore']
+            if score > cvssv2:
+                cvssv2 = score
+                accessVector = metric['cvssData']['accessVector']
+                vectorString = metric['cvssData']['vectorString']
     except KeyError:
-        cvssv2 = 0.0
-    cvssv3 = None
+        pass
+
+    cvssv3 = 0.0
     try:
-        accessVector = accessVector or elt['cve']['metrics']['cvssMetricV30'][0]['cvssData']['attackVector']
-        vectorString = vectorString or elt['cve']['metrics']['cvssMetricV30'][0]['cvssData']['vectorString']
-        cvssv3 = elt['cve']['metrics']['cvssMetricV30'][0]['cvssData']['baseScore']
+        # Iterate through all cvssMetricV30 entries and find the maximum score
+        for metric in elt['cve']['metrics']['cvssMetricV30']:
+            score = metric['cvssData']['baseScore']
+            if score > cvssv3:
+                cvssv3 = score
+                accessVector = accessVector or metric['cvssData']['attackVector']
+                vectorString = vectorString or metric['cvssData']['vectorString']
     except KeyError:
         pass
+
     try:
-        accessVector = accessVector or elt['cve']['metrics']['cvssMetricV31'][0]['cvssData']['attackVector']
-        vectorString = vectorString or elt['cve']['metrics']['cvssMetricV31'][0]['cvssData']['vectorString']
-        cvssv3 = cvssv3 or elt['cve']['metrics']['cvssMetricV31'][0]['cvssData']['baseScore']
+        # Iterate through all cvssMetricV31 entries and find the maximum score
+        for metric in elt['cve']['metrics']['cvssMetricV31']:
+            score = metric['cvssData']['baseScore']
+            if score > cvssv3:
+                cvssv3 = score
+                accessVector = accessVector or metric['cvssData']['attackVector']
+                vectorString = vectorString or metric['cvssData']['vectorString']
     except KeyError:
         pass
-    cvssv3 = cvssv3 or 0.0
+
+    cvssv4 = 0.0
     try:
-        accessVector = accessVector or elt['cve']['metrics']['cvssMetricV40'][0]['cvssData']['attackVector']
-        vectorString = vectorString or elt['cve']['metrics']['cvssMetricV40'][0]['cvssData']['vectorString']
-        cvssv4 = elt['cve']['metrics']['cvssMetricV40'][0]['cvssData']['baseScore']
+        # Iterate through all cvssMetricV40 entries and find the maximum score
+        for metric in elt['cve']['metrics']['cvssMetricV40']:
+            score = metric['cvssData']['baseScore']
+            if score > cvssv4:
+                cvssv4 = score
+                accessVector = accessVector or metric['cvssData']['attackVector']
+                vectorString = vectorString or metric['cvssData']['vectorString']
     except KeyError:
-        cvssv4 = 0.0
+        pass
+
     accessVector = accessVector or "UNKNOWN"
     vectorString = vectorString or "UNKNOWN"