Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.0/patches/2197823/?format=api
{ "id": 2197823, "url": "http://patchwork.ozlabs.org/api/1.0/patches/2197823/?format=api", "project": { "id": 27, "url": "http://patchwork.ozlabs.org/api/1.0/projects/27/?format=api", "name": "Buildroot development", "link_name": "buildroot", "list_id": "buildroot.buildroot.org", "list_email": "buildroot@buildroot.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260218163205.1639035-3-fabien.lehoussel@smile.fr>", "date": "2026-02-18T16:32:04", "name": "[2/2] support/scripts: add filter-kernel-cve script for Linux kernel CVE analysis", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "9d4add0ff9fb234da4e7c20f450ce4c02fa16140", "submitter": { "id": 91059, "url": "http://patchwork.ozlabs.org/api/1.0/people/91059/?format=api", "name": "Fabien LEHOUSSEL", "email": "fabien.lehoussel@smile.fr" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/buildroot/patch/20260218163205.1639035-3-fabien.lehoussel@smile.fr/mbox/", "series": [ { "id": 492573, "url": "http://patchwork.ozlabs.org/api/1.0/series/492573/?format=api", "date": "2026-02-18T16:32:03", "name": "Linux kernel CVE filtering improvements", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/492573/mbox/" } ], "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2197823/checks/", "tags": {}, "headers": { "Return-Path": "<buildroot-bounces@buildroot.org>", "X-Original-To": [ "incoming-buildroot@patchwork.ozlabs.org", "buildroot@buildroot.org" ], "Delivered-To": [ "patchwork-incoming-buildroot@legolas.ozlabs.org", "buildroot@buildroot.org" ], "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=buildroot.org header.i=@buildroot.org\n header.a=rsa-sha256 header.s=default header.b=iAOwI8PP;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=buildroot.org\n (client-ip=2605:bc80:3010::136; helo=smtp3.osuosl.org;\n envelope-from=buildroot-bounces@buildroot.org; receiver=patchwork.ozlabs.org)" ], "Received": [ "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fGMVY1fT9z1xvq\n\tfor <incoming-buildroot@patchwork.ozlabs.org>;\n Thu, 19 Feb 2026 03:32:21 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id C309C60904;\n\tWed, 18 Feb 2026 16:32:19 +0000 (UTC)", "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 0Q_UzOJkJJLJ; Wed, 18 Feb 2026 16:32:17 +0000 (UTC)", "from lists1.osuosl.org (lists1.osuosl.org [140.211.166.142])\n\tby smtp3.osuosl.org (Postfix) with ESMTP id A690A60A4E;\n\tWed, 18 Feb 2026 16:32:17 +0000 (UTC)", "from smtp3.osuosl.org (smtp3.osuosl.org [IPv6:2605:bc80:3010::136])\n by lists1.osuosl.org (Postfix) with ESMTP id 761B3206\n for <buildroot@buildroot.org>; Wed, 18 Feb 2026 16:32:14 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp3.osuosl.org (Postfix) with ESMTP id 5BFFE608F5\n for <buildroot@buildroot.org>; Wed, 18 Feb 2026 16:32:14 +0000 (UTC)", "from smtp3.osuosl.org ([127.0.0.1])\n by localhost (smtp3.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id U5CmZIp1V4pN for <buildroot@buildroot.org>;\n Wed, 18 Feb 2026 16:32:13 +0000 (UTC)", "from mail-wm1-x343.google.com (mail-wm1-x343.google.com\n [IPv6:2a00:1450:4864:20::343])\n by smtp3.osuosl.org (Postfix) with ESMTPS id 81B5E60902\n for <buildroot@buildroot.org>; Wed, 18 Feb 2026 16:32:12 +0000 (UTC)", "by mail-wm1-x343.google.com with SMTP id\n 5b1f17b1804b1-4837584120eso161785e9.1\n for <buildroot@buildroot.org>; Wed, 18 Feb 2026 08:32:12 -0800 (PST)", "from FRSMI25-GRAVITY.. ([2a01:e0a:943:83d0:6dc6:c524:f7df:4d64])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-483983e8e3dsm20677605e9.20.2026.02.18.08.32.09\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Wed, 18 Feb 2026 08:32:09 -0800 (PST)" ], "X-Virus-Scanned": [ "amavis at osuosl.org", "amavis at osuosl.org" ], "X-Comment": "SPF check N/A for local connections - client-ip=140.211.166.142;\n helo=lists1.osuosl.org; envelope-from=buildroot-bounces@buildroot.org;\n receiver=<UNKNOWN> ", "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 smtp3.osuosl.org A690A60A4E", "OpenDKIM Filter v2.11.0 smtp3.osuosl.org 81B5E60902" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=buildroot.org;\n\ts=default; t=1771432337;\n\tbh=laYuNYnmefYaT34Ee4j0CxGLh260os3stPemyRaWNGc=;\n\th=To:Cc:Date:In-Reply-To:References:Subject:List-Id:\n\t List-Unsubscribe:List-Archive:List-Post:List-Help:List-Subscribe:\n\t From:Reply-To:From;\n\tb=iAOwI8PPVSELePEJX4KF4rVXvU7ObSf2shl/YyTScUaUBfOekYUFcbd5xF8PeKhuh\n\t YA+VzrDE1E6uZEwzzU6Bt1kv/S74aam7J9iqtfk3SBPkH15JxB2oCsBjb3fGIWqisP\n\t 4PNZdBcH/Z0lXjwoYazlfPmkB97gW1z+r2o1uzi2cCJL5XnKv/X+jR/DAwNW6CX0mM\n\t /NSpKd3q6xBuUD4w5ugvi/VUuwiblyhd9UhMoeARE/EI5cTX/x2SJkFt93Gp3eSoj3\n\t 1NdP+JoX+MgFb10grV25csh1jmMUK34JfzG/rBQRo1Xtm7VgGeCwYlGOqm8lgBsswj\n\t 9Thvb3x6AQPQg==", "Received-SPF": "Pass (mailfrom) identity=mailfrom;\n client-ip=2a00:1450:4864:20::343; helo=mail-wm1-x343.google.com;\n envelope-from=fabien.lehoussel@smile.fr; receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp3.osuosl.org 81B5E60902", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1771432330; x=1772037130;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=ZoVLyL39SKoXOPXTChwP5wuAP2uDliUAjyLSoOUBq/w=;\n b=Pop2AEYRjsxe4UxFw3ckEEHF6urW9S0f40uxsW0hbz29P5msjH0wLmvzT+cbv63HyL\n NKDb9WGHlsT2PKAjEwF+qX6BhZZ0igdF83juPt7ufsXFhg7/N21zB8FZl1Uq/K4XOVpR\n tpprDhztXNyqdTansdVame70PITnmJ/dbOIc3yhZUjRamkBCmKFcwJgJzAOCHuXst93w\n 2+4MLMgXMBOvuYe/IjiHmRdAPITQMc6bwAirPmF7AxKDp2233BEdE1zvE1bzviUpWtBN\n BdXm/0/daLxe1TgjwgqYbddIX4VwG9NeTo/3RwPp/wz0BlfCZSn3080uLYdtDFIKBVNX\n rM/g==", "X-Gm-Message-State": "AOJu0YxxTDuphffrGEDyDPEqWk+TTUsd9fzVCZ1f0h4afj5dyLPhf0Ac\n 22Ktr3u9L0AKHdtv1I97Vt1neWT7Lxi4uBvEYhBaqJBG8KMMs1GfHolMQgGZeLEWjvCjiXQ4DfS\n DkfYC0CE=", "X-Gm-Gg": "AZuq6aL02dw4+zwbspPj8Q+PHBI8vt1m4xiPy7+b6Tx3tA4xorhJd4A/x/gExhROXfw\n a6WrmRfHY+yy+WspeZb7NFUDp04ZTCyC1lOpSMA3S0lT9HzhAX0iqdPWEnG49/i5Icv89ybOVoD\n E4AfvFl5m3XZm2zadYcMxZfBmIvJ4ewVvl2CzS2+iMy1Mr2r/C0F0Oa+vm3YC03J4ypcilfrdsP\n aebNydsiT9JTVGQ1TC7dP+5Ge7CSJjQ6M48vncnnwk2md/93zFDx5w0pIlZQAP+mUNlSdoaPeCy\n ++ZvlngfzEK8TVCKVB2sNSHW1+slYL27/KnVB7xF1O6xkQOsdEumqu222Thmn+nWKUnZev8Aqgi\n tu+gXdQ1VUIrt4rbQeu6tCxGRuEUrScjuzD2/XLOZE+UwIsydd8Vi0ZDgVzx+LINFMhaCVB0kC2\n 6e/Rbz7n+EYRT2R+A3LUbShYwsBRoVXCpFTefrxs2c", "X-Received": "by 2002:a05:600c:444f:b0:475:de14:db1e with SMTP id\n 5b1f17b1804b1-48379bd742amr243534805e9.24.1771432329907;\n Wed, 18 Feb 2026 08:32:09 -0800 (PST)", "To": "buildroot@buildroot.org", "Cc": "Fabien Lehoussel <fabien.lehoussel@smile.fr>", "Date": "Wed, 18 Feb 2026 17:32:04 +0100", "Message-ID": "<20260218163205.1639035-3-fabien.lehoussel@smile.fr>", "X-Mailer": "git-send-email 2.43.0", "In-Reply-To": "<20260218163205.1639035-1-fabien.lehoussel@smile.fr>", "References": "<20260218163205.1639035-1-fabien.lehoussel@smile.fr>", "MIME-Version": "1.0", "X-Mailman-Original-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=smile.fr; s=google; t=1771432330; x=1772037130; darn=buildroot.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=ZoVLyL39SKoXOPXTChwP5wuAP2uDliUAjyLSoOUBq/w=;\n b=CbrZDAPUmeZlUoSTF+TTCfmqdUStXb57XcKTktuvuKuu61WgX57yM//n8rywtW4Ktr\n pSmuLKJzsHvqaKixMHtoLw9LOHuEREdB9SpuXqn6FmcQ+oqD+sK+NMgKoFqmQnZ4HMAl\n JdNy/4u0+z4Ju+bEq45FVFRdBbSefpLhrLTKw=", "X-Mailman-Original-Authentication-Results": [ "smtp3.osuosl.org;\n dmarc=pass (p=reject dis=none)\n header.from=smile.fr", "smtp3.osuosl.org;\n dkim=pass (1024-bit key) header.d=smile.fr header.i=@smile.fr\n header.a=rsa-sha256 header.s=google header.b=CbrZDAPU" ], "Subject": "[Buildroot] [PATCH 2/2] support/scripts: add filter-kernel-cve\n script for Linux kernel CVE analysis", "X-BeenThere": "buildroot@buildroot.org", "X-Mailman-Version": "2.1.30", "Precedence": "list", "List-Id": "Discussion and development of buildroot <buildroot.buildroot.org>", "List-Unsubscribe": "<https://lists.buildroot.org/mailman/options/buildroot>,\n <mailto:buildroot-request@buildroot.org?subject=unsubscribe>", "List-Archive": "<http://lists.buildroot.org/pipermail/buildroot/>", "List-Post": "<mailto:buildroot@buildroot.org>", "List-Help": "<mailto:buildroot-request@buildroot.org?subject=help>", "List-Subscribe": "<https://lists.buildroot.org/mailman/listinfo/buildroot>,\n <mailto:buildroot-request@buildroot.org?subject=subscribe>", "From": "Fabien Lehoussel via buildroot <buildroot@buildroot.org>", "Reply-To": "Fabien Lehoussel <fabien.lehoussel@smile.fr>", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "base64", "Errors-To": "buildroot-bounces@buildroot.org", "Sender": "\"buildroot\" <buildroot-bounces@buildroot.org>" }, "content": "Add a new script to filter and analyze Linux kernel CVEs from Buildroot's\nCycloneDX SBOM report. The script removes CVEs that are definitively NOT\napplicable to the build by cross-referencing affected file information\nfrom the official CVE database (CNA - CVE Numbering Authority) against\nthe kernel's compiled files (from compile_commands.json).\n\nThis enables removing false positives while keeping potentially applicable\nCVEs that require further investigation.\n\nFeatures:\n- Loads CycloneDX SBOM reports from file or stdin\n- Extracts CVEs affecting the \"linux\" component\n- Queries the official CVE Numbering Authority (CNA) database (cvelistV5)\n for affected file information (programFiles)\n- Matches CVE program files against actually compiled kernel files\n- Categorizes CVEs into three groups:\n * Confirmed applicable: CVEs where affected files are compiled (kept)\n * Not applicable: CVEs where NO affected files are compiled (removed)\n * Insufficient data: CVEs not found in CNA database (kept for review)\n- Outputs filtered SBOM report removing only definitively non-applicable CVEs\n- Generates detailed analysis report with file-to-CVE mapping\n- Optionally auto-clones or updates CNA database from GitHub\n- Supports stdin/stdout for pipeline integration with cve-check\n\nUsage examples:\n # Using in a pipeline\n make show-info | utils/generate-cyclonedx | support/scripts/cve-check | \\\n support/scripts/filter-kernel-cve \\\n --cc-path output/build/linux-X.Y.Z/compile_commands.json \\\n --cna-path cvelistV5 > sbom_filtered.json\n\n # Using with files\n support/scripts/filter-kernel-cve \\\n --buildroot-report sbom.json \\\n --cc-path compile_commands.json \\\n --cna-path cvelistV5 \\\n --output-report sbom_filtered.json \\\n --output-details cve_details.json \\\n --log-file filter.log --log-level DEBUG \\\n --auto-update\n\nSigned-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>\n---\n support/scripts/filter-kernel-cve | 592 ++++++++++++++++++++++++++++++\n 1 file changed, 592 insertions(+)\n create mode 100755 support/scripts/filter-kernel-cve", "diff": "diff --git a/support/scripts/filter-kernel-cve b/support/scripts/filter-kernel-cve\nnew file mode 100755\nindex 0000000000..a3181aae0f\n--- /dev/null\n+++ b/support/scripts/filter-kernel-cve\n@@ -0,0 +1,592 @@\n+#!/usr/bin/env python3\n+\"\"\"\n+Analyze Linux kernel CVEs from Buildroot CycloneDX SBOM report.\n+\n+This script takes as input:\n+ 1. The CycloneDX SBOM report generated by Buildroot (containing CVEs)\n+ 2. The compile_commands.json file from the kernel build\n+ 3. The directory containing CVEs in CNA 5.x format (https://github.com/CVEProject/cvelistV5)\n+\n+For each CVE affecting the \"linux\" component:\n+ - Load the CVE from the CNA database\n+ - Extract affected files (programFiles)\n+ - Check if these files are in compile_commands.json\n+ - If YES → CVE is applicable (keep in report)\n+ - If NO → CVE is not applicable (remove from report)\n+\n+Outputs:\n+ 1. Updated Buildroot SBOM report (only non-applicable CVEs removed)\n+ 2. Detailed CVE analysis report with breakdown by category (optional)\n+\n+Categories in the analysis:\n+ - Applicable CVEs: CVEs affecting compiled files\n+ - Removed CVEs: CVEs not affecting any compiled files\n+ - Insufficient data CVEs: CVEs kept due to lack of information\n+\n+Example usage:\n+make show-info | \\\n+ utils/generate-cyclonedx | \\\n+ support/script/cve-check --nvd-path dl/buildroot-nvd/ | \\\n+ filter-kernel-cve \\\n+ --cc-path /path/to/output/build/linux-X.Y.Z/compile_commands.json \\\n+ --cna-path /path/to/cvelistV5 \\\n+ --no-cna-update \\\n+ --output-details /path/to/cve_details.json \\\n+ --log-file /path/to/filter.log \\\n+ --log-level=DEBUG\n+or \n+ filter-kernel-cve \\\n+ --in-file /path/to/sbom.json \\\n+ --cc-path /path/to/compile_commands.json \\\n+ --cna-path /path/to/cvelistV5 \\\n+ --no-cna-update \\\n+ --output-details /path/to/cve_details.json \\\n+ --log-file /path/to/filter.log \\\n+ --log-level=DEBUG\n+\"\"\"\n+\n+import argparse\n+import json\n+import logging\n+import sys\n+import subprocess\n+import os\n+from pathlib import Path\n+from typing import Optional\n+\n+# CVE repository URL\n+CVE_REPO_URL = \"https://github.com/CVEProject/cvelistV5.git\"\n+\n+# Configure logging\n+logger = logging.getLogger(__name__)\n+\n+def parse_args():\n+ parser = argparse.ArgumentParser(\n+ description=\"Filter Linux kernel CVEs from Buildroot CycloneDX SBOM report\"\n+ )\n+\n+ parser.add_argument(\n+ \"-i\", \"--in-file\",\n+ type=str,\n+ default=None,\n+ help=\"CycloneDX SBOM JSON report file (default: read from stdin)\",\n+ )\n+ parser.add_argument(\n+ \"-o\",\"--out-file\",\n+ type=str,\n+ default=None,\n+ help=\"Updated Buildroot SBOM report (default: write to stdout)\",\n+ )\n+ parser.add_argument(\n+ \"--cc-path\",\n+ required=True,\n+ help=\"Kernel compile_commands.json file\",\n+ )\n+ parser.add_argument(\n+ \"--cna-path\",\n+ required=True,\n+ help=\"Directory containing CVEs in CNA 5.x format (JSON files)\",\n+ )\n+ parser.add_argument(\n+ \"--no-cna-update\",\n+ action=\"store_true\",\n+ help=\"Doesn't clone or update CVE database\",\n+ )\n+ parser.add_argument(\n+ \"--output-details\",\n+ default=\"kernel_cve_details.json\",\n+ help=\"JSON file with detailed CVE analysis\",\n+ )\n+ parser.add_argument(\n+ \"--log-file\",\n+ default=None,\n+ help=\"Log file path (if not set, no logs are displayed)\",\n+ )\n+ parser.add_argument(\n+ \"--log-level\",\n+ choices=[\"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\"],\n+ default=\"INFO\",\n+ help=\"Set logging level (default: INFO)\",\n+ )\n+ return parser.parse_args()\n+\n+\n+def setup_logging(log_level: str, log_file: Optional[str]):\n+ \"\"\"Configure logging only if log_file is specified.\"\"\"\n+ if not log_file:\n+ # Disable logging if no log file specified\n+ logging.disable(logging.CRITICAL)\n+ return\n+\n+ # Determine log level\n+ level = getattr(logging, log_level)\n+\n+ # Create formatter\n+ formatter = logging.Formatter('%(message)s')\n+\n+ # Configure root logger\n+ root_logger = logging.getLogger()\n+ root_logger.setLevel(level)\n+\n+ # Remove existing handlers\n+ for handler in root_logger.handlers[:]:\n+ root_logger.removeHandler(handler)\n+\n+ # Add file handler\n+ file_handler = logging.FileHandler(log_file, mode='w', encoding='utf-8')\n+ file_handler.setLevel(level)\n+ file_handler.setFormatter(formatter)\n+ root_logger.addHandler(file_handler)\n+\n+\n+# ---------------------------------------------------------------------------\n+# CVE Database management\n+# ---------------------------------------------------------------------------\n+def sync_cve_database(cna_dir: str) -> None:\n+ \"\"\"\n+ Clone or update the CVE database from GitHub.\n+ \n+ If the directory doesn't exist, clone the repository.\n+ If it exists and is a git repository, pull the latest changes.\n+ \"\"\"\n+ cna_path = Path(cna_dir)\n+ git_dir = cna_path / \".git\"\n+ \n+ try:\n+ if git_dir.exists():\n+ # Directory is a git repo, pull latest changes\n+ logger.info(f\"Updating CVE database from {CVE_REPO_URL}...\")\n+ subprocess.run(\n+ [\"git\", \"pull\"],\n+ cwd=cna_dir,\n+ stdout=subprocess.DEVNULL,\n+ stderr=subprocess.DEVNULL,\n+ check=True,\n+ )\n+ logger.info(\"CVE database updated successfully\")\n+ else:\n+ # Clone the repository\n+ logger.info(f\"Cloning CVE database from {CVE_REPO_URL}...\")\n+ cna_path.parent.mkdir(parents=True, exist_ok=True)\n+ subprocess.run(\n+ [\"git\", \"clone\", CVE_REPO_URL, cna_dir],\n+ stdout=subprocess.DEVNULL,\n+ stderr=subprocess.DEVNULL,\n+ check=True,\n+ )\n+ logger.info(\"CVE database cloned successfully\")\n+ except subprocess.CalledProcessError as e:\n+ logger.error(f\"Failed to sync CVE database: {e}\")\n+ sys.exit(1)\n+ except FileNotFoundError:\n+ logger.error(\"git is not installed or not in PATH\")\n+ sys.exit(1)\n+\n+\n+# ---------------------------------------------------------------------------\n+# Load Buildroot CycloneDX SBOM report\n+# ---------------------------------------------------------------------------\n+def load_cve_check_report(input_file: Optional[str]) -> dict:\n+ \"\"\"\n+ Load the CycloneDX SBOM JSON report from file or stdin.\n+ \n+ Expected format:\n+ {\n+ \"bomFormat\": \"CycloneDX\",\n+ \"components\": [...],\n+ \"vulnerabilities\": [...],\n+ \"dependencies\": [...]\n+ }\n+ \"\"\"\n+ if input_file:\n+ p = Path(input_file)\n+ if not p.exists():\n+ logger.error(f\"Input report not found: {input_file}\")\n+ sys.exit(1)\n+ with open(p, encoding=\"utf-8\") as f:\n+ data = json.load(f)\n+ else:\n+ # Read from stdin\n+ try:\n+ data = json.load(sys.stdin)\n+ except json.JSONDecodeError as e:\n+ logger.error(f\"Failed to parse JSON from stdin: {e}\")\n+ sys.exit(1)\n+\n+ if data.get(\"bomFormat\") != \"CycloneDX\":\n+ logger.error(f\"Not a CycloneDX SBOM format. Found: {data.get('bomFormat')}\")\n+ sys.exit(1)\n+\n+ logger.info(\"CycloneDX SBOM report loaded\")\n+ return data\n+\n+\n+def extract_linux_cves(cve_check_report: dict) -> list[dict]:\n+ \"\"\"\n+ Extract CVEs affecting the \"linux\" component from Buildroot CycloneDX SBOM.\n+\n+ Returns a list of dicts with CVE info.\n+ \"\"\"\n+ linux_cves = []\n+\n+ # Extract Linux version from components[]\n+ linux_version = None\n+ for component in cve_check_report.get(\"components\", []):\n+ if component.get(\"name\") == \"linux\":\n+ linux_version = component.get(\"version\", \"unknown\")\n+ logger.debug(f\"Found Linux version in SBOM: {linux_version}\")\n+ break\n+\n+ # Extract CVEs from vulnerabilities[]\n+ total_vulns = len(cve_check_report.get(\"vulnerabilities\", []))\n+ logger.debug(f\"Total vulnerabilities in SBOM: {total_vulns}\")\n+\n+ for vuln in cve_check_report.get(\"vulnerabilities\", []):\n+ cve_id = vuln.get(\"id\", \"\")\n+ if not cve_id.startswith(\"CVE-\"):\n+ continue\n+\n+ # Check if this CVE affects \"linux\"\n+ affects = vuln.get(\"affects\", [])\n+ if not any(a.get(\"ref\") == \"linux\" for a in affects):\n+ continue\n+\n+ logger.debug(f\"Found Linux CVE: {cve_id}\")\n+\n+ linux_cves.append({\n+ \"cve_id\": cve_id,\n+ \"original_entry\": vuln,\n+ })\n+\n+ logger.info(f\"{len(linux_cves)} CVE(s) affecting 'linux' component found\")\n+ return linux_cves\n+\n+\n+# ---------------------------------------------------------------------------\n+# Load compile_commands.json\n+# ---------------------------------------------------------------------------\n+def load_compiled_files(compile_commands_path: str) -> set[str]:\n+ \"\"\"\n+ Load compile_commands.json and extract the list of compiled files.\n+ Returns a set of relative paths (e.g., 'drivers/net/foo.c').\n+ \"\"\"\n+ p = Path(compile_commands_path)\n+ if not p.exists():\n+ logger.error(f\"compile_commands.json not found: {compile_commands_path}\")\n+ sys.exit(1)\n+ \n+ with open(p, encoding=\"utf-8\") as f:\n+ commands = json.load(f)\n+\n+ logger.debug(f\"Loaded compile_commands.json with {len(commands)} entries\")\n+\n+ if not isinstance(commands, list):\n+ logger.error(\"Unexpected format in compile_commands.json\")\n+ sys.exit(1)\n+\n+ # Infer kernel_dir from compile_commands\n+ kernel_dir = None\n+ if commands:\n+ first_dir = Path(commands[0].get(\"directory\", \"\"))\n+ kernel_dir = str(first_dir.resolve())\n+ logger.debug(f\"Inferred kernel_dir from compile_commands: {kernel_dir}\")\n+\n+ kernel_root = Path(kernel_dir).resolve() if kernel_dir else None\n+ logger.debug(f\"Using kernel root: {kernel_root}\")\n+ compiled_files = set()\n+ \n+ for entry in commands:\n+ file_path = Path(entry.get(\"file\", \"\"))\n+ \n+ # Make relative to kernel_root if possible\n+ if kernel_root:\n+ try:\n+ rel_path = file_path.resolve().relative_to(kernel_root)\n+ compiled_files.add(str(rel_path))\n+ except ValueError:\n+ # File outside kernel tree → ignore\n+ continue\n+ else:\n+ # Fallback: use path as-is\n+ compiled_files.add(str(file_path))\n+\n+ logger.info(f\"{len(compiled_files)} compiled file(s) extracted from compile_commands.json\")\n+ return compiled_files\n+\n+# ---------------------------------------------------------------------------\n+# Load CVE from CNA database\n+# ---------------------------------------------------------------------------\n+def load_cve_from_cna(cna_dir: str, cve_id: str) -> Optional[dict]:\n+ \"\"\"\n+ Load a specific CVE from the CNA directory.\n+\n+ Searches for a file named CVE-YYYY-NNNNN.json or similar.\n+ Returns a dict with extracted info, or None if not found/invalid.\n+ \"\"\"\n+ cna_path = Path(cna_dir)\n+\n+ # Construct possible file names\n+ possible_names = [\n+ f\"{cve_id}.json\",\n+ f\"{cve_id.replace('CVE-', '')}.json\",\n+ ]\n+\n+ cve_file = None\n+ for name in possible_names:\n+ candidate = cna_path / name\n+ if candidate.exists():\n+ cve_file = candidate\n+ logger.debug(f\"Found CVE file: {candidate}\")\n+ break\n+\n+ # Recursive search as last resort\n+ if not cve_file:\n+ logger.debug(f\"Searching recursively for {cve_id}.json in {cna_dir}\")\n+ matches = list(cna_path.rglob(f\"{cve_id}.json\"))\n+ if matches:\n+ cve_file = matches[0]\n+ logger.debug(f\"Found CVE file: {cve_file}\")\n+\n+ if not cve_file:\n+ logger.debug(f\"CVE file not found for {cve_id}\")\n+ return None\n+\n+ try:\n+ with open(cve_file, encoding=\"utf-8\") as f:\n+ data = json.load(f)\n+ except (json.JSONDecodeError, OSError):\n+ return None\n+\n+ # Verify CNA 5.x format\n+ if data.get(\"dataType\") != \"CVE_RECORD\":\n+ return None\n+\n+ cna = data.get(\"containers\", {}).get(\"cna\", {})\n+ if not cna:\n+ return None\n+\n+ # Extract programFiles\n+ program_files = []\n+ for affected in cna.get(\"affected\", []):\n+ if affected.get(\"vendor\") == \"Linux\" and affected.get(\"product\") == \"Linux\":\n+ program_files.extend(affected.get(\"programFiles\", []))\n+\n+ # Deduplicate\n+ program_files = sorted(set(program_files))\n+\n+ logger.debug(f\"Extracted {len(program_files)} program file(s) from CVE\")\n+ if program_files and logger.isEnabledFor(logging.DEBUG):\n+ for pf in program_files:\n+ logger.debug(f\" - {pf}\")\n+\n+ return {\n+ \"cve_id\": cve_id,\n+ \"program_files\": program_files,\n+ }\n+\n+\n+# ---------------------------------------------------------------------------\n+# File matching\n+# ---------------------------------------------------------------------------\n+def match_files(program_files: list[str], compiled_files: set[str]) -> list[str]:\n+ \"\"\"\n+ Return compiled files that match the CVE's programFiles.\n+ Uses suffix matching to handle path differences.\n+ \"\"\"\n+ matched = []\n+\n+ logger.debug(f\"Checking {len(program_files)} program file(s) with compiled files\")\n+\n+ for prog_file in program_files:\n+ # Normalize: remove leading slash\n+ prog_norm = prog_file.lstrip(\"/\")\n+ logger.debug(f\"Checking program file: {prog_norm}\")\n+\n+ found = False\n+ for compiled in compiled_files:\n+ # Exact match\n+ if compiled == prog_norm:\n+ if compiled not in matched:\n+ matched.append(compiled)\n+ logger.debug(f\" ✓ Exact match with: {compiled}\")\n+ found = True\n+ continue\n+\n+ # Suffix match (CVE may have different path)\n+ if compiled.endswith(\"/\" + prog_norm) or prog_norm.endswith(\"/\" + compiled):\n+ if compiled not in matched:\n+ matched.append(compiled)\n+ logger.debug(f\" ✓ Suffix match with: {compiled}\")\n+ found = True\n+\n+ if not found:\n+ logger.debug(f\" ✗ No match found\")\n+\n+ return sorted(matched)\n+\n+\n+# ---------------------------------------------------------------------------\n+# Helper: Save JSON file with directory creation\n+# ---------------------------------------------------------------------------\n+def save_json_report(filepath: str, data: dict, description: str) -> None:\n+ \"\"\"Saves a JSON report, creating directories if needed.\"\"\"\n+ output_path = Path(filepath)\n+ output_path.parent.mkdir(parents=True, exist_ok=True)\n+ \n+ with open(output_path, \"w\", encoding=\"utf-8\") as f:\n+ json.dump(data, f, indent=2, ensure_ascii=False)\n+ logger.info(f\"[OK] {description} → {filepath}\")\n+\n+\n+def write_output_file(output_file: Optional[str], data: dict) -> None:\n+ \"\"\"Write JSON output to file or stdout.\"\"\"\n+ if output_file:\n+ output_path = Path(output_file)\n+ output_path.parent.mkdir(parents=True, exist_ok=True)\n+ with open(output_path, \"w\", encoding=\"utf-8\") as f:\n+ json.dump(data, f, indent=2, ensure_ascii=False)\n+ logger.info(f\"[OK] Filtered SBOM report → {output_file}\")\n+ else:\n+ # Write to stdout\n+ json.dump(data, sys.stdout, indent=2, ensure_ascii=False)\n+ sys.stdout.write('\\n')\n+\n+\n+# ---------------------------------------------------------------------------\n+# Main\n+# ---------------------------------------------------------------------------\n+def main():\n+ args = parse_args()\n+\n+ # Setup logging BEFORE any logging calls\n+ setup_logging(args.log_level, args.log_file)\n+\n+ # Sync CVE database if requested\n+ if not args.no_cna_update:\n+ logger.info(\"[STEP 0] Syncing CVE database...\")\n+ sync_cve_database(args.cna_path)\n+\n+ # Load data\n+ logger.info(\"[STEP 1] Loading Buildroot SBOM report...\")\n+ cve_check_report = load_cve_check_report(args.in_file)\n+ linux_cves = extract_linux_cves(cve_check_report)\n+\n+ if not linux_cves:\n+ logger.info(\"No CVEs affecting 'linux' component in the report.\")\n+ logger.info(\"Nothing to filter, report unchanged.\")\n+ write_output_file(args.out_file, cve_check_report)\n+ return\n+\n+ logger.info(\"[STEP 2] Loading compile_commands.json...\")\n+ compiled_files = load_compiled_files(args.cc_path)\n+ \n+ logger.info(f\"[STEP 3] Analyzing {len(linux_cves)} CVE(s)...\")\n+\n+ cves_kept = [] # Confirmed applicable CVEs\n+ cves_removed = [] # Non-applicable CVEs (files not compiled)\n+ cves_insufficient_data = [] # CVEs not found in CNA database or do not contain sufficient information\n+\n+ cve_details = {} # CVE_ID → affected compiled files\n+\n+ for i, linux_cve in enumerate(linux_cves, 1):\n+ cve_id = linux_cve[\"cve_id\"]\n+ logger.info(f\"[{i}/{len(linux_cves)}] {cve_id}...\")\n+ \n+ # Load CVE from CNA database\n+ cve_cna = load_cve_from_cna(args.cna_path, cve_id)\n+ \n+ if not cve_cna:\n+ logger.info(f\" ✗ Not found in CNA database\")\n+ cves_insufficient_data.append(cve_id)\n+ continue\n+\n+ # Check if any compiled files are affected\n+ if cve_cna[\"program_files\"]:\n+ matched = match_files(cve_cna[\"program_files\"], compiled_files)\n+\n+ # Always keep program_files details\n+ cve_details[cve_id] = {\n+ \"program_files\": cve_cna[\"program_files\"],\n+ \"matched_compiled\": matched,\n+ }\n+\n+ if matched:\n+ logger.info(f\" ✓ Applicable ({len(matched)} compiled file(s))\")\n+ cves_kept.append(cve_id)\n+ else:\n+ logger.info(f\" ✗ Not applicable (files not compiled)\")\n+ cves_removed.append(cve_id)\n+ else:\n+ logger.info(f\" ✗ No program files info, keeping CVE\")\n+ cves_insufficient_data.append(cve_id)\n+\n+ # ---------------------------------------------------------------------------\n+ # Reconstruct updated Buildroot SBOM report (preserve all fields)\n+ # ---------------------------------------------------------------------------\n+ logger.info(\"[STEP 4] Generating filtered report...\")\n+ \n+ # Filter out only non-applicable CVEs (keep applicable + insufficient_data)\n+ if \"vulnerabilities\" in cve_check_report:\n+ cve_check_report[\"vulnerabilities\"] = [\n+ vuln for vuln in cve_check_report[\"vulnerabilities\"]\n+ if vuln.get(\"id\") not in cves_removed\n+ ]\n+\n+ write_output_file(args.out_file, cve_check_report)\n+\n+ # ---------------------------------------------------------------------------\n+ # Generate CVE details file\n+ # ---------------------------------------------------------------------------\n+ if args.output_details:\n+ details_output = {\n+ \"summary\": {\n+ \"total_cves_analyzed\": len(linux_cves),\n+ \"cves_applicable\": len(cves_kept),\n+ \"cves_insufficient_data\": len(cves_insufficient_data),\n+ \"cves_removed\": len(cves_removed)\n+ },\n+ \"applicable_cves\": {cve_id: cve_details[cve_id] for cve_id in cves_kept},\n+ \"removed_cves\": {cve_id: cve_details[cve_id] for cve_id in cves_removed},\n+ \"cves_insufficient_data\": cves_insufficient_data,\n+ }\n+ save_json_report(args.output_details, details_output, \"CVE details\")\n+\n+ # ---------------------------------------------------------------------------\n+ # Display summary\n+ # ---------------------------------------------------------------------------\n+ logger.info(f\"{'='*60}\")\n+ logger.info(f\" Confirmed applicable CVEs : {len(cves_kept)}\")\n+ logger.info(f\" Insufficient data (kept) : {len(cves_insufficient_data)}\")\n+ logger.info(f\" Non-applicable CVEs (removed) : {len(cves_removed)}\")\n+\n+ logger.info(f\"{'='*60}\")\n+\n+ # Show details in DEBUG mode\n+ if logger.isEnabledFor(logging.DEBUG):\n+ if cves_kept:\n+ logger.debug(f\"\\n{'─'*60}\")\n+ logger.debug(f\"CONFIRMED APPLICABLE CVEs\")\n+ logger.debug(f\"{'─'*60}\")\n+ for cve_id in sorted(cves_kept):\n+ details = cve_details[cve_id]\n+ logger.debug(f\"{cve_id}\")\n+ logger.debug(f\" Affected files: {len(details['matched_compiled'])}\")\n+ for f in details[\"matched_compiled\"]:\n+ logger.debug(f\" - {f}\")\n+\n+ if cves_insufficient_data:\n+ logger.debug(f\"\\n{'─'*60}\")\n+ logger.debug(f\"UNKNOWN STATE CVEs\")\n+ logger.debug(f\"{'─'*60}\")\n+ for cve_id in sorted(cves_insufficient_data):\n+ logger.debug(f\"{cve_id}\")\n+ if cves_removed:\n+ logger.debug(f\"\\n{'─'*60}\")\n+ logger.debug(f\"REMOVED CVEs\")\n+ logger.debug(f\"{'─'*60}\")\n+ for cve_id in sorted(cves_removed):\n+ logger.debug(f\"{cve_id}\")\n+\n+\n+if __name__ == \"__main__\":\n+ main()\n\\ No newline at end of file\n", "prefixes": [ "2/2" ] }