diff mbox series

[1/2] linux/generate-cve-exclusions: use data from CVEProject

Message ID 20250410094837.897013-1-daniel.turull@ericsson.com
State Accepted, archived
Commit 12612e8680798bdce39fbb79885e661596dbd53c
Headers show
Series [1/2] linux/generate-cve-exclusions: use data from CVEProject | expand

Commit Message

Daniel Turull April 10, 2025, 9:48 a.m. UTC
From: Daniel Turull <daniel.turull@ericsson.com>

The old script was relying on linuxkernelcves.com that was archived in
May 2024 when kernel.org became a CNA.

The new script reads CVE json files from the datadir that can be either
from the official kernel.org CNA [1] or CVEProject [2]

[1] https://git.kernel.org/pub/scm/linux/security/vulns.git
[2] https://github.com/CVEProject/cvelistV5

Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
---
 .../linux/generate-cve-exclusions.py          | 116 +++++++++++++-----
 1 file changed, 85 insertions(+), 31 deletions(-)

Comments

Marko, Peter April 25, 2025, 5:32 p.m. UTC | #1
> -----Original Message-----
> From: openembedded-core@lists.openembedded.org <openembedded-
> core@lists.openembedded.org> On Behalf Of Daniel Turull via
> lists.openembedded.org
> Sent: Thursday, April 10, 2025 11:49
> To: openembedded-core@lists.openembedded.org; bruce.ashfield@gmail.com
> Cc: Daniel Turull <daniel.turull@ericsson.com>
> Subject: [OE-core] [PATCH 2/2] cve-exclusions: update with all CVEs from
> linuxkernelcve
> 
> From: Daniel Turull <daniel.turull@ericsson.com>
> 
> Execute new script generate-cve-exclusions.py
> ./generate-cve-exclusions.py ~/cvelistV5/ 6.12.19 > cve-exclusion_6.12.inc
> 
> After using the database from CVEproject, some old
> CVEs did not have correct metadata, therefore moving missing ones
> from old cve-exclusions_6.12.inc into cve-exclusion.inc
> 
> Comparing output from cve_check before and after, two CVEs are removed:
> CVE-2023-52904 and CVE-2024-38381
> 
> Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
> ---
>  meta/recipes-kernel/linux/cve-exclusion.inc   |  118 +
>  .../linux/cve-exclusion_6.12.inc              | 9194 +++++++++++------
>  2 files changed, 5980 insertions(+), 3332 deletions(-)
> 
> diff --git a/meta/recipes-kernel/linux/cve-exclusion.inc b/meta/recipes-
> kernel/linux/cve-exclusion.inc
> index 7857633943..5f96a81bdd 100644
> --- a/meta/recipes-kernel/linux/cve-exclusion.inc
> +++ b/meta/recipes-kernel/linux/cve-exclusion.inc
> @@ -32,3 +32,121 @@ CVE_STATUS[CVE-2020-11935] = "not-applicable-
> config: Issue only affects aufs, wh
>  CVE_STATUS[CVE-2023-23005] = "disputed: There are no realistic cases \
>  in which a user can cause the alloc_memory_type error case to be reached. \
>  See: https://bugzilla.suse.com/show_bug.cgi?id=1208844#c2"
> +
> +# Old CVES taken before using new data from kernel CNA
> +
> +CVE_STATUS[CVE-2014-8171] = "fixed-version: Fixed from version 3.12rc1"
> +
> +CVE_STATUS[CVE-2017-1000255] = "fixed-version: Fixed from version 4.14rc5"
> +
> +CVE_STATUS[CVE-2018-10840] = "fixed-version: Fixed from version 4.18rc1"
> +
> +CVE_STATUS[CVE-2018-10876] = "fixed-version: Fixed from version 4.18rc4"
> +
> +CVE_STATUS[CVE-2018-10882] = "fixed-version: Fixed from version 4.18rc4"
> +
> +CVE_STATUS[CVE-2018-10902] = "fixed-version: Fixed from version 4.18rc6"
> +
> +CVE_STATUS[CVE-2018-14625] = "fixed-version: Fixed from version 4.20rc6"
> +
> +CVE_STATUS[CVE-2019-3016] = "fixed-version: Fixed from version 5.6rc1"
> +
> +CVE_STATUS[CVE-2019-3819] = "fixed-version: Fixed from version 5.0rc6"
> +
> +CVE_STATUS[CVE-2019-3887] = "fixed-version: Fixed from version 5.1rc4"
> +
> +CVE_STATUS[CVE-2020-10742] = "fixed-version: Fixed from version 3.16rc1"
> +
> +CVE_STATUS[CVE-2020-16119] = "fixed-version: Fixed from version 5.15rc2"
> +
> +CVE_STATUS[CVE-2020-1749] = "fixed-version: Fixed from version 5.5rc1"
> +
> +CVE_STATUS[CVE-2020-25672] = "fixed-version: Fixed from version 5.12rc7"
> +
> +CVE_STATUS[CVE-2020-27815] = "fixed-version: Fixed from version 5.11rc1"
> +
> +CVE_STATUS[CVE-2020-8834] = "fixed-version: Fixed from version 4.18rc1"
> +
> +CVE_STATUS[CVE-2021-20194] = "fixed-version: Fixed from version 5.10rc1"
> +
> +CVE_STATUS[CVE-2021-20265] = "fixed-version: Fixed from version 4.5rc3"
> +
> +CVE_STATUS[CVE-2021-3564] = "fixed-version: Fixed from version 5.13rc5"
> +
> +CVE_STATUS[CVE-2021-3669] = "fixed-version: Fixed from version 5.15rc1"
> +
> +CVE_STATUS[CVE-2021-3759] = "fixed-version: Fixed from version 5.15rc1"
> +
> +CVE_STATUS[CVE-2021-4218] = "fixed-version: Fixed from version 5.8rc1"
> +
> +CVE_STATUS[CVE-2022-0286] = "fixed-version: Fixed from version 5.14rc2"
> +
> +CVE_STATUS[CVE-2022-1462] = "fixed-version: Fixed from version 5.19rc7"
> +
> +CVE_STATUS[CVE-2022-2308] = "fixed-version: Fixed from version 6.0"
> +
> +CVE_STATUS[CVE-2022-2327] = "fixed-version: Fixed from version 5.12rc1"
> +
> +CVE_STATUS[CVE-2022-2663] = "fixed-version: Fixed from version 6.0rc5"
> +
> +CVE_STATUS[CVE-2022-2785] = "fixed-version: Fixed from version 6.0rc1"
> +
> +CVE_STATUS[CVE-2022-3435] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-3523] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-3534] = "fixed-version: Fixed from version 6.2rc1"
> +
> +CVE_STATUS[CVE-2022-3566] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-3567] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-3619] = "fixed-version: Fixed from version 6.1rc4"
> +
> +CVE_STATUS[CVE-2022-3621] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-3624] = "fixed-version: Fixed from version 6.0rc1"
> +
> +CVE_STATUS[CVE-2022-3629] = "fixed-version: Fixed from version 6.0rc1"
> +
> +CVE_STATUS[CVE-2022-3630] = "fixed-version: Fixed from version 6.0rc1"
> +
> +CVE_STATUS[CVE-2022-3633] = "fixed-version: Fixed from version 6.0rc1"
> +
> +CVE_STATUS[CVE-2022-3636] = "fixed-version: Fixed from version 5.19rc1"
> +
> +CVE_STATUS[CVE-2022-36402] = "fixed-version: Fixed from version 6.5"
> +
> +CVE_STATUS[CVE-2022-3646] = "fixed-version: Fixed from version 6.1rc1"
> +
> +CVE_STATUS[CVE-2022-42895] = "fixed-version: Fixed from version 6.1rc4"
> +
> +CVE_STATUS[CVE-2022-4382] = "fixed-version: Fixed from version 6.2rc5"
> +
> +CVE_STATUS[CVE-2023-1073] = "fixed-version: Fixed from version 6.2rc5"
> +
> +CVE_STATUS[CVE-2023-1074] = "fixed-version: Fixed from version 6.2rc6"
> +
> +CVE_STATUS[CVE-2023-1075] = "fixed-version: Fixed from version 6.2rc7"
> +
> +CVE_STATUS[CVE-2023-1076] = "fixed-version: Fixed from version 6.3rc1"
> +
> +CVE_STATUS[CVE-2023-2898] = "fixed-version: Fixed from version 6.5rc1"
> +
> +CVE_STATUS[CVE-2023-3772] = "fixed-version: Fixed from version 6.5rc7"
> +
> +CVE_STATUS[CVE-2023-3773] = "fixed-version: Fixed from version 6.5rc7"
> +
> +CVE_STATUS[CVE-2023-4155] = "fixed-version: Fixed from version 6.5rc6"
> +
> +CVE_STATUS[CVE-2023-6176] = "fixed-version: Fixed from version 6.6rc2"
> +
> +CVE_STATUS[CVE-2023-6270] = "cpe-stable-backport: Backported in 6.6.23"
> +
> +CVE_STATUS[CVE-2023-6610] = "cpe-stable-backport: Backported in 6.6.13"
> +
> +CVE_STATUS[CVE-2023-6679] = "fixed-version: only affects 6.7rc1 onwards"
> +
> +CVE_STATUS[CVE-2023-7042] = "cpe-stable-backport: Backported in 6.6.23"
> +
> +CVE_STATUS[CVE-2024-0193] = "cpe-stable-backport: Backported in 6.6.10"

