Christian Brauner <brauner@kernel.org> says:
binfmt_misc: write access fixes, RCU handler lookup and cleanups
The first two patches fix two i_writecount imbalances on
MISC_FMT_OPEN_FILE interpreter files that turned up while auditing
the file for the rework below and are marked for stable: removing an
entry never restored the write access denied by open_exec() at
registration, leaving the interpreter unwritable until its inode gets
evicted, and the write denial taken on the interpreter clone during
exec is not paired with the FMODE_FSNOTIFY_HSM aware release the exec
machinery uses, so pre-content watches make execs leak write denials.
Also, a register string whose delimiter is one of the flag characters
('P', 'O', 'C', 'F') makes the flag scan in create_entry() run past the
end of the register buffer. Reject such a delimiter up front.
The rest reworks the locking and tidies the file up.
The current rwlock protects very little. Entries are immutable after
publication except for the Enabled bit which is already toggled
locklessly via set_bit()/clear_bit() and entry lifetime is already
handled by the users refcount. The read lock's only remaining job is to
make "the entry is still linked" and "take a reference" atomic with
respect to the unlink sites.
So make the lookup an RCU walk that acquires a reference via
refcount_inc_not_zero() and free entries via kfree_rcu(). The removal
paths need to detect whether an entry has already been unlinked and
rely on list_del_init() reinitialization for that today, but
reinitializing the forward pointer of a removed entry would make a
concurrent lockless walker standing on it loop indefinitely. hlists
support exactly this pattern: hlist_del_init_rcu() keeps the forward
pointer of a removed entry intact for concurrent walkers and only
zeroes ->pprev with hlist_unhashed() serving as the linked test. Hence
the third patch converts the entry list to an hlist so the RCU
conversion in the fourth is a pure locking change.
Writers remain serialized by the inode lock of the root dentry with
one exception. Handler removal semantics are unchanged. An exec that
acquired a reference just before its handler was unregistered already
completes with the removed handler today. The read lock never protected
against that, it only made the window smaller.
With this an exec that matches no binfmt_misc entry no longer writes
to any shared cacheline at all.
The fifth patch annotates the long-standing lockless ->enabled accesses
for KCSAN and the three patches after it make the entry flags proper
enums and give struct binfmt_misc_entry a name that isn't Node.
The remaining patches are a cleanup pass over the whole file: remove
the VERBOSE_STATUS and USE_DEBUG compile-time toggles, convert the
entry file to seq_file, factor out entry matching, entry removal and
the register string field parsing, make the entry/register string
allocation a flexible array member, give the parse_command() results
names, let cleanup.h unwind the entry registration and exec error paths
and prune the include list down to what is used. Aside from
seq_lseek() now bounding seeks on entry files and the ETXTBSY
propagation in the second patch the cleanups have no user-visible
effect.
The penultimate patch adds what the comment in remove_binfmt_handler()
had been suggesting for years: entries can now be removed via unlink(2)
in addition to the -1 write. The status and register control files
refuse removal.
* patches from https://patch.msgid.link/
20260710-work-binfmt_misc-locking-v3-0-
a162f7cb58d6@kernel.org: (24 commits)
binfmt_misc: allow removing entries via unlink(2)
binfmt_misc: include what is used
binfmt_misc: assorted small cleanups
binfmt_misc: use __free(kfree) in bm_register_write()
binfmt_misc: split the field parsing out of create_entry()
binfmt_misc: use a flexible array member for the register string
binfmt_misc: simplify check_special_flags()
binfmt_misc: factor out the entry removal
binfmt_misc: give the parse_command() results names
binfmt_misc: return errors directly in load_misc_binary()
binfmt_misc: rename load_binfmt_misc() to current_binfmt_misc()
binfmt_misc: factor out the entry matching
binfmt_misc: convert the entry file to seq_file
binfmt_misc: use print_hex_dump_debug() for the register debug output
binfmt_misc: remove the VERBOSE_STATUS toggle
binfmt_misc: rename Node to struct binfmt_misc_entry
binfmt_misc: turn the entry behavior flags into an enum
binfmt_misc: turn the entry bit numbers into a proper enum
binfmt_misc: annotate racy accesses to ->enabled
binfmt_misc: use RCU for the handler lookup
...
Link: https://patch.msgid.link/20260710-work-binfmt_misc-locking-v3-0-a162f7cb58d6@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>