Thread (5 messages) 5 messages, 2 authors, 7d ago
COOLING7d

[RFC PATCH 3/3] fs: add KUnit tests for tasks with a null root or cwd

From: John Ericson <hidden>
Date: 2026-06-29 07:00:04
Also in: linux-arch, linux-fsdevel, linux-nfs, lkml, netfs
Subsystem: filesystems (vfs and infrastructure), the rest · Maintainers: Alexander Viro, Christian Brauner, Linus Torvalds

From: John Ericson <redacted>

A KUnit suite (`CONFIG_NULL_ROOT_CWD_KUNIT_TEST`) exercises the previous
patch against a task whose own root and/or cwd it nulls: absolute and
`AT_FDCWD`-relative lookups fail, `..` climbs, and descriptor-anchored
lookups keep working. Each test unshares its `fs_struct` so it only ever
touches a private copy, and restores the original root/cwd afterwards.

It is gated by `CONFIG_NULL_ROOT_CWD_KUNIT_TEST` and `#include`d into
`fs/fs_struct.c`, following the `fs/tests/*_kunit.c` pattern.

Link: https://lore.kernel.org/all/a49ce818-f38d-41b0-bbf7-80b8aad998b1@app.fastmail.com/ (local)
Signed-off-by: John Ericson <redacted>
Assisted-by: Claude:claude-opus-4-8
---
 fs/Kconfig                     |  11 +++
 fs/fs_struct.c                 |   4 +
 fs/tests/null_root_cwd_kunit.c | 147 +++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+)
 create mode 100644 fs/tests/null_root_cwd_kunit.c
diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..9023597b6e2b 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -18,6 +18,17 @@ config VALIDATE_FS_PARSER
 config FS_IOMAP
 	bool
 
+config NULL_ROOT_CWD_KUNIT_TEST
+	bool "KUnit tests for tasks with a null root or cwd" if !KUNIT_ALL_TESTS
+	depends on KUNIT=y
+	default KUNIT_ALL_TESTS
+	help
+	  Build KUnit tests that exercise path resolution for tasks whose
+	  fs->root and/or fs->pwd is the NULL path (no root directory and/or
+	  no current working directory).
+
+	  If unsure, say N.
+
 # Stackable filesystems
 config FS_STACK
 	bool
diff --git a/fs/fs_struct.c b/fs/fs_struct.c
index 394875d06fd6..bf620bba7f35 100644
--- a/fs/fs_struct.c
+++ b/fs/fs_struct.c
@@ -153,3 +153,7 @@ struct fs_struct init_fs = {
 	.seq		= __SEQLOCK_UNLOCKED(init_fs.seq),
 	.umask		= 0022,
 };
+
+#ifdef CONFIG_NULL_ROOT_CWD_KUNIT_TEST
+#include "tests/null_root_cwd_kunit.c"
+#endif
diff --git a/fs/tests/null_root_cwd_kunit.c b/fs/tests/null_root_cwd_kunit.c
new file mode 100644
index 000000000000..3fb7e63545f8
--- /dev/null
+++ b/fs/tests/null_root_cwd_kunit.c
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * KUnit tests for tasks whose fs->root and/or fs->pwd is the NULL path.
+ * See "fs: support tasks with a null root or cwd".
+ *
+ * Each test runs against this task's own fs_struct. We unshare it first
+ * so we only ever touch a private copy, never the shared one, and we
+ * restore the original root/cwd afterwards.
+ */
+#include <kunit/test.h>
+#include <linux/fs_struct.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+
+/* The NULL path: { .mnt = NULL, .dentry = NULL }. */
+static const struct path null_path;
+
+struct null_fs_ctx {
+	struct path saved_root;
+	struct path saved_pwd;
+	struct path anchor;	/* a real directory, standing in for a dirfd */
+};
+
+static int null_fs_setup(struct null_fs_ctx *ctx)
+{
+	int err;
+
+	err = unshare_fs_struct();
+	if (err)
+		return err;
+	err = kern_path("/", LOOKUP_DIRECTORY, &ctx->anchor);
+	if (err)
+		return err;
+	get_fs_root(current->fs, &ctx->saved_root);
+	get_fs_pwd(current->fs, &ctx->saved_pwd);
+	return 0;
+}
+
+static void null_fs_teardown(struct null_fs_ctx *ctx)
+{
+	set_fs_root(current->fs, &ctx->saved_root);
+	set_fs_pwd(current->fs, &ctx->saved_pwd);
+	path_put(&ctx->saved_root);
+	path_put(&ctx->saved_pwd);
+	path_put(&ctx->anchor);
+}
+
+/* Resolve @name, drop any reference it returns, and yield the errno. */
+static int try_kern_path(const char *name)
+{
+	struct path out;
+	int err = kern_path(name, 0, &out);
+
+	if (!err)
+		path_put(&out);
+	return err;
+}
+
+static int try_fd_relative(struct null_fs_ctx *ctx, const char *name)
+{
+	struct path out;
+	int err = vfs_path_lookup(ctx->anchor.dentry, ctx->anchor.mnt,
+				  name, 0, &out);
+
+	if (!err)
+		path_put(&out);
+	return err;
+}
+
+/* No root: absolute paths fail, but ".." climbs and the cwd still works. */
+static void null_root_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_root(current->fs, &null_path);
+
+	/* A leading '/' has nothing to anchor to. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), -ENOENT);
+
+	/* ".." is unbounded rather than refused (it would have been
+	 * -ENOENT before this feature). It starts from the still-present
+	 * cwd and runs out of parents at the mount root.
+	 */
+	KUNIT_EXPECT_EQ(test, try_kern_path(".."), 0);
+
+	/* The cwd is untouched: AT_FDCWD-relative lookups still resolve. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), 0);
+
+	/* A dirfd-anchored lookup never consults fs->root. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+/* No cwd: AT_FDCWD-relative paths fail, but absolute and dirfds work. */
+static void null_cwd_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_pwd(current->fs, &null_path);
+
+	/* Relative-to-cwd lookups have no starting point. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), -ENOENT);
+	KUNIT_EXPECT_EQ(test, try_kern_path("foo"), -ENOENT);
+
+	/* The root is untouched: absolute lookups still resolve. */
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), 0);
+
+	/* A dirfd-anchored lookup never consults fs->pwd. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+/* Neither root nor cwd: only descriptor-relative lookups remain. */
+static void null_root_and_cwd_test(struct kunit *test)
+{
+	struct null_fs_ctx ctx;
+
+	KUNIT_ASSERT_EQ(test, null_fs_setup(&ctx), 0);
+	set_fs_root(current->fs, &null_path);
+	set_fs_pwd(current->fs, &null_path);
+
+	KUNIT_EXPECT_EQ(test, try_kern_path("/"), -ENOENT);
+	KUNIT_EXPECT_EQ(test, try_kern_path("."), -ENOENT);
+
+	/* The held descriptor still names files. */
+	KUNIT_EXPECT_EQ(test, try_fd_relative(&ctx, "."), 0);
+
+	null_fs_teardown(&ctx);
+}
+
+static struct kunit_case null_root_cwd_test_cases[] = {
+	KUNIT_CASE(null_root_test),
+	KUNIT_CASE(null_cwd_test),
+	KUNIT_CASE(null_root_and_cwd_test),
+	{},
+};
+
+static struct kunit_suite null_root_cwd_test_suite = {
+	.name = "null_root_cwd",
+	.test_cases = null_root_cwd_test_cases,
+};
+
+kunit_test_suite(null_root_cwd_test_suite);
-- 
2.51.2
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help