From 3c1b73fc6a4d7bc5469ab2679ef954f7b754d34b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 12 Jan 2026 16:47:09 +0100 Subject: [PATCH] fs: add init_pivot_root() We will soon be able to pivot_root() with the introduction of the immutable rootfs. Add a wrapper for kernel internal usage. Link: https://patch.msgid.link/20260112-work-immutable-rootfs-v2-2-88dd1c34a204@kernel.org Signed-off-by: Christian Brauner --- fs/init.c | 17 +++++ fs/internal.h | 1 + fs/namespace.c | 137 ++++++++++++++++++---------------- include/linux/init_syscalls.h | 1 + 4 files changed, 91 insertions(+), 65 deletions(-) diff --git a/fs/init.c b/fs/init.c index e0f5429c0a49..e33b2690d851 100644 --- a/fs/init.c +++ b/fs/init.c @@ -13,6 +13,23 @@ #include #include "internal.h" +int __init init_pivot_root(const char *new_root, const char *put_old) +{ + struct path new_path __free(path_put) = {}; + struct path old_path __free(path_put) = {}; + int ret; + + ret = kern_path(new_root, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &new_path); + if (ret) + return ret; + + ret = kern_path(put_old, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &old_path); + if (ret) + return ret; + + return path_pivot_root(&new_path, &old_path); +} + int __init init_mount(const char *dev_name, const char *dir_name, const char *type_page, unsigned long flags, void *data_page) { diff --git a/fs/internal.h b/fs/internal.h index ab638d41ab81..4b27a4b0fdef 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -90,6 +90,7 @@ extern bool may_mount(void); int path_mount(const char *dev_name, const struct path *path, const char *type_page, unsigned long flags, void *data_page); int path_umount(const struct path *path, int flags); +int path_pivot_root(struct path *new, struct path *old); int show_path(struct seq_file *m, struct dentry *root); diff --git a/fs/namespace.c b/fs/namespace.c index 8b082b1de7f3..9261f56ccc81 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4498,6 +4498,77 @@ bool path_is_under(const struct path *path1, const struct path *path2) } EXPORT_SYMBOL(path_is_under); +int path_pivot_root(struct path *new, struct path *old) +{ + struct path root __free(path_put) = {}; + struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent; + int error; + + if (!may_mount()) + return -EPERM; + + error = security_sb_pivotroot(old, new); + if (error) + return error; + + get_fs_root(current->fs, &root); + + LOCK_MOUNT(old_mp, old); + old_mnt = old_mp.parent; + if (IS_ERR(old_mnt)) + return PTR_ERR(old_mnt); + + new_mnt = real_mount(new->mnt); + root_mnt = real_mount(root.mnt); + ex_parent = new_mnt->mnt_parent; + root_parent = root_mnt->mnt_parent; + if (IS_MNT_SHARED(old_mnt) || + IS_MNT_SHARED(ex_parent) || + IS_MNT_SHARED(root_parent)) + return -EINVAL; + if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) + return -EINVAL; + if (new_mnt->mnt.mnt_flags & MNT_LOCKED) + return -EINVAL; + if (d_unlinked(new->dentry)) + return -ENOENT; + if (new_mnt == root_mnt || old_mnt == root_mnt) + return -EBUSY; /* loop, on the same file system */ + if (!path_mounted(&root)) + return -EINVAL; /* not a mountpoint */ + if (!mnt_has_parent(root_mnt)) + return -EINVAL; /* absolute root */ + if (!path_mounted(new)) + return -EINVAL; /* not a mountpoint */ + if (!mnt_has_parent(new_mnt)) + return -EINVAL; /* absolute root */ + /* make sure we can reach put_old from new_root */ + if (!is_path_reachable(old_mnt, old_mp.mp->m_dentry, new)) + return -EINVAL; + /* make certain new is below the root */ + if (!is_path_reachable(new_mnt, new->dentry, &root)) + return -EINVAL; + lock_mount_hash(); + umount_mnt(new_mnt); + if (root_mnt->mnt.mnt_flags & MNT_LOCKED) { + new_mnt->mnt.mnt_flags |= MNT_LOCKED; + root_mnt->mnt.mnt_flags &= ~MNT_LOCKED; + } + /* mount new_root on / */ + attach_mnt(new_mnt, root_parent, root_mnt->mnt_mp); + umount_mnt(root_mnt); + /* mount old root on put_old */ + attach_mnt(root_mnt, old_mnt, old_mp.mp); + touch_mnt_namespace(current->nsproxy->mnt_ns); + /* A moved mount should not expire automatically */ + list_del_init(&new_mnt->mnt_expire); + unlock_mount_hash(); + mnt_notify_add(root_mnt); + mnt_notify_add(new_mnt); + chroot_fs_refs(&root, new); + return 0; +} + /* * pivot_root Semantics: * Moves the root file system of the current process to the directory put_old, @@ -4528,13 +4599,8 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root, { struct path new __free(path_put) = {}; struct path old __free(path_put) = {}; - struct path root __free(path_put) = {}; - struct mount *new_mnt, *root_mnt, *old_mnt, *root_parent, *ex_parent; int error; - if (!may_mount()) - return -EPERM; - error = user_path_at(AT_FDCWD, new_root, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &new); if (error) @@ -4545,66 +4611,7 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root, if (error) return error; - error = security_sb_pivotroot(&old, &new); - if (error) - return error; - - get_fs_root(current->fs, &root); - - LOCK_MOUNT(old_mp, &old); - old_mnt = old_mp.parent; - if (IS_ERR(old_mnt)) - return PTR_ERR(old_mnt); - - new_mnt = real_mount(new.mnt); - root_mnt = real_mount(root.mnt); - ex_parent = new_mnt->mnt_parent; - root_parent = root_mnt->mnt_parent; - if (IS_MNT_SHARED(old_mnt) || - IS_MNT_SHARED(ex_parent) || - IS_MNT_SHARED(root_parent)) - return -EINVAL; - if (!check_mnt(root_mnt) || !check_mnt(new_mnt)) - return -EINVAL; - if (new_mnt->mnt.mnt_flags & MNT_LOCKED) - return -EINVAL; - if (d_unlinked(new.dentry)) - return -ENOENT; - if (new_mnt == root_mnt || old_mnt == root_mnt) - return -EBUSY; /* loop, on the same file system */ - if (!path_mounted(&root)) - return -EINVAL; /* not a mountpoint */ - if (!mnt_has_parent(root_mnt)) - return -EINVAL; /* absolute root */ - if (!path_mounted(&new)) - return -EINVAL; /* not a mountpoint */ - if (!mnt_has_parent(new_mnt)) - return -EINVAL; /* absolute root */ - /* make sure we can reach put_old from new_root */ - if (!is_path_reachable(old_mnt, old_mp.mp->m_dentry, &new)) - return -EINVAL; - /* make certain new is below the root */ - if (!is_path_reachable(new_mnt, new.dentry, &root)) - return -EINVAL; - lock_mount_hash(); - umount_mnt(new_mnt); - if (root_mnt->mnt.mnt_flags & MNT_LOCKED) { - new_mnt->mnt.mnt_flags |= MNT_LOCKED; - root_mnt->mnt.mnt_flags &= ~MNT_LOCKED; - } - /* mount new_root on / */ - attach_mnt(new_mnt, root_parent, root_mnt->mnt_mp); - umount_mnt(root_mnt); - /* mount old root on put_old */ - attach_mnt(root_mnt, old_mnt, old_mp.mp); - touch_mnt_namespace(current->nsproxy->mnt_ns); - /* A moved mount should not expire automatically */ - list_del_init(&new_mnt->mnt_expire); - unlock_mount_hash(); - mnt_notify_add(root_mnt); - mnt_notify_add(new_mnt); - chroot_fs_refs(&root, &new); - return 0; + return path_pivot_root(&new, &old); } static unsigned int recalc_flags(struct mount_kattr *kattr, struct mount *mnt) diff --git a/include/linux/init_syscalls.h b/include/linux/init_syscalls.h index 92045d18cbfc..28776ee28d8e 100644 --- a/include/linux/init_syscalls.h +++ b/include/linux/init_syscalls.h @@ -17,3 +17,4 @@ int __init init_mkdir(const char *pathname, umode_t mode); int __init init_rmdir(const char *pathname); int __init init_utimes(char *filename, struct timespec64 *ts); int __init init_dup(struct file *file); +int __init init_pivot_root(const char *new_root, const char *put_old);