diff options
Diffstat (limited to 'nixos/modules/system/boot/binfmt.nix')
-rw-r--r-- | nixos/modules/system/boot/binfmt.nix | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix new file mode 100644 index 00000000000..33748358e45 --- /dev/null +++ b/nixos/modules/system/boot/binfmt.nix @@ -0,0 +1,325 @@ +{ config, lib, pkgs, ... }: +let + inherit (lib) mkOption types optionalString stringAfter; + + cfg = config.boot.binfmt; + + makeBinfmtLine = name: { recognitionType, offset, magicOrExtension + , mask, preserveArgvZero, openBinary + , matchCredentials, fixBinary, ... + }: let + type = if recognitionType == "magic" then "M" else "E"; + offset' = toString offset; + mask' = toString mask; + interpreter = "/run/binfmt/${name}"; + flags = if !(matchCredentials -> openBinary) + then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true." + else optionalString preserveArgvZero "P" + + optionalString (openBinary && !matchCredentials) "O" + + optionalString matchCredentials "C" + + optionalString fixBinary "F"; + in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}"; + + 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 + # and + # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh + # TODO: maybe put these in a JSON file? + magics = { + armv6l-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + armv7l-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + aarch64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff''; + }; + aarch64_be-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + i386-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i486-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i586-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + i686-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + x86_64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + alpha-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90''; + mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + sparc64-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + sparc-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc64-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + powerpc64le-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00''; + }; + mips-linux = { + magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + mipsel-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + mips64-linux = { + magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff''; + }; + mips64el-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + riscv32-linux = { + magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + riscv64-linux = { + magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00''; + mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff''; + }; + wasm32-wasi = { + magicOrExtension = ''\x00asm''; + mask = ''\xff\xff\xff\xff''; + }; + wasm64-wasi = { + magicOrExtension = ''\x00asm''; + mask = ''\xff\xff\xff\xff''; + }; + x86_64-windows = { + magicOrExtension = ".exe"; + recognitionType = "extension"; + }; + i686-windows = { + magicOrExtension = ".exe"; + recognitionType = "extension"; + }; + }; + +in { + imports = [ + (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ]) + ]; + + options = { + boot.binfmt = { + registrations = mkOption { + default = {}; + + description = '' + Extra binary formats to register with the kernel. + See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details. + ''; + + type = types.attrsOf (types.submodule ({ config, ... }: { + options = { + recognitionType = mkOption { + default = "magic"; + description = "Whether to recognize executables by magic number or extension."; + type = types.enum [ "magic" "extension" ]; + }; + + offset = mkOption { + default = null; + description = "The byte offset of the magic number used for recognition."; + type = types.nullOr types.int; + }; + + magicOrExtension = mkOption { + description = "The magic number or extension to match on."; + type = types.str; + }; + + mask = mkOption { + default = null; + description = + "A mask to be ANDed with the byte sequence of the file before matching"; + type = types.nullOr types.str; + }; + + interpreter = mkOption { + description = '' + The interpreter to invoke to run the program. + + Note that the actual registration will point to + /run/binfmt/''${name}, so the kernel interpreter length + limit doesn't apply. + ''; + type = types.path; + }; + + preserveArgvZero = mkOption { + default = false; + description = '' + Whether to pass the original argv[0] to the interpreter. + + See the description of the 'P' flag in the kernel docs + for more details; + ''; + type = types.bool; + }; + + openBinary = mkOption { + default = config.matchCredentials; + description = '' + Whether to pass the binary to the interpreter as an open + file descriptor, instead of a path. + ''; + type = types.bool; + }; + + matchCredentials = mkOption { + default = false; + description = '' + Whether to launch with the credentials and security + token of the binary, not the interpreter (e.g. setuid + bit). + + See the description of the 'C' flag in the kernel docs + for more details. + + Implies/requires openBinary = true. + ''; + type = types.bool; + }; + + fixBinary = mkOption { + default = false; + description = '' + Whether to open the interpreter file as soon as the + registration is loaded, rather than waiting for a + relevant file to be invoked. + + See the description of the 'F' flag in the kernel docs + for more details. + ''; + 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; + }; + }; + })); + }; + + emulatedSystems = mkOption { + default = []; + example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ]; + description = '' + List of systems to emulate. Will also configure Nix to + support your new systems. + Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation. + ''; + type = types.listOf types.str; + }; + }; + }; + + config = { + boot.binfmt.registrations = builtins.listToAttrs (map (system: { + name = system; + 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); + nix.settings = lib.mkIf (cfg.emulatedSystems != []) { + extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux"; + extra-sandbox-paths = 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)); + system.activationScripts.binfmt = stringAfter [ "specialfs" ] '' + mkdir -p -m 0755 /run/binfmt + ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)} + ''; + systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {}) [ + "proc-sys-fs-binfmt_misc.automount" + "proc-sys-fs-binfmt_misc.mount" + "systemd-binfmt.service" + ]; + }; +} |