Skip to contentSkip to conversation

proc: subset=pid: Relax check of mount visibility

PATCH RESEND In review Alexey Gladkov 4 participants 61 messages Jul 16, 2021 – Apr 27

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v6 cover proc: subset=pid: Relax check of mount visibility

Allow to mount procfs with subset=pid option even if the entire procfs is not fully accessible to the mounter.

Changelog
---------
v6:
* Add documentation about procfs mount restrictions.
* Reorder commits for better review.

v4:
* Set SB_I_DYNAMIC only if pidonly is set.
* Add an error message if subset=pid is canceled during remount.

v3:
* Add 'const' to struct cred *mounter_cred (fix kernel test robot warning).

v2:
* cache the mounters credentials and make access to the net directories contingent of the permissions of the mounter of procfs.

--

Alexey Gladkov (5):
docs: proc: add documentation about mount restrictions
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
proc: Disable cancellation of subset=pid option
proc: Relax check of mount visibility
docs: proc: add documentation about relaxing visibility restrictions

 Documentation/filesystems/proc.rst | 15 +++++++++++++++
 fs/namespace.c                     | 30 ++++++++++++++++++------------
 fs/proc/proc_net.c                 |  8 ++++++++
 fs/proc/root.c                     | 24 +++++++++++++++++++-----
 include/linux/fs.h                 |  1 +
 include/linux/proc_fs.h            |  1 +
 6 files changed, 62 insertions(+), 17 deletions(-)
Signature · 1 line
2.29.3
#1 lore raw

PATCH RESEND v6 1/5 docs: proc: add documentation about mount restrictions

---
 Documentation/filesystems/proc.rst | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
Documentation/filesystems/proc.rst +14 -0
index 2fa69f710e2a..5a1bb0e081fd 100644
@@ -50,6 +50,7 @@ fixes/update part 1.1  Stefani Seibold <[email protected]>    June 9 2009
 
   4	Configuring procfs
   4.1	Mount options
+  4.2	Mount restrictions
 
   5	Filesystem behavior
 
@@ -2175,6 +2176,19 @@ information about processes information, just add identd to this group.
 subset=pid hides all top level files and directories in the procfs that
 are not related to tasks.
 
+4.2	Mount restrictions
+--------------------------
+
+If user namespaces are in use, the kernel additionally checks the instances of
+procfs available to the mounter and will not allow procfs to be mounted if:
+
+  1. This mount is not fully visible.
+
+     a. It's root directory is not the root directory of the filesystem.
+     b. If any file or non-empty procfs directory is hidden by another mount.
+
+  2. A new mount overrides the readonly option or any option from atime familty.
+
 Chapter 5: Filesystem behavior
 ==============================
 
Signature · 1 line
2.29.3
#2 lore raw

PATCH RESEND v6 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

In reply to Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 5 +++++
 include/linux/proc_fs.h | 1 +
 3 files changed, 14 insertions(+)
fs/proc/proc_net.c +8 -0
index 18601042af99..a198f74cdb3b 100644
@@ -26,6 +26,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -259,6 +260,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -271,6 +273,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +5 -0
index 5e444d4f9717..6a75ac717455 100644
@@ -171,6 +171,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
@@ -220,6 +221,9 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
+	put_cred(fs_info->mounter_cred);
+	fs_info->mounter_cred = get_cred(fc->cred);
+
 	proc_apply_options(fs_info, fc, current_user_ns());
 	return 0;
 }
@@ -274,6 +278,7 @@ static void proc_kill_sb(struct super_block *sb)
 
 	kill_anon_super(sb);
 	put_pid_ns(fs_info->pid_ns);
+	put_cred(fs_info->mounter_cred);
 	kfree(fs_info);
 }
 
include/linux/proc_fs.h +1 -0
index 000cc0533c33..ffa871941bd0 100644
@@ -64,6 +64,7 @@ struct proc_fs_info {
 	kgid_t pid_gid;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
+	const struct cred *mounter_cred;
 };
 
 static inline struct proc_fs_info *proc_sb_info(struct super_block *sb)
Signature · 1 line
2.29.3
#3 lore raw

PATCH RESEND v6 3/5 proc: Disable cancellation of subset=pid option

In reply to Alexey Gladkov

When procfs is mounted with subset=pid option, where is no way to remount it with this option removed. This is done in order not to make visible what ever was hidden since some checks occur during mount.

This patch makes the limitation explicit and prints an error message.

---
 fs/proc/root.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)
fs/proc/root.c +10 -5
index 6a75ac717455..0d20bb67e79a 100644
@@ -145,7 +145,7 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
@@ -155,8 +155,12 @@ static void proc_apply_options(struct proc_fs_info *fs_info,
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
-	if (ctx->mask & (1 << Opt_subset))
+	if (ctx->mask & (1 << Opt_subset)) {
+		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
+	}
+	return 0;
 }
 
 static int proc_fill_super(struct super_block *s, struct fs_context *fc)
@@ -172,7 +176,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	proc_apply_options(fs_info, fc, current_user_ns());
+	ret = proc_apply_options(fs_info, fc, current_user_ns());
+	if (ret)
+		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -224,8 +230,7 @@ static int proc_reconfigure(struct fs_context *fc)
 	put_cred(fs_info->mounter_cred);
 	fs_info->mounter_cred = get_cred(fc->cred);
 
-	proc_apply_options(fs_info, fc, current_user_ns());
-	return 0;
+	return proc_apply_options(fs_info, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
2.29.3
#4 lore raw

PATCH RESEND v6 4/5 proc: Relax check of mount visibility

In reply to Alexey Gladkov

Allow to mount procfs with subset=pid option even if the entire procfs is not fully accessible to the user.

---
 fs/namespace.c     | 30 ++++++++++++++++++------------
 fs/proc/root.c     | 16 ++++++++++------
 include/linux/fs.h |  1 +
 3 files changed, 29 insertions(+), 18 deletions(-)
fs/namespace.c +18 -12
index 9d33909d0f9e..f38570fdfc3f 100644
@@ -3951,7 +3951,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_DYNAMIC) &&
+		    mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
 			continue;
 
 		/* A local view of the mount flags */
@@ -3971,18 +3972,23 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 		    ((mnt_flags & MNT_ATIME_MASK) != (new_flags & MNT_ATIME_MASK)))
 			continue;
 
-		/* This mount is not fully visible if there are any
-		 * locked child mounts that cover anything except for
-		 * empty directories.
+		/* If this filesystem is completely dynamic, then it
+		 * makes no sense to check for any child mounts.
 		 */
-		list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
-			struct inode *inode = child->mnt_mountpoint->d_inode;
-			/* Only worry about locked mounts */
-			if (!(child->mnt.mnt_flags & MNT_LOCKED))
-				continue;
-			/* Is the directory permanetly empty? */
-			if (!is_empty_dir_inode(inode))
-				goto next;
+		if (!(sb->s_iflags & SB_I_DYNAMIC)) {
+			/* This mount is not fully visible if there are any
+			 * locked child mounts that cover anything except for
+			 * empty directories.
+			 */
+			list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+				struct inode *inode = child->mnt_mountpoint->d_inode;
+				/* Only worry about locked mounts */
+				if (!(child->mnt.mnt_flags & MNT_LOCKED))
+					continue;
+				/* Is the directory permanetly empty? */
+				if (!is_empty_dir_inode(inode))
+					goto next;
+			}
 		}
 		/* Preserve the locked attributes */
 		*new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
fs/proc/root.c +10 -6
index 0d20bb67e79a..c739ed94246c 100644
@@ -145,18 +145,21 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static int proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct super_block *s,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
 	struct proc_fs_context *ctx = fc->fs_private;
+	struct proc_fs_info *fs_info = proc_sb_info(s);
 
 	if (ctx->mask & (1 << Opt_gid))
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
 	if (ctx->mask & (1 << Opt_subset)) {
-		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+		if (ctx->pidonly == PROC_PIDONLY_ON)
+			s->s_iflags |= SB_I_DYNAMIC;
+		else if (fs_info->pidonly == PROC_PIDONLY_ON)
 			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
 	}
@@ -176,9 +179,6 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	ret = proc_apply_options(fs_info, fc, current_user_ns());
-	if (ret)
-		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -190,6 +190,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	s->s_time_gran = 1;
 	s->s_fs_info = fs_info;
 
+	ret = proc_apply_options(s, fc, current_user_ns());
+	if (ret)
+		return ret;
+
 	/*
 	 * procfs isn't actually a stacking filesystem; however, there is
 	 * too much magic going on inside it to permit stacking things on
@@ -230,7 +234,7 @@ static int proc_reconfigure(struct fs_context *fc)
 	put_cred(fs_info->mounter_cred);
 	fs_info->mounter_cred = get_cred(fc->cred);
 
-	return proc_apply_options(fs_info, fc, current_user_ns());
+	return proc_apply_options(sb, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
include/linux/fs.h +1 -0
index fd47deea7c17..2c9a47bad796 100644
@@ -1390,6 +1390,7 @@ extern int send_sigurg(struct fown_struct *fown);
 #define SB_I_USERNS_VISIBLE		0x00000010 /* fstype already mounted */
 #define SB_I_IMA_UNVERIFIABLE_SIGNATURE	0x00000020
 #define SB_I_UNTRUSTED_MOUNTER		0x00000040
+#define SB_I_DYNAMIC			0x00000080
 
 #define SB_I_SKIP_SYNC	0x00000100	/* Skip superblock at global sync */
 
Signature · 1 line
2.29.3
#5 lore raw

PATCH RESEND v6 5/5 docs: proc: add documentation about relaxing visibility restrictions

In reply to Alexey Gladkov
---
 Documentation/filesystems/proc.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Documentation/filesystems/proc.rst +2 -1
index 5a1bb0e081fd..9d993aef7f1c 100644
@@ -2182,7 +2182,8 @@ are not related to tasks.
 If user namespaces are in use, the kernel additionally checks the instances of
 procfs available to the mounter and will not allow procfs to be mounted if:
 
-  1. This mount is not fully visible.
+  1. This mount is not fully visible unless the new procfs is going to be
+     mounted with subset=pid option.
 
      a. It's root directory is not the root directory of the filesystem.
      b. If any file or non-empty procfs directory is hidden by another mount.
Signature · 1 line
2.29.3
#6 lore raw
In reply to Alexey Gladkov

Hello Alexey,

Would it be possible to revive this patch series?

I wanted to add an additional downstream use case that would benefit from this work. In particular, I am trying to run the sandbox sunwalker-box [1] without root privileges and/or inside a container.

The sandbox aims to prevent cross-run communication via side channels, and PID allocation is one such channel. Therefore, it creates a new PID namespace and mounts the corresponding procfs instance inside of the sandbox. This currently works without a real root when procfs is fully accessible, but obviously fails otherwise.

Thanks,
Dan Klishch

[1] https://github.com/purplesyringa/sunwalker-box/

#7 lore raw
Quoted 14 lines · Dan Klishch

Hello Alexey,

Would it be possible to revive this patch series?

I wanted to add an additional downstream use case that would benefit from this work. In particular, I am trying to run the sandbox sunwalker-box [1] without root privileges and/or inside a container.

The sandbox aims to prevent cross-run communication via side channels, and PID allocation is one such channel. Therefore, it creates a new PID namespace and mounts the corresponding procfs instance inside of the sandbox. This currently works without a real root when procfs is fully accessible, but obviously fails otherwise.

Quoted 5 lines

Thanks,
Dan Klishch

[1] https://github.com/purplesyringa/sunwalker-box/

Overmounting "dangerous" files in procfs is an incorrect and potentially dangerous practice. I know that many programs (docker, podman, etc.) use this method, but it is not the correct way to isolate dangerous files in procfs.

In particular, this is one of the reasons why this patchset was abandoned.

It is quite difficult to implement these checks in procfs correctly and not break anything. It is much easier to implement file access restrictions in procfs using an ebpf controller. Some time ago, I tried to implement such a controller [1], and it seemed to me that it was much easier than adding complex checks to the kernel.

If I'm wrong and missing a use case, let me know and we can go back to the patches.

[1] https://github.com/legionus/proc-bpf-controller

Signature · 1 line
Rgrds, legion
#8 lore raw
Quoted 2 lines

It is much easier to implement file access
restrictions in procfs using an ebpf controller.

But if we already have a masked /proc from podman/docker/user who decided to run mount --bind /dev/null /proc/smth, the sandbox will not have a choice other than to bail out. Also, correct me if I am wrong, installing ebpf controller requires CAP_BPF in initial userns, so rootless podman will not be able to mask /proc "properly" even if someone sends a patch switching it to ebpf.

Thanks,
Dan Klishch

#9 lore raw
Quoted 6 lines · Dan Klishch
Quoted 2 lines

It is much easier to implement file access
restrictions in procfs using an ebpf controller.

But if we already have a masked /proc from podman/docker/user who decided to run mount --bind /dev/null /proc/smth, the sandbox will not have a choice other than to bail out.

I misunderstood you. I thought you were writing your own container implementation.

Yes, if you want a nested container inside docker/podman, then file overmount technique is already used there.

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Quoted 3 lines

Also, correct me if I am wrong, installing ebpf controller requires CAP_BPF in initial userns, so rootless podman will not be able to mask /proc "properly" even if someone sends a patch switching it to ebpf.

You can turn on /proc/sys/kernel/unprivileged_bpf_disabled.

Signature · 1 line
Rgrds, legion
#10 lore raw
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

Quoted 5 lines
Quoted 3 lines

Also, correct me if I am wrong, installing ebpf controller requires CAP_BPF in initial userns, so rootless podman will not be able to mask /proc "properly" even if someone sends a patch switching it to ebpf.

You can turn on /proc/sys/kernel/unprivileged_bpf_disabled.

$ cat /proc/sys/kernel/unprivileged_bpf_disabled
0
$ unshare -pfr --mount-proc
$ ./proc-controller -p deny /proc/cpuinfo
libbpf: prog 'proc_access_restrict': BPF program load failed: Operation not permitted
libbpf: prog 'proc_access_restrict': failed to load: -1
libbpf: failed to load object './proc-controller.bpf.o'
proc-controller: ERROR: loading BPF object file failed

I think only packet filters are allowed to be installed by non-root.

Thanks,
Dan Klishch

#11 lore raw
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

I don't think you'll be able to do that. procfs doesn't allow itself to be overlayed [1]. What should block mounting overlayfs and fuse on top of procfs.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/root.c#n274

Quoted 15 lines
Quoted 5 lines
Quoted 3 lines

Also, correct me if I am wrong, installing ebpf controller requires CAP_BPF in initial userns, so rootless podman will not be able to mask /proc "properly" even if someone sends a patch switching it to ebpf.

You can turn on /proc/sys/kernel/unprivileged_bpf_disabled.

$ cat /proc/sys/kernel/unprivileged_bpf_disabled
0
$ unshare -pfr --mount-proc
$ ./proc-controller -p deny /proc/cpuinfo
libbpf: prog 'proc_access_restrict': BPF program load failed: Operation not permitted
libbpf: prog 'proc_access_restrict': failed to load: -1
libbpf: failed to load object './proc-controller.bpf.o'
proc-controller: ERROR: loading BPF object file failed
Quoted 1 line

I think only packet filters are allowed to be installed by non-root.

