mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Eric W. Biederman](/assets/img/avatar_default.png)
syzbot writes:
> KASAN: use-after-free Read in dput (2)
>
> proc_fill_super: allocate dentry failed
> ==================================================================
> BUG: KASAN: use-after-free in fast_dput fs/dcache.c:727 [inline]
> BUG: KASAN: use-after-free in dput+0x53e/0xdf0 fs/dcache.c:846
> Read of size 4 at addr ffff88808a618cf0 by task syz-executor.0/8426
>
> CPU: 0 PID: 8426 Comm: syz-executor.0 Not tainted 5.6.0-next-20200412-syzkaller #0
> Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
> Call Trace:
> __dump_stack lib/dump_stack.c:77 [inline]
> dump_stack+0x188/0x20d lib/dump_stack.c:118
> print_address_description.constprop.0.cold+0xd3/0x315 mm/kasan/report.c:382
> __kasan_report.cold+0x35/0x4d mm/kasan/report.c:511
> kasan_report+0x33/0x50 mm/kasan/common.c:625
> fast_dput fs/dcache.c:727 [inline]
> dput+0x53e/0xdf0 fs/dcache.c:846
> proc_kill_sb+0x73/0xf0 fs/proc/root.c:195
> deactivate_locked_super+0x8c/0xf0 fs/super.c:335
> vfs_get_super+0x258/0x2d0 fs/super.c:1212
> vfs_get_tree+0x89/0x2f0 fs/super.c:1547
> do_new_mount fs/namespace.c:2813 [inline]
> do_mount+0x1306/0x1b30 fs/namespace.c:3138
> __do_sys_mount fs/namespace.c:3347 [inline]
> __se_sys_mount fs/namespace.c:3324 [inline]
> __x64_sys_mount+0x18f/0x230 fs/namespace.c:3324
> do_syscall_64+0xf6/0x7d0 arch/x86/entry/common.c:295
> entry_SYSCALL_64_after_hwframe+0x49/0xb3
> RIP: 0033:0x45c889
> Code: ad b6 fb ff c3 66 2e 0f 1f 84 00 00 00 00 00 66 90 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 0f 83 7b b6 fb ff c3 66 2e 0f 1f 84 00 00 00 00
> RSP: 002b:00007ffc1930ec48 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
> RAX: ffffffffffffffda RBX: 0000000001324914 RCX: 000000000045c889
> RDX: 0000000020000140 RSI: 0000000020000040 RDI: 0000000000000000
> RBP: 000000000076bf00 R08: 0000000000000000 R09: 0000000000000000
> R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000003
> R13: 0000000000000749 R14: 00000000004ca15a R15: 0000000000000013
Looking at the code now that it the internal mount of proc is no
longer used it is possible to unmount proc. If proc is unmounted
the fields of the pid namespace that were used for filesystem
specific state are not reinitialized.
Which means that proc_self and proc_thread_self can be pointers to
already freed dentries.
The reported user after free appears to be from mounting and
unmounting proc followed by mounting proc again and using error
injection to cause the new root dentry allocation to fail. This in
turn results in proc_kill_sb running with proc_self and
proc_thread_self still retaining their values from the previous mount
of proc. Then calling dput on either proc_self of proc_thread_self
will result in double put. Which KASAN sees as a use after free.
Solve this by always reinitializing the filesystem state stored
in the struct pid_namespace, when proc is unmounted.
Reported-by: syzbot+72868dd424eb66c6b95f@syzkaller.appspotmail.com
Acked-by: Christian Brauner <christian.brauner@ubuntu.com>
Fixes: 69879c01a0
("proc: Remove the now unnecessary internal mount of proc")
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
301 lines
7 KiB
C
301 lines
7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* linux/fs/proc/root.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linus Torvalds
|
|
*
|
|
* proc root directory handling functions
|
|
*/
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/errno.h>
|
|
#include <linux/time.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/stat.h>
|
|
#include <linux/module.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/user_namespace.h>
|
|
#include <linux/fs_context.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/pid_namespace.h>
|
|
#include <linux/fs_parser.h>
|
|
#include <linux/cred.h>
|
|
#include <linux/magic.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "internal.h"
|
|
|
|
struct proc_fs_context {
|
|
struct pid_namespace *pid_ns;
|
|
unsigned int mask;
|
|
int hidepid;
|
|
int gid;
|
|
};
|
|
|
|
enum proc_param {
|
|
Opt_gid,
|
|
Opt_hidepid,
|
|
};
|
|
|
|
static const struct fs_parameter_spec proc_fs_parameters[] = {
|
|
fsparam_u32("gid", Opt_gid),
|
|
fsparam_u32("hidepid", Opt_hidepid),
|
|
{}
|
|
};
|
|
|
|
static int proc_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
|
{
|
|
struct proc_fs_context *ctx = fc->fs_private;
|
|
struct fs_parse_result result;
|
|
int opt;
|
|
|
|
opt = fs_parse(fc, proc_fs_parameters, param, &result);
|
|
if (opt < 0)
|
|
return opt;
|
|
|
|
switch (opt) {
|
|
case Opt_gid:
|
|
ctx->gid = result.uint_32;
|
|
break;
|
|
|
|
case Opt_hidepid:
|
|
ctx->hidepid = result.uint_32;
|
|
if (ctx->hidepid < HIDEPID_OFF ||
|
|
ctx->hidepid > HIDEPID_INVISIBLE)
|
|
return invalfc(fc, "hidepid value must be between 0 and 2.\n");
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx->mask |= 1 << opt;
|
|
return 0;
|
|
}
|
|
|
|
static void proc_apply_options(struct super_block *s,
|
|
struct fs_context *fc,
|
|
struct pid_namespace *pid_ns,
|
|
struct user_namespace *user_ns)
|
|
{
|
|
struct proc_fs_context *ctx = fc->fs_private;
|
|
|
|
if (ctx->mask & (1 << Opt_gid))
|
|
pid_ns->pid_gid = make_kgid(user_ns, ctx->gid);
|
|
if (ctx->mask & (1 << Opt_hidepid))
|
|
pid_ns->hide_pid = ctx->hidepid;
|
|
}
|
|
|
|
static int proc_fill_super(struct super_block *s, struct fs_context *fc)
|
|
{
|
|
struct pid_namespace *pid_ns = get_pid_ns(s->s_fs_info);
|
|
struct inode *root_inode;
|
|
int ret;
|
|
|
|
proc_apply_options(s, fc, pid_ns, current_user_ns());
|
|
|
|
/* User space would break if executables or devices appear on proc */
|
|
s->s_iflags |= SB_I_USERNS_VISIBLE | SB_I_NOEXEC | SB_I_NODEV;
|
|
s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC;
|
|
s->s_blocksize = 1024;
|
|
s->s_blocksize_bits = 10;
|
|
s->s_magic = PROC_SUPER_MAGIC;
|
|
s->s_op = &proc_sops;
|
|
s->s_time_gran = 1;
|
|
|
|
/*
|
|
* procfs isn't actually a stacking filesystem; however, there is
|
|
* too much magic going on inside it to permit stacking things on
|
|
* top of it
|
|
*/
|
|
s->s_stack_depth = FILESYSTEM_MAX_STACK_DEPTH;
|
|
|
|
/* procfs dentries and inodes don't require IO to create */
|
|
s->s_shrink.seeks = 0;
|
|
|
|
pde_get(&proc_root);
|
|
root_inode = proc_get_inode(s, &proc_root);
|
|
if (!root_inode) {
|
|
pr_err("proc_fill_super: get root inode failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
s->s_root = d_make_root(root_inode);
|
|
if (!s->s_root) {
|
|
pr_err("proc_fill_super: allocate dentry failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = proc_setup_self(s);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
return proc_setup_thread_self(s);
|
|
}
|
|
|
|
static int proc_reconfigure(struct fs_context *fc)
|
|
{
|
|
struct super_block *sb = fc->root->d_sb;
|
|
struct pid_namespace *pid = sb->s_fs_info;
|
|
|
|
sync_filesystem(sb);
|
|
|
|
proc_apply_options(sb, fc, pid, current_user_ns());
|
|
return 0;
|
|
}
|
|
|
|
static int proc_get_tree(struct fs_context *fc)
|
|
{
|
|
struct proc_fs_context *ctx = fc->fs_private;
|
|
|
|
return get_tree_keyed(fc, proc_fill_super, ctx->pid_ns);
|
|
}
|
|
|
|
static void proc_fs_context_free(struct fs_context *fc)
|
|
{
|
|
struct proc_fs_context *ctx = fc->fs_private;
|
|
|
|
put_pid_ns(ctx->pid_ns);
|
|
kfree(ctx);
|
|
}
|
|
|
|
static const struct fs_context_operations proc_fs_context_ops = {
|
|
.free = proc_fs_context_free,
|
|
.parse_param = proc_parse_param,
|
|
.get_tree = proc_get_tree,
|
|
.reconfigure = proc_reconfigure,
|
|
};
|
|
|
|
static int proc_init_fs_context(struct fs_context *fc)
|
|
{
|
|
struct proc_fs_context *ctx;
|
|
|
|
ctx = kzalloc(sizeof(struct proc_fs_context), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
ctx->pid_ns = get_pid_ns(task_active_pid_ns(current));
|
|
put_user_ns(fc->user_ns);
|
|
fc->user_ns = get_user_ns(ctx->pid_ns->user_ns);
|
|
fc->fs_private = ctx;
|
|
fc->ops = &proc_fs_context_ops;
|
|
return 0;
|
|
}
|
|
|
|
static void proc_kill_sb(struct super_block *sb)
|
|
{
|
|
struct pid_namespace *ns;
|
|
|
|
ns = (struct pid_namespace *)sb->s_fs_info;
|
|
if (ns->proc_self)
|
|
dput(ns->proc_self);
|
|
if (ns->proc_thread_self)
|
|
dput(ns->proc_thread_self);
|
|
kill_anon_super(sb);
|
|
|
|
/* Make the pid namespace safe for the next mount of proc */
|
|
ns->proc_self = NULL;
|
|
ns->proc_thread_self = NULL;
|
|
ns->pid_gid = GLOBAL_ROOT_GID;
|
|
ns->hide_pid = 0;
|
|
|
|
put_pid_ns(ns);
|
|
}
|
|
|
|
static struct file_system_type proc_fs_type = {
|
|
.name = "proc",
|
|
.init_fs_context = proc_init_fs_context,
|
|
.parameters = proc_fs_parameters,
|
|
.kill_sb = proc_kill_sb,
|
|
.fs_flags = FS_USERNS_MOUNT | FS_DISALLOW_NOTIFY_PERM,
|
|
};
|
|
|
|
void __init proc_root_init(void)
|
|
{
|
|
proc_init_kmemcache();
|
|
set_proc_pid_nlink();
|
|
proc_self_init();
|
|
proc_thread_self_init();
|
|
proc_symlink("mounts", NULL, "self/mounts");
|
|
|
|
proc_net_init();
|
|
proc_mkdir("fs", NULL);
|
|
proc_mkdir("driver", NULL);
|
|
proc_create_mount_point("fs/nfsd"); /* somewhere for the nfsd filesystem to be mounted */
|
|
#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)
|
|
/* just give it a mountpoint */
|
|
proc_create_mount_point("openprom");
|
|
#endif
|
|
proc_tty_init();
|
|
proc_mkdir("bus", NULL);
|
|
proc_sys_init();
|
|
|
|
register_filesystem(&proc_fs_type);
|
|
}
|
|
|
|
static int proc_root_getattr(const struct path *path, struct kstat *stat,
|
|
u32 request_mask, unsigned int query_flags)
|
|
{
|
|
generic_fillattr(d_inode(path->dentry), stat);
|
|
stat->nlink = proc_root.nlink + nr_processes();
|
|
return 0;
|
|
}
|
|
|
|
static struct dentry *proc_root_lookup(struct inode * dir, struct dentry * dentry, unsigned int flags)
|
|
{
|
|
if (!proc_pid_lookup(dentry, flags))
|
|
return NULL;
|
|
|
|
return proc_lookup(dir, dentry, flags);
|
|
}
|
|
|
|
static int proc_root_readdir(struct file *file, struct dir_context *ctx)
|
|
{
|
|
if (ctx->pos < FIRST_PROCESS_ENTRY) {
|
|
int error = proc_readdir(file, ctx);
|
|
if (unlikely(error <= 0))
|
|
return error;
|
|
ctx->pos = FIRST_PROCESS_ENTRY;
|
|
}
|
|
|
|
return proc_pid_readdir(file, ctx);
|
|
}
|
|
|
|
/*
|
|
* The root /proc directory is special, as it has the
|
|
* <pid> directories. Thus we don't use the generic
|
|
* directory handling functions for that..
|
|
*/
|
|
static const struct file_operations proc_root_operations = {
|
|
.read = generic_read_dir,
|
|
.iterate_shared = proc_root_readdir,
|
|
.llseek = generic_file_llseek,
|
|
};
|
|
|
|
/*
|
|
* proc root can do almost nothing..
|
|
*/
|
|
static const struct inode_operations proc_root_inode_operations = {
|
|
.lookup = proc_root_lookup,
|
|
.getattr = proc_root_getattr,
|
|
};
|
|
|
|
/*
|
|
* This is the root "inode" in the /proc tree..
|
|
*/
|
|
struct proc_dir_entry proc_root = {
|
|
.low_ino = PROC_ROOT_INO,
|
|
.namelen = 5,
|
|
.mode = S_IFDIR | S_IRUGO | S_IXUGO,
|
|
.nlink = 2,
|
|
.refcnt = REFCOUNT_INIT(1),
|
|
.proc_iops = &proc_root_inode_operations,
|
|
.proc_dir_ops = &proc_root_operations,
|
|
.parent = &proc_root,
|
|
.subdir = RB_ROOT,
|
|
.name = "/proc",
|
|
};
|