diff options
author | Jann Horn <jann@thejh.net> | 2016-03-22 17:25:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-03-22 18:36:02 -0400 |
commit | 378c6520e7d29280f400ef2ceaf155c86f05a71a (patch) | |
tree | d16654900c79dd882ece48eaaeda3afcffd10e5a | |
parent | 1333ab03150478df8d6f5673a91df1e50dc6ab97 (diff) |
fs/coredump: prevent fsuid=0 dumps into user-controlled directories
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>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | arch/um/drivers/mconsole_kern.c | 2 | ||||
-rw-r--r-- | fs/coredump.c | 30 | ||||
-rw-r--r-- | fs/fhandle.c | 2 | ||||
-rw-r--r-- | fs/open.c | 6 | ||||
-rw-r--r-- | include/linux/fs.h | 2 | ||||
-rw-r--r-- | kernel/sysctl_binary.c | 2 |
6 files changed, 32 insertions, 12 deletions
diff --git a/arch/um/drivers/mconsole_kern.c b/arch/um/drivers/mconsole_kern.c index b821b13d343a..8a6b57108ac2 100644 --- a/arch/um/drivers/mconsole_kern.c +++ b/arch/um/drivers/mconsole_kern.c | |||
@@ -133,7 +133,7 @@ void mconsole_proc(struct mc_request *req) | |||
133 | ptr += strlen("proc"); | 133 | ptr += strlen("proc"); |
134 | ptr = skip_spaces(ptr); | 134 | ptr = skip_spaces(ptr); |
135 | 135 | ||
136 | file = file_open_root(mnt->mnt_root, mnt, ptr, O_RDONLY); | 136 | file = file_open_root(mnt->mnt_root, mnt, ptr, O_RDONLY, 0); |
137 | if (IS_ERR(file)) { | 137 | if (IS_ERR(file)) { |
138 | mconsole_reply(req, "Failed to open file", 1, 0); | 138 | mconsole_reply(req, "Failed to open file", 1, 0); |
139 | printk(KERN_ERR "open /proc/%s: %ld\n", ptr, PTR_ERR(file)); | 139 | printk(KERN_ERR "open /proc/%s: %ld\n", ptr, PTR_ERR(file)); |
diff --git a/fs/coredump.c b/fs/coredump.c index 9ea87e9fdccf..47c32c3bfa1d 100644 --- a/fs/coredump.c +++ b/fs/coredump.c | |||
@@ -32,6 +32,9 @@ | |||
32 | #include <linux/pipe_fs_i.h> | 32 | #include <linux/pipe_fs_i.h> |
33 | #include <linux/oom.h> | 33 | #include <linux/oom.h> |
34 | #include <linux/compat.h> | 34 | #include <linux/compat.h> |
35 | #include <linux/sched.h> | ||
36 | #include <linux/fs.h> | ||
37 | #include <linux/path.h> | ||
35 | #include <linux/timekeeping.h> | 38 | #include <linux/timekeeping.h> |
36 | 39 | ||
37 | #include <asm/uaccess.h> | 40 | #include <asm/uaccess.h> |
@@ -649,6 +652,8 @@ void do_coredump(const siginfo_t *siginfo) | |||
649 | } | 652 | } |
650 | } else { | 653 | } else { |
651 | struct inode *inode; | 654 | struct inode *inode; |
655 | int open_flags = O_CREAT | O_RDWR | O_NOFOLLOW | | ||
656 | O_LARGEFILE | O_EXCL; | ||
652 | 657 | ||
653 | if (cprm.limit < binfmt->min_coredump) | 658 | if (cprm.limit < binfmt->min_coredump) |
654 | goto fail_unlock; | 659 | goto fail_unlock; |
@@ -687,10 +692,27 @@ void do_coredump(const siginfo_t *siginfo) | |||
687 | * what matters is that at least one of the two processes | 692 | * what matters is that at least one of the two processes |
688 | * writes its coredump successfully, not which one. | 693 | * writes its coredump successfully, not which one. |
689 | */ | 694 | */ |
690 | cprm.file = filp_open(cn.corename, | 695 | if (need_suid_safe) { |
691 | O_CREAT | 2 | O_NOFOLLOW | | 696 | /* |
692 | O_LARGEFILE | O_EXCL, | 697 | * Using user namespaces, normal user tasks can change |
693 | 0600); | 698 | * their current->fs->root to point to arbitrary |
699 | * directories. Since the intention of the "only dump | ||
700 | * with a fully qualified path" rule is to control where | ||
701 | * coredumps may be placed using root privileges, | ||
702 | * current->fs->root must not be used. Instead, use the | ||
703 | * root directory of init_task. | ||
704 | */ | ||
705 | struct path root; | ||
706 | |||
707 | task_lock(&init_task); | ||
708 | get_fs_root(init_task.fs, &root); | ||
709 | task_unlock(&init_task); | ||
710 | cprm.file = file_open_root(root.dentry, root.mnt, | ||
711 | cn.corename, open_flags, 0600); | ||
712 | path_put(&root); | ||
713 | } else { | ||
714 | cprm.file = filp_open(cn.corename, open_flags, 0600); | ||
715 | } | ||
694 | if (IS_ERR(cprm.file)) | 716 | if (IS_ERR(cprm.file)) |
695 | goto fail_unlock; | 717 | goto fail_unlock; |
696 | 718 | ||
diff --git a/fs/fhandle.c b/fs/fhandle.c index d59712dfa3e7..ca3c3dd01789 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c | |||
@@ -228,7 +228,7 @@ long do_handle_open(int mountdirfd, | |||
228 | path_put(&path); | 228 | path_put(&path); |
229 | return fd; | 229 | return fd; |
230 | } | 230 | } |
231 | file = file_open_root(path.dentry, path.mnt, "", open_flag); | 231 | file = file_open_root(path.dentry, path.mnt, "", open_flag, 0); |
232 | if (IS_ERR(file)) { | 232 | if (IS_ERR(file)) { |
233 | put_unused_fd(fd); | 233 | put_unused_fd(fd); |
234 | retval = PTR_ERR(file); | 234 | retval = PTR_ERR(file); |
@@ -992,14 +992,12 @@ struct file *filp_open(const char *filename, int flags, umode_t mode) | |||
992 | EXPORT_SYMBOL(filp_open); | 992 | EXPORT_SYMBOL(filp_open); |
993 | 993 | ||
994 | struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt, | 994 | struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt, |
995 | const char *filename, int flags) | 995 | const char *filename, int flags, umode_t mode) |
996 | { | 996 | { |
997 | struct open_flags op; | 997 | struct open_flags op; |
998 | int err = build_open_flags(flags, 0, &op); | 998 | int err = build_open_flags(flags, mode, &op); |
999 | if (err) | 999 | if (err) |
1000 | return ERR_PTR(err); | 1000 | return ERR_PTR(err); |
1001 | if (flags & O_CREAT) | ||
1002 | return ERR_PTR(-EINVAL); | ||
1003 | return do_file_open_root(dentry, mnt, filename, &op); | 1001 | return do_file_open_root(dentry, mnt, filename, &op); |
1004 | } | 1002 | } |
1005 | EXPORT_SYMBOL(file_open_root); | 1003 | EXPORT_SYMBOL(file_open_root); |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 35d99266ca9a..14a97194b34b 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -2263,7 +2263,7 @@ extern long do_sys_open(int dfd, const char __user *filename, int flags, | |||
2263 | extern struct file *file_open_name(struct filename *, int, umode_t); | 2263 | extern struct file *file_open_name(struct filename *, int, umode_t); |
2264 | extern struct file *filp_open(const char *, int, umode_t); | 2264 | extern struct file *filp_open(const char *, int, umode_t); |
2265 | extern struct file *file_open_root(struct dentry *, struct vfsmount *, | 2265 | extern struct file *file_open_root(struct dentry *, struct vfsmount *, |
2266 | const char *, int); | 2266 | const char *, int, umode_t); |
2267 | extern struct file * dentry_open(const struct path *, int, const struct cred *); | 2267 | extern struct file * dentry_open(const struct path *, int, const struct cred *); |
2268 | extern int filp_close(struct file *, fl_owner_t id); | 2268 | extern int filp_close(struct file *, fl_owner_t id); |
2269 | 2269 | ||
diff --git a/kernel/sysctl_binary.c b/kernel/sysctl_binary.c index 7e7746a42a62..10a1d7dc9313 100644 --- a/kernel/sysctl_binary.c +++ b/kernel/sysctl_binary.c | |||
@@ -1321,7 +1321,7 @@ static ssize_t binary_sysctl(const int *name, int nlen, | |||
1321 | } | 1321 | } |
1322 | 1322 | ||
1323 | mnt = task_active_pid_ns(current)->proc_mnt; | 1323 | mnt = task_active_pid_ns(current)->proc_mnt; |
1324 | file = file_open_root(mnt->mnt_root, mnt, pathname, flags); | 1324 | file = file_open_root(mnt->mnt_root, mnt, pathname, flags, 0); |
1325 | result = PTR_ERR(file); | 1325 | result = PTR_ERR(file); |
1326 | if (IS_ERR(file)) | 1326 | if (IS_ERR(file)) |
1327 | goto out_putname; | 1327 | goto out_putname; |