I probably forgot about that. I wrote this code a long time ago, and to be honest, I forgot whether it can be used for rootless.

Signature · 1 line
Rgrds, legion
#12 lore raw
Quoted 13 lines · Alexey Gladkov
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

Quoted 5 lines

I don't think you'll be able to do that. procfs doesn't allow itself to be overlayed [1]. What should block mounting overlayfs and fuse on top of procfs.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/root.c#n274

This is why I have been careful not to say overlayfs. With [2] (warning:
zero-shot ChatGPT output), I can do:

$ ./fuse-overlay target --source=/proc
$ ls target
1   88   194   1374    889840  908552
2   90   195   1375    889987  908619
3   91   196   1379    890031  908658
4   92   203   1412    890063  908756
5   93   205   1590    890085  908804
6   94   233   1644    890139  908951
7   96   237   1802    890246  909848
8   97   239   1850    890271  909914
10  98   240   1852    894665  909924
13  99   243   1865    895854  909926
15  100  244   1888    895864  910005
16  102  246   1889    896030  acpi
17  103  262   1891    896205  asound
18  104  263   1895    896508  bus
19  105  264   1896    896544  driver
20  106  265   1899    896706  dynamic_debug
<...>

[2] https://gist.github.com/DanShaders/547eeb74a90315356b98472feae47474

This requires a much more careful thought wrt magic symlinks and permission checks. The fact that I am highly unlikely to 100% correctly reimplement the checks and special behavior of procfs makes me not want to proceed with the FUSE route.

Quoted 4 lines · Christian Brauner

The standard way of making it possible to mount procfs inside of a container with a separate mount namespace that has a procfs inside it with overmounted entries is to ensure that a fully-visible procfs instance is present.

Yes, this is a solution. However, this is only marginally better than passing --privileged to the outer container (in a sense that we require outer sandbox to remove some protections for the inner sandbox to work).

Quoted 7 lines

The container needs to inherit a fully-visible instance somehow if you want nesting. Using an unprivileged LSM such as landlock to prevent any access to the fully visible procfs instance is usually the better way.

My hope is that once signed bpf is more widely adopted that distros will just start enabling blessed bpf programs that will just take on the access protecting instead of the clumsy bind-mount protection mechanism.

These are big changes to container runtimes that are unlikely to happen soon. In contrast, the patch we are discussing will be available in 2 months after the merge for me to use on ArchLinux, and in a couple more months on Ubuntu.

So, is there any way forward with the patch or should I continue trying to find a userspace solution?

Thanks,
Dan Klishch

#13 lore raw
Quoted 68 lines · Dan Klishch
Quoted 13 lines · Alexey Gladkov
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

Quoted 5 lines

I don't think you'll be able to do that. procfs doesn't allow itself to be overlayed [1]. What should block mounting overlayfs and fuse on top of procfs.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/root.c#n274

This is why I have been careful not to say overlayfs. With [2] (warning:
zero-shot ChatGPT output), I can do:

$ ./fuse-overlay target --source=/proc
$ ls target
1   88   194   1374    889840  908552
2   90   195   1375    889987  908619
3   91   196   1379    890031  908658
4   92   203   1412    890063  908756
5   93   205   1590    890085  908804
6   94   233   1644    890139  908951
7   96   237   1802    890246  909848
8   97   239   1850    890271  909914
10  98   240   1852    894665  909924
13  99   243   1865    895854  909926
15  100  244   1888    895864  910005
16  102  246   1889    896030  acpi
17  103  262   1891    896205  asound
18  104  263   1895    896508  bus
19  105  264   1896    896544  driver
20  106  265   1899    896706  dynamic_debug
<...>

[2] https://gist.github.com/DanShaders/547eeb74a90315356b98472feae47474

This requires a much more careful thought wrt magic symlinks and permission checks. The fact that I am highly unlikely to 100% correctly reimplement the checks and special behavior of procfs makes me not want to proceed with the FUSE route.

Quoted 4 lines · Christian Brauner

The standard way of making it possible to mount procfs inside of a container with a separate mount namespace that has a procfs inside it with overmounted entries is to ensure that a fully-visible procfs instance is present.

Yes, this is a solution. However, this is only marginally better than passing --privileged to the outer container (in a sense that we require outer sandbox to remove some protections for the inner sandbox to work).

Quoted 7 lines

The container needs to inherit a fully-visible instance somehow if you want nesting. Using an unprivileged LSM such as landlock to prevent any access to the fully visible procfs instance is usually the better way.

My hope is that once signed bpf is more widely adopted that distros will just start enabling blessed bpf programs that will just take on the access protecting instead of the clumsy bind-mount protection mechanism.

Quoted 7 lines

These are big changes to container runtimes that are unlikely to happen soon. In contrast, the patch we are discussing will be available in 2 months after the merge for me to use on ArchLinux, and in a couple more months on Ubuntu.

So, is there any way forward with the patch or should I continue trying to find a userspace solution?

I still consider these patches useful. I made them precisely to remove some of the restrictions we have for procfs because of global files in the root of this filesystem.

I can update and prepare a new version of patchset if Christian thinks it's useful too.

Signature · 1 line
Rgrds, legion
#14 lore raw
Quoted 77 lines · Alexey Gladkov
Quoted 68 lines · Dan Klishch
Quoted 13 lines · Alexey Gladkov
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

Quoted 5 lines

I don't think you'll be able to do that. procfs doesn't allow itself to be overlayed [1]. What should block mounting overlayfs and fuse on top of procfs.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/root.c#n274

This is why I have been careful not to say overlayfs. With [2] (warning:
zero-shot ChatGPT output), I can do:

$ ./fuse-overlay target --source=/proc
$ ls target
1   88   194   1374    889840  908552
2   90   195   1375    889987  908619
3   91   196   1379    890031  908658
4   92   203   1412    890063  908756
5   93   205   1590    890085  908804
6   94   233   1644    890139  908951
7   96   237   1802    890246  909848
8   97   239   1850    890271  909914
10  98   240   1852    894665  909924
13  99   243   1865    895854  909926
15  100  244   1888    895864  910005
16  102  246   1889    896030  acpi
17  103  262   1891    896205  asound
18  104  263   1895    896508  bus
19  105  264   1896    896544  driver
20  106  265   1899    896706  dynamic_debug
<...>

[2] https://gist.github.com/DanShaders/547eeb74a90315356b98472feae47474

This requires a much more careful thought wrt magic symlinks and permission checks. The fact that I am highly unlikely to 100% correctly reimplement the checks and special behavior of procfs makes me not want to proceed with the FUSE route.

Quoted 4 lines · Christian Brauner

The standard way of making it possible to mount procfs inside of a container with a separate mount namespace that has a procfs inside it with overmounted entries is to ensure that a fully-visible procfs instance is present.

Yes, this is a solution. However, this is only marginally better than passing --privileged to the outer container (in a sense that we require outer sandbox to remove some protections for the inner sandbox to work).

Quoted 7 lines

The container needs to inherit a fully-visible instance somehow if you want nesting. Using an unprivileged LSM such as landlock to prevent any access to the fully visible procfs instance is usually the better way.

My hope is that once signed bpf is more widely adopted that distros will just start enabling blessed bpf programs that will just take on the access protecting instead of the clumsy bind-mount protection mechanism.

Quoted 7 lines

These are big changes to container runtimes that are unlikely to happen soon. In contrast, the patch we are discussing will be available in 2 months after the merge for me to use on ArchLinux, and in a couple more months on Ubuntu.

So, is there any way forward with the patch or should I continue trying to find a userspace solution?

Quoted 6 lines

I still consider these patches useful. I made them precisely to remove some of the restrictions we have for procfs because of global files in the root of this filesystem.

I can update and prepare a new version of patchset if Christian thinks it's useful too.

Let's see it at least! No need to preemptively dismiss it. :)

#15 lore raw
Quoted 78 lines · Christian Brauner
Quoted 77 lines · Alexey Gladkov
Quoted 75 lines · Dan Klishch
Quoted 13 lines · Alexey Gladkov
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

Quoted 5 lines

I don't think you'll be able to do that. procfs doesn't allow itself to be overlayed [1]. What should block mounting overlayfs and fuse on top of procfs.

[1] https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/proc/root.c#n274

This is why I have been careful not to say overlayfs. With [2] (warning:
zero-shot ChatGPT output), I can do:

$ ./fuse-overlay target --source=/proc
$ ls target
1   88   194   1374    889840  908552
2   90   195   1375    889987  908619
3   91   196   1379    890031  908658
4   92   203   1412    890063  908756
5   93   205   1590    890085  908804
6   94   233   1644    890139  908951
7   96   237   1802    890246  909848
8   97   239   1850    890271  909914
10  98   240   1852    894665  909924
13  99   243   1865    895854  909926
15  100  244   1888    895864  910005
16  102  246   1889    896030  acpi
17  103  262   1891    896205  asound
18  104  263   1895    896508  bus
19  105  264   1896    896544  driver
20  106  265   1899    896706  dynamic_debug
<...>

[2] https://gist.github.com/DanShaders/547eeb74a90315356b98472feae47474

This requires a much more careful thought wrt magic symlinks and permission checks. The fact that I am highly unlikely to 100% correctly reimplement the checks and special behavior of procfs makes me not want to proceed with the FUSE route.

Quoted 4 lines · Christian Brauner

The standard way of making it possible to mount procfs inside of a container with a separate mount namespace that has a procfs inside it with overmounted entries is to ensure that a fully-visible procfs instance is present.

Yes, this is a solution. However, this is only marginally better than passing --privileged to the outer container (in a sense that we require outer sandbox to remove some protections for the inner sandbox to work).

Quoted 7 lines

The container needs to inherit a fully-visible instance somehow if you want nesting. Using an unprivileged LSM such as landlock to prevent any access to the fully visible procfs instance is usually the better way.

My hope is that once signed bpf is more widely adopted that distros will just start enabling blessed bpf programs that will just take on the access protecting instead of the clumsy bind-mount protection mechanism.

These are big changes to container runtimes that are unlikely to happen soon. In contrast, the patch we are discussing will be available in 2 months after the merge for me to use on ArchLinux, and in a couple more months on Ubuntu.

So, is there any way forward with the patch or should I continue trying to find a userspace solution?

Quoted 9 lines
Quoted 6 lines

I still consider these patches useful. I made them precisely to remove some of the restrictions we have for procfs because of global files in the root of this filesystem.

I can update and prepare a new version of patchset if Christian thinks it's useful too.

Let's see it at least! No need to preemptively dismiss it. :)

So what do you think about these changes?

https://lore.kernel.org/all/[email protected]/#t

Signature · 1 line
Rgrds, legion
#16 lore raw
In reply to Dan Klishch
Quoted 11 lines · Dan Klishch
Quoted 3 lines · Alexey Gladkov

But then, if I understand you correctly, this patch will not be enough for you. procfs with subset=pid will not allow you to have /proc/meminfo, /proc/cpuinfo, etc.

Hmm, I didn't think of this. sunwalker-box only exposes cpuinfo and PID tree to the sandboxed programs (empirically, this is enough for most of programs you want sandboxing for). With that in mind, this patch and a FUSE providing an overlay with cpuinfo / seccomp intercepting opens of /proc/cpuinfo / a small kernel patch with a new mount option for procfs to expose more static files still look like a clean solution to me.

The standard way of making it possible to mount procfs inside of a container with a separate mount namespace that has a procfs inside it with overmounted entries is to ensure that a fully-visible procfs instance is present. This is for example what Incus does when nesting containers is enabled. In systemd I implemented the same logic years ago:

commit b71a0192c040f585397cfc6fc2ca025bf839733d

Author:     Christian Brauner <[email protected]>
AuthorDate: Mon Nov 28 12:36:47 2022 +0100
Commit:     Christian Brauner (Microsoft) <[email protected]>
CommitDate: Mon Dec 5 18:34:25 2022 +0100

nspawn: mount temporary visible procfs and sysfs instance

In order to mount procfs and sysfs in an unprivileged container the kernel requires that a fully visible instance is already present in the target mount namespace. Mount one here so the inner child can mount its own instances. Later we umount the temporary instances created here before we actually exec the payload. Since the rootfs is shared the umount will propagate into the container. Note, the inner child wouldn't be able to unmount the instances on its own since it doesn't own the originating mount namespace. IOW, the outer child needs to do this.

So far nspawn didn't run into this issue because it used MS_MOVE which meant that the shadow mount tree pinned a procfs and sysfs instance which the kernel would find. The shadow mount tree is gone with proper pivot_root() semantics.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

Also, correct me if I am wrong, installing ebpf controller requires CAP_BPF in initial userns, so rootless podman will not be able to mask /proc "properly" even if someone sends a patch switching it to ebpf.

The container needs to inherit a fully-visible instance somehow if you want nesting. Using an unprivileged LSM such as landlock to prevent any access to the fully visible procfs instance is usually the better way.

My hope is that once signed bpf is more widely adopted that distros will just start enabling blessed bpf programs that will just take on the access protecting instead of the clumsy bind-mount protection mechanism.

#17 lore raw

v7 cover proc: subset=pid: Relax check of mount visibility

In reply to Dan Klishch

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

Currently, these restrictions prevent pidfs from being mounted inside rootless containers, as almost all container implementations override part of procfs to hide certain directories. Relaxing these restrictions will allow pidfs to be used in nested containerization.

Changelog
---------
v7:
* Rebase on v6.19-rc5.
* Rename SB_I_DYNAMIC to SB_I_USERNS_ALLOW_REVEALING.

v6:
* Add documentation about procfs mount restrictions.
* Reorder commits for better review.

v4:
* Set SB_I_DYNAMIC only if pidonly is set.
* Add an error message if subset=pid is canceled during remount.

v3:
* Add 'const' to struct cred *mounter_cred (fix kernel test robot warning).

v2:
* cache the mounters credentials and make access to the net directories contingent of the permissions of the mounter of procfs.

--

Alexey Gladkov (5):
docs: proc: add documentation about mount restrictions
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
proc: Disable cancellation of subset=pid option
proc: Relax check of mount visibility
docs: proc: add documentation about relaxing visibility restrictions

 Documentation/filesystems/proc.rst | 15 +++++++++++++++
 fs/namespace.c                     | 29 ++++++++++++++++-------------
 fs/proc/proc_net.c                 |  8 ++++++++
 fs/proc/root.c                     | 24 +++++++++++++++++++-----
 include/linux/fs/super_types.h     |  2 ++
 include/linux/proc_fs.h            |  1 +
 6 files changed, 61 insertions(+), 18 deletions(-)
Signature · 1 line
2.52.0
#18 lore raw

PATCH v7 1/5 docs: proc: add documentation about mount restrictions

procfs has a number of mounting restrictions that are not documented anywhere.

---
 Documentation/filesystems/proc.rst | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
Documentation/filesystems/proc.rst +14 -0
index 8256e857e2d7..c8864fcbdec7 100644
@@ -52,6 +52,7 @@ fixes/update part 1.1  Stefani Seibold <[email protected]>    June 9 2009
 
   4	Configuring procfs
   4.1	Mount options
+  4.2	Mount restrictions
 
   5	Filesystem behavior
 
@@ -2410,6 +2411,19 @@ will use the calling process's active pid namespace. Note that the pid
 namespace of an existing procfs instance cannot be modified (attempting to do
 so will give an `-EBUSY` error).
 
