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