Linux filesystem development
 help / color / mirror / Atom feed
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 v2 03/23] binfmt_misc: convert entry list to an hlist
Date: Fri, 10 Jul 2026 00:29:58 +0200	[thread overview]
Message-ID: <20260710-work-binfmt_misc-locking-v2-3-2a1c3d4126a7@kernel.org> (raw)
In-Reply-To: <20260710-work-binfmt_misc-locking-v2-0-2a1c3d4126a7@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 24142859658c..ab7dbd898977 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;
 
@@ -662,8 +662,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)
 {
@@ -676,8 +676,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);
 	}
@@ -698,7 +698,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);
 }
@@ -754,7 +754,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);
@@ -798,7 +798,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;
@@ -871,8 +871,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) {
@@ -898,7 +899,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);
@@ -973,7 +974,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


  parent reply	other threads:[~2026-07-09 22:30 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-09 22:29 [PATCH v2 00/23] binfmt_misc: write access fixes, RCU handler lookup and cleanups Christian Brauner
2026-07-09 22:29 ` [PATCH v2 01/23] binfmt_misc: restore write access when removing an entry Christian Brauner
2026-07-09 22:29 ` [PATCH v2 02/23] binfmt_misc: use exe_file_deny_write_access() for the interpreter clone Christian Brauner
2026-07-09 22:29 ` Christian Brauner [this message]
2026-07-09 22:29 ` [PATCH v2 04/23] binfmt_misc: use RCU for the handler lookup Christian Brauner
2026-07-10 10:53   ` Jori Koolstra
2026-07-09 22:30 ` [PATCH v2 05/23] binfmt_misc: annotate racy accesses to ->enabled Christian Brauner
2026-07-09 22:30 ` [PATCH v2 06/23] binfmt_misc: turn the entry bit numbers into a proper enum Christian Brauner
2026-07-09 22:30 ` [PATCH v2 07/23] binfmt_misc: turn the entry behavior flags into an enum Christian Brauner
2026-07-09 22:30 ` [PATCH v2 08/23] binfmt_misc: rename Node to struct binfmt_misc_entry Christian Brauner
2026-07-09 22:30 ` [PATCH v2 09/23] binfmt_misc: remove the VERBOSE_STATUS toggle Christian Brauner
2026-07-09 22:30 ` [PATCH v2 10/23] binfmt_misc: use print_hex_dump_debug() for the register debug output Christian Brauner
2026-07-09 22:30 ` [PATCH v2 11/23] binfmt_misc: convert the entry file to seq_file Christian Brauner
2026-07-09 22:30 ` [PATCH v2 12/23] binfmt_misc: factor out the entry matching Christian Brauner
2026-07-09 22:30 ` [PATCH v2 13/23] binfmt_misc: rename load_binfmt_misc() to current_binfmt_misc() Christian Brauner
2026-07-09 22:30 ` [PATCH v2 14/23] binfmt_misc: return errors directly in load_misc_binary() Christian Brauner
2026-07-09 22:30 ` [PATCH v2 15/23] binfmt_misc: give the parse_command() results names Christian Brauner
2026-07-09 22:30 ` [PATCH v2 16/23] binfmt_misc: factor out the entry removal Christian Brauner
2026-07-09 22:30 ` [PATCH v2 17/23] binfmt_misc: simplify check_special_flags() Christian Brauner
2026-07-09 22:30 ` [PATCH v2 18/23] binfmt_misc: use a flexible array member for the register string Christian Brauner
2026-07-09 22:30 ` [PATCH v2 19/23] binfmt_misc: split the field parsing out of create_entry() Christian Brauner
2026-07-09 22:30 ` [PATCH v2 20/23] binfmt_misc: use __free(kfree) in bm_register_write() Christian Brauner
2026-07-09 22:30 ` [PATCH v2 21/23] binfmt_misc: assorted small cleanups Christian Brauner
2026-07-09 22:30 ` [PATCH v2 22/23] binfmt_misc: include what is used Christian Brauner
2026-07-09 22:30 ` [PATCH v2 23/23] binfmt_misc: allow removing entries via unlink(2) Christian Brauner
2026-07-10  7:58 ` [PATCH v2 00/23] binfmt_misc: write access fixes, RCU handler lookup and cleanups 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-v2-3-2a1c3d4126a7@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