cve-report.bbclass: add class

Implements "report_cve" and "report_patched" tasks.

"report_patched" prepares image manifest with patched CVE info.
"report_cve" runs cvert-* scripts to generate kernel and package CVE reports.

You can configure it to set report filenames, reuse NVD feeds,
stop after manifest generation and ignore specific classes,
like native, nativesdk, etc.

Signed-off-by: grygorii tertychnyi <gtertych@cisco.com>
Signed-off-by: Armin Kuster <akuster808@gmail.com>
This commit is contained in:
Andrii Bordunov via Openembedded-core
2018-10-10 19:25:11 +03:00
committed by Armin Kuster
parent 0fc645a946
commit 7c08edc7f5

216
classes/cve-report.bbclass Normal file
View File

@@ -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
}