summaryrefslogtreecommitdiff
path: root/fs/binfmt_misc_bpf.c
AgeCommit message (Collapse)Author
2026-09-18binfmt_misc: fix racy checks in bpf set_interp kfuncsChris Mason
bpf_binprm_set_interp() tests path[0] != '/' on the buffer its load program passes and then reads the same buffer again to copy it with kmemdup_nul(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If byte 0 is overwritten in that window, the kfunc stages a relative or empty interpreter path. The staged path is not checked again, so open_exec() resolves a relative path against the working directory of the task doing the exec. bpf_binprm_set_interp_arg() has the same pattern for its "!len" test and can stage an empty argument, which the interpreter then receives as an empty argv entry. The verifier checks the path and path__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. Both kfuncs are KF_SLEEPABLE and allocate with GFP_KERNEL between the test and the copy, so the task can sleep inside the window: load program bpf(BPF_MAP_UPDATE_ELEM) bpf_binprm_set_interp() strnlen(path, path__sz) path[0] != '/' is false kmemdup_nul(path, len, GFP_KERNEL) allocation may sleep array_map_update_elem() copy_map_value() rewrites byte 0 copy reads path again bm_bpf_stage_selection() The test in the load program's column proves what byte 0 held only at the moment the test ran. The map update takes no lock, so it can store to byte 0 right after. kmemdup_nul() then copies the rewritten bytes, and bm_bpf_stage_selection() publishes them as bprm->bpf_interp. The staged path is not checked again on its way to open_exec(): load_misc_binary() entry_select_interpreter() returns bprm->bpf_interp unchanged build_interp_argv() copy_string_kernel() copies it as argv[0] bprm_change_interp() kstrdup() entry_open_interpreter() open_exec() unless a bound file is staged or the entry is an 'F' entry None of these functions tests the first byte, and load_misc_binary() hands the pointer to nothing else. In bpf_binprm_set_interp_arg(), strnlen() finds a non-zero len, a NUL is then stored to byte 0, and build_interp_argv() later copies the empty bprm->bpf_interp_arg with copy_string_kernel(). The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The allocation can sleep inside the window, and with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Check the private copy in both kfuncs, so that the string that gets staged is the string that was checked. bpf_binprm_select_interp() already looks its name up in a private copy for the same reason. The remaining tests work on path__sz, arg__sz or the local len, and the copy length is len, so the copy stays inside the extent the verifier checked. Results of bpf_binprm_set_interp() with the check on the copy: - A NUL stored to byte 0 fails interp[0] != '/' and gets -EINVAL. - For len == 0, kmemdup_nul() returns an empty string, so an empty path still gets -EINVAL. - A NUL stored further into the string only shortens it to another absolute path, or another non-empty argument, that the program could have passed anyway. - A path that both lacks the leading '/' and is PATH_MAX or longer now gets -ENAMETOOLONG instead of -EINVAL. - A path that is empty or lacks the leading '/' is now rejected after the copy rather than before it, so such a call makes an allocation and returns -ENOMEM instead of -EINVAL if that allocation fails. bpf_binprm_set_interp_arg() still rejects an empty argument before allocating, so its results are unchanged apart from the raced case fixed here. Both new checks run before the previously staged string is freed or replaced. A failing call frees only its own allocation and leaves the earlier selection in place, as the -ENOMEM path already does. Fixes: b4bfe2f6b011 ("binfmt_misc: add binfmt_misc_ops bpf struct_ops") Signed-off-by: Chris Mason <mason@kernel.org> Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-2-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-09-18binfmt_misc: fix OOB read in bpf_binprm_select_interp()Chris Mason
bpf_binprm_select_interp() checks the name its load program passes with strnlen(name, name__sz) and then hands the same buffer to binfmt_misc_find_interp(), which compares it with an unbounded strcmp(). The buffer can be a BPF map value that another CPU rewrites between the two reads. If the terminating NUL is overwritten in that window, strcmp() reads past the name__sz bytes the verifier checked. That is an out-of-bounds read of up to 31 bytes of whatever follows the checked name__sz bytes. The verifier checks the name and name__sz pair with BPF_READ | BPF_WRITE, so a writable array map value is an accepted argument. bpf(BPF_MAP_UPDATE_ELEM) on an array map copies the new value over the old one in place and takes no lock. The NUL that strnlen() finds can be overwritten before strcmp() reads the buffer again: CPU0 CPU1 bpf_binprm_select_interp() strnlen(name, name__sz) finds the NUL inside name__sz bpf(BPF_MAP_UPDATE_ELEM) array_map_update_elem() copy_map_value() overwrites the NUL binfmt_misc_find_interp() strcmp(interp->name, name) reads past name__sz strnlen() proves that a NUL lies inside name__sz only at the moment it runs. The map update on CPU1 takes no lock, so it can store over the NUL right after. The lookup on CPU0 then walks the live buffer again, once per bound interpreter: fs/binfmt_misc.c:binfmt_misc_find_interp list_for_each_entry(interp, interps, list) if (!strcmp(interp->name, name)) return interp; strcmp() stops at the first mismatch or at the end of interp->name. bm_entry_add_interp() caps a bound name at BINFMT_MISC_INTERP_NAME_MAX (32) bytes, so strcmp() reads at most 33 bytes of name. The smallest name__sz the kfunc accepts is 2, which leaves up to 31 bytes read beyond the checked extent. The handler's own load program has to pass a writable map value, and something has to store into it while the kfunc runs. The window between strnlen() and strcmp() is short, but with a BPF_F_MMAPABLE array the store is a plain user space write into the mapped value, so a loop can hit it without a single bpf() call. Copy the name into a stack buffer of BINFMT_MISC_INTERP_NAME_MAX + 1 bytes, terminate it, and look up the copy. The memcpy() length is below name__sz, so the copy stays inside the extent the verifier checked, and the BPF buffer is not read again afterwards. Return -ENOENT first for a name longer than BINFMT_MISC_INTERP_NAME_MAX. bm_entry_add_interp() rejects a longer name, and the only other binding site attaches the empty name. No entry can bind such a name, so that lookup already ended in -ENOENT and no result changes. Check the first byte of the copy and return -EINVAL if it is NUL, as the existing "!len" test does for an empty name. Only an 'F' entry binds the empty name and a 'B' entry cannot carry 'F', so without that check a racing store of NUL to byte 0 would look up a name no entry binds and end in -ENOENT rather than -EINVAL. A NUL stored further into the name only shortens it to another name the program could have passed anyway. binfmt_misc_find_interp() itself is left alone: entry_attach_interpreter() calls it with a kernel string, and this kfunc now calls it with a private copy. Fixes: 6ec7c96bee30 ("binfmt_misc: let a 'B' entry bind its interpreters") Signed-off-by: Chris Mason <mason@kernel.org> Link: https://patch.msgid.link/20260918-work-binfmt_misc-fixes-v1-1-647b24bc1c46@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-03binfmt_misc: let a 'B' entry bind its interpretersChristian Brauner
A 'B' entry's load program selects its interpreter by absolute path, which open_exec() resolves at exec time in the mount namespace of whoever runs the binary. The handler names an interpreter but does not get to say which file that is. Whoever controls the filesystem view of the exec decides that instead. Static entries settled this long ago with 'F'. The interpreter is opened at registration in the registrant's context and every exec runs a clone of that file. Give a 'B' entry the same, for as many interpreters as it needs. An entry registered with 'D' cannot be matched yet, so it still belongs to whoever is configuring it and can be given interpreters one write at a time: echo ':qemu:B::::qemu_user:D' > register echo '+aarch64 /usr/bin/qemu-aarch64' > qemu echo '+arm /usr/bin/qemu-arm' > qemu echo 1 > qemu Each path is opened by its write, with the credentials the entry file was opened with, by the same helper that opens an 'F' interpreter. The load program picks one per exec with bpf_binprm_select_interp() and the entry hands out a clone of it. Nothing is resolved again, in any namespace. The path is everything past the first space, so no interpreter has to fit in a register string. An entry binds at most a hundred interpreters (BINFMT_MISC_INTERP_MAX). Every binding pins a struct file that no file descriptor accounts for, so RLIMIT_NOFILE does not apply and some cap is needed. A hundred is plenty and raising it later is cheap, lowering it is not. Selection is by name so the register string and the program need not agree on an order, and so the handler is not tied to where a distribution puts its interpreters. A name is a single word of printable ASCII so the entry file can report 'name path' lines. The interpreter runs under the path it was registered under. The entry file reads user memory once. bm_entry_write() copies the write in and dispatches on the first byte, and parse_command() takes the copied buffer. The status file has no binding to spell, so it keeps its own small copy in read_command(). That moves the length cap ahead of the dispatch. A write to an entry file longer than a binding can be is now refused with -E2BIG, and one from a bad address reports -EFAULT, where the command parser used to report -EINVAL for anything past three bytes. Configurations of one instance are kept apart by the lock removal already takes. Reading the set out of the entry file takes no lock. Bindings are rcu-published and the open entry file pins the entry together with everything it bound, so a reader either sees a whole node or misses it. The interpreter is opened before the configuration lock because resolving the path may walk this very filesystem, and only after the command has been parsed and the name validated from the copied buffer, so a write that can never bind opens nothing and the errno reflects the actual failure. Link: https://patch.msgid.link/20260730-work-binfmt_misc-preopen-v1-7-4a0b0da71f16@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-03binfmt_misc: let a bpf handler request loader substitutionChristian Brauner
Give bpf handlers the per-exec equivalent of the static 'L' flag. A load program that sets BPF_BINPRM_LOADER has its selected interpreter substituted for the binary's PT_INTERP instead of run with the binary as payload. The binary otherwise executes as a fully native exec. A single handler can now grade its dispatch per binary: native-arch ELF with PT_INTERP gets loader substitution for full native identity. Anything else, such as foreign arch, static, non-ELF can use transparent or classic dispatch. The load program can read the binary's ELF header from bprm->buf to make that call. Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-19-e57866e4ae0f@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-03binfmt_misc: let a bpf handler run the interpreter transparentlyChristian Brauner
Expose transparent mode 'T' to the bpf handler via a new BPF_BINPRM_TRANSPARENT flag. A bpf handler can decide per binary whether the dispatch is transparent. This way users may choose a native-looking loader for one binary and a visible wrapper invocation for the next. Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-12-e57866e4ae0f@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-08-03binfmt_misc: add binfmt_misc_ops bpf struct_opsChristian Brauner
Add the bpf plumbing for binary type handlers whose matching and interpreter selection are implemented by bpf programs instead of a fixed magic/extension and a fixed interpreter string recorded at registration time. This serves relocatable binary formats where the interpreter must be computed per binary, e.g. relative to the location of the binary itself, as discussed for hermetic Nix-style executables. A handler is an instance of the new binfmt_misc_ops struct_ops with a name that binfmt_misc entries reference it by and two ops: bool (*match)(struct linux_binprm *bprm); int (*load)(struct linux_binprm *bprm); struct_ops is the sanctioned mechanism for this kind of user-supplied policy callback: program types, attach types, and the uapi helper list are frozen, and every recently added subsystem hook (bpf qdisc, SMC handshake control, io_uring loop ops, sched_ext) is a struct_ops user. The ops receive the bprm as a trusted BTF pointer, so a program can match on the header in bprm->buf, read arbitrary file content via bpf_dynptr_from_file() to parse e.g. ELF program headers, and inspect the binary's location. No dedicated program type, ctx blob, or uapi helper is needed. The two ops split along what they decide, not what they may do: the match program decides whether the handler applies to a binary, the load program decides how a matched binary is run. Both are required to be sleepable. Matching cannot be limited to the prefetched 256 bytes in bprm->buf: deciding whether a handler applies takes e.g. parsing the ELF program headers to find an interpreter segment, which sits at an arbitrary file offset, and non-sleepable file reads are limited to whatever happens to be resident in the page cache. A match program that cannot read the file reliably would have to match broadly and leave the rejection to its load program, which breaks first-match-wins entry semantics the moment more than one handler is registered. Reliable file reads at exec time fault in the file's pages, so both ops must be able to sleep. This also constrains the caller: binfmt_misc must invoke both from sleepable context, which a later patch takes care of. Both ops are required; a handler that wants to decide everything from the load program supplies a match program that just returns true. The load program communicates its decisions through three new kfuncs: int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path, size_t path__sz); selects the interpreter and enforces an absolute path shorter than PATH_MAX. int bpf_binprm_set_interp_arg(struct linux_binprm *bprm, const char *arg, size_t arg__sz); passes a single optional argument to the interpreter, mirroring the optional argument of a #! interpreter line - something a static entry cannot express at all. int bpf_binprm_set_flags(struct linux_binprm *bprm, enum bpf_binprm_flags flags); chooses the invocation flags for this exec, with BPF_BINPRM_PRESERVE_ARGV0, BPF_BINPRM_CREDENTIALS and BPF_BINPRM_EXECFD mapping to 'P', 'C' and 'O'. Unknown bits are rejected so a program built against a newer kernel fails loudly on an older one rather than silently losing a flag. Repeated calls replace the staged flags and a zero argument clears them again - the set-or-clear semantics of bpf_bprm_opts_set() on the same struct. A flags word carries this better than a kfunc per flag: it is one call, it is set atomically, and new behaviour is a new bit rather than new surface - the same shape the register string's flags field already has. All three stage their result in the bprm; consuming it from load_misc_binary() is wired up by the following patches. The bprm is exclusively owned by the task doing the exec, so no shared or per-CPU state is involved and nothing here can race. The kfuncs are registered for struct_ops programs with a filter that limits them to the load program of a binfmt_misc_ops instance, keyed off the struct_ops member offset the program attaches to: match decides whether a handler applies, load decides how the binary is run, and the verifier enforces that split at program load time. Registering an ops instance (updating the struct_ops map or attaching its link) publishes the handler under its name in a registry keyed by the registering task's user namespace. Lookups do not walk that hierarchy: a handler is only visible in the user namespace it was registered in, so an entry can only reference a handler registered in the same user namespace as its binfmt_misc instance. Consumers take a reference on the ops via bpf_struct_ops_get() which pins the underlying map and programs, so an activated handler keeps working even if the map is deleted or the registering container goes away; deregistration only prevents new activations, exactly like unregistering a tcp congestion ops with live users. Link: https://lore.kernel.org/20260704211409.1978485-1-farid.m.zakaria@gmail.com Link: https://patch.msgid.link/20260714-work-bpf-binfmt_misc-v2-2-57b7529c002c@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>