All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] image-without-static-linkage: add class
@ 2022-07-04 16:25 Johannes Schilling
  2022-07-04 16:25 ` [PATCH 2/2] image-without-static-linkage: add selftest Johannes Schilling
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Johannes Schilling @ 2022-07-04 16:25 UTC (permalink / raw)
  To: yocto; +Cc: Johannes Schilling

This class provides a new image QA check that tries to detect static
linkage of a set of well-known libraries, leveraging the detectors from
cve-bin-tool[0].

To use in your project, provide a config file as described in the header
comment of the class, and inherit image-without-static-linkage in your
image recipe.

[0] https://github.com/intel/cve-bin-tool/tree/main/cve_bin_tool/checkers
---
 classes/image-without-static-linkage.bbclass  |  65 +++++++++
 .../python/python3-packaging_%.bbappend       |   1 +
 .../cve-bin-tool/cve-bin-tool-native.bb       |  34 +++++
 .../files/cve-bin-tool-static-linkage-checker | 126 ++++++++++++++++++
 4 files changed, 226 insertions(+)
 create mode 100644 classes/image-without-static-linkage.bbclass
 create mode 100644 recipes-devtools/python/python3-packaging_%.bbappend
 create mode 100644 recipes-security/cve-bin-tool/cve-bin-tool-native.bb
 create mode 100644 recipes-security/cve-bin-tool/files/cve-bin-tool-static-linkage-checker

diff --git a/classes/image-without-static-linkage.bbclass b/classes/image-without-static-linkage.bbclass
new file mode 100644
index 0000000..c6f2013
--- /dev/null
+++ b/classes/image-without-static-linkage.bbclass
@@ -0,0 +1,65 @@
+# Provide a QA check for statically linked copies of libraries.
+#
+# You need to provide a config file in TOML format and point the
+# variable `STATIC_LINKAGE_CHECK_CONFIG_FILE` to it.
+#
+# The file format is as follows
+# ```
+# [checkers]
+# modules = [
+#   # list of checker module names of cve-bin-tool checkers lib to
+#   # enable, i.e. file names in the cve_bin_tool/checkers subfolder.
+#   # https://github.com/intel/cve-bin-tool/tree/main/cve_bin_tool/checkers
+#   "librsvg",
+#   "zlib",
+# ]
+#
+# [exceptions]
+# ignore_dirs = [
+#   # list of directories, everything under these is completely ignored
+#   "/var/lib/opkg",
+# ]
+#
+# [exceptions.ignore_checks]
+#   # for each binary path, a list of checkers from the global list to
+#   # ignore for this binary (allowlist)
+#   "/bin/ary/name" = [ "zlib" ],
+# ```
+
+IMAGE_QA_COMMANDS += "image_check_static_linkage"
+
+DEPENDS += "cve-bin-tool-native"
+
+inherit python3native
+
+
+STATIC_LINKAGE_CUSTOM_ERROR_MESSAGE ??= ""
+
+python image_check_static_linkage() {
+    import json
+    from pathlib import Path
+    import subprocess
+
+    from oe.utils import ImageQAFailed
+
+    check_result = subprocess.check_output(["cve-bin-tool-static-linkage-checker",
+        "--config", d.getVar("STATIC_LINKAGE_CHECK_CONFIG_FILE"),
+        d.getVar("IMAGE_ROOTFS"),
+    ])
+    check_result = json.loads(check_result)
+
+    deploy_dir = Path(d.getVar("DEPLOYDIR"))
+    deploy_dir.mkdir(parents=True, exist_ok=True)
+    image_basename = d.getVar("IMAGE_BASENAME")
+    stats_filename = "static_linkage_stats-" + image_basename + ".json"
+    with open(deploy_dir / stats_filename, "w") as stats_out:
+        json.dump(check_result, stats_out)
+
+    binaries_with_violations = {k: v for k, v in check_result.items() if v}
+    if binaries_with_violations:
+        msg = "Static linkage check: found {} violations".format(len(binaries_with_violations))
+        for violator, violations in binaries_with_violations.items():
+            msg += "\n{}: {}".format(violator, violations)
+
+        raise ImageQAFailed(msg, image_check_static_linkage)
+}
diff --git a/recipes-devtools/python/python3-packaging_%.bbappend b/recipes-devtools/python/python3-packaging_%.bbappend
new file mode 100644
index 0000000..d6f5869
--- /dev/null
+++ b/recipes-devtools/python/python3-packaging_%.bbappend
@@ -0,0 +1 @@
+BBCLASSEXTEND += "native"
diff --git a/recipes-security/cve-bin-tool/cve-bin-tool-native.bb b/recipes-security/cve-bin-tool/cve-bin-tool-native.bb
new file mode 100644
index 0000000..3efbdf7
--- /dev/null
+++ b/recipes-security/cve-bin-tool/cve-bin-tool-native.bb
@@ -0,0 +1,34 @@
+SUMMARY = "Scanner for statically linked library copies"
+HOMEPAGE = "https://github.com/intel/cve-bin-tool"
+
+LICENSE = "GPL-3.0"
+LIC_FILES_CHKSUM = "file://LICENSE.md;md5=97a733ff40c50b4bfc74471e1f6ca88b"
+
+VERSION = "3.1"
+
+
+SRC_URI = "\
+    https://github.com/intel/cve-bin-tool/archive/refs/tags/v${VERSION}.tar.gz \
+    file://cve-bin-tool-static-linkage-checker \
+"
+
+SRC_URI[md5sum] = "af6958f8be7f7ce0d2b5ddffa34a1aee"
+SRC_URI[sha256sum] = "c4faaa401a2605a0d3f3c947deaf01cb56b4da927bfc29b5e959cde243bf5daf"
+
+inherit python3native native
+
+S = "${WORKDIR}/cve-bin-tool-3.1"
+inherit setuptools3
+
+RDEPENDS_${PN} = "\
+  python3-rich-native \
+  python3-packaging-native \
+"
+
+do_install:append() {
+  install -m 0755 "${WORKDIR}/cve-bin-tool-static-linkage-checker" "${D}${bindir}"
+}
+FILES-${PN}:append = "${bindir}/cve-bin-tool-static-linkage-checker"
+
+do_configure[noexec] = "1"
+do_compile[noexec] = "1"
diff --git a/recipes-security/cve-bin-tool/files/cve-bin-tool-static-linkage-checker b/recipes-security/cve-bin-tool/files/cve-bin-tool-static-linkage-checker
new file mode 100644
index 0000000..7da1b3b
--- /dev/null
+++ b/recipes-security/cve-bin-tool/files/cve-bin-tool-static-linkage-checker
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+
+from importlib import import_module
+from pathlib import Path
+
+import argparse
+import json
+import subprocess
+import toml
+
+
+def parse_args():
+    """
+    Parse command line arguments.
+    """
+    parser = argparse.ArgumentParser(
+        prog=sys.argv[0],
+        description="Checker for staticly linked copies of libraries",
+    )
+
+    parser.add_argument(
+        "directory",
+        help="Path to the directory to scan",
+    )
+
+    parser.add_argument(
+        "--config",
+        help="Path to the config file",
+        required=True,
+    )
+
+    return parser.parse_args()
+
+
+def list_input_files(rootdir):
+    """
+    Iterate over the input rootfs and find any file that is an executable ELF file, yielding their
+    names for the next step to iterate over.
+    """
+    import sys
+    with subprocess.Popen(
+        ["find", rootdir, "-type", "f", "-executable", "-printf", "/%P\\n"],
+        stdout=subprocess.PIPE,
+    ) as find:
+        for line in find.stdout:
+            executable_filename = line.decode().strip()
+            file_out = subprocess.check_output(["file", rootdir + executable_filename]).decode()
+            if "ELF " not in file_out:
+                continue
+
+            yield executable_filename
+
+
+# PurePath.is_relative_to was only added in python 3.9
+def _path_is_relative_to(subdir, base):
+    try:
+        subdir.relative_to(base)
+        return True
+    except ValueError:
+        return False
+
+
+def check_file(root_dir, filename, checkers, exceptions):
+    """
+    Check an executable file for traces of static linkage using all the checkers specified and
+    applying all exceptions specified.
+    """
+    full_filepath = root_dir + filename
+    strings_out = subprocess.check_output(["strings", full_filepath]).decode()
+
+    filepath = Path(filename)
+    if any(
+        _path_is_relative_to(Path(ex), filepath) for ex in exceptions["ignore_dirs"]
+    ):
+        return []
+
+    found_lib_versions = []
+    for checker_name, checker in checkers.items():
+        if filename in exceptions["ignore_checks"]:
+            if checker_name in exceptions["ignore_checks"][filename]:
+                continue
+
+        vi = checker().get_version(strings_out, filename)
+        if vi and vi["is_or_contains"] == "contains" and vi["version"] != "UNKNOWN":
+            found_lib_versions.append({checker_name: vi["version"]})
+
+    return found_lib_versions
+
+
+def _load_checker_class(mod_name):
+    """
+    Load a checker class given the module name.
+
+    The class and module name can be generated from each other (the setup.py file for cve-bin-tool
+    does the same), e.g. module `libjpeg_turbo` contains checker class `LibjpegTurboChecker`.
+    """
+    class_name = "".join(mod_name.replace("_", " ").title().split()) + "Checker"
+
+    mod = import_module(f"cve_bin_tool.checkers.{mod_name}")
+    return getattr(mod, class_name)
+
+
+def main():
+    """
+    Main entry point.
+    """
+    args = parse_args()
+    config = toml.load(args.config)
+
+    all_checkers = {
+        modname: _load_checker_class(modname)
+        for modname in config["checkers"]["modules"]
+    }
+
+    violations = {
+        f: check_file(args.directory, f, all_checkers, config["exceptions"])
+        for f in list_input_files(args.directory)
+    }
+
+    print(json.dumps(violations))
+
+
+if __name__ == "__main__":
+    import sys
+
+    sys.exit(main())
-- 
2.36.1



^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-07-18 13:42 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-07-04 16:25 [PATCH 1/2] image-without-static-linkage: add class Johannes Schilling
2022-07-04 16:25 ` [PATCH 2/2] image-without-static-linkage: add selftest Johannes Schilling
2022-07-05  8:05 ` [yocto] [PATCH 1/2] image-without-static-linkage: add class Alexandre Belloni
2022-07-05  8:47 ` Quentin Schulz
     [not found] ` <16FEAD00052C073D.2437@lists.yoctoproject.org>
2022-07-18 13:41   ` [yocto] [PATCHv2 " Schilling, Johannes
2022-07-18 13:42   ` [yocto] [PATCHv2 2/2] image-without-static-linkage: add selftest Schilling, Johannes

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.