Merge patch series "fs,kthread: start all kthreads in nullfs"

Christian Brauner <brauner@kernel.org> says:

Summary:

* all kthreads are isolated in a separate SB_KERNMOUNT of nullfs.
  -> no lookup of anything else, no mounting on top of it, completely
  isolated.
* init has a separate fs_struct from all kthreads
* scoped_with_init_fs() allows a kthread to temporarily assume init's
  fs_struct for filesystem operations.

So this is a bit of a crazy series. When the kernel is started it
roughly goes like this:

init_task
==> create pid 1 (systemd etc.)
==> pid 2 (kthreadd)

After this point all kthreads and PID 1 share the same filesystem state.
That obviously already came up when we discussed pivot_root() as this
allows pivot_root() to rewrite the fs_struct of all kthreads.

This rewriting is really weird and mostly done so kthread can use init's
filesystem state when they would like to. But this really should be
discouraged. The rewriting should also stop completely. I worked a bit
to get rid of it in a more fundamental way. Is it crazy? Yes. Is it
likely broken? Yes. Does it at least boot? Yes.

Instead of sharing fs_struct between kernel threads and pid 1, pid 1
get's a completely separate fs_struct. All kthreads continue sharing
init_fs as before and pid 1's fs_struct is isolated from kthread's
filesystem state. IOW, userspace init cannot affect kthreads filesystem
state anymore and kthreads cannot affect userspace's filesystem state
anymore - without explicit opt-in.

All kthreads are anchored in a kernel internal mount of nullfs that
cannot be mounted on and that cannot be used to follow other mounts.
It's a completely private mount that insulates kthreads.

This series makes performing mountains of filesystem work such as path
lookup and file opening and so on from kthreads hard - painfully so. I
think this is a benefit because it takes the idea of just offloading
_security sensitive_ operations in init's filesystem state and
running random binaries or opening and creating files to kthreads
difficult behind the shed... And imho it should.

The only remaining kernel tasks that actually share init's filesystem
state are usermodhelpers - as they execute random binaries in the root
filesystem. Another concept we should really show the back of the shed.

This gives a lot stronger guarantees than what we have now. This also
makes path lookup from kthreads fail by default. IOW, it won't be
possible anymore to just lookup random stuff in init's filesytem state
without explicitly opting in to that.

The places that need to perform lookup in init's filesystem state may
use scoped_with_init_fs() which will temporarily override the caller's
fs_struct with init's fs_struct.

We now also warn and notice when pid 1 simply stops sharing filesystem
state with us, i.e., abandons it's userspace_init_fs.

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 userspace_init_fs to nullfs. Every kworker
that does lookups after this point will fail. Every usermodehelper call
will fail. This is a lot stronger but I wouldn't know what it means for
pid 1 to simply stop sharing its fs state with the kernel. Clearly it
wanted to separate so cut all ties.

I've went through the kernel and looked at hopefully everything that
does path lookup from kthreads (workqueues, ...).

TL;DR:

==== PID 1 (systemd) ====

  root@localhost:~# stat --file-system /proc/1/root
    File: "/proc/1/root"
      ID: e3cb00dd533cd3d7 Namelen: 255     Type: ext2/ext3

  root@localhost:~# cat /proc/1/mountinfo | wc -l
  30

==== PID 2 (kthreadd) ====

  root@localhost:~# stat --file-system /proc/2/root
    File: "/proc/2/root"
      ID: 200000000 Namelen: 255     Type: nullfs

  root@localhost:~# cat /proc/2/mountinfo | wc -l
  0

* patches from https://patch.msgid.link/20260601-work-kthread-nullfs-v4-0-77ee053060e0@kernel.org: (25 commits)
  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
  rnbd: use scoped_with_init_fs() for block device open
  ...

Link: https://patch.msgid.link/20260601-work-kthread-nullfs-v4-0-77ee053060e0@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-06-04 11:14:28 +02:00
commit 1b8a585da1
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
29 changed files with 279 additions and 104 deletions

View File

@ -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);

View File

@ -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",

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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(&current->fs->seq);
current->fs->users++;
read_sequnlock_excl(&current->fs->seq);
userspace_init_fs = current->fs;
}

View File

@ -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);

View 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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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",

View File

@ -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>
@ -5867,7 +5868,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;

View File

@ -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);

View File

@ -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 */

View File

@ -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

View File

@ -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: */

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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.
*/

View File

@ -1613,9 +1613,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);
@ -1628,7 +1646,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;
@ -2276,7 +2294,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);
@ -2818,6 +2836,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);
@ -3215,7 +3234,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;
@ -3246,6 +3265,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;
@ -3293,23 +3316,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 */

View File

@ -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);

View File

@ -71,10 +71,8 @@ static int call_usermodehelper_exec_async(void *data)
spin_unlock_irq(&current->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;

View File

@ -1196,17 +1196,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 {