1{ config, lib, pkgs, ... }:
2let
3 inherit (lib) mkOption mkDefault types optionalString;
4
5 cfg = config.boot.binfmt;
6
7 makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
8 , mask, preserveArgvZero, openBinary
9 , matchCredentials, fixBinary, ...
10 }: let
11 type = if recognitionType == "magic" then "M" else "E";
12 offset' = toString offset;
13 mask' = toString mask;
14 interpreter = "/run/binfmt/${name}";
15 flags = if !(matchCredentials -> openBinary)
16 then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
17 else optionalString preserveArgvZero "P" +
18 optionalString (openBinary && !matchCredentials) "O" +
19 optionalString matchCredentials "C" +
20 optionalString fixBinary "F";
21 in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
22
23 mkInterpreter = name: { interpreter, wrapInterpreterInShell, ... }:
24 if wrapInterpreterInShell
25 then pkgs.writeShellScript "${name}-interpreter" ''
26 #!${pkgs.bash}/bin/sh
27 exec -- ${interpreter} "$@"
28 ''
29 else interpreter;
30
31 getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
32 getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
33
34 # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
35 # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
36 # and
37 # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
38 # TODO: maybe put these in a JSON file?
39 magics = {
40 armv6l-linux = {
41 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
42 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
43 };
44 armv7l-linux = {
45 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
46 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
47 };
48 aarch64-linux = {
49 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
50 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
51 };
52 aarch64_be-linux = {
53 magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
54 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
55 };
56 i386-linux = {
57 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
58 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
59 };
60 i486-linux = {
61 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
62 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
63 };
64 i586-linux = {
65 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
66 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
67 };
68 i686-linux = {
69 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
70 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
71 };
72 x86_64-linux = {
73 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
74 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
75 };
76 alpha-linux = {
77 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
78 mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
79 };
80 sparc64-linux = {
81 magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
82 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
83 };
84 sparc-linux = {
85 magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
86 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
87 };
88 powerpc-linux = {
89 magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
90 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
91 };
92 powerpc64-linux = {
93 magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
94 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
95 };
96 powerpc64le-linux = {
97 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
98 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
99 };
100 mips-linux = {
101 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'';
102 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'';
103 };
104 mipsel-linux = {
105 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'';
106 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'';
107 };
108 mips64-linux = {
109 magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
110 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
111 };
112 mips64el-linux = {
113 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
114 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
115 };
116 mips64-linuxabin32 = {
117 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'';
118 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'';
119 };
120 mips64el-linuxabin32 = {
121 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'';
122 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'';
123 };
124 riscv32-linux = {
125 magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
126 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
127 };
128 riscv64-linux = {
129 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
130 mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
131 };
132 loongarch64-linux = {
133 magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01'';
134 mask = ''\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
135 };
136 wasm32-wasi = {
137 magicOrExtension = ''\x00asm'';
138 mask = ''\xff\xff\xff\xff'';
139 };
140 wasm64-wasi = {
141 magicOrExtension = ''\x00asm'';
142 mask = ''\xff\xff\xff\xff'';
143 };
144 x86_64-windows.magicOrExtension = "MZ";
145 i686-windows.magicOrExtension = "MZ";
146 };
147
148in {
149 imports = [
150 (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
151 ];
152
153 options = {
154 boot.binfmt = {
155 registrations = mkOption {
156 default = {};
157
158 description = ''
159 Extra binary formats to register with the kernel.
160 See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
161 '';
162
163 type = types.attrsOf (types.submodule ({ config, ... }: {
164 options = {
165 recognitionType = mkOption {
166 default = "magic";
167 description = "Whether to recognize executables by magic number or extension.";
168 type = types.enum [ "magic" "extension" ];
169 };
170
171 offset = mkOption {
172 default = null;
173 description = "The byte offset of the magic number used for recognition.";
174 type = types.nullOr types.int;
175 };
176
177 magicOrExtension = mkOption {
178 description = "The magic number or extension to match on.";
179 type = types.str;
180 };
181
182 mask = mkOption {
183 default = null;
184 description = "A mask to be ANDed with the byte sequence of the file before matching";
185 type = types.nullOr types.str;
186 };
187
188 interpreter = mkOption {
189 description = ''
190 The interpreter to invoke to run the program.
191
192 Note that the actual registration will point to
193 /run/binfmt/''${name}, so the kernel interpreter length
194 limit doesn't apply.
195 '';
196 type = types.path;
197 };
198
199 preserveArgvZero = mkOption {
200 default = false;
201 description = ''
202 Whether to pass the original argv[0] to the interpreter.
203
204 See the description of the 'P' flag in the kernel docs
205 for more details;
206 '';
207 type = types.bool;
208 };
209
210 openBinary = mkOption {
211 default = config.matchCredentials;
212 description = ''
213 Whether to pass the binary to the interpreter as an open
214 file descriptor, instead of a path.
215 '';
216 type = types.bool;
217 };
218
219 matchCredentials = mkOption {
220 default = false;
221 description = ''
222 Whether to launch with the credentials and security
223 token of the binary, not the interpreter (e.g. setuid
224 bit).
225
226 See the description of the 'C' flag in the kernel docs
227 for more details.
228
229 Implies/requires openBinary = true.
230 '';
231 type = types.bool;
232 };
233
234 fixBinary = mkOption {
235 default = false;
236 description = ''
237 Whether to open the interpreter file as soon as the
238 registration is loaded, rather than waiting for a
239 relevant file to be invoked.
240
241 See the description of the 'F' flag in the kernel docs
242 for more details.
243 '';
244 type = types.bool;
245 };
246
247 wrapInterpreterInShell = mkOption {
248 default = true;
249 description = ''
250 Whether to wrap the interpreter in a shell script.
251
252 This allows a shell command to be set as the interpreter.
253 '';
254 type = types.bool;
255 };
256
257 interpreterSandboxPath = mkOption {
258 internal = true;
259 default = null;
260 description = ''
261 Path of the interpreter to expose in the build sandbox.
262 '';
263 type = types.nullOr types.path;
264 };
265 };
266 }));
267 };
268
269 emulatedSystems = mkOption {
270 default = [];
271 example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
272 description = ''
273 List of systems to emulate. Will also configure Nix to
274 support your new systems.
275 Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
276 '';
277 type = types.listOf (types.enum (builtins.attrNames magics));
278 };
279 };
280 };
281
282 config = {
283 boot.binfmt.registrations = builtins.listToAttrs (map (system: assert system != pkgs.stdenv.hostPlatform.system; {
284 name = system;
285 value = { config, ... }: let
286 interpreter = getEmulator system;
287 qemuArch = getQemuArch system;
288
289 preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
290 interpreterReg = let
291 wrapperName = "qemu-${qemuArch}-binfmt-P";
292 wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
293 in
294 if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
295 else interpreter;
296 in ({
297 preserveArgvZero = mkDefault preserveArgvZero;
298
299 interpreter = mkDefault interpreterReg;
300 wrapInterpreterInShell = mkDefault (!config.preserveArgvZero);
301 interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
302 } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")));
303 }) cfg.emulatedSystems);
304 nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
305 extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
306 extra-sandbox-paths = let
307 ruleFor = system: cfg.registrations.${system};
308 hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
309 in [ "/run/binfmt" ]
310 ++ lib.optional hasWrappedRule "${pkgs.bash}"
311 ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
312 };
313
314 environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
315 (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
316
317 systemd = lib.mkMerge [
318 ({ tmpfiles.rules = [
319 "d /run/binfmt 0755 -"
320 ] ++ lib.mapAttrsToList
321 (name: interpreter:
322 "L+ /run/binfmt/${name} - - - - ${interpreter}"
323 )
324 (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations);
325 })
326
327 (lib.mkIf (config.boot.binfmt.registrations != {}) {
328 additionalUpstreamSystemUnits = [
329 "proc-sys-fs-binfmt_misc.automount"
330 "proc-sys-fs-binfmt_misc.mount"
331 "systemd-binfmt.service"
332 ];
333 services.systemd-binfmt.after = [ "systemd-tmpfiles-setup.service" ];
334 services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
335 })
336 ];
337 };
338}