[PATCH 0/7] proc: block mounts below /proc/<pid>/net and on the links into /proc/self
Jan Kara
jack at suse.cz
Mon Oct 26 06:00:58 PDT 2026
On Sat 24-10-26 12:38:26, Christian Brauner wrote:
> In Linux 6.12 I stopped allowing mounts on the entries below
> /proc/<pid>/fd/, /proc/<pid>/fdinfo/ and /proc/<pid>/map_files/ (cf.
> [1]). This extends that to two more groups of entries:
>
> (1) Everything below /proc/<pid>/net/.
>
> proc_net_d_revalidate() fails on every lookup since 1fde6f21d90f
> ("proc: fix /proc/net/* after setns(2)"). lookup_fast() answers that
> with d_invalidate() which detaches every mount on the dentry. That
> affects every mount namespace and includes locked mounts.
>
> So a mask on /proc/<pid>/net/dev goes away the first time anybody
> looks the name up. The directory is 0555 so anybody really means
> anybody. This is reproducible with a container that masks
> /proc/<pid>/net/dev with a locked bind mount and a stat() by nobody
> in the initial namespaces makes the real file show up again. So it's
> pointless.
>
> A mount that can't stick is better refused than taken away. I also
> have a version that makes proc_net_d_revalidate() check the network
> namespace instead so mounts there stick. But I think that's
> bullsh*t. It would make mounts on the net entries of other processes
> stick as well and the few users that try to mount there fail today
> anyway (see below).
>
> (2) The links into /proc/self: /proc/self, /proc/thread-self,
> /proc/mounts, /proc/net and the afs and nfs links below /proc/fs/.
>
> These resolve to a different directory for every process. A mount
> on the link itself is crossed by every lookup through it in that
> mount namespace, so it redirects all processes at once. Mounting
> through the links keeps working and lands on the caller's own entry
> as before.
>
> What I really wanted was to block mounting on anything below
> /proc/<pid>/. That was the big hammer I mentioned in [1] years ago. We
> can't... I did a pretty exhaustive survey of who mounts on the entries
> below /proc/<pid>/ or on the links into it:
>
> - GitHub code search and regex searches on Sourcegraph
> - Debian Code Search over unstable
> - the systemd units of every binary package in Debian 11, 12, 13 and
> sid, Ubuntu 22.04, 24.04 and 26.04, Fedora 43 and 44, CentOS Stream
> 10, openSUSE Leap 16 and Tumbleweed and Arch
> - clones of 46 container runtimes and orchestrators, the sandboxes
> (systemd, bubblewrap, flatpak, firejail, snapd, nsjail, minijail,
> Chromium and Firefox), Android and the rooting tools on top of it,
> and the AppArmor and SELinux policies
> - detection rules, issue trackers, lore, LWN and the Q&A sites
>
> The legitimate users all mount on entries of the process that does the
> mount:
>
> (1) systemd units that list /proc/self/... in ReadOnlyPaths=,
> ReadWritePaths= or ReadWriteDirectories=. systemd resolves
> /proc/self in the child of the service and binds the entry onto
> itself before it execs the service. If the mount fails the unit
> fails with 226/NAMESPACE and the "-" prefix doesn't help. That's
> netdata in Debian 11 and 12 and Ubuntu 22.04 and 24.04, opendht in
> Debian 12 and sid, Ubuntu 24.04 and Arch, accountsservice 22.x in
> Debian 12 and Ubuntu 22.04, OpenSearch 3, wazuh-indexer, TrueNAS
> SCALE, OONI's backend and the JupyterHub systemdspawner example. Of
> the 731 unit files on GitHub that name a /proc path in one of these
> directives 53 name an entry of the service itself.
>
> (2) systemd's make_filesystem() binds /dev/null over /proc/self/mounts
> before it execs mkfs.btrfs so that it doesn't refuse a device whose
> other partitions are mounted. systemd-repart, systemd-homed and
> systemd-makefs use that.
>
> (3) mkosi binds a fake /proc/self/mountinfo for grub-bios-setup.
>
> (4) runc applies maskedPaths such as /proc/1/environ from the
> container's init when a config asks for it, bwrap does the same
> from its pid 1, firejail hides the name of its pid 1 and clampdown
> masks /proc/1/mem.
>
> (5) Shell scripts that mount on /proc/$$/... and then exec, e.g.,
> luci-app-shadowrt on OpenWrt.
>
> (6) Tests in systemd, elogind, LXC, libpathrs and bun.
>
> Fedora's SELinux policy even grants fsadm_t and init_t the permission to
> mount over their own entries because of (1) and (2). Sad, very sad...
>
> Everything else I found is malware or offensive tooling:
>
> Hiding a process by mounting over /proc/<pid> is catalogued as MITRE
> ATT&CK T1564.013 and used in the wild by TeamTNT, Commando Cat, the KONO
> DIO DA miner, KV-botnet, UNC2891 and kbotne. hackshell and emp3r0r ship
> it and Elastic's endpoint rule kills any process that does it.
>
> So blocking all of /proc/<pid>/ would break units in released
> distributions and systemd itself. A rule that only lets a process
> mount on its own entries, and on a directory only a bind of itself,
> keeps (1) to (4) working, except for a few bwrap sandboxes that put a
> tmpfs over directories like /proc/1/task, and stops all of the attacks.
> I have that as well but it's not really a fix so we can delay it.
>
> The remaining entries of a process only fail pid_revalidate() once the
> task is gone and proc_flush_pid() invalidates them when it is reaped.
> Their mounts go away together with the task and nothing gets revealed.
>
> Nothing I found mounts on the links themselves and crun even has a
> test that expects it to fail. The handful of units that mask entries
> below /proc/<pid>/net/, e.g., Algo VPN's ReadOnlyPaths=/proc/net/pfkey
> for strongswan, already fail today.
>
> systemd's own lookup invalidates the dentry before it can bind onto it.
> With this series they fail the same way. Apart from tests in libpathrs
> and securejoin that had to be adapted the only fallout from the 6.12
> change we found is one small project that handed libmount's new mount
> api a /proc/self/fd/N target that move_mount() doesn't follow without
> MOVE_MOUNT_T_SYMLINKS. Ubuntu even backported the 6.12 change to their
> 22.04 and 24.04 kernels this year.
>
> Nuke this crap.
>
> [1]: https://lore.kernel.org/r/20240806-work-procfs-v1-0-fb04e1d09f0c@kernel.org
>
> Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
Makes sense to me although I don't dare to judge all possible consequences
(I don't have much practical experience with container runtimes that play
these games). Anyway, feel free to add:
Acked-by: Jan Kara <jack at suse.cz>
Honza
> ---
> Christian Brauner (7):
> proc: don't let an entry below /proc/<pid>/net be a mountpoint
> proc: don't let /proc/self and /proc/thread-self be mountpoints
> proc: don't let /proc/mounts and /proc/net be mountpoints
> afs: create /proc/fs/afs with proc_self_symlink()
> nfs: create the /proc/fs/nfsfs links with proc_self_symlink()
> proc: explain which entries can't be mountpoints
> selftests/proc: test which entries can't be mountpoints
>
> fs/afs/main.c | 2 +-
> fs/nfs/client.c | 4 +-
> fs/proc/generic.c | 31 ++++-
> fs/proc/internal.h | 18 ++-
> fs/proc/proc_net.c | 2 +-
> fs/proc/root.c | 2 +-
> fs/proc/self.c | 2 +
> fs/proc/thread_self.c | 2 +
> include/linux/proc_fs.h | 5 +
> tools/testing/selftests/proc/.gitignore | 1 +
> tools/testing/selftests/proc/Makefile | 1 +
> tools/testing/selftests/proc/proc-unmountable.c | 150 ++++++++++++++++++++++++
> 12 files changed, 208 insertions(+), 12 deletions(-)
> ---
> base-commit: 7be4bc52fd9e0e55dc9a2e0232b89427d2c4b614
> change-id: 20261024-work-procfs-fixes-8b21a6d17448
>
--
Jan Kara <jack at suse.com>
SUSE Labs, CR
More information about the linux-afs
mailing list