Linux userland API discussions
 help / color / mirror / Atom feed
* [RFC PATCH 0/3] fs: support tasks with a null root or cwd
@ 2026-06-29  6:58 John Ericson
  2026-06-29  6:58 ` [RFC PATCH 1/3] fs: Add documentation to some `struct fs_struct` fields John Ericson
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: John Ericson @ 2026-06-29  6:58 UTC (permalink / raw)
  To: Andy Lutomirski, Al Viro, Christian Brauner, Jan Kara,
	David Howells, Chuck Lever, Jeff Layton, Shuah Khan, David Laight,
	H. Peter Anvin, Li Chen, Cong Wang, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jonathan Corbet, Kees Cook, Sergei Zimmerman, Farid Zakaria,
	linux-arch, LKML, linux-fsdevel, linux-api, netfs, linux-nfs
  Cc: John Ericson

From: John Ericson <mail@JohnEricson.me>

This patch series allows processes to avoid having a root directory
and/or a working directory. The actual implementation is in the second
patch. The first patch just prepares some documentation. (Are struct
fields documented like this? Would this be good regardless?) And the
third patch adds unit tests.

I have tested these patches by `kunit.py run`-ing my new test suite.

The motivation is discussed at length in the thread starting with [1].
This patch would be the first in a series trying to allow for
less-privileged and more cheaply less-privileged processes than we have
today, by nulling out their namespace fields (or similar) so they do not
belong to any namespaces. This specific change is a prelude (in spirit,
it might actually be orthogonal in practice) to nulling out the mount
namespace, so that the process does not "belong" to any mount namespace.

Nothing creates such a task yet; the new UAPIs that would take advantage
of this feature are left as future work. It may not be appropriate to
submit such a "dead-code" patch, but I wanted to demonstrate just how
easy this change is (along with at least a unit test to exercise it),
before getting into UAPI designs.

The marquee new UAPI around this would, I hope, be the new non-fork-exec
process spawning API Li Chen started in [2]. The idea would be that,
instead of doing process initialization in the child post-fork, it is
done from the parent, against (my terminology) an "embryonic" (not yet
runnable; withheld from the schedule) process. These null fields
(`struct path` to directories in this patch, pointers to namespaces in
the latter patches to be written) allow for lightweight and minimally
privileged initial embryonic processes, allowing for good performance
(don't preemptively allocate things the caller may not want) and abiding
by the "principle of least privilege" (initialization should always
grant, not take away, privileges and resources from the embryonic
process).

So far in the linked discussion, the alternative that best addresses my
motivation is using the new "nullfs" for the root directory and cwd.
Practically, that is almost as good -- reading and statting the
directory will work, but it will reliably be and remain empty regardless
of how privileged the caller is. The downside is simply that we would
like those operations also to fail, to more readily signal to developers
and users (human or agent, for that matter) that the working directory
and root directory should be avoided, and paths should instead always be
paired with a file descriptor and used with `*at` or other modern UAPIs.

[1]: https://lore.kernel.org/all/a49ce818-f38d-41b0-bbf7-80b8aad998b1@app.fastmail.com/

[2]: https://lore.kernel.org/all/20260528095235.2491226-1-me@linux.beauty/

John Ericson (3):
  fs: Add documentation to some `struct fs_struct` fields
  fs: support tasks with a null root or cwd
  fs: add KUnit tests for tasks with a null root or cwd

 fs/Kconfig                     |  11 +++
 fs/cachefiles/daemon.c         |   6 +-
 fs/d_path.c                    |   6 +-
 fs/fhandle.c                   |   3 +
 fs/fs_struct.c                 |   4 +
 fs/namei.c                     |  22 ++++-
 fs/proc/base.c                 |   8 +-
 fs/tests/null_root_cwd_kunit.c | 147 +++++++++++++++++++++++++++++++++
 include/linux/fs_struct.h      |  29 ++++++-
 9 files changed, 228 insertions(+), 8 deletions(-)
 create mode 100644 fs/tests/null_root_cwd_kunit.c

-- 
2.51.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH 1/3] fs: Add documentation to some `struct fs_struct` fields
  2026-06-29  6:58 [RFC PATCH 0/3] fs: support tasks with a null root or cwd John Ericson
@ 2026-06-29  6:58 ` John Ericson
  2026-06-29  6:58 ` [RFC PATCH 2/3] fs: support tasks with a null root or cwd John Ericson
  2026-06-29  6:58 ` [RFC PATCH 3/3] fs: add KUnit tests for " John Ericson
  2 siblings, 0 replies; 5+ messages in thread
