mirror of
https://git.yoctoproject.org/poky
synced 2026-05-31 00:39:46 +00:00
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: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
committed by
Richard Purdie
parent
3ae80177fb
commit
fa6c07bc1a
@@ -94,10 +94,11 @@ python do_cve_check () {
|
|||||||
"""
|
"""
|
||||||
Check recipe for patched and unpatched CVEs
|
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")):
|
if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
|
||||||
try:
|
try:
|
||||||
patched_cves = get_patches_cves(d)
|
patched_cves = get_patched_cves(d)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
bb.fatal("Failure in searching patches")
|
bb.fatal("Failure in searching patches")
|
||||||
whitelisted, patched, unpatched = check_cves(d, patched_cves)
|
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 ''}"
|
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 ''}"
|
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):
|
def check_cves(d, patched_cves):
|
||||||
"""
|
"""
|
||||||
Connect to the NVD database and find unpatched cves.
|
Connect to the NVD database and find unpatched cves.
|
||||||
|
|||||||
@@ -63,3 +63,86 @@ def _cmpkey(release, patch_l, pre_l, pre_v):
|
|||||||
else:
|
else:
|
||||||
_pre = float(pre_v) if pre_v else float('-inf')
|
_pre = float(pre_v) if pre_v else float('-inf')
|
||||||
return _release, _patch, _pre
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user