mirror of
git://git.yoctoproject.org/poky.git
synced 2025-07-19 21:09:03 +02:00

Rootfs VEX file is created by gathering files from CVE_CHECK_DIR (deploy directory), however recipes generate the files only in CVE_CHECK_DIR (log directory). This make the rootfs VEX be always empty without any message. The code is copied from cve_check class, which writes to both, so let keep them aligned and make also vex write both files. Also add a warning for case that a cve file would be still missing. (From OE-Core rev: ee6541d0940c65685aaafd7d41a59a9406392e7d) Signed-off-by: Peter Marko <peter.marko@siemens.com> Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
304 lines
10 KiB
Plaintext
304 lines
10 KiB
Plaintext
#
|
|
# Copyright OpenEmbedded Contributors
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
|
|
# This class is used to generate metadata needed by external
|
|
# tools to check for vulnerabilities, for example CVEs.
|
|
#
|
|
# In order to use this class just inherit the class in the
|
|
# local.conf file and it will add the generate_vex task for
|
|
# every recipe. If an image is build it will generate a report
|
|
# in DEPLOY_DIR_IMAGE for all the packages used, it will also
|
|
# generate a file for all recipes used in the build.
|
|
#
|
|
# Variables use CVE_CHECK prefix to keep compatibility with
|
|
# the cve-check class
|
|
#
|
|
# Example:
|
|
# bitbake -c generate_vex openssl
|
|
# bitbake core-image-sato
|
|
# bitbake -k -c generate_vex universe
|
|
#
|
|
# The product name that the CVE database uses defaults to BPN, but may need to
|
|
# be overriden per recipe (for example tiff.bb sets CVE_PRODUCT=libtiff).
|
|
CVE_PRODUCT ??= "${BPN}"
|
|
CVE_VERSION ??= "${PV}"
|
|
|
|
CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve"
|
|
|
|
CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json"
|
|
CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt"
|
|
|
|
CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve"
|
|
CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json"
|
|
CVE_CHECK_MANIFEST_JSON ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}.json"
|
|
|
|
# Skip CVE Check for packages (PN)
|
|
CVE_CHECK_SKIP_RECIPE ?= ""
|
|
|
|
# Replace NVD DB check status for a given CVE. Each of CVE has to be mentioned
|
|
# separately with optional detail and description for this status.
|
|
#
|
|
# CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
|
|
# CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"
|
|
#
|
|
# Settings the same status and reason for multiple CVEs is possible
|
|
# via CVE_STATUS_GROUPS variable.
|
|
#
|
|
# CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_PATCHED"
|
|
#
|
|
# CVE_STATUS_WIN = "CVE-1234-0001 CVE-1234-0003"
|
|
# CVE_STATUS_WIN[status] = "not-applicable-platform: Issue only applies on Windows"
|
|
# CVE_STATUS_PATCHED = "CVE-1234-0002 CVE-1234-0004"
|
|
# CVE_STATUS_PATCHED[status] = "fixed-version: Fixed externally"
|
|
#
|
|
# All possible CVE statuses could be found in cve-check-map.conf
|
|
# CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
|
|
# CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
|
|
#
|
|
# CVE_CHECK_IGNORE is deprecated and CVE_STATUS has to be used instead.
|
|
# Keep CVE_CHECK_IGNORE until other layers migrate to new variables
|
|
CVE_CHECK_IGNORE ?= ""
|
|
|
|
# Layers to be excluded
|
|
CVE_CHECK_LAYER_EXCLUDELIST ??= ""
|
|
|
|
# Layers to be included
|
|
CVE_CHECK_LAYER_INCLUDELIST ??= ""
|
|
|
|
|
|
# set to "alphabetical" for version using single alphabetical character as increment release
|
|
CVE_VERSION_SUFFIX ??= ""
|
|
|
|
python () {
|
|
if bb.data.inherits_class("cve-check", d):
|
|
raise bb.parse.SkipRecipe("Skipping recipe: found incompatible combination of cve-check and vex enabled at the same time.")
|
|
|
|
from oe.cve_check import extend_cve_status
|
|
extend_cve_status(d)
|
|
}
|
|
|
|
def generate_json_report(d, out_path, link_path):
|
|
if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
|
|
import json
|
|
from oe.cve_check import cve_check_merge_jsons, update_symlinks
|
|
|
|
bb.note("Generating JSON CVE summary")
|
|
index_file = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")
|
|
summary = {"version":"1", "package": []}
|
|
with open(index_file) as f:
|
|
filename = f.readline()
|
|
while filename:
|
|
with open(filename.rstrip()) as j:
|
|
data = json.load(j)
|
|
cve_check_merge_jsons(summary, data)
|
|
filename = f.readline()
|
|
|
|
summary["package"].sort(key=lambda d: d['name'])
|
|
|
|
with open(out_path, "w") as f:
|
|
json.dump(summary, f, indent=2)
|
|
|
|
update_symlinks(out_path, link_path)
|
|
|
|
python vex_save_summary_handler () {
|
|
import shutil
|
|
import datetime
|
|
from oe.cve_check import update_symlinks
|
|
|
|
cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
|
|
|
|
bb.utils.mkdirhier(cvelogpath)
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
|
|
json_summary_link_name = os.path.join(cvelogpath, d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON"))
|
|
json_summary_name = os.path.join(cvelogpath, "cve-summary-%s.json" % (timestamp))
|
|
generate_json_report(d, json_summary_name, json_summary_link_name)
|
|
bb.plain("Complete CVE JSON report summary created at: %s" % json_summary_link_name)
|
|
}
|
|
|
|
addhandler vex_save_summary_handler
|
|
vex_save_summary_handler[eventmask] = "bb.event.BuildCompleted"
|
|
|
|
python do_generate_vex () {
|
|
"""
|
|
Generate metadata needed for vulnerability checking for
|
|
the current recipe
|
|
"""
|
|
from oe.cve_check import get_patched_cves
|
|
|
|
try:
|
|
patched_cves = get_patched_cves(d)
|
|
cves_status = []
|
|
products = d.getVar("CVE_PRODUCT").split()
|
|
for product in products:
|
|
if ":" in product:
|
|
_, product = product.split(":", 1)
|
|
cves_status.append([product, False])
|
|
|
|
except FileNotFoundError:
|
|
bb.fatal("Failure in searching patches")
|
|
|
|
cve_write_data_json(d, patched_cves, cves_status)
|
|
}
|
|
|
|
addtask generate_vex before do_build
|
|
|
|
python vex_cleanup () {
|
|
"""
|
|
Delete the file used to gather all the CVE information.
|
|
"""
|
|
bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH"))
|
|
}
|
|
|
|
addhandler vex_cleanup
|
|
vex_cleanup[eventmask] = "bb.event.BuildCompleted"
|
|
|
|
python vex_write_rootfs_manifest () {
|
|
"""
|
|
Create VEX/CVE manifest when building an image
|
|
"""
|
|
|
|
import json
|
|
from oe.rootfs import image_list_installed_packages
|
|
from oe.cve_check import cve_check_merge_jsons, update_symlinks
|
|
|
|
deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
|
|
if os.path.exists(deploy_file_json):
|
|
bb.utils.remove(deploy_file_json)
|
|
|
|
# Create a list of relevant recipies
|
|
recipies = set()
|
|
for pkg in list(image_list_installed_packages(d)):
|
|
pkg_info = os.path.join(d.getVar('PKGDATA_DIR'),
|
|
'runtime-reverse', pkg)
|
|
pkg_data = oe.packagedata.read_pkgdatafile(pkg_info)
|
|
recipies.add(pkg_data["PN"])
|
|
|
|
bb.note("Writing rootfs VEX manifest")
|
|
deploy_dir = d.getVar("IMGDEPLOYDIR")
|
|
link_name = d.getVar("IMAGE_LINK_NAME")
|
|
|
|
json_data = {"version":"1", "package": []}
|
|
text_data = ""
|
|
|
|
save_pn = d.getVar("PN")
|
|
|
|
for pkg in recipies:
|
|
# To be able to use the CVE_CHECK_RECIPE_FILE_JSON variable we have to evaluate
|
|
# it with the different PN names set each time.
|
|
d.setVar("PN", pkg)
|
|
|
|
pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
|
|
if os.path.exists(pkgfilepath):
|
|
with open(pkgfilepath) as j:
|
|
data = json.load(j)
|
|
cve_check_merge_jsons(json_data, data)
|
|
else:
|
|
bb.warn("Missing cve file for %s" % pkg)
|
|
|
|
d.setVar("PN", save_pn)
|
|
|
|
link_path = os.path.join(deploy_dir, "%s.json" % link_name)
|
|
manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON")
|
|
|
|
with open(manifest_name, "w") as f:
|
|
json.dump(json_data, f, indent=2)
|
|
|
|
update_symlinks(manifest_name, link_path)
|
|
bb.plain("Image VEX JSON report stored in: %s" % manifest_name)
|
|
}
|
|
|
|
ROOTFS_POSTPROCESS_COMMAND:prepend = "vex_write_rootfs_manifest; "
|
|
do_rootfs[recrdeptask] += "do_generate_vex "
|
|
do_populate_sdk[recrdeptask] += "do_generate_vex "
|
|
|
|
def cve_write_data_json(d, cve_data, cve_status):
|
|
"""
|
|
Prepare CVE data for the JSON format, then write it.
|
|
Done for each recipe.
|
|
"""
|
|
|
|
from oe.cve_check import get_cpe_ids
|
|
import json
|
|
|
|
output = {"version":"1", "package": []}
|
|
nvd_link = "https://nvd.nist.gov/vuln/detail/"
|
|
|
|
fdir_name = d.getVar("FILE_DIRNAME")
|
|
layer = fdir_name.split("/")[-3]
|
|
|
|
include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split()
|
|
exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split()
|
|
|
|
if exclude_layers and layer in exclude_layers:
|
|
return
|
|
|
|
if include_layers and layer not in include_layers:
|
|
return
|
|
|
|
product_data = []
|
|
for s in cve_status:
|
|
p = {"product": s[0], "cvesInRecord": "Yes"}
|
|
if s[1] == False:
|
|
p["cvesInRecord"] = "No"
|
|
product_data.append(p)
|
|
product_data = list({p['product']:p for p in product_data}.values())
|
|
|
|
package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV"))
|
|
cpes = get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION"))
|
|
package_data = {
|
|
"name" : d.getVar("PN"),
|
|
"layer" : layer,
|
|
"version" : package_version,
|
|
"products": product_data,
|
|
"cpes": cpes
|
|
}
|
|
|
|
cve_list = []
|
|
|
|
for cve in sorted(cve_data):
|
|
issue_link = "%s%s" % (nvd_link, cve)
|
|
|
|
cve_item = {
|
|
"id" : cve,
|
|
"status" : cve_data[cve]["abbrev-status"],
|
|
"link": issue_link,
|
|
}
|
|
if 'NVD-summary' in cve_data[cve]:
|
|
cve_item["summary"] = cve_data[cve]["NVD-summary"]
|
|
cve_item["scorev2"] = cve_data[cve]["NVD-scorev2"]
|
|
cve_item["scorev3"] = cve_data[cve]["NVD-scorev3"]
|
|
cve_item["scorev4"] = cve_data[cve]["NVD-scorev4"]
|
|
cve_item["vector"] = cve_data[cve]["NVD-vector"]
|
|
cve_item["vectorString"] = cve_data[cve]["NVD-vectorString"]
|
|
if 'status' in cve_data[cve]:
|
|
cve_item["detail"] = cve_data[cve]["status"]
|
|
if 'justification' in cve_data[cve]:
|
|
cve_item["description"] = cve_data[cve]["justification"]
|
|
if 'resource' in cve_data[cve]:
|
|
cve_item["patch-file"] = cve_data[cve]["resource"]
|
|
cve_list.append(cve_item)
|
|
|
|
package_data["issue"] = cve_list
|
|
output["package"].append(package_data)
|
|
|
|
deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
|
|
|
|
write_string = json.dumps(output, indent=2)
|
|
|
|
cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
|
|
index_path = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")
|
|
bb.utils.mkdirhier(cvelogpath)
|
|
bb.utils.mkdirhier(os.path.dirname(deploy_file))
|
|
fragment_file = os.path.basename(deploy_file)
|
|
fragment_path = os.path.join(cvelogpath, fragment_file)
|
|
with open(fragment_path, "w") as f:
|
|
f.write(write_string)
|
|
with open(deploy_file, "w") as f:
|
|
f.write(write_string)
|
|
with open(index_path, "a+") as f:
|
|
f.write("%s\n" % fragment_path)
|