From d3ad19688db4360bf33fc0336fed200eecce793e Mon Sep 17 00:00:00 2001 From: Thomas Perale Date: Wed, 24 Jun 2026 12:13:39 +0200 Subject: [PATCH] utils/generate-cyclonedx: split vulnerabilities per state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a vulnerability is present in multiple components and only one of them has a patch that addresses that vulnerability, all components will currently have that vulnerability marked as `resolved_with_pedigree`. Right now, this issue does not affect any packages, but in the future it might affect packages that provide multiple version options (e.g. gnupg and gnupg2). There is a small chance this happens in a real-world use case, but it may occur in the context of maintenance when running `make show-info-all`. This commit changes how vulnerabilities fixed by a patch are stored. Instead of placing all vulnerabilities into a single set, it now keeps an index of the component for which the patch has been applied. When generating the vulnerability list, the component reference is checked instead of only the vulnerability ID. If a vulnerability ID has multiple states for different references, multiple vulnerability entries are created with distinct analyses. Consider the hypothetical case where gnupg ignores a vulnerability because it was introduced in a later version, while gnupg2 has patched that vulnerability. This would result in the following JSON: ```json [ { "id": "CVE-1234-1234", "analysis": { "state": "in_triage", "detail": "The CVE 'CVE-1234-1234' has been marked as ignored by Buildroot" }, "affects": [ {"ref": "gnupg"} ] }, { "id": "CVE-1234-1234", "analysis": { "state": "resolved_with_pedigree", "detail": "The CVE 'CVE-1234-1234' has been marked as ignored by Buildroot" }, "affects": [ {"ref": "gnupg2"} ] } ] ``` Signed-off-by: Thomas Perale [Arnout: - update the VULN_WITH_PEDIGREE comment to match the code - use set() as default instead of {}] Signed-off-by: Arnout Vandecappelle (cherry picked from commit 345c8ffc9693d1880168e2eb704b5fba5d928c42) Signed-off-by: Raphaël Mélotte --- utils/generate-cyclonedx | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/utils/generate-cyclonedx b/utils/generate-cyclonedx index fa20f6c1cf..98009cef98 100755 --- a/utils/generate-cyclonedx +++ b/utils/generate-cyclonedx @@ -12,6 +12,7 @@ import argparse import importlib.util import json import os +from collections import defaultdict from pathlib import Path from typing import Iterator import urllib.error @@ -36,10 +37,10 @@ BR2_VERSION_FULL = ( .strip() ) -# Set of vulnerabilities that were addressed by a patch present in buildroot -# tree. This set is used to set the analysis of the ignored CVEs to -# 'resolved_with_pedigree'. -VULN_WITH_PEDIGREE = set() +# Vulnerabilities that were addressed by a patch present in buildroot tree, +# as a set per component reference. This is used to set the analysis of the +# ignored CVEs of that component to 'resolved_with_pedigree'. +VULN_WITH_PEDIGREE = defaultdict(set) SPDX_LICENSES = [] @@ -218,11 +219,12 @@ def read_patch_file(patch_path: Path) -> str: return f.read() -def cyclonedx_patches(patch_list: list[str]): +def cyclonedx_patches(comp_ref: str, patch_list: list[str]): """Translate a list of patches from the show-info JSON to a list of patches in CycloneDX format. Args: + comp_ref (str): Reference of the component the patches are part of. patch_list (list): Array of patch relative paths for a given component. Returns: @@ -245,7 +247,7 @@ def cyclonedx_patches(patch_list: list[str]): issue = {} cves = extract_cves_from_header(header) if cves: - VULN_WITH_PEDIGREE.update(cves) + VULN_WITH_PEDIGREE[comp_ref].update(cves) issue = { "resolves": [ { @@ -402,7 +404,7 @@ def cyclonedx_component(name, comp): "cpe": comp["cpe-id"], } if "cpe-id" in comp else {}), **cyclonedx_external_refs(comp), - **(cyclonedx_patches(comp["patches"]) if comp.get("patches") else {}), + **(cyclonedx_patches(name, comp["patches"]) if comp.get("patches") else {}), "properties": [{ "name": "BR_TYPE", "value": comp["type"], @@ -437,11 +439,14 @@ def cyclonedx_vulnerabilities(show_info_dict): Returns: list: Solved vulnerabilities list in CycloneDX format. """ - cves = {} + cves = defaultdict(list) for name, comp in show_info_dict.items(): for cve in comp.get('ignore_cves', []): - cves.setdefault(cve, []).append(name) + state = "in_triage" + if cve in VULN_WITH_PEDIGREE.get(name, set()): + state = "resolved_with_pedigree" + cves[(cve, state)].append(name) return [{ "id": cve, @@ -450,13 +455,13 @@ def cyclonedx_vulnerabilities(show_info_dict): "url": "https://nvd.nist.gov/vuln/detail/" + cve }, "analysis": { - "state": "resolved_with_pedigree" if cve in VULN_WITH_PEDIGREE else "in_triage", + "state": state, "detail": f"The CVE '{cve}' has been marked as ignored by Buildroot" }, "affects": [ {"ref": bomref} for bomref in components ] - } for cve, components in cves.items()] + } for (cve, state), components in cves.items()] def br2_virtual_is_provided_by(ref, show_info_dict) -> list: