summaryrefslogtreecommitdiffstats
path: root/meta/classes/cve-check.bbclass
diff options
context:
space:
mode:
authorRoss Burton <ross.burton@intel.com>2019-09-25 12:11:02 +0100
committerRichard Purdie <richard.purdie@linuxfoundation.org>2019-09-30 16:44:42 +0100
commit411624fa506a74eba7f1b1e159bd1a2286aa6686 (patch)
tree7ce4de536965ab503c2473b916c8b97b20f273ee /meta/classes/cve-check.bbclass
parent5be20f94d629b8f5ebb34254b98ab6913e54b4e4 (diff)
downloadpoky-411624fa506a74eba7f1b1e159bd1a2286aa6686.tar.gz
cve-check: backport rewrite from master
As detailed at [1] the XML feeds provided by NIST are being discontinued on October 9th 2019. As cve-check-tool uses these feeds, cve-check.bbclass will be inoperable after this date. To ensure that cve-check continues working, backport the following commits from master to move away from the unmaintained cve-check-tool to our own Python code that fetches the JSON: 546d14135c5 cve-update-db: New recipe to update CVE database bc144b028f6 cve-check: Remove dependency to cve-check-tool-native 7f62a20b32a cve-check: Manage CVE_PRODUCT with more than one name 3bf63bc6084 cve-check: Consider CVE that affects versions with less than operator c0eabd30d7b cve-update-db: Use std library instead of urllib3 27eb839ee65 cve-check: be idiomatic 09be21f4d17 cve-update-db: Manage proxy if needed. 975793e3825 cve-update-db: do_populate_cve_db depends on do_fetch 0325dd72714 cve-update-db: Catch request.urlopen errors. 4078da92b49 cve-check: Depends on cve-update-db-native f7676e9a38d cve-update-db: Use NVD CPE data to populate PRODUCTS table bc0195be1b1 cve-check: Update unpatched CVE matching c807c2a6409 cve-update-db-native: Skip recipe when cve-check class is not loaded. 07bb8b25e17 cve-check: remove redundant readline CVE whitelisting 5388ed6d137 cve-check-tool: remove 270ac00cb43 cve-check.bbclass: initialize to_append e6bf9000987 cve-check: allow comparison of Vendor as well as Product 91770338f76 cve-update-db-native: use SQL placeholders instead of format strings 7069302a4cc cve-check: Replace CVE_CHECK_CVE_WHITELIST by CVE_CHECK_WHITELIST 78de2cb39d7 cve-update-db-native: Remove hash column from database. 4b301030cf9 cve-update-db-native: use os.path.join instead of + f0d822fad2a cve-update-db: actually inherit native b309840b6aa cve-update-db-native: use executemany() to optimise CPE insertion bb4e53af33d cve-update-db-native: improve metadata parsing 94227459792 cve-update-db-native: clean up JSON fetching 95438d52b73 cve-update-db-native: fix https proxy issues 1f9a963b9ff glibc: exclude child recipes from CVE scanning [1] https://nvd.nist.gov/General/News/XML-Vulnerability-Feed-Retirement (From OE-Core rev: 8c87e78547c598cada1bce92e7b25d85b994e2eb) Signed-off-by: Ross Burton <ross.burton@intel.com> Signed-off-by: Armin Kuster <akuster808@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/classes/cve-check.bbclass')
-rw-r--r--meta/classes/cve-check.bbclass142
1 files changed, 87 insertions, 55 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index 743bc08a4f..c00d2910be 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -26,7 +26,7 @@ CVE_PRODUCT ??= "${BPN}"
26CVE_VERSION ??= "${PV}" 26CVE_VERSION ??= "${PV}"
27 27
28CVE_CHECK_DB_DIR ?= "${DL_DIR}/CVE_CHECK" 28CVE_CHECK_DB_DIR ?= "${DL_DIR}/CVE_CHECK"
29CVE_CHECK_DB_FILE ?= "${CVE_CHECK_DB_DIR}/nvd.db" 29CVE_CHECK_DB_FILE ?= "${CVE_CHECK_DB_DIR}/nvdcve_1.0.db"
30 30
31CVE_CHECK_LOG ?= "${T}/cve.log" 31CVE_CHECK_LOG ?= "${T}/cve.log"
32CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check" 32CVE_CHECK_TMP_FILE ?= "${TMPDIR}/cve_check"
@@ -37,32 +37,33 @@ CVE_CHECK_COPY_FILES ??= "1"
37CVE_CHECK_CREATE_MANIFEST ??= "1" 37CVE_CHECK_CREATE_MANIFEST ??= "1"
38 38
39# Whitelist for packages (PN) 39# Whitelist for packages (PN)
40CVE_CHECK_PN_WHITELIST = "\ 40CVE_CHECK_PN_WHITELIST ?= ""
41 glibc-locale \
42"
43 41
44# Whitelist for CVE and version of package 42# Whitelist for CVE. If a CVE is found, then it is considered patched.
45CVE_CHECK_CVE_WHITELIST = "{\ 43# The value is a string containing space separated CVE values:
46 'CVE-2014-2524': ('6.3','5.2',), \ 44#
47}" 45# CVE_CHECK_WHITELIST = 'CVE-2014-2524 CVE-2018-1234'
46#
47CVE_CHECK_WHITELIST ?= ""
48 48
49python do_cve_check () { 49python do_cve_check () {
50 """ 50 """
51 Check recipe for patched and unpatched CVEs 51 Check recipe for patched and unpatched CVEs
52 """ 52 """
53 53
54 if os.path.exists(d.getVar("CVE_CHECK_TMP_FILE")): 54 if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
55 patched_cves = get_patches_cves(d) 55 patched_cves = get_patches_cves(d)
56 patched, unpatched = check_cves(d, patched_cves) 56 patched, unpatched = check_cves(d, patched_cves)
57 if patched or unpatched: 57 if patched or unpatched:
58 cve_data = get_cve_info(d, patched + unpatched) 58 cve_data = get_cve_info(d, patched + unpatched)
59 cve_write_data(d, patched, unpatched, cve_data) 59 cve_write_data(d, patched, unpatched, cve_data)
60 else: 60 else:
61 bb.note("Failed to update CVE database, skipping CVE check") 61 bb.note("No CVE database found, skipping CVE check")
62
62} 63}
63 64
64addtask cve_check after do_unpack before do_build 65addtask cve_check after do_unpack before do_build
65do_cve_check[depends] = "cve-check-tool-native:do_populate_sysroot cve-check-tool-native:do_populate_cve_db" 66do_cve_check[depends] = "cve-update-db-native:do_populate_cve_db"
66do_cve_check[nostamp] = "1" 67do_cve_check[nostamp] = "1"
67 68
68python cve_check_cleanup () { 69python cve_check_cleanup () {
@@ -163,65 +164,94 @@ def get_patches_cves(d):
163 164
164def check_cves(d, patched_cves): 165def check_cves(d, patched_cves):
165 """ 166 """
166 Run cve-check-tool looking for patched and unpatched CVEs. 167 Connect to the NVD database and find unpatched cves.
167 """ 168 """
168
169 import ast, csv, tempfile, subprocess, io 169 import ast, csv, tempfile, subprocess, io
170 from distutils.version import LooseVersion
170 171
171 cves_patched = []
172 cves_unpatched = [] 172 cves_unpatched = []
173 bpn = d.getVar("CVE_PRODUCT") 173 # CVE_PRODUCT can contain more than one product (eg. curl/libcurl)
174 products = d.getVar("CVE_PRODUCT").split()
174 # If this has been unset then we're not scanning for CVEs here (for example, image recipes) 175 # If this has been unset then we're not scanning for CVEs here (for example, image recipes)
175 if not bpn: 176 if not products:
176 return ([], []) 177 return ([], [])
177 pv = d.getVar("CVE_VERSION").split("+git")[0] 178 pv = d.getVar("CVE_VERSION").split("+git")[0]
178 cves = " ".join(patched_cves)
179 cve_db_dir = d.getVar("CVE_CHECK_DB_DIR")
180 cve_whitelist = ast.literal_eval(d.getVar("CVE_CHECK_CVE_WHITELIST"))
181 cve_cmd = "cve-check-tool"
182 cmd = [cve_cmd, "--no-html", "--skip-update", "--csv", "--not-affected", "-t", "faux", "-d", cve_db_dir]
183 179
184 # If the recipe has been whitlisted we return empty lists 180 # If the recipe has been whitlisted we return empty lists
185 if d.getVar("PN") in d.getVar("CVE_CHECK_PN_WHITELIST").split(): 181 if d.getVar("PN") in d.getVar("CVE_CHECK_PN_WHITELIST").split():
186 bb.note("Recipe has been whitelisted, skipping check") 182 bb.note("Recipe has been whitelisted, skipping check")
187 return ([], []) 183 return ([], [])
188 184
189 try: 185 old_cve_whitelist = d.getVar("CVE_CHECK_CVE_WHITELIST")
190 # Write the faux CSV file to be used with cve-check-tool 186 if old_cve_whitelist:
191 fd, faux = tempfile.mkstemp(prefix="cve-faux-") 187 bb.warn("CVE_CHECK_CVE_WHITELIST is deprecated, please use CVE_CHECK_WHITELIST.")
192 with os.fdopen(fd, "w") as f: 188 cve_whitelist = d.getVar("CVE_CHECK_WHITELIST").split()
193 for pn in bpn.split(): 189
194 f.write("%s,%s,%s,\n" % (pn, pv, cves)) 190 import sqlite3
195 cmd.append(faux) 191 db_file = d.getVar("CVE_CHECK_DB_FILE")
196 192 conn = sqlite3.connect(db_file)
197 output = subprocess.check_output(cmd).decode("utf-8") 193
198 bb.debug(2, "Output of command %s:\n%s" % ("\n".join(cmd), output)) 194 for product in products:
199 except subprocess.CalledProcessError as e: 195 c = conn.cursor()
200 bb.warn("Couldn't check for CVEs: %s (output %s)" % (e, e.output)) 196 if ":" in product:
201 finally: 197 vendor, product = product.split(":", 1)
202 os.remove(faux) 198 c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ? AND VENDOR IS ?", (product, vendor))
203 199 else:
204 for row in csv.reader(io.StringIO(output)): 200 c.execute("SELECT * FROM PRODUCTS WHERE PRODUCT IS ?", (product,))
205 # Third row has the unpatched CVEs 201
206 if row[2]: 202 for row in c:
207 for cve in row[2].split(): 203 cve = row[0]
208 # Skip if the CVE has been whitlisted for the current version 204 version_start = row[3]
209 if pv in cve_whitelist.get(cve,[]): 205 operator_start = row[4]
210 bb.note("%s-%s has been whitelisted for %s" % (bpn, pv, cve)) 206 version_end = row[5]
207 operator_end = row[6]
208
209 if cve in cve_whitelist:
210 bb.note("%s-%s has been whitelisted for %s" % (product, pv, cve))
211 elif cve in patched_cves:
212 bb.note("%s has been patched" % (cve))
213 else:
214 to_append = False
215 if (operator_start == '=' and pv == version_start):
216 cves_unpatched.append(cve)
211 else: 217 else:
218 if operator_start:
219 try:
220 to_append_start = (operator_start == '>=' and LooseVersion(pv) >= LooseVersion(version_start))
221 to_append_start |= (operator_start == '>' and LooseVersion(pv) > LooseVersion(version_start))
222 except:
223 bb.note("%s: Failed to compare %s %s %s for %s" %
224 (product, pv, operator_start, version_start, cve))
225 to_append_start = False
226 else:
227 to_append_start = False
228
229 if operator_end:
230 try:
231 to_append_end = (operator_end == '<=' and LooseVersion(pv) <= LooseVersion(version_end))
232 to_append_end |= (operator_end == '<' and LooseVersion(pv) < LooseVersion(version_end))
233 except:
234 bb.note("%s: Failed to compare %s %s %s for %s" %
235 (product, pv, operator_end, version_end, cve))
236 to_append_end = False
237 else:
238 to_append_end = False
239
240 if operator_start and operator_end:
241 to_append = to_append_start and to_append_end
242 else:
243 to_append = to_append_start or to_append_end
244
245 if to_append:
212 cves_unpatched.append(cve) 246 cves_unpatched.append(cve)
213 bb.debug(2, "%s-%s is not patched for %s" % (bpn, pv, cve)) 247 bb.debug(2, "%s-%s is not patched for %s" % (product, pv, cve))
214 # Fourth row has patched CVEs 248 conn.close()
215 if row[3]:
216 for cve in row[3].split():
217 cves_patched.append(cve)
218 bb.debug(2, "%s-%s is patched for %s" % (bpn, pv, cve))
219 249
220 return (cves_patched, cves_unpatched) 250 return (list(patched_cves), cves_unpatched)
221 251
222def get_cve_info(d, cves): 252def get_cve_info(d, cves):
223 """ 253 """
224 Get CVE information from the database used by cve-check-tool. 254 Get CVE information from the database.
225 255
226 Unfortunately the only way to get CVE info is set the output to 256 Unfortunately the only way to get CVE info is set the output to
227 html (hard to parse) or query directly the database. 257 html (hard to parse) or query directly the database.
@@ -241,9 +271,10 @@ def get_cve_info(d, cves):
241 for row in cur.execute(query, tuple(cves)): 271 for row in cur.execute(query, tuple(cves)):
242 cve_data[row[0]] = {} 272 cve_data[row[0]] = {}
243 cve_data[row[0]]["summary"] = row[1] 273 cve_data[row[0]]["summary"] = row[1]
244 cve_data[row[0]]["score"] = row[2] 274 cve_data[row[0]]["scorev2"] = row[2]
245 cve_data[row[0]]["modified"] = row[3] 275 cve_data[row[0]]["scorev3"] = row[3]
246 cve_data[row[0]]["vector"] = row[4] 276 cve_data[row[0]]["modified"] = row[4]
277 cve_data[row[0]]["vector"] = row[5]
247 conn.close() 278 conn.close()
248 279
249 return cve_data 280 return cve_data
@@ -270,7 +301,8 @@ def cve_write_data(d, patched, unpatched, cve_data):
270 unpatched_cves.append(cve) 301 unpatched_cves.append(cve)
271 write_string += "CVE STATUS: Unpatched\n" 302 write_string += "CVE STATUS: Unpatched\n"
272 write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"] 303 write_string += "CVE SUMMARY: %s\n" % cve_data[cve]["summary"]
273 write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["score"] 304 write_string += "CVSS v2 BASE SCORE: %s\n" % cve_data[cve]["scorev2"]
305 write_string += "CVSS v3 BASE SCORE: %s\n" % cve_data[cve]["scorev3"]
274 write_string += "VECTOR: %s\n" % cve_data[cve]["vector"] 306 write_string += "VECTOR: %s\n" % cve_data[cve]["vector"]
275 write_string += "MORE INFORMATION: %s%s\n\n" % (nvd_link, cve) 307 write_string += "MORE INFORMATION: %s%s\n\n" % (nvd_link, cve)
276 308