summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLee Chee Yang <chee.yang.lee@intel.com>2021-01-22 18:07:19 +0800
committerRichard Purdie <richard.purdie@linuxfoundation.org>2021-02-10 23:55:53 +0000
commitf829419105c8a85dd403ab61d70ce730f5bf9103 (patch)
treef63af4034fd0f12c50b625072ed9ccafaf6a3b24
parent4b4d1dac11ad3d53df50a4dd220a3a9b0219ab34 (diff)
downloadpoky-f829419105c8a85dd403ab61d70ce730f5bf9103.tar.gz
cve-check: replace Looseversion with custom version class
The way distutils.version.LooseVersion compare version are tricky, it treat all these ( "1.0-beta2", "1.0-rc1", "1.0A", "1.0p2" and "1.0pre1") as greater version than "1.0". This might be right for "1.0A" and "1.0p1" but not for the rest, also these version could be confusing, the "p" in "1.0p1" can be "pre" or "patched" version or even other meaning. Replace Looseversion with custom class, it uses regex to capture common version format like "1.1.1" or tag format using date like "2020-12-12" as release section, check for following known string/tags ( beta, rc, pre, dev, alpha, preview) as pre-release section, any other trailing characters are difficult to understand/define so ignore them. Compare release section and pre-release section saperately. included selftest for the version class. [YOCTO#14127] (From OE-Core rev: 294baea424472341d2ec880f13699076315d8274) Signed-off-by: Lee Chee Yang <chee.yang.lee@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org> (cherry picked from commit 6ced85e9ddd3569240f1e8b82130d1ac0fffbc40) Signed-off-by: Steve Sakoman <steve@sakoman.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/classes/cve-check.bbclass10
-rw-r--r--meta/lib/oe/cve_check.py58
-rw-r--r--meta/lib/oeqa/selftest/cases/cve_check.py27
3 files changed, 90 insertions, 5 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index 669da6c8e9..93af667544 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -203,7 +203,7 @@ def check_cves(d, patched_cves):
203 """ 203 """
204 Connect to the NVD database and find unpatched cves. 204 Connect to the NVD database and find unpatched cves.
205 """ 205 """
206 from distutils.version import LooseVersion 206 from oe.cve_check import Version
207 207
208 pn = d.getVar("PN") 208 pn = d.getVar("PN")
209 real_pv = d.getVar("PV") 209 real_pv = d.getVar("PV")
@@ -260,8 +260,8 @@ def check_cves(d, patched_cves):
260 else: 260 else:
261 if operator_start: 261 if operator_start:
262 try: 262 try:
263 vulnerable_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start)) 263 vulnerable_start = (operator_start == '>=' and Version(pv) >= Version(version_start))
264 vulnerable_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start)) 264 vulnerable_start |= (operator_start == '>' and Version(pv) > Version(version_start))
265 except: 265 except:
266 bb.warn("%s: Failed to compare %s %s %s for %s" % 266 bb.warn("%s: Failed to compare %s %s %s for %s" %
267 (product, pv, operator_start, version_start, cve)) 267 (product, pv, operator_start, version_start, cve))
@@ -271,8 +271,8 @@ def check_cves(d, patched_cves):
271 271
272 if operator_end: 272 if operator_end:
273 try: 273 try:
274 vulnerable_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end)) 274 vulnerable_end = (operator_end == '<=' and Version(pv) <= Version(version_end) )
275 vulnerable_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end)) 275 vulnerable_end |= (operator_end == '<' and Version(pv) < Version(version_end) )
276 except: 276 except:
277 bb.warn("%s: Failed to compare %s %s %s for %s" % 277 bb.warn("%s: Failed to compare %s %s %s for %s" %
278 (product, pv, operator_end, version_end, cve)) 278 (product, pv, operator_end, version_end, cve))
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py
new file mode 100644
index 0000000000..ec48a3f829
--- /dev/null
+++ b/meta/lib/oe/cve_check.py
@@ -0,0 +1,58 @@
1import collections
2import re
3import itertools
4
5_Version = collections.namedtuple(
6 "_Version", ["release", "pre_l", "pre_v"]
7)
8
9class Version():
10 _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]+)?)?)(.*)?"""
11 _regex = re.compile(r"^\s*" + _version_pattern + r"\s*$", re.VERBOSE | re.IGNORECASE)
12 def __init__(self, version):
13 match = self._regex.search(version)
14 if not match:
15 raise Exception("Invalid version: '{0}'".format(version))
16
17 self._version = _Version(
18 release=tuple(int(i) for i in match.group("release").replace("-",".").split(".")),
19 pre_l=match.group("pre_l"),
20 pre_v=match.group("pre_v")
21 )
22
23 self._key = _cmpkey(
24 self._version.release,
25 self._version.pre_l,
26 self._version.pre_v
27 )
28
29 def __le__(self, other):
30 if not isinstance(other, Version):
31 return NotImplemented
32 return self._key <= other._key
33
34 def __lt__(self, other):
35 if not isinstance(other, Version):
36 return NotImplemented
37 return self._key < other._key
38
39 def __ge__(self, other):
40 if not isinstance(other, Version):
41 return NotImplemented
42 return self._key >= other._key
43
44 def __gt__(self, other):
45 if not isinstance(other, Version):
46 return NotImplemented
47 return self._key > other._key
48
49def _cmpkey(release, pre_l, pre_v):
50 # remove leading 0
51 _release = tuple(
52 reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
53 )
54 if pre_l is None and pre_v is None:
55 _pre = float('inf')
56 else:
57 _pre = float(pre_v) if pre_v else float('-inf')
58 return _release, _pre
diff --git a/meta/lib/oeqa/selftest/cases/cve_check.py b/meta/lib/oeqa/selftest/cases/cve_check.py
new file mode 100644
index 0000000000..35e2b29a9a
--- /dev/null
+++ b/meta/lib/oeqa/selftest/cases/cve_check.py
@@ -0,0 +1,27 @@
1from oe.cve_check import Version
2from oeqa.selftest.case import OESelftestTestCase
3
4class CVECheck(OESelftestTestCase):
5
6 def test_version_compare(self):
7 result = Version("100") > Version("99")
8 self.assertTrue( result, msg="Failed to compare version '100' > '99'")
9 result = Version("2.3.1") > Version("2.2.3")
10 self.assertTrue( result, msg="Failed to compare version '2.3.1' > '2.2.3'")
11 result = Version("2021-01-21") > Version("2020-12-25")
12 self.assertTrue( result, msg="Failed to compare version '2021-01-21' > '2020-12-25'")
13 result = Version("1.2-20200910") < Version("1.2-20200920")
14 self.assertTrue( result, msg="Failed to compare version '1.2-20200910' < '1.2-20200920'")
15
16 result = Version("1.0") >= Version("1.0beta")
17 self.assertTrue( result, msg="Failed to compare version '1.0' >= '1.0beta'")
18 result = Version("1.0-rc2") > Version("1.0-rc1")
19 self.assertTrue( result, msg="Failed to compare version '1.0-rc2' > '1.0-rc1'")
20 result = Version("1.0.alpha1") < Version("1.0")
21 self.assertTrue( result, msg="Failed to compare version '1.0.alpha1' < '1.0'")
22 result = Version("1.0_dev") <= Version("1.0")
23 self.assertTrue( result, msg="Failed to compare version '1.0_dev' <= '1.0'")
24
25 # ignore "p1" and "p2", so these should be equal
26 result = Version("1.0p2") <= Version("1.0p1") and Version("1.0p2") >= Version("1.0p1")
27 self.assertTrue( result ,msg="Failed to compare version '1.0p2' to '1.0p1'")