From: John Ericson @ 2026-06-29  6:58 UTC (permalink / raw)
  To: Andy Lutomirski, Al Viro, Christian Brauner, Jan Kara,
	David Howells, Chuck Lever, Jeff Layton, Shuah Khan, David Laight,
	H. Peter Anvin, Li Chen, Cong Wang, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jonathan Corbet, Kees Cook, Sergei Zimmerman, Farid Zakaria,
	linux-arch, LKML, linux-fsdevel, linux-api, netfs, linux-nfs
  Cc: John Ericson

From: John Ericson <mail@JohnEricson.me>

This will be expanded upon in the next commit.

Link: https://lore.kernel.org/all/a49ce818-f38d-41b0-bbf7-80b8aad998b1@app.fastmail.com/
Signed-off-by: John Ericson <mail@JohnEricson.me>
---
 include/linux/fs_struct.h | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h
index 0070764b790a..b5db5de9eb01 100644
--- a/include/linux/fs_struct.h
+++ b/include/linux/fs_struct.h
@@ -12,7 +12,21 @@ struct fs_struct {
 	seqlock_t seq;
 	int umask;
 	int in_exec;
-	struct path root, pwd;
+
+	/*
+	 * The root directory for the task(s) that points to this
+	 * `fs_struct`. The root directory also controls how `..`
+	 * resolve; path traversal is not allowed to resolve upwards
+	 * beyond the root directory. (It is for this latter reason that
+	 * `chroot` is a privileged operation.)
+	 */
+	struct path root;
+
+	/*
+	 * The current working directory for the task(s) that points to
+	 * this `fs_struct`.
+	 */
+	struct path pwd;
 } __randomize_layout;
 
 extern struct kmem_cache *fs_cachep;
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH 2/3] fs: support tasks with a null root or cwd
  2026-06-29  6:58 [RFC PATCH 0/3] fs: support tasks with a null root or cwd John Ericson
  2026-06-29  6:58 ` [RFC PATCH 1/3] fs: Add documentation to some `struct fs_struct` fields John Ericson
@ 2026-06-29  6:58 ` John Ericson
  2026-06-29  9:19   ` Christian Brauner
  2026-06-29  6:58 ` [RFC PATCH 3/3] fs: add KUnit tests for " John Ericson
  2 siblings, 1 reply; 5+ messages in thread
From: John Ericson @ 2026-06-29  6:58 UTC (permalink / raw)
  To: Andy Lutomirski, Al Viro, Christian Brauner, Jan Kara,
	David Howells, Chuck Lever, Jeff Layton, Shuah Khan, David Laight,
	H. Peter Anvin, Li Chen, Cong Wang, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jonathan Corbet, Kees Cook, Sergei Zimmerman, Farid Zakaria,
	linux-arch, LKML, linux-fsdevel, linux-api, netfs, linux-nfs
  Cc: John Ericson

From: John Ericson <mail@JohnEricson.me>

A task's root directory (`fs->root`) and current working directory
(`fs->pwd`) are normally established by `chroot(2)`/`pivot_root(2)` and
`chdir(2)`/`fchdir(2)` (or inherited across `fork(2)`). Allow either to
instead be the null path, as documented in `struct fs_struct`. The two
are independent: a task may opt out of one, the other, or both.

A task with no root cannot use absolute pathnames, and its `..` is no
longer bounded by a process root: it climbs to the root of the mount the
walk is in (the security implications are discussed in `struct
fs_struct`). A task with no cwd cannot use `AT_FDCWD`-relative
pathnames. Either way it can still name files through the `*at(2)`
descriptors it holds.

Teach the readers of these fields to cope instead of dereferencing the
NULL dentry, each checking the field it uses:

- namei: `set_root()` now tolerates a NULL root (skipping the
  `nd->root.dentry->d_seq` read), so `nd_jump_root()` returns `-ENOENT`
  for absolute paths and symlinks, while `..` falls through to
  `follow_dotdot()` -- which already treats a NULL `nd->root` as "no
  boundary" and climbs. The `AT_FDCWD` legs of `path_init()` return
  `-ENOENT` with no cwd; real-dirfd lookups (`openat(2)`, `openat2(2)`)
  are unaffected.

- `getcwd(2)`, `/proc/PID/{root,cwd}`, `open_by_handle_at()` with
  `AT_FDCWD`, and the cachefiles `cull`/`inuse` commands return an error
  rather than dereferencing the NULL path.

The setters need no change: `chdir(2)`/`chroot(2)`/`pivot_root(2)`
resolve via `filename_lookup(AT_FDCWD, ...)`, which simply fails with no
root or cwd, and `fchdir(2)` installs a cwd from an fd without
consulting the old one. `d_path()` is unaffected: `__prepend_path()`
only compares against the root.

These opt-outs are not sticky; keeping a task rootless or cwd-less is an
orthogonal policy decision (e.g. seccomp filtering the setters above).

Link: https://lore.kernel.org/all/a49ce818-f38d-41b0-bbf7-80b8aad998b1@app.fastmail.com/
Signed-off-by: John Ericson <mail@JohnEricson.me>
Assisted-by: Claude:claude-opus-4-8
---
 fs/cachefiles/daemon.c    |  6 ++++--
 fs/d_path.c               |  6 +++++-
 fs/fhandle.c              |  3 +++
 fs/namei.c                | 22 ++++++++++++++++++++--
 fs/proc/base.c            |  8 ++++++--
 include/linux/fs_struct.h | 13 +++++++++++++
 6 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c
index 4117b145ac94..344feeb89c61 100644
--- a/fs/cachefiles/daemon.c
+++ b/fs/cachefiles/daemon.c
@@ -652,7 +652,8 @@ static int cachefiles_daemon_cull(struct cachefiles_cache *cache, char *args)
 
 	get_fs_pwd(current->fs, &path);
 
-	if (!d_can_lookup(path.dentry))
+	/* A task may have no cwd. */
+	if (!path.mnt || !d_can_lookup(path.dentry))
 		goto notdir;
 
 	cachefiles_begin_secure(cache, &saved_cred);
@@ -723,7 +724,8 @@ static int cachefiles_daemon_inuse(struct cachefiles_cache *cache, char *args)
 
 	get_fs_pwd(current->fs, &path);
 
-	if (!d_can_lookup(path.dentry))
+	/* A task may have no cwd. */
+	if (!path.mnt || !d_can_lookup(path.dentry))
 		goto notdir;
 
 	cachefiles_begin_secure(cache, &saved_cred);
diff --git a/fs/d_path.c b/fs/d_path.c
index a48957c0971e..5f16d1efa37c 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -422,7 +422,11 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
 	rcu_read_lock();
 	get_fs_root_and_pwd_rcu(current->fs, &root, &pwd);
 
