fs/coredump: prevent fsuid=0 dumps into user-controlled directories
authorJann Horn <jann@thejh.net>
Tue, 22 Mar 2016 21:25:36 +0000 (14:25 -0700)
committerBen Hutchings <ben@decadent.org.uk>
Sat, 30 Apr 2016 22:05:20 +0000 (00:05 +0200)
commit 378c6520e7d29280f400ef2ceaf155c86f05a71a upstream.

This commit fixes the following security hole affecting systems where
all of the following conditions are fulfilled:

 - The fs.suid_dumpable sysctl is set to 2.
 - The kernel.core_pattern sysctl's value starts with "/". (Systems
   where kernel.core_pattern starts with "|/" are not affected.)
 - Unprivileged user namespace creation is permitted. (This is
   true on Linux >=3.8, but some distributions disallow it by
   default using a distro patch.)

Under these conditions, if a program executes under secure exec rules,
causing it to run with the SUID_DUMP_ROOT flag, then unshares its user
namespace, changes its root directory and crashes, the coredump will be
written using fsuid=0 and a path derived from kernel.core_pattern - but
this path is interpreted relative to the root directory of the process,
allowing the attacker to control where a coredump will be written with
root privileges.

To fix the security issue, always interpret core_pattern for dumps that
are written under SUID_DUMP_ROOT relative to the root directory of init.

Signed-off-by: Jann Horn <jann@thejh.net>
Acked-by: Kees Cook <keescook@chromium.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
[bwh: Backported to 3.2: adjust filename, context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
arch/um/drivers/mconsole_kern.c
fs/exec.c
fs/fhandle.c
fs/open.c
include/linux/fs.h
kernel/sysctl_binary.c

index c70e047..f4a6af2 100644 (file)
@@ -133,7 +133,7 @@ void mconsole_proc(struct mc_request *req)
        ptr += strlen("proc");
        ptr = skip_spaces(ptr);
 
-       file = file_open_root(mnt->mnt_root, mnt, ptr, O_RDONLY);
+       file = file_open_root(mnt->mnt_root, mnt, ptr, O_RDONLY, 0);
        if (IS_ERR(file)) {
                mconsole_reply(req, "Failed to open file", 1, 0);
                goto out;
index aba5e13..a0006d8 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -55,6 +55,9 @@
 #include <linux/pipe_fs_i.h>
 #include <linux/oom.h>
 #include <linux/compat.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/path.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -2246,6 +2249,8 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
                }
        } else {
                struct inode *inode;
+               int open_flags = O_CREAT | O_RDWR | O_NOFOLLOW |
+                                O_LARGEFILE | O_EXCL;
 
                if (cprm.limit < binfmt->min_coredump)
                        goto fail_unlock;
@@ -2284,10 +2289,27 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs)
                 * what matters is that at least one of the two processes
                 * writes its coredump successfully, not which one.
                 */
-               cprm.file = filp_open(cn.corename,
-                                O_CREAT | 2 | O_NOFOLLOW |
-                                O_LARGEFILE | O_EXCL,
-                                0600);
+               if (need_suid_safe) {
+                       /*
+                        * Using user namespaces, normal user tasks can change
+                        * their current->fs->root to point to arbitrary
+                        * directories. Since the intention of the "only dump
+                        * 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.
+                        */
+                       struct path root;
+
+                       task_lock(&init_task);
+                       get_fs_root(init_task.fs, &root);
+                       task_unlock(&init_task);
+                       cprm.file = file_open_root(root.dentry, root.mnt,
+                               cn.corename, open_flags, 0600);
+                       path_put(&root);
+               } else {
+                       cprm.file = filp_open(cn.corename, open_flags, 0600);
+               }
                if (IS_ERR(cprm.file))
                        goto fail_unlock;
 
index c9e18f3..710438a 100644 (file)
@@ -229,7 +229,7 @@ long do_handle_open(int mountdirfd,
                path_put(&path);
                return fd;
        }
-       file = file_open_root(path.dentry, path.mnt, "", open_flag);
+       file = file_open_root(path.dentry, path.mnt, "", open_flag, 0);
        if (IS_ERR(file)) {
                put_unused_fd(fd);
                retval =  PTR_ERR(file);
index b8485d3..ca155d4 100644 (file)
--- a/fs/open.c
+++ b/fs/open.c
@@ -958,12 +958,10 @@ struct file *filp_open(const char *filename, int flags, int mode)
 EXPORT_SYMBOL(filp_open);
 
 struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt,
-                           const char *filename, int flags)
+                           const char *filename, int flags, umode_t mode)
 {
        struct open_flags op;
-       int lookup = build_open_flags(flags, 0, &op);
-       if (flags & O_CREAT)
-               return ERR_PTR(-EINVAL);
+       int lookup = build_open_flags(flags, mode, &op);
        if (!filename && (flags & O_DIRECTORY))
                if (!dentry->d_inode->i_op->lookup)
                        return ERR_PTR(-ENOTDIR);
index dd74385..44e856b 100644 (file)
@@ -2073,7 +2073,7 @@ extern long do_sys_open(int dfd, const char __user *filename, int flags,
                        int mode);
 extern struct file *filp_open(const char *, int, int);
 extern struct file *file_open_root(struct dentry *, struct vfsmount *,
-                                  const char *, int);
+                                  const char *, int, umode_t);
 extern struct file * dentry_open(struct dentry *, struct vfsmount *, int,
                                 const struct cred *);
 extern int filp_close(struct file *, fl_owner_t id);
index 9f9aa32..cd2ea02 100644 (file)
@@ -1346,7 +1346,7 @@ static ssize_t binary_sysctl(const int *name, int nlen,
        }
 
        mnt = current->nsproxy->pid_ns->proc_mnt;
-       file = file_open_root(mnt->mnt_root, mnt, pathname, flags);
+       file = file_open_root(mnt->mnt_root, mnt, pathname, flags, 0);
        result = PTR_ERR(file);
        if (IS_ERR(file))
                goto out_putname;