mirror of
https://gitlab.com/buildroot.org/buildroot.git
synced 2026-10-01 06:21:56 -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>
(cherry picked from commit b00ac4e346)
Signed-off-by: Thomas Perale <thomas.perale@mind.be>
This commit is contained in:
committed by
Thomas Perale
parent
99e75fa835
commit
d578727aae
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from checkpackagelib.base import _CheckFunction
|
from checkpackagelib.base import _CheckFunction
|
||||||
from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
|
from checkpackagelib.lib import ConsecutiveEmptyLines # noqa: F401
|
||||||
@@ -117,6 +118,47 @@ class Indent(_CheckFunction):
|
|||||||
text]
|
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):
|
class OverriddenVariable(_CheckFunction):
|
||||||
CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\1\)")
|
CONCATENATING = re.compile(r"^([A-Z0-9_]+)\s*(\+|:|)=\s*\$\(\1\)")
|
||||||
END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
|
END_CONDITIONAL = re.compile(r"^\s*({})".format("|".join(end_conditional)))
|
||||||
|
|||||||
@@ -197,6 +197,63 @@ def test_Indent(testname, filename, string, expected):
|
|||||||
assert warnings == 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 = [
|
OverriddenVariable = [
|
||||||
('simple assignment',
|
('simple assignment',
|
||||||
'any.mk',
|
'any.mk',
|
||||||
|
|||||||
Reference in New Issue
Block a user