diff options
author | Jann Horn <jann@thejh.net> | 2016-01-20 18:00:04 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-01-20 20:09:18 -0500 |
commit | caaee6234d05a58c5b4d05e7bf766131b810a657 (patch) | |
tree | 6227530109dd91ab5447fbd2211f09bc636845a7 /fs/proc | |
parent | 3dfb7d8cdbc7ea0c2970450e60818bb3eefbad69 (diff) |
ptrace: use fsuid, fsgid, effective creds for fs access checks
By checking the effective credentials instead of the real UID / permitted
capabilities, ensure that the calling process actually intended to use its
credentials.
To ensure that all ptrace checks use the correct caller credentials (e.g.
in case out-of-tree code or newly added code omits the PTRACE_MODE_*CREDS
flag), use two new flags and require one of them to be set.
The problem was that when a privileged task had temporarily dropped its
privileges, e.g. by calling setreuid(0, user_uid), with the intent to
perform following syscalls with the credentials of a user, it still passed
ptrace access checks that the user would not be able to pass.
While an attacker should not be able to convince the privileged task to
perform a ptrace() syscall, this is a problem because the ptrace access
check is reused for things in procfs.
In particular, the following somewhat interesting procfs entries only rely
on ptrace access checks:
/proc/$pid/stat - uses the check for determining whether pointers
should be visible, useful for bypassing ASLR
/proc/$pid/maps - also useful for bypassing ASLR
/proc/$pid/cwd - useful for gaining access to restricted
directories that contain files with lax permissions, e.g. in
this scenario:
lrwxrwxrwx root root /proc/13020/cwd -> /root/foobar
drwx------ root root /root
drwxr-xr-x root root /root/foobar
-rw-r--r-- root root /root/foobar/secret
Therefore, on a system where a root-owned mode 6755 binary changes its
effective credentials as described and then dumps a user-specified file,
this could be used by an attacker to reveal the memory layout of root's
processes or reveal the contents of files he is not allowed to access
(through /proc/$pid/cwd).
[akpm@linux-foundation.org: fix warning]
Signed-off-by: Jann Horn <jann@thejh.net>
Acked-by: Kees Cook <keescook@chromium.org>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Morris <james.l.morris@oracle.com>
Cc: "Serge E. Hallyn" <serge.hallyn@ubuntu.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Eric W. Biederman" <ebiederm@xmission.com>
Cc: Willy Tarreau <w@1wt.eu>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/proc')
-rw-r--r-- | fs/proc/array.c | 2 | ||||
-rw-r--r-- | fs/proc/base.c | 21 | ||||
-rw-r--r-- | fs/proc/namespaces.c | 4 |
3 files changed, 14 insertions, 13 deletions
diff --git a/fs/proc/array.c b/fs/proc/array.c index d73291f5f0fc..b6c00ce0e29e 100644 --- a/fs/proc/array.c +++ b/fs/proc/array.c | |||
@@ -395,7 +395,7 @@ static int do_task_stat(struct seq_file *m, struct pid_namespace *ns, | |||
395 | 395 | ||
396 | state = *get_task_state(task); | 396 | state = *get_task_state(task); |
397 | vsize = eip = esp = 0; | 397 | vsize = eip = esp = 0; |
398 | permitted = ptrace_may_access(task, PTRACE_MODE_READ | PTRACE_MODE_NOAUDIT); | 398 | permitted = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS | PTRACE_MODE_NOAUDIT); |
399 | mm = get_task_mm(task); | 399 | mm = get_task_mm(task); |
400 | if (mm) { | 400 | if (mm) { |
401 | vsize = task_vsize(mm); | 401 | vsize = task_vsize(mm); |
diff --git a/fs/proc/base.c b/fs/proc/base.c index 2cf5d7e37375..e665097c1da5 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c | |||
@@ -403,7 +403,7 @@ static const struct file_operations proc_pid_cmdline_ops = { | |||
403 | static int proc_pid_auxv(struct seq_file *m, struct pid_namespace *ns, | 403 | static int proc_pid_auxv(struct seq_file *m, struct pid_namespace *ns, |
404 | struct pid *pid, struct task_struct *task) | 404 | struct pid *pid, struct task_struct *task) |
405 | { | 405 | { |
406 | struct mm_struct *mm = mm_access(task, PTRACE_MODE_READ); | 406 | struct mm_struct *mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); |
407 | if (mm && !IS_ERR(mm)) { | 407 | if (mm && !IS_ERR(mm)) { |
408 | unsigned int nwords = 0; | 408 | unsigned int nwords = 0; |
409 | do { | 409 | do { |
@@ -430,7 +430,8 @@ static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns, | |||
430 | 430 | ||
431 | wchan = get_wchan(task); | 431 | wchan = get_wchan(task); |
432 | 432 | ||
433 | if (wchan && ptrace_may_access(task, PTRACE_MODE_READ) && !lookup_symbol_name(wchan, symname)) | 433 | if (wchan && ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS) |
434 | && !lookup_symbol_name(wchan, symname)) | ||
434 | seq_printf(m, "%s", symname); | 435 | seq_printf(m, "%s", symname); |
435 | else | 436 | else |
436 | seq_putc(m, '0'); | 437 | seq_putc(m, '0'); |
@@ -444,7 +445,7 @@ static int lock_trace(struct task_struct *task) | |||
444 | int err = mutex_lock_killable(&task->signal->cred_guard_mutex); | 445 | int err = mutex_lock_killable(&task->signal->cred_guard_mutex); |
445 | if (err) | 446 | if (err) |
446 | return err; | 447 | return err; |
447 | if (!ptrace_may_access(task, PTRACE_MODE_ATTACH)) { | 448 | if (!ptrace_may_access(task, PTRACE_MODE_ATTACH_FSCREDS)) { |
448 | mutex_unlock(&task->signal->cred_guard_mutex); | 449 | mutex_unlock(&task->signal->cred_guard_mutex); |
449 | return -EPERM; | 450 | return -EPERM; |
450 | } | 451 | } |
@@ -697,7 +698,7 @@ static int proc_fd_access_allowed(struct inode *inode) | |||
697 | */ | 698 | */ |
698 | task = get_proc_task(inode); | 699 | task = get_proc_task(inode); |
699 | if (task) { | 700 | if (task) { |
700 | allowed = ptrace_may_access(task, PTRACE_MODE_READ); | 701 | allowed = ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS); |
701 | put_task_struct(task); | 702 | put_task_struct(task); |
702 | } | 703 | } |
703 | return allowed; | 704 | return allowed; |
@@ -732,7 +733,7 @@ static bool has_pid_permissions(struct pid_namespace *pid, | |||
732 | return true; | 733 | return true; |
733 | if (in_group_p(pid->pid_gid)) | 734 | if (in_group_p(pid->pid_gid)) |
734 | return true; | 735 | return true; |
735 | return ptrace_may_access(task, PTRACE_MODE_READ); | 736 | return ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS); |
736 | } | 737 | } |
737 | 738 | ||
738 | 739 | ||
@@ -809,7 +810,7 @@ struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) | |||
809 | struct mm_struct *mm = ERR_PTR(-ESRCH); | 810 | struct mm_struct *mm = ERR_PTR(-ESRCH); |
810 | 811 | ||
811 | if (task) { | 812 | if (task) { |
812 | mm = mm_access(task, mode); | 813 | mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); |
813 | put_task_struct(task); | 814 | put_task_struct(task); |
814 | 815 | ||
815 | if (!IS_ERR_OR_NULL(mm)) { | 816 | if (!IS_ERR_OR_NULL(mm)) { |
@@ -1860,7 +1861,7 @@ static int map_files_d_revalidate(struct dentry *dentry, unsigned int flags) | |||
1860 | if (!task) | 1861 | if (!task) |
1861 | goto out_notask; | 1862 | goto out_notask; |
1862 | 1863 | ||
1863 | mm = mm_access(task, PTRACE_MODE_READ); | 1864 | mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); |
1864 | if (IS_ERR_OR_NULL(mm)) | 1865 | if (IS_ERR_OR_NULL(mm)) |
1865 | goto out; | 1866 | goto out; |
1866 | 1867 | ||
@@ -2013,7 +2014,7 @@ static struct dentry *proc_map_files_lookup(struct inode *dir, | |||
2013 | goto out; | 2014 | goto out; |
2014 | 2015 | ||
2015 | result = -EACCES; | 2016 | result = -EACCES; |
2016 | if (!ptrace_may_access(task, PTRACE_MODE_READ)) | 2017 | if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) |
2017 | goto out_put_task; | 2018 | goto out_put_task; |
2018 | 2019 | ||
2019 | result = -ENOENT; | 2020 | result = -ENOENT; |
@@ -2066,7 +2067,7 @@ proc_map_files_readdir(struct file *file, struct dir_context *ctx) | |||
2066 | goto out; | 2067 | goto out; |
2067 | 2068 | ||
2068 | ret = -EACCES; | 2069 | ret = -EACCES; |
2069 | if (!ptrace_may_access(task, PTRACE_MODE_READ)) | 2070 | if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) |
2070 | goto out_put_task; | 2071 | goto out_put_task; |
2071 | 2072 | ||
2072 | ret = 0; | 2073 | ret = 0; |
@@ -2533,7 +2534,7 @@ static int do_io_accounting(struct task_struct *task, struct seq_file *m, int wh | |||
2533 | if (result) | 2534 | if (result) |
2534 | return result; | 2535 | return result; |
2535 | 2536 | ||
2536 | if (!ptrace_may_access(task, PTRACE_MODE_READ)) { | 2537 | if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { |
2537 | result = -EACCES; | 2538 | result = -EACCES; |
2538 | goto out_unlock; | 2539 | goto out_unlock; |
2539 | } | 2540 | } |
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c index 1dece8781f91..276f12431dbf 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c | |||
@@ -46,7 +46,7 @@ static const char *proc_ns_get_link(struct dentry *dentry, | |||
46 | if (!task) | 46 | if (!task) |
47 | return error; | 47 | return error; |
48 | 48 | ||
49 | if (ptrace_may_access(task, PTRACE_MODE_READ)) { | 49 | if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { |
50 | error = ns_get_path(&ns_path, task, ns_ops); | 50 | error = ns_get_path(&ns_path, task, ns_ops); |
51 | if (!error) | 51 | if (!error) |
52 | nd_jump_link(&ns_path); | 52 | nd_jump_link(&ns_path); |
@@ -67,7 +67,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl | |||
67 | if (!task) | 67 | if (!task) |
68 | return res; | 68 | return res; |
69 | 69 | ||
70 | if (ptrace_may_access(task, PTRACE_MODE_READ)) { | 70 | if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) { |
71 | res = ns_get_name(name, sizeof(name), task, ns_ops); | 71 | res = ns_get_name(name, sizeof(name), task, ns_ops); |
72 | if (res >= 0) | 72 | if (res >= 0) |
73 | res = readlink_copy(buffer, buflen, name); | 73 | res = readlink_copy(buffer, buflen, name); |