diff options
-rwxr-xr-x | meta/recipes-kernel/linux/generate-cve-exclusions.py | 116 |
1 files changed, 85 insertions, 31 deletions
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 @@ | |||
1 | #! /usr/bin/env python3 | 1 | #! /usr/bin/env python3 |
2 | 2 | ||
3 | # Generate granular CVE status metadata for a specific version of the kernel | 3 | # Generate granular CVE status metadata for a specific version of the kernel |
4 | # using data from linuxkernelcves.com. | 4 | # using json data from cvelistV5 or vulns repository |
5 | # | 5 | # |
6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
7 | 7 | ||
@@ -9,7 +9,8 @@ import argparse | |||
9 | import datetime | 9 | import datetime |
10 | import json | 10 | import json |
11 | import pathlib | 11 | import pathlib |
12 | import re | 12 | import os |
13 | import glob | ||
13 | 14 | ||
14 | from packaging.version import Version | 15 | from packaging.version import Version |
15 | 16 | ||
@@ -25,22 +26,75 @@ def parse_version(s): | |||
25 | return Version(s) | 26 | return Version(s) |
26 | return None | 27 | return None |
27 | 28 | ||
29 | def get_fixed_versions(cve_info, base_version): | ||
30 | ''' | ||
31 | Get fixed versionss | ||
32 | ''' | ||
33 | first_affected = None | ||
34 | fixed = None | ||
35 | fixed_backport = None | ||
36 | next_version = Version(str(base_version) + ".5000") | ||
37 | for affected in cve_info["containers"]["cna"]["affected"]: | ||
38 | # In case the CVE info is not complete, it might not have default status and therefore | ||
39 | # we don't know the status of this CVE. | ||
40 | if not "defaultStatus" in affected: | ||
41 | return first_affected, fixed, fixed_backport | ||
42 | if affected["defaultStatus"] == "affected": | ||
43 | for version in affected["versions"]: | ||
44 | v = Version(version["version"]) | ||
45 | if v == 0: | ||
46 | #Skiping non-affected | ||
47 | continue | ||
48 | if version["status"] == "affected" and not first_affected: | ||
49 | first_affected = v | ||
50 | elif (version["status"] == "unaffected" and | ||
51 | version['versionType'] == "original_commit_for_fix"): | ||
52 | fixed = v | ||
53 | elif base_version < v and v < next_version: | ||
54 | fixed_backport = v | ||
55 | elif affected["defaultStatus"] == "unaffected": | ||
56 | # Only specific versions are affected. We care only about our base version | ||
57 | if "versions" not in affected: | ||
58 | continue | ||
59 | for version in affected["versions"]: | ||
60 | if "versionType" not in version: | ||
61 | continue | ||
62 | if version["versionType"] == "git": | ||
63 | continue | ||
64 | v = Version(version["version"]) | ||
65 | # in case it is not in our base version | ||
66 | less_than = Version(version["lessThan"]) | ||
67 | |||
68 | if not first_affected: | ||
69 | first_affected = v | ||
70 | fixed = less_than | ||
71 | if base_version < v and v < next_version: | ||
72 | first_affected = v | ||
73 | fixed = less_than | ||
74 | fixed_backport = less_than | ||
75 | |||
76 | return first_affected, fixed, fixed_backport | ||
77 | |||
78 | def is_linux_cve(cve_info): | ||
79 | '''Return true is the CVE belongs to Linux''' | ||
80 | if not "affected" in cve_info["containers"]["cna"]: | ||
81 | return False | ||
82 | for affected in cve_info["containers"]["cna"]["affected"]: | ||
83 | if not "product" in affected: | ||
84 | return False | ||
85 | if affected["product"] == "Linux" and affected["vendor"] == "Linux": | ||
86 | return True | ||
87 | return False | ||
28 | 88 | ||
29 | def main(argp=None): | 89 | def main(argp=None): |
30 | parser = argparse.ArgumentParser() | 90 | parser = argparse.ArgumentParser() |
31 | parser.add_argument("datadir", type=pathlib.Path, help="Path to a clone of https://github.com/nluedtke/linux_kernel_cves") | 91 | 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") |
32 | parser.add_argument("version", type=Version, help="Kernel version number to generate data for, such as 6.1.38") | 92 | parser.add_argument("version", type=Version, help="Kernel version number to generate data for, such as 6.1.38") |
33 | 93 | ||
34 | args = parser.parse_args(argp) | 94 | args = parser.parse_args(argp) |
35 | datadir = args.datadir | 95 | datadir = args.datadir |
36 | version = args.version | 96 | version = args.version |
37 | base_version = f"{version.major}.{version.minor}" | 97 | base_version = Version(f"{version.major}.{version.minor}") |
38 | |||
39 | with open(datadir / "data" / "kernel_cves.json", "r") as f: | ||
40 | cve_data = json.load(f) | ||
41 | |||
42 | with open(datadir / "data" / "stream_fixes.json", "r") as f: | ||
43 | stream_data = json.load(f) | ||
44 | 98 | ||
45 | print(f""" | 99 | print(f""" |
46 | # Auto-generated CVE metadata, DO NOT EDIT BY HAND. | 100 | # Auto-generated CVE metadata, DO NOT EDIT BY HAND. |
@@ -55,17 +109,23 @@ python check_kernel_cve_status_version() {{ | |||
55 | do_cve_check[prefuncs] += "check_kernel_cve_status_version" | 109 | do_cve_check[prefuncs] += "check_kernel_cve_status_version" |
56 | """) | 110 | """) |
57 | 111 | ||
58 | for cve, data in cve_data.items(): | 112 | # Loop though all CVES and check if they are kernel related, newer than 2015 |
59 | if "affected_versions" not in data: | 113 | pattern = os.path.join(datadir, '**', "CVE-20*.json") |
60 | print(f"# Skipping {cve}, no affected_versions") | ||
61 | print() | ||
62 | continue | ||
63 | 114 | ||
64 | affected = data["affected_versions"] | 115 | files = glob.glob(pattern, recursive=True) |
65 | first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups() | 116 | for cve_file in sorted(files): |
66 | first_affected = parse_version(first_affected) | 117 | # Get CVE Id |
67 | fixed = parse_version(fixed) | 118 | cve = cve_file[cve_file.rfind("/")+1:cve_file.rfind(".json")] |
119 | # We process from 2015 data, old request are not properly formated | ||
120 | year = cve.split("-")[1] | ||
121 | if int(year) < 2015: | ||
122 | continue | ||
123 | with open(cve_file, 'r', encoding='utf-8') as json_file: | ||
124 | cve_info = json.load(json_file) | ||
68 | 125 | ||
126 | if not is_linux_cve(cve_info): | ||
127 | continue | ||
128 | first_affected, fixed, backport_ver = get_fixed_versions(cve_info, base_version) | ||
69 | if not fixed: | 129 | if not fixed: |
70 | print(f"# {cve} has no known resolution") | 130 | print(f"# {cve} has no known resolution") |
71 | elif first_affected and version < first_affected: | 131 | elif first_affected and version < first_affected: |
@@ -75,19 +135,13 @@ do_cve_check[prefuncs] += "check_kernel_cve_status_version" | |||
75 | f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"' | 135 | f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"' |
76 | ) | 136 | ) |
77 | else: | 137 | else: |
78 | if cve in stream_data: | 138 | if backport_ver: |
79 | backport_data = stream_data[cve] | 139 | if backport_ver <= version: |
80 | if base_version in backport_data: | 140 | print( |
81 | backport_ver = Version(backport_data[base_version]["fixed_version"]) | 141 | f'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"' |
82 | if backport_ver <= version: | 142 | ) |
83 | print( | ||
84 | f'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"' | ||
85 | ) | ||
86 | else: | ||
87 | # TODO print a note that the kernel needs bumping | ||
88 | print(f"# {cve} needs backporting (fixed from {backport_ver})") | ||
89 | else: | 143 | else: |
90 | print(f"# {cve} needs backporting (fixed from {fixed})") | 144 | print(f"# {cve} needs backporting (fixed from {backport_ver})") |
91 | else: | 145 | else: |
92 | print(f"# {cve} needs backporting (fixed from {fixed})") | 146 | print(f"# {cve} needs backporting (fixed from {fixed})") |
93 | 147 | ||