diff options
| author | Andrew Morton <akpm@osdl.org> | 2004-04-20 17:41:53 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2004-04-20 17:41:53 -0700 |
| commit | b7fbe52c11d0c7b535ba75487d7e0913ede3671b (patch) | |
| tree | d6cd35e6eb11b7912cc19faabb4534063a1ebfe4 /security | |
| parent | ce0cbde11f79c665ccc0d4ed01a4dd6976808b6b (diff) | |
[PATCH] compute_creds race
From: Andy Lutomirski <luto@myrealbox.com>
Fixes from me, Olaf Dietsche <olaf+list.linux-kernel@olafdietsche.de>
In fs/exec.c, compute_creds does:
task_lock(current);
if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) {
current->mm->dumpable = 0;
if (must_not_trace_exec(current)
|| atomic_read(¤t->fs->count) > 1
|| atomic_read(¤t->files->count) > 1
|| atomic_read(¤t->sighand->count) > 1) {
if(!capable(CAP_SETUID)) {
bprm->e_uid = current->uid;
bprm->e_gid = current->gid;
}
}
}
current->suid = current->euid = current->fsuid = bprm->e_uid;
current->sgid = current->egid = current->fsgid = bprm->e_gid;
task_unlock(current);
security_bprm_compute_creds(bprm);
I assume the task_lock is to prevent another process (on SMP or preempt)
from ptracing the execing process between the check and the assignment. If
that's the concern then the fact that the lock is dropped before the call
to security_brpm_compute_creds means that, if security_bprm_compute_creds
does anything interesting, there's a race.
For my (nearly complete) caps patch, I obviously need to fix this. But I
think it may be exploitable now. Suppose there are two processes, A (the
malicious code) and B (which uses exec). B starts out unprivileged (A and
B have, e.g., uid and euid = 500).
1. A ptraces B.
2. B calls exec on some setuid-root program.
3. in cap_bprm_set_security, B sets bprm->cap_permitted to the full
set.
4. B gets to compute_creds in exec.c, calls task_lock, and does not
change its uid.
5. B calls task_unlock.
6. A detaches from B (on preempt or SMP).
7. B gets to task_lock in cap_bprm_compute_creds, changes its
capabilities, and returns from compute_creds into load_elf_binary.
8. load_elf_binary calls create_elf_tables (line 852 in 2.6.5-mm1),
which calls cap_bprm_secureexec (through LSM), which returns false (!).
9. exec finishes.
The setuid program is now running with uid=euid=500 but full permitted
capabilities. There are two (or three) ways to effectively get local root
now:
1. IIRC, linux 2.4 doesn't check capabilities in ptrace, so A could
just ptrace B again.
2. LD_PRELOAD.
3. There are probably programs that will misbehave on their own under
these circumstances.
Is there some reason why this is not doable?
The patch renames bprm_compute_creds to bprm_apply_creds and moves all uid
logic into the hook, where the test and the resulting modification can both
happen under task_lock().
This way, out-of-tree LSMs will fail to compile instead of malfunctioning.
It should also make life easier for LSMs and will certainly make it easier
for me to finish the cap patch.
Diffstat (limited to 'security')
| -rw-r--r-- | security/capability.c | 2 | ||||
| -rw-r--r-- | security/commoncap.c | 38 | ||||
| -rw-r--r-- | security/dummy.c | 31 | ||||
| -rw-r--r-- | security/root_plug.c | 2 | ||||
| -rw-r--r-- | security/selinux/hooks.c | 8 |
5 files changed, 57 insertions, 24 deletions
diff --git a/security/capability.c b/security/capability.c index ba7daa4592dd..f1e81ba6b79d 100644 --- a/security/capability.c +++ b/security/capability.c @@ -35,7 +35,7 @@ static struct security_operations capability_ops = { .netlink_send = cap_netlink_send, .netlink_recv = cap_netlink_recv, - .bprm_compute_creds = cap_bprm_compute_creds, + .bprm_apply_creds = cap_bprm_apply_creds, .bprm_set_security = cap_bprm_set_security, .bprm_secureexec = cap_bprm_secureexec, diff --git a/security/commoncap.c b/security/commoncap.c index e902b60ecc30..07265810c353 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -115,13 +115,15 @@ int cap_bprm_set_security (struct linux_binprm *bprm) return 0; } -/* Copied from fs/exec.c */ static inline int must_not_trace_exec (struct task_struct *p) { - return (p->ptrace & PT_PTRACED) && !(p->ptrace & PT_PTRACE_CAP); + return ((p->ptrace & PT_PTRACED) && !(p->ptrace & PT_PTRACE_CAP)) + || atomic_read(&p->fs->count) > 1 + || atomic_read(&p->files->count) > 1 + || atomic_read(&p->sighand->count) > 1; } -void cap_bprm_compute_creds (struct linux_binprm *bprm) +void cap_bprm_apply_creds (struct linux_binprm *bprm) { /* Derived from fs/exec.c:compute_creds. */ kernel_cap_t new_permitted, working; @@ -132,18 +134,26 @@ void cap_bprm_compute_creds (struct linux_binprm *bprm) new_permitted = cap_combine (new_permitted, working); task_lock(current); + + if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) { + current->mm->dumpable = 0; + + if (must_not_trace_exec(current) && !capable(CAP_SETUID)) { + bprm->e_uid = current->uid; + bprm->e_gid = current->gid; + } + } + + current->suid = current->euid = current->fsuid = bprm->e_uid; + current->sgid = current->egid = current->fsgid = bprm->e_gid; + if (!cap_issubset (new_permitted, current->cap_permitted)) { current->mm->dumpable = 0; - if (must_not_trace_exec (current) - || atomic_read (¤t->fs->count) > 1 - || atomic_read (¤t->files->count) > 1 - || atomic_read (¤t->sighand->count) > 1) { - if (!capable (CAP_SETPCAP)) { - new_permitted = cap_intersect (new_permitted, - current-> - cap_permitted); - } + if (must_not_trace_exec (current) && !capable (CAP_SETPCAP)) { + new_permitted = cap_intersect (new_permitted, + current-> + cap_permitted); } } @@ -315,7 +325,7 @@ int cap_vm_enough_memory(long pages) vm_acct_memory(pages); - /* + /* * Sometimes we want to use more memory than we have */ if (sysctl_overcommit_memory == 1) @@ -377,7 +387,7 @@ EXPORT_SYMBOL(cap_capget); EXPORT_SYMBOL(cap_capset_check); EXPORT_SYMBOL(cap_capset_set); EXPORT_SYMBOL(cap_bprm_set_security); -EXPORT_SYMBOL(cap_bprm_compute_creds); +EXPORT_SYMBOL(cap_bprm_apply_creds); EXPORT_SYMBOL(cap_bprm_secureexec); EXPORT_SYMBOL(cap_inode_setxattr); EXPORT_SYMBOL(cap_inode_removexattr); diff --git a/security/dummy.c b/security/dummy.c index 3bf46b0f5997..c34991da5d6e 100644 --- a/security/dummy.c +++ b/security/dummy.c @@ -26,6 +26,8 @@ #include <net/sock.h> #include <linux/xattr.h> #include <linux/hugetlb.h> +#include <linux/ptrace.h> +#include <linux/file.h> static int dummy_ptrace (struct task_struct *parent, struct task_struct *child) { @@ -116,7 +118,7 @@ static int dummy_vm_enough_memory(long pages) vm_acct_memory(pages); - /* + /* * Sometimes we want to use more memory than we have */ if (sysctl_overcommit_memory == 1) @@ -169,9 +171,30 @@ static void dummy_bprm_free_security (struct linux_binprm *bprm) return; } -static void dummy_bprm_compute_creds (struct linux_binprm *bprm) +static inline int must_not_trace_exec (struct task_struct *p) { - return; + return ((p->ptrace & PT_PTRACED) && !(p->ptrace & PT_PTRACE_CAP)) + || atomic_read(&p->fs->count) > 1 + || atomic_read(&p->files->count) > 1 + || atomic_read(&p->sighand->count) > 1; +} + +static void dummy_bprm_apply_creds (struct linux_binprm *bprm) +{ + task_lock(current); + if (bprm->e_uid != current->uid || bprm->e_gid != current->gid) { + current->mm->dumpable = 0; + + if (must_not_trace_exec(current) && !capable(CAP_SETUID)) { + bprm->e_uid = current->uid; + bprm->e_gid = current->gid; + } + } + + current->suid = current->euid = current->fsuid = bprm->e_uid; + current->sgid = current->egid = current->fsgid = bprm->e_gid; + + task_unlock(current); } static int dummy_bprm_set_security (struct linux_binprm *bprm) @@ -887,7 +910,7 @@ void security_fixup_ops (struct security_operations *ops) set_to_dummy_if_null(ops, vm_enough_memory); set_to_dummy_if_null(ops, bprm_alloc_security); set_to_dummy_if_null(ops, bprm_free_security); - set_to_dummy_if_null(ops, bprm_compute_creds); + set_to_dummy_if_null(ops, bprm_apply_creds); set_to_dummy_if_null(ops, bprm_set_security); set_to_dummy_if_null(ops, bprm_check_security); set_to_dummy_if_null(ops, bprm_secureexec); diff --git a/security/root_plug.c b/security/root_plug.c index c683d6133321..ec8955dcba29 100644 --- a/security/root_plug.c +++ b/security/root_plug.c @@ -90,7 +90,7 @@ static struct security_operations rootplug_security_ops = { .capset_set = cap_capset_set, .capable = cap_capable, - .bprm_compute_creds = cap_bprm_compute_creds, + .bprm_apply_creds = cap_bprm_apply_creds, .bprm_set_security = cap_bprm_set_security, .task_post_setuid = cap_task_post_setuid, diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 5e765cd7535b..358e66212b0f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1745,7 +1745,7 @@ static inline void flush_unauthorized_files(struct files_struct * files) spin_unlock(&files->file_lock); } -static void selinux_bprm_compute_creds(struct linux_binprm *bprm) +static void selinux_bprm_apply_creds(struct linux_binprm *bprm) { struct task_security_struct *tsec, *psec; struct bprm_security_struct *bsec; @@ -1755,7 +1755,7 @@ static void selinux_bprm_compute_creds(struct linux_binprm *bprm) struct rlimit *rlim, *initrlim; int rc, i; - secondary_ops->bprm_compute_creds(bprm); + secondary_ops->bprm_apply_creds(bprm); tsec = current->security; @@ -2560,7 +2560,7 @@ static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim /* Control the ability to change the hard limit (whether lowering or raising it), so that the hard limit can later be used as a safe reset point for the soft limit - upon context transitions. See selinux_bprm_compute_creds. */ + upon context transitions. See selinux_bprm_apply_creds. */ if (old_rlim->rlim_max != new_rlim->rlim_max) return task_has_perm(current, current, PROCESS__SETRLIMIT); @@ -3971,7 +3971,7 @@ struct security_operations selinux_ops = { .bprm_alloc_security = selinux_bprm_alloc_security, .bprm_free_security = selinux_bprm_free_security, - .bprm_compute_creds = selinux_bprm_compute_creds, + .bprm_apply_creds = selinux_bprm_apply_creds, .bprm_set_security = selinux_bprm_set_security, .bprm_check_security = selinux_bprm_check_security, .bprm_secureexec = selinux_bprm_secureexec, |
