Re: [PATCH v8 4/5] proc: Relax check of mount visibility

Christian Brauner
2026-02-17T11:59:59Z

On Fri, Feb 13, 2026 at 11:44:29AM +0100, Alexey Gladkov wrote:
> When /proc is mounted with the subset=pid option, all system files from
> the root of the file system are not accessible in userspace. Only
> dynamic information about processes is available, which cannot be
> hidden with overmount.
>
> For this reason, checking for full visibility is not relevant if
> mounting is performed with the subset=pid option.
>
> Signed-off-by: Alexey Gladkov <legion@kernel.org>
> ---
> fs/namespace.c | 29 ++++++++++++++++-------------
> fs/proc/root.c | 17 ++++++++++-------
> include/linux/fs/super_types.h | 2 ++
> 3 files changed, 28 insertions(+), 20 deletions(-)
>
> diff --git a/fs/namespace.c b/fs/namespace.c
> index c58674a20cad..7daa86315c05 100644
> --- a/fs/namespace.c
> +++ b/fs/namespace.c
> @@ -6116,7 +6116,8 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
> /* This mount is not fully visible if it's root directory
> * is not the root directory of the filesystem.
> */
> - if (mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
> + if (!(sb->s_iflags & SB_I_USERNS_ALLOW_REVEALING) &&
> + mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
> continue;
>
> /* A local view of the mount flags */
> @@ -6136,18 +6137,20 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
> ((mnt_flags & MNT_ATIME_MASK) != (new_flags & MNT_ATIME_MASK)))
> continue;

There are a few things that I find problematic here.

Even before your change the mount flags of the first fully visible
procfs mount would be picked up. If the caller was unlucky they could
stumble upon the most restricted procfs mount in the mount namespace
rbtree. Leading to weird scenarios where a user cannot write to the
procfs instance they just mounted but could to another one that is also
in their namespace.

The other thing is that with this change specifically:

if (!(sb->s_iflags & SB_I_USERNS_ALLOW_REVEALING) &&
mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)

we start caring about mount options of even partially exposed procfs
mounts. IOW, if someone had a bind-mount of e.g., /proc/pressure
somewhere that got inherited via CLONE_NEWNS then we suddenly take the
mount options of that into account for a new /proc/<pid>/* only instance.
I think we should continue caring only about procfs mounts that are
visible from their root.

The the other problem is that it is really annoying that we walk all
mounts in a mount namespace just to find procfs and sysfs mounts in
there. Currently a lot of workloads still do the CLONE_NEWNS dance
meaning they inherit all the crap from the host and then proceed to
setup their new rootfs. Busy container workloads that can be a lot.

So let's just be honest about it and treat procfs and sysfs as the
snowflakes that they have become and record their instances in a
separate per mount namespace hlist as in the (untested) patch below [1].

Also SB_I_USERNS_ALLOW_REVEALING seems unnecessary. The only time we
care about that flag is when we setup a new superblock. So this could
easily be a struct fs_context bitfield that just exists for the duration
of the creation of the new superblock and mount. So maybe pass that down
to mount_too_revealing() and further down into the actual helper.

[1]:
From 4bbd41e7a3ef91667dd334f31b1b6bf8caec0599 Mon Sep 17 00:00:00 2001
From: Christian Brauner <brauner@kernel.org>
Date: Tue, 17 Feb 2026 12:02:34 +0100
Subject: [PATCH] namespace: record fully visible mounts in list

Instead of wading through all the mounts in the mount namespace rbtree
to find fully visible procfs and sysfs mounts, be honest about them
being special cruft and record them in a separate per-mount namespace
list.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
fs/mount.h | 4 ++++
fs/namespace.c | 19 +++++++++++--------
2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/fs/mount.h b/fs/mount.h
index e0816c11a198..5df134d56d47 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -25,6 +25,7 @@ struct mnt_namespace {
__u32 n_fsnotify_mask;
struct fsnotify_mark_connector __rcu *n_fsnotify_marks;
#endif
+ struct hlist_head mnt_visible_mounts; /* SB_I_USERNS_VISIBLE mounts */
unsigned int nr_mounts; /* # of mounts in the namespace */
unsigned int pending_mounts;
refcount_t passive; /* number references not pinning @mounts */
@@ -90,6 +91,7 @@ struct mount {
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct hlist_head mnt_stuck_children;
+ struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */
struct mount *overmount; /* mounted on ->mnt_root */
} __randomize_layout;

@@ -207,6 +209,8 @@ static inline void move_from_ns(struct mount *mnt)
ns->mnt_first_node = rb_next(&mnt->mnt_node);
rb_erase(&mnt->mnt_node, &ns->mounts);
RB_CLEAR_NODE(&mnt->mnt_node);
+ if (!hlist_unhashed(&mnt->mnt_ns_visible))
+ hlist_del_init(&mnt->mnt_ns_visible);
}

bool has_locked_children(struct mount *mnt, struct dentry *dentry);
diff --git a/fs/namespace.c b/fs/namespace.c
index a67cbe42746d..764081c690d5 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -321,6 +321,7 @@ static struct mount *alloc_vfsmnt(const char *name)
INIT_HLIST_NODE(&mnt->mnt_slave);
INIT_HLIST_NODE(&mnt->mnt_mp_list);
INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
+ INIT_HLIST_NODE(&mnt->mnt_ns_visible);
RB_CLEAR_NODE(&mnt->mnt_node);
mnt->mnt.mnt_idmap = &nop_mnt_idmap;
}
@@ -1098,6 +1099,10 @@ static void mnt_add_to_ns(struct mnt_namespace *ns, struct mount *mnt)
rb_link_node(&mnt->mnt_node, parent, link);
rb_insert_color(&mnt->mnt_node, &ns->mounts);

+ if ((mnt->mnt.mnt_sb->s_iflags & SB_I_USERNS_VISIBLE) &&
+ mnt->mnt.mnt_root == mnt->mnt.mnt_sb->s_root)
+ hlist_add_head(&mnt->mnt_ns_visible, &ns->mnt_visible_mounts);
+
mnt_notify_add(mnt);
}

@@ -6295,22 +6300,20 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
int *new_mnt_flags)
{
int new_flags = *new_mnt_flags;
- struct mount *mnt, *n;
+ struct mount *mnt;
+
+ /* Don't acquire namespace semaphore without a good reason. */
+ if (hlist_empty(&ns->mnt_visible_mounts))
+ return false;

guard(namespace_shared)();
- rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
+ hlist_for_each_entry(mnt, &ns->mnt_visible_mounts, mnt_ns_visible) {
struct mount *child;
int mnt_flags;

if (mnt->mnt.mnt_sb->s_type != sb->s_type)
continue;

- /* This mount is not fully visible if it's root directory
- * is not the root directory of the filesystem.
- */
- if (mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
- continue;
-
/* A local view of the mount flags */
mnt_flags = mnt->mnt.mnt_flags;

--
2.47.3