The last 5 entries here are wrong.
CVEs in kernel 6.12 cannot be ignored based on statement about 6.6.x branch.

Peter
Daniel Turull April 25, 2025, 5:56 p.m. UTC | #2
Hi,
I’ll send a fix for this on Monday with the actual version where it was fixed. I used the data from the old file.

Have a nice weekend

Daniel
Gyorgy Sarvari April 26, 2025, 9:02 a.m. UTC | #3
On 4/10/25 11:48, Daniel Turull via lists.openembedded.org wrote:
> From: Daniel Turull <daniel.turull@ericsson.com>
>
> The old script was relying on linuxkernelcves.com that was archived in
> May 2024 when kernel.org became a CNA.
>
> The new script reads CVE json files from the datadir that can be either
> from the official kernel.org CNA [1] or CVEProject [2]
>
> [1] https://git.kernel.org/pub/scm/linux/security/vulns.git
> [2] https://github.com/CVEProject/cvelistV5
>
> Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
> ---
>  .../linux/generate-cve-exclusions.py          | 116 +++++++++++++-----
>  1 file 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 @@
>  #! /usr/bin/env python3
>  
>  # Generate granular CVE status metadata for a specific version of the kernel
> -# using data from linuxkernelcves.com.
> +# using json data from cvelistV5 or vulns repository
>  #
>  # SPDX-License-Identifier: GPL-2.0-only
>  
> @@ -9,7 +9,8 @@ import argparse
>  import datetime
>  import json
>  import pathlib
> -import re
> +import os
> +import glob
>  
>  from packaging.version import Version
>  
> @@ -25,22 +26,75 @@ def parse_version(s):
>          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
Is this part, the universally true? E.g. CVE-2024-46700 has been fixed
since 6.10.8, but the generated list indicates that there is no solution
for it. Looking at the raw data, it lists the fix, but without the
"original_commit_for_fix" versionType. Is this a data problem, or a
parsing one?
> +                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:
> +                    first_affected = v
> +                    fixed = less_than
> +                    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/nluedtke/linux_kernel_cves")
> +    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 = 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)
> +    base_version = Version(f"{version.major}.{version.minor}")
>  
>      print(f"""
>  # Auto-generated CVE metadata, DO NOT EDIT BY HAND.
> @@ -55,17 +109,23 @@ python check_kernel_cve_status_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
> +    # Loop though all CVES and check if they are kernel related, newer than 2015
> +    pattern = os.path.join(datadir, '**', "CVE-20*.json")
>  
> -        affected = data["affected_versions"]
> -        first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups()
> -        first_affected = parse_version(first_affected)
> -        fixed = parse_version(fixed)
> +    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:
> @@ -75,19 +135,13 @@ do_cve_check[prefuncs] += "check_kernel_cve_status_version"
>                  f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"'
>              )
>          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'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
> -                        )
> -                    else:
> -                        # TODO print a note that the kernel needs bumping
> -                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
> +            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 {fixed})")
> +                    print(f"# {cve} needs backporting (fixed from {backport_ver})")
>              else:
>                  print(f"# {cve} needs backporting (fixed from {fixed})")
>  
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#214634): https://lists.openembedded.org/g/openembedded-core/message/214634
> Mute This Topic: https://lists.openembedded.org/mt/112188268/6084445
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [skandigraun@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
Daniel Turull April 26, 2025, 4:43 p.m. UTC | #4
Hi Gyorgy,

I think is a data problem in the CVE information. From what I saw most of the CVEs by the kernel CNA has "original_commit_for_fix"
Probably there will be a handful of CVE that will need manual intervention, or involve more parsing to resolve where the commit is in the kernel.
Or we could assume that if there is no original commit for fix, but there is a major version that is unaffected, then we can assume that is fixed.

In this particular case for CVE-2024-46700 will be from 6.11.

https://git.kernel.org/pub/scm/linux/security/vulns.git/tree/cve/published/2024/CVE-2024-46700.json
                    "versions": [
                        {
                            "version": "6.11",
                            "status": "affected"
                        },
                        {
                            "version": "0",
                            "lessThan": "6.11",
                            "status": "unaffected",
                            "versionType": "semver"
                        },
                        {
                            "version": "6.10.8",
                            "lessThanOrEqual": "6.10.*",
                            "status": "unaffected",
                            "versionType": "semver"
                        }

Best regards,
Daniel

-----Original Message-----
From: Gyorgy Sarvari <skandigraun@gmail.com>
Sent: Saturday, 26 April 2025 11:03
To: Daniel Turull <daniel.turull@ericsson.com>; openembedded-core@lists.openembedded.org; bruce.ashfield@gmail.com
Subject: Re: [OE-core] [PATCH 1/2] linux/generate-cve-exclusions: use data from CVEProject

[You don't often get email from skandigraun@gmail.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]

On 4/10/25 11:48, Daniel Turull via lists.openembedded.org wrote:
> From: Daniel Turull <daniel.turull@ericsson.com>
>
> The old script was relying on linuxkernelcves.com that was archived in
> May 2024 when kernel.org became a CNA.
>
> The new script reads CVE json files from the datadir that can be
> either from the official kernel.org CNA [1] or CVEProject [2]
>
> [1]
>
> Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
> ---
>  .../linux/generate-cve-exclusions.py          | 116 +++++++++++++-----
>  1 file 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 @@
>  #! /usr/bin/env python3
>
>  # Generate granular CVE status metadata for a specific version of the
> kernel -# using data from linuxkernelcves.com.
> +# using json data from cvelistV5 or vulns repository
>  #
>  # SPDX-License-Identifier: GPL-2.0-only
>
> @@ -9,7 +9,8 @@ import argparse
>  import datetime
>  import json
>  import pathlib
> -import re
> +import os
> +import glob
>
>  from packaging.version import Version
>
> @@ -25,22 +26,75 @@ def parse_version(s):
>          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
Is this part, the universally true? E.g. CVE-2024-46700 has been fixed since 6.10.8, but the generated list indicates that there is no solution for it. Looking at the raw data, it lists the fix, but without the "original_commit_for_fix" versionType. Is this a data problem, or a parsing one?
> +                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:
> +                    first_affected = v
> +                    fixed = less_than
> +                    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/nluedtke/linux_kernel_cves")
> +    parser.add_argument("datadir", type=pathlib.Path, help="Path to a
> + clone of
>
>      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)
> +    base_version = Version(f"{version.major}.{version.minor}")
>
>      print(f"""
>  # Auto-generated CVE metadata, DO NOT EDIT BY HAND.
> @@ -55,17 +109,23 @@ python check_kernel_cve_status_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
> +    # Loop though all CVES and check if they are kernel related, newer than 2015
> +    pattern = os.path.join(datadir, '**', "CVE-20*.json")
>
> -        affected = data["affected_versions"]
> -        first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups()
> -        first_affected = parse_version(first_affected)
> -        fixed = parse_version(fixed)
> +    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:
> @@ -75,19 +135,13 @@ do_cve_check[prefuncs] += "check_kernel_cve_status_version"
>                  f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"'
>              )
>          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'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
> -                        )
> -                    else:
> -                        # TODO print a note that the kernel needs bumping
> -                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
> +            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 {fixed})")
> +                    print(f"# {cve} needs backporting (fixed from
> + {backport_ver})")
>              else:
>                  print(f"# {cve} needs backporting (fixed from
> {fixed})")
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#214634):
Daniel Turull April 28, 2025, 6 a.m. UTC | #5
Hi,
I think is a data problem in the CVE information. From what I saw most of the CVEs by the kernel CNA has "original_commit_for_fix"
Probably they will be a handful of CVE that will need manual intervention, or involve more parsing to resolve where the commit is in the kernel.
Or we could assume that if there is no original commit for fix, but there is a major version that is unaffected, then we can assume that is fixed.

