All of lore.kernel.org
 help / color / mirror / Atom feed
From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: linux-afs@lists.infradead.org, linux-nfs@vger.kernel.org,
	 Alexander Viro <viro@zeniv.linux.org.uk>,
	Jan Kara <jack@suse.cz>,  Aleksa Sarai <cyphar@cyphar.com>,
	Chris Mason <mason@kernel.org>,
	 "Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 7/7] selftests/proc: test which entries can't be mountpoints
Date: Sat, 24 Oct 2026 12:38:33 +0200	[thread overview]
Message-ID: <20261024-work-procfs-fixes-v1-7-46b03c3a8fac@kernel.org> (raw)
In-Reply-To: <20261024-work-procfs-fixes-v1-0-46b03c3a8fac@kernel.org>

Check that nothing can be mounted on:

- /proc/self, /proc/thread-self, /proc/mounts and /proc/net themselves
- the entries below /proc/<pid>/net
- the entries below /proc/<pid>/fd and /proc/<pid>/fdinfo

Mounting through /proc/mounts still works and lands on the entry of the
caller.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
 tools/testing/selftests/proc/.gitignore         |   1 +
 tools/testing/selftests/proc/Makefile           |   1 +
 tools/testing/selftests/proc/proc-unmountable.c | 150 ++++++++++++++++++++++++
 3 files changed, 152 insertions(+)

diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 9c9735570abf..1dd43799ec45 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -20,6 +20,7 @@
 /proc-uptime-001
 /proc-uptime-002
 /proc-pidns
+/proc-unmountable
 /read
 /self
 /setns-dcache
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index a7de2bb6d8be..49afe08daebe 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -30,5 +30,6 @@ TEST_GEN_PROGS += thread-self
 TEST_GEN_PROGS += proc-multiple-procfs
 TEST_GEN_PROGS += proc-fsconfig-hidepid
 TEST_GEN_PROGS += proc-pidns
+TEST_GEN_PROGS += proc-unmountable
 
 include ../lib.mk
