1{ config, lib, ... }:
2let
3 inherit (lib) mkOption types optionalString;
4
5 cfg = config.boot.binfmtMiscRegistrations;
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.binfmtMiscRegistrations.${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 binfmtFile = builtins.toFile "binfmt_nixos.conf"
24 (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine cfg));
25
26 activationSnippet = name: { interpreter, ... }:
27 "ln -sf ${interpreter} /run/binfmt/${name}";
28 activationScript = ''
29 mkdir -p -m 0755 /run/binfmt
30 ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet cfg)}
31 '';
32in {
33 options = {
34 boot.binfmtMiscRegistrations = mkOption {
35 default = {};
36
37 description = ''
38 Extra binary formats to register with the kernel.
39 See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
40 '';
41
42 type = types.attrsOf (types.submodule ({ config, ... }: {
43 options = {
44 recognitionType = mkOption {
45 default = "magic";
46 description = "Whether to recognize executables by magic number or extension.";
47 type = types.enum [ "magic" "extension" ];
48 };
49
50 offset = mkOption {
51 default = null;
52 description = "The byte offset of the magic number used for recognition.";
53 type = types.nullOr types.int;
54 };
55
56 magicOrExtension = mkOption {
57 description = "The magic number or extension to match on.";
58 type = types.str;
59 };
60
61 mask = mkOption {
62 default = null;
63 description =
64 "A mask to be ANDed with the byte sequence of the file before matching";
65 type = types.nullOr types.str;
66 };
67
68 interpreter = mkOption {
69 description = ''
70 The interpreter to invoke to run the program.
71
72 Note that the actual registration will point to
73 /run/binfmt/''${name}, so the kernel interpreter length
74 limit doesn't apply.
75 '';
76 type = types.path;
77 };
78
79 preserveArgvZero = mkOption {
80 default = false;
81 description = ''
82 Whether to pass the original argv[0] to the interpreter.
83
84 See the description of the 'P' flag in the kernel docs
85 for more details;
86 '';
87 type = types.bool;
88 };
89
90 openBinary = mkOption {
91 default = config.matchCredentials;
92 description = ''
93 Whether to pass the binary to the interpreter as an open
94 file descriptor, instead of a path.
95 '';
96 type = types.bool;
97 };
98
99 matchCredentials = mkOption {
100 default = false;
101 description = ''
102 Whether to launch with the credentials and security
103 token of the binary, not the interpreter (e.g. setuid
104 bit).
105
106 See the description of the 'C' flag in the kernel docs
107 for more details.
108
109 Implies/requires openBinary = true.
110 '';
111 type = types.bool;
112 };
113
114 fixBinary = mkOption {
115 default = false;
116 description = ''
117 Whether to open the interpreter file as soon as the
118 registration is loaded, rather than waiting for a
119 relevant file to be invoked.
120
121 See the description of the 'F' flag in the kernel docs
122 for more details.
123 '';
124 type = types.bool;
125 };
126 };
127 }));
128 };
129 };
130
131 config = lib.mkIf (cfg != {}) {
132 environment.etc."binfmt.d/nixos.conf".source = binfmtFile;
133 system.activationScripts.binfmt = activationScript;
134 systemd.additionalUpstreamSystemUnits =
135 [ "proc-sys-fs-binfmt_misc.automount"
136 "proc-sys-fs-binfmt_misc.mount"
137 ];
138 };
139}