+4.2	Mount restrictions
+--------------------------
+
+If user namespaces are in use, the kernel additionally checks the instances of
+procfs available to the mounter and will not allow procfs to be mounted if:
+
+  1. This mount is not fully visible.
+
+     a. It's root directory is not the root directory of the filesystem.
+     b. If any file or non-empty procfs directory is hidden by another mount.
+
+  2. A new mount overrides the readonly option or any option from atime familty.
+
 Chapter 5: Filesystem behavior
 ==============================
 
Signature · 1 line
2.52.0
#19 lore raw

PATCH v7 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

In reply to Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 5 +++++
 include/linux/proc_fs.h | 1 +
 3 files changed, 14 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +5 -0
index d8ca41d823e4..ed8a101d09d3 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
@@ -303,6 +304,9 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
+	put_cred(fs_info->mounter_cred);
+	fs_info->mounter_cred = get_cred(fc->cred);
+
 	proc_apply_options(fs_info, fc, current_user_ns());
 	return 0;
 }
@@ -350,6 +354,7 @@ static void proc_kill_sb(struct super_block *sb)
 	kill_anon_super(sb);
 	if (fs_info) {
 		put_pid_ns(fs_info->pid_ns);
+		put_cred(fs_info->mounter_cred);
 		kfree_rcu(fs_info, rcu);
 	}
 }
include/linux/proc_fs.h +1 -0
index 19d1c5e5f335..ec123c277d49 100644
@@ -67,6 +67,7 @@ enum proc_pidonly {
 struct proc_fs_info {
 	struct pid_namespace *pid_ns;
 	kgid_t pid_gid;
+	const struct cred *mounter_cred;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
 	struct rcu_head rcu;
Signature · 1 line
2.52.0
#20 lore raw

Re: [PATCH v7 2/5] proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

Quoted 59 lines · Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 5 +++++
 include/linux/proc_fs.h | 1 +
 3 files changed, 14 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +1 -0
index d8ca41d823e4..ed8a101d09d3 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */

fs/proc/root.c static int proc_reconfigure(struct fs_context *fc) · line 308

Quoted 5 lines
fs/proc/root.c +2 -0
@@ -303,6 +304,9 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
+	put_cred(fs_info->mounter_cred);
+	fs_info->mounter_cred = get_cred(fc->cred);

Afaict, this races with get_proc_task_net(). You need a synchronization mechanism here so that get_proc_task_net() doesn't risk accessing invalid mounter creds while someone concurrently updates the creds. Proposal how to fix that below.

But I'm kinda torn here anyway whether we want that credential change on remount. The problem is that someone might inadvertently allow access to /proc/<pid>/net as a side-effect simply because they remounted procfs. But they never had a chance to prevent this.

I think it's best if mounter_creds stays fixed just as they do for overlayfs. So we don't allow them to change on reconfigure. That also makes all of the code I hinted at below pointless.

If we ever want to change the credentials it's easier to add a mount option to procfs like I did for overlayfs.

_Untested_ patches:

First, the preparatory patch diff (no functional changes intended):

fs/proc/proc_net.c +10 -10
index 52f0b75cbce2..81825e5819b8 100644
@@ -268,19 +268,19 @@ EXPORT_SYMBOL_GPL(proc_create_net_single_write);
 static struct net *get_proc_task_net(struct inode *dir)
 {
        struct task_struct *task;
-       struct nsproxy *ns;
-       struct net *net = NULL;
+       struct net *net;
 
-       rcu_read_lock();
+       guard(rcu)();
        task = pid_task(proc_pid(dir), PIDTYPE_PID);
-       if (task != NULL) {
-               task_lock(task);
-               ns = task->nsproxy;
-               if (ns != NULL)
-                       net = get_net(ns->net_ns);
-               task_unlock(task);
+       if (!task)
+               return NULL;
+
+       scoped_guard(task_lock, task) {
+               struct nsproxy *ns = task->nsproxy;
+               if (!ns)
+                       return NULL;
+               net = get_net(ns->net_ns);
        }
-       rcu_read_unlock();
 
        return net;
 }

And then on top of it something like:

fs/proc/proc_net.c +11 -0
index 81825e5819b8..47dc9806395c 100644
@@ -269,6 +269,8 @@ static struct net *get_proc_task_net(struct inode *dir)
 {
        struct task_struct *task;
        struct net *net;
+       struct proc_fs_info *fs_info;
+       const struct cred *cred;
 
        guard(rcu)();
        task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,15 @@ static struct net *get_proc_task_net(struct inode *dir)
                net = get_net(ns->net_ns);
        }
 
+       fs_info = proc_sb_info(dir->i_sb);
+       if (fs_info->pidonly != PROC_PIDONLY_ON)
+               return net;
+
+       cred = rcu_dereference(fs_info->mounter_cred);
+       if (security_capable(cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) != 0) {
+               put_net(net);
+               return NULL;
+       }
        return net;
 }
 
fs/proc/root.c +6 -2
index d8ca41d823e4..68397900dab7 100644
@@ -300,11 +300,15 @@ static int proc_reconfigure(struct fs_context *fc)
 {
        struct super_block *sb = fc->root->d_sb;
        struct proc_fs_info *fs_info = proc_sb_info(sb);
+       const struct cred *cred;
 
        sync_filesystem(sb);
 
-       proc_apply_options(fs_info, fc, current_user_ns());
-       return 0;
+       cred = rcu_replace_pointer(fs_info->mounter_cred, get_cred(fc->cred),
+                                  lockdep_is_held(&sb->s_umount));
+       put_cred(cred);
+
+       return proc_apply_options(sb, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
#21 lore raw

Re: [PATCH v7 2/5] proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

Quoted 71 lines · Christian Brauner
Quoted 59 lines · Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 5 +++++
 include/linux/proc_fs.h | 1 +
 3 files changed, 14 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +1 -0
index d8ca41d823e4..ed8a101d09d3 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */

fs/proc/root.c static int proc_reconfigure(struct fs_context *fc) · line 308

Quoted 5 lines
fs/proc/root.c +2 -0
@@ -303,6 +304,9 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
+	put_cred(fs_info->mounter_cred);
+	fs_info->mounter_cred = get_cred(fc->cred);

Afaict, this races with get_proc_task_net(). You need a synchronization mechanism here so that get_proc_task_net() doesn't risk accessing invalid mounter creds while someone concurrently updates the creds. Proposal how to fix that below.

Quoted 4 lines

But I'm kinda torn here anyway whether we want that credential change on remount. The problem is that someone might inadvertently allow access to /proc/<pid>/net as a side-effect simply because they remounted procfs. But they never had a chance to prevent this.

I think you're right, and there's no need to change credentials on remount. At least not now.

Quoted 3 lines

I think it's best if mounter_creds stays fixed just as they do for overlayfs. So we don't allow them to change on reconfigure. That also makes all of the code I hinted at below pointless.

I'll just remove the mounter_cred update from proc_reconfigure.

Quoted 96 lines

If we ever want to change the credentials it's easier to add a mount option to procfs like I did for overlayfs.

_Untested_ patches:

First, the preparatory patch diff (no functional changes intended):

fs/proc/proc_net.c +10 -10
index 52f0b75cbce2..81825e5819b8 100644
@@ -268,19 +268,19 @@ EXPORT_SYMBOL_GPL(proc_create_net_single_write);
 static struct net *get_proc_task_net(struct inode *dir)
 {
        struct task_struct *task;
-       struct nsproxy *ns;
-       struct net *net = NULL;
+       struct net *net;
 
-       rcu_read_lock();
+       guard(rcu)();
        task = pid_task(proc_pid(dir), PIDTYPE_PID);
-       if (task != NULL) {
-               task_lock(task);
-               ns = task->nsproxy;
-               if (ns != NULL)
-                       net = get_net(ns->net_ns);
-               task_unlock(task);
+       if (!task)
+               return NULL;
+
+       scoped_guard(task_lock, task) {
+               struct nsproxy *ns = task->nsproxy;
+               if (!ns)
+                       return NULL;
+               net = get_net(ns->net_ns);
        }
-       rcu_read_unlock();
 
        return net;
 }

And then on top of it something like:

fs/proc/proc_net.c +11 -0
index 81825e5819b8..47dc9806395c 100644
@@ -269,6 +269,8 @@ static struct net *get_proc_task_net(struct inode *dir)
 {
        struct task_struct *task;
        struct net *net;
+       struct proc_fs_info *fs_info;
+       const struct cred *cred;
 
        guard(rcu)();
        task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,15 @@ static struct net *get_proc_task_net(struct inode *dir)
                net = get_net(ns->net_ns);
        }
 
+       fs_info = proc_sb_info(dir->i_sb);
+       if (fs_info->pidonly != PROC_PIDONLY_ON)
+               return net;
+
+       cred = rcu_dereference(fs_info->mounter_cred);
+       if (security_capable(cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) != 0) {
+               put_net(net);
+               return NULL;
+       }
        return net;
 }
 
fs/proc/root.c +6 -2
index d8ca41d823e4..68397900dab7 100644
@@ -300,11 +300,15 @@ static int proc_reconfigure(struct fs_context *fc)
 {
        struct super_block *sb = fc->root->d_sb;
        struct proc_fs_info *fs_info = proc_sb_info(sb);
+       const struct cred *cred;
 
        sync_filesystem(sb);
 
-       proc_apply_options(fs_info, fc, current_user_ns());
-       return 0;
+       cred = rcu_replace_pointer(fs_info->mounter_cred, get_cred(fc->cred),
+                                  lockdep_is_held(&sb->s_umount));
+       put_cred(cred);
+
+       return proc_apply_options(sb, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
Rgrds, legion
#22 lore raw

PATCH v7 3/5 proc: Disable cancellation of subset=pid option

In reply to Alexey Gladkov

When procfs is mounted with subset=pid option, where is no way to remount it with this option removed. This is done in order not to make visible what ever was hidden since some checks occur during mount.

This patch makes the limitation explicit and prints an error message.

---
 fs/proc/root.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)
fs/proc/root.c +10 -5
index ed8a101d09d3..b9f33b67cdd6 100644
@@ -223,7 +223,7 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
@@ -233,13 +233,17 @@ static void proc_apply_options(struct proc_fs_info *fs_info,
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
-	if (ctx->mask & (1 << Opt_subset))
+	if (ctx->mask & (1 << Opt_subset)) {
+		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
+	}
 	if (ctx->mask & (1 << Opt_pidns) &&
 	    !WARN_ON_ONCE(fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)) {
 		put_pid_ns(fs_info->pid_ns);
 		fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	}
+	return 0;
 }
 
 static int proc_fill_super(struct super_block *s, struct fs_context *fc)
@@ -255,7 +259,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	proc_apply_options(fs_info, fc, current_user_ns());
+	ret = proc_apply_options(fs_info, fc, current_user_ns());
+	if (ret)
+		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -307,8 +313,7 @@ static int proc_reconfigure(struct fs_context *fc)
 	put_cred(fs_info->mounter_cred);
 	fs_info->mounter_cred = get_cred(fc->cred);
 
-	proc_apply_options(fs_info, fc, current_user_ns());
-	return 0;
+	return proc_apply_options(fs_info, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
2.52.0
#23 lore raw

PATCH v7 4/5 proc: Relax check of mount visibility

In reply to Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 16 ++++++++++------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 19 deletions(-)
fs/namespace.c +16 -13
index c58674a20cad..7daa86315c05 100644
@@ -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;
 
-		/* This mount is not fully visible if there are any
-		 * locked child mounts that cover anything except for
-		 * empty directories.
-		 */
-		list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
-			struct inode *inode = child->mnt_mountpoint->d_inode;
-			/* Only worry about locked mounts */
-			if (!(child->mnt.mnt_flags & MNT_LOCKED))
-				continue;
-			/* Is the directory permanently empty? */
-			if (!is_empty_dir_inode(inode))
-				goto next;
+		if (!(sb->s_iflags & SB_I_USERNS_ALLOW_REVEALING)) {
+			/* This mount is not fully visible if there are any
+			 * locked child mounts that cover anything except for
+			 * empty directories.
+			 */
+			list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+				struct inode *inode = child->mnt_mountpoint->d_inode;
+				/* Only worry about locked mounts */
+				if (!IS_MNT_LOCKED(child))
+					continue;
+				/* Is the directory permanently empty? */
+				if (!is_empty_dir_inode(inode))
+					goto next;
+			}
 		}
 		/* Preserve the locked attributes */
 		*new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
fs/proc/root.c +10 -6
index b9f33b67cdd6..354dc13417e3 100644
@@ -223,18 +223,21 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static int proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct super_block *s,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
 	struct proc_fs_context *ctx = fc->fs_private;
+	struct proc_fs_info *fs_info = proc_sb_info(s);
 
 	if (ctx->mask & (1 << Opt_gid))
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
 	if (ctx->mask & (1 << Opt_subset)) {
-		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+		if (ctx->pidonly == PROC_PIDONLY_ON)
+			s->s_iflags |= SB_I_USERNS_ALLOW_REVEALING;
+		else if (fs_info->pidonly == PROC_PIDONLY_ON)
 			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
 	}
@@ -259,9 +262,6 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	ret = proc_apply_options(fs_info, fc, current_user_ns());
-	if (ret)
-		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -273,6 +273,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	s->s_time_gran = 1;
 	s->s_fs_info = fs_info;
 
+	ret = proc_apply_options(s, fc, current_user_ns());
+	if (ret)
+		return ret;
+
 	/*
 	 * procfs isn't actually a stacking filesystem; however, there is
 	 * too much magic going on inside it to permit stacking things on
@@ -313,7 +317,7 @@ static int proc_reconfigure(struct fs_context *fc)
 	put_cred(fs_info->mounter_cred);
 	fs_info->mounter_cred = get_cred(fc->cred);
 
-	return proc_apply_options(fs_info, fc, current_user_ns());
+	return proc_apply_options(sb, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
include/linux/fs/super_types.h +2 -0
index 6bd3009e09b3..5e640b9140df 100644
@@ -333,4 +333,6 @@ struct super_block {
 #define SB_I_NOIDMAP	0x00002000	/* No idmapped mounts on this superblock */
 #define SB_I_ALLOW_HSM	0x00004000	/* Allow HSM events on this superblock */
 
+#define SB_I_USERNS_ALLOW_REVEALING	0x00008000 /* Skip full visibility check */
+
 #endif /* _LINUX_FS_SUPER_TYPES_H */
Signature · 1 line
2.52.0
#24 lore raw

PATCH v7 5/5 docs: proc: add documentation about relaxing visibility restrictions

In reply to Alexey Gladkov
---
 Documentation/filesystems/proc.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Documentation/filesystems/proc.rst +2 -1
index c8864fcbdec7..3acf178c1202 100644
@@ -2417,7 +2417,8 @@ so will give an `-EBUSY` error).
 If user namespaces are in use, the kernel additionally checks the instances of
 procfs available to the mounter and will not allow procfs to be mounted if:
 
-  1. This mount is not fully visible.
+  1. This mount is not fully visible unless the new procfs is going to be
+     mounted with subset=pid option.
 
      a. It's root directory is not the root directory of the filesystem.
      b. If any file or non-empty procfs directory is hidden by another mount.
Signature · 1 line
2.52.0
#25 lore raw

v8 cover proc: subset=pid: Relax check of mount visibility

In reply to Alexey Gladkov

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

Currently, these restrictions prevent pidfs from being mounted inside rootless containers, as almost all container implementations override part of procfs to hide certain directories. Relaxing these restrictions will allow pidfs to be used in nested containerization.

---
Changelog
---------
v8:
* Remove mounter credential change on remount as suggested by Christian Brauner.

v7:
* Rebase on v6.19-rc5.
* Rename SB_I_DYNAMIC to SB_I_USERNS_ALLOW_REVEALING.

v6:
* Add documentation about procfs mount restrictions.
* Reorder commits for better review.

v4:
* Set SB_I_DYNAMIC only if pidonly is set.
* Add an error message if subset=pid is canceled during remount.

v3:
* Add 'const' to struct cred *mounter_cred (fix kernel test robot warning).

v2:
* cache the mounters credentials and make access to the net directories contingent of the permissions of the mounter of procfs.

Alexey Gladkov (5):
docs: proc: add documentation about mount restrictions
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
proc: Disable cancellation of subset=pid option
proc: Relax check of mount visibility
docs: proc: add documentation about relaxing visibility restrictions

 Documentation/filesystems/proc.rst | 15 +++++++++++++++
 fs/namespace.c                     | 29 ++++++++++++++++-------------
 fs/proc/proc_net.c                 |  8 ++++++++
 fs/proc/root.c                     | 22 ++++++++++++++++------
 include/linux/fs/super_types.h     |  2 ++
 include/linux/proc_fs.h            |  1 +
 6 files changed, 58 insertions(+), 19 deletions(-)
Signature · 1 line
2.53.0
#26 lore raw

PATCH v8 1/5 docs: proc: add documentation about mount restrictions

procfs has a number of mounting restrictions that are not documented anywhere.

---
 Documentation/filesystems/proc.rst | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
Documentation/filesystems/proc.rst +14 -0
index 8256e857e2d7..c8864fcbdec7 100644
@@ -52,6 +52,7 @@ fixes/update part 1.1  Stefani Seibold <[email protected]>    June 9 2009
 
   4	Configuring procfs
   4.1	Mount options
+  4.2	Mount restrictions
 
   5	Filesystem behavior
 
@@ -2410,6 +2411,19 @@ will use the calling process's active pid namespace. Note that the pid
 namespace of an existing procfs instance cannot be modified (attempting to do
 so will give an `-EBUSY` error).
 
+4.2	Mount restrictions
+--------------------------
+
+If user namespaces are in use, the kernel additionally checks the instances of
+procfs available to the mounter and will not allow procfs to be mounted if:
+
+  1. This mount is not fully visible.
+
+     a. It's root directory is not the root directory of the filesystem.
+     b. If any file or non-empty procfs directory is hidden by another mount.
+
+  2. A new mount overrides the readonly option or any option from atime familty.
+
 Chapter 5: Filesystem behavior
 ==============================
 
Signature · 1 line
2.53.0
#27 lore raw

PATCH v8 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

In reply to Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN. To avoid inadvertently allowing access to /proc/<pid>/net, updating mounter credentials is not supported.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 2 ++
 include/linux/proc_fs.h | 1 +
 3 files changed, 11 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +2 -0
index d8ca41d823e4..c4af3a9b1a44 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
@@ -350,6 +351,7 @@ static void proc_kill_sb(struct super_block *sb)
 	kill_anon_super(sb);
 	if (fs_info) {
 		put_pid_ns(fs_info->pid_ns);
+		put_cred(fs_info->mounter_cred);
 		kfree_rcu(fs_info, rcu);
 	}
 }
include/linux/proc_fs.h +1 -0
index 19d1c5e5f335..ec123c277d49 100644
@@ -67,6 +67,7 @@ enum proc_pidonly {
 struct proc_fs_info {
 	struct pid_namespace *pid_ns;
 	kgid_t pid_gid;
+	const struct cred *mounter_cred;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
 	struct rcu_head rcu;
Signature · 1 line
2.53.0
#28 lore raw

PATCH v8 3/5 proc: Disable cancellation of subset=pid option

In reply to Alexey Gladkov

When procfs is mounted with subset=pid option, where is no way to remount it with this option removed. This is done in order not to make visible what ever was hidden since some checks occur during mount.

This patch makes the limitation explicit and prints an error message.

---
 fs/proc/root.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)
fs/proc/root.c +10 -5
index c4af3a9b1a44..535a168046e3 100644
@@ -223,7 +223,7 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
@@ -233,13 +233,17 @@ static void proc_apply_options(struct proc_fs_info *fs_info,
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
-	if (ctx->mask & (1 << Opt_subset))
+	if (ctx->mask & (1 << Opt_subset)) {
+		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
+	}
 	if (ctx->mask & (1 << Opt_pidns) &&
 	    !WARN_ON_ONCE(fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)) {
 		put_pid_ns(fs_info->pid_ns);
 		fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	}
+	return 0;
 }
 
 static int proc_fill_super(struct super_block *s, struct fs_context *fc)
@@ -255,7 +259,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	proc_apply_options(fs_info, fc, current_user_ns());
+	ret = proc_apply_options(fs_info, fc, current_user_ns());
+	if (ret)
+		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -304,8 +310,7 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
-	proc_apply_options(fs_info, fc, current_user_ns());
-	return 0;
+	return proc_apply_options(fs_info, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
2.53.0
#29 lore raw

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

In reply to Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 17 ++++++++++-------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 20 deletions(-)
fs/namespace.c +16 -13
index c58674a20cad..7daa86315c05 100644
@@ -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;
 
-		/* This mount is not fully visible if there are any
-		 * locked child mounts that cover anything except for
-		 * empty directories.
-		 */
-		list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
-			struct inode *inode = child->mnt_mountpoint->d_inode;
-			/* Only worry about locked mounts */
-			if (!(child->mnt.mnt_flags & MNT_LOCKED))
-				continue;
-			/* Is the directory permanently empty? */
-			if (!is_empty_dir_inode(inode))
-				goto next;
+		if (!(sb->s_iflags & SB_I_USERNS_ALLOW_REVEALING)) {
+			/* This mount is not fully visible if there are any
+			 * locked child mounts that cover anything except for
+			 * empty directories.
+			 */
+			list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
+				struct inode *inode = child->mnt_mountpoint->d_inode;
+				/* Only worry about locked mounts */
+				if (!IS_MNT_LOCKED(child))
+					continue;
+				/* Is the directory permanently empty? */
+				if (!is_empty_dir_inode(inode))
+					goto next;
+			}
 		}
 		/* Preserve the locked attributes */
 		*new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
fs/proc/root.c +10 -7
index 535a168046e3..e029d3587494 100644
@@ -223,18 +223,21 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static int proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct super_block *s,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
 	struct proc_fs_context *ctx = fc->fs_private;
+	struct proc_fs_info *fs_info = proc_sb_info(s);
 
 	if (ctx->mask & (1 << Opt_gid))
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
 	if (ctx->mask & (1 << Opt_subset)) {
-		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+		if (ctx->pidonly == PROC_PIDONLY_ON)
+			s->s_iflags |= SB_I_USERNS_ALLOW_REVEALING;
+		else if (fs_info->pidonly == PROC_PIDONLY_ON)
 			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
 	}
@@ -259,9 +262,6 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	ret = proc_apply_options(fs_info, fc, current_user_ns());
-	if (ret)
-		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -273,6 +273,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	s->s_time_gran = 1;
 	s->s_fs_info = fs_info;
 
+	ret = proc_apply_options(s, fc, current_user_ns());
+	if (ret)
+		return ret;
+
 	/*
 	 * procfs isn't actually a stacking filesystem; however, there is
 	 * too much magic going on inside it to permit stacking things on
@@ -306,11 +310,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 static int proc_reconfigure(struct fs_context *fc)
 {
 	struct super_block *sb = fc->root->d_sb;
-	struct proc_fs_info *fs_info = proc_sb_info(sb);
 
 	sync_filesystem(sb);
 
-	return proc_apply_options(fs_info, fc, current_user_ns());
+	return proc_apply_options(sb, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
include/linux/fs/super_types.h +2 -0
index 6bd3009e09b3..5e640b9140df 100644
@@ -333,4 +333,6 @@ struct super_block {
 #define SB_I_NOIDMAP	0x00002000	/* No idmapped mounts on this superblock */
 #define SB_I_ALLOW_HSM	0x00004000	/* Allow HSM events on this superblock */
 
+#define SB_I_USERNS_ALLOW_REVEALING	0x00008000 /* Skip full visibility check */
+
 #endif /* _LINUX_FS_SUPER_TYPES_H */
Signature · 1 line
2.53.0
#30 lore raw

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

Quoted 30 lines · Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 17 ++++++++++-------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 20 deletions(-)
fs/namespace.c +2 -1
index c58674a20cad..7daa86315c05 100644
@@ -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 */

fs/namespace.c static bool mnt_already_visible(struct mnt_namespace *ns, · line 6138

Quoted 2 lines
fs/namespace.c +0 -0
@@ -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 <[email protected]>
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.

---
 fs/mount.h     |  4 ++++
 fs/namespace.c | 19 +++++++++++--------
 2 files changed, 15 insertions(+), 8 deletions(-)
fs/mount.h +4 -0
index e0816c11a198..5df134d56d47 100644
@@ -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);
fs/namespace.c +11 -8
index a67cbe42746d..764081c690d5 100644
@@ -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;
 
Signature · 1 line
2.47.3
#31 lore raw

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

Quoted 73 lines · Christian Brauner
Quoted 30 lines · Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 17 ++++++++++-------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 20 deletions(-)
fs/namespace.c +2 -1
index c58674a20cad..7daa86315c05 100644
@@ -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 */

fs/namespace.c static bool mnt_already_visible(struct mnt_namespace *ns, · line 6138

Quoted 2 lines
fs/namespace.c +0 -0
@@ -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]:

Quoted 1 line

From 4bbd41e7a3ef91667dd334f31b1b6bf8caec0599 Mon Sep 17 00:00:00 2001

Quoted 8 lines

From: Christian Brauner <[email protected]>
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.

If you rework this I would expect to take it for v7.3. It's a bit late now...

#32 lore raw

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

Quoted 78 lines · Christian Brauner
Quoted 77 lines · Christian Brauner
Quoted 30 lines · Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 17 ++++++++++-------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 20 deletions(-)
fs/namespace.c +2 -1
index c58674a20cad..7daa86315c05 100644
@@ -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 */

fs/namespace.c static bool mnt_already_visible(struct mnt_namespace *ns, · line 6138

Quoted 2 lines
fs/namespace.c +0 -0
@@ -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]:

Quoted 1 line

From 4bbd41e7a3ef91667dd334f31b1b6bf8caec0599 Mon Sep 17 00:00:00 2001

From: Christian Brauner <[email protected]>
Date: Tue, 17 Feb 2026 12:02:34 +0100
Subject: [PATCH] namespace: record fully visible mounts in list

Quoted 7 lines
Quoted 4 lines

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.

If you rework this I would expect to take it for v7.3. It's a bit late now...

No problem. I understand. Sorry it took me so long to get back to you. I was laid off from my job and had to look for a new one quickly.

I’ll be back soon to update this patch.

Signature · 1 line
Rgrds, legion
#33 lore raw

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

Quoted 84 lines · Alexey Gladkov
Quoted 83 lines · Christian Brauner
Quoted 81 lines · Christian Brauner
Quoted 30 lines · Alexey Gladkov

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.

---
 fs/namespace.c                 | 29 ++++++++++++++++-------------
 fs/proc/root.c                 | 17 ++++++++++-------
 include/linux/fs/super_types.h |  2 ++
 3 files changed, 28 insertions(+), 20 deletions(-)
fs/namespace.c +2 -1
index c58674a20cad..7daa86315c05 100644
@@ -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 */

fs/namespace.c static bool mnt_already_visible(struct mnt_namespace *ns, · line 6138

Quoted 2 lines
fs/namespace.c +0 -0
@@ -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]:

Quoted 1 line

From 4bbd41e7a3ef91667dd334f31b1b6bf8caec0599 Mon Sep 17 00:00:00 2001

From: Christian Brauner <[email protected]>
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.

Quoted 5 lines
Quoted 2 lines

If you rework this I would expect to take it for v7.3. It's a bit late now...

No problem. I understand. Sorry it took me so long to get back to you. I was laid off from my job and had to look for a new one quickly.

Damn, I'm very sorry to hear this. But the hopefully this will just be a very minor setback.

#34 lore raw

PATCH v8 5/5 docs: proc: add documentation about relaxing visibility restrictions

In reply to Alexey Gladkov
---
 Documentation/filesystems/proc.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
Documentation/filesystems/proc.rst +2 -1
index c8864fcbdec7..3acf178c1202 100644
@@ -2417,7 +2417,8 @@ so will give an `-EBUSY` error).
 If user namespaces are in use, the kernel additionally checks the instances of
 procfs available to the mounter and will not allow procfs to be mounted if:
 
-  1. This mount is not fully visible.
+  1. This mount is not fully visible unless the new procfs is going to be
+     mounted with subset=pid option.
 
      a. It's root directory is not the root directory of the filesystem.
      b. If any file or non-empty procfs directory is hidden by another mount.
Signature · 1 line
2.53.0
#35 lore raw

v9 cover proc: subset=pid: Relax check of mount visibility

In reply to Alexey Gladkov

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

Currently, these restrictions prevent pidfs from being mounted inside rootless containers, as almost all container implementations override part of procfs to hide certain directories. Relaxing these restrictions will allow pidfs to be used in nested containerization.

---

Changelog
---------
v9:
* Rework the patch based on the one proposed by Christian Brauner.

v8:
* Remove mounter credential change on remount as suggested by Christian Brauner.

v7:
* Rebase on v6.19-rc5.
* Rename SB_I_DYNAMIC to SB_I_USERNS_ALLOW_REVEALING.

v6:
* Add documentation about procfs mount restrictions.
* Reorder commits for better review.

v4:
* Set SB_I_DYNAMIC only if pidonly is set.
* Add an error message if subset=pid is canceled during remount.

v3:
* Add 'const' to struct cred *mounter_cred (fix kernel test robot warning).

v2:
* cache the mounters credentials and make access to the net directories contingent of the permissions of the mounter of procfs.

Alexey Gladkov (4):
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
proc: Disable cancellation of subset=pid option
proc: Skip the visibility check if subset=pid is used
docs: proc: add documentation about mount restrictions

Christian Brauner (1):
namespace: record fully visible mounts in list

 Documentation/filesystems/proc.rst | 15 +++++++++++++
 fs/fs_context.c                    |  1 +
 fs/mount.h                         |  4 ++++
 fs/namespace.c                     | 34 ++++++++++++++++--------------
 fs/proc/proc_net.c                 |  8 +++++++
 fs/proc/root.c                     | 24 ++++++++++++++++-----
 include/linux/fs_context.h         |  1 +
 include/linux/proc_fs.h            |  1 +
 8 files changed, 67 insertions(+), 21 deletions(-)

base-commit: 028ef9c96e96197026887c0f092424679298aae8

Signature · 1 line
2.53.0
#36 lore raw

PATCH v9 1/5 namespace: record fully visible mounts in list

From: Christian Brauner <[email protected]>

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.

---
 fs/mount.h     |  4 ++++
 fs/namespace.c | 19 +++++++++++--------
 2 files changed, 15 insertions(+), 8 deletions(-)
fs/mount.h +4 -0
index e0816c11a198..5df134d56d47 100644
@@ -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);
fs/namespace.c +11 -8
index 854f4fc66469..539b74403072 100644
@@ -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);
 }
 
@@ -6310,22 +6315,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;
 
Signature · 1 line
2.53.0
#37 lore raw

PATCH v9 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

In reply to Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN. To avoid inadvertently allowing access to /proc/<pid>/net, updating mounter credentials is not supported.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 2 ++
 include/linux/proc_fs.h | 1 +
 3 files changed, 11 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +2 -0
index 0f9100559471..6d18f9ee0375 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
@@ -350,6 +351,7 @@ static void proc_kill_sb(struct super_block *sb)
 	kill_anon_super(sb);
 	if (fs_info) {
 		put_pid_ns(fs_info->pid_ns);
+		put_cred(fs_info->mounter_cred);
 		kfree_rcu(fs_info, rcu);
 	}
 }
include/linux/proc_fs.h +1 -0
index 19d1c5e5f335..ec123c277d49 100644
@@ -67,6 +67,7 @@ enum proc_pidonly {
 struct proc_fs_info {
 	struct pid_namespace *pid_ns;
 	kgid_t pid_gid;
+	const struct cred *mounter_cred;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
 	struct rcu_head rcu;
Signature · 1 line
2.53.0
#38 lore raw

PATCH v9 3/5 proc: Disable cancellation of subset=pid option

In reply to Alexey Gladkov

When procfs is mounted with subset=pid option, where is no way to remount it with this option removed. This is done in order not to make visible what ever was hidden since some checks occur during mount.

This patch makes the limitation explicit and prints an error message.

---
 fs/proc/root.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)
fs/proc/root.c +10 -5
index 6d18f9ee0375..05558654df31 100644
@@ -223,7 +223,7 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
@@ -233,13 +233,17 @@ static void proc_apply_options(struct proc_fs_info *fs_info,
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
 		fs_info->hide_pid = ctx->hidepid;
-	if (ctx->mask & (1 << Opt_subset))
+	if (ctx->mask & (1 << Opt_subset)) {
+		if (ctx->pidonly != PROC_PIDONLY_ON && fs_info->pidonly == PROC_PIDONLY_ON)
+			return invalf(fc, "proc: subset=pid cannot be unset\n");
 		fs_info->pidonly = ctx->pidonly;
+	}
 	if (ctx->mask & (1 << Opt_pidns) &&
 	    !WARN_ON_ONCE(fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)) {
 		put_pid_ns(fs_info->pid_ns);
 		fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	}
+	return 0;
 }
 
 static int proc_fill_super(struct super_block *s, struct fs_context *fc)
@@ -255,7 +259,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	proc_apply_options(fs_info, fc, current_user_ns());
+	ret = proc_apply_options(fs_info, fc, current_user_ns());
+	if (ret)
+		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
@@ -304,8 +310,7 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
-	proc_apply_options(fs_info, fc, current_user_ns());
-	return 0;
+	return proc_apply_options(fs_info, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
2.53.0
#39 lore raw

PATCH v9 4/5 proc: Skip the visibility check if subset=pid is used

In reply to Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---
 fs/fs_context.c            |  1 +
 fs/namespace.c             | 15 +++++++--------
 fs/proc/root.c             |  7 +++++++
 include/linux/fs_context.h |  1 +
 4 files changed, 16 insertions(+), 8 deletions(-)
fs/fs_context.c +1 -0
index a37b0a093505..2fd3d6422a38 100644
@@ -545,6 +545,7 @@ void vfs_clean_context(struct fs_context *fc)
 	kfree(fc->source);
 	fc->source = NULL;
 	fc->exclusive = false;
+	fc->skip_visibility = false;
 
 	fc->purpose = FS_CONTEXT_FOR_RECONFIGURE;
 	fc->phase = FS_CONTEXT_AWAITING_RECONF;
fs/namespace.c +7 -8
index 539b74403072..32aaedb020c1 100644
@@ -3755,7 +3755,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp
 	return graft_tree(newmnt, mp);
 }
 
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags);
 
 /*
  * Create a new mount using a superblock configuration and request it
@@ -3764,19 +3764,17 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct super_block *sb;
 	struct vfsmount *mnt __free(mntput) = fc_mount(fc);
 	int error;
 
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
-	sb = fc->root->d_sb;
-	error = security_sb_kern_mount(sb);
+	error = security_sb_kern_mount(fc->root->d_sb);
 	if (unlikely(error))
 		return error;
 
-	if (unlikely(mount_too_revealing(sb, &mnt_flags))) {
+	if (unlikely(mount_too_revealing(fc, &mnt_flags))) {
 		errorfcp(fc, "VFS", "Mount too revealing");
 		return -EPERM;
 	}
@@ -4463,7 +4461,7 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 		return ret;
 
 	ret = -EPERM;
-	if (mount_too_revealing(fc->root->d_sb, &mnt_flags)) {
+	if (mount_too_revealing(fc, &mnt_flags)) {
 		errorfcp(fc, "VFS", "Mount too revealing");
 		return ret;
 	}
@@ -6368,10 +6366,11 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 	return false;
 }
 
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }
 
 bool mnt_may_suid(struct vfsmount *mnt)
fs/proc/root.c +7 -0
index 05558654df31..6dc870b3061b 100644
@@ -263,6 +263,13 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	if (ret)
 		return ret;
 
+	/*
+	 * The dynamic part of procfs cannot be hidden using overmount.
+	 * Therefore, the check for "not fully visible" can be skipped.
+	 */
+	if (fs_info->pidonly)
+		fc->skip_visibility = true;
+
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
 	s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC;
include/linux/fs_context.h +1 -0
index 0d6c8a6d7be2..d80b77df2628 100644
@@ -110,6 +110,7 @@ struct fs_context {
 	bool			global:1;	/* Goes into &init_user_ns */
 	bool			oldapi:1;	/* Coming from mount(2) */
 	bool			exclusive:1;    /* create new superblock, reject existing one */
+	bool			skip_visibility:1; /* Skip visibility check for SB_I_USERNS_VISIBLE */
 };
 
 struct fs_context_operations {
Signature · 1 line
2.53.0
#40 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

fs/namespace.c +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

fs/namespace.c static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
fs/namespace.c +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Unfortunately, the fact that both subset=pid and fully-loaded procfs look like the same type (procfs) to mnt_already_visible() is something people have already exploited in userspace. (The k8s workaround from a long time ago used subset=pid from a dead pidns to stop the mount from being useful to an attacker while still bypassing mount_too_revealing(). That being said, that workaround was removed a long time ago and I don't know how widespread this is.)

I'd be happy to give it a shot but if it breaks userspace we might need a new mount option to work around it...

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/
#41 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 34 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

fs/namespace.c +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

fs/namespace.c static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
fs/namespace.c +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

Quoted 2 lines

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

Quoted 10 lines

Unfortunately, the fact that both subset=pid and fully-loaded procfs look like the same type (procfs) to mnt_already_visible() is something people have already exploited in userspace. (The k8s workaround from a long time ago used subset=pid from a dead pidns to stop the mount from being useful to an attacker while still bypassing mount_too_revealing(). That being said, that workaround was removed a long time ago and I don't know how widespread this is.)

I'd be happy to give it a shot but if it breaks userspace we might need a new mount option to work around it...

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/
#42 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 35 lines · Aleksa Sarai
Quoted 34 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

Quoted 6 lines
Quoted 2 lines

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

Aleksa?

From 1041e9c62d74ffd03c7bfc748cc952714ab51f01 Mon Sep 17 00:00:00 2001
From: Christian Brauner <[email protected]>
Date: Thu, 16 Apr 2026 15:22:15 +0200
Subject: [PATCH] [FOLD]: Don't allow to abuse restricted procfs mounts to
mount unrestricted ones

---
 fs/namespace.c                 | 11 ++++++++++-
 fs/proc/root.c                 |  5 ++++-
 include/linux/fs/super_types.h |  1 +
 3 files changed, 15 insertions(+), 2 deletions(-)
fs/namespace.c +10 -1
index 7b171a67dd50..099ce44e46b5 100644
@@ -6351,10 +6351,19 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 
 	guard(namespace_shared)();
 	hlist_for_each_entry(mnt, &ns->mnt_visible_mounts, mnt_ns_visible) {
+		const struct super_block *sb_visible = mnt->mnt.mnt_sb;
 		struct mount *child;
 		int mnt_flags;
 
-		if (mnt->mnt.mnt_sb->s_type != sb->s_type)
+		if (sb_visible->s_type != sb->s_type)
+			continue;
+
+		/*
+		 * If the new superblock is not going to be restricted then any
+		 * mount that is restricted cannot be used to allow it.
+		 */
+		if (!(sb->s_iflags & SB_I_USERNS_RESTRICTED) &&
+		    (sb_visible->s_iflags & SB_I_USERNS_RESTRICTED))
 			continue;
 
 		/* A local view of the mount flags */
fs/proc/root.c +4 -1
index 350fc4f888ab..a035467b465b 100644
@@ -267,8 +267,11 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	 * The dynamic part of procfs cannot be hidden using overmount.
 	 * Therefore, the check for "not fully visible" can be skipped.
 	 */
-	if (fs_info->pidonly)
+	if (fs_info->pidonly) {
 		fc->skip_visibility = true;
+		/* Indicate that this procfs instance hides a bunch of files. */
+		s->s_iflags |= SB_I_USERNS_RESTRICTED;
+	}
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
include/linux/fs/super_types.h +1 -0
index 182efbeb9520..4ae7d0c3d55b 100644
@@ -326,6 +326,7 @@ struct super_block {
 #define SB_I_STABLE_WRITES 0x00000008	/* don't modify blks until WB is done */
 
 /* sb->s_iflags to limit user namespace mounts */
+#define SB_I_USERNS_RESTRICTED		0x00000010
 #define SB_I_IMA_UNVERIFIABLE_SIGNATURE	0x00000020
 #define SB_I_UNTRUSTED_MOUNTER		0x00000040
 #define SB_I_EVM_HMAC_UNSUPPORTED	0x00000080
Signature · 1 line
2.47.3

From bcb9e887619cbf5f2cd0a6a80b299e3a5fc2692a Mon Sep 17 00:00:00 2001
From: Christian Brauner <[email protected]>
Date: Thu, 16 Apr 2026 15:04:31 +0200
Subject: [PATCH 1/2] fs: move SB_I_USERNS_VISIBLE to
FS_USERNS_MOUNT_RESTRICTED

Whether a filesystem's mounts need to undergo a visibility check in user namespaces is a static property of the filesystem type, not a runtime property of each superblock instance. Both proc and sysfs always set SB_I_USERNS_VISIBLE on their superblocks unconditionally (sysfs does so on first creation, and subsequent mounts reuse the same superblock).

Move this flag from sb->s_iflags (SB_I_USERNS_VISIBLE) to file_system_type->fs_flags (FS_USERNS_MOUNT_RESTRICTED) so the intent is expressed at the filesystem type level where it belongs.

All check sites are updated to test sb->s_type->fs_flags instead of sb->s_iflags. The SB_I_NOEXEC and SB_I_NODEV flags remain on the superblock as they are runtime properties set during fill_super.

---
 fs/namespace.c                 | 4 ++--
 fs/proc/root.c                 | 4 ++--
 fs/sysfs/mount.c               | 4 +---
 include/linux/fs.h             | 1 +
 include/linux/fs/super_types.h | 1 -
 kernel/acct.c                  | 2 +-
 6 files changed, 7 insertions(+), 9 deletions(-)
fs/namespace.c +2 -2
index fe919abd2f01..a60ddfe71c7a 100644
@@ -6405,10 +6405,10 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return false;
 
 	/* Can this filesystem be too revealing? */
-	s_iflags = sb->s_iflags;
-	if (!(s_iflags & SB_I_USERNS_VISIBLE))
+	if (!(sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED))
 		return false;
 
+	s_iflags = sb->s_iflags;
 	if ((s_iflags & required_iflags) != required_iflags) {
 		WARN_ONCE(1, "Expected s_iflags to contain 0x%lx\n",
 			  required_iflags);
fs/proc/root.c +2 -2
index 0f9100559471..b65053f9f046 100644
@@ -257,7 +257,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
-	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
+	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
 	s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC;
 	s->s_blocksize = 1024;
 	s->s_blocksize_bits = 10;
@@ -359,7 +359,7 @@ static struct file_system_type proc_fs_type = {
 	.init_fs_context	= proc_init_fs_context,
 	.parameters		= proc_fs_parameters,
 	.kill_sb		= proc_kill_sb,
-	.fs_flags		= FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
+	.fs_flags		= FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED | FS_DISALLOW_NOTIFY_PERM,
 };
 
 void __init proc_root_init(void)
fs/sysfs/mount.c +1 -3
index b199e8ff79b1..b45ea5d511e7 100644
@@ -32,8 +32,6 @@ static int sysfs_get_tree(struct fs_context *fc)
 	if (ret)
 		return ret;
 
-	if (kfc->new_sb_created)
-		fc->root->d_sb->s_iflags |= SB_I_USERNS_VISIBLE;
 	return 0;
 }
 
@@ -93,7 +91,7 @@ static struct file_system_type sysfs_fs_type = {
 	.name			= "sysfs",
 	.init_fs_context	= sysfs_init_fs_context,
 	.kill_sb		= sysfs_kill_sb,
-	.fs_flags		= FS_USERNS_MOUNT,
+	.fs_flags		= FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED,
 };
 
 int __init sysfs_init(void)
include/linux/fs.h +1 -0
index b5b01bb22d12..17a6baefb7d3 100644
@@ -2280,6 +2280,7 @@ struct file_system_type {
 #define FS_MGTIME		64	/* FS uses multigrain timestamps */
 #define FS_LBS			128	/* FS supports LBS */
 #define FS_POWER_FREEZE		256	/* Always freeze on suspend/hibernate */
+#define FS_USERNS_MOUNT_RESTRICTED 512	/* Restrict mount in userns if not already visible */
 #define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
 	int (*init_fs_context)(struct fs_context *);
 	const struct fs_parameter_spec *parameters;
include/linux/fs/super_types.h +0 -1
index 383050e7fdf5..182efbeb9520 100644
@@ -326,7 +326,6 @@ struct super_block {
 #define SB_I_STABLE_WRITES 0x00000008	/* don't modify blks until WB is done */
 
 /* sb->s_iflags to limit user namespace mounts */
-#define SB_I_USERNS_VISIBLE		0x00000010 /* fstype already mounted */
 #define SB_I_IMA_UNVERIFIABLE_SIGNATURE	0x00000020
 #define SB_I_UNTRUSTED_MOUNTER		0x00000040
 #define SB_I_EVM_HMAC_UNSUPPORTED	0x00000080
kernel/acct.c +1 -1
index cbbf79d718cf..c440d43479ca 100644
@@ -249,7 +249,7 @@ static int acct_on(const char __user *name)
 		return -EINVAL;
 
 	/* Exclude procfs and sysfs. */
-	if (file_inode(file)->i_sb->s_iflags & SB_I_USERNS_VISIBLE)
+	if (file_inode(file)->i_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED)
 		return -EINVAL;
 
 	if (!(file->f_mode & FMODE_CAN_WRITE))
Signature · 1 line
2.47.3

From b43adcafc42753065739a22a053417d43694d9ac Mon Sep 17 00:00:00 2001
From: Christian Brauner <[email protected]>
Date: Thu, 16 Apr 2026 15:04:47 +0200
Subject: [PATCH 2/2] sysfs: remove trivial sysfs_get_tree() wrapper

Now that FS_USERNS_MOUNT_RESTRICTED is a file_system_type flag, sysfs_get_tree() is a trivial wrapper around kernfs_get_tree() with no additional logic. Point sysfs_fs_context_ops.get_tree directly at kernfs_get_tree() and remove the wrapper.

---
 fs/sysfs/mount.c | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)
fs/sysfs/mount.c +1 -13
index b45ea5d511e7..88c10823fcaf 100644
@@ -23,18 +23,6 @@
 static struct kernfs_root *sysfs_root;
 struct kernfs_node *sysfs_root_kn;
 
-static int sysfs_get_tree(struct fs_context *fc)
-{
-	struct kernfs_fs_context *kfc = fc->fs_private;
-	int ret;
-
-	ret = kernfs_get_tree(fc);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
 static void sysfs_fs_context_free(struct fs_context *fc)
 {
 	struct kernfs_fs_context *kfc = fc->fs_private;
@@ -47,7 +35,7 @@ static void sysfs_fs_context_free(struct fs_context *fc)
 
 static const struct fs_context_operations sysfs_fs_context_ops = {
 	.free		= sysfs_fs_context_free,
-	.get_tree	= sysfs_get_tree,
+	.get_tree	= kernfs_get_tree,
 };
 
 static int sysfs_init_fs_context(struct fs_context *fc)
Signature · 1 line
2.47.3
#43 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 39 lines · Christian Brauner
Quoted 38 lines · Aleksa Sarai
Quoted 36 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Quoted 8 lines
Quoted 3 lines

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

Seems quite reasonable to me.

Quoted 3 lines

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

I think that's fine here and is probably all we need for now (though I think that the name is a little confusing especially given my next comment), but I think there is a bit of a deeper problem here that deserves to be mentioned if only for posterity.

The core issue really is that we actually have two versions of procfs now, and they are effectively distinguished by fs_context flags instead of fs_type. Back when subset=pid was merged there was a discussion about a hypothetical proc2 -- while that got dropped, we are kind of reinventing this split in an ad-hoc way.

Conceptually considering them as two distinct fs_types would be the more semantically correct thing to do, though I think it would be overkill to come up with some framework for that. (I also really hope we don't have any other filesystems where this is also the case.)

This is why I think the name is a little weird, since it isn't an issue about one procfs being restricted in a userns, it's that they are conceptually unrelated filesystem types. A very lightweight version of this would be making it something like SB_I_RESTRICTED_VARIANT that would be more a little more strict than what you have now -- SB_I_RESTRICTED_VARIANT would not be treated as compatible with anything (even another SB_I_RESTRICTED_VARIANT mount) so that if we add more variants in the future we don't treat them the same either. (Again, I don't think we need this now?) Of course this would still allow subset=pid without a procfs mount because of the other special casing for it in mount_too_revealing().

We are also limited in what semantics we can change here too (and I'm a little worried that even this bit for SB_I_USERNS_RESTRICTED is at risk of breaking something) so maybe that isn't needed today.

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/
#44 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 66 lines · Aleksa Sarai
Quoted 39 lines · Christian Brauner
Quoted 38 lines · Aleksa Sarai
Quoted 36 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Quoted 8 lines
Quoted 3 lines

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

Seems quite reasonable to me.

Quoted 3 lines

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

I think that's fine here and is probably all we need for now (though I think that the name is a little confusing especially given my next comment), but I think there is a bit of a deeper problem here that deserves to be mentioned if only for posterity.

The core issue really is that we actually have two versions of procfs now, and they are effectively distinguished by fs_context flags instead of fs_type. Back when subset=pid was merged there was a discussion about a hypothetical proc2 -- while that got dropped, we are kind of reinventing this split in an ad-hoc way.

Quoted 4 lines

Conceptually considering them as two distinct fs_types would be the more semantically correct thing to do, though I think it would be overkill to come up with some framework for that. (I also really hope we don't have any other filesystems where this is also the case.)

Doing this is almost trivial. The problem really is that we'd expose "procfs2" or whatever as a new mount option to userspace without actually having fundamentally revamped what we would like procfs2 to do. IOW, it would elevate a security hack that got added a while ago to a new filesystem type. I think that would just be sad. So I'm not so excited about doing this.

Quoted 3 lines

This is why I think the name is a little weird, since it isn't an issue about one procfs being restricted in a userns, it's that they are conceptually unrelated filesystem types. A very lightweight version of

I somewhat disagree because it's unclear where you draw the line. If you use the hidepid= stuff at its most restrictive where it only shows tasks your user owns you could also argue that it's very close to a separate filesystem type. It's strictly subtractive and so is pidonly. There's just nothing that's better or novel.

Quoted 8 lines

this would be making it something like SB_I_RESTRICTED_VARIANT that would be more a little more strict than what you have now -- SB_I_RESTRICTED_VARIANT would not be treated as compatible with anything (even another SB_I_RESTRICTED_VARIANT mount) so that if we add more variants in the future we don't treat them the same either. (Again, I don't think we need this now?) Of course this would still allow subset=pid without a procfs mount because of the other special casing for it in mount_too_revealing().

It's not a problem today. I think we can simply treat SB_I_RESTRICTED_VARIANT as not allowing other restricted variants. And we'd need a more elaborate mechanism anyway once we'd have "compatible" subsets.

Quoted 3 lines

We are also limited in what semantics we can change here too (and I'm a little worried that even this bit for SB_I_USERNS_RESTRICTED is at risk of breaking something) so maybe that isn't needed today.

So I think yes, SB_I_RESTRICTED_VARIANT is fine by me for now.

#45 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 67 lines · Christian Brauner
Quoted 66 lines · Aleksa Sarai
Quoted 39 lines · Christian Brauner
Quoted 38 lines · Aleksa Sarai
Quoted 36 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Quoted 8 lines
Quoted 3 lines

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

Seems quite reasonable to me.

Quoted 3 lines

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

I think that's fine here and is probably all we need for now (though I think that the name is a little confusing especially given my next comment), but I think there is a bit of a deeper problem here that deserves to be mentioned if only for posterity.

The core issue really is that we actually have two versions of procfs now, and they are effectively distinguished by fs_context flags instead of fs_type. Back when subset=pid was merged there was a discussion about a hypothetical proc2 -- while that got dropped, we are kind of reinventing this split in an ad-hoc way.

Quoted 8 lines
Quoted 4 lines

Conceptually considering them as two distinct fs_types would be the more semantically correct thing to do, though I think it would be overkill to come up with some framework for that. (I also really hope we don't have any other filesystems where this is also the case.)

Doing this is almost trivial. The problem really is that we'd expose "procfs2" or whatever as a new mount option to userspace without actually having fundamentally revamped what we would like procfs2 to do.

Agree.

The new procfs2 should not have the issues present in procfs.

Quoted 28 lines

IOW, it would elevate a security hack that got added a while ago to a new filesystem type. I think that would just be sad. So I'm not so excited about doing this.

Quoted 3 lines

This is why I think the name is a little weird, since it isn't an issue about one procfs being restricted in a userns, it's that they are conceptually unrelated filesystem types. A very lightweight version of

I somewhat disagree because it's unclear where you draw the line. If you use the hidepid= stuff at its most restrictive where it only shows tasks your user owns you could also argue that it's very close to a separate filesystem type. It's strictly subtractive and so is pidonly. There's just nothing that's better or novel.

Quoted 8 lines

this would be making it something like SB_I_RESTRICTED_VARIANT that would be more a little more strict than what you have now -- SB_I_RESTRICTED_VARIANT would not be treated as compatible with anything (even another SB_I_RESTRICTED_VARIANT mount) so that if we add more variants in the future we don't treat them the same either. (Again, I don't think we need this now?) Of course this would still allow subset=pid without a procfs mount because of the other special casing for it in mount_too_revealing().

It's not a problem today. I think we can simply treat SB_I_RESTRICTED_VARIANT as not allowing other restricted variants. And we'd need a more elaborate mechanism anyway once we'd have "compatible" subsets.

Quoted 5 lines
Quoted 3 lines

We are also limited in what semantics we can change here too (and I'm a little worried that even this bit for SB_I_USERNS_RESTRICTED is at risk of breaking something) so maybe that isn't needed today.

So I think yes, SB_I_RESTRICTED_VARIANT is fine by me for now.

Should I create a new version and add this to my patchset?

Signature · 1 line
Rgrds, legion
#46 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

Quoted 109 lines · Alexey Gladkov
Quoted 67 lines · Christian Brauner
Quoted 66 lines · Aleksa Sarai
Quoted 39 lines · Christian Brauner
Quoted 38 lines · Aleksa Sarai
Quoted 36 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Quoted 8 lines
Quoted 3 lines

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

Seems quite reasonable to me.

Quoted 3 lines

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

I think that's fine here and is probably all we need for now (though I think that the name is a little confusing especially given my next comment), but I think there is a bit of a deeper problem here that deserves to be mentioned if only for posterity.

The core issue really is that we actually have two versions of procfs now, and they are effectively distinguished by fs_context flags instead of fs_type. Back when subset=pid was merged there was a discussion about a hypothetical proc2 -- while that got dropped, we are kind of reinventing this split in an ad-hoc way.

Quoted 8 lines
Quoted 4 lines

Conceptually considering them as two distinct fs_types would be the more semantically correct thing to do, though I think it would be overkill to come up with some framework for that. (I also really hope we don't have any other filesystems where this is also the case.)

Doing this is almost trivial. The problem really is that we'd expose "procfs2" or whatever as a new mount option to userspace without actually having fundamentally revamped what we would like procfs2 to do.

Agree.

The new procfs2 should not have the issues present in procfs.

Quoted 28 lines

IOW, it would elevate a security hack that got added a while ago to a new filesystem type. I think that would just be sad. So I'm not so excited about doing this.

Quoted 3 lines

This is why I think the name is a little weird, since it isn't an issue about one procfs being restricted in a userns, it's that they are conceptually unrelated filesystem types. A very lightweight version of

I somewhat disagree because it's unclear where you draw the line. If you use the hidepid= stuff at its most restrictive where it only shows tasks your user owns you could also argue that it's very close to a separate filesystem type. It's strictly subtractive and so is pidonly. There's just nothing that's better or novel.

Quoted 8 lines

this would be making it something like SB_I_RESTRICTED_VARIANT that would be more a little more strict than what you have now -- SB_I_RESTRICTED_VARIANT would not be treated as compatible with anything (even another SB_I_RESTRICTED_VARIANT mount) so that if we add more variants in the future we don't treat them the same either. (Again, I don't think we need this now?) Of course this would still allow subset=pid without a procfs mount because of the other special casing for it in mount_too_revealing().

It's not a problem today. I think we can simply treat SB_I_RESTRICTED_VARIANT as not allowing other restricted variants. And we'd need a more elaborate mechanism anyway once we'd have "compatible" subsets.

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

We are also limited in what semantics we can change here too (and I'm a little worried that even this bit for SB_I_USERNS_RESTRICTED is at risk of breaking something) so maybe that isn't needed today.

So I think yes, SB_I_RESTRICTED_VARIANT is fine by me for now.

Should I create a new version and add this to my patchset?

Yes, please!

#47 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

In reply to Christian Brauner
Quoted 83 lines · Christian Brauner
Quoted 66 lines · Aleksa Sarai
Quoted 39 lines · Christian Brauner
Quoted 38 lines · Aleksa Sarai
Quoted 36 lines · Aleksa Sarai
Quoted 21 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---

(quoted diff) +2 -1
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)

Review static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags · line 6391

Quoted 6 lines
(quoted diff) +1 -1
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }

Unless I'm missing something (I haven't tested this locally yet, sorry), this will allow you to bypass mount_too_revealing() even for non-subset=pid mounts because once you create a subset=pid mount then a regular procfs mount will see the subset=pid mount and permit it.

I think the solution is quite simple -- you can also skip super-blocks that have fc->skip_visibility set in mnt_already_visible().

Quoted 8 lines
Quoted 3 lines

I now see that check was present in v8 but I guess its importance wasn't obvious. I guess this means we will need to reintroduce SB_I_USERNS_ALLOW_REVEALING. :/

I've been playing with something else. So first we should move SB_I_USERNS_REVEALING to an fs_type flag. It's not an optional thing and always set and never removed. That also means we can simplify sysfs_get_tree() to just kernfs_get_tree().

Seems quite reasonable to me.

Quoted 3 lines

And then we raise SB_I_USERNS_RESTRICTED on all procfs mounts with pid_only and disallow using them for calculating mount permissions for unrestricted procfs mounts.

I think that's fine here and is probably all we need for now (though I think that the name is a little confusing especially given my next comment), but I think there is a bit of a deeper problem here that deserves to be mentioned if only for posterity.

The core issue really is that we actually have two versions of procfs now, and they are effectively distinguished by fs_context flags instead of fs_type. Back when subset=pid was merged there was a discussion about a hypothetical proc2 -- while that got dropped, we are kind of reinventing this split in an ad-hoc way.

Quoted 4 lines

Conceptually considering them as two distinct fs_types would be the more semantically correct thing to do, though I think it would be overkill to come up with some framework for that. (I also really hope we don't have any other filesystems where this is also the case.)

Doing this is almost trivial. The problem really is that we'd expose "procfs2" or whatever as a new mount option to userspace without actually having fundamentally revamped what we would like procfs2 to do. IOW, it would elevate a security hack that got added a while ago to a new filesystem type. I think that would just be sad. So I'm not so excited about doing this.

Quoted 3 lines

This is why I think the name is a little weird, since it isn't an issue about one procfs being restricted in a userns, it's that they are conceptually unrelated filesystem types. A very lightweight version of

Quoted 5 lines

I somewhat disagree because it's unclear where you draw the line. If you use the hidepid= stuff at its most restrictive where it only shows tasks your user owns you could also argue that it's very close to a separate filesystem type. It's strictly subtractive and so is pidonly. There's just nothing that's better or novel.

I agree that hidepid=4 is arguably yet another procfs subset (in fact I was thinking of giving it as an example in my earlier mail) and arguably should have somewhat similar restrictions. hidepid=1 and hidepid=2 are not as extreme IMHO (since most /proc/$pid/* accesses are gated behind ptrace_may_access() anyway).

One potential model here would be to have an opaque bitmask of restrictions and only more-or-equally restricted superblocks can count towards mnt_already_visible(). But again, I think we can add this once we need it.

Quoted 14 lines
Quoted 8 lines

this would be making it something like SB_I_RESTRICTED_VARIANT that would be more a little more strict than what you have now -- SB_I_RESTRICTED_VARIANT would not be treated as compatible with anything (even another SB_I_RESTRICTED_VARIANT mount) so that if we add more variants in the future we don't treat them the same either. (Again, I don't think we need this now?) Of course this would still allow subset=pid without a procfs mount because of the other special casing for it in mount_too_revealing().

It's not a problem today. I think we can simply treat SB_I_RESTRICTED_VARIANT as not allowing other restricted variants. And we'd need a more elaborate mechanism anyway once we'd have "compatible" subsets.

Quoted 5 lines
Quoted 3 lines

We are also limited in what semantics we can change here too (and I'm a little worried that even this bit for SB_I_USERNS_RESTRICTED is at risk of breaking something) so maybe that isn't needed today.

So I think yes, SB_I_RESTRICTED_VARIANT is fine by me for now.

Sounds good.

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/
#48 lore raw

Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used

In reply to Alexey Gladkov
Quoted 101 lines · Alexey Gladkov

When procfs is mounted with the subset=pid option, all system files and directories from the root of the filesystem 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.

---
 fs/fs_context.c            |  1 +
 fs/namespace.c             | 15 +++++++--------
 fs/proc/root.c             |  7 +++++++
 include/linux/fs_context.h |  1 +
 4 files changed, 16 insertions(+), 8 deletions(-)
fs/fs_context.c +1 -0
index a37b0a093505..2fd3d6422a38 100644
@@ -545,6 +545,7 @@ void vfs_clean_context(struct fs_context *fc)
 	kfree(fc->source);
 	fc->source = NULL;
 	fc->exclusive = false;
+	fc->skip_visibility = false;
 
 	fc->purpose = FS_CONTEXT_FOR_RECONFIGURE;
 	fc->phase = FS_CONTEXT_AWAITING_RECONF;
fs/namespace.c +7 -8
index 539b74403072..32aaedb020c1 100644
@@ -3755,7 +3755,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mp
 	return graft_tree(newmnt, mp);
 }
 
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags);
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags);
 
 /*
  * Create a new mount using a superblock configuration and request it
@@ -3764,19 +3764,17 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 static int do_new_mount_fc(struct fs_context *fc, const struct path *mountpoint,
 			   unsigned int mnt_flags)
 {
-	struct super_block *sb;
 	struct vfsmount *mnt __free(mntput) = fc_mount(fc);
 	int error;
 
 	if (IS_ERR(mnt))
 		return PTR_ERR(mnt);
 
-	sb = fc->root->d_sb;
-	error = security_sb_kern_mount(sb);
+	error = security_sb_kern_mount(fc->root->d_sb);
 	if (unlikely(error))
 		return error;
 
-	if (unlikely(mount_too_revealing(sb, &mnt_flags))) {
+	if (unlikely(mount_too_revealing(fc, &mnt_flags))) {
 		errorfcp(fc, "VFS", "Mount too revealing");
 		return -EPERM;
 	}
@@ -4463,7 +4461,7 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags,
 		return ret;
 
 	ret = -EPERM;
-	if (mount_too_revealing(fc->root->d_sb, &mnt_flags)) {
+	if (mount_too_revealing(fc, &mnt_flags)) {
 		errorfcp(fc, "VFS", "Mount too revealing");
 		return ret;
 	}
@@ -6368,10 +6366,11 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 	return false;
 }
 
-static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags)
+static bool mount_too_revealing(struct fs_context *fc, int *new_mnt_flags)
 {
 	const unsigned long required_iflags = SB_I_NOEXEC | SB_I_NODEV;
 	struct mnt_namespace *ns = current->nsproxy->mnt_ns;
+	const struct super_block *sb = fc->root->d_sb;
 	unsigned long s_iflags;
 
 	if (ns->user_ns == &init_user_ns)
@@ -6388,7 +6387,7 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
-	return !mnt_already_visible(ns, sb, new_mnt_flags);
+	return (!fc->skip_visibility && !mnt_already_visible(ns, sb, new_mnt_flags));
 }
 
 bool mnt_may_suid(struct vfsmount *mnt)
fs/proc/root.c +0 -0
index 05558654df31..6dc870b3061b 100644
@@ -263,6 +263,13 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	if (ret)
 		return ret;

fs/proc/root.c static int proc_fill_super(struct super_block *s, struct fs_context *fc) · line 274

Quoted 10 lines
fs/proc/root.c +7 -0
 
+	/*
+	 * The dynamic part of procfs cannot be hidden using overmount.
+	 * Therefore, the check for "not fully visible" can be skipped.
+	 */
+	if (fs_info->pidonly)
+		fc->skip_visibility = true;
+
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;

I think we should move the SB_I_USERNS_VISIBLE check to the fs_type. It really is something that applies to the filesystem type and isn't a per-superblock thing. Then we can raise SB_I_USERNS_VISIBLE only on superblocks that are restricted via pid_only and discount those when deciding to allow procfs mount without pid_only. Something that Aleksa had pointed out on an earlier review. Let ms see if I can write that up.

#49 lore raw

PATCH v9 5/5 docs: proc: add documentation about mount restrictions

In reply to Alexey Gladkov

procfs has a number of mounting restrictions that are not documented anywhere.

---
 Documentation/filesystems/proc.rst | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
Documentation/filesystems/proc.rst +15 -0
index b0c0d1b45b99..699cc372e6c8 100644
@@ -52,6 +52,7 @@ fixes/update part 1.1  Stefani Seibold <[email protected]>    June 9 2009
 
   4	Configuring procfs
   4.1	Mount options
+  4.2	Mount restrictions
 
   5	Filesystem behavior
 
@@ -2410,6 +2411,20 @@ will use the calling process's active pid namespace. Note that the pid
 namespace of an existing procfs instance cannot be modified (attempting to do
 so will give an `-EBUSY` error).
 
+4.2	Mount restrictions
+--------------------------
+
+If user namespaces are in use, the kernel additionally checks the instances of
+procfs available to the mounter and will not allow procfs to be mounted if:
+
+  1. This mount is not fully visible unless the new procfs is going to be
+     mounted with subset=pid option.
+
+     a. It's root directory is not the root directory of the filesystem.
+     b. If any file or non-empty procfs directory is hidden by another mount.
+
+  2. A new mount overrides the readonly option or any option from atime familty.
+
 Chapter 5: Filesystem behavior
 ==============================
 
Signature · 1 line
2.53.0
#50 lore raw

v10 cover proc: subset=pid: Relax check of mount visibility

In reply to Alexey Gladkov

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

Currently, these restrictions prevent pidfs from being mounted inside rootless containers, as almost all container implementations override part of procfs to hide certain directories. Relaxing these restrictions will allow pidfs to be used in nested containerization.

---
Changelog
---------
v10:
* Rework visibility checks around Christian's FS_USERNS_MOUNT_RESTRICTED and SB_I_RESTRICTED_VARIANT approach instead of fs_context skip_visibility.
* Add Christian's sysfs_get_tree() cleanup.
* Treat subset=pid procfs as a restricted variant that is allowed without mnt_already_visible(), but cannot be used as visibility evidence for later mounts.
* Forbid changing subset=pid on procfs reconfigure in either direction to avoid exposing pre-existing overmounts after switching to subset=pid.
* Make failed subset=pid reconfigure leave other procfs options unchanged.
* Update procfs documentation accordingly.

v9:
* Rework the patch based on the one proposed by Christian Brauner.

v8:
* Remove mounter credential change on remount as suggested by Christian Brauner.

v7:
* Rebase on v6.19-rc5.
* Rename SB_I_DYNAMIC to SB_I_USERNS_ALLOW_REVEALING.

v6:
* Add documentation about procfs mount restrictions.
* Reorder commits for better review.

v4:
* Set SB_I_DYNAMIC only if pidonly is set.
* Add an error message if subset=pid is canceled during remount.

v3:
* Add 'const' to struct cred *mounter_cred (fix kernel test robot warning).

v2:
* cache the mounters credentials and make access to the net directories contingent of the permissions of the mounter of procfs.

Alexey Gladkov (4):
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
proc: prevent reconfiguring subset=pid
proc: handle subset=pid separately in userns visibility checks
docs: proc: add documentation about mount restrictions

Christian Brauner (3):
namespace: record fully visible mounts in list
fs: move SB_I_USERNS_VISIBLE to FS_USERNS_MOUNT_RESTRICTED
sysfs: remove trivial sysfs_get_tree() wrapper

 Documentation/filesystems/proc.rst | 19 ++++++++++++++++-
 fs/mount.h                         |  4 ++++
 fs/namespace.c                     | 34 +++++++++++++++++++++++-------
 fs/proc/proc_net.c                 |  8 +++++++
 fs/proc/root.c                     | 24 +++++++++++++++------
 fs/sysfs/mount.c                   | 18 ++--------------
 include/linux/fs.h                 |  1 +
 include/linux/fs/super_types.h     |  2 +-
 include/linux/proc_fs.h            |  1 +
 kernel/acct.c                      |  2 +-
 10 files changed, 80 insertions(+), 33 deletions(-)
Signature · 1 line
2.54.0
#51 lore raw

PATCH v10 1/7 namespace: record fully visible mounts in list

From: Christian Brauner <[email protected]>

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.

---
 fs/mount.h     |  4 ++++
 fs/namespace.c | 19 +++++++++++--------
 2 files changed, 15 insertions(+), 8 deletions(-)
fs/mount.h +4 -0
index e0816c11a198..5df134d56d47 100644
@@ -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);
fs/namespace.c +11 -8
index 854f4fc66469..539b74403072 100644
@@ -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);
 }
 
@@ -6310,22 +6315,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;
 
Signature · 1 line
2.54.0
#52 lore raw

PATCH v10 2/7 fs: move SB_I_USERNS_VISIBLE to FS_USERNS_MOUNT_RESTRICTED

In reply to Alexey Gladkov

From: Christian Brauner <[email protected]>

Whether a filesystem's mounts need to undergo a visibility check in user namespaces is a static property of the filesystem type, not a runtime property of each superblock instance. Both proc and sysfs always set SB_I_USERNS_VISIBLE on their superblocks unconditionally (sysfs does so on first creation, and subsequent mounts reuse the same superblock).

Move this flag from sb->s_iflags (SB_I_USERNS_VISIBLE) to file_system_type->fs_flags (FS_USERNS_MOUNT_RESTRICTED) so the intent is expressed at the filesystem type level where it belongs.

All check sites are updated to test sb->s_type->fs_flags instead of sb->s_iflags. The SB_I_NOEXEC and SB_I_NODEV flags remain on the superblock as they are runtime properties set during fill_super.

---
 fs/namespace.c                 | 6 +++---
 fs/proc/root.c                 | 4 ++--
 fs/sysfs/mount.c               | 4 +---
 include/linux/fs.h             | 1 +
 include/linux/fs/super_types.h | 1 -
 kernel/acct.c                  | 2 +-
 6 files changed, 8 insertions(+), 10 deletions(-)
fs/namespace.c +3 -3
index 539b74403072..ed13416370e3 100644
@@ -1099,7 +1099,7 @@ 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) &&
+	if ((mnt->mnt.mnt_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED) &&
 	    mnt->mnt.mnt_root == mnt->mnt.mnt_sb->s_root)
 		hlist_add_head(&mnt->mnt_ns_visible, &ns->mnt_visible_mounts);
 
@@ -6378,10 +6378,10 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return false;
 
 	/* Can this filesystem be too revealing? */
-	s_iflags = sb->s_iflags;
-	if (!(s_iflags & SB_I_USERNS_VISIBLE))
+	if (!(sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED))
 		return false;
 
+	s_iflags = sb->s_iflags;
 	if ((s_iflags & required_iflags) != required_iflags) {
 		WARN_ONCE(1, "Expected s_iflags to contain 0x%lx\n",
 			  required_iflags);
fs/proc/root.c +2 -2
index 0f9100559471..b65053f9f046 100644
@@ -257,7 +257,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
-	s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
+	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
 	s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC;
 	s->s_blocksize = 1024;
 	s->s_blocksize_bits = 10;
@@ -359,7 +359,7 @@ static struct file_system_type proc_fs_type = {
 	.init_fs_context	= proc_init_fs_context,
 	.parameters		= proc_fs_parameters,
 	.kill_sb		= proc_kill_sb,
-	.fs_flags		= FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
+	.fs_flags		= FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED | FS_DISALLOW_NOTIFY_PERM,
 };
 
 void __init proc_root_init(void)
fs/sysfs/mount.c +1 -3
index b199e8ff79b1..b45ea5d511e7 100644
@@ -32,8 +32,6 @@ static int sysfs_get_tree(struct fs_context *fc)
 	if (ret)
 		return ret;
 
-	if (kfc->new_sb_created)
-		fc->root->d_sb->s_iflags |= SB_I_USERNS_VISIBLE;
 	return 0;
 }
 
@@ -93,7 +91,7 @@ static struct file_system_type sysfs_fs_type = {
 	.name			= "sysfs",
 	.init_fs_context	= sysfs_init_fs_context,
 	.kill_sb		= sysfs_kill_sb,
-	.fs_flags		= FS_USERNS_MOUNT,
+	.fs_flags		= FS_USERNS_MOUNT | FS_USERNS_MOUNT_RESTRICTED,
 };
 
 int __init sysfs_init(void)
include/linux/fs.h +1 -0
index 8b3dd145b25e..4e6553359938 100644
@@ -2280,6 +2280,7 @@ struct file_system_type {
 #define FS_MGTIME		64	/* FS uses multigrain timestamps */
 #define FS_LBS			128	/* FS supports LBS */
 #define FS_POWER_FREEZE		256	/* Always freeze on suspend/hibernate */
+#define FS_USERNS_MOUNT_RESTRICTED 512	/* Restrict mount in userns if not already visible */
 #define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
 	int (*init_fs_context)(struct fs_context *);
 	const struct fs_parameter_spec *parameters;
include/linux/fs/super_types.h +0 -1
index 383050e7fdf5..182efbeb9520 100644
@@ -326,7 +326,6 @@ struct super_block {
 #define SB_I_STABLE_WRITES 0x00000008	/* don't modify blks until WB is done */
 
 /* sb->s_iflags to limit user namespace mounts */
-#define SB_I_USERNS_VISIBLE		0x00000010 /* fstype already mounted */
 #define SB_I_IMA_UNVERIFIABLE_SIGNATURE	0x00000020
 #define SB_I_UNTRUSTED_MOUNTER		0x00000040
 #define SB_I_EVM_HMAC_UNSUPPORTED	0x00000080
kernel/acct.c +1 -1
index 1e19722c64c3..9fdfee6f4729 100644
@@ -249,7 +249,7 @@ static int acct_on(const char __user *name)
 		return -EINVAL;
 
 	/* Exclude procfs and sysfs. */
-	if (file_inode(file)->i_sb->s_iflags & SB_I_USERNS_VISIBLE)
+	if (file_inode(file)->i_sb->s_type->fs_flags & FS_USERNS_MOUNT_RESTRICTED)
 		return -EINVAL;
 
 	if (!(file->f_mode & FMODE_CAN_WRITE))
Signature · 1 line
2.54.0
#53 lore raw

PATCH v10 3/7 sysfs: remove trivial sysfs_get_tree() wrapper

In reply to Alexey Gladkov

From: Christian Brauner <[email protected]>

Now that FS_USERNS_MOUNT_RESTRICTED is a file_system_type flag, sysfs_get_tree() is a trivial wrapper around kernfs_get_tree() with no additional logic. Point sysfs_fs_context_ops.get_tree directly at kernfs_get_tree() and remove the wrapper.

---
 fs/sysfs/mount.c | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)
fs/sysfs/mount.c +1 -13
index b45ea5d511e7..88c10823fcaf 100644
@@ -23,18 +23,6 @@
 static struct kernfs_root *sysfs_root;
 struct kernfs_node *sysfs_root_kn;
 
-static int sysfs_get_tree(struct fs_context *fc)
-{
-	struct kernfs_fs_context *kfc = fc->fs_private;
-	int ret;
-
-	ret = kernfs_get_tree(fc);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
 static void sysfs_fs_context_free(struct fs_context *fc)
 {
 	struct kernfs_fs_context *kfc = fc->fs_private;
@@ -47,7 +35,7 @@ static void sysfs_fs_context_free(struct fs_context *fc)
 
 static const struct fs_context_operations sysfs_fs_context_ops = {
 	.free		= sysfs_fs_context_free,
-	.get_tree	= sysfs_get_tree,
+	.get_tree	= kernfs_get_tree,
 };
 
 static int sysfs_init_fs_context(struct fs_context *fc)
Signature · 1 line
2.54.0
#54 lore raw

PATCH v10 4/7 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN

In reply to Alexey Gladkov

Cache the mounters credentials and allow access to the net directories contingent of the permissions of the mounter of proc.

Do not show /proc/self/net when proc is mounted with subset=pid option and the mounter does not have CAP_NET_ADMIN. To avoid inadvertently allowing access to /proc/<pid>/net, updating mounter credentials is not supported.

---
 fs/proc/proc_net.c      | 8 ++++++++
 fs/proc/root.c          | 2 ++
 include/linux/proc_fs.h | 1 +
 3 files changed, 11 insertions(+)
fs/proc/proc_net.c +8 -0
index 52f0b75cbce2..6e0ccef0169f 100644
@@ -23,6 +23,7 @@
 #include <linux/uidgid.h>
 #include <net/net_namespace.h>
 #include <linux/seq_file.h>
+#include <linux/security.h>
 
 #include "internal.h"
 
@@ -270,6 +271,7 @@ static struct net *get_proc_task_net(struct inode *dir)
 	struct task_struct *task;
 	struct nsproxy *ns;
 	struct net *net = NULL;
+	struct proc_fs_info *fs_info = proc_sb_info(dir->i_sb);
 
 	rcu_read_lock();
 	task = pid_task(proc_pid(dir), PIDTYPE_PID);
@@ -282,6 +284,12 @@ static struct net *get_proc_task_net(struct inode *dir)
 	}
 	rcu_read_unlock();
 
+	if (net && (fs_info->pidonly == PROC_PIDONLY_ON) &&
+	    security_capable(fs_info->mounter_cred, net->user_ns, CAP_NET_ADMIN, CAP_OPT_NONE) < 0) {
+		put_net(net);
+		net = NULL;
+	}
+
 	return net;
 }
 
fs/proc/root.c +2 -0
index b65053f9f046..89e5678129e4 100644
@@ -254,6 +254,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return -ENOMEM;
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
+	fs_info->mounter_cred = get_cred(fc->cred);
 	proc_apply_options(fs_info, fc, current_user_ns());
 
 	/* User space would break if executables or devices appear on proc */
@@ -350,6 +351,7 @@ static void proc_kill_sb(struct super_block *sb)
 	kill_anon_super(sb);
 	if (fs_info) {
 		put_pid_ns(fs_info->pid_ns);
+		put_cred(fs_info->mounter_cred);
 		kfree_rcu(fs_info, rcu);
 	}
 }
include/linux/proc_fs.h +1 -0
index 19d1c5e5f335..ec123c277d49 100644
@@ -67,6 +67,7 @@ enum proc_pidonly {
 struct proc_fs_info {
 	struct pid_namespace *pid_ns;
 	kgid_t pid_gid;
+	const struct cred *mounter_cred;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
 	struct rcu_head rcu;
Signature · 1 line
2.54.0
#55 lore raw

PATCH v10 5/7 proc: prevent reconfiguring subset=pid

In reply to Alexey Gladkov

Changing subset=pid on an existing procfs instance is not safe. If a full procfs mount has entries hidden by overmounts, switching it to subset=pid would hide the top-level procfs entries from lookup and readdir while leaving the existing overmounts reachable.

Reject attempts to change the subset=pid state during reconfigure before applying any other procfs mount options, so a failed reconfigure cannot partially update the instance.

---
 fs/proc/root.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)
fs/proc/root.c +11 -4
index 89e5678129e4..1bf75a4ee146 100644
@@ -223,12 +223,17 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
 	struct proc_fs_context *ctx = fc->fs_private;
 
+	if ((ctx->mask & (1 << Opt_subset)) &&
+	    fc->purpose == FS_CONTEXT_FOR_RECONFIGURE &&
+	    ctx->pidonly != fs_info->pidonly)
+		return invalf(fc, "proc: subset=pid cannot be changed\n");
+
 	if (ctx->mask & (1 << Opt_gid))
 		fs_info->pid_gid = make_kgid(user_ns, ctx->gid);
 	if (ctx->mask & (1 << Opt_hidepid))
@@ -240,6 +245,7 @@ static void proc_apply_options(struct proc_fs_info *fs_info,
 		put_pid_ns(fs_info->pid_ns);
 		fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	}
+	return 0;
 }
 
 static int proc_fill_super(struct super_block *s, struct fs_context *fc)
@@ -255,7 +261,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 
 	fs_info->pid_ns = get_pid_ns(ctx->pid_ns);
 	fs_info->mounter_cred = get_cred(fc->cred);
-	proc_apply_options(fs_info, fc, current_user_ns());
+	ret = proc_apply_options(fs_info, fc, current_user_ns());
+	if (ret)
+		return ret;
 
 	/* User space would break if executables or devices appear on proc */
 	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
@@ -304,8 +312,7 @@ static int proc_reconfigure(struct fs_context *fc)
 
 	sync_filesystem(sb);
 
-	proc_apply_options(fs_info, fc, current_user_ns());
-	return 0;
+	return proc_apply_options(fs_info, fc, current_user_ns());
 }
 
 static int proc_get_tree(struct fs_context *fc)
Signature · 1 line
2.54.0
#56 lore raw

Re: [PATCH v10 5/7] proc: prevent reconfiguring subset=pid

Quoted 23 lines · Alexey Gladkov

Changing subset=pid on an existing procfs instance is not safe. If a full procfs mount has entries hidden by overmounts, switching it to subset=pid would hide the top-level procfs entries from lookup and readdir while leaving the existing overmounts reachable.

Reject attempts to change the subset=pid state during reconfigure before applying any other procfs mount options, so a failed reconfigure cannot partially update the instance.

---
 fs/proc/root.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)
fs/proc/root.c +0 -1
index 89e5678129e4..1bf75a4ee146 100644
@@ -223,12 +223,17 @@ static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
 	return 0;
 }
 
-static void proc_apply_options(struct proc_fs_info *fs_info,

fs/proc/root.c static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param) · line 235

Quoted 10 lines
fs/proc/root.c +5 -0
+static int proc_apply_options(struct proc_fs_info *fs_info,
 			       struct fs_context *fc,
 			       struct user_namespace *user_ns)
 {
 	struct proc_fs_context *ctx = fc->fs_private;
 
+	if ((ctx->mask & (1 << Opt_subset)) &&
+	    fc->purpose == FS_CONTEXT_FOR_RECONFIGURE &&
+	    ctx->pidonly != fs_info->pidonly)
+		return invalf(fc, "proc: subset=pid cannot be changed\n");

Minor nit: Unless I'm missing something, you can just use invalfc here to auto-add the "proc" prefix (which is what most things do). Also, the newline is unnecessary and will look odd in mount(8) as nobody else adds newlines (the fs_context log is not newline-separated).

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/
#57 lore raw

PATCH v10 6/7 proc: handle subset=pid separately in userns visibility checks

In reply to Alexey Gladkov

When procfs is mounted with subset=pid, only the dynamic process-related part of the filesystem remains visible. That part cannot be hidden by overmounts, so checking whether an existing procfs mount is fully visible does not make sense for this mode.

At the same time, a subset=pid procfs mount must not be used as evidence that a later procfs mount would not reveal additional information. It provides a restricted view of procfs, not the full filesystem view.

Mark subset=pid procfs instances as restricted variants. Ignore restricted variants when looking for an already-visible mount, and allow new restricted variants without consulting mnt_already_visible().

---
 fs/namespace.c                 | 17 ++++++++++++++++-
 fs/proc/root.c                 |  3 +++
 include/linux/fs/super_types.h |  1 +
 3 files changed, 20 insertions(+), 1 deletion(-)
fs/namespace.c +16 -1
index ed13416370e3..389d8654f36f 100644
@@ -6323,10 +6323,18 @@ static bool mnt_already_visible(struct mnt_namespace *ns,
 
 	guard(namespace_shared)();
 	hlist_for_each_entry(mnt, &ns->mnt_visible_mounts, mnt_ns_visible) {
+		const struct super_block *sb_visible = mnt->mnt.mnt_sb;
 		struct mount *child;
 		int mnt_flags;
 
-		if (mnt->mnt.mnt_sb->s_type != sb->s_type)
+		if (sb_visible->s_type != sb->s_type)
+			continue;
+
+		/*
+		 * Restricted variants are not compatible with anything, even
+		 * other restricted variants.
+		 */
+		if (sb_visible->s_iflags & SB_I_RESTRICTED_VARIANT)
 			continue;
 
 		/* A local view of the mount flags */
@@ -6388,6 +6396,13 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flags
 		return true;
 	}
 
+	/*
+	 * Restricted variants don't need an already visible mount because they
+	 * don't expose the full filesystem view.
+	 */
+	if (s_iflags & SB_I_RESTRICTED_VARIANT)
+		return false;
+
 	return !mnt_already_visible(ns, sb, new_mnt_flags);
 }
 
fs/proc/root.c +3 -0
index 1bf75a4ee146..99adddfeb4a4 100644
@@ -275,6 +275,9 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	s->s_time_gran = 1;
 	s->s_fs_info = fs_info;
 
+	if (fs_info->pidonly == PROC_PIDONLY_ON)
+		s->s_iflags |= SB_I_RESTRICTED_VARIANT;
+
 	/*
 	 * procfs isn't actually a stacking filesystem; however, there is
 	 * too much magic going on inside it to permit stacking things on
include/linux/fs/super_types.h +1 -0
index 182efbeb9520..a6cdc8f6de4e 100644
@@ -326,6 +326,7 @@ struct super_block {
 #define SB_I_STABLE_WRITES 0x00000008	/* don't modify blks until WB is done */
 
 /* sb->s_iflags to limit user namespace mounts */
+#define SB_I_RESTRICTED_VARIANT		0x00000010
 #define SB_I_IMA_UNVERIFIABLE_SIGNATURE	0x00000020
 #define SB_I_UNTRUSTED_MOUNTER		0x00000040
 #define SB_I_EVM_HMAC_UNSUPPORTED	0x00000080
Signature · 1 line
2.54.0
#58 lore raw

PATCH v10 7/7 docs: proc: add documentation about mount restrictions

In reply to Alexey Gladkov

procfs has a number of mounting restrictions that are not documented anywhere.

---
 Documentation/filesystems/proc.rst | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
Documentation/filesystems/proc.rst +18 -1
index b0c0d1b45b99..b836b725b35d 100644
@@ -52,6 +52,7 @@ fixes/update part 1.1  Stefani Seibold <[email protected]>    June 9 2009
 
   4	Configuring procfs
   4.1	Mount options
+  4.2	Mount restrictions
 
   5	Filesystem behavior
 
@@ -2401,7 +2402,9 @@ prohibited by hidepid=.  If you use some daemon like identd which needs to learn
 information about processes information, just add identd to this group.
 
 subset=pid hides all top level files and directories in the procfs that
-are not related to tasks.
+are not related to tasks. This option cannot be changed on an existing
+procfs instance because overmounts that existed before the change could
+otherwise remain reachable after the top level procfs entries are hidden.
 
 pidns= specifies a pid namespace (either as a string path to something like
 `/proc/$pid/ns/pid`, or a file descriptor when using `FSCONFIG_SET_FD`) that
@@ -2410,6 +2413,20 @@ will use the calling process's active pid namespace. Note that the pid
 namespace of an existing procfs instance cannot be modified (attempting to do
 so will give an `-EBUSY` error).
 
+4.2	Mount restrictions
+--------------------------
+
+If user namespaces are in use, the kernel additionally checks the instances of
+procfs available to the mounter and will not allow procfs to be mounted if:
+
+  1. This mount is not fully visible unless the new procfs is going to be
+     mounted with subset=pid option.
+
+     a. Its root directory is not the root directory of the filesystem.
+     b. If any file or non-empty procfs directory is hidden by another mount.
+
+  2. A new mount overrides the readonly option or any option from atime family.
+
 Chapter 5: Filesystem behavior
 ==============================
 
Signature · 1 line
2.54.0
maintainer Applied
#59 lore raw
In reply to Alexey Gladkov
Quoted 8 lines · Alexey Gladkov

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

[...]

Thanks, I think we ended with something that looks quite decent now.

---

Applied to the vfs-7.2.procfs branch of the vfs/vfs.git tree. Patches in the vfs-7.2.procfs branch should appear in linux-next soon.

Please report any outstanding bugs that were missed during review in a new review to the original patch series allowing us to drop it.

It's encouraged to provide Acked-bys and Reviewed-bys even though the patch has now been applied. If possible patch trailers will be updated.

Note that commit hashes shown below are subject to change due to rebase, trailer updates or similar. If in doubt, please check the listed branch.

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.2.procfs

[1/7] namespace: record fully visible mounts in list

      https://git.kernel.org/vfs/vfs/c/18920cc2ade4
[2/7] fs: move SB_I_USERNS_VISIBLE to FS_USERNS_MOUNT_RESTRICTED
      https://git.kernel.org/vfs/vfs/c/a09358516cf2
[3/7] sysfs: remove trivial sysfs_get_tree() wrapper
      https://git.kernel.org/vfs/vfs/c/630dc69a9f7d
[4/7] proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
      https://git.kernel.org/vfs/vfs/c/0ff06ac76088
[5/7] proc: prevent reconfiguring subset=pid
      https://git.kernel.org/vfs/vfs/c/87341f4e3436
[6/7] proc: handle subset=pid separately in userns visibility checks
      https://git.kernel.org/vfs/vfs/c/6691ea02bddb
[7/7] docs: proc: add documentation about mount restrictions
      https://git.kernel.org/vfs/vfs/c/65cb11ddcfcd
Reviewed-by on the v10 series
#60 lore raw
In reply to Alexey Gladkov
Quoted 11 lines · Alexey Gladkov

When mounting procfs with the subset=pids option, all static files become unavailable and only the dynamic part with information about pids is accessible.

In this case, there is no point in imposing additional restrictions on the visibility of the entire filesystem for the mounter. Everything that can be hidden in procfs is already inaccessible.

Currently, these restrictions prevent pidfs from being mounted inside rootless containers, as almost all container implementations override part of procfs to hide certain directories. Relaxing these restrictions will allow pidfs to be used in nested containerization.

Aside from one minor nit about invalf, looks great! Feel free to take my

Signature · 2 lines
Aleksa Sarai
https://www.cyphar.com/