[LTP] [PATCH STAGING v2 09/16] fchroot06: test path walks under failfs root

Andrea Cervesato <[email protected]>
Newsgroups it.linux.lists.ltp
Message-ID <[email protected]>
From: Andrea Cervesato <[email protected]>

Verify that once the root is in failfs only lookups anchored at a
file descriptor keep working: relative lookups from the working
directory and from a pre-opened directory fd succeed, absolute
symlinks fail with EOPNOTSUPP and ".." walks clamp at the top of the
mount tree, landing on the real root.

Signed-off-by: Andrea Cervesato <[email protected]>
---
 runtest/staging                               |   1 +
 testcases/kernel/syscalls/fchroot/.gitignore  |   1 +
 testcases/kernel/syscalls/fchroot/fchroot06.c | 117 ++++++++++++++++++++++++++
 3 files changed, 119 insertions(+)

diff --git a/runtest/staging b/runtest/staging
index 49f51d637..8b6b1ecd9 100644
--- a/runtest/staging
+++ b/runtest/staging
@@ -5,3 +5,4 @@ fchroot02 fchroot02
 fchroot03 fchroot03
 fchroot04 fchroot04
 fchroot05 fchroot05
+fchroot06 fchroot06
diff --git a/testcases/kernel/syscalls/fchroot/.gitignore b/testcases/kernel/syscalls/fchroot/.gitignore
index 0697f10eb..12151270a 100644
--- a/testcases/kernel/syscalls/fchroot/.gitignore
+++ b/testcases/kernel/syscalls/fchroot/.gitignore
@@ -3,3 +3,4 @@ fchroot02
 fchroot03
 fchroot04
 fchroot05
+fchroot06
diff --git a/testcases/kernel/syscalls/fchroot/fchroot06.c b/testcases/kernel/syscalls/fchroot/fchroot06.c
new file mode 100644
index 000000000..bf6c4fea4
--- /dev/null
+++ b/testcases/kernel/syscalls/fchroot/fchroot06.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 SUSE LLC Andrea Cervesato <[email protected]>
+ */
+
+/*\
+ * Test path walks under the failfs root.
+ *
+ * Once :manpage:`fchroot(2)` moved the process root into failfs, only
+ * lookups anchored at a file descriptor keep working:
+ *
+ * - lookups relative to the working directory, which stays in the real
+ *   filesystem, keep working
+ * - lookups anchored at a pre-opened directory fd keep working, including
+ *   resolution of relative symlinks
+ * - absolute symlinks restart the walk at the failfs root and fail with
+ *   ``EOPNOTSUPP``
+ * - ".." walks clamp at the top of the mount tree, not at the failfs
+ *   root, so walking up from the working directory lands on the real
+ *   root
+ *
+ * Root is required because entering failfs with the ``FD_FAILFS_ROOT``
+ * sentinel requires ``CAP_SYS_CHROOT``.
+ *
+ * The test runs in a forked child so the root of the parent process is
+ * left untouched.
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sys/stat.h>
+#include "tst_test.h"
+#include "lapi/fcntl.h"
+#include "lapi/syscalls.h"
+#include "tst_safe_file_at.h"
+
+#define RELDIR "rel"
+#define ABSDIR "abs"
+
+static char upwards[PATH_MAX];
+
+static void run(void)
+{
+	if (SAFE_FORK())
+		return;
+
+	struct stat realroot, st;
+	int dfd, fd;
+
+	SAFE_STAT("/", &realroot);
+	dfd = SAFE_OPEN(".", O_RDONLY | O_DIRECTORY);
+
+	TST_EXP_PASS(tst_syscall(__NR_fchroot, FD_FAILFS_ROOT, 0),
+		"fchroot() with the FD_FAILFS_ROOT sentinel");
+
+	fd = SAFE_OPENAT(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY);
+	SAFE_CLOSE(fd);
+
+	fd = SAFE_OPENAT(dfd, "canary", O_WRONLY | O_CREAT, 0600);
+	SAFE_WRITE(SAFE_WRITE_ALL, fd, "x", 1);
+	SAFE_CLOSE(fd);
+
+	fd = SAFE_OPENAT(dfd, RELDIR, O_RDONLY);
+	SAFE_CLOSE(fd);
+
+	TST_EXP_FAIL2(openat(dfd, ABSDIR, O_RDONLY), EOPNOTSUPP,
+		"resolution of an absolute symlink");
+
+	fd = SAFE_OPENAT(AT_FDCWD, upwards, O_PATH);
+	SAFE_FSTAT(fd, &st);
+	SAFE_CLOSE(fd);
+
+	TST_EXP_EXPR(st.st_dev == realroot.st_dev &&
+		st.st_ino == realroot.st_ino,
+		"'..' walk clamps at the top of the mount tree");
+
+	SAFE_CLOSE(dfd);
+
+	exit(0);
+}
+
+static void setup(void)
+{
+	char *tmpdir;
+	char abs_path[PATH_MAX];
+	struct stat root_st, st;
+	int fd, off;
+
+	tmpdir = tst_tmpdir_path();
+	snprintf(abs_path, sizeof(abs_path), "%s/%s", tmpdir, "target");
+
+	SAFE_TOUCH("target", 0644, NULL);
+	SAFE_SYMLINK("target", RELDIR);
+	SAFE_SYMLINK(abs_path, ABSDIR);
+
+	SAFE_STAT("/", &root_st);
+
+	off = snprintf(upwards, sizeof(upwards), "..");
+	while (1) {
+		fd = SAFE_OPENAT(AT_FDCWD, upwards, O_PATH);
+		SAFE_FSTAT(fd, &st);
+		SAFE_CLOSE(fd);
+
+		if (st.st_dev == root_st.st_dev && st.st_ino == root_st.st_ino)
+			break;
+
+		off += snprintf(upwards + off, sizeof(upwards) - off, "/..");
+	}
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.test_all = run,
+	.needs_root = 1,
+	.needs_tmpdir = 1,
+	.forks_child = 1,
+};

-- 
2.51.0


-- 
Mailing list info: https://lists.linux.it/listinfo/ltp
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.