#! /usr/bin/env python3 # Generate granular CVE status metadata for a specific version of the kernel # using json data from cvelistV5 or vulns repository # # SPDX-License-Identifier: GPL-2.0-only import argparse import datetime import json import pathlib import os import glob import subprocess 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 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 == Version('0'): #Skiping non-affected continue if version["status"] == "unaffected" and first_affected and v < first_affected: first_affected = Version(f"{v.major}.{v.minor}") 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: 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/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.resolve() version = args.version base_version = Version(f"{version.major}.{version.minor}") data_version = subprocess.check_output(("git", "describe", "--tags", "HEAD"), cwd=datadir, text=True) print(f""" # Auto-generated CVE metadata, DO NOT EDIT BY HAND. # Generated at {datetime.datetime.now(datetime.timezone.utc)} for kernel version {version} # From {datadir.name} {data_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" """) # Loop though all CVES and check if they are kernel related, newer than 2015 pattern = os.path.join(datadir, '**', "CVE-20*.json") 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: print(f'CVE_STATUS[{cve}] = "fixed-version: only affects {first_affected} onwards"') elif fixed <= version: print( f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"' ) else: if backport_ver: if backport_ver <= version: print( f'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"' ) else: print(f"# {cve} may need backporting (fixed from {backport_ver})") else: print(f"# {cve} needs backporting (fixed from {fixed})") print() if __name__ == "__main__": main()