projects/linux·commit cd051cfe

Merge tag 'vfs-7.3-rc1.failfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

cd051cfe · Linus Torvalds <[email protected]> · 2026-08-17 09:15:52 -07:00

Pull failfs filesystem from Christian Brauner:
 "Add failfs and expose a FD_FAILFS_ROOT sentinel.

  This allows userspace to shed their filesystem state completely. A
  process with its root or working directory in failfs must anchor every
  path lookup at an explicit file descriptor. Absolute paths, absolute
  symlinks and AT_FDCWD-relative lookups simply fail.

  Failfs is the counterpart to nullfs. nullfs says adds a permanently
  empty, immutable directory whose lookups fail with ENOENT but which
  can be opened, read, stat'd and mounted upon. Failfs on the other hand
  fails every operation. The root cannot be opened at all. A single
  instance is mounted during early boot via kern_mount(), which makes it
  logically distinct from every mount namespace.

  This is accompanied by a new fchroot() system call which makes
  chrooting via a file descriptor a first class concept. It's possible
  to chroot into failfs as an unprivileged user provided the task has no
  new privileges set"

* tag 'vfs-7.3-rc1.failfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  Documentation: add failfs documentation
  selftests/filesystems: add failfs selftests
  arch: hookup fchroot() system call
  fs: support FD_FAILFS_ROOT in fchroot()
  fs: add fchroot()
  fs: support FD_FAILFS_ROOT in fchdir()
  fs: add failfs
Commit
cd051cfe1e35a471fc2cdf6d32fae6ee23305ecb
Parents
d31a688a · 1d38e750

Merge commit. Changes below are compared with the first parent: d31a688a.

Changes

44 files changed · +931 −5

FileStatusAddDelContent
Documentation/filesystems/failfs.rstadded+73−0View file
Documentation/filesystems/index.rstmodified+1−0View file
arch/alpha/kernel/syscalls/syscall.tblmodified+1−0View file
arch/arm/tools/syscall.tblmodified+1−0View file
arch/arm64/tools/syscall_32.tblmodified+1−0View file
arch/m68k/kernel/syscalls/syscall.tblmodified+1−0View file
arch/microblaze/kernel/syscalls/syscall.tblmodified+1−0View file
arch/mips/kernel/syscalls/syscall_n32.tblmodified+1−0View file
arch/mips/kernel/syscalls/syscall_n64.tblmodified+1−0View file
arch/mips/kernel/syscalls/syscall_o32.tblmodified+1−0View file
arch/parisc/kernel/syscalls/syscall.tblmodified+1−0View file
arch/powerpc/kernel/syscalls/syscall.tblmodified+1−0View file
arch/s390/kernel/syscalls/syscall.tblmodified+1−0View file
arch/sh/kernel/syscalls/syscall.tblmodified+1−0View file
arch/sparc/kernel/syscalls/syscall.tblmodified+1−0View file
arch/x86/entry/syscalls/syscall_32.tblmodified+1−0View file
arch/x86/entry/syscalls/syscall_64.tblmodified+1−0View file
arch/xtensa/kernel/syscalls/syscall.tblmodified+1−0View file
fs/Makefilemodified+1−1View file
fs/d_path.cmodified+2−1View file
fs/failfs.cadded+166−0View file
fs/internal.hmodified+4−0View file
fs/namespace.cmodified+1−0View file
fs/open.cmodified+50−1View file
include/linux/syscalls.hmodified+1−0View file
include/uapi/asm-generic/unistd.hmodified+5−1View file
include/uapi/linux/fcntl.hmodified+1−0View file
include/uapi/linux/magic.hmodified+1−0View file
scripts/syscall.tblmodified+1−0View file
tools/include/uapi/asm-generic/unistd.hmodified+5−1View file
tools/perf/arch/arm/entry/syscalls/syscall.tblmodified+1−0View file
tools/perf/arch/mips/entry/syscalls/syscall_n64.tblmodified+1−0View file
tools/perf/arch/powerpc/entry/syscalls/syscall.tblmodified+1−0View file
tools/perf/arch/s390/entry/syscalls/syscall.tblmodified+1−0View file
tools/perf/arch/sh/entry/syscalls/syscall.tblmodified+1−0View file
tools/perf/arch/sparc/entry/syscalls/syscall.tblmodified+1−0View file
tools/perf/arch/x86/entry/syscalls/syscall_32.tblmodified+1−0View file
tools/perf/arch/x86/entry/syscalls/syscall_64.tblmodified+1−0View file
tools/perf/arch/xtensa/entry/syscalls/syscall.tblmodified+1−0View file
tools/scripts/syscall.tblmodified+1−0View file
tools/testing/selftests/Makefilemodified+1−0View file
tools/testing/selftests/filesystems/failfs/.gitignoreadded+2−0View file
tools/testing/selftests/filesystems/failfs/Makefileadded+5−0View file
tools/testing/selftests/filesystems/failfs/failfs_test.cadded+585−0View file

