[PATCH RFC 3/7] fs: add fchroot()
From: Christian Brauner <brauner@kernel.org>
Date: 2026-07-23 11:30:46
Also in:
linux-doc, linux-fsdevel, lkml
Subsystem:
abi/api, filesystems (vfs and infrastructure), the rest · Maintainers:
Alexander Viro, Christian Brauner, Linus Torvalds
Add a file descriptor based counterpart to chroot(2). This has been overdue for a long time. It is the natural companion to fchdir() and avoids re-resolving a path that the caller already holds a file descriptor to. No TOCTOU between resolving the target and changing the root. It composes with modern fd-based APIs meaning it works with O_PATH file descriptors and file descriptors to detached mount trees created via open_tree(OPEN_TREE_CLONE). The permission model is identical to chroot(2). Rhe caller must have CAP_SYS_CHROOT in its user namespace, must pass MAY_EXEC | MAY_CHDIR permission checks on the target directory, and LSMs are consulted via the same security_path_chroot() hook. The system call takes a flags argument for future extensibility which must currently be zero. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> --- fs/open.c | 29 +++++++++++++++++++++++++++++ include/linux/syscalls.h | 1 + 2 files changed, 30 insertions(+)
diff --git a/fs/open.c b/fs/open.c
index 56b6032d4d81..c57f641f2e29 100644
--- a/fs/open.c
+++ b/fs/open.c@@ -618,6 +618,35 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename) return error; } +SYSCALL_DEFINE2(fchroot, int, fd, unsigned int, flags) +{ + int error; + + if (flags) + return -EINVAL; + + 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; + + error = security_path_chroot(&fd_file(f)->f_path); + if (error) + return error; + + set_fs_root(current->fs, &fd_file(f)->f_path); + return 0; +} + int chmod_common(const struct path *path, umode_t mode) { struct inode *inode = path->dentry->d_inode;
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 874d9067a43b..8413b624ad47 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);
--
2.53.0