mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 01:43:47 -04:00
Merge tag 'vfs-7.3-rc1.kthread' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull kthread vfs updates from Christian Brauner: "This stops kernel threads from sharing filesystem state with userspace. This work is about 3 cycles old and has been in -next for about that time. When the kernel boots init_task creates PID 1 and then kthreadd. From that point every kthread and PID 1 share the same fs_struct. That is why pivot_root() has to rewrite the fs_struct of all kthreads. The rewriting exists so that kthreads can use init's filesystem state when they want to. It also means userspace can move the ground out from under the kernel. PID 1 now gets a completely separate fs_struct. All kthreads are anchored in a private SB_KERNMOUNT instance of nullfs that cannot be mounted on and cannot be used to follow other mounts. Userspace init can no longer affect kthread filesystem state and kthreads can no longer affect userspace fs state without explicit opting in to that. Path lookup from a kthread now fails by default. It makes it deliberately hard to offload security sensitive operations into init's filesystem state from a kthread. Places that legitimately need to look something up there opt in through the new scoped_with_init_fs() which temporarily overrides the caller's fs_struct with init's. usermodehelpers remain the only kernel tasks that genuinely share init's filesystem state, since they execute random binaries in the root filesystem (excellent...). The visible result is that /proc/2/root is a nullfs with an empty mountinfo while /proc/1/root is the real root" * tag 'vfs-7.3-rc1.kthread' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (26 commits) initramfs_test: use test init/exit hooks to override init fs fs: stop rewriting paths for PF_EXITING | PF_DUMPCORE fs: stop rewriting kthread fs structs fs: start all kthreads in nullfs nullfs: make nullfs multi-instance devtmpfs: create private mount namespace fs: add umh argument to struct kernel_clone_args fs: stop sharing fs_struct between init_task and pid 1 af_unix: use scoped_with_init_fs() for coredump socket lookup initramfs: use scoped_with_init_fs() for rootfs unpacking pnfs/blocklayout: use scoped_with_init_fs() for SCSI device lookup ksmbd: use scoped_with_init_fs() for VFS path operations ksmbd: use scoped_with_init_fs() for filesystem info path lookup ksmbd: use scoped_with_init_fs() for share path resolution fs: use scoped_with_init_fs() for kernel_read_file_from_path_initns() coredump: use scoped_with_init_fs() for coredump path resolution btrfs: use scoped_with_init_fs() for update_dev_time() scsi: target: use scoped_with_init_fs() for APTPL metadata scsi: target: use scoped_with_init_fs() for ALUA metadata crypto: ccp: use scoped_with_init_fs() for SEV file access ...
This commit is contained in:
@@ -413,7 +413,7 @@ static noinline int __init devtmpfs_setup(void *p)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = ksys_unshare(CLONE_NEWNS);
|
||||
err = ksys_unshare(UNSHARE_EMPTY_MNTNS);
|
||||
if (err)
|
||||
goto out;
|
||||
err = init_mount("devtmpfs", "/", "devtmpfs", DEVTMPFS_MFLAGS, NULL);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/fs_struct.h>
|
||||
|
||||
#include "rnbd-srv.h"
|
||||
#include "rnbd-srv-trace.h"
|
||||
@@ -734,7 +735,8 @@ static int process_msg_open(struct rnbd_srv_session *srv_sess,
|
||||
goto reject;
|
||||
}
|
||||
|
||||
bdev_file = bdev_file_open_by_path(full_path, open_flags, NULL, NULL);
|
||||
scoped_with_init_fs()
|
||||
bdev_file = bdev_file_open_by_path(full_path, open_flags, NULL, NULL);
|
||||
if (IS_ERR(bdev_file)) {
|
||||
ret = PTR_ERR(bdev_file);
|
||||
pr_err("Opening device '%s' on session %s failed, failed to open the block device, err: %pe\n",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/init_syscalls.h>
|
||||
|
||||
/* static minor (LCD_MINOR) */
|
||||
@@ -160,18 +161,22 @@ static void __init miscdev_test_can_open(struct kunit *test, struct miscdevice *
|
||||
char *devname;
|
||||
|
||||
devname = kasprintf(GFP_KERNEL, "/dev/%s", misc->name);
|
||||
ret = init_mknod(devname, S_IFCHR | 0600,
|
||||
new_encode_dev(MKDEV(MISC_MAJOR, misc->minor)));
|
||||
if (ret != 0)
|
||||
KUNIT_FAIL(test, "failed to create node\n");
|
||||
|
||||
filp = filp_open(devname, O_RDONLY, 0);
|
||||
if (IS_ERR(filp))
|
||||
KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp));
|
||||
else
|
||||
fput(filp);
|
||||
/* Tests run in a nullfs kthread; borrow the init fs to resolve /dev. */
|
||||
scoped_with_init_fs() {
|
||||
ret = init_mknod(devname, S_IFCHR | 0600,
|
||||
new_encode_dev(MKDEV(MISC_MAJOR, misc->minor)));
|
||||
if (ret != 0)
|
||||
KUNIT_FAIL(test, "failed to create node\n");
|
||||
|
||||
init_unlink(devname);
|
||||
filp = filp_open(devname, O_RDONLY, 0);
|
||||
if (IS_ERR(filp))
|
||||
KUNIT_FAIL(test, "failed to open misc device: %ld\n", PTR_ERR(filp));
|
||||
else
|
||||
fput(filp);
|
||||
|
||||
init_unlink(devname);
|
||||
}
|
||||
kfree(devname);
|
||||
}
|
||||
|
||||
|
||||
@@ -260,20 +260,16 @@ static int sev_cmd_buffer_len(int cmd)
|
||||
|
||||
static struct file *open_file_as_root(const char *filename, int flags, umode_t mode)
|
||||
{
|
||||
struct path root __free(path_put) = {};
|
||||
|
||||
task_lock(&init_task);
|
||||
get_fs_root(init_task.fs, &root);
|
||||
task_unlock(&init_task);
|
||||
|
||||
CLASS(prepare_creds, cred)();
|
||||
if (!cred)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
cred->fsuid = GLOBAL_ROOT_UID;
|
||||
|
||||
scoped_with_creds(cred)
|
||||
return file_open_root(&root, filename, flags, mode);
|
||||
scoped_with_init_fs() {
|
||||
scoped_with_creds(cred)
|
||||
return filp_open(filename, flags, mode);
|
||||
}
|
||||
}
|
||||
|
||||
static int sev_read_init_ex_file(void)
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <scsi/scsi_proto.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
@@ -856,10 +858,17 @@ static int core_alua_write_tpg_metadata(
|
||||
unsigned char *md_buf,
|
||||
u32 md_buf_len)
|
||||
{
|
||||
struct file *file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
||||
struct file *file;
|
||||
loff_t pos = 0;
|
||||
int ret;
|
||||
|
||||
if (tsk_is_kthread(current)) {
|
||||
scoped_with_init_fs()
|
||||
file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
||||
} else {
|
||||
file = filp_open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
||||
}
|
||||
|
||||
if (IS_ERR(file)) {
|
||||
pr_err("filp_open(%s) for ALUA metadata failed\n", path);
|
||||
return -ENODEV;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <linux/file.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <scsi/scsi_proto.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
@@ -1969,7 +1970,8 @@ static int __core_scsi3_write_aptpl_to_file(
|
||||
if (!path)
|
||||
return -ENOMEM;
|
||||
|
||||
file = filp_open(path, flags, 0600);
|
||||
scoped_with_init_fs()
|
||||
file = filp_open(path, flags, 0600);
|
||||
if (IS_ERR(file)) {
|
||||
pr_err("filp_open(%s) for APTPL metadata"
|
||||
" failed\n", path);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <linux/uuid.h>
|
||||
#include <linux/list_sort.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include "misc.h"
|
||||
#include "disk-io.h"
|
||||
#include "extent-tree.h"
|
||||
@@ -2125,8 +2126,16 @@ static int btrfs_add_dev_item(struct btrfs_trans_handle *trans,
|
||||
static void update_dev_time(const char *device_path)
|
||||
{
|
||||
struct path path;
|
||||
int err;
|
||||
|
||||
if (!kern_path(device_path, LOOKUP_FOLLOW, &path)) {
|
||||
if (tsk_is_kthread(current)) {
|
||||
scoped_with_init_fs()
|
||||
err = kern_path(device_path, LOOKUP_FOLLOW, &path);
|
||||
} else {
|
||||
err = kern_path(device_path, LOOKUP_FOLLOW, &path);
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
vfs_utimes(&path, NULL);
|
||||
path_put(&path);
|
||||
}
|
||||
|
||||
@@ -921,15 +921,10 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,
|
||||
* with a fully qualified path" rule is to control where
|
||||
* coredumps may be placed using root privileges,
|
||||
* current->fs->root must not be used. Instead, use the
|
||||
* root directory of init_task.
|
||||
* root directory of PID 1.
|
||||
*/
|
||||
struct path root;
|
||||
|
||||
task_lock(&init_task);
|
||||
get_fs_root(init_task.fs, &root);
|
||||
task_unlock(&init_task);
|
||||
file = file_open_root(&root, cn->corename, open_flags, 0600);
|
||||
path_put(&root);
|
||||
scoped_with_init_fs()
|
||||
file = filp_open(cn->corename, open_flags, 0600);
|
||||
} else {
|
||||
file = filp_open(cn->corename, open_flags, 0600);
|
||||
}
|
||||
|
||||
103
fs/fs_struct.c
103
fs/fs_struct.c
@@ -8,6 +8,7 @@
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/init_task.h>
|
||||
#include "internal.h"
|
||||
#include "mount.h"
|
||||
|
||||
/*
|
||||
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
|
||||
@@ -60,8 +61,11 @@ void chroot_fs_refs(const struct path *old_root, const struct path *new_root)
|
||||
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(g, p) {
|
||||
if (p->flags & (PF_KTHREAD | PF_EXITING | PF_DUMPCORE))
|
||||
continue;
|
||||
|
||||
task_lock(p);
|
||||
fs = p->fs;
|
||||
fs = p->real_fs;
|
||||
if (fs) {
|
||||
int hits = 0;
|
||||
write_seqlock(&fs->seq);
|
||||
@@ -89,12 +93,13 @@ void free_fs_struct(struct fs_struct *fs)
|
||||
|
||||
void exit_fs(struct task_struct *tsk)
|
||||
{
|
||||
struct fs_struct *fs = tsk->fs;
|
||||
struct fs_struct *fs = tsk->real_fs;
|
||||
|
||||
if (fs) {
|
||||
int kill;
|
||||
task_lock(tsk);
|
||||
read_seqlock_excl(&fs->seq);
|
||||
tsk->real_fs = NULL;
|
||||
tsk->fs = NULL;
|
||||
kill = !--fs->users;
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
@@ -126,7 +131,7 @@ struct fs_struct *copy_fs_struct(struct fs_struct *old)
|
||||
|
||||
int unshare_fs_struct(void)
|
||||
{
|
||||
struct fs_struct *fs = current->fs;
|
||||
struct fs_struct *fs = current->real_fs;
|
||||
struct fs_struct *new_fs = copy_fs_struct(fs);
|
||||
int kill;
|
||||
|
||||
@@ -135,8 +140,10 @@ int unshare_fs_struct(void)
|
||||
|
||||
task_lock(current);
|
||||
read_seqlock_excl(&fs->seq);
|
||||
VFS_WARN_ON_ONCE(fs != current->fs);
|
||||
kill = !--fs->users;
|
||||
current->fs = new_fs;
|
||||
current->real_fs = new_fs;
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
task_unlock(current);
|
||||
|
||||
@@ -147,9 +154,99 @@ int unshare_fs_struct(void)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unshare_fs_struct);
|
||||
|
||||
/*
|
||||
* PID 1 may choose to stop sharing fs_struct state with us.
|
||||
* Either via unshare(CLONE_FS) or unshare(CLONE_NEWNS). Of
|
||||
* course, PID 1 could have chosen to create arbitrary process
|
||||
* trees that all share fs_struct state via CLONE_FS. This is a
|
||||
* strong statement: We only care about PID 1 aka the thread-group
|
||||
* leader so subthread's fs_struct state doesn't matter.
|
||||
*
|
||||
* PID 1 unsharing fs_struct state is a bug. PID 1 relies on
|
||||
* various kthreads to be able to perform work based on its
|
||||
* fs_struct state. Breaking that contract sucks for both sides.
|
||||
* So just don't bother with extra work for this. No sane init
|
||||
* system should ever do this.
|
||||
*
|
||||
* On older kernels if PID 1 unshared its filesystem state with us the
|
||||
* kernel simply used the stale fs_struct state implicitly pinning
|
||||
* anything that PID 1 had last used. Even if PID 1 might've moved on to
|
||||
* some completely different fs_struct state and might've even unmounted
|
||||
* the old root.
|
||||
*
|
||||
* This has hilarious consequences: Think continuing to dump coredump
|
||||
* state into an implicitly pinned directory somewhere. Calling random
|
||||
* binaries in the old rootfs via usermodehelpers.
|
||||
*
|
||||
* Be aggressive about this: We simply reject operating on stale
|
||||
* fs_struct state by reverting to nullfs. Every kworker that does
|
||||
* lookups after this point will fail. Every usermodehelper call will
|
||||
* fail. Tough luck but let's be kind and emit a warning to userspace.
|
||||
*/
|
||||
static inline void validate_fs_switch(struct fs_struct *old_fs)
|
||||
{
|
||||
might_sleep();
|
||||
|
||||
if (likely(current->pid != 1))
|
||||
return;
|
||||
/* @old_fs may be dangling but for comparison it's fine */
|
||||
if (old_fs != userspace_init_fs)
|
||||
return;
|
||||
pr_warn("VFS: Pid 1 stopped sharing filesystem state\n");
|
||||
set_fs_root(userspace_init_fs, &init_fs.root);
|
||||
set_fs_pwd(userspace_init_fs, &init_fs.root);
|
||||
}
|
||||
|
||||
struct fs_struct *switch_fs_struct(struct fs_struct *new_fs)
|
||||
{
|
||||
struct fs_struct *fs;
|
||||
|
||||
scoped_guard(task_lock, current) {
|
||||
fs = current->fs;
|
||||
VFS_WARN_ON_ONCE(fs != current->real_fs);
|
||||
read_seqlock_excl(&fs->seq);
|
||||
current->fs = new_fs;
|
||||
current->real_fs = new_fs;
|
||||
if (--fs->users)
|
||||
new_fs = NULL;
|
||||
else
|
||||
new_fs = fs;
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
}
|
||||
|
||||
validate_fs_switch(fs);
|
||||
return new_fs;
|
||||
}
|
||||
|
||||
/* to be mentioned only in INIT_TASK */
|
||||
struct fs_struct init_fs = {
|
||||
.users = 1,
|
||||
.seq = __SEQLOCK_UNLOCKED(init_fs.seq),
|
||||
.umask = 0022,
|
||||
};
|
||||
|
||||
struct fs_struct *userspace_init_fs __ro_after_init;
|
||||
EXPORT_SYMBOL_GPL(userspace_init_fs);
|
||||
|
||||
void __init init_userspace_fs(void)
|
||||
{
|
||||
struct mount *m;
|
||||
struct path root;
|
||||
|
||||
/* Move PID 1 from nullfs into the initramfs. */
|
||||
m = topmost_overmount(current->nsproxy->mnt_ns->root);
|
||||
root.mnt = &m->mnt;
|
||||
root.dentry = root.mnt->mnt_root;
|
||||
|
||||
VFS_WARN_ON_ONCE(current->pid != 1);
|
||||
|
||||
set_fs_root(current->fs, &root);
|
||||
set_fs_pwd(current->fs, &root);
|
||||
|
||||
/* Hold a reference for the global pointer. */
|
||||
read_seqlock_excl(¤t->fs->seq);
|
||||
current->fs->users++;
|
||||
read_sequnlock_excl(¤t->fs->seq);
|
||||
|
||||
userspace_init_fs = current->fs;
|
||||
}
|
||||
|
||||
@@ -150,18 +150,13 @@ ssize_t kernel_read_file_from_path_initns(const char *path, loff_t offset,
|
||||
enum kernel_read_file_id id)
|
||||
{
|
||||
struct file *file;
|
||||
struct path root;
|
||||
ssize_t ret;
|
||||
|
||||
if (!path || !*path)
|
||||
return -EINVAL;
|
||||
|
||||
task_lock(&init_task);
|
||||
get_fs_root(init_task.fs, &root);
|
||||
task_unlock(&init_task);
|
||||
|
||||
file = file_open_root(&root, path, O_RDONLY, 0);
|
||||
path_put(&root);
|
||||
scoped_with_init_fs()
|
||||
file = filp_open(path, O_RDONLY, 0);
|
||||
if (IS_ERR(file))
|
||||
return PTR_ERR(file);
|
||||
|
||||
|
||||
@@ -6184,12 +6184,14 @@ static void __init init_mount_tree(void)
|
||||
struct path root;
|
||||
|
||||
/*
|
||||
* We create two mounts:
|
||||
* We create three mounts:
|
||||
*
|
||||
* (1) nullfs with mount id 1
|
||||
* (2) mutable rootfs with mount id 2
|
||||
* (3) private nullfs for kthreads (SB_KERNMOUNT)
|
||||
*
|
||||
* with (2) mounted on top of (1).
|
||||
* with (2) mounted on top of (1). The init_task's root and pwd
|
||||
* are pointed at (3) so all kthreads start isolated in nullfs.
|
||||
*/
|
||||
nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL);
|
||||
if (IS_ERR(nullfs_mnt))
|
||||
@@ -6229,12 +6231,14 @@ static void __init init_mount_tree(void)
|
||||
init_mnt_ns.nr_mounts++;
|
||||
}
|
||||
|
||||
nullfs_mnt = kern_mount(&nullfs_fs_type);
|
||||
if (IS_ERR(nullfs_mnt))
|
||||
panic("VFS: Failed to create private nullfs instance");
|
||||
root.mnt = nullfs_mnt;
|
||||
root.dentry = nullfs_mnt->mnt_root;
|
||||
|
||||
init_task.nsproxy->mnt_ns = &init_mnt_ns;
|
||||
get_mnt_ns(&init_mnt_ns);
|
||||
|
||||
/* The root and pwd always point to the mutable rootfs. */
|
||||
root.mnt = mnt;
|
||||
root.dentry = mnt->mnt_root;
|
||||
set_fs_pwd(current->fs, &root);
|
||||
set_fs_root(current->fs, &root);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include <linux/sunrpc/svc.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/nfs4.h>
|
||||
#include <linux/nfs_fs.h>
|
||||
#include <linux/nfs_xdr.h>
|
||||
@@ -363,15 +364,22 @@ static struct file *
|
||||
bl_open_path(struct pnfs_block_volume *v, const char *prefix)
|
||||
{
|
||||
struct file *bdev_file;
|
||||
const char *devname;
|
||||
const char *devname __free(kfree) = NULL;
|
||||
|
||||
devname = kasprintf(GFP_KERNEL, "/dev/disk/by-id/%s%*phN",
|
||||
prefix, v->scsi.designator_len, v->scsi.designator);
|
||||
if (!devname)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
bdev_file = bdev_file_open_by_path(devname,
|
||||
BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL);
|
||||
if (tsk_is_kthread(current)) {
|
||||
scoped_with_init_fs()
|
||||
bdev_file = bdev_file_open_by_path(devname,
|
||||
BLK_OPEN_READ | BLK_OPEN_WRITE,
|
||||
NULL, NULL);
|
||||
} else {
|
||||
bdev_file = bdev_file_open_by_path(devname,
|
||||
BLK_OPEN_READ | BLK_OPEN_WRITE, NULL, NULL);
|
||||
}
|
||||
if (IS_ERR(bdev_file)) {
|
||||
dprintk("failed to open device %s (%ld)\n",
|
||||
devname, PTR_ERR(bdev_file));
|
||||
@@ -380,7 +388,6 @@ bl_open_path(struct pnfs_block_volume *v, const char *prefix)
|
||||
file_bdev(bdev_file)->bd_disk->disk_name);
|
||||
}
|
||||
|
||||
kfree(devname);
|
||||
return bdev_file;
|
||||
}
|
||||
|
||||
|
||||
12
fs/nullfs.c
12
fs/nullfs.c
@@ -40,14 +40,9 @@ static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For now this is a single global instance. If needed we can make it
|
||||
* mountable by userspace at which point we will need to make it
|
||||
* multi-instance.
|
||||
*/
|
||||
static int nullfs_fs_get_tree(struct fs_context *fc)
|
||||
{
|
||||
return get_tree_single(fc, nullfs_fs_fill_super);
|
||||
return get_tree_nodev(fc, nullfs_fs_fill_super);
|
||||
}
|
||||
|
||||
static const struct fs_context_operations nullfs_fs_context_ops = {
|
||||
@@ -57,9 +52,8 @@ static const struct fs_context_operations nullfs_fs_context_ops = {
|
||||
static int nullfs_init_fs_context(struct fs_context *fc)
|
||||
{
|
||||
fc->ops = &nullfs_fs_context_ops;
|
||||
fc->global = true;
|
||||
fc->sb_flags = SB_NOUSER;
|
||||
fc->s_iflags = SB_I_NOEXEC | SB_I_NODEV;
|
||||
fc->sb_flags |= SB_NOUSER;
|
||||
fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,8 +168,8 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
|
||||
cred = get_task_cred(p);
|
||||
|
||||
task_lock(p);
|
||||
if (p->fs)
|
||||
umask = p->fs->umask;
|
||||
if (p->real_fs)
|
||||
umask = p->real_fs->umask;
|
||||
if (p->files)
|
||||
max_fds = files_fdtable(p->files)->max_fds;
|
||||
task_unlock(p);
|
||||
|
||||
@@ -211,8 +211,8 @@ static int get_task_root(struct task_struct *task, struct path *root)
|
||||
int result = -ENOENT;
|
||||
|
||||
task_lock(task);
|
||||
if (task->fs) {
|
||||
get_fs_root(task->fs, root);
|
||||
if (task->real_fs) {
|
||||
get_fs_root(task->real_fs, root);
|
||||
result = 0;
|
||||
}
|
||||
task_unlock(task);
|
||||
@@ -225,8 +225,8 @@ static int proc_cwd_link(struct dentry *dentry, struct path *path,
|
||||
int result = -ENOENT;
|
||||
|
||||
task_lock(task);
|
||||
if (task->fs) {
|
||||
get_fs_pwd(task->fs, path);
|
||||
if (task->real_fs) {
|
||||
get_fs_pwd(task->real_fs, path);
|
||||
result = 0;
|
||||
}
|
||||
task_unlock(task);
|
||||
|
||||
@@ -254,13 +254,13 @@ static int mounts_open_common(struct inode *inode, struct file *file,
|
||||
}
|
||||
ns = nsp->mnt_ns;
|
||||
get_mnt_ns(ns);
|
||||
if (!task->fs) {
|
||||
if (!task->real_fs) {
|
||||
task_unlock(task);
|
||||
put_task_struct(task);
|
||||
ret = -ENOENT;
|
||||
goto err_put_ns;
|
||||
}
|
||||
get_fs_root(task->fs, &root);
|
||||
get_fs_root(task->real_fs, &root);
|
||||
task_unlock(task);
|
||||
put_task_struct(task);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/parser.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
@@ -193,7 +194,8 @@ static struct ksmbd_share_config *share_config_request(struct ksmbd_work *work,
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = kern_path(share->path, 0, &share->vfs_path);
|
||||
scoped_with_init_fs()
|
||||
ret = kern_path(share->path, 0, &share->vfs_path);
|
||||
ksmbd_revert_fsids(work);
|
||||
if (ret) {
|
||||
ksmbd_debug(SMB, "failed to access '%s'\n",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <net/addrconf.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/namei.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/statfs.h>
|
||||
#include <linux/ethtool.h>
|
||||
#include <linux/falloc.h>
|
||||
@@ -5949,7 +5950,8 @@ static int smb2_get_info_filesystem(struct ksmbd_work *work,
|
||||
if (!share->path)
|
||||
return -EIO;
|
||||
|
||||
rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path);
|
||||
scoped_with_init_fs()
|
||||
rc = kern_path(share->path, LOOKUP_NO_SYMLINKS, &path);
|
||||
if (rc) {
|
||||
pr_err("cannot create vfs path\n");
|
||||
return -EIO;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <crypto/sha2.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/filelock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/backing-dev.h>
|
||||
@@ -67,8 +68,9 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
|
||||
}
|
||||
|
||||
CLASS(filename_kernel, filename)(pathname);
|
||||
err = vfs_path_parent_lookup(filename, flags, path, &last,
|
||||
root_share_path);
|
||||
scoped_with_init_fs()
|
||||
err = vfs_path_parent_lookup(filename, flags, path, &last,
|
||||
root_share_path);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -623,7 +625,8 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
|
||||
if (ksmbd_override_fsids(work))
|
||||
return -ENOMEM;
|
||||
|
||||
err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath);
|
||||
scoped_with_init_fs()
|
||||
err = kern_path(oldname, LOOKUP_NO_SYMLINKS, &oldpath);
|
||||
if (err) {
|
||||
pr_err("cannot get linux path for %s, err = %d\n",
|
||||
oldname, err);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <linux/path.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/seqlock.h>
|
||||
#include <linux/vfsdebug.h>
|
||||
|
||||
struct fs_struct {
|
||||
int users;
|
||||
@@ -16,6 +17,7 @@ struct fs_struct {
|
||||
} __randomize_layout;
|
||||
|
||||
extern struct kmem_cache *fs_cachep;
|
||||
extern struct fs_struct *userspace_init_fs;
|
||||
|
||||
extern void exit_fs(struct task_struct *);
|
||||
extern void set_fs_root(struct fs_struct *, const struct path *);
|
||||
@@ -40,6 +42,8 @@ static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
}
|
||||
|
||||
struct fs_struct *switch_fs_struct(struct fs_struct *new_fs);
|
||||
|
||||
extern bool current_chrooted(void);
|
||||
|
||||
static inline int current_umask(void)
|
||||
@@ -47,4 +51,34 @@ static inline int current_umask(void)
|
||||
return current->fs->umask;
|
||||
}
|
||||
|
||||
/*
|
||||
* Temporarily use userspace_init_fs for path resolution in kthreads.
|
||||
* Callers should use scoped_with_init_fs() which automatically
|
||||
* restores the original fs_struct at scope exit.
|
||||
*/
|
||||
static inline struct fs_struct *__override_init_fs(void)
|
||||
{
|
||||
struct fs_struct *old_fs;
|
||||
|
||||
old_fs = current->fs;
|
||||
WRITE_ONCE(current->fs, userspace_init_fs);
|
||||
return old_fs;
|
||||
}
|
||||
|
||||
static inline void __revert_init_fs(struct fs_struct *old_fs)
|
||||
{
|
||||
VFS_WARN_ON_ONCE(current->fs != userspace_init_fs);
|
||||
WRITE_ONCE(current->fs, old_fs);
|
||||
}
|
||||
|
||||
DEFINE_CLASS(__override_init_fs,
|
||||
struct fs_struct *,
|
||||
__revert_init_fs(_T),
|
||||
__override_init_fs(), void)
|
||||
|
||||
#define scoped_with_init_fs() \
|
||||
scoped_class(__override_init_fs, __UNIQUE_ID(label))
|
||||
|
||||
void __init init_userspace_fs(void);
|
||||
|
||||
#endif /* _LINUX_FS_STRUCT_H */
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
extern struct files_struct init_files;
|
||||
extern struct fs_struct init_fs;
|
||||
extern struct fs_struct *userspace_init_fs;
|
||||
extern struct nsproxy init_nsproxy;
|
||||
|
||||
#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
|
||||
|
||||
@@ -1191,6 +1191,7 @@ struct task_struct {
|
||||
unsigned long last_switch_time;
|
||||
#endif
|
||||
/* Filesystem information: */
|
||||
struct fs_struct *real_fs;
|
||||
struct fs_struct *fs;
|
||||
|
||||
/* Open file information: */
|
||||
|
||||
@@ -31,6 +31,7 @@ struct kernel_clone_args {
|
||||
u32 io_thread:1;
|
||||
u32 user_worker:1;
|
||||
u32 no_files:1;
|
||||
u32 umh:1;
|
||||
unsigned long stack;
|
||||
unsigned long stack_size;
|
||||
unsigned long tls;
|
||||
|
||||
@@ -162,6 +162,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
|
||||
RCU_POINTER_INITIALIZER(cred, &init_cred),
|
||||
.comm = INIT_TASK_COMM,
|
||||
.thread = INIT_THREAD,
|
||||
.real_fs = &init_fs,
|
||||
.fs = &init_fs,
|
||||
.files = &init_files,
|
||||
#ifdef CONFIG_IO_URING
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/hex.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/init_syscalls.h>
|
||||
@@ -716,7 +717,7 @@ static void __init populate_initrd_image(char *err)
|
||||
}
|
||||
#endif /* CONFIG_BLK_DEV_RAM */
|
||||
|
||||
static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
|
||||
static void __init unpack_initramfs(async_cookie_t cookie)
|
||||
{
|
||||
/* Load the built in initramfs */
|
||||
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
|
||||
@@ -724,7 +725,7 @@ static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
|
||||
panic_show_mem("%s", err); /* Failed to decompress INTERNAL initramfs */
|
||||
|
||||
if (!initrd_start || IS_ENABLED(CONFIG_INITRAMFS_FORCE))
|
||||
goto done;
|
||||
return;
|
||||
|
||||
if (IS_ENABLED(CONFIG_BLK_DEV_RAM))
|
||||
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
|
||||
@@ -739,9 +740,14 @@ static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
|
||||
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
security_initramfs_populated();
|
||||
static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
|
||||
{
|
||||
scoped_with_init_fs() {
|
||||
unpack_initramfs(cookie);
|
||||
security_initramfs_populated();
|
||||
}
|
||||
|
||||
/*
|
||||
* If the initrd region is overlapped with crashkernel reserved region,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/init_syscalls.h>
|
||||
#include <linux/initrd.h>
|
||||
@@ -562,7 +563,7 @@ static struct kunit_case __refdata initramfs_test_cases[] = {
|
||||
{},
|
||||
};
|
||||
|
||||
static int __init initramfs_test_init(struct kunit_suite *suite)
|
||||
static int __init initramfs_suite_init(struct kunit_suite *suite)
|
||||
{
|
||||
/*
|
||||
* unpack_to_rootfs() uses module-static state (victim, byte_count,
|
||||
@@ -574,9 +575,23 @@ static int __init initramfs_test_init(struct kunit_suite *suite)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Tests run in a nullfs kthread; always use the init fs for path resolution. */
|
||||
static int __init initramfs_test_init(struct kunit *test)
|
||||
{
|
||||
test->priv = __override_init_fs();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __init initramfs_test_exit(struct kunit *test)
|
||||
{
|
||||
__revert_init_fs(test->priv);
|
||||
}
|
||||
|
||||
static struct kunit_suite __refdata initramfs_test_suite = {
|
||||
.name = "initramfs",
|
||||
.suite_init = initramfs_test_init,
|
||||
.suite_init = initramfs_suite_init,
|
||||
.init = initramfs_test_init,
|
||||
.exit = initramfs_test_exit,
|
||||
.test_cases = initramfs_test_cases,
|
||||
};
|
||||
kunit_test_init_section_suites(&initramfs_test_suite);
|
||||
|
||||
10
init/main.c
10
init/main.c
@@ -103,6 +103,7 @@
|
||||
#include <linux/stackdepot.h>
|
||||
#include <linux/randomize_kstack.h>
|
||||
#include <linux/pidfs.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/ptdump.h>
|
||||
#include <linux/time_namespace.h>
|
||||
#include <linux/unaligned.h>
|
||||
@@ -670,6 +671,11 @@ static __initdata DECLARE_COMPLETION(kthreadd_done);
|
||||
|
||||
static noinline void __ref __noreturn rest_init(void)
|
||||
{
|
||||
struct kernel_clone_args init_args = {
|
||||
.flags = (CLONE_VM | CLONE_UNTRACED),
|
||||
.fn = kernel_init,
|
||||
.fn_arg = NULL,
|
||||
};
|
||||
struct task_struct *tsk;
|
||||
int pid;
|
||||
|
||||
@@ -679,7 +685,7 @@ static noinline void __ref __noreturn rest_init(void)
|
||||
* the init task will end up wanting to create kthreads, which, if
|
||||
* we schedule it before we create kthreadd, will OOPS.
|
||||
*/
|
||||
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
|
||||
pid = kernel_clone(&init_args);
|
||||
/*
|
||||
* Pin init on the boot CPU. Task migration is not properly working
|
||||
* until sched_init_smp() has been run. It will set the allowed
|
||||
@@ -1540,6 +1546,8 @@ static int __ref kernel_init(void *unused)
|
||||
{
|
||||
int ret;
|
||||
|
||||
init_userspace_fs();
|
||||
|
||||
/*
|
||||
* Wait until kthreadd is all set-up.
|
||||
*/
|
||||
|
||||
@@ -1618,9 +1618,27 @@ static int copy_exec_state(u64 clone_flags, struct task_struct *tsk)
|
||||
return task_exec_state_copy(tsk);
|
||||
}
|
||||
|
||||
static int copy_fs(u64 clone_flags, struct task_struct *tsk)
|
||||
static int copy_fs(u64 clone_flags, struct task_struct *tsk, bool umh)
|
||||
{
|
||||
struct fs_struct *fs = current->fs;
|
||||
struct fs_struct *fs;
|
||||
|
||||
/*
|
||||
* Usermodehelper may copy userspace_init_fs filesystem state but
|
||||
* they don't get to create mount namespaces, share the
|
||||
* filesystem state, or be started from a non-initial mount
|
||||
* namespace.
|
||||
*/
|
||||
if (umh) {
|
||||
if (clone_flags & (CLONE_NEWNS | CLONE_FS))
|
||||
return -EINVAL;
|
||||
if (current->nsproxy->mnt_ns != &init_mnt_ns)
|
||||
return -EINVAL;
|
||||
fs = userspace_init_fs;
|
||||
} else {
|
||||
fs = current->fs;
|
||||
VFS_WARN_ON_ONCE(current->fs != current->real_fs);
|
||||
}
|
||||
|
||||
if (clone_flags & CLONE_FS) {
|
||||
/* tsk->fs is already what we want */
|
||||
read_seqlock_excl(&fs->seq);
|
||||
@@ -1633,7 +1651,7 @@ static int copy_fs(u64 clone_flags, struct task_struct *tsk)
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
return 0;
|
||||
}
|
||||
tsk->fs = copy_fs_struct(fs);
|
||||
tsk->real_fs = tsk->fs = copy_fs_struct(fs);
|
||||
if (!tsk->fs)
|
||||
return -ENOMEM;
|
||||
return 0;
|
||||
@@ -2277,7 +2295,7 @@ __latent_entropy struct task_struct *copy_process(
|
||||
retval = copy_files(clone_flags, p, args->no_files);
|
||||
if (retval)
|
||||
goto bad_fork_cleanup_semundo;
|
||||
retval = copy_fs(clone_flags, p);
|
||||
retval = copy_fs(clone_flags, p, args->umh);
|
||||
if (retval)
|
||||
goto bad_fork_cleanup_files;
|
||||
retval = copy_sighand(clone_flags, p);
|
||||
@@ -2819,6 +2837,7 @@ pid_t user_mode_thread(int (*fn)(void *), void *arg, unsigned long flags)
|
||||
.exit_signal = (flags & CSIGNAL),
|
||||
.fn = fn,
|
||||
.fn_arg = arg,
|
||||
.umh = 1,
|
||||
};
|
||||
|
||||
return kernel_clone(&args);
|
||||
@@ -3216,7 +3235,7 @@ static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp
|
||||
*/
|
||||
int ksys_unshare(unsigned long unshare_flags)
|
||||
{
|
||||
struct fs_struct *fs, *new_fs = NULL;
|
||||
struct fs_struct *new_fs = NULL;
|
||||
struct files_struct *new_fd = NULL;
|
||||
struct cred *new_cred = NULL;
|
||||
struct nsproxy *new_nsproxy = NULL;
|
||||
@@ -3247,6 +3266,10 @@ int ksys_unshare(unsigned long unshare_flags)
|
||||
if (unshare_flags & CLONE_NEWNS)
|
||||
unshare_flags |= CLONE_FS;
|
||||
|
||||
/* No unsharing with overriden fs state */
|
||||
VFS_WARN_ON_ONCE(unshare_flags & (CLONE_NEWNS | CLONE_FS) &&
|
||||
current->fs != current->real_fs);
|
||||
|
||||
err = check_unshare_flags(unshare_flags);
|
||||
if (err)
|
||||
goto bad_unshare_out;
|
||||
@@ -3294,23 +3317,13 @@ int ksys_unshare(unsigned long unshare_flags)
|
||||
new_nsproxy = NULL;
|
||||
}
|
||||
|
||||
task_lock(current);
|
||||
if (new_fs)
|
||||
new_fs = switch_fs_struct(new_fs);
|
||||
|
||||
if (new_fs) {
|
||||
fs = current->fs;
|
||||
read_seqlock_excl(&fs->seq);
|
||||
current->fs = new_fs;
|
||||
if (--fs->users)
|
||||
new_fs = NULL;
|
||||
else
|
||||
new_fs = fs;
|
||||
read_sequnlock_excl(&fs->seq);
|
||||
}
|
||||
|
||||
if (new_fd)
|
||||
if (new_fd) {
|
||||
guard(task_lock)(current);
|
||||
swap(current->files, new_fd);
|
||||
|
||||
task_unlock(current);
|
||||
}
|
||||
|
||||
if (new_cred) {
|
||||
/* Install the new user namespace */
|
||||
|
||||
@@ -186,7 +186,7 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
|
||||
ret = kcmp_ptr(task1->files, task2->files, KCMP_FILES);
|
||||
break;
|
||||
case KCMP_FS:
|
||||
ret = kcmp_ptr(task1->fs, task2->fs, KCMP_FS);
|
||||
ret = kcmp_ptr(task1->real_fs, task2->real_fs, KCMP_FS);
|
||||
break;
|
||||
case KCMP_SIGHAND:
|
||||
ret = kcmp_ptr(task1->sighand, task2->sighand, KCMP_SIGHAND);
|
||||
|
||||
@@ -71,10 +71,8 @@ static int call_usermodehelper_exec_async(void *data)
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
/*
|
||||
* Initial kernel threads share ther FS with init, in order to
|
||||
* get the init root directory. But we've now created a new
|
||||
* thread that is going to execve a user process and has its own
|
||||
* 'struct fs_struct'. Reset umask to the default.
|
||||
* Usermodehelper threads get a copy of userspace init's
|
||||
* fs_struct. Reset umask to the default.
|
||||
*/
|
||||
current->fs->umask = 0022;
|
||||
|
||||
|
||||
@@ -1197,17 +1197,12 @@ static struct sock *unix_find_bsd(struct sockaddr_un *sunaddr, int addr_len,
|
||||
unix_mkname_bsd(sunaddr, addr_len);
|
||||
|
||||
if (flags & SOCK_COREDUMP) {
|
||||
struct path root;
|
||||
|
||||
task_lock(&init_task);
|
||||
get_fs_root(init_task.fs, &root);
|
||||
task_unlock(&init_task);
|
||||
|
||||
scoped_with_kernel_creds()
|
||||
err = vfs_path_lookup(root.dentry, root.mnt, sunaddr->sun_path,
|
||||
LOOKUP_BENEATH | LOOKUP_NO_SYMLINKS |
|
||||
LOOKUP_NO_MAGICLINKS, &path);
|
||||
path_put(&root);
|
||||
scoped_with_init_fs() {
|
||||
scoped_with_kernel_creds()
|
||||
err = kern_path(sunaddr->sun_path,
|
||||
LOOKUP_NO_SYMLINKS |
|
||||
LOOKUP_NO_MAGICLINKS, &path);
|
||||
}
|
||||
if (err)
|
||||
goto fail;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user