Christian Brauner @ 2026-07-23 13:30 +02: > Document the failfs semantics, the FD_FAILFS_ROOT sentinel, the > fchroot() entry requirements, and the ways back out. > > Signed-off-by: Christian Brauner (Amutable) > --- > Documentation/filesystems/failfs.rst | 64 ++++++++++++++++++++++++++++++++++++ > Documentation/filesystems/index.rst | 1 + > 2 files changed, 65 insertions(+) > > diff --git a/Documentation/filesystems/failfs.rst b/Documentation/filesystems/failfs.rst > new file mode 100644 > index 000000000000..46d91525916b > --- /dev/null > +++ b/Documentation/filesystems/failfs.rst > @@ -0,0 +1,64 @@ > +.. SPDX-License-Identifier: GPL-2.0 > + > +====== > +failfs > +====== > + > +failfs is a kernel-internal filesystem that fails every operation > +reaching it with ``EOPNOTSUPP``. It is the counterpart to nullfs. Where > +nullfs is a permanently empty failfs means "nothing is supported here". It > +cannot be mounted from userspace, nothing can be mounted on top of it. It > +cannot be cloned. A small change here makes the wording more clear: from 'Where nullfs is a permanently empty failfs means "nothing is supported here"' to 'Where nullfs is permanently empty, failfs means "nothing is supported here"'. > + > +The only way into it is the ``FD_FAILFS_ROOT`` file descriptor sentinel which > +is understood by ``fchdir(2)`` and ``fchroot(2)``. > + > +Semantics > +========= > + > +Every path walk of a component through failfs fails with > +``EOPNOTSUPP`` before that component is parsed, including ``.``. > + > +The root itself cannot be opened at all not even with ``O_PATH``. > + > +A process with its working directory in failfs fails every > +``AT_FDCWD``-relative lookup. As with any working directory that is > +unreachable from the process root, the ``getcwd(2)`` system call returns > +a path prefixed with ``(unreachable)``. > + > +A process with its root directory in failfs fails every absolute path > +lookup including absolute symlinks and the interpreter of dynamically > +linked binaries. In other words, this fails exec. > + > +Lookups anchored at explicit directory file descriptors keep working. It > +is the ``fs_struct`` equivalent of ``RESOLVE_BENEATH``. The process must > +anchor every lookup at a file descriptor it explicitly holds. > + > +Entering > +======== > + > +``fchroot(FD_FAILFS_ROOT, 0)`` requires ``CAP_SYS_CHROOT`` in the > +caller's user namespace, mirroring ``chroot(2)``. Unprivileged callers > +may enter if both of the following hold: > + > +* ``no_new_privs`` is set: setuid binaries on regular mounts remain > + reachable via inherited directory file descriptors and executing them > + with an unusable root directory is the classic confused deputy. > + > +* The caller is not already chrooted: the root directory is what > + confines ``..`` resolution and the failfs root can never be reached by > + walking up a real mount tree, so moving the root of a chrooted task to > + failfs would allow it to escape its chroot via ``openat(fd, "..")``. > + > +Leaving > +======= > + > +A process that entered failfs counts as chrooted. It cannot create user > +namespaces to regain ``CAP_SYS_CHROOT``, and ``chroot(2)`` or > +``fchroot(2)`` back out require ``CAP_SYS_CHROOT``. The only other exit > +is ``setns(2)`` with a mount namespace file descriptor, which requires > +``CAP_SYS_ADMIN`` over the target mount namespace as well as > +``CAP_SYS_CHROOT`` and ``CAP_SYS_ADMIN`` in the caller's user namespace > +and resets both root and working directory. A process that holds no such > +file descriptor and restricts ``*chdir()``/``*chroot()``/``setns()`` via > +seccomp has thrown away the key. > diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst > index 1f71cf159547..734a45e51667 100644 > --- a/Documentation/filesystems/index.rst > +++ b/Documentation/filesystems/index.rst > @@ -91,6 +91,7 @@ Documentation for filesystem implementations. > ext3 > ext4/index > f2fs > + failfs > gfs2/index > hfs > hfsplus