utils/checkpackagelib: add new check MissingCVEPatch

To indicate that a patch fixes a vulnerability in Buildroot, the convention is:
1. In the patch file, add a tag 'CVE: <cve id>'
2. In <pkg>.mk, and an entry to <PKG>_IGNORE_CVES, and add a comment above
   that new entry to reference the patch file(s)

However, as packages get bumped and their patches are added, removed or
rebased; it happens that IGNORE_CVES get outdated. One important issue is
marking a CVE as ignored, while the corresponding patch is not in Buildroot.

To detect such cases, add a new checker to checkpackagelib that finds
occurences of:

    # 000x-some-patch.patch
    PKG_IGNORE_CVES += CVE-XXXX-YYYY

For each one of them, ensure that the mentioned patch files actually exist
and contain the `CVE: ...` tag.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Titouan Christophe <titouan.christophe@mind.be>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
This commit is contained in:
Titouan Christophe
2026-07-03 10:31:36 +02:00
committed by Thomas Petazzoni
parent 636f69ab45
commit b00ac4e346
2 changed files with 99 additions and 0 deletions

View File

@@ -6,6 +6,7 @@
import os
import re
from pathlib import Path
from checkpackagelib.base import _CheckFunction
from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
@@ -117,6 +118,47 @@ class Indent(_CheckFunction):
text]
class MissingCVEPatch(_CheckFunction):
PATCH_COMMENT = re.compile(r"^#\s*(\S+\.patch)\s*$")
IGNORE_CVES = re.compile(r"^[A-Z0-9_]+_IGNORE_CVES\s*\+?=")
CVE_TAG_IN_PATCH = re.compile(r"^CVE: *CVE-\d+-\d+$")
def before(self):
self.pending_patches = []
self.package_dir = Path(self.filename).parent
def check_patch_files(self):
for patch_lineno, patch_name, patch_text in self.pending_patches:
patch_file = self.package_dir / patch_name
self.pending_patch = None
if not patch_file.is_file():
return ["{}:{}: patch file '{}' mentioned for ignored CVEs is missing"
.format(self.filename, patch_lineno, patch_name),
patch_text]
if not any(map(self.CVE_TAG_IN_PATCH.match, patch_file.open())):
return ["{}: patch file '{}' is missing 'CVE:' tag"
.format(self.filename, patch_name),
patch_text]
def check_line(self, lineno, text):
m = self.PATCH_COMMENT.match(text.rstrip())
if m:
self.pending_patches.append((lineno, m.group(1), text))
return
# other comments do not break the association between the patch
# comment and the following _IGNORE_CVES assignment
if text.lstrip().startswith("#"):
return
if self.IGNORE_CVES.search(text):
return self.check_patch_files()
self.pending_patches = []
class OverriddenVariable(_CheckFunction):
CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\1\)")
END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))

View File

@@ -197,6 +197,63 @@ def test_Indent(testname, filename, string, expected):
assert warnings == expected
MissingCVEPatch = [
('patches present',
{'0001-some-fix.patch': 'CVE: CVE-2000-1234', '0002-other-fix.patch': 'CVE: CVE-2000-1234'},
'# 0001-some-fix.patch\n'
'# 0002-other-fix.patch\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[]),
('patch missing',
{'0002-other-fix.patch': 'CVE: CVE-2000-1234'},
'# 0001-some-fix.patch\n'
'# 0002-other-fix.patch\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[['{}:1: patch file \'0001-some-fix.patch\' mentioned for ignored CVEs is missing',
'# 0001-some-fix.patch\n']]),
('CVE tag missing',
{'0001-some-fix.patch': ''},
'# 0001-some-fix.patch\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[['{}: patch file \'0001-some-fix.patch\' is missing \'CVE:\' tag',
'# 0001-some-fix.patch\n']]),
('patch in version subdir',
{'1.0/0001-some-fix.patch': 'CVE: CVE-2000-1234'},
'# 1.0/0001-some-fix.patch\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[]),
('comments between patch and ignore',
{},
'# 0001-some-fix.patch\n'
'# this entry is not stale\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[['{}:1: patch file \'0001-some-fix.patch\' mentioned for ignored CVEs is missing',
'# 0001-some-fix.patch\n']]),
('comment not followed by ignore',
{},
'# 0001-some-fix.patch\n'
'FOO_DEPENDENCIES = host-foo\n'
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[]),
('ignore without patch comment',
{},
'FOO_IGNORE_CVES += CVE-2000-1234\n',
[]),
]
@pytest.mark.parametrize('testname,patches,string,expected', MissingCVEPatch)
def test_MissingCVEPatch(testname, patches, string, expected, tmp_path):
filename = str(tmp_path / 'foo.mk')
for patch_name, content in patches.items():
patch_path = tmp_path / patch_name
patch_path.parent.mkdir(parents=True, exist_ok=True)
patch_path.write_text(content)
warnings = util.check_file(m.MissingCVEPatch, filename, string)
expected = [[w[0].format(filename)] + w[1:] for w in expected]
assert warnings == expected
OverriddenVariable = [
('simple assignment',
'any.mk',