Only the first 600 diff lines are shown. Download the complete patch.

Documentation/filesystems/failfs.rst

View file
diff --git a/Documentation/filesystems/failfs.rst b/Documentation/filesystems/failfs.rst
new file mode 100644
index 0000000..21ff2db
--- /dev/null
+++ b/Documentation/filesystems/failfs.rst
@@ -0,0 +1,73 @@
+.. 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 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.
+
+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 ``.``.
+
+No path lookup can open the root, 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 all 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, "..")``.
+
+* The caller does not share its ``fs_struct``: ``no_new_privs`` is
+ checked on the calling thread, but the root lives in the ``fs_struct``.
+ A ``CLONE_FS`` sibling without ``no_new_privs`` could otherwise execute
+ a setuid binary with the failfs root, so entry requires ``fs->users ==
+ 1``, the same restriction ``setns(2)`` applies for the mount and user
+ namespaces.
+
+Leaving
+=======
+
+Backing out is currently hard, but this is a property of the current
+implementation, not a guaranteed interface, and may be loosened later.
+For now a process that entered failfs counts as chrooted, so it cannot
+create user namespaces to regain ``CAP_SYS_CHROOT``, and ``chroot(2)``
+or ``fchroot(2)`` back out require ``CAP_SYS_CHROOT``. The remaining way
+out today 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 cannot currently get back out.

Documentation/filesystems/index.rst

View file
diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst
index 1f71cf1..734a45e 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

arch/alpha/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
index f31b7af..52e3538 100644
--- a/arch/alpha/kernel/syscalls/syscall.tbl
+++ b/arch/alpha/kernel/syscalls/syscall.tbl
@@ -511,3 +511,4 @@
579 common file_setattr sys_file_setattr
580 common listns sys_listns
581 common rseq_slice_yield sys_rseq_slice_yield
+582 common fchroot sys_fchroot

arch/arm/tools/syscall.tbl

View file
diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
index 94351e2..55717ed 100644
--- a/arch/arm/tools/syscall.tbl
+++ b/arch/arm/tools/syscall.tbl
@@ -486,3 +486,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/arm64/tools/syscall_32.tbl

View file
diff --git a/arch/arm64/tools/syscall_32.tbl b/arch/arm64/tools/syscall_32.tbl
index 62d93d8..df2d1d8 100644
--- a/arch/arm64/tools/syscall_32.tbl
+++ b/arch/arm64/tools/syscall_32.tbl
@@ -483,3 +483,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/m68k/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
index 2489342..ba7a4d8 100644
--- a/arch/m68k/kernel/syscalls/syscall.tbl
+++ b/arch/m68k/kernel/syscalls/syscall.tbl
@@ -471,3 +471,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/microblaze/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
index 223d263..c55a5c9 100644
--- a/arch/microblaze/kernel/syscalls/syscall.tbl
+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
@@ -477,3 +477,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/mips/kernel/syscalls/syscall_n32.tbl

View file
diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
index 7430714..9ae88e4 100644
--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
@@ -410,3 +410,4 @@
469 n32 file_setattr sys_file_setattr
470 n32 listns sys_listns
471 n32 rseq_slice_yield sys_rseq_slice_yield
+472 n32 fchroot sys_fchroot

arch/mips/kernel/syscalls/syscall_n64.tbl

View file
diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
index 630aab9..83dc93a 100644
--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
@@ -386,3 +386,4 @@
469 n64 file_setattr sys_file_setattr
470 n64 listns sys_listns
471 n64 rseq_slice_yield sys_rseq_slice_yield
+472 n64 fchroot sys_fchroot

arch/mips/kernel/syscalls/syscall_o32.tbl

View file
diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
index 1286531..9c62429 100644
--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
@@ -459,3 +459,4 @@
469 o32 file_setattr sys_file_setattr
470 o32 listns sys_listns
471 o32 rseq_slice_yield sys_rseq_slice_yield
+472 o32 fchroot sys_fchroot

