123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- #! /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()
|