From 494ed4d6ee6f3d3b25a0b5d45f3f9e8dd1c45ccf Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Sat, 26 Dec 2020 16:55:33 +0100 Subject: [PATCH 1/2] systemd: patch runtime dlopen calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures that all the features that are implemented via dlopen(3) are available (or explicitly deactivated) by pointing dlopen to the absolute store path instead of relying on the linkers runtime lookup code. All of the dlopen calls have to be handled. When new ones are introduced by upstream (or one of our patches) those must be explicitly declared, otherwise the build will fail. As of systemd version 247 we've seen a few errors like `libpcre2.… not found` when using e.g. --grep with journalctl. Those errors should become less unexpected now. There are generally two classes of dlopen calls. Those that we want to support and those that should be deactivated / unsupported. This change enforces that we handle all dlopen calls explicitly. Meaning: There is not a single dlopen call in the code source tree that we did not explicitly handle. In order to do this I introduced a list of attributes that maps from shared object name to the package that contains them. The package can be null meaning the reference should be nuked and the shared object will never be loadable during runtime (because it points at an invalid store path location). --- pkgs/os-specific/linux/systemd/default.nix | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index e6cb589c9bfe..2822bffdb51c 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -160,6 +160,90 @@ stdenv.mkDerivation { --replace \ "find_program('objcopy'" \ "find_program('${stdenv.cc.bintools.targetPrefix}objcopy'" + '' + (let + + # The folllowing dlopen patches ensure that all the features that are + # implemented via dlopen(3) are available (or explicitly deactivated) by + # pointing dlopen to the absolute store path instead of relying on the + # linkers runtime lookup code. + # + # All of the dlopen calls have to be handled. When new ones are introduced + # by upstream (or one of our patches) they must be explicitly declared, + # otherwise the build will fail. + # + # As of systemd version 247 we've seen a few errors like `libpcre2.… not + # found` when using e.g. --grep with journalctl. Those errors should + # become less unexpected now. + # + # There are generally two classes of dlopen(3) calls. Those that we want to + # support and those that should be deactivated / unsupported. This change + # enforces that we handle all dlopen calls explicitly. Meaning: There is + # not a single dlopen call in the source code tree that we did not + # explicitly handle. + # + # In order to do this we introduced a list of attributes that maps from + # shared object name to the package that contains them. The package can be + # null meaning the reference should be nuked and the shared object will + # never be loadable during runtime (because it points at an invalid store + # path location). + # + # To get a list of dynamically loaded libraries issue something like + # `grep -ri 'dlopen("lib' $src` and update the below list. + dlopenLibs = [ + # We did never provide support for libxkbcommon & qrencode + { name = "libxkbcommon.so.0"; pkg = null; } + { name = "libqrencode.so.4"; pkg = null; } + + # We did not provide libpwquality before so it is safe to disable it for + # now. + { name = "libpwquality.so.1"; pkg = null; } + + # Only include cryptsetup if it is enabled. We might not be able to + # provide it during "bootstrap" in e.g. the minimal systemd build as + # cryptsetup has udev (aka systemd) in it's dependencies. + { name = "libcryptsetup.so.12"; pkg = if withCryptsetup then cryptsetup else null; } + + # We are using libidn2 so we only provide that and ignore the others. + # Systemd does this decision during configure time and uses ifdef's to + # enable specific branches. We can safely ignore (nuke) the libidn "v1" + # libraries. + { name = "libidn2.so.0"; pkg = libidn2; } + { name = "libidn.so.12"; pkg = null; } + { name = "libidn.so.11"; pkg = null; } + + # journalctl --grep requires libpcre so lets provide it + { name = "libpcre2-8.so.0"; pkg = pcre2; } + ]; + + patchDlOpen = dl: let + library = "${lib.makeLibraryPath [dl.pkg]}/${dl.name}"; + in if dl.pkg == null then '' + # remove the dependency on the library by replacing it with an invalid path + for file in $(grep -lr 'dlopen("${dl.name}"' src); do + echo "patching dlopen(\"${dl.name}\", …) in $file to an invalid store path ("/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-not-implemented/${dl.name}")…" + substituteInPlace "$file" --replace 'dlopen("${dl.name}"' 'dlopen("/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-not-implemented/${dl.name}"' + done + '' else '' + # ensure that the library we provide actually exists + if ! [ -e ${library} ]; then + echo 'The shared library `${library}` does not exist but was given as subtitute for `${dl.name}`' + exit 1 + fi + # make the path to the dependency explicit + for file in $(grep -lr 'dlopen("${dl.name}"' src); do + echo "patching dlopen(\"${dl.name}\", …) in $file to ${library}…" + substituteInPlace "$file" --replace 'dlopen("${dl.name}"' 'dlopen("${library}"' + done + ''; + in # patch all the dlopen calls to contain absolute paths to the libraries + lib.concatMapStringsSep "\n" patchDlOpen dlopenLibs) + # finally ensure that there are no left-over dlopen calls that we didn't handle + + '' + if grep -qr 'dlopen("[^/]' src; then + echo "Found unhandled dlopen calls: " + grep -r 'dlopen("[^/]' src + exit 1 + fi ''; outputs = [ "out" "man" "dev" ]; From 37539e776833f9d89c8bdff95208a09d5eb0e30b Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Sat, 26 Dec 2020 17:03:34 +0100 Subject: [PATCH 2/2] nixos/tests/systemd-journal: ensure that --grep works Previously, after the version bump to v247, we broke journalctl --grep as libpcre2 was lazily loaded during runtime using dlopen(3). This ensures that we have a test case that alerts us when it fails again. --- nixos/tests/systemd-journal.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/tests/systemd-journal.nix b/nixos/tests/systemd-journal.nix index c50c151ae10d..b0a3f62ce1cb 100644 --- a/nixos/tests/systemd-journal.nix +++ b/nixos/tests/systemd-journal.nix @@ -13,6 +13,8 @@ import ./make-test-python.nix ({ pkgs, ... }: testScript = '' machine.wait_for_unit("multi-user.target") + machine.succeed("journalctl --grep=systemd") + machine.succeed( "${pkgs.curl}/bin/curl -s localhost:19531/machine | ${pkgs.jq}/bin/jq -e '.hostname == \"machine\"'" )