arch/parisc/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
index c6331da..88adc40 100644
--- a/arch/parisc/kernel/syscalls/syscall.tbl
+++ b/arch/parisc/kernel/syscalls/syscall.tbl
@@ -470,3 +470,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/powerpc/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
index 4fcc7c5..cfbb700 100644
--- a/arch/powerpc/kernel/syscalls/syscall.tbl
+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
@@ -562,3 +562,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 nospu rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/s390/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
index 09a7ef0..1b45e68 100644
--- a/arch/s390/kernel/syscalls/syscall.tbl
+++ b/arch/s390/kernel/syscalls/syscall.tbl
@@ -398,3 +398,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/sh/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
index 70b315c..ace068d 100644
--- a/arch/sh/kernel/syscalls/syscall.tbl
+++ b/arch/sh/kernel/syscalls/syscall.tbl
@@ -475,3 +475,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/sparc/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index 7e71bf7..5b9fe0e 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -517,3 +517,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

arch/x86/entry/syscalls/syscall_32.tbl

View file
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index f832ebd..2c172ef 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -477,3 +477,4 @@
469 i386 file_setattr sys_file_setattr
470 i386 listns sys_listns
471 i386 rseq_slice_yield sys_rseq_slice_yield
+472 i386 fchroot sys_fchroot

arch/x86/entry/syscalls/syscall_64.tbl

View file
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 524155d..d5b6045 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -396,6 +396,7 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot
#
# Due to a historical design error, certain syscalls are numbered differently

arch/xtensa/kernel/syscalls/syscall.tbl

View file
diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
index a9bca4e..d354bb2 100644
--- a/arch/xtensa/kernel/syscalls/syscall.tbl
+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
@@ -442,3 +442,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

fs/Makefile

View file
diff --git a/fs/Makefile b/fs/Makefile
index d470432..055dfc2 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \
stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \
kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \
- file_attr.o fserror.o nullfs.o
+ file_attr.o fserror.o nullfs.o failfs.o
obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o
obj-$(CONFIG_PROC_FS) += proc_namespace.o

fs/d_path.c

View file
diff --git a/fs/d_path.c b/fs/d_path.c
index a48957c..c253090 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -279,7 +279,8 @@ char *d_path(const struct path *path, char *buf, int buflen)
* and instead have d_path return the mounted path.
*/
if (path->dentry->d_op && path->dentry->d_op->d_dname &&
- (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))
+ (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root ||
+ failfs_mnt(path->mnt)))
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
rcu_read_lock();

fs/failfs.c

