diff mbox series

[4/9] tools: add gen-cve-release-notes

Message ID 20251128-release-note-5-3-updates-v1-4-6ef679198e80@bootlin.com
State New
Headers show
Series 5.3 release updates | expand

Commit Message

Antonin Godard Nov. 28, 2025, 3:35 p.m. UTC
The gen-cve-release-notes can be used to compare two cve-check reports
and generate a list of "patched" CVEs, and output that in a rST table
format.

By "patched", it means that it meets one of the following condition:
- the CVE status changes from Unpatched to Patched in the new report.
- the Unpatched CVE is unlisted in the new report.

Signed-off-by: Antonin Godard <antonin.godard@bootlin.com>
---
 documentation/tools/gen-cve-release-notes | 67 +++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)
diff mbox series

Patch

diff --git a/documentation/tools/gen-cve-release-notes b/documentation/tools/gen-cve-release-notes
new file mode 100755
index 000000000..71d8fe502
--- /dev/null
+++ b/documentation/tools/gen-cve-release-notes
@@ -0,0 +1,67 @@ 
+#!/usr/bin/env python3
+#
+# Compare two CVE reports and output a formatted rST CVE array (for release
+# notes).
+#
+# We consider that a CVE is "fixed" if either:
+# - the CVE status changes from Unpatched to Patched in the new report.
+# - the Unpatched CVE is unlisted in the new report.
+#
+# Reports can be found here: https://valkyrie.yocto.io/pub/non-release/?type=metrics
+
+import sys
+import json
+
+json_prev_path = sys.argv[1]
+json_next_path = sys.argv[2]
+
+data_prev, data_next = None, None
+
+with open(json_prev_path, 'r') as fd_prev, open(json_prev_path, 'r') as fd_next:
+    data_prev = json.load(fd_prev)["package"]
+    data_next = json.load(fd_next)["package"]
+
+patched_cves = {}
+
+for pkg_prev in data_prev:
+    pkg_name = pkg_prev["name"]
+
+    pkg_next = None
+    for p in data_next:
+        if p["name"] == pkg_name:
+            pkg_next = p
+            break
+
+    if pkg_next is not None:
+        prev_unpatched = [cve["id"] for cve in pkg_prev["issue"] if cve["status"] == "Unpatched"]
+        # next_patched = set([cve["id"] for cve in pkg_next["issue"] if cve["status"] == "Patched"])
+        pkg_patched_cves = []
+        for cve in prev_unpatched:
+            if cve in pkg_next["issue"] and pkg_next["issue"][cve]["status"] == "Patched":
+                pkg_patched_cves.append(cve)
+            if cve not in pkg_next["issue"]:
+                pkg_patched_cves.append(cve)
+
+        if pkg_patched_cves:
+            patched_cves[pkg_name] = sorted(pkg_patched_cves, key=lambda cve: (int(cve.split('-')[1]), int(cve.split('-')[2])))
+
+if not patched_cves:
+    print("No patched CVEs found")
+    exit(0)
+
+# Remove -native duplicates
+for pkg in list(patched_cves):
+    if pkg.endswith("-native") and pkg[:-len("-native")] in patched_cves:
+        patched_cves.pop(pkg)
+
+print(""".. list-table::
+   :widths: 30 70
+   :header-rows: 1
+
+   * - Recipe
+     - CVE IDs""")
+
+for pkg in sorted(patched_cves.keys()):
+    cves_rst = ", ".join([f":cve_nist:`{c[4:]}`" for c in patched_cves[pkg]])
+    print(f"   * - ``{pkg}``")
+    print(f"     - {cves_rst}")