git.iko.soy/typeable/nixpkgs
on master help sign in
Merge pull request #143060 from zhaofengli/binfmt-argv0

nixos/binfmt: Add QEMU wrapper to preserve argv[0]

commit29acc14f0db8957855c1e629a105992a4c975100 author0x4A6F <0x4A6F@users.noreply.github.com> dateThu 6 Jan 2022, 19:21 (5 years ago) parent7a02221c8d1379
5 files changed+246−24
nixos/modules/system/boot/binfmt.nix+48−4 nixos/tests/systemd-binfmt.nix+86−20 pkgs/applications/virtualization/qemu/binfmt-p-wrapper.c+79 pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nix+31 pkgs/top-level/all-packages.nix+2
nixos/modules/system/boot/binfmt.nix+48−4
@@ -20,16 +20,20 @@ let
2020 optionalString fixBinary "F";
2121 in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
2222
23− activationSnippet = name: { interpreter, ... }: ''
23+ activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
2424 rm -f /run/binfmt/${name}
2525 cat > /run/binfmt/${name} << 'EOF'
2626 #!${pkgs.bash}/bin/sh
2727 exec -- ${interpreter} "$@"
2828 EOF
2929 chmod +x /run/binfmt/${name}
30+ '' else ''
31+ rm -f /run/binfmt/${name}
32+ ln -s ${interpreter} /run/binfmt/${name}
3033 '';
3134
3235 getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
36+ getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
3337
3438 # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
3539 # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
@@ -238,6 +242,25 @@ in {
238242 '';
239243 type = types.bool;
240244 };
245+
246+ wrapInterpreterInShell = mkOption {
247+ default = true;
248+ description = ''
249+ Whether to wrap the interpreter in a shell script.
250+
251+ This allows a shell command to be set as the interpreter.
252+ '';
253+ type = types.bool;
254+ };
255+
256+ interpreterSandboxPath = mkOption {
257+ internal = true;
258+ default = null;
259+ description = ''
260+ Path of the interpreter to expose in the build sandbox.
261+ '';
262+ type = types.nullOr types.path;
263+ };
241264 };
242265 }));
243266 };
@@ -258,16 +281,37 @@ in {
258281 config = {
259282 boot.binfmt.registrations = builtins.listToAttrs (map (system: {
260283 name = system;
261− value = {
284+ value = let
262285 interpreter = getEmulator system;
286+ qemuArch = getQemuArch system;
287+
288+ preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
289+ interpreterReg = let
290+ wrapperName = "qemu-${qemuArch}-binfmt-P";
291+ wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
292+ in
293+ if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
294+ else interpreter;
295+ in {
296+ inherit preserveArgvZero;
297+
298+ interpreter = interpreterReg;
299+ wrapInterpreterInShell = !preserveArgvZero;
300+ interpreterSandboxPath = dirOf (dirOf interpreterReg);
263301 } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
264302 }) cfg.emulatedSystems);
265303 # TODO: add a nix.extraPlatforms option to NixOS!
266304 nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
267305 extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
268306 '';
269− nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != [])
270− ([ "/run/binfmt" "${pkgs.bash}" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems));
307+ nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != []) (
308+ let
309+ ruleFor = system: cfg.registrations.${system};
310+ hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
311+ in [ "/run/binfmt" ]
312+ ++ lib.optional hasWrappedRule "${pkgs.bash}"
313+ ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems)
314+ );
271315
272316 environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
273317 (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
nixos/tests/systemd-binfmt.nix+86−20
@@ -1,24 +1,90 @@
11# Teach the kernel how to run armv7l and aarch64-linux binaries,
22# and run GNU Hello for these architectures.
3−import ./make-test-python.nix ({ pkgs, ... }: {
4− name = "systemd-binfmt";
5− machine = {
6− boot.binfmt.emulatedSystems = [
7− "armv7l-linux"
8− "aarch64-linux"
9− ];
10− };
113
12− testScript = let
13− helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
14− helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
15− in ''
16− machine.start()
17− assert "world" in machine.succeed(
18− "${helloArmv7l}/bin/hello"
19− )
20− assert "world" in machine.succeed(
21− "${helloAarch64}/bin/hello"
22− )
4+{ system ? builtins.currentSystem,
5+ config ? {},
6+ pkgs ? import ../.. { inherit system config; }
7+}:
8+
9+with import ../lib/testing-python.nix { inherit system pkgs; };
10+
11+let
12+ expectArgv0 = xpkgs: xpkgs.runCommandCC "expect-argv0" {
13+ src = pkgs.writeText "expect-argv0.c" ''
14+ #include <stdio.h>
15+ #include <string.h>
16+
17+ int main(int argc, char **argv) {
18+ fprintf(stderr, "Our argv[0] is %s\n", argv[0]);
19+
20+ if (strcmp(argv[0], argv[1])) {
21+ fprintf(stderr, "ERROR: argv[0] is %s, should be %s\n", argv[0], argv[1]);
22+ return 1;
23+ }
24+
25+ return 0;
26+ }
27+ '';
28+ } ''
29+ $CC -o $out $src
2330 '';
24−})
31+in {
32+ basic = makeTest {
33+ name = "systemd-binfmt";
34+ machine = {
35+ boot.binfmt.emulatedSystems = [
36+ "armv7l-linux"
37+ "aarch64-linux"
38+ ];
39+ };
40+
41+ testScript = let
42+ helloArmv7l = pkgs.pkgsCross.armv7l-hf-multiplatform.hello;
43+ helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
44+ in ''
45+ machine.start()
46+
47+ assert "world" in machine.succeed(
48+ "${helloArmv7l}/bin/hello"
49+ )
50+
51+ assert "world" in machine.succeed(
52+ "${helloAarch64}/bin/hello"
53+ )
54+ '';
55+ };
56+
57+ preserveArgvZero = makeTest {
58+ name = "systemd-binfmt-preserve-argv0";
59+ machine = {
60+ boot.binfmt.emulatedSystems = [
61+ "aarch64-linux"
62+ ];
63+ };
64+ testScript = let
65+ testAarch64 = expectArgv0 pkgs.pkgsCross.aarch64-multiplatform;
66+ in ''
67+ machine.start()
68+ machine.succeed("exec -a meow ${testAarch64} meow")
69+ '';
70+ };
71+
72+ ldPreload = makeTest {
73+ name = "systemd-binfmt-ld-preload";
74+ machine = {
75+ boot.binfmt.emulatedSystems = [
76+ "aarch64-linux"
77+ ];
78+ };
79+ testScript = let
80+ helloAarch64 = pkgs.pkgsCross.aarch64-multiplatform.hello;
81+ libredirectAarch64 = pkgs.pkgsCross.aarch64-multiplatform.libredirect;
82+ in ''
83+ machine.start()
84+
85+ assert "error" not in machine.succeed(
86+ "LD_PRELOAD='${libredirectAarch64}/lib/libredirect.so' ${helloAarch64}/bin/hello 2>&1"
87+ ).lower()
88+ '';
89+ };
90+}
pkgs/applications/virtualization/qemu/binfmt-p-wrapper.cnew file+79
@@ -0,0 +1,79 @@
1+// This is a tiny wrapper that converts the extra arv[0] argument
2+// from binfmt-misc with the P flag enabled to QEMU parameters.
3+// It also prevents LD_* environment variables from being applied
4+// to QEMU itself.
5+
6+#include <stdio.h>
7+#include <stdlib.h>
8+#include <string.h>
9+#include <unistd.h>
10+
11+#ifndef TARGET_QEMU
12+#error "Define TARGET_QEMU to be the path to the qemu-user binary (e.g., -DTARGET_QEMU=\"/full/path/to/qemu-riscv64\")"
13+#endif
14+
15+extern char **environ;
16+
17+int main(int argc, char *argv[]) {
18+ if (argc < 3) {
19+ fprintf(stderr, "%s: This should be run as the binfmt interpreter with the P flag\n", argv[0]);
20+ fprintf(stderr, "%s: My preconfigured qemu-user binary: %s\n", argv[0], TARGET_QEMU);
21+ return 1;
22+ }
23+
24+ size_t environ_count = 0;
25+ for (char **cur = environ; *cur != NULL; ++cur) {
26+ environ_count++;
27+ }
28+
29+ size_t new_argc = 3;
30+ size_t new_argv_alloc = argc + 2 * environ_count + 2; // [ "-E", env ] for each LD_* env + [ "-0", argv0 ]
31+ char **new_argv = (char**)malloc((new_argv_alloc + 1) * sizeof(char*));
32+ if (!new_argv) {
33+ fprintf(stderr, "FATAL: Failed to allocate new argv array\n");
34+ abort();
35+ }
36+
37+ new_argv[0] = TARGET_QEMU;
38+ new_argv[1] = "-0";
39+ new_argv[2] = argv[2];
40+
41+ // Pass all LD_ env variables as -E and strip them in `new_environ`
42+ size_t new_environc = 0;
43+ char **new_environ = (char**)malloc((environ_count + 1) * sizeof(char*));
44+ if (!new_environ) {
45+ fprintf(stderr, "FATAL: Failed to allocate new environ array\n");
46+ abort();
47+ }
48+
49+ for (char **cur = environ; *cur != NULL; ++cur) {
50+ if (strncmp("LD_", *cur, 3) == 0) {
51+ new_argv[new_argc++] = "-E";
52+ new_argv[new_argc++] = *cur;
53+ } else {
54+ new_environ[new_environc++] = *cur;
55+ }
56+ }
57+ new_environ[new_environc] = NULL;
58+
59+ size_t new_arg_start = new_argc;
60+ new_argc += argc - 3 + 2; // [ "--", full_binary_path ]
61+
62+ if (argc > 3) {
63+ memcpy(&new_argv[new_arg_start + 2], &argv[3], (argc - 3) * sizeof(char**));
64+ }
65+
66+ new_argv[new_arg_start] = "--";
67+ new_argv[new_arg_start + 1] = argv[1];
68+ new_argv[new_argc] = NULL;
69+
70+#ifdef DEBUG
71+ for (size_t i = 0; i < new_argc; ++i) {
72+ fprintf(stderr, "argv[%zu] = %s\n", i, new_argv[i]);
73+ }
74+#endif
75+
76+ return execve(new_argv[0], new_argv, new_environ);
77+}
78+
79+// vim: et:ts=4:sw=4
pkgs/applications/virtualization/qemu/binfmt-p-wrapper.nixnew file+31
@@ -0,0 +1,31 @@
1+# binfmt preserve-argv[0] wrapper
2+#
3+# More details in binfmt-p-wrapper.c
4+#
5+# The wrapper has to be static so LD_* environment variables
6+# cannot affect the execution of the wrapper itself.
7+
8+{ lib, stdenv, pkgsStatic, enableDebug ? false }:
9+
10+name: emulator:
11+
12+pkgsStatic.stdenv.mkDerivation {
13+ inherit name;
14+
15+ src = ./binfmt-p-wrapper.c;
16+
17+ dontUnpack = true;
18+ dontInstall = true;
19+
20+ buildPhase = ''
21+ runHook preBuild
22+
23+ mkdir -p $out/bin
24+ $CC -o $out/bin/${name} -static -std=c99 -O2 \
25+ -DTARGET_QEMU=\"${emulator}\" \
26+ ${lib.optionalString enableDebug "-DDEBUG"} \
27+ $src
28+
29+ runHook postBuild
30+ '';
31+}
pkgs/top-level/all-packages.nix+2
@@ -28115,6 +28115,8 @@ with pkgs;
2811528115
2811628116 qemu-utils = callPackage ../applications/virtualization/qemu/utils.nix {};
2811728117
28118+ wrapQemuBinfmtP = callPackage ../applications/virtualization/qemu/binfmt-p-wrapper.nix { };
28119+
2811828120 qgis-unwrapped = libsForQt5.callPackage ../applications/gis/qgis/unwrapped.nix {
2811928121 withGrass = false;
2812028122 };