View file
diff --git a/fs/failfs.c b/fs/failfs.c
new file mode 100644
index 0000000..66a36da
--- /dev/null
+++ b/fs/failfs.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2026 Christian Brauner <[email protected]> */
+#include <linux/fs.h>
+#include <linux/fs/super_types.h>
+#include <linux/fs_context.h>
+#include <linux/fs_struct.h>
+#include <linux/magic.h>
+#include <linux/mount.h>
+
+#include "internal.h"
+
+static struct path failfs_root_path = {};
+
+void failfs_get_root(struct path *path)
+{
+ *path = failfs_root_path;
+ path_get(path);
+}
+
+bool failfs_mnt(const struct vfsmount *mnt)
+{
+ return mnt->mnt_sb == failfs_root_path.mnt->mnt_sb;
+}
+
+static int failfs_permission(struct mnt_idmap *idmap, struct inode *inode,
+ int mask)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct dentry *failfs_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ /* Unreachable: ->permission() already failed the walk. */
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static int failfs_getattr(struct mnt_idmap *idmap, const struct path *path,
+ struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct inode_operations failfs_dir_inode_operations = {
+ .permission = failfs_permission,
+ .lookup = failfs_lookup,
+ .getattr = failfs_getattr,
+};
+
+static const struct file_operations failfs_dir_operations = {};
+
+static int failfs_d_weak_revalidate(struct dentry *dentry, unsigned int flags)
+{
+ /*
+ * The root is only ever reached as a path-walk terminal by jumping
+ * to it: as "/" when it is the caller's root, or through a
+ * /proc/<pid>/{root,cwd} magic link. ->permission() already fails
+ * every walk of a component, but a jump lands on the root without
+ * one. Refuse here too so the root cannot be pinned by an O_PATH
+ * open or encoded into a file handle.
+ */
+ return -EOPNOTSUPP;
+}
+
+static char *failfs_dname(struct dentry *dentry, char *buffer, int buflen)
+{
+ return dynamic_dname(buffer, buflen, "failfs:/");
+}
+
+static const struct dentry_operations failfs_dentry_operations = {
+ .d_dname = failfs_dname,
+ .d_weak_revalidate = failfs_d_weak_revalidate,
+};
+
+static int failfs_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+ return -EOPNOTSUPP;
+}
+
+static const struct super_operations failfs_super_operations = {
+ .statfs = failfs_statfs,
+};
+
+static int failfs_fill_super(struct super_block *s, struct fs_context *fc)
+{
+ struct inode *inode;
+
+ s->s_maxbytes = MAX_LFS_FILESIZE;
+ s->s_blocksize = PAGE_SIZE;
+ s->s_blocksize_bits = PAGE_SHIFT;
+ s->s_magic = FAIL_FS_MAGIC;
+ s->s_op = &failfs_super_operations;
+ s->s_export_op = NULL;
+ s->s_xattr = NULL;
+ s->s_time_gran = 1;
+ s->s_d_flags = 0;
+
+ inode = new_inode(s);
+ if (!inode)
+ return -ENOMEM;
+
+ /* failfs supports no operations... */
+ inode->i_mode = S_IFDIR;
+ set_nlink(inode, 2);
+ inode->i_op = &failfs_dir_inode_operations;
+ inode->i_fop = &failfs_dir_operations;
+ simple_inode_init_ts(inode);
+ inode->i_ino = 1;
+ /* ... and is immutable. */
+ inode->i_flags |= S_IMMUTABLE;
+
+ set_default_d_op(s, &failfs_dentry_operations);
+ s->s_root = d_make_root(inode);
+ if (!s->s_root)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int failfs_get_tree(struct fs_context *fc)
+{
+ return get_tree_single(fc, failfs_fill_super);
+}
+
+static const struct fs_context_operations failfs_context_ops = {
+ .get_tree = failfs_get_tree,
+};
+
+static int failfs_init_fs_context(struct fs_context *fc)
+{
+ fc->ops = &failfs_context_ops;
+ fc->global = true;
+ fc->sb_flags |= SB_NOUSER;
+ fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
+ return 0;
+}
+
+int failfs_current_chdir(void)
+{
+ struct path path;
+
+ failfs_get_root(&path);
+ set_fs_pwd(current->fs, &path);
+ path_put(&path);
+ return 0;
+}
+
+static struct file_system_type failfs_fs_type = {
+ .name = "failfs",
+ .init_fs_context = failfs_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
+void __init failfs_init(void)
+{
+ struct vfsmount *mnt;
+
+ /* A single instance that is member of no mount namespace. */
+ mnt = kern_mount(&failfs_fs_type);
+ if (IS_ERR(mnt))
+ panic("VFS: Failed to create failfs");
+
+ failfs_root_path.mnt = mnt;
+ failfs_root_path.dentry = mnt->mnt_root;
+}

fs/internal.h

View file
diff --git a/fs/internal.h b/fs/internal.h
index 355d93f..67aa044 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -362,3 +362,7 @@ int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
void pidfs_get_root(struct path *path);
void nsfs_get_root(struct path *path);
+void failfs_get_root(struct path *path);
+void __init failfs_init(void);
+bool failfs_mnt(const struct vfsmount *mnt);
+int failfs_current_chdir(void);

fs/namespace.c

View file
diff --git a/fs/namespace.c b/fs/namespace.c
index 3d5cd5b..87c365f 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -6274,6 +6274,7 @@ void __init mnt_init(void)
shmem_init();
init_rootfs();
init_mount_tree();
+ failfs_init();
}
void put_mnt_ns(struct mnt_namespace *ns)

fs/open.c

View file
diff --git a/fs/open.c b/fs/open.c
index 408925d..6b1c14e 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -570,9 +570,12 @@ retry:
SYSCALL_DEFINE1(fchdir, unsigned int, fd)
{
- CLASS(fd_raw, f)(fd);
int error;
+ if ((int)fd == FD_FAILFS_ROOT)
+ return failfs_current_chdir();
+
+ CLASS(fd_raw, f)(fd);
if (fd_empty(f))
return -EBADF;
@@ -615,6 +618,52 @@ dput_and_out:
return error;
}
+SYSCALL_DEFINE2(fchroot, int, fd, unsigned int, flags)
+{
+ struct path path;
+ int error;
+
+ if (flags)
+ return -EINVAL;
+
+ if (fd == FD_FAILFS_ROOT) {
+ if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT)) {
+ if (!task_no_new_privs(current))
+ return -EPERM;
+ /* A shared fs_struct lets a sibling exec setuid past the check above. */
+ if (current->fs->users != 1)
+ return -EINVAL;
+ /* Moving the root to failfs lifts the old root's ".." barrier. */
+ if (current_chrooted())
+ return -EPERM;
+ }
+ failfs_get_root(&path);
+ } else {
+ CLASS(fd_raw, f)(fd);
+ if (fd_empty(f))
+ return -EBADF;
+
+ if (!d_can_lookup(fd_file(f)->f_path.dentry))
+ return -ENOTDIR;
+
+ error = file_permission(fd_file(f), MAY_EXEC | MAY_CHDIR);
+ if (error)
+ return error;
+
+ if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
+ return -EPERM;
+
+ path = fd_file(f)->f_path;
+ path_get(&path);
+ }
+
+ error = security_path_chroot(&path);
+ if (!error)
+ set_fs_root(current->fs, &path);
+ path_put(&path);
+ return error;
+}
+
int chmod_common(const struct path *path, umode_t mode)
{
struct inode *inode = path->dentry->d_inode;

include/linux/syscalls.h

View file
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 874d906..8413b62 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -457,6 +457,7 @@ asmlinkage long sys_faccessat2(int dfd, const char __user *filename, int mode,
asmlinkage long sys_chdir(const char __user *filename);
asmlinkage long sys_fchdir(unsigned int fd);
asmlinkage long sys_chroot(const char __user *filename);
+asmlinkage long sys_fchroot(int fd, unsigned int flags);
asmlinkage long sys_fchmod(unsigned int fd, umode_t mode);
asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
umode_t mode);

include/uapi/asm-generic/unistd.h

View file
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index a627acc..5b7e77a 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -863,8 +863,12 @@ __SYSCALL(__NR_listns, sys_listns)
#define __NR_rseq_slice_yield 471
__SYSCALL(__NR_rseq_slice_yield, sys_rseq_slice_yield)
+/* fs/open.c */
+#define __NR_fchroot 472
+__SYSCALL(__NR_fchroot, sys_fchroot)
+
#undef __NR_syscalls
-#define __NR_syscalls 472
+#define __NR_syscalls 473
/*
* 32 bit systems traditionally used different

include/uapi/linux/fcntl.h

View file
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index aadfbf6..e43e3de 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -124,6 +124,7 @@ struct delegation {
#define FD_PIDFS_ROOT -10002 /* Root of the pidfs filesystem */
#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
+#define FD_FAILFS_ROOT -10004 /* Root of the failfs filesystem */
#define FD_INVALID -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
/* Generic flags for the *at(2) family of syscalls. */

include/uapi/linux/magic.h

View file
diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h
index 4f2da93..fd5f0e9 100644
--- a/include/uapi/linux/magic.h
+++ b/include/uapi/linux/magic.h
@@ -105,5 +105,6 @@
#define PID_FS_MAGIC 0x50494446 /* "PIDF" */
#define GUEST_MEMFD_MAGIC 0x474d454d /* "GMEM" */
#define NULL_FS_MAGIC 0x4E554C4C /* "NULL" */
+#define FAIL_FS_MAGIC 0x4641494C /* "FAIL" */
#endif /* __LINUX_MAGIC_H__ */

scripts/syscall.tbl

View file
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index 7a42b32..0ab5316 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -412,3 +412,4 @@
469 common file_setattr sys_file_setattr
470 common listns sys_listns
471 common rseq_slice_yield sys_rseq_slice_yield
+472 common fchroot sys_fchroot

tools/include/uapi/asm-generic/unistd.h

View file
diff --git a/tools/include/uapi/asm-generic/unistd.h b/tools/include/uapi/asm-generic/unistd.h
index a627acc..5b7e77a 100644
--- a/tools/include/uapi/asm-generic/unistd.h
+++ b/tools/include/uapi/asm-generic/unistd.h
@@ -863,8 +863,12 @@ __SYSCALL(__NR_listns, sys_listns)
#define __NR_rseq_slice_yield 471

This diff is incomplete. Download the complete patch.

tools/perf/arch/arm/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/mips/entry/syscalls/syscall_n64.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/powerpc/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/s390/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/sh/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/sparc/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/x86/entry/syscalls/syscall_32.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/x86/entry/syscalls/syscall_64.tbl

View file

This diff is incomplete. Download the complete patch.

tools/perf/arch/xtensa/entry/syscalls/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/scripts/syscall.tbl

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/failfs/.gitignore

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/failfs/Makefile

View file

This diff is incomplete. Download the complete patch.

tools/testing/selftests/filesystems/failfs/failfs_test.c

View file

This diff is incomplete. Download the complete patch.