diff --git a/lib/systems/default.nix b/lib/systems/default.nix index 901912ed4a50d..a6ceef2cc3a11 100644 --- a/lib/systems/default.nix +++ b/lib/systems/default.nix @@ -277,25 +277,6 @@ let let selectEmulator = pkgs: let - qemu-user = pkgs.qemu.override { - smartcardSupport = false; - spiceSupport = false; - openGLSupport = false; - virglSupport = false; - vncSupport = false; - gtkSupport = false; - sdlSupport = false; - alsaSupport = false; - pulseSupport = false; - pipewireSupport = false; - jackSupport = false; - smbdSupport = false; - seccompSupport = false; - tpmSupport = false; - capstoneSupport = false; - enableDocs = false; - hostCpuTargets = [ "${final.qemuArch}-linux-user" ]; - }; wine = (pkgs.winePackagesFor "wine${toString final.parsed.cpu.bits}").minimal; in # Note: we guarantee that the return value is either `null` or a path @@ -306,7 +287,7 @@ let else if final.isWindows then "${wine}/bin/wine${optionalString (final.parsed.cpu.bits == 64) "64"}" else if final.isLinux && pkgs.stdenv.hostPlatform.isLinux && final.qemuArch != null - then "${qemu-user}/bin/qemu-${final.qemuArch}" + then "${pkgs.qemu-user}/bin/qemu-${final.qemuArch}" else if final.isWasi then "${pkgs.wasmtime}/bin/wasmtime" else if final.isMmix @@ -315,6 +296,10 @@ let in { emulatorAvailable = pkgs: (selectEmulator pkgs) != null; + # whether final.emulator pkgs.pkgsStatic works + staticEmulatorAvailable = pkgs: final.emulatorAvailable pkgs + && (final.isLinux || final.isWasi || final.isMmix); + emulator = pkgs: if (final.emulatorAvailable pkgs) then selectEmulator pkgs diff --git a/lib/tests/systems.nix b/lib/tests/systems.nix index 03c5d68689624..f5e7bdd5b705b 100644 --- a/lib/tests/systems.nix +++ b/lib/tests/systems.nix @@ -96,6 +96,7 @@ lib.runTests ( canExecute = null; emulator = null; emulatorAvailable = null; + staticEmulatorAvailable = null; isCompatible = null; }?${platformAttrName}; }; diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index a0ef92c0505f4..5a4ee29dadfe6 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -28,8 +28,6 @@ let '' else interpreter; - 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 @@ -280,28 +278,50 @@ in { ''; type = types.listOf (types.enum (builtins.attrNames magics)); }; + + preferStaticEmulators = mkOption { + default = false; + description = '' + Whether to use static emulators when available. + + This enables the kernel to preload the emulator binaries when + the binfmt registrations are added, obviating the need to make + the emulator binaries available inside chroots and chroot-like + sandboxes. + ''; + type = types.bool; + }; }; }; config = { + assertions = lib.mapAttrsToList (name: reg: { + assertion = reg.fixBinary -> !reg.wrapInterpreterInShell; + message = "boot.binfmt.registrations.\"${name}\" cannot have fixBinary when the interpreter is invoked through a shell."; + }) cfg.registrations; + boot.binfmt.registrations = builtins.listToAttrs (map (system: assert system != pkgs.stdenv.hostPlatform.system; { name = system; value = { config, ... }: let - interpreter = getEmulator system; - qemuArch = getQemuArch system; + elaborated = lib.systems.elaborate { inherit system; }; + useStaticEmulator = cfg.preferStaticEmulators && elaborated.staticEmulatorAvailable pkgs; + interpreter = elaborated.emulator (if useStaticEmulator then pkgs.pkgsStatic else pkgs); + + inherit (elaborated) qemuArch; + isQemu = "qemu-${qemuArch}" == baseNameOf interpreter; - preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter; interpreterReg = let wrapperName = "qemu-${qemuArch}-binfmt-P"; wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter; in - if preserveArgvZero then "${wrapper}/bin/${wrapperName}" + if isQemu && !useStaticEmulator then "${wrapper}/bin/${wrapperName}" else interpreter; in ({ - preserveArgvZero = mkDefault preserveArgvZero; + preserveArgvZero = mkDefault isQemu; interpreter = mkDefault interpreterReg; - wrapInterpreterInShell = mkDefault (!config.preserveArgvZero); + fixBinary = mkDefault useStaticEmulator; + wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary); interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter)); } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))); }) cfg.emulatedSystems); diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix index b16fda0ddb1a6..a5e46a455d325 100644 --- a/nixos/tests/systemd-binfmt.nix +++ b/nixos/tests/systemd-binfmt.nix @@ -87,4 +87,29 @@ in { ).lower() ''; }; + + chroot = makeTest { + name = "systemd-binfmt-chroot"; + nodes.machine = { pkgs, lib, ... }: { + boot.binfmt.emulatedSystems = [ + "aarch64-linux" "wasm32-wasi" + ]; + boot.binfmt.preferStaticEmulators = true; + + environment.systemPackages = [ + (pkgs.writeShellScriptBin "test-chroot" '' + set -euo pipefail + mkdir -p /tmp/chroot + cp ${lib.getExe' pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.busybox "busybox"} /tmp/chroot/busybox + cp ${lib.getExe pkgs.pkgsCross.wasi32.yaml2json} /tmp/chroot/yaml2json # wasi binaries that build are hard to come by + chroot /tmp/chroot /busybox uname -m | grep aarch64 + echo 42 | chroot /tmp/chroot /yaml2json | grep 42 + '') + ]; + }; + testScript = '' + machine.start() + machine.succeed("test-chroot") + ''; + }; } diff --git a/pkgs/applications/virtualization/qemu/default.nix b/pkgs/applications/virtualization/qemu/default.nix index 8625069da8b19..3b797c957e83f 100644 --- a/pkgs/applications/virtualization/qemu/default.nix +++ b/pkgs/applications/virtualization/qemu/default.nix @@ -94,10 +94,9 @@ stdenv.mkDerivation (finalAttrs: { ++ lib.optionals stdenv.hostPlatform.isDarwin [ sigtool ] ++ lib.optionals (!userOnly) [ dtc ]; - buildInputs = [ zlib glib pixman - vde2 lzo snappy libtasn1 - gnutls nettle curl libslirp - ] + buildInputs = [ glib zlib ] + ++ lib.optionals (!minimal) [ dtc pixman vde2 lzo snappy libtasn1 gnutls nettle libslirp ] + ++ lib.optionals (!userOnly) [ curl ] ++ lib.optionals ncursesSupport [ ncurses ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ CoreServices Cocoa Hypervisor Kernel rez setfile vmnet ] ++ lib.optionals seccompSupport [ libseccomp ] @@ -112,8 +111,7 @@ stdenv.mkDerivation (finalAttrs: { ++ lib.optionals smartcardSupport [ libcacard ] ++ lib.optionals spiceSupport [ spice-protocol spice ] ++ lib.optionals usbredirSupport [ usbredir ] - ++ lib.optionals stdenv.hostPlatform.isLinux [ libcap_ng libcap attr ] - ++ lib.optionals (stdenv.hostPlatform.isLinux && !userOnly) [ libaio ] + ++ lib.optionals (stdenv.hostPlatform.isLinux && !userOnly) [ libcap_ng libcap attr libaio ] ++ lib.optionals xenSupport [ xen ] ++ lib.optionals cephSupport [ ceph ] ++ lib.optionals glusterfsSupport [ glusterfs libuuid ] @@ -124,8 +122,7 @@ stdenv.mkDerivation (finalAttrs: { ++ lib.optionals smbdSupport [ samba ] ++ lib.optionals uringSupport [ liburing ] ++ lib.optionals canokeySupport [ canokey-qemu ] - ++ lib.optionals capstoneSupport [ capstone ] - ++ lib.optionals (!userOnly) [ dtc ]; + ++ lib.optionals capstoneSupport [ capstone ]; dontUseMesonConfigure = true; # meson's configurePhase isn't compatible with qemu build dontAddStaticConfigureFlags = true;