summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2022-07-08 13:47:36 +0200
committerRichard Purdie <richard.purdie@linuxfoundation.org>2022-07-16 06:52:48 +0100
commitdee08141f250f078eb891080e11fef163cd056e1 (patch)
tree68f34d6ecd38e384301422b6b62b9b273e7ca1d2
parent61023f9e61d6620c3b15f834df6a2e0ace2a5272 (diff)
downloadpoky-dee08141f250f078eb891080e11fef163cd056e1.tar.gz
classes/cve-check: Move get_patches_cves to library
Moving the function will allow other classes to capture which CVEs have been patched, in particular SBoM generation. Also add a function to capture the CPE ID from the CVE Product and Version (From OE-Core rev: 75d34259a715120be1d023e4fd7b6b4b125f2443) (From OE-Core rev: bba069463ca3813666d084643b0239b9af0199e1) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org> (cherry picked from commit fa6c07bc1a585f204dbdc28704f61448edb8fdc8) Signed-off-by: Akash Hadke <akash.hadke@kpit.com> Signed-off-by: Steve Sakoman <steve@sakoman.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/classes/cve-check.bbclass62
-rw-r--r--meta/lib/oe/cve_check.py82
2 files changed, 84 insertions, 60 deletions
diff --git a/meta/classes/cve-check.bbclass b/meta/classes/cve-check.bbclass
index 1688fe2dfe..9eb9a95574 100644
--- a/meta/classes/cve-check.bbclass
+++ b/meta/classes/cve-check.bbclass
@@ -136,10 +136,11 @@ python do_cve_check () {
136 """ 136 """
137 Check recipe for patched and unpatched CVEs 137 Check recipe for patched and unpatched CVEs
138 """ 138 """
139 from oe.cve_check import get_patched_cves
139 140
140 if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")): 141 if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
141 try: 142 try:
142 patched_cves = get_patches_cves(d) 143 patched_cves = get_patched_cves(d)
143 except FileNotFoundError: 144 except FileNotFoundError:
144 bb.fatal("Failure in searching patches") 145 bb.fatal("Failure in searching patches")
145 whitelisted, patched, unpatched, status = check_cves(d, patched_cves) 146 whitelisted, patched, unpatched, status = check_cves(d, patched_cves)
@@ -247,65 +248,6 @@ ROOTFS_POSTPROCESS_COMMAND_prepend = "${@'cve_check_write_rootfs_manifest; ' if
247do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 248do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
248do_populate_sdk[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}" 249do_populate_sdk[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
249 250
250def get_patches_cves(d):
251 """
252 Get patches that solve CVEs using the "CVE: " tag.
253 """
254
255 import re
256
257 pn = d.getVar("PN")
258 cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")
259
260 # Matches the last "CVE-YYYY-ID" in the file name, also if written
261 # in lowercase. Possible to have multiple CVE IDs in a single
262 # file name, but only the last one will be detected from the file name.
263 # However, patch files contents addressing multiple CVE IDs are supported
264 # (cve_match regular expression)
265
266 cve_file_name_match = re.compile(".*([Cc][Vv][Ee]\-\d{4}\-\d+)")
267
268 patched_cves = set()
269 bb.debug(2, "Looking for patches that solves CVEs for %s" % pn)
270 for url in src_patches(d):
271 patch_file = bb.fetch.decodeurl(url)[2]
272
273 if not os.path.isfile(patch_file):
274 bb.error("File Not found: %s" % patch_file)
275 raise FileNotFoundError
276
277 # Check patch file name for CVE ID
278 fname_match = cve_file_name_match.search(patch_file)
279 if fname_match:
280 cve = fname_match.group(1).upper()
281 patched_cves.add(cve)
282 bb.debug(2, "Found CVE %s from patch file name %s" % (cve, patch_file))
283
284 with open(patch_file, "r", encoding="utf-8") as f:
285 try:
286 patch_text = f.read()
287 except UnicodeDecodeError:
288 bb.debug(1, "Failed to read patch %s using UTF-8 encoding"
289 " trying with iso8859-1" % patch_file)
290 f.close()
291 with open(patch_file, "r", encoding="iso8859-1") as f:
292 patch_text = f.read()
293
294 # Search for one or more "CVE: " lines
295 text_match = False
296 for match in cve_match.finditer(patch_text):
297 # Get only the CVEs without the "CVE: " tag
298 cves = patch_text[match.start()+5:match.end()]
299 for cve in cves.split():
300 bb.debug(2, "Patch %s solves %s" % (patch_file, cve))
301 patched_cves.add(cve)
302 text_match = True
303
304 if not fname_match and not text_match:
305 bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)
306
307 return patched_cves
308
309def check_cves(d, patched_cves): 251def check_cves(d, patched_cves):
310 """ 252 """
311 Connect to the NVD database and find unpatched cves. 253 Connect to the NVD database and find unpatched cves.
diff --git a/meta/lib/oe/cve_check.py b/meta/lib/oe/cve_check.py
index b17390de90..a4b831831b 100644
--- a/meta/lib/oe/cve_check.py
+++ b/meta/lib/oe/cve_check.py
@@ -89,3 +89,85 @@ def update_symlinks(target_path, link_path):
89 if os.path.exists(os.path.realpath(link_path)): 89 if os.path.exists(os.path.realpath(link_path)):
90 os.remove(link_path) 90 os.remove(link_path)
91 os.symlink(os.path.basename(target_path), link_path) 91 os.symlink(os.path.basename(target_path), link_path)
92
93def get_patched_cves(d):
94 """
95 Get patches that solve CVEs using the "CVE: " tag.
96 """
97
98 import re
99 import oe.patch
100
101 pn = d.getVar("PN")
102 cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")
103
104 # Matches the last "CVE-YYYY-ID" in the file name, also if written
105 # in lowercase. Possible to have multiple CVE IDs in a single
106 # file name, but only the last one will be detected from the file name.
107 # However, patch files contents addressing multiple CVE IDs are supported
108 # (cve_match regular expression)
109
110 cve_file_name_match = re.compile(".*([Cc][Vv][Ee]\-\d{4}\-\d+)")
111
112 patched_cves = set()
113 bb.debug(2, "Looking for patches that solves CVEs for %s" % pn)
114 for url in oe.patch.src_patches(d):
115 patch_file = bb.fetch.decodeurl(url)[2]
116
117 if not os.path.isfile(patch_file):
118 bb.error("File Not found: %s" % patch_file)
119 raise FileNotFoundError
120
121 # Check patch file name for CVE ID
122 fname_match = cve_file_name_match.search(patch_file)
123 if fname_match:
124 cve = fname_match.group(1).upper()
125 patched_cves.add(cve)
126 bb.debug(2, "Found CVE %s from patch file name %s" % (cve, patch_file))
127
128 with open(patch_file, "r", encoding="utf-8") as f:
129 try:
130 patch_text = f.read()
131 except UnicodeDecodeError:
132 bb.debug(1, "Failed to read patch %s using UTF-8 encoding"
133 " trying with iso8859-1" % patch_file)
134 f.close()
135 with open(patch_file, "r", encoding="iso8859-1") as f:
136 patch_text = f.read()
137
138 # Search for one or more "CVE: " lines
139 text_match = False
140 for match in cve_match.finditer(patch_text):
141 # Get only the CVEs without the "CVE: " tag
142 cves = patch_text[match.start()+5:match.end()]
143 for cve in cves.split():
144 bb.debug(2, "Patch %s solves %s" % (patch_file, cve))
145 patched_cves.add(cve)
146 text_match = True
147
148 if not fname_match and not text_match:
149 bb.debug(2, "Patch %s doesn't solve CVEs" % patch_file)
150
151 return patched_cves
152
153
154def get_cpe_ids(cve_product, version):
155 """
156 Get list of CPE identifiers for the given product and version
157 """
158
159 version = version.split("+git")[0]
160
161 cpe_ids = []
162 for product in cve_product.split():
163 # CVE_PRODUCT in recipes may include vendor information for CPE identifiers. If not,
164 # use wildcard for vendor.
165 if ":" in product:
166 vendor, product = product.split(":", 1)
167 else:
168 vendor = "*"
169
170 cpe_id = f'cpe:2.3:a:{vendor}:{product}:{version}:*:*:*:*:*:*:*'
171 cpe_ids.append(cpe_id)
172
173 return cpe_ids