From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>,
linux-mm@kvack.org, Farid Zakaria <farid.m.zakaria@gmail.com>,
jannh@google.com
Subject: [PATCH v3 04/24] binfmt_misc: convert entry list to an hlist
Date: Fri, 10 Jul 2026 11:33:05 +0200 [thread overview]
Message-ID: <20260710-work-binfmt_misc-locking-v3-4-a162f7cb58d6@kernel.org> (raw)
In-Reply-To: <20260710-work-binfmt_misc-locking-v3-0-a162f7cb58d6@kernel.org>
The upcoming conversion of the handler lookup to RCU walks cannot use
list_del_init(): reinitializing the forward pointer of a removed entry
would make a concurrent lockless walker standing on that entry loop
back onto it indefinitely. The removal paths do rely on
reinitialization though because bm_{entry,status}_write() and
bm_evict_inode() need to detect whether an entry has already been
unlinked.
hlists support exactly this pattern: hlist_del_init_rcu() keeps the
forward pointer of the removed entry intact for concurrent walkers and
only zeroes ->pprev with hlist_unhashed() serving as the linked test.
Convert the entry list to an hlist now while keeping the rwlock so the
subsequent RCU conversion is a pure locking change. hlist_add_head()
inserts at the head just as list_add() did so lookup precedence
between registered handlers is unchanged.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/binfmt_misc.c | 25 +++++++++++++------------
include/linux/binfmts.h | 2 +-
kernel/user.c | 2 +-
3 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index b7664d90eb8f..89da2d4e7f91 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -48,7 +48,7 @@ enum {Enabled, Magic};
#define MISC_FMT_OPEN_FILE (1UL << 28)
typedef struct {
- struct list_head list;
+ struct hlist_node node;
unsigned long flags; /* type, status, etc. */
int offset; /* offset of magic */
int size; /* size of magic/mask */
@@ -95,7 +95,7 @@ static Node *search_binfmt_handler(struct binfmt_misc *misc,
Node *e;
/* Walk all the registered handlers. */
- list_for_each_entry(e, &misc->entries, list) {
+ hlist_for_each_entry(e, &misc->entries, node) {
char *s;
int j;
@@ -666,8 +666,8 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode)
*
* If the ->evict call was not caused by a super block shutdown but by a write
* to remove the entry or all entries via bm_{entry,status}_write() the entry
- * will have already been removed from the list. We keep the list_empty() check
- * to make that explicit.
+ * will have already been removed from the list. We keep the hlist_unhashed()
+ * check to make that explicit.
*/
static void bm_evict_inode(struct inode *inode)
{
@@ -680,8 +680,8 @@ static void bm_evict_inode(struct inode *inode)
misc = i_binfmt_misc(inode);
write_lock(&misc->entries_lock);
- if (!list_empty(&e->list))
- list_del_init(&e->list);
+ if (!hlist_unhashed(&e->node))
+ hlist_del_init(&e->node);
write_unlock(&misc->entries_lock);
put_binfmt_handler(e);
}
@@ -702,7 +702,7 @@ static void bm_evict_inode(struct inode *inode)
static void remove_binfmt_handler(struct binfmt_misc *misc, Node *e)
{
write_lock(&misc->entries_lock);
- list_del_init(&e->list);
+ hlist_del_init(&e->node);
write_unlock(&misc->entries_lock);
locked_recursive_removal(e->dentry, NULL);
}
@@ -758,7 +758,7 @@ static ssize_t bm_entry_write(struct file *file, const char __user *buffer,
* read-only. So we only need to take the write lock when we
* actually remove the entry from the list.
*/
- if (!list_empty(&e->list))
+ if (!hlist_unhashed(&e->node))
remove_binfmt_handler(i_binfmt_misc(inode), e);
inode_unlock(inode);
@@ -802,7 +802,7 @@ static int add_entry(Node *e, struct super_block *sb)
d_make_persistent(dentry, inode);
misc = i_binfmt_misc(inode);
write_lock(&misc->entries_lock);
- list_add(&e->list, &misc->entries);
+ hlist_add_head(&e->node, &misc->entries);
write_unlock(&misc->entries_lock);
simple_done_creating(dentry);
return 0;
@@ -875,8 +875,9 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
{
struct binfmt_misc *misc;
int res = parse_command(buffer, count);
- Node *e, *next;
+ struct hlist_node *next;
struct inode *inode;
+ Node *e;
misc = i_binfmt_misc(file_inode(file));
switch (res) {
@@ -902,7 +903,7 @@ static ssize_t bm_status_write(struct file *file, const char __user *buffer,
* read-only. So we only need to take the write lock when we
* actually remove the entry from the list.
*/
- list_for_each_entry_safe(e, next, &misc->entries, list)
+ hlist_for_each_entry_safe(e, next, &misc->entries, node)
remove_binfmt_handler(misc, e);
inode_unlock(inode);
@@ -977,7 +978,7 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
if (!misc)
return -ENOMEM;
- INIT_LIST_HEAD(&misc->entries);
+ INIT_HLIST_HEAD(&misc->entries);
rwlock_init(&misc->entries_lock);
/* Pairs with smp_load_acquire() in load_binfmt_misc(). */
diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h
index 2c77e383e737..071da63f2b48 100644
--- a/include/linux/binfmts.h
+++ b/include/linux/binfmts.h
@@ -101,7 +101,7 @@ struct linux_binfmt {
#if IS_ENABLED(CONFIG_BINFMT_MISC)
struct binfmt_misc {
- struct list_head entries;
+ struct hlist_head entries;
rwlock_t entries_lock;
bool enabled;
} __randomize_layout;
diff --git a/kernel/user.c b/kernel/user.c
index 7aef4e679a6a..c6a2bfb4d918 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -23,7 +23,7 @@
#if IS_ENABLED(CONFIG_BINFMT_MISC)
struct binfmt_misc init_binfmt_misc = {
- .entries = LIST_HEAD_INIT(init_binfmt_misc.entries),
+ .entries = HLIST_HEAD_INIT,
.enabled = true,
.entries_lock = __RW_LOCK_UNLOCKED(init_binfmt_misc.entries_lock),
};
--
2.53.0
next prev parent reply other threads:[~2026-07-10 9:33 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-10 9:33 [PATCH v3 00/24] binfmt_misc: write access fixes, RCU handler lookup and cleanups Christian Brauner
2026-07-10 9:33 ` [PATCH v3 01/24] binfmt_misc: restore write access when removing an entry Christian Brauner
2026-07-10 9:33 ` [PATCH v3 02/24] binfmt_misc: use exe_file_deny_write_access() for the interpreter clone Christian Brauner
2026-07-10 9:33 ` [PATCH v3 03/24] binfmt_misc: reject a flag character as the field delimiter Christian Brauner
2026-07-10 9:33 ` Christian Brauner [this message]
2026-07-10 9:33 ` [PATCH v3 05/24] binfmt_misc: use RCU for the handler lookup Christian Brauner
2026-07-10 9:33 ` [PATCH v3 06/24] binfmt_misc: annotate racy accesses to ->enabled Christian Brauner
2026-07-10 9:33 ` [PATCH v3 07/24] binfmt_misc: turn the entry bit numbers into a proper enum Christian Brauner
2026-07-10 9:33 ` [PATCH v3 08/24] binfmt_misc: turn the entry behavior flags into an enum Christian Brauner
2026-07-10 9:33 ` [PATCH v3 09/24] binfmt_misc: rename Node to struct binfmt_misc_entry Christian Brauner
2026-07-10 9:33 ` [PATCH v3 10/24] binfmt_misc: remove the VERBOSE_STATUS toggle Christian Brauner
2026-07-10 9:33 ` [PATCH v3 11/24] binfmt_misc: use print_hex_dump_debug() for the register debug output Christian Brauner
2026-07-10 9:33 ` [PATCH v3 12/24] binfmt_misc: convert the entry file to seq_file Christian Brauner
2026-07-10 9:33 ` [PATCH v3 13/24] binfmt_misc: factor out the entry matching Christian Brauner
2026-07-10 9:33 ` [PATCH v3 14/24] binfmt_misc: rename load_binfmt_misc() to current_binfmt_misc() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 15/24] binfmt_misc: return errors directly in load_misc_binary() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 16/24] binfmt_misc: give the parse_command() results names Christian Brauner
2026-07-10 9:33 ` [PATCH v3 17/24] binfmt_misc: factor out the entry removal Christian Brauner
2026-07-10 9:33 ` [PATCH v3 18/24] binfmt_misc: simplify check_special_flags() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 19/24] binfmt_misc: use a flexible array member for the register string Christian Brauner
2026-07-10 9:33 ` [PATCH v3 20/24] binfmt_misc: split the field parsing out of create_entry() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 21/24] binfmt_misc: use __free(kfree) in bm_register_write() Christian Brauner
2026-07-10 9:33 ` [PATCH v3 22/24] binfmt_misc: assorted small cleanups Christian Brauner
2026-07-10 9:33 ` [PATCH v3 23/24] binfmt_misc: include what is used Christian Brauner
2026-07-10 9:33 ` [PATCH v3 24/24] binfmt_misc: allow removing entries via unlink(2) Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260710-work-binfmt_misc-locking-v3-4-a162f7cb58d6@kernel.org \
--to=brauner@kernel.org \
--cc=farid.m.zakaria@gmail.com \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox