1{ config, lib, pkgs, ... }:
2let
3 inherit (lib) mkOption mkDefault types optionalString stringAfter;
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 = lib.mdDoc ''
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 = lib.mdDoc "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 = lib.mdDoc "The byte offset of the magic number used for recognition.";
174 type = types.nullOr types.int;
175 };
176
177 magicOrExtension = mkOption {
178 description = lib.mdDoc "The magic number or extension to match on.";
179 type = types.str;
180 };
181
182 mask = mkOption {
183 default = null;
184 description =
185 lib.mdDoc "A mask to be ANDed with the byte sequence of the file before matching";
186 type = types.nullOr types.str;
187 };
188
189 interpreter = mkOption {
190 description = lib.mdDoc ''
191 The interpreter to invoke to run the program.
192
193 Note that the actual registration will point to
194 /run/binfmt/''${name}, so the kernel interpreter length
195 limit doesn't apply.
196 '';
197 type = types.path;
198 };
199
200 preserveArgvZero = mkOption {
201 default = false;
202 description = lib.mdDoc ''
203 Whether to pass the original argv[0] to the interpreter.
204
205 See the description of the 'P' flag in the kernel docs
206 for more details;
207 '';
208 type = types.bool;
209 };
210
211 openBinary = mkOption {
212 default = config.matchCredentials;
213 description = lib.mdDoc ''
214 Whether to pass the binary to the interpreter as an open
215 file descriptor, instead of a path.
216 '';
217 type = types.bool;
218 };
219
220 matchCredentials = mkOption {
221 default = false;
222 description = lib.mdDoc ''
223 Whether to launch with the credentials and security
224 token of the binary, not the interpreter (e.g. setuid
225 bit).
226
227 See the description of the 'C' flag in the kernel docs
228 for more details.
229
230 Implies/requires openBinary = true.
231 '';
232 type = types.bool;
233 };
234
235 fixBinary = mkOption {
236 default = false;
237 description = lib.mdDoc ''
238 Whether to open the interpreter file as soon as the
239 registration is loaded, rather than waiting for a
240 relevant file to be invoked.
241
242 See the description of the 'F' flag in the kernel docs
243 for more details.
244 '';
245 type = types.bool;
246 };
247
248 wrapInterpreterInShell = mkOption {
249 default = true;
250 description = lib.mdDoc ''
251 Whether to wrap the interpreter in a shell script.
252
253 This allows a shell command to be set as the interpreter.
254 '';
255 type = types.bool;
256 };
257
258 interpreterSandboxPath = mkOption {
259 internal = true;
260 default = null;
261 description = lib.mdDoc ''
262 Path of the interpreter to expose in the build sandbox.
263 '';
264 type = types.nullOr types.path;
265 };
266 };
267 }));
268 };
269
270 emulatedSystems = mkOption {
271 default = [];
272 example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
273 description = lib.mdDoc ''
274 List of systems to emulate. Will also configure Nix to
275 support your new systems.
276 Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
277 '';
278 type = types.listOf (types.enum (builtins.attrNames magics));
279 };
280 };
281 };
282
283 config = {
284 boot.binfmt.registrations = builtins.listToAttrs (map (system: {
285 name = system;
286 value = { config, ... }: let
287 interpreter = getEmulator system;
288 qemuArch = getQemuArch system;
289
290 preserveArgvZero = "qemu-${qemuArch}" == baseNameOf interpreter;
291 interpreterReg = let
292 wrapperName = "qemu-${qemuArch}-binfmt-P";
293 wrapper = pkgs.wrapQemuBinfmtP wrapperName interpreter;
294 in
295 if preserveArgvZero then "${wrapper}/bin/${wrapperName}"
296 else interpreter;
297 in ({
298 preserveArgvZero = mkDefault preserveArgvZero;
299
300 interpreter = mkDefault interpreterReg;
301 wrapInterpreterInShell = mkDefault (!config.preserveArgvZero);
302 interpreterSandboxPath = mkDefault (dirOf (dirOf config.interpreter));
303 } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}")));
304 }) cfg.emulatedSystems);
305 nix.settings = lib.mkIf (cfg.emulatedSystems != []) {
306 extra-platforms = cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux";
307 extra-sandbox-paths = let
308 ruleFor = system: cfg.registrations.${system};
309 hasWrappedRule = lib.any (system: (ruleFor system).wrapInterpreterInShell) cfg.emulatedSystems;
310 in [ "/run/binfmt" ]
311 ++ lib.optional hasWrappedRule "${pkgs.bash}"
312 ++ (map (system: (ruleFor system).interpreterSandboxPath) cfg.emulatedSystems);
313 };
314
315 environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
316 (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
317
318 systemd = lib.mkMerge [
319 ({ tmpfiles.rules = [
320 "d /run/binfmt 0755 -"
321 ] ++ lib.mapAttrsToList
322 (name: interpreter:
323 "L+ /run/binfmt/${name} - - - - ${interpreter}"
324 )
325 (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations);
326 })
327
328 (lib.mkIf (config.boot.binfmt.registrations != {}) {
329 additionalUpstreamSystemUnits = [
330 "proc-sys-fs-binfmt_misc.automount"
331 "proc-sys-fs-binfmt_misc.mount"
332 "systemd-binfmt.service"
333 ];
334 services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
335 })
336 ];
337 };
338}