Skip to content
Snippets Groups Projects
Commit fa6c07bc authored by Joshua Watt's avatar Joshua Watt Committed by Richard Purdie
Browse files

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)

Signed-off-by: default avatarJoshua Watt <JPEWhacker@gmail.com>
Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: default avatarRichard Purdie <richard.purdie@linuxfoundation.org>
parent 3ae80177
No related branches found
No related tags found
No related merge requests found
......@@ -94,10 +94,11 @@ python do_cve_check () {
"""
Check recipe for patched and unpatched CVEs
"""
from oe.cve_check import get_patched_cves
if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
try:
patched_cves = get_patches_cves(d)
patched_cves = get_patched_cves(d)
except FileNotFoundError:
bb.fatal("Failure in searching patches")
whitelisted, patched, unpatched = check_cves(d, patched_cves)
......@@ -156,65 +157,6 @@ python cve_check_write_rootfs_manifest () {
ROOTFS_POSTPROCESS_COMMAND:prepend = "${@'cve_check_write_rootfs_manifest; ' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
do_rootfs[recrdeptask] += "${@'do_cve_check' if d.getVar('CVE_CHECK_CREATE_MANIFEST') == '1' else ''}"
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 the last "CVE-YYYY-ID" in the file name, also if written
# in lowercase. Possible to have multiple CVE IDs in a single
# file name, but only the last one will be detected from the file name.
# However, patch files contents addressing multiple CVE IDs are supported
# (cve_match regular expression)
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]
if not os.path.isfile(patch_file):
bb.error("File Not found: %s" % patch_file)
raise FileNotFoundError
# 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
def check_cves(d, patched_cves):
"""
Connect to the NVD database and find unpatched cves.
......
......@@ -63,3 +63,86 @@ def _cmpkey(release, patch_l, pre_l, pre_v):
else:
_pre = float(pre_v) if pre_v else float('-inf')
return _release, _patch, _pre
def get_patched_cves(d):
"""
Get patches that solve CVEs using the "CVE: " tag.
"""
import re
import oe.patch
pn = d.getVar("PN")
cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")
# Matches the last "CVE-YYYY-ID" in the file name, also if written
# in lowercase. Possible to have multiple CVE IDs in a single
# file name, but only the last one will be detected from the file name.
# However, patch files contents addressing multiple CVE IDs are supported
# (cve_match regular expression)
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 oe.patch.src_patches(d):
patch_file = bb.fetch.decodeurl(url)[2]
if not os.path.isfile(patch_file):
bb.error("File Not found: %s" % patch_file)
raise FileNotFoundError
# 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
def get_cpe_ids(cve_product, version):
"""
Get list of CPE identifiers for the given product and version
"""
version = version.split("+git")[0]
cpe_ids = []
for product in cve_product.split():
# CVE_PRODUCT in recipes may include vendor information for CPE identifiers. If not,
# use wildcard for vendor.
if ":" in product:
vendor, product = product.split(":", 1)
else:
vendor = "*"
cpe_id = f'cpe:2.3:a:{vendor}:{product}:{version}:*:*:*:*:*:*:*'
cpe_ids.append(cpe_id)
return cpe_ids
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment