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 v3 24/24] binfmt_misc: allow removing entries via unlink(2)
Date: Fri, 10 Jul 2026 11:33:25 +0200	[thread overview]
Message-ID: <20260710-work-binfmt_misc-locking-v3-24-a162f7cb58d6@kernel.org> (raw)
In-Reply-To: <20260710-work-binfmt_misc-locking-v3-0-a162f7cb58d6@kernel.org>

Removing a binary type handler requires echoing -1 into its entry
file which works but is an odd interface to discover for something
that already looks like a plain file in a filesystem. The comment on
remove_binfmt_handler() has been suggesting a proper ->unlink()
method for years, so add one: unlinking an entry file unhashes the
entry from the handler list and removes the file, exactly like
writing -1 to it does. The status and register control files refuse
removal with EPERM the same way binderfs protects binder-control.
Writing -1 keeps working.

Permission-wise nothing new is exposed: unlink(2) requires write
access to the root directory which is owned by the (user namespace)
root with mode 0755, matching the privilege needed to write to the
0644 entry files. The VFS calls ->unlink() with the root inode lock
held so the existing writer serialization scheme applies unchanged,
and eviction of the unlinked inode drops the entry reference exactly
as for the write based removal.

Document the new way in admin-guide/binfmt-misc.rst.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 Documentation/admin-guide/binfmt-misc.rst |  3 +-
 fs/binfmt_misc.c                          | 77 ++++++++++++++++++++++---------
 2 files changed, 58 insertions(+), 22 deletions(-)

diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst
index c0a34fbf8022..306ef48f5de6 100644
--- a/Documentation/admin-guide/binfmt-misc.rst
+++ b/Documentation/admin-guide/binfmt-misc.rst
@@ -133,7 +133,8 @@ or 1 (to enable) to ``/proc/sys/fs/binfmt_misc/status`` or
 Catting the file tells you the current status of ``binfmt_misc/the_entry``.
 
 You can remove one entry or all entries by echoing -1 to ``/proc/.../the_name``
-or ``/proc/sys/fs/binfmt_misc/status``.
+or ``/proc/sys/fs/binfmt_misc/status``. A single entry can also be removed
+by simply unlinking (``rm``) ``/proc/.../the_name``.
 
 
 Hints
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index 46c65a618b19..fcaad14f81a3 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -639,8 +639,8 @@ static struct binfmt_misc *i_binfmt_misc(struct inode *inode)
  * entry is removed or the filesystem is unmounted and the super block is
  * shutdown.
  *
- * 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
+ * If the ->evict call was not caused by a super block shutdown but by
+ * removing the entry via bm_{entry,status}_write() or unlink(2) the entry
  * will have already been removed from the list. We keep the hlist_unhashed()
  * check to make that explicit.
 */
@@ -662,6 +662,26 @@ static void bm_evict_inode(struct inode *inode)
 	}
 }
 
+/**
+ * unlink_binfmt_handler - unhash a binary type handler
+ * @misc: handle to binfmt_misc instance
+ * @e: binary type handler to unhash
+ *
+ * Adding and removing entries via bm_{entry,register,status}_write() and
+ * unlink(2) happens under the exclusively held inode lock of the root
+ * dentry keeping the list stable for writers. load_misc_binary() walks it
+ * concurrently under RCU. The entries_lock is only held around the actual
+ * unlink to serialize against bm_evict_inode() which unlinks entries
+ * during umount without holding the root inode lock.
+ */
+static void unlink_binfmt_handler(struct binfmt_misc *misc,
+				  struct binfmt_misc_entry *e)
+{
+	spin_lock(&misc->entries_lock);
+	hlist_del_init_rcu(&e->node);
+	spin_unlock(&misc->entries_lock);
+}
+
 /**
  * remove_binfmt_handler - remove a binary type handler
  * @misc: handle to binfmt_misc instance
@@ -669,29 +689,15 @@ static void bm_evict_inode(struct inode *inode)
  *
  * Remove a binary type handler from the list of binary type handlers and
  * remove its associated dentry.
- *
- * Adding and removing entries via bm_{entry,register,status}_write()
- * happens under the exclusively held inode lock of the root dentry keeping
- * the list stable for writers. load_misc_binary() walks it concurrently
- * under RCU. The entries_lock is only held around the actual unlink to
- * serialize against bm_evict_inode() which unlinks entries during umount
- * without holding the root inode lock.
- *
- * In the future, we might want to think about adding a proper ->unlink()
- * method to binfmt_misc instead of forcing callers to use writes to files
- * in order to delete binary type handlers. But it has worked for so long
- * that it's not a pressing issue.
  */
 static void remove_binfmt_handler(struct binfmt_misc *misc,
 				  struct binfmt_misc_entry *e)
 {
-	spin_lock(&misc->entries_lock);
-	hlist_del_init_rcu(&e->node);
-	spin_unlock(&misc->entries_lock);
+	unlink_binfmt_handler(misc, e);
 	locked_recursive_removal(e->dentry, NULL);
 }
 
-/* Remove @e unless a concurrent write already unlinked it. */
+/* Remove @e unless it was already removed. */
 static void bm_remove_entry(struct binfmt_misc_entry *e, struct super_block *sb)
 {
 	struct inode *root = d_inode(sb->s_root);
@@ -716,6 +722,32 @@ static void bm_remove_all_entries(struct binfmt_misc *misc,
 	inode_unlock(root);
 }
 
+/**
+ * bm_unlink - remove a binary type handler via unlink(2)
+ * @dir: inode of the root directory
+ * @dentry: entry file to remove
+ *
+ * Removing the entry file removes its binary type handler, exactly like
+ * writing -1 to it does. The status and register control files can't be
+ * removed. The VFS calls this with the root inode lock held which
+ * serializes against the write based add and remove paths.
+ */
+static int bm_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct binfmt_misc_entry *e = d_inode(dentry)->i_private;
+
+	if (!e)
+		return -EPERM;
+
+	unlink_binfmt_handler(i_binfmt_misc(dir), e);
+	return simple_unlink(dir, dentry);
+}
+
+static const struct inode_operations bm_dir_inode_operations = {
+	.lookup		= simple_lookup,
+	.unlink		= bm_unlink,
+};
+
 /* /<entry> */
 
 static int bm_entry_open(struct inode *inode, struct file *file)
@@ -966,9 +998,12 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
 	WRITE_ONCE(misc->enabled, true);
 
 	err = simple_fill_super(sb, BINFMTFS_MAGIC, bm_files);
-	if (!err)
-		sb->s_op = &bm_super_ops;
-	return err;
+	if (err)
+		return err;
+
+	sb->s_op = &bm_super_ops;
+	d_inode(sb->s_root)->i_op = &bm_dir_inode_operations;
+	return 0;
 }
 
 static void bm_free(struct fs_context *fc)

-- 
2.53.0


      parent reply	other threads:[~2026-07-10  9:34 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 ` [PATCH v3 04/24] binfmt_misc: convert entry list to an hlist Christian Brauner
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 ` Christian Brauner [this message]

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-24-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