cve_check: add CVE_VERSION_SUFFIX to indicate suffix in versioning

add CVE_VERSION_SUFFIX to indicate the version suffix type, currently
works in two value, "alphabetical" if the version string uses single
alphabetical character suffix as incremental release, blank to not
consider the unidentified suffixes. This can be expand when more suffix
pattern identified.

refactor cve_check.Version class to use functools and add parameter to
handle suffix condition.

Also update testcases to cover new changes.

(From OE-Core rev: 5dfd5ad5144708b474ef31eaa89a846c57be8ac0)

Signed-off-by: Lee Chee Yang <chee.yang.lee@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Lee Chee Yang
2021-01-29 11:51:15 +08:00
committed by Richard Purdie
parent ef208aaf0f
commit 86b42289bd
3 changed files with 39 additions and 24 deletions

View File

@@ -53,6 +53,9 @@ CVE_CHECK_PN_WHITELIST ?= ""
#
CVE_CHECK_WHITELIST ?= ""
# set to "alphabetical" for version using single alphabetical character as increament release
CVE_VERSION_SUFFIX ??= ""
python cve_save_summary_handler () {
import shutil
import datetime
@@ -210,6 +213,7 @@ def check_cves(d, patched_cves):
pn = d.getVar("PN")
real_pv = d.getVar("PV")
suffix = d.getVar("CVE_VERSION_SUFFIX")
cves_unpatched = []
# CVE_PRODUCT can contain more than one product (eg. curl/libcurl)
@@ -263,8 +267,8 @@ def check_cves(d, patched_cves):
else:
if operator_start:
try:
vulnerable_start = (operator_start == '>=' and Version(pv) >= Version(version_start))
vulnerable_start |= (operator_start == '>' and Version(pv) > Version(version_start))
vulnerable_start = (operator_start == '>=' and Version(pv,suffix) >= Version(version_start,suffix))
vulnerable_start |= (operator_start == '>' and Version(pv,suffix) > Version(version_start,suffix))
except:
bb.warn("%s: Failed to compare %s %s %s for %s" %
(product, pv, operator_start, version_start, cve))
@@ -274,8 +278,8 @@ def check_cves(d, patched_cves):
if operator_end:
try:
vulnerable_end = (operator_end == '<=' and Version(pv) <= Version(version_end) )
vulnerable_end |= (operator_end == '<' and Version(pv) < Version(version_end) )
vulnerable_end = (operator_end == '<=' and Version(pv,suffix) <= Version(version_end,suffix) )
vulnerable_end |= (operator_end == '<' and Version(pv,suffix) < Version(version_end,suffix) )
except:
bb.warn("%s: Failed to compare %s %s %s for %s" %
(product, pv, operator_end, version_end, cve))

View File

@@ -1,58 +1,60 @@
import collections
import re
import itertools
import functools
_Version = collections.namedtuple(
"_Version", ["release", "pre_l", "pre_v"]
"_Version", ["release", "patch_l", "pre_l", "pre_v"]
)
@functools.total_ordering
class Version():
_version_pattern = r"""v?(?:(?P<release>[0-9]+(?:[-\.][0-9]+)*)(?P<pre>[-_\.]?(?P<pre_l>(rc|alpha|beta|pre|preview|dev))[-_\.]?(?P<pre_v>[0-9]+)?)?)(.*)?"""
_regex = re.compile(r"^\s*" + _version_pattern + r"\s*$", re.VERBOSE | re.IGNORECASE)
def __init__(self, version):
match = self._regex.search(version)
def __init__(self, version, suffix=None):
if str(suffix) == "alphabetical":
version_pattern = r"""r?v?(?:(?P<release>[0-9]+(?:[-\.][0-9]+)*)(?P<patch>[-_\.]?(?P<patch_l>[a-z]))?(?P<pre>[-_\.]?(?P<pre_l>(rc|alpha|beta|pre|preview|dev))[-_\.]?(?P<pre_v>[0-9]+)?)?)(.*)?"""
else:
version_pattern = r"""r?v?(?:(?P<release>[0-9]+(?:[-\.][0-9]+)*)(?P<pre>[-_\.]?(?P<pre_l>(rc|alpha|beta|pre|preview|dev))[-_\.]?(?P<pre_v>[0-9]+)?)?)(.*)?"""
regex = re.compile(r"^\s*" + version_pattern + r"\s*$", re.VERBOSE | re.IGNORECASE)
match = regex.search(version)
if not match:
raise Exception("Invalid version: '{0}'".format(version))
self._version = _Version(
release=tuple(int(i) for i in match.group("release").replace("-",".").split(".")),
patch_l=match.group("patch_l") if str(suffix) == "alphabetical" and match.group("patch_l") else "",
pre_l=match.group("pre_l"),
pre_v=match.group("pre_v")
)
self._key = _cmpkey(
self._version.release,
self._version.patch_l,
self._version.pre_l,
self._version.pre_v
)
def __le__(self, other):
def __eq__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._key <= other._key
def __lt__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._key < other._key
def __ge__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._key >= other._key
return self._key == other._key
def __gt__(self, other):
if not isinstance(other, Version):
return NotImplemented
return self._key > other._key
def _cmpkey(release, pre_l, pre_v):
def _cmpkey(release, patch_l, pre_l, pre_v):
# remove leading 0
_release = tuple(
reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
)
_patch = patch_l.upper()
if pre_l is None and pre_v is None:
_pre = float('inf')
else:
_pre = float(pre_v) if pre_v else float('-inf')
return _release, _pre
return _release, _patch, _pre

View File

@@ -23,5 +23,14 @@ class CVECheck(OESelftestTestCase):
self.assertTrue( result, msg="Failed to compare version '1.0_dev' <= '1.0'")
# ignore "p1" and "p2", so these should be equal
result = Version("1.0p2") <= Version("1.0p1") and Version("1.0p2") >= Version("1.0p1")
result = Version("1.0p2") == Version("1.0p1")
self.assertTrue( result ,msg="Failed to compare version '1.0p2' to '1.0p1'")
# ignore the "b" and "r"
result = Version("1.0b") == Version("1.0r")
self.assertTrue( result ,msg="Failed to compare version '1.0b' to '1.0r'")
# consider the trailing alphabet as patched level when comparing
result = Version("1.0b","alphabetical") < Version("1.0r","alphabetical")
self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' < '1.0r'")
result = Version("1.0b","alphabetical") > Version("1.0","alphabetical")
self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' > '1.0'")