diff options
author | 0x4A6F <0x4A6F@users.noreply.github.com> | 2022-01-06 19:21:47 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-06 19:21:47 +0100 |
commit | 29acc14f0db8957855c1e629a105992a4c975100 (patch) | |
tree | 7980af52690dc26b93263efcfa53d18253cdf858 | |
parent | 7a022212c83000b4d56d93df4cd5610caa74e4b9 (diff) | |
parent | c8d137961d29b7ad1e9470718802209b0e636776 (diff) | |
download | nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar.gz nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar.bz2 nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar.lz nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar.xz nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.tar.zst nixpkgs-29acc14f0db8957855c1e629a105992a4c975100.zip |
Merge pull request #143060 from zhaofengli/binfmt-argv0
nixos/binfmt: Add QEMU wrapper to preserve argv[0]
-rw-r--r-- | nixos/modules/system/boot/binfmt.nix | 52 | ||||
-rw-r--r-- | nixos/tests/systemd-binfmt.nix | 106 | ||||
-rw-r--r-- | pkgs/applications/virtualization/qemu/binfmt-p-wrapper.c | 79 | ||||
-rw-r--r-- | pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nix | 31 | ||||
-rw-r--r-- | pkgs/top-level/all-packages.nix | 2 |
5 files changed, 246 insertions, 24 deletions
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index fdb4d0e4c7f..5bc603530f7 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -20,16 +20,20 @@ let optionalString fixBinary "F"; in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}"; - activationSnippet = name: { interpreter, ... }: '' + activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then '' rm -f /run/binfmt/${name} cat > /run/binfmt/${name} << 'EOF' #!${pkgs.bash}/bin/sh exec -- ${interpreter} "$@" EOF chmod +x /run/binfmt/${name} + '' else '' + rm -f /run/binfmt/${name} + ln -s ${interpreter} /run/binfmt/${name} ''; getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs; + getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch; # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from: # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix @@ -238,6 +242,25 @@ in { ''; type = types.bool; }; + + wrapInterpreterInShell = mkOption { + default = true; + description = '' + Whether to wrap the interpreter in a shell script. + + This allows a shell command to be set as the interpreter. + ''; + type = types.bool; + }; + + interpreterSandboxPath = mkOption { + internal = true; + default = null; + description = '' + Path of the interpreter to expose in the build sandbox. + ''; + type = types.nullOr types.path; + }; }; })); }; @@ -258,16 +281,37 @@ in { config = { boot.binfmt.registrations = builtins.listToAttrs (map (system: { name = system; - value = { + value = let interpreter = getEmulator system; + qemuArch = getQemuArch system; + + preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter; + interpreterReg = let + wrapperName = "qemu-${qemuArch}-binfmt-P"; + wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter; + in + if preserveArgvZero then "${wrapper}/bin/${wrapperName}" + else interpreter; + in { + inherit preserveArgvZero; + + interpreter = interpreterReg; + wrapInterpreterInShell = !preserveArgvZero; + interpreterSandboxPath = dirOf (dirOf interpreterReg); } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")); }) cfg.emulatedSystems); # TODO: add a nix.extraPlatforms option to NixOS! nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) '' extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")} ''; - nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) - ([ "/run/binfmt" "${pkgs.bash}" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems)); + nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) ( + let + ruleFor = system: cfg.registrations.${system}; + hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems; + in [ "/run/binfmt" ] + ++ lib.optional hasWrappedRule "${pkgs.bash}" + ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems) + ); environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)); diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix index 2a676f3da98..a3a6efac3e4 100644 --- a/nixos/tests/systemd-binfmt.nix +++ b/nixos/tests/systemd-binfmt.nix @@ -1,24 +1,90 @@ # Teach the kernel how to run armv7l and aarch64-linux binaries, # and run GNU Hello for these architectures. -import ./make-test-python.nix ({ pkgs, ... }: { - name = "systemd-binfmt"; - machine = { - boot.binfmt.emulatedSystems = [ - "armv7l-linux" - "aarch64-linux" - ]; - }; - testScript = let - helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello; - helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello; - in '' - machine.start() - assert "world" in machine.succeed( - "${helloArmv7l}/bin/hello" - ) - assert "world" in machine.succeed( - "${helloAarch64}/bin/hello" - ) +{ system ? builtins.currentSystem, + config ? {}, + pkgs ? import ../.. { inherit system config; } +}: + +with import ../lib/testing-python.nix { inherit system pkgs; }; + +let + expectArgv0 = xpkgs: xpkgs.runCommandCC "expect-argv0" { + src = pkgs.writeText "expect-argv0.c" '' + #include <stdio.h> + #include <string.h> + + int main(int argc, char **argv) { + fprintf(stderr, "Our argv[0] is %s\n", argv[0]); + + if (strcmp(argv[0], argv[1])) { + fprintf(stderr, "ERROR: argv[0] is %s, should be %s\n", argv[0], argv[1]); + return 1; + } + + return 0; + } + ''; + } '' + $CC -o $out $src ''; -}) +in { + basic = makeTest { + name = "systemd-binfmt"; + machine = { + boot.binfmt.emulatedSystems = [ + "armv7l-linux" + "aarch64-linux" + ]; + }; + + testScript = let + helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello; + helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello; + in '' + machine.start() + + assert "world" in machine.succeed( + "${helloArmv7l}/bin/hello" + ) + + assert "world" in machine.succeed( + "${helloAarch64}/bin/hello" + ) + ''; + }; + + preserveArgvZero = makeTest { + name = "systemd-binfmt-preserve-argv0"; + machine = { + boot.binfmt.emulatedSystems = [ + "aarch64-linux" + ]; + }; + testScript = let + testAarch64 = expectArgv0 pkgs.pkgsCross.aarch64-multiplatform; + in '' + machine.start() + machine.succeed("exec -a meow ${testAarch64} meow") + ''; + }; + + ldPreload = makeTest { + name = "systemd-binfmt-ld-preload"; + machine = { + boot.binfmt.emulatedSystems = [ + "aarch64-linux" + ]; + }; + testScript = let + helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello; + libredirectAarch64 = pkgs.pkgsCross.aarch64-multiplatform.libredirect; + in '' + machine.start() + + assert "error" not in machine.succeed( + "LD_PRELOAD='${libredirectAarch64}/lib/libredirect.so' ${helloAarch64}/bin/hello 2>&1" + ).lower() + ''; + }; +} diff --git a/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.c b/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.c new file mode 100644 index 00000000000..f956768862e --- /dev/null +++ b/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.c @@ -0,0 +1,79 @@ +// This is a tiny wrapper that converts the extra arv[0] argument +// from binfmt-misc with the P flag enabled to QEMU parameters. +// It also prevents LD_* environment variables from being applied +// to QEMU itself. + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifndef TARGET_QEMU +#error "Define TARGET_QEMU to be the path to the qemu-user binary (e.g., -DTARGET_QEMU=\"/full/path/to/qemu-riscv64\")" +#endif + +extern char **environ; + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "%s: This should be run as the binfmt interpreter with the P flag\n", argv[0]); + fprintf(stderr, "%s: My preconfigured qemu-user binary: %s\n", argv[0], TARGET_QEMU); + return 1; + } + + size_t environ_count = 0; + for (char **cur = environ; *cur != NULL; ++cur) { + environ_count++; + } + + size_t new_argc = 3; + size_t new_argv_alloc = argc + 2 * environ_count + 2; // [ "-E", env ] for each LD_* env + [ "-0", argv0 ] + char **new_argv = (char**)malloc((new_argv_alloc + 1) * sizeof(char*)); + if (!new_argv) { + fprintf(stderr, "FATAL: Failed to allocate new argv array\n"); + abort(); + } + + new_argv[0] = TARGET_QEMU; + new_argv[1] = "-0"; + new_argv[2] = argv[2]; + + // Pass all LD_ env variables as -E and strip them in `new_environ` + size_t new_environc = 0; + char **new_environ = (char**)malloc((environ_count + 1) * sizeof(char*)); + if (!new_environ) { + fprintf(stderr, "FATAL: Failed to allocate new environ array\n"); + abort(); + } + + for (char **cur = environ; *cur != NULL; ++cur) { + if (strncmp("LD_", *cur, 3) == 0) { + new_argv[new_argc++] = "-E"; + new_argv[new_argc++] = *cur; + } else { + new_environ[new_environc++] = *cur; + } + } + new_environ[new_environc] = NULL; + + size_t new_arg_start = new_argc; + new_argc += argc - 3 + 2; // [ "--", full_binary_path ] + + if (argc > 3) { + memcpy(&new_argv[new_arg_start + 2], &argv[3], (argc - 3) * sizeof(char**)); + } + + new_argv[new_arg_start] = "--"; + new_argv[new_arg_start + 1] = argv[1]; + new_argv[new_argc] = NULL; + +#ifdef DEBUG + for (size_t i = 0; i < new_argc; ++i) { + fprintf(stderr, "argv[%zu] = %s\n", i, new_argv[i]); + } +#endif + + return execve(new_argv[0], new_argv, new_environ); +} + +// vim: et:ts=4:sw=4 diff --git a/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nix b/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nix new file mode 100644 index 00000000000..fada1456929 --- /dev/null +++ b/pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nix @@ -0,0 +1,31 @@ +# binfmt preserve-argv[0] wrapper +# +# More details in binfmt-p-wrapper.c +# +# The wrapper has to be static so LD_* environment variables +# cannot affect the execution of the wrapper itself. + +{ lib, stdenv, pkgsStatic, enableDebug ? false }: + +name: emulator: + +pkgsStatic.stdenv.mkDerivation { + inherit name; + + src = ./binfmt-p-wrapper.c; + + dontUnpack = true; + dontInstall = true; + + buildPhase = '' + runHook preBuild + + mkdir -p $out/bin + $CC -o $out/bin/${name} -static -std=c99 -O2 \ + -DTARGET_QEMU=\"${emulator}\" \ + ${lib.optionalString enableDebug "-DDEBUG"} \ + $src + + runHook postBuild + ''; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index bc24b12640e..5d9ee548986 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -28115,6 +28115,8 @@ with pkgs; qemu-utils = callPackage ../applications/virtualization/qemu/utils.nix {}; + wrapQemuBinfmtP = callPackage ../applications/virtualization/qemu/binfmt-p-wrapper.nix { }; + qgis-unwrapped = libsForQt5.callPackage ../applications/gis/qgis/unwrapped.nix { withGrass = false; }; |