diff mbox series

[kirkstone,5/8] linux-yocto: add script to generate kernel CVE_CHECK_IGNORE entries

Message ID c7a71692b7ed4cc2187f4c82bf11e32e0ce32cb6.1693169420.git.steve@sakoman.com
State Accepted, archived
Commit c7a71692b7ed4cc2187f4c82bf11e32e0ce32cb6
Headers show
Series [kirkstone,1/8] ffmpeg: add CVE_CHECK_IGNORE for CVE-2023-39018 | expand

Commit Message

Steve Sakoman Aug. 27, 2023, 8:52 p.m. UTC
From: Ross Burton <ross.burton@arm.com>

Instead of manually looking up new CVEs and determining what point
releases the fixes are incorporated into, add a script to generate the
CVE_CHECK_IGNORE data automatically.

First, note that this is very much an interim solution until the
cve-check class fetches data from www.linuxkernelcves.com directly.

The script should be passed the path to a local clone of the
linuxkernelcves repository[1] and the kernel version number. It will
then write to standard output the CVE_STATUS entries for every known
kernel CVE.

The script should be periodically reran as CVEs are backported and
kernels upgraded frequently.

[1] https://github.com/nluedtke/linux_kernel_cves

Note: for the backport this is not a cherry-pick of the commit in master
as the variable names are different. This incorporates the following
commits:

linux/generate-cve-exclusions: add version check warning
linux/generate-cve-exclusions.py: fix comparison
linux-yocto: add script to generate kernel CVE_STATUS entries

Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
---
 .../linux/generate-cve-exclusions.py          | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100755 meta/recipes-kernel/linux/generate-cve-exclusions.py
diff mbox series

Patch

diff --git a/meta/recipes-kernel/linux/generate-cve-exclusions.py b/meta/recipes-kernel/linux/generate-cve-exclusions.py
new file mode 100755
index 0000000000..b9b87f245d
--- /dev/null
+++ b/meta/recipes-kernel/linux/generate-cve-exclusions.py
@@ -0,0 +1,101 @@ 
+#! /usr/bin/env python3
+
+# Generate granular CVE status metadata for a specific version of the kernel
+# using data from linuxkernelcves.com.
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+import argparse
+import datetime
+import json
+import pathlib
+import re
+
+from packaging.version import Version
+
+
+def parse_version(s):
+    """
+    Parse the version string and either return a packaging.version.Version, or
+    None if the string was unset or "unk".
+    """
+    if s and s != "unk":
+        # packaging.version.Version doesn't approve of versions like v5.12-rc1-dontuse
+        s = s.replace("-dontuse", "")
+        return Version(s)
+    return None
+
+
+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("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)
+
+    print(f"""
+# Auto-generated CVE metadata, DO NOT EDIT BY HAND.
+# Generated at {datetime.datetime.now()} for version {version}
+
+python check_kernel_cve_status_version() {{
+    this_version = "{version}"
+    kernel_version = d.getVar("LINUX_VERSION")
+    if kernel_version != this_version:
+        bb.warn("Kernel CVE status needs updating: generated for %s but kernel is %s" % (this_version, kernel_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
+
+        affected = data["affected_versions"]
+        first_affected, last_affected = re.search(r"(.+) to (.+)", affected).groups()
+        first_affected = parse_version(first_affected)
+        last_affected = parse_version(last_affected)
+
+        handled = False
+        if not last_affected:
+            print(f"# {cve} has no known resolution")
+        elif first_affected and version < first_affected:
+            print(f"# fixed-version: only affects {first_affected} onwards")
+            handled = True
+        elif last_affected < version:
+            print(f"# fixed-version: Fixed after version {last_affected}")
+            handled = True
+        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"# cpe-stable-backport: Backported in {backport_ver}")
+                        handled = True
+                    else:
+                        # TODO print a note that the kernel needs bumping
+                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
+                else:
+                    print(f"# {cve} needs backporting (fixed from {last_affected})")
+            else:
+                print(f"# {cve} needs backporting (fixed from {last_affected})")
+
+        if handled:
+            print(f'CVE_CHECK_IGNORE += "{cve}"')
+
+        print()
+
+
+if __name__ == "__main__":
+    main()