summaryrefslogtreecommitdiffstats
path: root/meta/classes
diff options
context:
space:
mode:
Diffstat (limited to 'meta/classes')
-rw-r--r--meta/classes/vex.bbclass310
1 files changed, 310 insertions, 0 deletions
diff --git a/meta/classes/vex.bbclass b/meta/classes/vex.bbclass
new file mode 100644
index 0000000000..bb16e2a529
--- /dev/null
+++ b/meta/classes/vex.bbclass
@@ -0,0 +1,310 @@
1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7# This class is used to generate metadata needed by external
8# tools to check for vulnerabilities, for example CVEs.
9#
10# In order to use this class just inherit the class in the
11# local.conf file and it will add the generate_vex task for
12# every recipe. If an image is build it will generate a report
13# in DEPLOY_DIR_IMAGE for all the packages used, it will also
14# generate a file for all recipes used in the build.
15#
16# Variables use CVE_CHECK prefix to keep compatibility with
17# the cve-check class
18#
19# Example:
20# bitbake -c generate_vex openssl
21# bitbake core-image-sato
22# bitbake -k -c generate_vex universe
23#
24# The product name that the CVE database uses defaults to BPN, but may need to
25# be overriden per recipe (for example tiff.bb sets CVE_PRODUCT=libtiff).
26CVE_PRODUCT ??= "${BPN}"
27CVE_VERSION ??= "${PV}"
28
29CVE_CHECK_SUMMARY_DIR ?= "${LOG_DIR}/cve"
30
31CVE_CHECK_SUMMARY_FILE_NAME_JSON = "cve-summary.json"
32CVE_CHECK_SUMMARY_INDEX_PATH = "${CVE_CHECK_SUMMARY_DIR}/cve-summary-index.txt"
33
34CVE_CHECK_DIR ??= "${DEPLOY_DIR}/cve"
35CVE_CHECK_RECIPE_FILE_JSON ?= "${CVE_CHECK_DIR}/${PN}_cve.json"
36CVE_CHECK_MANIFEST_JSON ?= "${IMGDEPLOYDIR}/${IMAGE_NAME}.json"
37
38# Skip CVE Check for packages (PN)
39CVE_CHECK_SKIP_RECIPE ?= ""
40
41# Replace NVD DB check status for a given CVE. Each of CVE has to be mentioned
42# separately with optional detail and description for this status.
43#
44# CVE_STATUS[CVE-1234-0001] = "not-applicable-platform: Issue only applies on Windows"
45# CVE_STATUS[CVE-1234-0002] = "fixed-version: Fixed externally"
46#
47# Settings the same status and reason for multiple CVEs is possible
48# via CVE_STATUS_GROUPS variable.
49#
50# CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_PATCHED"
51#
52# CVE_STATUS_WIN = "CVE-1234-0001 CVE-1234-0003"
53# CVE_STATUS_WIN[status] = "not-applicable-platform: Issue only applies on Windows"
54# CVE_STATUS_PATCHED = "CVE-1234-0002 CVE-1234-0004"
55# CVE_STATUS_PATCHED[status] = "fixed-version: Fixed externally"
56#
57# All possible CVE statuses could be found in cve-check-map.conf
58# CVE_CHECK_STATUSMAP[not-applicable-platform] = "Ignored"
59# CVE_CHECK_STATUSMAP[fixed-version] = "Patched"
60#
61# CVE_CHECK_IGNORE is deprecated and CVE_STATUS has to be used instead.
62# Keep CVE_CHECK_IGNORE until other layers migrate to new variables
63CVE_CHECK_IGNORE ?= ""
64
65# Layers to be excluded
66CVE_CHECK_LAYER_EXCLUDELIST ??= ""
67
68# Layers to be included
69CVE_CHECK_LAYER_INCLUDELIST ??= ""
70
71
72# set to "alphabetical" for version using single alphabetical character as increment release
73CVE_VERSION_SUFFIX ??= ""
74
75python () {
76 if bb.data.inherits_class("cve-check", d):
77 raise bb.parse.SkipRecipe("Skipping recipe: found incompatible combination of cve-check and vex enabled at the same time.")
78
79 # Fallback all CVEs from CVE_CHECK_IGNORE to CVE_STATUS
80 cve_check_ignore = d.getVar("CVE_CHECK_IGNORE")
81 if cve_check_ignore:
82 bb.warn("CVE_CHECK_IGNORE is deprecated in favor of CVE_STATUS")
83 for cve in (d.getVar("CVE_CHECK_IGNORE") or "").split():
84 d.setVarFlag("CVE_STATUS", cve, "ignored")
85
86 # Process CVE_STATUS_GROUPS to set multiple statuses and optional detail or description at once
87 for cve_status_group in (d.getVar("CVE_STATUS_GROUPS") or "").split():
88 cve_group = d.getVar(cve_status_group)
89 if cve_group is not None:
90 for cve in cve_group.split():
91 d.setVarFlag("CVE_STATUS", cve, d.getVarFlag(cve_status_group, "status"))
92 else:
93 bb.warn("CVE_STATUS_GROUPS contains undefined variable %s" % cve_status_group)
94}
95
96def generate_json_report(d, out_path, link_path):
97 if os.path.exists(d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")):
98 import json
99 from oe.cve_check import cve_check_merge_jsons, update_symlinks
100
101 bb.note("Generating JSON CVE summary")
102 index_file = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")
103 summary = {"version":"1", "package": []}
104 with open(index_file) as f:
105 filename = f.readline()
106 while filename:
107 with open(filename.rstrip()) as j:
108 data = json.load(j)
109 cve_check_merge_jsons(summary, data)
110 filename = f.readline()
111
112 summary["package"].sort(key=lambda d: d['name'])
113
114 with open(out_path, "w") as f:
115 json.dump(summary, f, indent=2)
116
117 update_symlinks(out_path, link_path)
118
119python vex_save_summary_handler () {
120 import shutil
121 import datetime
122 from oe.cve_check import update_symlinks
123
124 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
125
126 bb.utils.mkdirhier(cvelogpath)
127 timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
128
129 json_summary_link_name = os.path.join(cvelogpath, d.getVar("CVE_CHECK_SUMMARY_FILE_NAME_JSON"))
130 json_summary_name = os.path.join(cvelogpath, "cve-summary-%s.json" % (timestamp))
131 generate_json_report(d, json_summary_name, json_summary_link_name)
132 bb.plain("Complete CVE JSON report summary created at: %s" % json_summary_link_name)
133}
134
135addhandler vex_save_summary_handler
136vex_save_summary_handler[eventmask] = "bb.event.BuildCompleted"
137
138python do_generate_vex () {
139 """
140 Generate metadata needed for vulnerability checking for
141 the current recipe
142 """
143 from oe.cve_check import get_patched_cves
144
145 try:
146 patched_cves = get_patched_cves(d)
147 cves_status = []
148 products = d.getVar("CVE_PRODUCT").split()
149 for product in products:
150 if ":" in product:
151 _, product = product.split(":", 1)
152 cves_status.append([product, False])
153
154 except FileNotFoundError:
155 bb.fatal("Failure in searching patches")
156
157 cve_write_data_json(d, patched_cves, cves_status)
158}
159
160addtask generate_vex before do_build
161
162python vex_cleanup () {
163 """
164 Delete the file used to gather all the CVE information.
165 """
166 bb.utils.remove(e.data.getVar("CVE_CHECK_SUMMARY_INDEX_PATH"))
167}
168
169addhandler vex_cleanup
170vex_cleanup[eventmask] = "bb.event.BuildCompleted"
171
172python vex_write_rootfs_manifest () {
173 """
174 Create VEX/CVE manifest when building an image
175 """
176
177 import json
178 from oe.rootfs import image_list_installed_packages
179 from oe.cve_check import cve_check_merge_jsons, update_symlinks
180
181 deploy_file_json = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
182 if os.path.exists(deploy_file_json):
183 bb.utils.remove(deploy_file_json)
184
185 # Create a list of relevant recipies
186 recipies = set()
187 for pkg in list(image_list_installed_packages(d)):
188 pkg_info = os.path.join(d.getVar('PKGDATA_DIR'),
189 'runtime-reverse', pkg)
190 pkg_data = oe.packagedata.read_pkgdatafile(pkg_info)
191 recipies.add(pkg_data["PN"])
192
193 bb.note("Writing rootfs VEX manifest")
194 deploy_dir = d.getVar("IMGDEPLOYDIR")
195 link_name = d.getVar("IMAGE_LINK_NAME")
196
197 json_data = {"version":"1", "package": []}
198 text_data = ""
199
200 save_pn = d.getVar("PN")
201
202 for pkg in recipies:
203 # To be able to use the CVE_CHECK_RECIPE_FILE_JSON variable we have to evaluate
204 # it with the different PN names set each time.
205 d.setVar("PN", pkg)
206
207 pkgfilepath = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
208 if os.path.exists(pkgfilepath):
209 with open(pkgfilepath) as j:
210 data = json.load(j)
211 cve_check_merge_jsons(json_data, data)
212
213 d.setVar("PN", save_pn)
214
215 link_path = os.path.join(deploy_dir, "%s.json" % link_name)
216 manifest_name = d.getVar("CVE_CHECK_MANIFEST_JSON")
217
218 with open(manifest_name, "w") as f:
219 json.dump(json_data, f, indent=2)
220
221 update_symlinks(manifest_name, link_path)
222 bb.plain("Image VEX JSON report stored in: %s" % manifest_name)
223}
224
225ROOTFS_POSTPROCESS_COMMAND:prepend = "vex_write_rootfs_manifest; "
226do_rootfs[recrdeptask] += "do_generate_vex "
227do_populate_sdk[recrdeptask] += "do_generate_vex "
228
229def cve_write_data_json(d, cve_data, cve_status):
230 """
231 Prepare CVE data for the JSON format, then write it.
232 Done for each recipe.
233 """
234
235 from oe.cve_check import get_cpe_ids
236 import json
237
238 output = {"version":"1", "package": []}
239 nvd_link = "https://nvd.nist.gov/vuln/detail/"
240
241 fdir_name = d.getVar("FILE_DIRNAME")
242 layer = fdir_name.split("/")[-3]
243
244 include_layers = d.getVar("CVE_CHECK_LAYER_INCLUDELIST").split()
245 exclude_layers = d.getVar("CVE_CHECK_LAYER_EXCLUDELIST").split()
246
247 if exclude_layers and layer in exclude_layers:
248 return
249
250 if include_layers and layer not in include_layers:
251 return
252
253 product_data = []
254 for s in cve_status:
255 p = {"product": s[0], "cvesInRecord": "Yes"}
256 if s[1] == False:
257 p["cvesInRecord"] = "No"
258 product_data.append(p)
259 product_data = list({p['product']:p for p in product_data}.values())
260
261 package_version = "%s%s" % (d.getVar("EXTENDPE"), d.getVar("PV"))
262 cpes = get_cpe_ids(d.getVar("CVE_PRODUCT"), d.getVar("CVE_VERSION"))
263 package_data = {
264 "name" : d.getVar("PN"),
265 "layer" : layer,
266 "version" : package_version,
267 "products": product_data,
268 "cpes": cpes
269 }
270
271 cve_list = []
272
273 for cve in sorted(cve_data):
274 issue_link = "%s%s" % (nvd_link, cve)
275
276 cve_item = {
277 "id" : cve,
278 "status" : cve_data[cve]["abbrev-status"],
279 "link": issue_link,
280 }
281 if 'NVD-summary' in cve_data[cve]:
282 cve_item["summary"] = cve_data[cve]["NVD-summary"]
283 cve_item["scorev2"] = cve_data[cve]["NVD-scorev2"]
284 cve_item["scorev3"] = cve_data[cve]["NVD-scorev3"]
285 cve_item["vector"] = cve_data[cve]["NVD-vector"]
286 cve_item["vectorString"] = cve_data[cve]["NVD-vectorString"]
287 if 'status' in cve_data[cve]:
288 cve_item["detail"] = cve_data[cve]["status"]
289 if 'justification' in cve_data[cve]:
290 cve_item["description"] = cve_data[cve]["justification"]
291 if 'resource' in cve_data[cve]:
292 cve_item["patch-file"] = cve_data[cve]["resource"]
293 cve_list.append(cve_item)
294
295 package_data["issue"] = cve_list
296 output["package"].append(package_data)
297
298 deploy_file = d.getVar("CVE_CHECK_RECIPE_FILE_JSON")
299
300 write_string = json.dumps(output, indent=2)
301
302 cvelogpath = d.getVar("CVE_CHECK_SUMMARY_DIR")
303 index_path = d.getVar("CVE_CHECK_SUMMARY_INDEX_PATH")
304 bb.utils.mkdirhier(cvelogpath)
305 fragment_file = os.path.basename(deploy_file)
306 fragment_path = os.path.join(cvelogpath, fragment_file)
307 with open(fragment_path, "w") as f:
308 f.write(write_string)
309 with open(index_path, "a+") as f:
310 f.write("%s\n" % fragment_path)