nixos/README.md: relax the requirement of providing option defaults (#334509)
[NixPkgs.git] / nixos / modules / system / boot / binfmt.nix
blobe7c914954f1eb0265fa978d55b40ddb0a3bc17e6
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   inherit (lib)
9     mkOption
10     mkDefault
11     types
12     optionalString
13     ;
15   cfg = config.boot.binfmt;
17   makeBinfmtLine =
18     name:
19     {
20       recognitionType,
21       offset,
22       magicOrExtension,
23       mask,
24       preserveArgvZero,
25       openBinary,
26       matchCredentials,
27       fixBinary,
28       ...
29     }:
30     let
31       type = if recognitionType == "magic" then "M" else "E";
32       offset' = toString offset;
33       mask' = toString mask;
34       interpreter = "/run/binfmt/${name}";
35       flags =
36         if !(matchCredentials -> openBinary) then
37           throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
38         else
39           optionalString preserveArgvZero "P"
40           + optionalString (openBinary && !matchCredentials) "O"
41           + optionalString matchCredentials "C"
42           + optionalString fixBinary "F";
43     in
44     ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
46   mkInterpreter =
47     name:
48     { interpreter, wrapInterpreterInShell, ... }:
49     if wrapInterpreterInShell then
50       pkgs.writeShellScript "${name}-interpreter" ''
51         #!${pkgs.bash}/bin/sh
52         exec -- ${interpreter} "$@"
53       ''
54     else
55       interpreter;
57   # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
58   # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
59   # and
60   # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
61   # TODO: maybe put these in a JSON file?
62   magics = {
63     armv6l-linux = {
64       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
65       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
66     };
67     armv7l-linux = {
68       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
69       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
70     };
71     aarch64-linux = {
72       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
73       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
74     };
75     aarch64_be-linux = {
76       magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
77       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
78     };
79     i386-linux = {
80       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
81       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
82     };
83     i486-linux = {
84       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
85       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
86     };
87     i586-linux = {
88       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
89       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
90     };
91     i686-linux = {
92       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
93       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
94     };
95     x86_64-linux = {
96       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
97       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
98     };
99     alpha-linux = {
100       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
101       mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
102     };
103     sparc64-linux = {
104       magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
105       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
106     };
107     sparc-linux = {
108       magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
109       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
110     };
111     powerpc-linux = {
112       magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
113       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
114     };
115     powerpc64-linux = {
116       magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
117       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
118     };
119     powerpc64le-linux = {
120       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
121       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
122     };
123     mips-linux = {
124       magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'';
125       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20'';
126     };
127     mipsel-linux = {
128       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'';
129       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00'';
130     };
131     mips64-linux = {
132       magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
133       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
134     };
135     mips64el-linux = {
136       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
137       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
138     };
139     mips64-linuxabin32 = {
140       magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20'';
141       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20'';
142     };
143     mips64el-linuxabin32 = {
144       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00'';
145       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00'';
146     };
147     riscv32-linux = {
148       magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
149       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
150     };
151     riscv64-linux = {
152       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
153       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
154     };
155     loongarch64-linux = {
156       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01'';
157       mask = ''\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
158     };
159     wasm32-wasi = {
160       magicOrExtension = ''\x00asm'';
161       mask = ''\xff\xff\xff\xff'';
162     };
163     wasm64-wasi = {
164       magicOrExtension = ''\x00asm'';
165       mask = ''\xff\xff\xff\xff'';
166     };
167     s390x-linux = {
168       magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16'';
169       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
170     };
171     x86_64-windows.magicOrExtension = "MZ";
172     i686-windows.magicOrExtension = "MZ";
173   };
177   imports = [
178     (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
179   ];
181   options = {
182     boot.binfmt = {
183       registrations = mkOption {
184         default = { };
186         description = ''
187           Extra binary formats to register with the kernel.
188           See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
189         '';
191         type = types.attrsOf (
192           types.submodule (
193             { config, ... }:
194             {
195               options = {
196                 recognitionType = mkOption {
197                   default = "magic";
198                   description = "Whether to recognize executables by magic number or extension.";
199                   type = types.enum [
200                     "magic"
201                     "extension"
202                   ];
203                 };
205                 offset = mkOption {
206                   default = null;
207                   description = "The byte offset of the magic number used for recognition.";
208                   type = types.nullOr types.int;
209                 };
211                 magicOrExtension = mkOption {
212                   description = "The magic number or extension to match on.";
213                   type = types.str;
214                 };
216                 mask = mkOption {
217                   default = null;
218                   description = "A mask to be ANDed with the byte sequence of the file before matching";
219                   type = types.nullOr types.str;
220                 };
222                 interpreter = mkOption {
223                   description = ''
224                     The interpreter to invoke to run the program.
226                     Note that the actual registration will point to
227                     /run/binfmt/''${name}, so the kernel interpreter length
228                     limit doesn't apply.
229                   '';
230                   type = types.path;
231                 };
233                 preserveArgvZero = mkOption {
234                   default = false;
235                   description = ''
236                     Whether to pass the original argv[0] to the interpreter.
238                     See the description of the 'P' flag in the kernel docs
239                     for more details;
240                   '';
241                   type = types.bool;
242                 };
244                 openBinary = mkOption {
245                   default = config.matchCredentials;
246                   description = ''
247                     Whether to pass the binary to the interpreter as an open
248                     file descriptor, instead of a path.
249                   '';
250                   type = types.bool;
251                 };
253                 matchCredentials = mkOption {
254                   default = false;
255                   description = ''
256                     Whether to launch with the credentials and security
257                     token of the binary, not the interpreter (e.g. setuid
258                     bit).
260                     See the description of the 'C' flag in the kernel docs
261                     for more details.
263                     Implies/requires openBinary = true.
264                   '';
265                   type = types.bool;
266                 };
268                 fixBinary = mkOption {
269                   default = false;
270                   description = ''
271                     Whether to open the interpreter file as soon as the
272                     registration is loaded, rather than waiting for a
273                     relevant file to be invoked.
275                     See the description of the 'F' flag in the kernel docs
276                     for more details.
277                   '';
278                   type = types.bool;
279                 };
281                 wrapInterpreterInShell = mkOption {
282                   default = true;
283                   description = ''
284                     Whether to wrap the interpreter in a shell script.
286                     This allows a shell command to be set as the interpreter.
287                   '';
288                   type = types.bool;
289                 };
291                 interpreterSandboxPath = mkOption {
292                   internal = true;
293                   default = null;
294                   description = ''
295                     Path of the interpreter to expose in the build sandbox.
296                   '';
297                   type = types.nullOr types.path;
298                 };
299               };
300             }
301           )
302         );
303       };
305       emulatedSystems = mkOption {
306         default = [ ];
307         example = [
308           "wasm32-wasi"
309           "x86_64-windows"
310           "aarch64-linux"
311         ];
312         description = ''
313           List of systems to emulate. Will also configure Nix to
314           support your new systems.
315           Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
316         '';
317         type = types.listOf (types.enum (builtins.attrNames magics));
318       };
320       addEmulatedSystemsToNixSandbox = mkOption {
321         type = types.bool;
322         default = true;
323         example = false;
324         description = ''
325           Whether to add the {option}`boot.binfmt.emulatedSystems` to {option}`nix.settings.extra-platforms`.
326           Disable this to use remote builders for those platforms, while allowing testing binaries locally.
327         '';
328       };
330       preferStaticEmulators = mkOption {
331         default = false;
332         description = ''
333           Whether to use static emulators when available.
335           This enables the kernel to preload the emulator binaries when
336           the binfmt registrations are added, obviating the need to make
337           the emulator binaries available inside chroots and chroot-like
338           sandboxes.
339         '';
340         type = types.bool;
341       };
342     };
343   };
345   config = {
346     assertions = lib.mapAttrsToList (name: reg: {
347       assertion = reg.fixBinary -> !reg.wrapInterpreterInShell;
348       message = "boot.binfmt.registrations.\"${name}\" cannot have fixBinary when the interpreter is invoked through a shell.";
349     }) cfg.registrations;
351     boot.binfmt.registrations = builtins.listToAttrs (
352       map (
353         system:
354         assert system != pkgs.stdenv.hostPlatform.system;
355         {
356           name = system;
357           value =
358             { config, ... }:
359             let
360               elaborated = lib.systems.elaborate { inherit system; };
361               useStaticEmulator = cfg.preferStaticEmulators && elaborated.staticEmulatorAvailable pkgs;
362               interpreter = elaborated.emulator (if useStaticEmulator then pkgs.pkgsStatic else pkgs);
364               inherit (elaborated) qemuArch;
365               isQemu = "qemu-${qemuArch}" == baseNameOf interpreter;
367               interpreterReg =
368                 let
369                   wrapperName = "qemu-${qemuArch}-binfmt-P";
370                   wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
371                 in
372                 if isQemu && !useStaticEmulator then "${wrapper}/bin/${wrapperName}" else interpreter;
373             in
374             (
375               {
376                 preserveArgvZero = mkDefault isQemu;
378                 interpreter = mkDefault interpreterReg;
379                 fixBinary = mkDefault useStaticEmulator;
380                 wrapInterpreterInShell = mkDefault (!config.preserveArgvZero && !config.fixBinary);
381                 interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
382               }
383               // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"))
384             );
385         }
386       ) cfg.emulatedSystems
387     );
388     nix.settings = lib.mkIf (cfg.addEmulatedSystemsToNixSandbox && cfg.emulatedSystems != [ ]) {
389       extra-platforms =
390         cfg.emulatedSystems
391         ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
392       extra-sandbox-paths =
393         let
394           ruleFor = system: cfg.registrations.${system};
395           hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
396         in
397         [ "/run/binfmt" ]
398         ++ lib.optional hasWrappedRule "${pkgs.bash}"
399         ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
400     };
402     environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" (
403       lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)
404     );
406     systemd = lib.mkMerge [
407       ({
408         tmpfiles.rules =
409           [
410             "d /run/binfmt 0755 -"
411           ]
412           ++ lib.mapAttrsToList (name: interpreter: "L+ /run/binfmt/${name} - - - - ${interpreter}") (
413             lib.mapAttrs mkInterpreter config.boot.binfmt.registrations
414           );
415       })
417       (lib.mkIf (config.boot.binfmt.registrations != { }) {
418         additionalUpstreamSystemUnits = [
419           "proc-sys-fs-binfmt_misc.automount"
420           "proc-sys-fs-binfmt_misc.mount"
421           "systemd-binfmt.service"
422         ];
423         services.systemd-binfmt.after = [ "systemd-tmpfiles-setup.service" ];
424         services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
425       })
426     ];
427   };