summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
author0x4A6F <0x4A6F@users.noreply.github.com>2022-01-06 19:21:47 +0100
committerGitHub <noreply@github.com>2022-01-06 19:21:47 +0100
commit29acc14f0db8957855c1e629a105992a4c975100 (patch)
tree7980af52690dc26b93263efcfa53d18253cdf858 /nixos
parent7a022212c83000b4d56d93df4cd5610caa74e4b9 (diff)
parentc8d137961d29b7ad1e9470718802209b0e636776 (diff)
downloadnixpkgs-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]
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/system/boot/binfmt.nix52
-rw-r--r--nixos/tests/systemd-binfmt.nix106
2 files changed, 134 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()
+    '';
+  };
+}