-	if (unlikely(d_unlinked(pwd.dentry))) {
+	/* A task may have no cwd. */
+	if (unlikely(!pwd.mnt)) {
+		rcu_read_unlock();
+		error = -ENOENT;
+	} else if (unlikely(d_unlinked(pwd.dentry))) {
 		rcu_read_unlock();
 		error = -ENOENT;
 	} else {
diff --git a/fs/fhandle.c b/fs/fhandle.c
index 1ca7eb3a6cb5..560f88f53633 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -180,6 +180,9 @@ static int get_path_anchor(int fd, struct path *root)
 
 	if (fd == AT_FDCWD) {
 		get_fs_pwd(current->fs, root);
+		/* A task may have no cwd. */
+		if (!root->mnt)
+			return -ENOENT;
 		return 0;
 	}
 
diff --git a/fs/namei.c b/fs/namei.c
index 5cc9f0f466b8..06b16815e866 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1120,11 +1120,20 @@ static int set_root(struct nameidata *nd)
 		do {
 			seq = read_seqbegin(&fs->seq);
 			nd->root = fs->root;
-			nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);
+			/*
+			 * A task may have no root. Leave nd->root as the NULL
+			 * path and skip the d_seq read: absolute lookups turn
+			 * the absence into -ENOENT in nd_jump_root(), while ".."
+			 * treats a NULL root as "no boundary" and climbs to its
+			 * mount root.
+			 */
+			if (likely(nd->root.mnt))
+				nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);
 		} while (read_seqretry(&fs->seq, seq));
 	} else {
 		get_fs_root(fs, &nd->root);
-		nd->state |= ND_ROOT_GRABBED;
+		if (likely(nd->root.mnt))
+			nd->state |= ND_ROOT_GRABBED;
 	}
 	return 0;
 }
@@ -1143,6 +1152,9 @@ static int nd_jump_root(struct nameidata *nd)
 		if (unlikely(error))
 			return error;
 	}
