Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.0/patches/2198164/?format=api
{ "id": 2198164, "url": "http://patchwork.ozlabs.org/api/1.0/patches/2198164/?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": "<20260219134834.151782-3-fabien.lehoussel@smile.fr>", "date": "2026-02-19T13:48:30", "name": "[v2,2/2] support/scripts/cve-check: add kernel CVE filtering based on compiled files", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "d25f93eefbf09070a35c90f03b744ab06b6bcac3", "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/20260219134834.151782-3-fabien.lehoussel@smile.fr/mbox/", "series": [ { "id": 492679, "url": "http://patchwork.ozlabs.org/api/1.0/series/492679/?format=api", "date": "2026-02-19T13:48:28", "name": "Linux kernel CVE filtering improvements", "version": 2, "mbox": "http://patchwork.ozlabs.org/series/492679/mbox/" } ], "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2198164/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=j29e5Y1+;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=buildroot.org\n (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org;\n envelope-from=buildroot-bounces@buildroot.org; receiver=patchwork.ozlabs.org)" ], "Received": [ "from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138])\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 4fGvqQ0dTfz1xvg\n\tfor <incoming-buildroot@patchwork.ozlabs.org>;\n Fri, 20 Feb 2026 00:48:50 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp1.osuosl.org (Postfix) with ESMTP id A15BC82348;\n\tThu, 19 Feb 2026 13:48:48 +0000 (UTC)", "from smtp1.osuosl.org ([127.0.0.1])\n by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 5knkQux5mRcK; Thu, 19 Feb 2026 13:48:46 +0000 (UTC)", "from lists1.osuosl.org (lists1.osuosl.org [140.211.166.142])\n\tby smtp1.osuosl.org (Postfix) with ESMTP id 7105382351;\n\tThu, 19 Feb 2026 13:48:46 +0000 (UTC)", "from smtp1.osuosl.org (smtp1.osuosl.org [140.211.166.138])\n by lists1.osuosl.org (Postfix) with ESMTP id 8BF88206\n for <buildroot@buildroot.org>; Thu, 19 Feb 2026 13:48:43 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp1.osuosl.org (Postfix) with ESMTP id 7D29682348\n for <buildroot@buildroot.org>; Thu, 19 Feb 2026 13:48:43 +0000 (UTC)", "from smtp1.osuosl.org ([127.0.0.1])\n by localhost (smtp1.osuosl.org [127.0.0.1]) (amavis, port 10024) with ESMTP\n id 2oT7fqYxmkKJ for <buildroot@buildroot.org>;\n Thu, 19 Feb 2026 13:48:42 +0000 (UTC)", "from mail-wm1-x32a.google.com (mail-wm1-x32a.google.com\n [IPv6:2a00:1450:4864:20::32a])\n by smtp1.osuosl.org (Postfix) with ESMTPS id AA5D08233E\n for <buildroot@buildroot.org>; Thu, 19 Feb 2026 13:48:41 +0000 (UTC)", "by mail-wm1-x32a.google.com with SMTP id\n 5b1f17b1804b1-48371bb515eso13124575e9.1\n for <buildroot@buildroot.org>; Thu, 19 Feb 2026 05:48:41 -0800 (PST)", "from FRSMI25-GRAVITY.idf.intranet\n (static-css-ccs-204145.business.bouyguestelecom.com. [176.157.204.145])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-4839f97831bsm16604295e9.12.2026.02.19.05.48.38\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Thu, 19 Feb 2026 05:48:38 -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 smtp1.osuosl.org 7105382351", "OpenDKIM Filter v2.11.0 smtp1.osuosl.org AA5D08233E" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=buildroot.org;\n\ts=default; t=1771508926;\n\tbh=FbxEwWrUDgPl+XcdO4evQ+9w8Ua1u/qOXlVW4UddqDw=;\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=j29e5Y1+wNILaKwCtZfugjixdqFknXFjc/S7Ac26D6rpJmBnOgkL7DpAneSZwdK7r\n\t /ZH55h5hgVlAVHarXizGQokZOfjAaS9ULQiOvdWMFMVhXyMMK6TuMeEO93S4U+CXev\n\t V4oN7jbdkv2ZzPkZGcqHwwftv2W3GVFZ7sHjTK/YpH/5is+tZynXFKlQtr0pjCMycB\n\t uB83RHmbiRg0qG46p3ktyCAObIPkYzsKO/amOP3dn2vDv2y2l2WdgUEaivZb3YbBft\n\t g7MaGG0HaF+yOs2UMFyO3Rm6LuMP5Soc7zSuiyZCxllDE1Pk+Lo1ywRrPtn/YqqBbS\n\t ZnUbYS6V2v5jg==", "Received-SPF": "Pass (mailfrom) identity=mailfrom;\n client-ip=2a00:1450:4864:20::32a; helo=mail-wm1-x32a.google.com;\n envelope-from=fabien.lehoussel@smile.fr; receiver=<UNKNOWN>", "DMARC-Filter": "OpenDMARC Filter v1.4.2 smtp1.osuosl.org AA5D08233E", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20230601; t=1771508919; x=1772113719;\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=5VH4yIgaxRyybX7Ho2Dr73M+t7UMEL2H5zub5PTxhgw=;\n b=rMxLxrUmp/sDsdGOZ7Slos2ocUoVni/VLIlgjwupsrZ9pXiMcJ2mqcYrLmjU6aZ1sl\n FyKWqPXc+ZlvWiKFrIQXyDN3aJGz7CHw3/eACDz8P4QhPysfTATcsWO/Mqk9hEAiJkJe\n oQaA0V6EYfb6u5Rlg6aL7vy9/A3KfWJkDM11DO55W6LISPTxyjH++QSyU0IZHd1LPumF\n P/03V8JPv9r1hKiq7P6ZqAOs3UV24BE4fyA7w1C4B4/B12Y9Dy/LpFMiZ+0CdCW1tRhQ\n 0aso+kgOM3daRHyzuA3cjIg1iX61MLOWoYRL9vwsio7wPvR3Gh+rnz79T0+GGbCdSJwe\n iMjQ==", "X-Gm-Message-State": "AOJu0YzAE68fMANcmeRy1FTj7Z5NBJGjz4k2OMK/4qaonqOSbbX9mmNk\n /ZqZ5pFRRjc1lEd5WE/9/8G9etpAYYBqvwFwrnSqrIlgqU2LhF1ikguIUvM0MFsSAnIc4JJWxZC\n AjCag", "X-Gm-Gg": "AZuq6aLVi5ePNWEJTfnA0/u0RoclcHRZRF0kJpi+1zmcyiIQG3sdA9OloMODcOpTsZP\n ftW25OtVgT5Dfo0YtwABP7QU0m2ms34Sq5W3QR9BqxYWOksA/agTNA+QmzSB3k7wpbApbz9wslL\n geXKemAEfL0jCyIhblMEkzf8IqcoHqojZhZjSyJ1B2RaqpXVv2W2YzN5xmgCkY36Vaa14CHUt9j\n LcGjExARN8VRGms3nQotBOOv8Yuu0s5VbdOvRRfO3LnJhYmiQ6yJ8X2TJFj0vslad5aU0i/16Sp\n /tIvxet6Bm4SiK57qqsj48lWtuJZQIdwThaXW2osMYB4+FhvxSfwTBci5Le8kCuoXhhxfTY6c9I\n oE/wvBH1cTJ1djt1/8dTDIhpRFAWf1n/bkCfVVwVYUsEO3AzFDg7Gk83Ax0la9hzUvHXohhfHcp\n lDLkCGVEguKI9iKTBgljcVZSw6HVeb2jkY1pZ6olMqLpxOJy7H0LETYKK3aLmSFNyZrLomiMjS0\n eozQoLBNQolSVajd8urcPx9XeJsyIEWrL0xUsTTMw==", "X-Received": "by 2002:a05:600c:3542:b0:477:54cd:200e with SMTP id\n 5b1f17b1804b1-48398a678cfmr96602115e9.1.1771508918776;\n Thu, 19 Feb 2026 05:48:38 -0800 (PST)", "To": "buildroot@buildroot.org", "Cc": "Thomas Perale <thomas.perale@mind.be>,\n Fabien Lehoussel <fabien.lehoussel@smile.fr>", "Date": "Thu, 19 Feb 2026 14:48:30 +0100", "Message-ID": "<20260219134834.151782-3-fabien.lehoussel@smile.fr>", "X-Mailer": "git-send-email 2.43.0", "In-Reply-To": "<20260219134834.151782-1-fabien.lehoussel@smile.fr>", "References": "<20260219134834.151782-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=1771508919; x=1772113719; 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=5VH4yIgaxRyybX7Ho2Dr73M+t7UMEL2H5zub5PTxhgw=;\n b=CFNJEmIsyzFgtyYizMmW4jBcMrEODkiAwPIipdGM91k3NE1OOgnSqUpKvYhzLrvrxt\n RW6r4RelcQ00wdIVA0zhf1EjPk9WQnr4GXz3d3QRPf/cZZTkDJj9n01I/B3zJYE2Lf8q\n 9wzwUGFcr8sD8qt7rae41J1bwv66ZnrhP5E+o=", "X-Mailman-Original-Authentication-Results": [ "smtp1.osuosl.org;\n dmarc=pass (p=reject dis=none)\n header.from=smile.fr", "smtp1.osuosl.org;\n dkim=pass (1024-bit key,\n unprotected) header.d=smile.fr header.i=@smile.fr header.a=rsa-sha256\n header.s=google header.b=CFNJEmIs" ], "Subject": "[Buildroot] [PATCH v2 2/2] support/scripts/cve-check: add kernel\n CVE filtering based on compiled files", "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": "Introduce optional filtering for Linux kernel CVEs using compile_commands.json\nand the CNA database. This reduces false positives by reporting only CVEs that\naffect compiled files, enhancing the relevance of CVE reports for specific builds.\n\nChanges:\n- Add CVEDatabase class for generic CVE database management (clone/pull)\n supporting both NVD and CNA databases with automatic sync capability\n Database path argument is the repository root directly (no automatic 'git' subfolder)\n enabling seamless CI/CD integration and clearer path semantics\n- Add CVE_LINUX class for specialized Linux kernel CVE analysis\n - Load compiled files from compile_commands.json\n - Match CVE affected files from CNA database against compiled files\n - Categorize CVEs as applicable, not applicable, or insufficient data\n- Enhance cve-check script with optional kernel CVE filtering\n - Activate filtering automatically when --cc-path and --cna-path are provided\n - Replace --no-nvd-update with generic --no-db-update flag for both databases\n - Filters out false positives Linux CVE: CVEs not affecting any compiled files\n - Keeps potentially applicable and uncertain Linux CVEs for manual review\n\nUsage:\n make show-info | utils/generate-cyclonedx | support/script/cve-check \\\n --nvd-path dl/buildroot-nvd/ \\\n --cc-path output/images/linux_compile_commands.json \\\n --cna-path dl/buildroot-cvelistV5 \\\n --no-db-update\n\nThis significantly reduces noise in CVE reports by correlating affected files\nwith the actual files compiled in the current build, making vulnerability\nreports more relevant to the specific build configuration.\n\nRequires:\n - linux_compile_commands.json from kernel build (BR2_LINUX_KERNEL_COMPILE_COMMANDS)\n - CNA database (https://github.com/CVEProject/cvelistV5.git) for Linux CVE analysis\nSigned-off-by: Fabien Lehoussel <fabien.lehoussel@smile.fr>\n---\n support/scripts/cve-check | 118 ++++++++++++++-\n support/scripts/cve.py | 292 +++++++++++++++++++++++++++++++++++---\n 2 files changed, 383 insertions(+), 27 deletions(-)", "diff": "diff --git a/support/scripts/cve-check b/support/scripts/cve-check\nindex ff14e4b238..d9e1bc940d 100755\n--- a/support/scripts/cve-check\n+++ b/support/scripts/cve-check\n@@ -7,11 +7,20 @@\n # The NVD database is cloned using a mirror of it and the content is compared\n # locally.\n #\n+# For Linux kernel CVEs, optionally filters them based on compiled files using\n+# the CNA database (cvelistV5) and compile_commands.json matching.\n+#\n # Example usage:\n # $ make show-info | utils/generate-cyclonedx | support/script/cve-check --nvd-path dl/buildroot-nvd/\n+#\n+# With kernel CVE filtering:\n+# $ make show-info | utils/generate-cyclonedx | support/script/cve-check \\\n+# --nvd-path dl/buildroot-nvd/ \\\n+# --cc-path output/images/linux_compile_commands.json \\\n+# --cna-path dl/buildroot-cvelistV5\n from collections import defaultdict\n from pathlib import Path\n-from typing import TypedDict\n+from typing import TypedDict, Optional\n import argparse\n import sys\n import json\n@@ -29,6 +38,17 @@ database.\n \n The NVD database is cloned using a mirror of it and the content is compared\n locally.\n+\n+For Linux kernel CVEs, optionally filters them based on compiled files using\n+the CNA database (cvelistV5) and compile_commands.json matching.\n+\n+When kernel CVE filtering is enabled (via --cc-path and --cna-path):\n+- Removes false positives: CVEs where NO affected files are actually compiled\n+- Keeps potentially applicable CVEs: where affected files ARE compiled\n+- Keeps uncertain CVEs: where CNA database lacks sufficient information\n+\n+This significantly reduces noise in CVE reports by only reporting CVEs that\n+can actually impact the current kernel build based on which files were compiled.\n \"\"\"\n \n \n@@ -281,6 +301,65 @@ def enrich_vulnerabilities(nvd_path: Path, sbom):\n vuln_append_or_update_affects_if_exists(vulnerabilities, vulnerability)\n \n \n+def filter_kernel_cves(sbom, cve_linux: cvecheck.CVE_LINUX, compiled_files: set):\n+ \"\"\"\n+ Filter Linux kernel CVEs based on compiled files.\n+ \n+ Removes CVEs where NO affected files are compiled.\n+ Keeps CVEs where affected files ARE compiled or insufficient data.\n+ \n+ Args:\n+ sbom (dict): Input SBOM with vulnerabilities.\n+ cve_linux (CVE_LINUX): CVE_LINUX instance for analysis.\n+ compiled_files (set): Set of compiled kernel files.\n+ \"\"\"\n+ vulnerabilities = sbom.get(\"vulnerabilities\", [])\n+ \n+ # Filter vulnerabilities: keep only applicable ones for kernel\n+ filtered_vulns = []\n+ for vuln in vulnerabilities:\n+ cve_id = vuln.get(\"id\", \"\")\n+ \n+ # Check if this CVE affects \"linux\" component\n+ affects = vuln.get(\"affects\", [])\n+ affects_linux = any(a.get(\"ref\") == \"linux\" for a in affects)\n+\n+ if not affects_linux or not cve_id.startswith(\"CVE-\"):\n+ # Not a kernel CVE, keep it\n+ filtered_vulns.append(vuln)\n+ continue\n+ \n+ # Check if kernel CVE is applicable\n+ status = cve_linux.affects(cve_id, compiled_files)\n+ \n+ if status != cvecheck.CVE_LINUX.CVE_NOT_APPLICABLE:\n+ # Keep if applicable or insufficient data\n+ filtered_vulns.append(vuln)\n+ # else: remove the CVE (not applicable)\n+ \n+ sbom[\"vulnerabilities\"] = filtered_vulns\n+\n+\n+def apply_kernel_cve_filtering(sbom, cve_linux, cc_path):\n+ \"\"\"\n+ Apply kernel CVE filtering based on compiled files.\n+ \n+ Args:\n+ sbom (dict): Input SBOM with vulnerabilities\n+ cve_linux (CVE_LINUX): CVE_LINUX instance for analysis\n+ cc_path (str): Path to compile_commands.json\n+ \"\"\"\n+ # Load compiled files\n+ compiled_files = cvecheck.CVE_LINUX.load_compiled_files(cc_path)\n+ if not compiled_files:\n+ print(\"Error: Failed to load compiled files\", file=sys.stderr)\n+ return False\n+ \n+ # Filter kernel CVEs\n+ filter_kernel_cves(sbom, cve_linux, compiled_files)\n+ return True\n+\n+\n def main():\n parser = argparse.ArgumentParser(description=DESCRIPTION)\n parser.add_argument(\"-i\", \"--in-file\", nargs=\"?\", type=argparse.FileType(\"r\"),\n@@ -297,30 +376,57 @@ def main():\n parser.add_argument(\"--include-resolved\", default=False, action='store_true',\n help=\"Add vulnerabilities already 'resolved' that don't affect a \" +\n \"component to the output CycloneDX vulnerabilities analysis.\")\n- parser.add_argument(\"--no-nvd-update\", default=False, action='store_true',\n- help=\"Doesn't update the NVD database.\")\n-\n+ parser.add_argument('--cc-path', dest='cc_path', default=None,\n+ help=\"Path to the kernel_compile_commands.json file for CVE filtering. \" +\n+ \"Allows removing false positives by comparing affected files with actually compiled files. \" +\n+ \"Requires --cna-path. \" +\n+ \"Requires BR2_LINUX_KERNEL_COMPILE_COMMANDS to be enabled.\")\n+ parser.add_argument('--cna-path', dest='cna_path', default=None,\n+ help='Path to CNA database (cvelistV5) for kernel CVE filtering')\n+ parser.add_argument(\"--no-db-update\", default=False, action='store_true',\n+ help=\"Doesn't clone/pull the CVE databases (NVD/CNA).\")\n args = parser.parse_args()\n \n if args.in_file is None or args.nvd_path is None:\n parser.print_help()\n sys.exit(1)\n \n+ cve_linux = None\n+ if args.cc_path:\n+ if args.cna_path is None:\n+ print(\"ERROR: cna_path is required when cc_path is specified!\", file=sys.stderr)\n+ parser.print_help()\n+ sys.exit(1)\n+ else:\n+ # Initialize CVE_LINUX for kernel CVE analysis\n+ cve_linux = cvecheck.CVE_LINUX(args.cna_path)\n+\n sbom = json.load(args.in_file)\n \n opt = Options(\n- include_resolved=args.include_resolved,\n+ include_resolved=args.include_resolved\n )\n \n+ # Sync NVD database if requested\n args.nvd_path.mkdir(parents=True, exist_ok=True)\n- if not args.no_nvd_update:\n+ if not args.no_db_update:\n cvecheck.CVE.download_nvd(args.nvd_path)\n \n+ # Sync CNA database if requested\n+ if cve_linux and not args.no_db_update:\n+ cve_linux.sync_database()\n+\n+ # Process\n if args.enrich_only:\n enrich_vulnerabilities(args.nvd_path, sbom)\n else:\n check_package_cves(args.nvd_path, sbom, opt)\n \n+ # Apply kernel CVE filtering if enabled\n+ if cve_linux:\n+ apply_kernel_cve_filtering(sbom, cve_linux, args.cc_path)\n+\n+ # write results\n args.out_file.write(json.dumps(sbom, indent=2))\n args.out_file.write('\\n')\n \ndiff --git a/support/scripts/cve.py b/support/scripts/cve.py\nindex 3875c4258c..d8b8bd8b05 100755\n--- a/support/scripts/cve.py\n+++ b/support/scripts/cve.py\n@@ -24,11 +24,14 @@ import json\n import subprocess\n import sys\n import operator\n+from pathlib import Path\n+from typing import Optional, Set, List\n \n sys.path.append('utils/')\n \n NVD_START_YEAR = 1999\n NVD_BASE_URL = \"https://github.com/fkie-cad/nvd-json-data-feeds/\"\n+CNA_REPO_URL = \"https://github.com/CVEProject/cvelistV5.git\"\n \n ops = {\n '>=': operator.ge,\n@@ -39,6 +42,67 @@ ops = {\n }\n \n \n+class CVEDatabase:\n+ \"\"\"Generic class for managing CVE database operations (clone, pull, sync)\"\"\"\n+ \n+ def __init__(self, repo_url: str, db_dir: str):\n+ \"\"\"\n+ Initialize CVE database manager.\n+ \n+ Args:\n+ repo_url (str): Git repository URL for the CVE database\n+ db_dir (str): Local directory path for the database\n+ \"\"\"\n+ self.repo_url = repo_url\n+ self.db_dir = db_dir\n+ \n+ def exists(self) -> bool:\n+ \"\"\"Check if the database directory exists\"\"\"\n+ return os.path.exists(self.db_dir)\n+ \n+ def is_git_repo(self) -> bool:\n+ \"\"\"Check if the database directory is a git repository\"\"\"\n+ return os.path.exists(os.path.join(self.db_dir, \".git\"))\n+ \n+ def sync(self) -> bool:\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+ Returns:\n+ bool: True if successful, False otherwise\n+ \"\"\"\n+ try:\n+ if self.is_git_repo():\n+ # Directory is a git repo, pull latest changes\n+ subprocess.run(\n+ [\"git\", \"pull\"],\n+ cwd=self.db_dir,\n+ stdout=subprocess.DEVNULL,\n+ stderr=subprocess.DEVNULL,\n+ check=True,\n+ )\n+ return True\n+ else:\n+ # Clone the repository\n+ os.makedirs(self.db_dir, exist_ok=True)\n+ subprocess.run(\n+ [\"git\", \"clone\", self.repo_url, self.db_dir],\n+ stdout=subprocess.DEVNULL,\n+ stderr=subprocess.DEVNULL,\n+ check=True,\n+ )\n+ return True\n+ except subprocess.CalledProcessError:\n+ print(f\"Warning: Failed to sync database from {self.repo_url}\", file=sys.stderr)\n+ return False\n+ except FileNotFoundError:\n+ print(\"Warning: git is not installed or not in PATH\", file=sys.stderr)\n+ return False\n+\n+\n class CPE:\n DISJOINT = 0\n SUBSET = 1\n@@ -145,24 +209,9 @@ class CVE:\n \n @staticmethod\n def download_nvd(nvd_dir):\n- nvd_git_dir = os.path.join(nvd_dir, \"git\")\n-\n- if os.path.exists(nvd_git_dir):\n- subprocess.check_call(\n- [\"git\", \"pull\"],\n- cwd=nvd_git_dir,\n- stdout=subprocess.DEVNULL,\n- stderr=subprocess.DEVNULL,\n- )\n- else:\n- # Create the directory and its parents; git\n- # happily clones into an empty directory.\n- os.makedirs(nvd_git_dir)\n- subprocess.check_call(\n- [\"git\", \"clone\", NVD_BASE_URL, nvd_git_dir],\n- stdout=subprocess.DEVNULL,\n- stderr=subprocess.DEVNULL,\n- )\n+ \"\"\"Download or update the NVD database\"\"\"\n+ db = CVEDatabase(NVD_BASE_URL, nvd_dir)\n+ db.sync()\n \n @staticmethod\n def sort_id(cve_ids):\n@@ -178,10 +227,8 @@ class CVE:\n feeds since NVD_START_YEAR. If the files are missing or outdated in\n nvd_dir, a fresh copy will be downloaded, and kept in .json.gz\n \"\"\"\n- nvd_git_dir = os.path.join(nvd_dir, \"git\")\n-\n for year in range(NVD_START_YEAR, datetime.datetime.now().year + 1):\n- for dirpath, _, filenames in os.walk(os.path.join(nvd_git_dir, f\"CVE-{year}\")):\n+ for dirpath, _, filenames in os.walk(os.path.join(nvd_dir, f\"CVE-{year}\")):\n for filename in filenames:\n if filename[-5:] != \".json\":\n continue\n@@ -340,3 +387,206 @@ class CVE:\n return self.CVE_AFFECTS\n \n return self.CVE_DOESNT_AFFECT\n+\n+\n+class CVE_LINUX:\n+ \"\"\"Specialized class for Linux kernel CVE analysis based on compiled files.\n+ \n+ Uses the CNA (CVE Numbering Authority) database (cvelistV5) to determine\n+ if CVEs affecting the Linux kernel are applicable to the current build\n+ based on which files are actually compiled.\n+ \"\"\"\n+ \n+ CVE_APPLICABLE = 1 # CVE affects compiled files\n+ CVE_NOT_APPLICABLE = 2 # CVE doesn't affect any compiled files\n+ CVE_INSUFFICIENT_DATA = 3 # CVE not found in CNA or no program files info\n+\n+ def __init__(self, cna_dir: str):\n+ \"\"\"Initialize with path to CNA database (cvelistV5)\"\"\"\n+ self.cna_dir = cna_dir\n+ self.db = CVEDatabase(CNA_REPO_URL, cna_dir)\n+\n+ def sync_database(self) -> bool:\n+ \"\"\"Sync (clone or pull) the CNA database from GitHub\"\"\"\n+ return self.db.sync()\n+\n+ @staticmethod\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+ print(f\"Error: compile_commands.json not found: {compile_commands_path}\", file=sys.stderr)\n+ return set()\n+ \n+ try:\n+ with open(p, encoding=\"utf-8\") as f:\n+ commands = json.load(f)\n+ except (json.JSONDecodeError, OSError) as e:\n+ print(f\"Error: Failed to load compile_commands.json: {e}\", file=sys.stderr)\n+ return set()\n+\n+ if not isinstance(commands, list):\n+ print(\"Error: Unexpected format in compile_commands.json\", file=sys.stderr)\n+ return set()\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+\n+ kernel_root = Path(kernel_dir).resolve() if kernel_dir else None\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+ return compiled_files\n+\n+ def load_cve_from_cna(self, 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(self.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+ break\n+\n+ # Recursive search as last resort\n+ if not cve_file:\n+ matches = list(cna_path.rglob(f\"{cve_id}.json\"))\n+ if matches:\n+ cve_file = matches[0]\n+\n+ if not cve_file:\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+ return {\n+ \"cve_id\": cve_id,\n+ \"program_files\": program_files,\n+ }\n+\n+ @staticmethod\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+ for prog_file in program_files:\n+ # Normalize: remove leading slash\n+ prog_norm = prog_file.lstrip(\"/\")\n+\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+ break\n+\n+ return sorted(matched)\n+\n+ def affects(self, cve_id: str, compiled_files: Set[str]) -> int:\n+ \"\"\"\n+ Determine if a Linux kernel CVE affects the current build.\n+ \n+ Returns:\n+ CVE_APPLICABLE: CVE affects compiled files\n+ CVE_NOT_APPLICABLE: CVE doesn't affect any compiled files\n+ CVE_INSUFFICIENT_DATA: CVE not found in CNA or no program files info\n+ \"\"\"\n+ cve_cna = self.load_cve_from_cna(cve_id)\n+ \n+ if not cve_cna:\n+ # CVE not found in CNA database - keep for review (insufficient data)\n+ return self.CVE_INSUFFICIENT_DATA\n+ \n+ if not cve_cna[\"program_files\"]:\n+ # No program files info - keep for review\n+ return self.CVE_INSUFFICIENT_DATA\n+ \n+ # Check if any affected files are compiled\n+ matched = self.match_files(cve_cna[\"program_files\"], compiled_files)\n+ \n+ if len(matched) > 0:\n+ return self.CVE_APPLICABLE\n+ else:\n+ return self.CVE_NOT_APPLICABLE\n+\n+ def get_affected_files(self, cve_id: str, compiled_files: Set[str]) -> dict:\n+ \"\"\"\n+ Get details about which files are affected by a CVE and which are compiled.\n+ \n+ Returns a dict with:\n+ - cve_id: The CVE identifier\n+ - program_files: All affected program files from CNA\n+ - matched_compiled: Compiled files that match affected files\n+ \"\"\"\n+ cve_cna = self.load_cve_from_cna(cve_id)\n+ \n+ if not cve_cna:\n+ return {\n+ \"cve_id\": cve_id,\n+ \"program_files\": [],\n+ \"matched_compiled\": [],\n+ }\n+ \n+ matched = self.match_files(cve_cna[\"program_files\"], compiled_files)\n+ \n+ return {\n+ \"cve_id\": cve_id,\n+ \"program_files\": cve_cna[\"program_files\"],\n+ \"matched_compiled\": matched,\n+ }\n", "prefixes": [ "v2", "2/2" ] }