diff --git a/tools/testing/selftests/proc/proc-unmountable.c b/tools/testing/selftests/proc/proc-unmountable.c
new file mode 100644
index 000000000000..a1282927cfff
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-unmountable.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/* The entries in /proc that can't be mountpoints. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdio.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include "kselftest_harness.h"
+
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE 1
+#endif
+
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
+#endif
+
+#ifndef MOVE_MOUNT_T_EMPTY_PATH
+#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040
+#endif
+
+#ifndef __NR_open_tree
+#define __NR_open_tree 428
+#endif
+
+#ifndef __NR_move_mount
+#define __NR_move_mount 429
+#endif
+
+static int sys_open_tree(int dfd, const char *path, unsigned int flags)
+{
+	return syscall(__NR_open_tree, dfd, path, flags);
+}
+
+static int sys_move_mount(int from_dfd, const char *from_path, int to_dfd,
+			  const char *to_path, unsigned int flags)
+{
+	return syscall(__NR_move_mount, from_dfd, from_path, to_dfd, to_path,
+		       flags);
+}
+
+/* Mount a bind of /dev/null right on the object @fd refers to. */
+static int move_mount_null(int fd)
+{
+	int fd_tree, ret;
+
+	fd_tree = sys_open_tree(AT_FDCWD, "/dev/null", OPEN_TREE_CLONE);
+	if (fd_tree < 0)
+		return -1;
+	ret = sys_move_mount(fd_tree, "", fd, "",
+			     MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
+	close(fd_tree);
+	return ret;
+}
+
+FIXTURE(unmountable) {
+	char path[PATH_MAX];
+};
+
+FIXTURE_SETUP(unmountable)
+{
+	if (geteuid())
+		SKIP(return, "mounting needs root");
+
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL), 0);
+}
+
+FIXTURE_TEARDOWN(unmountable)
+{
+}
+
+/* Nothing may be mounted on a link into /proc/self itself. */
+TEST_F(unmountable, self_links)
+{
+	static const char *const links[] = {
+		"/proc/self", "/proc/thread-self", "/proc/mounts", "/proc/net",
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(links); i++) {
+		int fd;
+
+		fd = open(links[i], O_PATH | O_NOFOLLOW | O_CLOEXEC);
+		/* /proc/net needs CONFIG_NET */
+		if (fd < 0 && errno == ENOENT)
+			continue;
+		ASSERT_GE(fd, 0);
+		EXPECT_EQ(move_mount_null(fd), -1) {
+			TH_LOG("mounted on %s", links[i]);
+		}
+		EXPECT_EQ(errno, ENOENT);
+		close(fd);
+	}
+}
+
+/* Mounting through a link into /proc/self lands on the entry of the caller. */
+TEST_F(unmountable, through_links)
+{
+	struct stat st;
+
+	ASSERT_EQ(mount("/dev/null", "/proc/mounts", NULL, MS_BIND, NULL), 0);
+	/* the caller's /proc/<pid>/mounts is covered */
+	ASSERT_EQ(stat("/proc/self/mounts", &st), 0);
+	EXPECT_TRUE(S_ISCHR(st.st_mode));
+	/* the link itself is not */
+	ASSERT_EQ(lstat("/proc/mounts", &st), 0);
+	EXPECT_TRUE(S_ISLNK(st.st_mode));
+}
+
+/* The entries below /proc/<pid>/net take no mount. */
+TEST_F(unmountable, net_entries)
+{
+	if (access("/proc/self/net/dev", F_OK))
+		SKIP(return, "needs CONFIG_NET");
+
+	ASSERT_EQ(mount("/dev/null", "/proc/self/net/dev", NULL, MS_BIND, NULL), -1);
+	ASSERT_EQ(errno, ENOENT);
+}
+
+/* Neither do the entries below /proc/<pid>/fd and /proc/<pid>/fdinfo. */
+TEST_F(unmountable, fd_entries)
+{
+	int fd, fd_link;
+
+	fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
+	ASSERT_GE(fd, 0);
+
+	snprintf(self->path, sizeof(self->path), "/proc/self/fd/%d", fd);
+	fd_link = open(self->path, O_PATH | O_NOFOLLOW | O_CLOEXEC);
+	ASSERT_GE(fd_link, 0);
+	EXPECT_EQ(move_mount_null(fd_link), -1);
+	EXPECT_EQ(errno, ENOENT);
+	close(fd_link);
+
+	snprintf(self->path, sizeof(self->path), "/proc/self/fdinfo/%d", fd);
+	EXPECT_EQ(mount("/dev/null", self->path, NULL, MS_BIND, NULL), -1);
+	EXPECT_EQ(errno, ENOENT);
+	close(fd);
+}
+
+TEST_HARNESS_MAIN

-- 
2.53.0


  parent reply	other threads:[~2026-10-24 10:38 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24 10:38 [PATCH 0/7] proc: block mounts below /proc/<pid>/net and on the links into /proc/self Christian Brauner
2026-10-24 10:38 ` [PATCH 1/7] proc: don't let an entry below /proc/<pid>/net be a mountpoint Christian Brauner
2026-10-24 10:38 ` [PATCH 2/7] proc: don't let /proc/self and /proc/thread-self be mountpoints Christian Brauner
2026-10-24 10:38 ` [PATCH 3/7] proc: don't let /proc/mounts and /proc/net " Christian Brauner
2026-10-24 10:38 ` [PATCH 4/7] afs: create /proc/fs/afs with proc_self_symlink() Christian Brauner
2026-10-24 10:38 ` [PATCH 5/7] nfs: create the /proc/fs/nfsfs links " Christian Brauner
2026-10-26 17:58   ` Anna Schumaker
2026-10-24 10:38 ` [PATCH 6/7] proc: explain which entries can't be mountpoints Christian Brauner
2026-10-24 10:38 ` Christian Brauner [this message]
2026-10-26 13:00 ` [PATCH 0/7] proc: block mounts below /proc/<pid>/net and on the links into /proc/self Jan Kara
2026-10-27  5:34 ` Aleksa Sarai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261024-work-procfs-fixes-v1-7-46b03c3a8fac@kernel.org \
    --to=brauner@kernel.org \
    --cc=cyphar@cyphar.com \
    --cc=jack@suse.cz \
    --cc=linux-afs@lists.infradead.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=mason@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.