mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-09-25 19:40:35 -09:00
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:
committed by
Thomas Petazzoni
parent
636f69ab45
commit
b00ac4e346
@@ -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)))
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user