diff mbox series

[1/2] linux/generate-cve-exclusions: use data from CVEProject

Message ID 20250410094837.897013-1-daniel.turull@ericsson.com
State New
Headers show
Series [1/2] linux/generate-cve-exclusions: use data from CVEProject | expand

Commit Message

daniel.turull@ericsson.com April 10, 2025, 9:48 a.m. UTC
From: Daniel Turull <daniel.turull@ericsson.com>

The old script was relying on linuxkernelcves.com that was archived in
May 2024 when kernel.org became a CNA.

The new script reads CVE json files from the datadir that can be either
from the official kernel.org CNA [1] or CVEProject [2]

[1] https://git.kernel.org/pub/scm/linux/security/vulns.git
[2] https://github.com/CVEProject/cvelistV5

Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
 .../linux/generate-cve-exclusions.py          | 116 +++++++++++++-----
 1 file changed, 85 insertions(+), 31 deletions(-)
diff mbox series

Patch

diff --git a/meta/recipes-kernel/linux/generate-cve-exclusions.py b/meta/recipes-kernel/linux/generate-cve-exclusions.py
index aa9195aab4..82fb4264e3 100755
--- a/meta/recipes-kernel/linux/generate-cve-exclusions.py
+++ b/meta/recipes-kernel/linux/generate-cve-exclusions.py
@@ -1,7 +1,7 @@ 
 #! /usr/bin/env python3
 
 # Generate granular CVE status metadata for a specific version of the kernel
-# using data from linuxkernelcves.com.
+# using json data from cvelistV5 or vulns repository
 #
 # SPDX-License-Identifier: GPL-2.0-only
 
@@ -9,7 +9,8 @@  import argparse
 import datetime
 import json
 import pathlib
-import re
+import os
+import glob
 
 from packaging.version import Version
 
@@ -25,22 +26,75 @@  def parse_version(s):
         return Version(s)
     return None
 
+def get_fixed_versions(cve_info, base_version):
+    '''
+    Get fixed versionss
+    '''
+    first_affected = None
+    fixed = None
+    fixed_backport = None
+    next_version = Version(str(base_version) + ".5000")
+    for affected in cve_info["containers"]["cna"]["affected"]:
+        # In case the CVE info is not complete, it might not have default status and therefore
+        # we don't know the status of this CVE.
+        if not "defaultStatus" in affected:
+            return first_affected, fixed, fixed_backport
+        if affected["defaultStatus"] == "affected":
+            for version in affected["versions"]:
+                v = Version(version["version"])
+                if v == 0:
+                    #Skiping non-affected
+                    continue
+                if version["status"] == "affected" and not first_affected:
+                    first_affected = v
+                elif (version["status"] == "unaffected" and
+                    version['versionType'] == "original_commit_for_fix"):
+                    fixed = v
+                elif base_version < v and v < next_version:
+                    fixed_backport = v
+        elif affected["defaultStatus"] == "unaffected":
+            # Only specific versions are affected. We care only about our base version
+            if "versions" not in affected:
+                continue
+            for version in affected["versions"]:
+                if "versionType" not in version:
+                    continue
+                if version["versionType"] == "git":
+                    continue
+                v = Version(version["version"])
+                # in case it is not in our base version
+                less_than = Version(version["lessThan"])
+
+                if not first_affected:
+                    first_affected = v
+                    fixed = less_than
+                if base_version < v and v < next_version:
+                    first_affected = v
+                    fixed = less_than
+                    fixed_backport = less_than
+
+    return first_affected, fixed, fixed_backport
+
+def is_linux_cve(cve_info):
+    '''Return true is the CVE belongs to Linux'''
+    if not "affected" in cve_info["containers"]["cna"]:
+        return False
+    for affected in cve_info["containers"]["cna"]["affected"]:
+        if not "product" in affected:
+            return False
+        if affected["product"] == "Linux" and affected["vendor"] == "Linux":
+            return True
+    return False
 
 def main(argp=None):
     parser = argparse.ArgumentParser()
-    parser.add_argument("datadir", type=pathlib.Path, help="Path to a clone of https://github.com/nluedtke/linux_kernel_cves")
+    parser.add_argument("datadir", type=pathlib.Path, help="Path to a clone of https://github.com/CVEProject/cvelistV5 or https://git.kernel.org/pub/scm/linux/security/vulns.git")
     parser.add_argument("version", type=Version, help="Kernel version number to generate data for, such as 6.1.38")
 
     args = parser.parse_args(argp)
     datadir = args.datadir
     version = args.version
-    base_version = f"{version.major}.{version.minor}"
-
-    with open(datadir / "data" / "kernel_cves.json", "r") as f:
-        cve_data = json.load(f)
-
-    with open(datadir / "data" / "stream_fixes.json", "r") as f:
-        stream_data = json.load(f)
+    base_version = Version(f"{version.major}.{version.minor}")
 
     print(f"""
 # Auto-generated CVE metadata, DO NOT EDIT BY HAND.
@@ -55,17 +109,23 @@  python check_kernel_cve_status_version() {{
 do_cve_check[prefuncs] += "check_kernel_cve_status_version"
 """)
 
-    for cve, data in cve_data.items():
-        if "affected_versions" not in data:
-            print(f"# Skipping {cve}, no affected_versions")
-            print()
-            continue
+    # Loop though all CVES and check if they are kernel related, newer than 2015
+    pattern = os.path.join(datadir, '**', "CVE-20*.json")
 
-        affected = data["affected_versions"]
-        first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups()
-        first_affected = parse_version(first_affected)
-        fixed = parse_version(fixed)
+    files = glob.glob(pattern, recursive=True)
+    for cve_file in sorted(files):
+        # Get CVE Id
+        cve = cve_file[cve_file.rfind("/")+1:cve_file.rfind(".json")]
+        # We process from 2015 data, old request are not properly formated
+        year = cve.split("-")[1]
+        if int(year) < 2015:
+            continue
+        with open(cve_file, 'r', encoding='utf-8') as json_file:
+            cve_info = json.load(json_file)
 
+        if not is_linux_cve(cve_info):
+            continue
+        first_affected, fixed, backport_ver = get_fixed_versions(cve_info, base_version)
         if not fixed:
             print(f"# {cve} has no known resolution")
         elif first_affected and version < first_affected:
@@ -75,19 +135,13 @@  do_cve_check[prefuncs] += "check_kernel_cve_status_version"
                 f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"'
             )
         else:
-            if cve in stream_data:
-                backport_data = stream_data[cve]
-                if base_version in backport_data:
-                    backport_ver = Version(backport_data[base_version]["fixed_version"])
-                    if backport_ver <= version:
-                        print(
-                            f'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
-                        )
-                    else:
-                        # TODO print a note that the kernel needs bumping
-                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
+            if backport_ver:
+                if backport_ver <= version:
+                    print(
+                        f'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
+                    )
                 else:
-                    print(f"# {cve} needs backporting (fixed from {fixed})")
+                    print(f"# {cve} needs backporting (fixed from {backport_ver})")
             else:
                 print(f"# {cve} needs backporting (fixed from {fixed})")