summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/classes/cve-report.bbclass216
1 files changed, 216 insertions, 0 deletions
diff --git a/meta/classes/cve-report.bbclass b/meta/classes/cve-report.bbclass
new file mode 100644
index 0000000000..35d58d0821
--- /dev/null
+++ b/meta/classes/cve-report.bbclass
@@ -0,0 +1,216 @@
+# Class to inherit when you want to generate a CVE reports.
+#
+# Generates package list file and package CVE report.
+#
+# Example:
+# echo 'INHERIT += "cve-report"' >> conf/local.conf
+# bitbake -c report_cve core-image-minimal
+#
+# Variables to be passed to "cvert-*" scripts:
+#
+# CVE_REPORT_MODE[foss]
+# Path to the CVE FOSS report to be generated.
+#
+# CVE_REPORT_MODE[restore]
+# Path to the CVE dump data file.
+#
+# E.g. for multiple MACHINEs:
+# (1) generate CVE dump:
+# cvert-update --store /path/to/cvedump $TEMP/nvdfeed
+# (2) for mach in $(get_machine_list); do
+# (source oe-init-build-env "build-$mach";
+# echo 'CVE_REPORT_MODE[restore] = "/path/to/cvedump"' >> conf/local.conf;
+# echo 'CVE_REPORT_MODE[foss] = "/path/to/report-foss-'${mach}'"' >> conf/local.conf;
+# MACHINE=$mach bitbake -c report_cve core-image-minimal)
+# done
+#
+# CVE_REPORT_MODE[offline]
+# Either "0" or "1". Offline mode ("--offline" parameter for cvert-* scripts).
+#
+# CVE_REPORT_MODE[feeddir]
+# Path to the NVD feed directory.
+#
+# CVE_REPORT_MODE[packagelist]
+# Path to the package list file to be generated.
+#
+# CVE_REPORT_MODE[packageonly]
+# Either "0" or "1". Generate package list file, then stop.
+#
+# CVE_REPORT_MODE[blacklist]
+# Ignore specific class.
+#
+
+CVE_REPORT_MODE[foss] ?= "${LOG_DIR}/cvert/report-foss.txt"
+CVE_REPORT_MODE[offline] ?= "0"
+CVE_REPORT_MODE[feeddir] ?= "${LOG_DIR}/nvdfeeds"
+CVE_REPORT_MODE[packagelist] ?= "${LOG_DIR}/cvert/package.lst"
+CVE_REPORT_MODE[packageonly] ?= "0"
+CVE_REPORT_MODE[blacklist] ?= "native,nativesdk,cross,crosssdk,cross-canadian,packagegroup,image"
+
+CVE_PRODUCT ??= "${BPN}"
+CVE_VERSION ??= "${PV}"
+
+addhandler generate_report_handler
+generate_report_handler[eventmask] = "bb.event.BuildCompleted"
+
+def cvert_update(d):
+ """Update NVD storage and prepare CVE dump"""
+
+ import tempfile
+ import subprocess
+
+ bb.utils.export_proxies(d)
+
+ dump = os.path.join(d.getVar("LOG_DIR"), "cvedump")
+
+ bb.note("Updating CVE database: %s" % dump)
+
+ cmd = [
+ "cvert-update",
+ "--store", dump,
+ "--debug",
+ d.getVarFlag("CVE_REPORT_MODE", "feeddir")
+ ]
+
+ if d.getVarFlag("CVE_REPORT_MODE", "offline") != "0":
+ cmd.append("--offline")
+
+ try:
+ bb.debug(2, "Call '%s'" % " ".join(cmd))
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
+ bb.debug(2, "Output: %s" % output)
+ except subprocess.CalledProcessError as e:
+ bb.error("Failed to run cvert-update: '%s'\n%s: %s" % (" ".join(cmd), e, e.output))
+
+ return dump
+
+# copied from cve-check.bbclass
+def get_patches_cves(d):
+ """Get patches that solve CVEs using the "CVE: " tag"""
+
+ import re
+
+ pn = d.getVar("PN")
+ cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")
+
+ # Matches last CVE-1234-211432 in the file name, also if written
+ # with small letters. Not supporting multiple CVE id's in a single
+ # file name.
+ cve_file_name_match = re.compile(".*([Cc][Vv][Ee]\-\d{4}\-\d+)")
+
+ patched_cves = set()
+ bb.debug(2, "Looking for patches that solves CVEs for %s" % pn)
+ for url in src_patches(d):
+ patch_file = bb.fetch.decodeurl(url)[2]
+
+ # Check patch file name for CVE ID
+ fname_match = cve_file_name_match.search(patch_file)
+ if fname_match:
+ cve = fname_match.group(1).upper()
+ patched_cves.add(cve)
+ bb.debug(2, "Found CVE %s from patch file name %s" % (cve, patch_file))
+
+ with open(patch_file, "r", encoding="utf-8") as f:
+ try:
+ patch_text = f.read()
+ except UnicodeDecodeError:
+ bb.debug(1, "Failed to read patch %s using UTF-8 encoding"
+ " trying with iso8859-1" % patch_file)
+ f.close()
+ with open(patch_file, "r", encoding="iso8859-1") as f:
+ patch_text = f.read()
+
+ # Search for one or more "CVE: " lines
+ text_match = False
+ for match in cve_match.finditer(patch_text):
+ # Get only the CVEs without the "CVE: " tag
+ cves = patch_text[match.start()+5:match.end()]
+ for cve in cves.split():
+ bb.debug(2, "Patch %s solves %s" % (patch_file, cve))
+ patched_cves.add(cve)
+ text_match = True
+
+ if not fname_match and not text_match:
+ bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)
+
+ return patched_cves
+
+
+python generate_report_handler() {
+ if d.getVarFlag("CVE_REPORT_MODE", "packageonly") != "0":
+ return
+
+ import subprocess
+
+ restore = d.getVarFlag("CVE_REPORT_MODE", "restore")
+
+ if not restore:
+ restore = cvert_update(d)
+
+ if os.path.exists(d.getVarFlag("CVE_REPORT_MODE", "packagelist")):
+ report_foss = d.getVarFlag("CVE_REPORT_MODE", "foss")
+
+ bb.note("Generating CVE FOSS report: %s" % report_foss)
+
+ cmd = [
+ "cvert-foss",
+ "--restore", restore,
+ "--output", report_foss,
+ d.getVarFlag("CVE_REPORT_MODE", "packagelist")
+ ]
+
+ try:
+ bb.debug(2, "Call '%s'" % " ".join(cmd))
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode()
+ bb.debug(2, "Output: %s" % output)
+ except subprocess.CalledProcessError as e:
+ bb.error("Failed to run cvert-foss: '%s'\n%s: %s" % (" ".join(cmd), e, e.output))
+}
+
+addhandler build_started
+build_started[eventmask] = "bb.event.BuildStarted"
+
+python build_started() {
+ packagelist = d.getVarFlag("CVE_REPORT_MODE", "packagelist")
+ bb.utils.remove(packagelist)
+ bb.utils.mkdirhier(os.path.dirname(packagelist))
+ bb.note("Package list: ", packagelist)
+}
+
+addtask do_report_cve after do_report_patched
+
+do_report_cve[recrdeptask] = "do_report_cve do_report_patched"
+do_report_cve[recideptask] = "do_${BB_DEFAULT_TASK}"
+do_report_cve[nostamp] = "1"
+
+do_report_cve() {
+ :
+}
+
+python do_report_patched() {
+ if not d.getVar("SRC_URI"):
+ return
+
+ cve_product = d.getVar("CVE_PRODUCT")
+
+ if not cve_product:
+ return
+
+ cve_version = d.getVar("CVE_VERSION")
+ patched_cves = get_patches_cves(d)
+
+ with open(d.getVarFlag("CVE_REPORT_MODE", "packagelist"), "a") as fil:
+ fil.write("%s,%s,%s\n" % (cve_product, cve_version, " ".join(patched_cves)))
+ bb.debug(2, "Append to package-list: '%s,%s,%s'" % (cve_product, cve_version, " ".join(patched_cves)))
+}
+
+addtask do_report_patched after do_unpack before do_build
+
+do_report_patched[nostamp] = "1"
+
+python() {
+ for b in d.getVarFlag("CVE_REPORT_MODE", "blacklist").split(","):
+ if bb.data.inherits_class(b, d):
+ bb.build.deltask("do_report_patched", d)
+ break
+}