]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
binfmt_misc: allow removing entries via unlink(2)
authorChristian Brauner <brauner@kernel.org>
Fri, 10 Jul 2026 09:33:25 +0000 (11:33 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Aug 2026 08:08:39 +0000 (10:08 +0200)
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.

Link: https://patch.msgid.link/20260710-work-binfmt_misc-locking-v3-24-a162f7cb58d6@kernel.org
Reviewed-by: Jori Koolstra <jkoolstra@xs4all.nl>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Documentation/admin-guide/binfmt-misc.rst
fs/binfmt_misc.c

index c0a34fbf8022b5e1bee12b8f316b0beedbc0ea13..306ef48f5de6dd627095106a79f5e0cf5452bf61 100644 (file)
@@ -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
index 62dbf99ca667bff430501a652c0fad72bc350e04..7896a50af80dc51cdf683f7e9d0cb9a70b77b771 100644 (file)
@@ -638,8 +638,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.
 */
@@ -661,6 +661,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
@@ -668,29 +688,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);
@@ -715,6 +721,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)
@@ -959,9 +991,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)