diff --git a/classes/source-info.bbclass b/classes/source-info.bbclass index d6c71b74..9d79a6ca 100644 --- a/classes/source-info.bbclass +++ b/classes/source-info.bbclass @@ -6,8 +6,8 @@ python do_collect_source_info() { source_info = d.getVar("EMLINUX_SOURCE_FROM") if source_info is None: - distro = d.getVar("DISTRO").split("-")[1] - source_info = distro + # if EMLINUX_SOURCE_FROM is not set, we should set unknown to reduce false positive. + source_info = "unknown" data = { "source_package_name": d.getVar("PN"), diff --git a/conf/cve/cve_check_ignore.yml b/conf/cve/cve_check_ignore.yml index 159d0105..dcd19520 100644 --- a/conf/cve/cve_check_ignore.yml +++ b/conf/cve/cve_check_ignore.yml @@ -33,6 +33,152 @@ #util-linux: # all: # - CVE-2022-0563 +apparmor: + all: + - CVE-2008-0731 +audit: + all: + - CVE-2007-4150 + - CVE-2007-4149 + - CVE-2007-4152 + - CVE-2007-4151 + - CVE-2007-4148 +bzip2: + all: + - CVE-2002-0759 + - CVE-2002-0760 + - CVE-2002-0761 + - CVE-2023-22895 +cailo: + all: + - CVE-2014-5116 +cpio: + all: + - CVE-2010-4226 + - CVE-2023-7216 +db5.3: + all: + - CVE-2015-2583 + - CVE-2015-2624 + - CVE-2015-2626 + - CVE-2015-2640 + - CVE-2015-2654 + - CVE-2015-2656 + - CVE-2015-4754 + - CVE-2015-4764 + - CVE-2015-4774 + - CVE-2015-4775 + - CVE-2015-4776 + - CVE-2015-4777 + - CVE-2015-4778 + - CVE-2015-4779 + - CVE-2015-4780 + - CVE-2015-4781 + - CVE-2015-4782 + - CVE-2015-4783 + - CVE-2015-4784 + - CVE-2015-4785 + - CVE-2015-4781 + - CVE-2015-4782 + - CVE-2015-4783 + - CVE-2015-4780 + - CVE-2015-4781 + - CVE-2015-4782 + - CVE-2015-4783 + - CVE-2015-4784 + - CVE-2015-4785 + - CVE-2015-4786 + - CVE-2015-4787 + - CVE-2015-4788 + - CVE-2015-4789 + - CVE-2015-4790 + - CVE-2016-0689 + - CVE-2016-0692 + - CVE-2016-0694 + - CVE-2017-3605 + - CVE-2017-3606 + - CVE-2017-3607 + - CVE-2017-3608 + - CVE-2017-3609 + - CVE-2017-3610 + - CVE-2017-3611 + - CVE-2017-3612 + - CVE-2017-3613 + - CVE-2017-3614 + - CVE-2017-3615 + - CVE-2017-3616 + - CVE-2017-3617 + - CVE-2019-2708 + - CVE-2019-2760 + - CVE-2019-2868 + - CVE-2019-2869 + - CVE-2019-2870 + - CVE-2019-2871 + - CVE-2020-2981 +ffmpeg: + all: + - CVE-2012-2782 + - CVE-2012-2791 + - CVE-2012-2798 + - CVE-2012-2800 + - CVE-2017-17555 +freerdp2: + all: + - CVE-2013-4118 + - CVE-2013-4119 + - CVE-2014-0250 + - CVE-2014-0791 + - CVE-2017-2834 + - CVE-2017-2835 + - CVE-2017-2836 + - CVE-2017-2837 + - CVE-2017-2838 + - CVE-2017-2839 + - CVE-2025-68118 +gcc-12: + all: + - CVE-2000-1219 + - CVE-2002-2439 + - CVE-2008-1367 + - CVE-2008-1685 + - CVE-2015-5276 + - CVE-2017-11671 + - CVE-2018-12886 + - CVE-2019-15847 + - CVE-2021-3826 + - CVE-2021-37322 + - CVE-2021-46195 +glibc: + all: + - CVE-2017-8804 +gnutls28: + all: + - CVE-2012-1569 + - CVE-2014-3467 + - CVE-2014-3468 + - CVE-2014-3469 +gnupg2: + all: + - CVE-2022-3515 +krb5: + all: + - CVE-2007-3149 + - CVE-2022-39028 +libpng1.6: + all: + - CVE-2016-3751 +libsepol: + all: + - CVE-2018-1063 + - CVE-2016-7545 + - CVE-2015-3170 +libvorbis: + all: + - CVE-2020-20412 +libxml2: + all: + - CVE-2012-2871 + - CVE-2015-6837 linux-cip: 6.1: - CVE-2016-3699 @@ -52,3 +198,50 @@ linux-cip: - CVE-2023-26242 - CVE-2023-3079 - CVE-2024-49928 +ncurses: + all: + - CVE-2019-15547 + - CVE-2019-15548 +openssl: + all: + - CVE-2008-1678 + - CVE-2023-53159 +opus: + all: + - CVE-2007-5295 + - CVE-2008-1884 + - CVE-2008-1956 + - CVE-2022-25345 +orc: + all: + - CVE-2018-8015 + - CVE-2025-47436 +perl: + all: + - CVE-2004-2286 +procps: + all: + - CVE-2018-1121 +snappy: + all: + - CVE-2023-28115 + - CVE-2023-41330 +systemd: + all: + - CVE-2018-20839 +tar: + all: + - CVE-2025-45582 +tiff: + all: + - CVE-2022-41727 +vim: + all: + - CVE-2021-28832 + - CVE-2019-14957 +xz-utils: + all: + - CVE-2020-22916 +zlib: + all: + - CVE-2023-6992 diff --git a/conf/cve/cve_products.yml b/conf/cve/cve_products.yml index 7fa3cd21..829a0a5a 100644 --- a/conf/cve/cve_products.yml +++ b/conf/cve/cve_products.yml @@ -5,12 +5,21 @@ aom: vendor: aomedia product: aomedia +apparmor: + vendor: canonical + product: apparmor apt: vendor: debian product: apt +audit: + vendor: linux_audit_project + product: linux_audit cyrus-sasl2: vendor: cyrusimap product: cyrus-sasl +dash: + vendor: dash + product: dash db5.3: vendor: oracle product: berkeley_db @@ -23,6 +32,9 @@ gcc-12: glib2.0: vendor: gnome product: glib +glibc: + vendor: gnu + product: glibc gnupg2: vendor: gnupg product: gnupg @@ -65,12 +77,27 @@ linux-cip-rt: openjpeg2: vendor: uclouvain product: openjpeg +openssl: + vendor: openssl + product: openssl pango1.0: vendor: gnome product: pango +perl: + vendor: perl + product: perl +shadow: + vendor: shadow_project + product: shadow tar: vendor: gnu product: tar +util-linux: + vendor: kernel + product: util-linux +vim: + vendor: vim + product: vim xvidcore: vendor: xvid product: xvid diff --git a/scripts/cve_check_ng.py b/scripts/cve_check_ng.py index 6bce1869..a7eae195 100755 --- a/scripts/cve_check_ng.py +++ b/scripts/cve_check_ng.py @@ -220,7 +220,7 @@ def load_plugins(plugin_files: list[str]): # 1. Plugin file must be located in scripts/lib/python/cve/plugin directory # 2. Plugin file name must be start with eml_cve_ then ends with _plugin.py # e.g. eml_cve_myplugin_plugin.py -def find_plugins() -> list[str]: +def find_plugins(disable_plugins: list[str]) -> list[str]: layer_dirs = bitbake_runner.find_layers() plugins = [] @@ -229,7 +229,11 @@ def find_plugins() -> list[str]: d = ld + plugin_dir pattern = f"{d}/eml_cve_*_plugin.py" for plugin in glob.glob(pattern): - plugins.append(plugin) + p = os.path.splitext(os.path.basename(plugin))[0] + if not p in disable_plugins: + plugins.append(plugin) + else: + logger.info(f"Plugin '{p}' is disabled") return plugins @@ -256,11 +260,29 @@ def fetch_kev_data(cve_data_dir: str) -> KevInfoList: return KevInfoList({}) +def create_disable_plugins_list(user_given_plugins: str) -> list[str]: + if not user_given_plugins: + return [] + + disable_plugins = [] + + do_not_disable = ["eml_cve_nvd_plugin"] + disable_plugins_tmp = [p.strip() for p in user_given_plugins.split(",")] + for p in disable_plugins_tmp: + if p not in do_not_disable: + disable_plugins.append(p) + else: + logger.warning(f"Plugin '{p}' cannot be disabeld") + + return disable_plugins + + def main(args: dict): if args.verbose_output: logger.setLevel(logging.DEBUG) bitbakeinfo = bitbake_runner.get_bitbake_information(args.image_name) + disable_plugins = create_disable_plugins_list(args.disable_plugins) dpkg_status_file = ( bitbakeinfo["dpkg_status"] @@ -271,6 +293,10 @@ def main(args: dict): logger.error(f"File {dpkg_status_file} is not found") exit(1) + debian_codename = args.debian_codename + if not debian_codename: + debian_codename = bitbakeinfo["image_distro"].split("-")[1] + # Read dpkg file to get installed package information installed_packages = PackageInfoHelper.parse_dpkg_status_file( dpkg_status_file, target_source_package=args.target_source_package @@ -290,7 +316,7 @@ def main(args: dict): ignore_list = create_ignore_list( bitbakeinfo["emlinux_layer_dir"], installed_packages, - args.debian_codename, + debian_codename, args.extra_cve_check_ignore, ) @@ -298,13 +324,13 @@ def main(args: dict): cl.create_directory(cve_data_dir) # Find and load plugins - plugin_files = find_plugins() + plugin_files = find_plugins(disable_plugins) plugin_objs = load_plugins(plugin_files) # Create plugin instance plugins = [] for obj in plugin_objs: - o = obj(cve_data_dir, args, bitbakeinfo, installed_packages, cve_product_list) + o = obj(cve_data_dir, args, bitbakeinfo, installed_packages, cve_product_list, debian_codename) plugins.append(o) check_results = [] @@ -373,8 +399,7 @@ def parse_options(): cve_check_opts.add_argument( "--debian-codename", dest="debian_codename", - help="debian codename(Debian 12 is bookworm)", - default="bookworm", + help="debian codename(bookworm, trixie, and etc)", metavar="DEBIANCODENAME", ) cve_check_opts.add_argument( @@ -445,7 +470,10 @@ def parse_options(): action="store_true", help="Skip update CVE databases", ) - + plugin_opts.add_argument( + "--disable-plugins", + help="List plugin names to be disabled without .py extension (comma separated). e.g. --disable-plugins eml_cve_debian_plugin,eml_cve_your_plugin", + ) return parser.parse_args() diff --git a/scripts/lib/python/cve/plugin/eml_cve_debian_plugin.py b/scripts/lib/python/cve/plugin/eml_cve_debian_plugin.py new file mode 100644 index 00000000..2d08cd28 --- /dev/null +++ b/scripts/lib/python/cve/plugin/eml_cve_debian_plugin.py @@ -0,0 +1,159 @@ +from typing import Any +from lib.python.cve.cve_product import CveProductList +from lib.python.cve.plugin.eml_cve_plugin_base import EmlCvePlugin +from lib.python.cve.cve_info import CveStatus, CveCheckResult, CveCheckResultList +from lib.python.package_info import PackageList + +import logging + +logger = logging.getLogger("emlinux-cve-check") + +import urllib.request +import urllib.parse +import gzip +import time +import json +import debian.debian_support +import os +import logging +import errno + +SECURITY_TRACKER_JSON_UPDATE_INTERVAL = 86400 + +DEBIAN_CVE_TRACKER_JSON_URL = "https://security-tracker.debian.org/tracker/data/json" + + +class EmlDebianPlugin(EmlCvePlugin): + def __init__( + self, + cve_data_dir: str, + args: Any, + bitbakeinfo: Any, + installed_packages: PackageList, + cve_products: CveProductList, + debian_codename: str, + ): + super().__init__( + "EmlDebianPlugin", + 2, + cve_data_dir, + args, + bitbakeinfo, + installed_packages, + cve_products, + debian_codename, + ) + + self.tracker = None + + self.debian_cve_json = f"{self.cve_data_dir}/debian_cves.json" + + def update_database(self) -> bool: + self._fetch_cve_data() + return True + + def run_check(self) -> CveCheckResultList: + logger.debug(f"{self.plugin_name}: run-check start") + if not os.path.exists(self.debian_cve_json): + raise FileNotFoundError( + errno.ENOENT, os.strerror(errno.ENOENT), self.debian_cve_json + ) + + self._collect_cves_from_installed_packages() + + return self.cve_check_result_list + + def _fetch_json_data(self): + request = urllib.request.Request(DEBIAN_CVE_TRACKER_JSON_URL) + for attempt in range(5): + try: + r = urllib.request.urlopen(request) + + if r.headers["content-encoding"] == "gzip": + buf = r.read() + raw_data = gzip.decompress(buf) + else: + raw_data = r.read().decode("utf-8") + + r.close() + except Exception as e: + logger.debug(f"json file: received error ({e}), retrying") + time.sleep(6) + else: + return json.loads(raw_data) + else: + # We failed at all attempts + return None + + def _is_skip_fetch_json_file(self, json_file: str): + if not json_file: + return False + + if not os.path.exists(json_file): + return False + + if ( + time.time() - os.path.getmtime(json_file) + < SECURITY_TRACKER_JSON_UPDATE_INTERVAL + ): + logger.info( + "Last database update is in 1day so skip Debian CVE database update" + ) + return True + + return False + + def _fetch_cve_data(self): + logger.info("Update debian CVE database") + if self._is_skip_fetch_json_file(self.debian_cve_json): + return + + data = self._fetch_json_data() + if data is None: + raise Exception("Failed to download Debian security tracker json file") + + with open(self.debian_cve_json, "w") as f: + json.dump(data, f) + + return + + def _read_security_tracker_json(self): + with open(self.debian_cve_json) as f: + return json.load(f) + + def _collect_cves_from_installed_packages(self): + tracker = self._read_security_tracker_json() + + for src_pkg_name in self.installed_packages: + source_from = self.installed_packages.get_source_from(src_pkg_name) + if source_from == "non-debian" or source_from == "unknown": + # if package is build from a recipe and not based on debian package, skip cve check. + continue + + # At fisrt, check default debian version which is used by emlinux + target_codename = self.debian_codename + if not source_from == "debian": + # Is package built from a recipe and it based on debian source package? + if source_from in cvedata[cveid]["releases"]: + target_codename = source_from + + if src_pkg_name in tracker: + cvedata = tracker[src_pkg_name] + for cveid in cvedata: + data = cvedata[cveid]["releases"][target_codename] + + # According to the json file, there are 3 types in the status filed. + # That are open, resolved, and undetermined + vuln_status = CveStatus.CVE_STATUS_UNPATCHED + if data["status"] == "resolved": + installed_version = debian.debian_support.Version( + self.installed_packages.get_version(src_pkg_name) + ) + fixed_version = debian.debian_support.Version( + data["fixed_version"] + ) + if installed_version >= fixed_version: + vuln_status = CveStatus.CVE_STATUS_PATCHED + + ci = CveCheckResult(cveid, src_pkg_name, vuln_status) + self.cve_check_result_list.add_cve_info(src_pkg_name, ci) diff --git a/scripts/lib/python/cve/plugin/eml_cve_nvd_plugin.py b/scripts/lib/python/cve/plugin/eml_cve_nvd_plugin.py index 9c43a1d6..a7b18d00 100644 --- a/scripts/lib/python/cve/plugin/eml_cve_nvd_plugin.py +++ b/scripts/lib/python/cve/plugin/eml_cve_nvd_plugin.py @@ -41,6 +41,7 @@ def __init__( bitbakeinfo: Any, installed_packages: PackageList, cve_products: CveProductList, + debian_codename: str, ): super().__init__( "EmlNVDPlugin", @@ -50,6 +51,7 @@ def __init__( bitbakeinfo, installed_packages, cve_products, + debian_codename, ) self.predownload_url = self.bitbakeinfo["cve_db_predownload"] @@ -107,12 +109,11 @@ def _check_cves( cveid = cverow[0] vulnerable = False product_cursor = conn.execute( - "SELECT VERSION_START, OPERATOR_START, VERSION_END, OPERATOR_START FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", + "SELECT VERSION_START, OPERATOR_START, VERSION_END, OPERATOR_END FROM PRODUCTS WHERE ID IS ? AND PRODUCT IS ? AND VENDOR LIKE ?", (cveid, product, vendor), ) for row in product_cursor: (version_start, operator_start, version_end, operator_end) = row - vuln_status = CveStatus.CVE_STATUS_PATCHED vulnerable = cl.check_affected( version, diff --git a/scripts/lib/python/cve/plugin/eml_cve_plugin_base.py b/scripts/lib/python/cve/plugin/eml_cve_plugin_base.py index 3e9a032b..76b90b96 100644 --- a/scripts/lib/python/cve/plugin/eml_cve_plugin_base.py +++ b/scripts/lib/python/cve/plugin/eml_cve_plugin_base.py @@ -22,6 +22,7 @@ def __init__( bitbakeinfo: Any, installed_packages: PackageList, cve_products: CveProductList, + debian_codename: str, ): self.plugin_name = plugin_name self.plugin_priority = priority @@ -33,6 +34,7 @@ def __init__( self.plugin_name, self.plugin_priority ) self.cve_products = cve_products + self.debian_codename = debian_codename def update_database(self) -> bool: raise NotImplementedError("It must be implemented in your plugin module") diff --git a/scripts/lib/python/package_info.py b/scripts/lib/python/package_info.py index 9840332a..3809868a 100644 --- a/scripts/lib/python/package_info.py +++ b/scripts/lib/python/package_info.py @@ -104,6 +104,12 @@ def get_version(self, src_pkg_name: str) -> str: return str(self.packages[src_pkg_name][0].version) + def get_source_from(self, src_pkg_name: str) -> str: + if not src_pkg_name in self.packages: + return None + + return str(self.packages[src_pkg_name][0].source_from) + class PackageInfoHelper: @staticmethod