diff --git a/cve_bin_tool/checkers/README.md b/cve_bin_tool/checkers/README.md index 82f58a64d4..02f2cd58e5 100644 --- a/cve_bin_tool/checkers/README.md +++ b/cve_bin_tool/checkers/README.md @@ -35,7 +35,7 @@ from cve_bin_tool.checkers import Checker class CurlChecker(Checker): ``` -Every checker must contain following 4 class attributes specific to product(ex: curl) +Every checker may contain following 5 class attributes specific to product(ex: curl) you are making checker for: 1. CONTAINS_PATTERNS - list of commonly found strings in the binary of the product @@ -43,6 +43,7 @@ you are making checker for: 3. VERSION_PATTERNS - list of version patterns found in binary of the product. 4. VENDOR_PRODUCT - list of vendor product pairs for the product as they appear in NVD. +5. IGNORE_PATTERNS (optional) - list of patterns that could cause false positives (e.g. error messages that mention specific product/versions) `CONTAINS_PATTERN`, `FILENAME_PATTERNS` and `VERSION_PATTERNS` supports regex to cover wide range of use cases. diff --git a/cve_bin_tool/checkers/__init__.py b/cve_bin_tool/checkers/__init__.py index 6718ef963a..7be1943d28 100644 --- a/cve_bin_tool/checkers/__init__.py +++ b/cve_bin_tool/checkers/__init__.py @@ -28,6 +28,7 @@ "binutils", "bird", "bison", + "bluez", "boinc", "botan", "bro", @@ -328,6 +329,10 @@ def __new__(cls, name, bases, props): raise InvalidCheckerError( f"Checker {name} has a VENDOR_PRODUCT string that is not lowercase" ) + if cls.IGNORE_PATTERNS is None: + cls.IGNORE_PATTERNS = [] + else: + cls.IGNORE_PATTERNS = list(map(re.compile, cls.IGNORE_PATTERNS)) # Compile regex cls.CONTAINS_PATTERNS = list(map(re.compile, cls.CONTAINS_PATTERNS)) cls.VERSION_PATTERNS = list(map(re.compile, cls.VERSION_PATTERNS)) @@ -342,6 +347,7 @@ class Checker(metaclass=CheckerMetaClass): VERSION_PATTERNS: list[str] = [] FILENAME_PATTERNS: list[str] = [] VENDOR_PRODUCT: list[tuple[str, str]] = [] + IGNORE_PATTERNS: list[str] = [] def guess_contains(self, lines): if any(pattern.search(lines) for pattern in self.CONTAINS_PATTERNS): @@ -358,6 +364,8 @@ def get_version(self, lines, filename): version_info["is_or_contains"] = "contains" if "is_or_contains" in version_info: - version_info["version"] = regex_find(lines, self.VERSION_PATTERNS) + version_info["version"] = regex_find( + lines, self.VERSION_PATTERNS, self.IGNORE_PATTERNS + ) return version_info diff --git a/cve_bin_tool/checkers/bluez.py b/cve_bin_tool/checkers/bluez.py new file mode 100644 index 0000000000..bdd5d1edb4 --- /dev/null +++ b/cve_bin_tool/checkers/bluez.py @@ -0,0 +1,24 @@ +# Copyright (C) 2023 Orange +# SPDX-License-Identifier: GPL-3.0-or-later + + +""" +CVE checker for bluez + +https://www.cvedetails.com/product/35116/Bluez-Bluez.html?vendor_id=8316 +https://www.cvedetails.com/product/35329/Bluez-Project-Bluez.html?vendor_id=3242 + +""" +from __future__ import annotations + +from cve_bin_tool.checkers import Checker + + +class BluezChecker(Checker): + CONTAINS_PATTERNS: list[str] = [] + FILENAME_PATTERNS: list[str] = [] + VERSION_PATTERNS = [ + r"bluez-([0-9]+\.[0-9]+)", + r"([0-9]+\.[0-9]+)\r?\n[a-zA-Z :]*(?:BlueZ|hcidump|hcitool|OBEX daemon)", + ] + VENDOR_PRODUCT = [("bluez", "bluez"), ("bluez_project", "bluez")] diff --git a/cve_bin_tool/checkers/libtiff.py b/cve_bin_tool/checkers/libtiff.py index b5c47f8e75..8e4a88067a 100644 --- a/cve_bin_tool/checkers/libtiff.py +++ b/cve_bin_tool/checkers/libtiff.py @@ -19,5 +19,5 @@ class LibtiffChecker(Checker): r'TIFF directory is missing required "StripByteCounts" field, calculating from imagelength', ] FILENAME_PATTERNS = [r"libtiff.so."] - VERSION_PATTERNS = [r"LIBTIFF, Version ([0-9]+\.[0-9]+\.[0-9]+)"] + VERSION_PATTERNS = [r"LIBTIFF, Version ([0-9]+\.[0-9]+\.[0-9]+)\r?\nCopyright"] VENDOR_PRODUCT = [("libtiff", "libtiff")] diff --git a/cve_bin_tool/checkers/pango.py b/cve_bin_tool/checkers/pango.py index c0cc2ca2c1..ab3a369a4b 100644 --- a/cve_bin_tool/checkers/pango.py +++ b/cve_bin_tool/checkers/pango.py @@ -16,5 +16,8 @@ class PangoChecker(Checker): CONTAINS_PATTERNS: list[str] = [] FILENAME_PATTERNS: list[str] = [] - VERSION_PATTERNS = [r"([0-9]+\.[0-9]+\.[0-9]+)\r?\n[pP]ango"] + VERSION_PATTERNS = [ + r"([0-9]+\.[0-9]+\.[0-9]+)\r?\n/etc/pango", + r"([0-9]+\.[0-9]+\.[0-9]+)\r?\nPango version", + ] VENDOR_PRODUCT = [("gnome", "pango"), ("pango", "pango")] diff --git a/cve_bin_tool/checkers/proftpd.py b/cve_bin_tool/checkers/proftpd.py index 2b09ede9c2..8debead123 100644 --- a/cve_bin_tool/checkers/proftpd.py +++ b/cve_bin_tool/checkers/proftpd.py @@ -17,5 +17,5 @@ class ProftpdChecker(Checker): CONTAINS_PATTERNS: list[str] = [] FILENAME_PATTERNS: list[str] = [] - VERSION_PATTERNS = [r"ProFTPD ([0-9]+\.[0-9]+\.[0-9a-z]+)"] + VERSION_PATTERNS = [r"ProFTPD ([0-9]+\.[0-9]+\.[0-9a-z]+) "] VENDOR_PRODUCT = [("proftpd_project", "proftpd"), ("proftpd", "proftpd")] diff --git a/cve_bin_tool/checkers/tcpdump.py b/cve_bin_tool/checkers/tcpdump.py index 6c90f2511d..e6aaebfbce 100644 --- a/cve_bin_tool/checkers/tcpdump.py +++ b/cve_bin_tool/checkers/tcpdump.py @@ -21,6 +21,6 @@ class TcpdumpChecker(Checker): r"tcpdump-([0-9]+\.[0-9]+\.[0-9]+)", r"([0-9]+\.[0-9]+\.[0-9]+)\r?\n[0-9a-f]*lookup_(?:emem|protoid)", r"Running\r?\n([0-9]+\.[0-9]+\.[0-9]+)\r?\n0123456789", - r"tcpdump[0-9a-zA-Z ,!'%:_=\(\)\\\.\-\r\n]*([0-9]+\.[0-9]+\.[0-9]+)", + r"tcpdump[0-9a-zA-Z ,!'%:_=\(\)\\\.\-\r\n]*\r?\n([0-9]+\.[0-9]+\.[0-9]+)", ] VENDOR_PRODUCT = [("tcpdump", "tcpdump")] diff --git a/cve_bin_tool/util.py b/cve_bin_tool/util.py index 47f0345e36..f489634aae 100644 --- a/cve_bin_tool/util.py +++ b/cve_bin_tool/util.py @@ -93,7 +93,9 @@ def __missing__(self, key: str) -> list[CVE] | set[str]: return self[key] -def regex_find(lines: str, version_patterns: list[Pattern[str]]) -> str: +def regex_find( + lines: str, version_patterns: list[Pattern[str]], ignore: list[Pattern[str]] +) -> str: """Search a set of lines to find a match for the given regex""" new_guess = "" @@ -101,6 +103,9 @@ def regex_find(lines: str, version_patterns: list[Pattern[str]]) -> str: match = pattern.search(lines) if match: new_guess = match.group(1).strip() + for i in ignore: + if str(i) in str(new_guess) or str(new_guess) in str(i): + new_guess = "" break if new_guess != "": new_guess = new_guess.replace("_", ".") diff --git a/test/condensed-downloads/bluez-5.66-5.fc38.aarch64.rpm.tar.gz b/test/condensed-downloads/bluez-5.66-5.fc38.aarch64.rpm.tar.gz new file mode 100644 index 0000000000..be80db557c Binary files /dev/null and b/test/condensed-downloads/bluez-5.66-5.fc38.aarch64.rpm.tar.gz differ diff --git a/test/condensed-downloads/bluez-daemon_5.50-5_x86_64.ipk.tar.gz b/test/condensed-downloads/bluez-daemon_5.50-5_x86_64.ipk.tar.gz new file mode 100644 index 0000000000..3e02b24ab9 Binary files /dev/null and b/test/condensed-downloads/bluez-daemon_5.50-5_x86_64.ipk.tar.gz differ diff --git a/test/condensed-downloads/bluez_5.50-1.2~deb10u2_amd64.deb.tar.gz b/test/condensed-downloads/bluez_5.50-1.2~deb10u2_amd64.deb.tar.gz new file mode 100644 index 0000000000..ac279ee0da Binary files /dev/null and b/test/condensed-downloads/bluez_5.50-1.2~deb10u2_amd64.deb.tar.gz differ diff --git a/test/condensed-downloads/libtiff5_4.2.0-1+deb11u4_amd64.deb.tar.gz b/test/condensed-downloads/libtiff5_4.2.0-1+deb11u4_amd64.deb.tar.gz new file mode 100644 index 0000000000..44d723e00b Binary files /dev/null and b/test/condensed-downloads/libtiff5_4.2.0-1+deb11u4_amd64.deb.tar.gz differ diff --git a/test/condensed-downloads/libtiff_4.1.0-1_x86_64.ipk.tar.gz b/test/condensed-downloads/libtiff_4.1.0-1_x86_64.ipk.tar.gz new file mode 100644 index 0000000000..bb4c3c11b0 Binary files /dev/null and b/test/condensed-downloads/libtiff_4.1.0-1_x86_64.ipk.tar.gz differ diff --git a/test/test_checkers.py b/test/test_checkers.py index 8ff1f94b75..061cd9f7cf 100644 --- a/test/test_checkers.py +++ b/test/test_checkers.py @@ -1,6 +1,8 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: GPL-3.0-or-later +from __future__ import annotations + import re import sys @@ -25,11 +27,13 @@ class MyChecker(Checker): VERSION_PATTERNS = [r"for"] FILENAME_PATTERNS = [r"myproduct"] VENDOR_PRODUCT = [("myvendor", "myproduct")] + IGNORE_PATTERNS = [r"ignore"] assert type(MyChecker.CONTAINS_PATTERNS[0]) == Pattern assert type(MyChecker.VERSION_PATTERNS[0]) == Pattern assert type(MyChecker.FILENAME_PATTERNS[0]) == Pattern assert type(MyChecker.VENDOR_PRODUCT[0]) == VendorProductPair + assert type(MyChecker.IGNORE_PATTERNS[0]) == Pattern def test_no_vpkg(self): with pytest.raises(AssertionError) as e: @@ -39,6 +43,7 @@ class MyChecker(Checker): VERSION_PATTERNS = [r"for"] FILENAME_PATTERNS = [r"myproduct"] PRODUCT_NAME = "mychecker" + IGNORE_PATTERNS = [r"ignore"] assert e.value.args[0] == "MyChecker needs at least one vendor product pair" @@ -139,3 +144,20 @@ def test_filename_is(self, checker_name, file_name, expected_results): for result, expected_result in zip(results, expected_results): assert result["is_or_contains"] == "is" + + class MyFakeChecker(Checker): + CONTAINS_PATTERNS: list[str] = [] + FILENAME_PATTERNS: list[str] = [r"checker"] + VERSION_PATTERNS = [r"mychecker-([0-9].[0-9]+)"] + VENDOR_PRODUCT = [("my", "checker")] + IGNORE_PATTERNS = [r"mychecker-5.6"] + + string = "Some other lines. \n Ignore this version pattern mychecker-5.6. \n Consider this version pattern mychecker-5.8. \n Some more lines." + lines = string.split("\n") + checker = MyFakeChecker() + + result1 = checker.get_version(lines[1], "") + assert result1["version"] == "UNKNOWN" + + result2 = checker.get_version(lines[2], "") + assert result2["version"] == "5.8" diff --git a/test/test_data/bluez.py b/test/test_data/bluez.py new file mode 100644 index 0000000000..b3d2ae8f68 --- /dev/null +++ b/test/test_data/bluez.py @@ -0,0 +1,33 @@ +# Copyright (C) 2022 Orange +# SPDX-License-Identifier: GPL-3.0-or-later + +mapping_test_data = [ + {"product": "bluez", "version": "5.28", "version_strings": ["bluez-5.28"]}, + { + "product": "bluez", + "version": "5.50", + "version_strings": ["5.50\nUsage: hcidump"], + }, + {"product": "bluez", "version": "5.50", "version_strings": ["5.50\nhcitool"]}, + {"product": "bluez", "version": "5.66", "version_strings": ["5.66\nBlueZ"]}, +] +package_test_data = [ + { + "url": "http://rpmfind.net/linux/fedora/linux/development/rawhide/Everything/aarch64/os/Packages/b/", + "package_name": "bluez-5.66-5.fc38.aarch64.rpm", + "product": "bluez", + "version": "5.66", + }, + { + "url": "http://ftp.fr.debian.org/debian/pool/main/b/bluez/", + "package_name": "bluez_5.50-1.2~deb10u2_amd64.deb", + "product": "bluez", + "version": "5.50", + }, + { + "url": "https://downloads.openwrt.org/releases/packages-19.07/x86_64/packages/", + "package_name": "bluez-daemon_5.50-5_x86_64.ipk", + "product": "bluez", + "version": "5.50", + }, +] diff --git a/test/test_data/libtiff.py b/test/test_data/libtiff.py index 387cdd57bf..f8e07a8c90 100644 --- a/test/test_data/libtiff.py +++ b/test/test_data/libtiff.py @@ -7,7 +7,7 @@ "version": "4.0.2", "version_strings": [ 'TIFF directory is missing required \\"StripByteCounts\\" field, calculating from imagelength', - "LIBTIFF, Version 4.0.2", + "LIBTIFF, Version 4.0.2\nCopyright", ], } ] @@ -24,4 +24,16 @@ "product": "libtiff", "version": "4.0.3", }, + { + "url": "http://ftp.fr.debian.org/debian/pool/main/t/tiff/", + "package_name": "libtiff5_4.2.0-1+deb11u4_amd64.deb", + "product": "libtiff", + "version": "4.2.0", + }, + { + "url": "https://downloads.openwrt.org/releases/packages-19.07/x86_64/packages/", + "package_name": "libtiff_4.1.0-1_x86_64.ipk", + "product": "libtiff", + "version": "4.1.0", + }, ] diff --git a/test/test_data/lynx.py b/test/test_data/lynx.py index f21d2d93ba..a0dcc9ba50 100644 --- a/test/test_data/lynx.py +++ b/test/test_data/lynx.py @@ -19,27 +19,23 @@ "package_name": "lynx-2.9.0-dev.10.2.fc37.1.aarch64.rpm", "product": "lynx", "version": "2.9.0dev.10", - "other_products": ["proftpd"], }, { "url": "http://rpmfind.net/linux/fedora-secondary/development/rawhide/Everything/ppc64le/os/Packages/l/", "package_name": "lynx-2.9.0-dev.10.2.fc37.1.ppc64le.rpm", "product": "lynx", "version": "2.9.0dev.10", - "other_products": ["proftpd"], }, { "url": "http://ftp.fr.debian.org/debian/pool/main/l/lynx/", "package_name": "lynx_2.8.9dev11-1_arm64.deb", "product": "lynx", "version": "2.8.9dev.11", - "other_products": ["proftpd"], }, { "url": "https://downloads.openwrt.org/releases/packages-19.07/x86_64/packages/", "package_name": "lynx_2.8.9rel.1-1_x86_64.ipk", "product": "lynx", "version": "2.8.9rel.1", - "other_products": ["proftpd"], }, ] diff --git a/test/test_data/pango.py b/test/test_data/pango.py index 573427f38c..2281029047 100644 --- a/test/test_data/pango.py +++ b/test/test_data/pango.py @@ -2,11 +2,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later mapping_test_data = [ + { + "product": "pango", + "version": "1.40.5", + "version_strings": ["1.40.5\n/etc/pango"], + }, { "product": "pango", "version": "1.40.5", "version_strings": ["1.40.5\nPango version"], - } + }, ] package_test_data = [ { diff --git a/test/test_data/proftpd.py b/test/test_data/proftpd.py index 44e469924d..3b17865033 100644 --- a/test/test_data/proftpd.py +++ b/test/test_data/proftpd.py @@ -5,7 +5,7 @@ { "product": "proftpd", "version": "1.3.8rc4", - "version_strings": ["ProFTPD 1.3.8rc4"], + "version_strings": ["ProFTPD 1.3.8rc4 "], } ] package_test_data = [ diff --git a/test/test_data/tcpdump.py b/test/test_data/tcpdump.py index c549260910..4d33c2e5e2 100644 --- a/test/test_data/tcpdump.py +++ b/test/test_data/tcpdump.py @@ -23,6 +23,7 @@ "version": "4.9.2", "version_strings": ["Running\n4.9.2\n0123456789"], }, + {"product": "tcpdump", "version": "4.1.1", "version_strings": ["tcpdump\n4.1.1"]}, ] package_test_data = [ {