diff --git a/lib/systems/default.nix b/lib/systems/default.nix index f4784c61c6752..ebd01e6623ecb 100644 --- a/lib/systems/default.nix +++ b/lib/systems/default.nix @@ -213,6 +213,10 @@ rec { then selectEmulator pkgs else throw "Don't know how to run ${final.config} executables."; + staticEmulator = pkgs: + if builtins.elem final.system pkgs.qemu-user-static.passthru.qemuUserPlatforms then + "${pkgs.qemu-user-static}/bin/qemu-${final.qemuArch}" + else null; }) // mapAttrs (n: v: v final.parsed) inspect.predicates // mapAttrs (n: v: v final.gcc.arch or "default") architectures.predicates // args; diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index b003d983d2be2..22907a40b1017 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -34,6 +34,7 @@ let getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs; getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch; + getStaticEmulator = system: (lib.systems.elaborate { inherit system; }).staticEmulator pkgs; # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from: # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix @@ -279,29 +280,54 @@ in { ''; type = types.listOf types.str; }; + + preferStaticEmulators = mkOption { + default = false; + description = lib.mdDoc '' + 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: { name = system; value = { config, ... }: let - interpreter = getEmulator system; + staticEmulator = getStaticEmulator system; + interpreter = + if cfg.preferStaticEmulators && staticEmulator != null then staticEmulator + else getEmulator system; qemuArch = getQemuArch system; - preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter; + isQemu = "qemu-${qemuArch}" == baseNameOf interpreter; + isStaticEmulator = interpreter == staticEmulator; + interpreterReg = let wrapperName = "qemu-${qemuArch}-binfmt-P"; wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter; in - if preserveArgvZero then "${wrapper}/bin/${wrapperName}" + if isQemu && !isStaticEmulator then "${wrapper}/bin/${wrapperName}" else interpreter; in ({ - preserveArgvZero = mkDefault preserveArgvZero; + preserveArgvZero = mkDefault isQemu; interpreter = mkDefault interpreterReg; - wrapInterpreterInShell = mkDefault (!config.preserveArgvZero); - interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter)); + fixBinary = mkDefault isStaticEmulator; + wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary); + interpreterSandboxPath = mkDefault + (if config.fixBinary then null else dirOf (dirOf config.interpreter)); } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))); }) cfg.emulatedSystems); nix.settings = lib.mkIf (cfg.emulatedSystems != []) { @@ -309,9 +335,12 @@ in { extra-sandbox-paths = let ruleFor = system: cfg.registrations.${system}; hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems; - in [ "/run/binfmt" ] + hasNonFixedRule = lib.any (system: !(ruleFor system).fixBinary) cfg.emulatedSystems; + in [ ] + ++ lib.optional hasNonFixedRule "/run/binfmt" ++ lib.optional hasWrappedRule "${pkgs.bash}" - ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems); + ++ (lib.filter (p: p != null) + (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems)); }; environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" diff --git a/nixos/tests/systemd-binfmt.nix b/nixos/tests/systemd-binfmt.nix index b16fda0ddb1a6..faf9d40dddb7b 100644 --- a/nixos/tests/systemd-binfmt.nix +++ b/nixos/tests/systemd-binfmt.nix @@ -87,4 +87,27 @@ in { ).lower() ''; }; + + chroot = makeTest { + name = "systemd-binfmt-chroot"; + nodes.machine = { pkgs, ... }: { + boot.binfmt.emulatedSystems = [ + "aarch64-linux" + ]; + boot.binfmt.preferStaticEmulators = true; + + environment.systemPackages = [ + (pkgs.writeShellScriptBin "test-chroot" '' + set -euo pipefail + mkdir -p /tmp/chroot + cp ${pkgs.pkgsCross.aarch64-multiplatform.pkgsStatic.busybox}/bin/busybox /tmp/chroot/busybox + chroot /tmp/chroot /busybox uname -m | grep aarch64 + '') + ]; + }; + testScript = '' + machine.start() + machine.succeed("test-chroot") + ''; + }; } diff --git a/pkgs/applications/virtualization/qemu/aio-find-static-library.patch b/pkgs/applications/virtualization/qemu/aio-find-static-library.patch new file mode 100644 index 0000000000000..408f540c5cec8 --- /dev/null +++ b/pkgs/applications/virtualization/qemu/aio-find-static-library.patch @@ -0,0 +1,14 @@ +diff --git a/meson.build b/meson.build +index 96de1a6ef9..908d841f6a 100644 +--- a/meson.build ++++ b/meson.build +@@ -420,8 +420,7 @@ zlib = dependency('zlib', required: true, kwargs: static_kwargs) + libaio = not_found + if not get_option('linux_aio').auto() or have_block + libaio = cc.find_library('aio', has_headers: ['libaio.h'], +- required: get_option('linux_aio'), +- kwargs: static_kwargs) ++ required: get_option('linux_aio')) + endif + linux_io_uring = not_found + if not get_option('linux_io_uring').auto() or have_block diff --git a/pkgs/applications/virtualization/qemu/default.nix b/pkgs/applications/virtualization/qemu/default.nix index b4288cb7d7b5d..11844251ab9b0 100644 --- a/pkgs/applications/virtualization/qemu/default.nix +++ b/pkgs/applications/virtualization/qemu/default.nix @@ -28,7 +28,10 @@ , uringSupport ? stdenv.isLinux, liburing , canokeySupport ? false, canokey-qemu , capstoneSupport ? true, capstone +, pluginsSupport ? !stdenv.hostPlatform.isStatic , enableDocs ? true +, enableTools ? true +, enableBlobs ? true , hostCpuOnly ? false , hostCpuTargets ? (if hostCpuOnly then (lib.optional stdenv.isx86_64 "i386-softmmu" @@ -63,8 +66,9 @@ stdenv.mkDerivation rec { pkg-config flex bison meson ninja # Don't change this to python3 and python3.pkgs.*, breaks cross-compilation - python3Packages.python python3Packages.sphinx python3Packages.sphinx-rtd-theme + python3Packages.python ] + ++ lib.optionals enableDocs [ python3Packages.sphinx python3Packages.sphinx-rtd-theme ] ++ lib.optionals gtkSupport [ wrapGAppsHook ] ++ lib.optionals hexagonSupport [ glib ] ++ lib.optionals stdenv.isDarwin [ sigtool ]; @@ -99,6 +103,7 @@ stdenv.mkDerivation rec { ++ lib.optionals capstoneSupport [ capstone ]; dontUseMesonConfigure = true; # meson's configurePhase isn't compatible with qemu build + dontAddStaticConfigureFlags = true; outputs = [ "out" ] ++ lib.optional guestAgentSupport "ga"; # On aarch64-linux we would shoot over the Hydra's 2G output limit. @@ -127,7 +132,10 @@ stdenv.mkDerivation rec { revert = true; }) ] - ++ lib.optional nixosTestRunner ./force-uid0-on-9p.patch; + ++ lib.optional nixosTestRunner ./force-uid0-on-9p.patch + + # Remove for QEMU 8.1 + ++ lib.optional stdenv.hostPlatform.isStatic ./aio-find-static-library.patch; postPatch = '' # Otherwise tries to ensure /var/run exists. @@ -151,7 +159,7 @@ stdenv.mkDerivation rec { configureFlags = [ "--disable-strip" # We'll strip ourselves after separating debug info. (lib.enableFeature enableDocs "docs") - "--enable-tools" + (lib.enableFeature enableTools "tools") "--localstatedir=/var" "--sysconfdir=/etc" # Always use our Meson, not the bundled version, which doesn't @@ -178,7 +186,12 @@ stdenv.mkDerivation rec { ++ lib.optional smbdSupport "--smbd=${samba}/bin/smbd" ++ lib.optional uringSupport "--enable-linux-io-uring" ++ lib.optional canokeySupport "--enable-canokey" - ++ lib.optional capstoneSupport "--enable-capstone"; + ++ lib.optional capstoneSupport "--enable-capstone" + ++ lib.optional (!pluginsSupport) "--disable-plugins" + ++ lib.optional (!enableBlobs) "--disable-install-blobs" + + # FIXME: "multiple definition of `strtoll'" with libnbcompat + ++ lib.optional stdenv.hostPlatform.isStatic "--static --extra-ldflags=-Wl,--allow-multiple-definition"; dontWrapGApps = true; @@ -241,7 +254,9 @@ stdenv.mkDerivation rec { # Add a ‘qemu-kvm’ wrapper for compatibility/convenience. postInstall = '' - ln -s $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} $out/bin/qemu-kvm + if [ -f $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} ]; then + ln -s $out/bin/qemu-system-${stdenv.hostPlatform.qemuArch} $out/bin/qemu-kvm + fi ''; passthru = { diff --git a/pkgs/applications/virtualization/qemu/user-static.nix b/pkgs/applications/virtualization/qemu/user-static.nix new file mode 100644 index 0000000000000..51e983e75f100 --- /dev/null +++ b/pkgs/applications/virtualization/qemu/user-static.nix @@ -0,0 +1,52 @@ +{ lib, pkgsStatic }: + +let + qemuUserPlatforms = [ + "aarch64-linux" + "armv7l-linux" + "i386-linux" + "powerpc-linux" + "powerpc64-linux" + "powerpc64le-linux" + "riscv32-linux" + "riscv64-linux" + "x86_64-linux" + ]; + + qemuTargets = map (system: (lib.systems.elaborate { inherit system; }).qemuArch + "-linux-user") qemuUserPlatforms; + + static = (pkgsStatic.qemu.override { + guestAgentSupport = false; + numaSupport = false; + seccompSupport = false; + alsaSupport = false; + pulseSupport = false; + sdlSupport = false; + jackSupport = false; + gtkSupport = false; + vncSupport = false; + smartcardSupport = false; + spiceSupport = false; + ncursesSupport = false; + libiscsiSupport = false; + smbdSupport = false; + tpmSupport = false; + uringSupport = false; + capstoneSupport = false; + enableDocs = false; + enableTools = false; + enableBlobs = false; + hostCpuTargets = qemuTargets; + }).overrideAttrs (old: { + # HACK: Otherwise the result will have the entire buildinput closure + # injected by the pkgsStatic stdenv + # + postFixup = (old.postFixup or "") + '' + rm -f $out/nix-support/propagated-build-inputs + ''; + }); +in static // { + passthru = (static.passthru or {}) // { + inherit qemuUserPlatforms; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 26e08cfb69967..35a34c8c7d4cc 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -33652,6 +33652,8 @@ with pkgs; inherit (darwin) sigtool; }; + qemu-user-static = callPackage ../applications/virtualization/qemu/user-static.nix { }; + qemu-utils = callPackage ../applications/virtualization/qemu/utils.nix { }; canokey-qemu = callPackage ../applications/virtualization/qemu/canokey-qemu.nix { };