+	/* Absolute paths need a root to jump to; a task may have none. */
+	if (unlikely(!nd->root.mnt))
+		return -ENOENT;
 	if (nd->flags & LOOKUP_RCU) {
 		struct dentry *d;
 		nd->path = nd->root;
@@ -2732,11 +2744,17 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 			do {
 				seq = read_seqbegin(&fs->seq);
 				nd->path = fs->pwd;
+				/* A task may have no cwd. */
+				if (unlikely(!nd->path.mnt))
+					return ERR_PTR(-ENOENT);
 				nd->inode = nd->path.dentry->d_inode;
 				nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
 			} while (read_seqretry(&fs->seq, seq));
 		} else {
 			get_fs_pwd(current->fs, &nd->path);
+			/* A task may have no cwd. */
+			if (unlikely(!nd->path.mnt))
+				return ERR_PTR(-ENOENT);
 			nd->inode = nd->path.dentry->d_inode;
 		}
 	} else {
diff --git a/fs/proc/base.c b/fs/proc/base.c
index 780f81259052..7f7cc86ce262 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -213,7 +213,9 @@ static int get_task_root(struct task_struct *task, struct path *root)
 	task_lock(task);
 	if (task->fs) {
 		get_fs_root(task->fs, root);
-		result = 0;
+		/* A task may have no root. */
+		if (root->mnt)
+			result = 0;
 	}
 	task_unlock(task);
 	return result;
@@ -227,7 +229,9 @@ static int proc_cwd_link(struct dentry *dentry, struct path *path,
 	task_lock(task);
 	if (task->fs) {
 		get_fs_pwd(task->fs, path);
-		result = 0;
+		/* A task may have no cwd. */
+		if (path->mnt)
+			result = 0;
 	}
 	task_unlock(task);
 	return result;
diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h
index b5db5de9eb01..84423b4bd21a 100644
--- a/include/linux/fs_struct.h
+++ b/include/linux/fs_struct.h
@@ -13,18 +13,31 @@ struct fs_struct {
 	int umask;
 	int in_exec;
 
+	/*
+	 * Note that these paths are explicitly intended to be nullable.
+	 * Since they are inline structs and not pointers, we use `.mnt
+	 * == NULL` to indicate nullability of the path as a whole.
+	 */
+
 	/*
 	 * The root directory for the task(s) that points to this
 	 * `fs_struct`. The root directory also controls how `..`
 	 * resolve; path traversal is not allowed to resolve upwards
 	 * beyond the root directory. (It is for this latter reason that
 	 * `chroot` is a privileged operation.)
+	 *
+	 * If null (as described above), absolute paths will not
+	 * resolve. In addition `..` will be unbounded, until one
+	 * reaches the top of the mount tree.
 	 */
 	struct path root;
 
 	/*
 	 * The current working directory for the task(s) that points to
 	 * this `fs_struct`.
+	 *
+	 * If null (as described above), relative paths with `AT_FDCWD`
+	 * will not resolve.
 	 */
 	struct path pwd;
 } __randomize_layout;
-- 
2.51.2


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH 3/3] fs: add KUnit tests for tasks with a null root or cwd
  2026-06-29  6:58 [RFC PATCH 0/3] fs: support tasks with a null root or cwd John Ericson
  2026-06-29  6:58 ` [RFC PATCH 1/3] fs: Add documentation to some `struct fs_struct` fields John Ericson
  2026-06-29  6:58 ` [RFC PATCH 2/3] fs: support tasks with a null root or cwd John Ericson
@ 2026-06-29  6:58 ` John Ericson
  2 siblings, 0 replies; 5+ messages in thread
From: John Ericson @ 2026-06-29  6:58 UTC (permalink / raw)
  To: Andy Lutomirski, Al Viro, Christian Brauner, Jan Kara,
	David Howells, Chuck Lever, Jeff Layton, Shuah Khan, David Laight,
	H. Peter Anvin, Li Chen, Cong Wang, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jonathan Corbet, Kees Cook, Sergei Zimmerman, Farid Zakaria,
	linux-arch, LKML, linux-fsdevel, linux-api, netfs, linux-nfs
  Cc: John Ericson

From: John Ericson <mail@JohnEricson.me>

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/
Signed-off-by: John Ericson <mail@JohnEricson.me>
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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH 2/3] fs: support tasks with a null root or cwd
  2026-06-29  6:58 ` [RFC PATCH 2/3] fs: support tasks with a null root or cwd John Ericson
@ 2026-06-29  9:19   ` Christian Brauner
  0 siblings, 0 replies; 5+ messages in thread
From: Christian Brauner @ 2026-06-29  9:19 UTC (permalink / raw)
  To: John Ericson
  Cc: Andy Lutomirski, Al Viro, Christian Brauner, Jan Kara,
	David Howells, Chuck Lever, Jeff Layton, Shuah Khan, David Laight,
	H. Peter Anvin, Li Chen, Cong Wang, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jonathan Corbet, Kees Cook, Sergei Zimmerman, Farid Zakaria,
	linux-arch, LKML, linux-fsdevel, linux-api, netfs, linux-nfs,
	John Ericson

> A task's root directory (`fs->root`) and current working directory
> (`fs->pwd`) are normally established by `chroot(2)`/`pivot_root(2)` and
> `chdir(2)`/`fchdir(2)` (or inherited across `fork(2)`). Allow either to
> instead be the null path, as documented in `struct fs_struct`. The two
> are independent: a task may opt out of one, the other, or both.

No, absolutely we're not going to have tasks with struct path's in their
struct fs_struct that have NULL members in them. struct path is used
insanely widely in the kernel this is just an an open invitation for a
slew of security bugs. Not going to happen.

-- 
Christian Brauner <brauner@kernel.org>

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-06-29  9:19 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-29  6:58 [RFC PATCH 0/3] fs: support tasks with a null root or cwd John Ericson
2026-06-29  6:58 ` [RFC PATCH 1/3] fs: Add documentation to some `struct fs_struct` fields John Ericson
2026-06-29  6:58 ` [RFC PATCH 2/3] fs: support tasks with a null root or cwd John Ericson
2026-06-29  9:19   ` Christian Brauner
2026-06-29  6:58 ` [RFC PATCH 3/3] fs: add KUnit tests for " John Ericson

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox