Skip to content

Commit 6cfdc43

Browse files
feat: checker version "do not match" patterns (#2777)
* Fixes #2700 Add IGNORE_PATTERNS to check against false version patterns. This allows you to explicitly avoid false positives against things like specific error messages that mention version numbers. --------- Co-authored-by: Terri Oda <[email protected]>
1 parent 9bdba0c commit 6cfdc43

File tree

4 files changed

+38
-3
lines changed

4 files changed

+38
-3
lines changed

cve_bin_tool/checkers/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ from cve_bin_tool.checkers import Checker
3535
class CurlChecker(Checker):
3636
```
3737

38-
Every checker must contain following 4 class attributes specific to product(ex: curl)
38+
Every checker may contain following 5 class attributes specific to product(ex: curl)
3939
you are making checker for:
4040

4141
1. CONTAINS_PATTERNS - list of commonly found strings in the binary of the product
4242
2. FILENAME_PATTERNS - list of different filename for the product
4343
3. VERSION_PATTERNS - list of version patterns found in binary of the product.
4444
4. VENDOR_PRODUCT - list of vendor product pairs for the product as they appear in
4545
NVD.
46+
5. IGNORE_PATTERNS (optional) - list of patterns that could cause false positives (e.g. error messages that mention specific product/versions)
4647

4748
`CONTAINS_PATTERN`, `FILENAME_PATTERNS` and `VERSION_PATTERNS` supports regex to cover
4849
wide range of use cases.

cve_bin_tool/checkers/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ def __new__(cls, name, bases, props):
328328
raise InvalidCheckerError(
329329
f"Checker {name} has a VENDOR_PRODUCT string that is not lowercase"
330330
)
331+
if cls.IGNORE_PATTERNS is None:
332+
cls.IGNORE_PATTERNS = []
333+
else:
334+
cls.IGNORE_PATTERNS = list(map(re.compile, cls.IGNORE_PATTERNS))
331335
# Compile regex
332336
cls.CONTAINS_PATTERNS = list(map(re.compile, cls.CONTAINS_PATTERNS))
333337
cls.VERSION_PATTERNS = list(map(re.compile, cls.VERSION_PATTERNS))
@@ -342,6 +346,7 @@ class Checker(metaclass=CheckerMetaClass):
342346
VERSION_PATTERNS: list[str] = []
343347
FILENAME_PATTERNS: list[str] = []
344348
VENDOR_PRODUCT: list[tuple[str, str]] = []
349+
IGNORE_PATTERNS: list[str] = []
345350

346351
def guess_contains(self, lines):
347352
if any(pattern.search(lines) for pattern in self.CONTAINS_PATTERNS):
@@ -358,6 +363,8 @@ def get_version(self, lines, filename):
358363
version_info["is_or_contains"] = "contains"
359364

360365
if "is_or_contains" in version_info:
361-
version_info["version"] = regex_find(lines, self.VERSION_PATTERNS)
366+
version_info["version"] = regex_find(
367+
lines, self.VERSION_PATTERNS, self.IGNORE_PATTERNS
368+
)
362369

363370
return version_info

cve_bin_tool/util.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,19 @@ def __missing__(self, key: str) -> list[CVE] | set[str]:
9393
return self[key]
9494

9595

96-
def regex_find(lines: str, version_patterns: list[Pattern[str]]) -> str:
96+
def regex_find(
97+
lines: str, version_patterns: list[Pattern[str]], ignore: list[Pattern[str]]
98+
) -> str:
9799
"""Search a set of lines to find a match for the given regex"""
98100
new_guess = ""
99101

100102
for pattern in version_patterns:
101103
match = pattern.search(lines)
102104
if match:
103105
new_guess = match.group(1).strip()
106+
for i in ignore:
107+
if str(i) in str(new_guess) or str(new_guess) in str(i):
108+
new_guess = ""
104109
break
105110
if new_guess != "":
106111
new_guess = new_guess.replace("_", ".")

test/test_checkers.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright (C) 2021 Intel Corporation
22
# SPDX-License-Identifier: GPL-3.0-or-later
33

4+
from __future__ import annotations
5+
46
import re
57
import sys
68

@@ -25,11 +27,13 @@ class MyChecker(Checker):
2527
VERSION_PATTERNS = [r"for"]
2628
FILENAME_PATTERNS = [r"myproduct"]
2729
VENDOR_PRODUCT = [("myvendor", "myproduct")]
30+
IGNORE_PATTERNS = [r"ignore"]
2831

2932
assert type(MyChecker.CONTAINS_PATTERNS[0]) == Pattern
3033
assert type(MyChecker.VERSION_PATTERNS[0]) == Pattern
3134
assert type(MyChecker.FILENAME_PATTERNS[0]) == Pattern
3235
assert type(MyChecker.VENDOR_PRODUCT[0]) == VendorProductPair
36+
assert type(MyChecker.IGNORE_PATTERNS[0]) == Pattern
3337

3438
def test_no_vpkg(self):
3539
with pytest.raises(AssertionError) as e:
@@ -39,6 +43,7 @@ class MyChecker(Checker):
3943
VERSION_PATTERNS = [r"for"]
4044
FILENAME_PATTERNS = [r"myproduct"]
4145
PRODUCT_NAME = "mychecker"
46+
IGNORE_PATTERNS = [r"ignore"]
4247

4348
assert e.value.args[0] == "MyChecker needs at least one vendor product pair"
4449

@@ -139,3 +144,20 @@ def test_filename_is(self, checker_name, file_name, expected_results):
139144

140145
for result, expected_result in zip(results, expected_results):
141146
assert result["is_or_contains"] == "is"
147+
148+
class MyFakeChecker(Checker):
149+
CONTAINS_PATTERNS: list[str] = []
150+
FILENAME_PATTERNS: list[str] = [r"checker"]
151+
VERSION_PATTERNS = [r"mychecker-([0-9].[0-9]+)"]
152+
VENDOR_PRODUCT = [("my", "checker")]
153+
IGNORE_PATTERNS = [r"mychecker-5.6"]
154+
155+
string = "Some other lines. \n Ignore this version pattern mychecker-5.6. \n Consider this version pattern mychecker-5.8. \n Some more lines."
156+
lines = string.split("\n")
157+
checker = MyFakeChecker()
158+
159+
result1 = checker.get_version(lines[1], "")
160+
assert result1["version"] == "UNKNOWN"
161+
162+
result2 = checker.get_version(lines[2], "")
163+
assert result2["version"] == "5.8"

0 commit comments

Comments
 (0)