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