proc: subset=pid: Relax check of mount visibility
Subsystems (4)
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
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
@@ -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
PATCH RESEND v6 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovCache 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
@@ -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
@@ -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
@@ -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
PATCH RESEND v6 3/5 proc: Disable cancellation of subset=pid option
In reply to Alexey GladkovWhen 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
@@ -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
PATCH RESEND v6 4/5 proc: Relax check of mount visibility
In reply to Alexey GladkovAllow 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
@@ -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
@@ -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
@@ -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
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
@@ -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
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
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
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
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
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_BPFin 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
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_BPFin 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
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_BPFin 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
Christian Brauner
maintainerQuoted 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_BPFin 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.
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
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
Christian Brauner
maintainerQuoted 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. :)
v7 cover proc: subset=pid: Relax check of mount visibility
In reply to Dan KlishchWhen 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
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
@@ -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
PATCH v7 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovCache 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
@@ -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
@@ -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
@@ -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
PATCH v7 3/5 proc: Disable cancellation of subset=pid option
In reply to Alexey GladkovWhen 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
@@ -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
PATCH v7 4/5 proc: Relax check of mount visibility
In reply to Alexey GladkovWhen /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
@@ -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
@@ -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
@@ -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
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
@@ -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
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
Christian Brauner
maintainerRe: [PATCH v7 2/5] proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovQuoted 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/netwhen proc is mounted with subset=pid option and the mounter does not haveCAP_NET_ADMIN.Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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@@ -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
@@ -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
@@ -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
@@ -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)
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/netwhen proc is mounted with subset=pid option and the mounter does not haveCAP_NET_ADMIN.Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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@@ -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 thatget_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_credsstays 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@@ -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@@ -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@@ -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
v8 cover proc: subset=pid: Relax check of mount visibility
In reply to Alexey GladkovWhen 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
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
@@ -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
PATCH v8 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovCache 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
@@ -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
@@ -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
@@ -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
PATCH v8 3/5 proc: Disable cancellation of subset=pid option
In reply to Alexey GladkovWhen 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
@@ -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
PATCH v8 4/5 proc: Relax check of mount visibility
In reply to Alexey GladkovWhen /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
@@ -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
@@ -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
@@ -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
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
@@ -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
Christian Brauner
maintainerRe: [PATCH v8 4/5] proc: Relax check of mount visibility
In reply to Alexey GladkovQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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
@@ -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
@@ -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
Christian Brauner
maintainerRe: [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.
Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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/pressuresomewhere that got inherited viaCLONE_NEWNSthen 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_NEWNSdance 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_REVEALINGseems unnecessary. The only time we care about that flag is when we setup a new superblock. So this could easily be a structfs_contextbitfield that just exists for the duration of the creation of the new superblock and mount. So maybe pass that down tomount_too_revealing()and further down into the actual helper.[1]:
Quoted 1 line
From
4bbd41e7a3ef91667dd334f31b1b6bf8caec0599Mon 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 listInstead 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...
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.
Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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/pressuresomewhere that got inherited viaCLONE_NEWNSthen 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_NEWNSdance 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_REVEALINGseems unnecessary. The only time we care about that flag is when we setup a new superblock. So this could easily be a structfs_contextbitfield that just exists for the duration of the creation of the new superblock and mount. So maybe pass that down tomount_too_revealing()and further down into the actual helper.[1]:
Quoted 1 line
From
4bbd41e7a3ef91667dd334f31b1b6bf8caec0599Mon Sep 17 00:00:00 2001From: 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
v9 cover proc: subset=pid: Relax check of mount visibility
In reply to Alexey GladkovWhen 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
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
@@ -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
@@ -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
PATCH v9 2/5 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovCache 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
@@ -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
@@ -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
@@ -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
PATCH v9 3/5 proc: Disable cancellation of subset=pid option
In reply to Alexey GladkovWhen 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
@@ -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
PATCH v9 4/5 proc: Skip the visibility check if subset=pid is used
In reply to Alexey GladkovWhen 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
@@ -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
@@ -3755,7 +3755,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mpreturn 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_flagsstatic 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_flagsreturn 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
@@ -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
@@ -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
PATCH v9 5/5 docs: proc: add documentation about mount restrictions
In reply to Alexey Gladkovprocfs 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
@@ -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
Christian Brauner
maintainerRe: [PATCH v8 4/5] proc: Relax check of mount visibility
In reply to Alexey GladkovQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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/pressuresomewhere that got inherited viaCLONE_NEWNSthen 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_NEWNSdance 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_REVEALINGseems unnecessary. The only time we care about that flag is when we setup a new superblock. So this could easily be a structfs_contextbitfield that just exists for the duration of the creation of the new superblock and mount. So maybe pass that down tomount_too_revealing()and further down into the actual helper.[1]:
Quoted 1 line
From
4bbd41e7a3ef91667dd334f31b1b6bf8caec0599Mon Sep 17 00:00:00 2001From: Christian Brauner <[email protected]>
Date: Tue, 17 Feb 2026 12:02:34 +0100
Subject: [PATCH] namespace: record fully visible mounts in listInstead 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.
Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used
In reply to Alexey GladkovQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>---
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_flagsreturn 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/
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.
Signed-off-by Alexey Gladkov <[email protected]>---
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_flagsreturn 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_visibilityset inmnt_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 bypassingmount_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/
Christian Brauner
maintainerRe: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used
In reply to Alexey GladkovQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>--- 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@@ -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@@ -3755,7 +3755,7 @@ static int do_add_mount(struct mount *newmnt, const struct pinned_mountpoint *mpreturn 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_flagsstatic 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_flagsreturn 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@@ -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.
Christian Brauner
maintainerRe: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used
In reply to Aleksa SaraiQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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
@@ -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
@@ -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
@@ -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
@@ -6405,10 +6405,10 @@ static bool mount_too_revealing(const struct super_block *sb, int *new_mnt_flagsreturn 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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_REVEALINGto anfs_typeflag. It's not an optional thing and always set and never removed. That also means we can simplifysysfs_get_tree()to justkernfs_get_tree().
Seems quite reasonable to me.
Quoted 3 lines
And then we raise
SB_I_USERNS_RESTRICTEDon all procfs mounts withpid_onlyand 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/
Christian Brauner
maintainerRe: [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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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_REVEALINGto anfs_typeflag. It's not an optional thing and always set and never removed. That also means we can simplifysysfs_get_tree()to justkernfs_get_tree().Seems quite reasonable to me.
Quoted 3 lines
And then we raise
SB_I_USERNS_RESTRICTEDon all procfs mounts withpid_onlyand 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_contextflags instead offs_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_typeswould 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_VARIANTthat would be more a little more strict than what you have now --SB_I_RESTRICTED_VARIANTwould not be treated as compatible with anything (even anotherSB_I_RESTRICTED_VARIANTmount) 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 inmount_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_RESTRICTEDis 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.
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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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_REVEALINGto anfs_typeflag. It's not an optional thing and always set and never removed. That also means we can simplifysysfs_get_tree()to justkernfs_get_tree().Seems quite reasonable to me.
Quoted 3 lines
And then we raise
SB_I_USERNS_RESTRICTEDon all procfs mounts withpid_onlyand 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_contextflags instead offs_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_typeswould 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_VARIANTthat would be more a little more strict than what you have now --SB_I_RESTRICTED_VARIANTwould not be treated as compatible with anything (even anotherSB_I_RESTRICTED_VARIANTmount) 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 inmount_too_revealing().It's not a problem today. I think we can simply treat
SB_I_RESTRICTED_VARIANTas 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_RESTRICTEDis at risk of breaking something) so maybe that isn't needed today.So I think yes,
SB_I_RESTRICTED_VARIANTis fine by me for now.
Should I create a new version and add this to my patchset?
Signature · 1 line
Rgrds, legion
Christian Brauner
maintainerRe: [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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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_REVEALINGto anfs_typeflag. It's not an optional thing and always set and never removed. That also means we can simplifysysfs_get_tree()to justkernfs_get_tree().Seems quite reasonable to me.
Quoted 3 lines
And then we raise
SB_I_USERNS_RESTRICTEDon all procfs mounts withpid_onlyand 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_contextflags instead offs_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_typeswould 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_VARIANTthat would be more a little more strict than what you have now --SB_I_RESTRICTED_VARIANTwould not be treated as compatible with anything (even anotherSB_I_RESTRICTED_VARIANTmount) 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 inmount_too_revealing().It's not a problem today. I think we can simply treat
SB_I_RESTRICTED_VARIANTas 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_RESTRICTEDis at risk of breaking something) so maybe that isn't needed today.So I think yes,
SB_I_RESTRICTED_VARIANTis fine by me for now.Should I create a new version and add this to my patchset?
Yes, please!
Re: [PATCH v9 4/5] proc: Skip the visibility check if subset=pid is used
In reply to Christian BraunerQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>---
(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_visibilityset inmnt_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_REVEALINGto anfs_typeflag. It's not an optional thing and always set and never removed. That also means we can simplifysysfs_get_tree()to justkernfs_get_tree().Seems quite reasonable to me.
Quoted 3 lines
And then we raise
SB_I_USERNS_RESTRICTEDon all procfs mounts withpid_onlyand 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_contextflags instead offs_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_typeswould 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_VARIANTthat would be more a little more strict than what you have now --SB_I_RESTRICTED_VARIANTwould not be treated as compatible with anything (even anotherSB_I_RESTRICTED_VARIANTmount) 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 inmount_too_revealing().It's not a problem today. I think we can simply treat
SB_I_RESTRICTED_VARIANTas 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_RESTRICTEDis at risk of breaking something) so maybe that isn't needed today.So I think yes,
SB_I_RESTRICTED_VARIANTis fine by me for now.
Sounds good.
Signature · 2 lines
Aleksa Sarai https://www.cyphar.com/
v10 cover proc: subset=pid: Relax check of mount visibility
In reply to Alexey GladkovWhen 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
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
@@ -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
@@ -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
PATCH v10 2/7 fs: move SB_I_USERNS_VISIBLE to FS_USERNS_MOUNT_RESTRICTED
In reply to Alexey GladkovFrom: 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
@@ -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_flagsreturn 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v10 3/7 sysfs: remove trivial sysfs_get_tree() wrapper
In reply to Alexey GladkovFrom: 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
@@ -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
PATCH v10 4/7 proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
In reply to Alexey GladkovCache 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
@@ -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
@@ -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
@@ -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
PATCH v10 5/7 proc: prevent reconfiguring subset=pid
In reply to Alexey GladkovChanging 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
@@ -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
PATCH v10 6/7 proc: handle subset=pid separately in userns visibility checks
In reply to Alexey GladkovWhen 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
@@ -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_flagsreturn 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
@@ -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
@@ -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
PATCH v10 7/7 docs: proc: add documentation about mount restrictions
In reply to Alexey Gladkovprocfs 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
@@ -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
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
Re: [PATCH v10 5/7] proc: prevent reconfiguring subset=pid
In reply to Alexey GladkovQuoted 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.
Signed-off-by Alexey Gladkov <[email protected]>--- fs/proc/root.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)fs/proc/root.c +0
-1@@ -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/
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/