From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Alexander Viro <viro@zeniv.linux.org.uk>, Jan Kara <jack@suse.cz>,
Kees Cook <kees@kernel.org>,
linux-mm@kvack.org, bpf@vger.kernel.org,
Farid Zakaria <farid.m.zakaria@gmail.com>,
Jonathan Corbet <corbet@lwn.net>,
bpf@vger.kernel.org, jannh@google.com, mail@johnericson.me,
stable@vger.kernel.org,
"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH] binfmt_misc: don't leak the user namespace when the mount fails
Date: Tue, 28 Jul 2026 15:48:10 +0200 [thread overview]
Message-ID: <20260728-work-binfmt_misc-usernsleak-v1-1-dbd8d5e626e7@kernel.org> (raw)
bm_get_tree() takes a reference to the user namespace and hands it to
get_tree_keyed() as the sget key. sget_fc() moves that reference into
sb->s_fs_info and clears fc->s_fs_info, so from that point on the
superblock owns it and bm_free() doesn't see it anymore.
The superblock drops it in ->put_super(). But generic_shutdown_super()
only calls ->put_super() from inside the if (sb->s_root) branch, so
nothing releases it when bm_fill_super() fails:
- The kzalloc_obj() failure leaves s_root NULL and the whole branch is
skipped.
- A simple_fill_super() failure in the file loop leaves s_root set, but
s_op still points at simple_super_operations, which has no
->put_super(). bm_fill_super() installs s_ops only once
simple_fill_super() returned success, and installing it earlier
wouldn't help either because simple_fill_super() overwrites s_op.
Either way vfs_get_super() calls deactivate_locked_super() and the
reference is gone for good. binfmt_misc mounts are available in a user
namespace and both the inode and the dentry cache are SLAB_ACCOUNT, so
an unprivileged caller under a tight memory cgroup can fail
simple_fill_super() on demand and leak one user namespace per attempt.
Drop the reference in ->kill_sb() instead, which runs unconditionally,
the same way nfsd and rpc_pipefs release their keyed s_fs_info.
That also stops ->put_super() from clearing s_fs_info while the
superblock is still on @fs_supers. generic_shutdown_super() leaves it
there on purpose so that sget_fc() keeps finding it until kill_sb() has
run, but a NULL s_fs_info makes test_keyed_super() miss it, so a
concurrent mount for the same user namespace skips the grab_super()
wait and creates a second superblock for a namespace that is still
being torn down.
Fixes: 21ca59b365c0 ("binfmt_misc: enable sandboxed mounts")
Cc: stable@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Note for stable: this applies as-is only from v6.19 onwards. Before
7beafd51c4e1 ("convert binfmt_misc") ->kill_sb is kill_litter_super()
and bm_kill_sb() has to call that instead of kill_anon_super(): back
then simple_fill_super() left the dentries it created pinned and
d_genocide() is what drops them. Taking this patch verbatim on v6.7 to
v6.18 turns every binfmt_misc umount into a "Dentry still in use"
BUG() in shrink_dcache_for_umount().
---
fs/binfmt_misc.c | 32 +++++++++++++++-----------------
1 file changed, 15 insertions(+), 17 deletions(-)
diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c
index a73a37b8a013..c97f10b48b5b 100644
--- a/fs/binfmt_misc.c
+++ b/fs/binfmt_misc.c
@@ -921,18 +921,9 @@ static const struct file_operations bm_status_operations = {
/* Superblock handling */
-static void bm_put_super(struct super_block *sb)
-{
- struct user_namespace *user_ns = sb->s_fs_info;
-
- sb->s_fs_info = NULL;
- put_user_ns(user_ns);
-}
-
static const struct super_operations s_ops = {
.statfs = simple_statfs,
.evict_inode = bm_evict_inode,
- .put_super = bm_put_super,
};
static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
@@ -990,13 +981,12 @@ static int bm_fill_super(struct super_block *sb, struct fs_context *fc)
/*
* When the binfmt_misc superblock for this userns is shutdown
* ->enabled might have been set to false and we don't reinitialize
- * ->enabled again in put_super() as someone might already be mounting
- * binfmt_misc again. It also would be pointless since by the time
- * ->put_super() is called we know that the binary type list for this
- * bintfmt_misc mount is empty making load_misc_binary() return
- * -ENOEXEC independent of whether ->enabled is true. Instead, if
- * someone mounts binfmt_misc for the first time or again we simply
- * reset ->enabled to true.
+ * ->enabled again during shutdown as someone might already be mounting
+ * binfmt_misc again. It also would be pointless since by then we know
+ * that the binary type list for this binfmt_misc mount is empty making
+ * load_misc_binary() return -ENOEXEC independent of whether ->enabled
+ * is true. Instead, if someone mounts binfmt_misc for the first time or
+ * again we simply reset ->enabled to true.
*/
misc->enabled = true;
@@ -1022,6 +1012,14 @@ static const struct fs_context_operations bm_context_ops = {
.get_tree = bm_get_tree,
};
+static void bm_kill_sb(struct super_block *sb)
+{
+ struct user_namespace *user_ns = sb->s_fs_info;
+
+ kill_anon_super(sb);
+ put_user_ns(user_ns);
+}
+
static int bm_init_fs_context(struct fs_context *fc)
{
fc->ops = &bm_context_ops;
@@ -1038,7 +1036,7 @@ static struct file_system_type bm_fs_type = {
.name = "binfmt_misc",
.init_fs_context = bm_init_fs_context,
.fs_flags = FS_USERNS_MOUNT,
- .kill_sb = kill_anon_super,
+ .kill_sb = bm_kill_sb,
};
MODULE_ALIAS_FS("binfmt_misc");
---
base-commit: 61d2304c0f286fe4a14ec6cee87588350c276d02
change-id: 20260728-work-binfmt_misc-usernsleak-c224e596beba
next reply other threads:[~2026-07-28 13:48 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-28 13:48 Christian Brauner [this message]
2026-08-09 22:37 ` [PATCH] binfmt_misc: don't leak the user namespace when the mount fails patchwork-bot+netdevbpf
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=20260728-work-binfmt_misc-usernsleak-v1-1-dbd8d5e626e7@kernel.org \
--to=brauner@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=corbet@lwn.net \
--cc=farid.m.zakaria@gmail.com \
--cc=jack@suse.cz \
--cc=jannh@google.com \
--cc=kees@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=mail@johnericson.me \
--cc=stable@vger.kernel.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.