Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 5 additions & 20 deletions lib/systems/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/tests/systems.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ lib.runTests (
canExecute = null;
emulator = null;
emulatorAvailable = null;
staticEmulatorAvailable = null;
isCompatible = null;
}?${platformAttrName};
};
Expand Down
36 changes: 28 additions & 8 deletions nixos/modules/system/boot/binfmt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
25 changes: 25 additions & 0 deletions nixos/tests/systemd-binfmt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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")
'';
};
}
13 changes: 5 additions & 8 deletions pkgs/applications/virtualization/qemu/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Comment on lines 97 to 99
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really sure how to verify this but disabling features we don't need is probably a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I did:

  1. Verify manually that for !minimal && !userOnly (i.e. all the normal qemus like qemu, qemu-full, qemu_kvm…), only the order of inputs changes (which is hopefully irrelevant).
  2. qemu-user working as intended is covered by a test added here
  3. For qemu-utils:
    • nix build github:NixOS/nixpkgs/{c46d43c625f161d3bd22052a5f8f9df703d81be8,f69411bcfb0dd3565a19667fdf509bbadb38f008}#qemu-utils
    • The sizes of the output binaries are all the same: ls -l result*/bin/* | cut -d\ -f5- | sort, so it's unlikely that some larger functionality went missing
    • Looking e.g. through nix run nixpkgs#diffoscope result*/bin/qemu-storage-daemon shows some probably irrelevant differences in the length of some dummy strings (Probably due to error: derivation '…-qemu-utils-9.0.2.drv' may not be deterministic: output '…-qemu-utils-9.0.2' differs -.-)

++ lib.optionals ncursesSupport [ ncurses ]
++ lib.optionals stdenv.hostPlatform.isDarwin [ CoreServices Cocoa Hypervisor Kernel rez setfile vmnet ]
++ lib.optionals seccompSupport [ libseccomp ]
Expand All @@ -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 ]
Expand All @@ -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;
Expand Down