In this particular case for CVE-2024-46700 will be from 6.11

                    "versions": [
                        {
                            "version": "6.11",
                            "status": "affected"
                        },
                        {
                            "version": "0",
                            "lessThan": "6.11",
                            "status": "unaffected",
                            "versionType": "semver"
                        },
                        {
                            "version": "6.10.8",
                            "lessThanOrEqual": "6.10.*",
                            "status": "unaffected",
                            "versionType": "semver"
                        }

Best regards,
Daniel

-----Original Message-----
From: Gyorgy Sarvari <skandigraun@gmail.com>
Sent: Saturday, 26 April 2025 11:03
To: Daniel Turull <daniel.turull@ericsson.com>; openembedded-core@lists.openembedded.org; bruce.ashfield@gmail.com
Subject: Re: [OE-core] [PATCH 1/2] linux/generate-cve-exclusions: use data from CVEProject

[You don't often get email from skandigraun@gmail.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]

On 4/10/25 11:48, Daniel Turull via lists.openembedded.org wrote:
> From: Daniel Turull <daniel.turull@ericsson.com>
>
> The old script was relying on linuxkernelcves.com that was archived in
> May 2024 when kernel.org became a CNA.
>
> The new script reads CVE json files from the datadir that can be
> either from the official kernel.org CNA [1] or CVEProject [2]
>
> [1]
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgit<https://git/>.
> kernel.org%2Fpub%2Fscm%2Flinux%2Fsecurity%2Fvulns.git&data=05%7C02%7Cd
> aniel.turull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84
> cebfbfd47abbe52080c6b87953f%7C0%7C0%7C638812549782452012%7CUnknown%7CT
> WFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiI
> sIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=c7iGjBRYMLeRXh84w
> 9NdryRbcuBhawJ6rb6GE%2B8QIxA%3D&reserved=0
> [2]
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgith<https://gith/>
> ub.com%2FCVEProject%2FcvelistV5&data=05%7C02%7Cdaniel.turull%40ericsso
> n.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84cebfbfd47abbe52080c6b8
> 7953f%7C0%7C0%7C638812549782472181%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1
> hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUI
> joyfQ%3D%3D%7C0%7C%7C%7C&sdata=Xl98xn%2BkPpf2PlhbFAsaZ0a4o%2BB22ZGpfZG
> n0xCjs0A%3D&reserved=0
>
> Signed-off-by: Daniel Turull <daniel.turull@ericsson.com>
> ---
>  .../linux/generate-cve-exclusions.py          | 116 +++++++++++++-----
>  1 file 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 @@
>  #! /usr/bin/env python3
>
>  # Generate granular CVE status metadata for a specific version of the
> kernel -# using data from linuxkernelcves.com.
> +# using json data from cvelistV5 or vulns repository
>  #
>  # SPDX-License-Identifier: GPL-2.0-only
>
> @@ -9,7 +9,8 @@ import argparse
>  import datetime
>  import json
>  import pathlib
> -import re
> +import os
> +import glob
>
>  from packaging.version import Version
>
> @@ -25,22 +26,75 @@ def parse_version(s):
>          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
Is this part, the universally true? E.g. CVE-2024-46700 has been fixed since 6.10.8, but the generated list indicates that there is no solution for it. Looking at the raw data, it lists the fix, but without the "original_commit_for_fix" versionType. Is this a data problem, or a parsing one?
> +                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:
> +                    first_affected = v
> +                    fixed = less_than
> +                    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://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnluedtke%2Flinux_kernel_cves&data=05%7C02%7Cdaniel.turull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84cebfbfd47abbe52080c6b87953f%7C0%7C0%7C638812549782484768%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=0r9Baryo71lokma3ZdKM3aDGeOBnUsiyydC3S3y8FbI%3D&reserved=0")<https://github.com/nluedtke/linux_kernel_cves>
> +    parser.add_argument("datadir", type=pathlib.Path, help="Path to a
> + clone of
> + https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgi<https://gi/>
> + thub.com%2FCVEProject%2FcvelistV5&data=05%7C02%7Cdaniel.turull%40eri
> + csson.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84cebfbfd47abbe520
> + 80c6b87953f%7C0%7C0%7C638812549782496794%7CUnknown%7CTWFpbGZsb3d8eyJ
> + FbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWF
> + pbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=dy6LRJujWanl9uh%2F%2FMhjXAY
> + ujxHTSIyFHcd8HF%2FLgws%3D&reserved=0 or
> + https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgi<https://gi/>
> + t.kernel.org%2Fpub%2Fscm%2Flinux%2Fsecurity%2Fvulns.git&data=05%7C02
> + %7Cdaniel.turull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099%7
> + C92e84cebfbfd47abbe52080c6b87953f%7C0%7C0%7C638812549782508741%7CUnk
> + nown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiO
> + iJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=YgCHu
> + oq6QdQICOtN5Qjk3ItN6rV7gXamVKpXog%2BlLUA%3D&reserved=0")
>      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)
> +    base_version = Version(f"{version.major}.{version.minor}")
>
>      print(f"""
>  # Auto-generated CVE metadata, DO NOT EDIT BY HAND.
> @@ -55,17 +109,23 @@ python check_kernel_cve_status_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
> +    # Loop though all CVES and check if they are kernel related, newer than 2015
> +    pattern = os.path.join(datadir, '**', "CVE-20*.json")
>
> -        affected = data["affected_versions"]
> -        first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups()
> -        first_affected = parse_version(first_affected)
> -        fixed = parse_version(fixed)
> +    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:
> @@ -75,19 +135,13 @@ do_cve_check[prefuncs] += "check_kernel_cve_status_version"
>                  f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"'
>              )
>          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'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
> -                        )
> -                    else:
> -                        # TODO print a note that the kernel needs bumping
> -                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
> +            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 {fixed})")
> +                    print(f"# {cve} needs backporting (fixed from
> + {backport_ver})")
>              else:
>                  print(f"# {cve} needs backporting (fixed from
> {fixed})")
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#214634):
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Flist<https://list/>
> s.openembedded.org%2Fg%2Fopenembedded-core%2Fmessage%2F214634&data=05%
> 7C02%7Cdaniel.turull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099
> %7C92e84cebfbfd47abbe52080c6b87953f%7C0%7C0%7C638812549782520520%7CUnk
> nown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJ
> XaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=N%2FajC%2
> BwaxgTyd5D2DAbsA182aWoqI0dlEF96UKH6cE0%3D&reserved=0
> Mute This Topic:
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Flist<https://list/>
> s.openembedded.org%2Fmt%2F112188268%2F6084445&data=05%7C02%7Cdaniel.tu
> rull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84cebfbfd4
> 7abbe52080c6b87953f%7C0%7C0%7C638812549782532455%7CUnknown%7CTWFpbGZsb
> 3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjo
> iTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=x03Wd8taSus7Zj8PBuFrIYuQz
> rF2WIvYlpN%2F9nPoouo%3D&reserved=0
> Group Owner: openembedded-core+owner@lists.openembedded.org
> Unsubscribe:
> https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Flist<https://list/>
> s.openembedded.org%2Fg%2Fopenembedded-core%2Funsub&data=05%7C02%7Cdani
> el.turull%40ericsson.com%7Cd67bf63bfe764e1b689208dd84a12099%7C92e84ceb
> fbfd47abbe52080c6b87953f%7C0%7C0%7C638812549782544122%7CUnknown%7CTWFp
> bGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIk
> FOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=xlzCTPsysHzP%2FD8Qas
> qif1ZEedwv5Rpwvy7jkZhLBPA%3D&reserved=0 [skandigraun@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
diff mbox series

Patch

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 @@ 
 #! /usr/bin/env python3
 
 # Generate granular CVE status metadata for a specific version of the kernel
-# using data from linuxkernelcves.com.
+# using json data from cvelistV5 or vulns repository
 #
 # SPDX-License-Identifier: GPL-2.0-only
 
@@ -9,7 +9,8 @@  import argparse
 import datetime
 import json
 import pathlib
-import re
+import os
+import glob
 
 from packaging.version import Version
 
@@ -25,22 +26,75 @@  def parse_version(s):
         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:
+                    first_affected = v
+                    fixed = less_than
+                    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/nluedtke/linux_kernel_cves")
+    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 = 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)
+    base_version = Version(f"{version.major}.{version.minor}")
 
     print(f"""
 # Auto-generated CVE metadata, DO NOT EDIT BY HAND.
@@ -55,17 +109,23 @@  python check_kernel_cve_status_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
+    # Loop though all CVES and check if they are kernel related, newer than 2015
+    pattern = os.path.join(datadir, '**', "CVE-20*.json")
 
-        affected = data["affected_versions"]
-        first_affected, fixed = re.search(r"(.+) to (.+)", affected).groups()
-        first_affected = parse_version(first_affected)
-        fixed = parse_version(fixed)
+    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:
@@ -75,19 +135,13 @@  do_cve_check[prefuncs] += "check_kernel_cve_status_version"
                 f'CVE_STATUS[{cve}] = "fixed-version: Fixed from version {fixed}"'
             )
         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'CVE_STATUS[{cve}] = "cpe-stable-backport: Backported in {backport_ver}"'
-                        )
-                    else:
-                        # TODO print a note that the kernel needs bumping
-                        print(f"# {cve} needs backporting (fixed from {backport_ver})")
+            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 {fixed})")
+                    print(f"# {cve} needs backporting (fixed from {backport_ver})")
             else:
                 print(f"# {cve} needs backporting (fixed from {fixed})")