Files
poky/meta/recipes-kernel/linux/generate-cve-exclusions.py
Peter Marko 0b25d55bc0 linux/cve-exclusion: do not shift first_affected
Stop shifting first_affected if backport is indicated. This does not
have effect on generated list, but makes the logic cleaner as it will
not shift it to "first affected on our branch" and also make it behave
like in defaultStatus==affected case.

Cc: daniel.turull@ericsson.com
(From OE-Core rev: dc1ecb69389dd79354084757ba6b9af0781afcc0)

Signed-off-by: Peter Marko <peter.marko@siemens.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2025-04-29 09:55:32 +01:00

151 lines
5.5 KiB
Python
Executable File

#! /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
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 == 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:
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
version = args.version
base_version = Version(f"{version.major}.{version.minor}")
print(f"""
# Auto-generated CVE metadata, DO NOT EDIT BY HAND.
# Generated at {datetime.datetime.now(datetime.timezone.utc)} 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"
""")
# 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} needs backporting (fixed from {backport_ver})")
else:
print(f"# {cve} needs backporting (fixed from {fixed})")
print()
if __name__ == "__main__":
main()