cve-check: rewrite look to fix false negatives

A previous optimisation was premature and resulted in false-negatives in the report.

Rewrite the checking algorithm to first get the list of potential CVEs by
vendor:product, then iterate through every matching CPE for that CVE to
determine if the bounds match or not.  By doing this in two stages we can know
if we've checked every CPE, instead of accidentally breaking out of the scan too
early.

(From OE-Core rev: d61aff9e22704ad69df1f7ab0f8784f4e7cc0c69)

(From OE-Core rev: 541dc24d974d3e22c45a650c34298eebc45121e8)

Signed-off-by: Ross Burton <ross.burton@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Ross Burton
2019-12-08 20:35:55 +02:00
committed by Richard Purdie
parent 3091533130
commit 1a09e2a3cf

View File

@@ -165,7 +165,6 @@ def check_cves(d, patched_cves):
"""
Connect to the NVD database and find unpatched cves.
"""
import ast, csv, tempfile, subprocess, io
from distutils.version import LooseVersion
cves_unpatched = []
@@ -187,68 +186,74 @@ def check_cves(d, patched_cves):
cve_whitelist = d.getVar("CVE_CHECK_WHITELIST").split()
import sqlite3
db_file = d.getVar("CVE_CHECK_DB_FILE")
conn = sqlite3.connect(db_file)
db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro")
conn = sqlite3.connect(db_file, uri=True)
# For each of the known product names (e.g. curl has CPEs using curl and libcurl)...
for product in products:
c = conn.cursor()
if ":" in product:
vendor, product = product.split(":", 1)
c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR IS ?", (product, vendor))
else:
c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ?", (product,))
vendor = "%"
for row in c:
cve = row[0]
version_start = row[3]
operator_start = row[4]
version_end = row[5]
operator_end = row[6]
# Find all relevant CVE IDs.
for cverow in conn.execute("SELECT DISTINCT ID FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR LIKE ?", (product, vendor)):
cve = cverow[0]
if cve in cve_whitelist:
bb.note("%s-%s has been whitelisted for %s" % (product, pv, cve))
# TODO: this should be in the report as 'whitelisted'
patched_cves.add(cve)
continue
elif cve in patched_cves:
bb.note("%s has been patched" % (cve))
else:
to_append = False
continue
vulnerable = False
for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)):
(_, _, _, version_start, operator_start, version_end, operator_end) = row
#bb.debug(2, "Evaluating row " + str(row))
if (operator_start == '=' and pv == version_start):
to_append = True
vulnerable = True
else:
if operator_start:
try:
to_append_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start))
to_append_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start))
vulnerable_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start))
vulnerable_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start))
except:
bb.warn("%s: Failed to compare %s %s %s for %s" %
(product, pv, operator_start, version_start, cve))
to_append_start = False
vulnerable_start = False
else:
to_append_start = False
vulnerable_start = False
if operator_end:
try:
to_append_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end))
to_append_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end))
vulnerable_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end))
vulnerable_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end))
except:
bb.warn("%s: Failed to compare %s %s %s for %s" %
(product, pv, operator_end, version_end, cve))
to_append_end = False
vulnerable_end = False
else:
to_append_end = False
vulnerable_end = False
if operator_start and operator_end:
to_append = to_append_start and to_append_end
vulnerable = vulnerable_start and vulnerable_end
else:
to_append = to_append_start or to_append_end
vulnerable = vulnerable_start or vulnerable_end
if to_append:
if vulnerable:
bb.note("%s-%s is vulnerable to %s" % (product, pv, cve))
cves_unpatched.append(cve)
else:
bb.note("%s-%s is not vulnerable to %s" % (product, pv, cve))
patched_cves.add(cve)
break
if not vulnerable:
bb.note("%s-%s is not vulnerable to %s" % (product, pv, cve))
# TODO: not patched but not vulnerable
patched_cves.add(cve)
conn.close()
return (list(patched_cves), cves_unpatched)