[PATCH 7/7] selftests/proc: test which entries can't be mountpoints

Christian Brauner brauner at kernel.org
Sat Oct 24 03:38:33 PDT 2026


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 at 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




More information about the linux-afs mailing list