diff options
author | Ross Burton <ross.burton@intel.com> | 2019-11-18 16:46:45 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2019-11-21 23:08:20 +0000 |
commit | cd50a3111399ca79389d91455825f3e88319c8aa (patch) | |
tree | 36127a75d26a3c7f47d6c7d6c9b09f6323cbf16a /meta | |
parent | fa8f6a236fae2778b36eeface3f1f7ccec964795 (diff) | |
download | poky-cd50a3111399ca79389d91455825f3e88319c8aa.tar.gz |
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)
Signed-off-by: Ross Burton <ross.burton@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta')
-rw-r--r-- | meta/classes/cve-check.bbclass | 63 |
1 files changed, 34 insertions, 29 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass index 3326944d79..c1cbdbde7b 100644 --- a/meta/classes/cve-check.bbclass +++ b/meta/classes/cve-check.bbclass | |||
@@ -165,7 +165,6 @@ def check_cves(d, patched_cves): | |||
165 | """ | 165 | """ |
166 | Connect to the NVD database and find unpatched cves. | 166 | Connect to the NVD database and find unpatched cves. |
167 | """ | 167 | """ |
168 | import ast, csv, tempfile, subprocess, io | ||
169 | from distutils.version import LooseVersion | 168 | from distutils.version import LooseVersion |
170 | 169 | ||
171 | cves_unpatched = [] | 170 | cves_unpatched = [] |
@@ -187,68 +186,74 @@ def check_cves(d, patched_cves): | |||
187 | cve_whitelist = d.getVar("CVE_CHECK_WHITELIST").split() | 186 | cve_whitelist = d.getVar("CVE_CHECK_WHITELIST").split() |
188 | 187 | ||
189 | import sqlite3 | 188 | import sqlite3 |
190 | db_file = d.getVar("CVE_CHECK_DB_FILE") | 189 | db_file = d.expand("file:${CVE_CHECK_DB_FILE}?mode=ro") |
191 | conn = sqlite3.connect(db_file) | 190 | conn = sqlite3.connect(db_file, uri=True) |
192 | 191 | ||
192 | # For each of the known product names (e.g. curl has CPEs using curl and libcurl)... | ||
193 | for product in products: | 193 | for product in products: |
194 | c = conn.cursor() | ||
195 | if ":" in product: | 194 | if ":" in product: |
196 | vendor, product = product.split(":", 1) | 195 | vendor, product = product.split(":", 1) |
197 | c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR IS ?", (product, vendor)) | ||
198 | else: | 196 | else: |
199 | c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ?", (product,)) | 197 | vendor = "%" |
200 | 198 | ||
201 | for row in c: | 199 | # Find all relevant CVE IDs. |
202 | cve = row[0] | 200 | for cverow in conn.execute("SELECT DISTINCT ID FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR LIKE ?", (product, vendor)): |
203 | version_start = row[3] | 201 | cve = cverow[0] |
204 | operator_start = row[4] | ||
205 | version_end = row[5] | ||
206 | operator_end = row[6] | ||
207 | 202 | ||
208 | if cve in cve_whitelist: | 203 | if cve in cve_whitelist: |
209 | bb.note("%s-%s has been whitelisted for %s" % (product, pv, cve)) | 204 | bb.note("%s-%s has been whitelisted for %s" % (product, pv, cve)) |
210 | # TODO: this should be in the report as 'whitelisted' | 205 | # TODO: this should be in the report as 'whitelisted' |
211 | patched_cves.add(cve) | 206 | patched_cves.add(cve) |
207 | continue | ||
212 | elif cve in patched_cves: | 208 | elif cve in patched_cves: |
213 | bb.note("%s has been patched" % (cve)) | 209 | bb.note("%s has been patched" % (cve)) |
214 | else: | 210 | continue |
215 | to_append = False | 211 | |
212 | vulnerable = False | ||
213 | for row in conn.execute("SELECT * FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cve, product, vendor)): | ||
214 | (_, _, _, version_start, operator_start, version_end, operator_end) = row | ||
215 | #bb.debug(2, "Evaluating row " + str(row)) | ||
216 | |||
216 | if (operator_start == '=' and pv == version_start): | 217 | if (operator_start == '=' and pv == version_start): |
217 | to_append = True | 218 | vulnerable = True |
218 | else: | 219 | else: |
219 | if operator_start: | 220 | if operator_start: |
220 | try: | 221 | try: |
221 | to_append_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start)) | 222 | vulnerable_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start)) |
222 | to_append_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start)) | 223 | vulnerable_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start)) |
223 | except: | 224 | except: |
224 | bb.warn("%s: Failed to compare %s %s %s for %s" % | 225 | bb.warn("%s: Failed to compare %s %s %s for %s" % |
225 | (product, pv, operator_start, version_start, cve)) | 226 | (product, pv, operator_start, version_start, cve)) |
226 | to_append_start = False | 227 | vulnerable_start = False |
227 | else: | 228 | else: |
228 | to_append_start = False | 229 | vulnerable_start = False |
229 | 230 | ||
230 | if operator_end: | 231 | if operator_end: |
231 | try: | 232 | try: |
232 | to_append_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end)) | 233 | vulnerable_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end)) |
233 | to_append_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end)) | 234 | vulnerable_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end)) |
234 | except: | 235 | except: |
235 | bb.warn("%s: Failed to compare %s %s %s for %s" % | 236 | bb.warn("%s: Failed to compare %s %s %s for %s" % |
236 | (product, pv, operator_end, version_end, cve)) | 237 | (product, pv, operator_end, version_end, cve)) |
237 | to_append_end = False | 238 | vulnerable_end = False |
238 | else: | 239 | else: |
239 | to_append_end = False | 240 | vulnerable_end = False |
240 | 241 | ||
241 | if operator_start and operator_end: | 242 | if operator_start and operator_end: |
242 | to_append = to_append_start and to_append_end | 243 | vulnerable = vulnerable_start and vulnerable_end |
243 | else: | 244 | else: |
244 | to_append = to_append_start or to_append_end | 245 | vulnerable = vulnerable_start or vulnerable_end |
245 | 246 | ||
246 | if to_append: | 247 | if vulnerable: |
247 | bb.note("%s-%s is vulnerable to %s" % (product, pv, cve)) | 248 | bb.note("%s-%s is vulnerable to %s" % (product, pv, cve)) |
248 | cves_unpatched.append(cve) | 249 | cves_unpatched.append(cve) |
249 | else: | 250 | break |
250 | bb.note("%s-%s is not vulnerable to %s" % (product, pv, cve)) | 251 | |
251 | patched_cves.add(cve) | 252 | if not vulnerable: |
253 | bb.note("%s-%s is not vulnerable to %s" % (product, pv, cve)) | ||
254 | # TODO: not patched but not vulnerable | ||
255 | patched_cves.add(cve) | ||
256 | |||
252 | conn.close() | 257 | conn.close() |
253 | 258 | ||
254 | return (list(patched_cves), cves_unpatched) | 259 | return (list(patched_cves), cves_unpatched) |