From 25ea5d567acbb9896164f8b398f69456ea5d57e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20M=C3=A9lotte?= Date: Wed, 23 Apr 2025 17:29:05 +0200 Subject: [PATCH] support/scripts/pkg-stats: add support for reporting stale CVE entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NVD database contains some CPEs that are wrongly not associated with any version number. They are for example sometimes associated with very old CVEs. Those CPEs are annoying, because they pollute our pkg-stat CVE results with CVE entries which actually don't affect us. The proper way to solve it is, and should remain, to fix the NVD database by reporting these issues. Having to deal with a lot of CVEs/CPEs, the NVD database is however slow to be updated. To reduce the noise in our pkg-stats results in the meantime, one possibility is to add entries for those CVEs. This however comes with the downside that even once the NVD database gets fixed, those ignored entries risk remaining in Buildroot forever because they are undetected. This commit tries to address this downside by checking for and reporting CVEs that are ignored in Buildroot, but where the NVD reports our package version as unaffected. Those CVEs will appear in the 'CVEs Ignored' column as '(stale)', and the cell will be colored the same way warnings are. This should allow us to detect and remove those entries. It can be tested for example by adding the following variable to the apache package (for a CVE that was recently fixed in the NVD database): APACHE_IGNORE_CVES = CVE-1999-0236 Signed-off-by: Raphaël Mélotte Signed-off-by: Thomas Petazzoni (cherry picked from commit 56ea5a02268631f2ea679c067e9b0ec94e0e512e) Signed-off-by: Thomas Perale --- support/scripts/cve.py | 4 +--- support/scripts/pkg-stats | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/support/scripts/cve.py b/support/scripts/cve.py index 18fda8e1a1..1cbe24c02d 100755 --- a/support/scripts/cve.py +++ b/support/scripts/cve.py @@ -184,13 +184,11 @@ class CVE: """The set of CPE products referred by this CVE definition""" return set(cpe_product(p['id']) for p in self.each_cpe()) - def affects(self, name, version, cve_ignore_list, cpeid=None): + def affects(self, name, version, cpeid=None): """ True if the Buildroot Package object passed as argument is affected by this CVE. """ - if self.identifier in cve_ignore_list: - return self.CVE_DOESNT_AFFECT pkg_version = distutils.version.LooseVersion(version) if not hasattr(pkg_version, "version"): diff --git a/support/scripts/pkg-stats b/support/scripts/pkg-stats index 46b12cd992..a0a46aa8cb 100755 --- a/support/scripts/pkg-stats +++ b/support/scripts/pkg-stats @@ -122,6 +122,7 @@ class Package: self.cves = list() self.ignored_cves = list() self.unsure_cves = list() + self.stale_cve_ignores = list() self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None} self.status = {} @@ -638,7 +639,18 @@ def check_package_cve_affects(cve, cpe_product_pkgs): if product not in cpe_product_pkgs: continue for pkg in cpe_product_pkgs[product]: - cve_status = cve.affects(pkg.name, pkg.current_version, pkg.ignored_cves, pkg.cpeid) + cve_status = cve.affects(pkg.name, pkg.current_version, pkg.cpeid) + + if cve.identifier in pkg.ignored_cves: + if cve_status == cve.CVE_DOESNT_AFFECT: + # We have an ignore entry for a CVE which is + # already reported as 'not affected'. This might + # happen for example when the NVD database doesn't + # initially include version numbers for a CPE, and + # later fixes it. Store it so that we can report + # it. + pkg.stale_cve_ignores.append(cve.identifier) + cve_status = cve.CVE_DOESNT_AFFECT if cve_status == cve.CVE_AFFECTS: pkg.cves.append(cve.identifier) elif cve_status == cve.CVE_UNKNOWN: @@ -670,6 +682,8 @@ def check_package_cves(nvd_path, packages): if 'cve' not in pkg.status: if pkg.cves or pkg.unsure_cves: pkg.status['cve'] = ("error", "affected by CVEs") + elif pkg.stale_cve_ignores: + pkg.status['cve'] = ("warning", "has stale CVE ignores") else: pkg.status['cve'] = ("ok", "not affected by CVEs") @@ -712,10 +726,13 @@ def calculate_stats(packages): stats["patches"] += pkg.patch_count stats["total-cves"] += len(pkg.cves) stats["total-unsure-cves"] += len(pkg.unsure_cves) + stats["total-stale-cve-ignores"] += len(pkg.stale_cve_ignores) if len(pkg.cves) != 0: stats["pkg-cves"] += 1 if len(pkg.unsure_cves) != 0: stats["pkg-unsure-cves"] += 1 + if len(pkg.stale_cve_ignores) != 0: + stats["pkg-stale-cve-ignores"] += 1 if pkg.cpeid: stats["cpe-id"] += 1 else: @@ -867,7 +884,7 @@ function expandField(fieldId){ .wrong, .lotsofpatches, .invalid_url, .version-needs-update, .cpe-nok, .cve-nok { background: #ff9a69; } - .somepatches, .somewarnings, .missing_url, .version-unknown, .cpe-unknown, .cve-unknown { + .somepatches, .somewarnings, .missing_url, .version-unknown, .cpe-unknown, .cve-unknown, .cve-stale { background: #ffd870; } .cve_ignored, .version-error { @@ -1080,10 +1097,17 @@ def dump_html_pkg(f, pkg): div_class = ["centered data ignored_cves"] div_class.append(f'_{pkg_css_class}') if pkg.ignored_cves: - div_class.append("cve_ignored") + if pkg.stale_cve_ignores: + div_class.append("cve-stale") + else: + div_class.append("cve_ignored") f.write(f'
\n') for ignored_cve in pkg.ignored_cves: - f.write(f' {ignored_cve}
\n') + if ignored_cve in pkg.stale_cve_ignores: + f.write(f""" {ignored_cve}""" + """ (stale)
\n""") + else: + f.write(f' {ignored_cve}
\n') f.write("
\n") # CPE ID @@ -1193,6 +1217,10 @@ def dump_html_stats(f, stats): stats["pkg-unsure-cves"]) f.write('
Total number of unsure CVEs affecting all packages
%s
\n' % stats["total-unsure-cves"]) + f.write('
Packages with stale CVE ignores
%s
\n' % + stats["pkg-stale-cve-ignores"]) + f.write('
Total number of stale CVE ignores affecting all packages
%s
\n' % + stats["total-stale-cve-ignores"]) f.write('
Packages with CPE ID
%s
\n' % stats["cpe-id"]) f.write('
Packages without CPE ID
%s
\n' %