get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.0/patches/2198164/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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"
    ]
}