1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 inherit (lib)
9 mkOption
10 mkDefault
11 types
12 optionalString
13 ;
14
15 cfg = config.boot.binfmt;
16
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}";
45
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;
56
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 };
174
175in
176{
177 imports = [
178 (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
179 ];
180
181 options = {
182 boot.binfmt = {
183 registrations = mkOption {
184 default = { };
185
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 '';
190
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 };
204
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 };
210
211 magicOrExtension = mkOption {
212 description = "The magic number or extension to match on.";
213 type = types.str;
214 };
215
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 };
221
222 interpreter = mkOption {
223 description = ''
224 The interpreter to invoke to run the program.
225
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 };
232
233 preserveArgvZero = mkOption {
234 default = false;
235 description = ''
236 Whether to pass the original argv[0] to the interpreter.
237
238 See the description of the 'P' flag in the kernel docs
239 for more details;
240 '';
241 type = types.bool;
242 };
243
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 };
252
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).
259
260 See the description of the 'C' flag in the kernel docs
261 for more details.
262
263 Implies/requires openBinary = true.
264 '';
265 type = types.bool;
266 };
267
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.
274
275 See the description of the 'F' flag in the kernel docs
276 for more details.
277 '';
278 type = types.bool;
279 };
280
281 wrapInterpreterInShell = mkOption {
282 default = true;
283 description = ''
284 Whether to wrap the interpreter in a shell script.
285
286 This allows a shell command to be set as the interpreter.
287 '';
288 type = types.bool;
289 };
290
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 };
304
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 };
319
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 };
329
330 preferStaticEmulators = mkOption {
331 default = false;
332 description = ''
333 Whether to use static emulators when available.
334
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 };
344
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;
350
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);
363
364 inherit (elaborated) qemuArch;
365 isQemu = "qemu-${qemuArch}" == baseNameOf interpreter;
366
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;
377
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 ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
391 extra-sandbox-paths =
392 let
393 ruleFor = system: cfg.registrations.${system};
394 hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
395 in
396 [ "/run/binfmt" ]
397 ++ lib.optional hasWrappedRule "${pkgs.bash}"
398 ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
399 };
400
401 environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" (
402 lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)
403 );
404
405 systemd = lib.mkMerge [
406 ({
407 tmpfiles.rules = [
408 "d /run/binfmt 0755 -"
409 ]
410 ++ lib.mapAttrsToList (name: interpreter: "L+ /run/binfmt/${name} - - - - ${interpreter}") (
411 lib.mapAttrs mkInterpreter config.boot.binfmt.registrations
412 );
413 })
414
415 (lib.mkIf (config.boot.binfmt.registrations != { }) {
416 additionalUpstreamSystemUnits = [
417 "proc-sys-fs-binfmt_misc.automount"
418 "proc-sys-fs-binfmt_misc.mount"
419 "systemd-binfmt.service"
420 ];
421 services.systemd-binfmt.after = [ "systemd-tmpfiles-setup.service" ];
422 services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
423 })
424 ];
425 };
426}