From 9889f74a6b500e807f5d2f297700126537e94b6a Mon Sep 17 00:00:00 2001 From: Jakub Kadlcik Date: Feb 19 2023 22:33:02 +0000 Subject: ccpp: add automatical check for static executables I am not a C/C++ developer, so please correct me if the check isn't valid. But this is basically what I do during review - I find all executable files, run `file` on them and see if there is "dynamically linked" string present. I usually call also `ldd` on such executables but I omitted this to avoid too many bash calls. In order to implement this check, I needed to access the `rpms-unpacked` directory. This isn't done by any other plugin yet, and so far only (bash) scripts are doing such checks. I am intentionally trying to set a precedent here because I have some more checks on my mind and I would like to touch bash as little as possible. Ideally not at all. --- diff --git a/plugins/ccpp.py b/plugins/ccpp.py index 136a568..f228153 100644 --- a/plugins/ccpp.py +++ b/plugins/ccpp.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """ Test module for C/C++ based packages. """ +import os import re from FedoraReview import CheckBase, Mock, RegistryBase, RpmFile @@ -159,6 +160,39 @@ class CheckNoStaticExecutables(CCppCheckBase): self.automatic = False self.type = "MUST" + def run_on_applicable(self): + """Run the test, called if is_applicable() is True.""" + # Use already existing bash function to extract RPMs + cmd = 'env -i bash -c "source ./review-env.sh; unpack_rpms"' + self._run_cmd(cmd, shell=True) + + # If the RPMs weren't unpacked successfully, the reviewer will have to + # check them manually + rpms_unpacked = os.path.join(os.getcwd(), "rpms-unpacked") + if not os.path.exists(rpms_unpacked): + self.set_passed(self.PENDING) + return + + static = [] + for root, _, files in os.walk("."): + for path in files: + path = os.path.join(root, path) + if os.access(path, os.X_OK) and self._is_static(path): + static.append(os.path.basename(path)) + + if static: + msg = "Static executables found: {0}".format(", ".join(static)) + self.set_passed(self.FAIL, msg) + return + self.set_passed(self.PASS) + + def _is_static(self, path): + # If this turns out to be too slow, we should find a python alternative + stdout = self._run_cmd("file " + path) + if "LSB pie executable" not in stdout: + return False + return "dynamically linked" not in stdout + class CheckSoFiles(CCppCheckBase): """ diff --git a/src/FedoraReview/checks.py b/src/FedoraReview/checks.py index 942a0a6..b22b4d9 100644 --- a/src/FedoraReview/checks.py +++ b/src/FedoraReview/checks.py @@ -207,7 +207,15 @@ class _ChecksLoader(object): sys.path.insert(0, appdir) sys.path.insert(0, XdgDirs.app_datadir) plugins = load("plugins") - for plugin in sorted(plugins, key=lambda p: len(p.__name__)): + + # Run plugins in an alphabetical order with one exception. Let's put + # `shell_api` plugin on the first place, so it creates `review-env.sh` + # as soon as possible and other plugins can optionally use it. + plugins = sorted(plugins, key=lambda p: len(p.__name__)) + shell_api = [x.__name__ for x in plugins].index("plugins.shell_api") + plugins.insert(0, plugins.pop(shell_api)) + + for plugin in plugins: if plugin.__name__ == "plugins.plugins": continue registry = plugin.Registry(self) diff --git a/src/FedoraReview/helpers_mixin.py b/src/FedoraReview/helpers_mixin.py index 63e4f48..9e99e54 100644 --- a/src/FedoraReview/helpers_mixin.py +++ b/src/FedoraReview/helpers_mixin.py @@ -47,12 +47,14 @@ class HelpersMixin(object): except AttributeError: pass - def _run_cmd(self, cmd, header="Run command"): + def _run_cmd(self, cmd, header="Run command", shell=False): """Run a command using using subprocess, return output.""" self.log.debug(header + ": " + str(cmd)) - if isinstance(cmd, str): + if isinstance(cmd, str) and not shell: cmd = cmd.split(" ") - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) + proc = Popen(cmd, stdout=PIPE, stderr=PIPE, + universal_newlines=True, shell=shell) + output, error = "", "undefined" try: output, error = proc.communicate()