|
Chuck Ebbert |
fd554cc |
From: David Howells <dhowells@redhat.com>
|
|
Chuck Ebbert |
fd554cc |
Date: Thu, 29 Jul 2010 11:45:49 +0000 (+0100)
|
|
Chuck Ebbert |
fd554cc |
Subject: CRED: Fix get_task_cred() and task_state() to not resurrect dead credentials
|
|
Chuck Ebbert |
fd554cc |
X-Git-Tag: v2.6.35~11
|
|
Chuck Ebbert |
fd554cc |
X-Git-Url: http://git.kernel.org/?p=linux%2Fkernel%2Fgit%2Ftorvalds%2Flinux-2.6.git;a=commitdiff_plain;h=de09a9771a5346029f4d11e4ac886be7f9b
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
CRED: Fix get_task_cred() and task_state() to not resurrect dead credentials
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
It's possible for get_task_cred() as it currently stands to 'corrupt' a set of
|
|
Chuck Ebbert |
fd554cc |
credentials by incrementing their usage count after their replacement by the
|
|
Chuck Ebbert |
fd554cc |
task being accessed.
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
What happens is that get_task_cred() can race with commit_creds():
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
TASK_1 TASK_2 RCU_CLEANER
|
|
Chuck Ebbert |
fd554cc |
-->get_task_cred(TASK_2)
|
|
Chuck Ebbert |
fd554cc |
rcu_read_lock()
|
|
Chuck Ebbert |
fd554cc |
__cred = __task_cred(TASK_2)
|
|
Chuck Ebbert |
fd554cc |
-->commit_creds()
|
|
Chuck Ebbert |
fd554cc |
old_cred = TASK_2->real_cred
|
|
Chuck Ebbert |
fd554cc |
TASK_2->real_cred = ...
|
|
Chuck Ebbert |
fd554cc |
put_cred(old_cred)
|
|
Chuck Ebbert |
fd554cc |
call_rcu(old_cred)
|
|
Chuck Ebbert |
fd554cc |
[__cred->usage == 0]
|
|
Chuck Ebbert |
fd554cc |
get_cred(__cred)
|
|
Chuck Ebbert |
fd554cc |
[__cred->usage == 1]
|
|
Chuck Ebbert |
fd554cc |
rcu_read_unlock()
|
|
Chuck Ebbert |
fd554cc |
-->put_cred_rcu()
|
|
Chuck Ebbert |
fd554cc |
[__cred->usage == 1]
|
|
Chuck Ebbert |
fd554cc |
panic()
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
However, since a tasks credentials are generally not changed very often, we can
|
|
Chuck Ebbert |
fd554cc |
reasonably make use of a loop involving reading the creds pointer and using
|
|
Chuck Ebbert |
fd554cc |
atomic_inc_not_zero() to attempt to increment it if it hasn't already hit zero.
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
If successful, we can safely return the credentials in the knowledge that, even
|
|
Chuck Ebbert |
fd554cc |
if the task we're accessing has released them, they haven't gone to the RCU
|
|
Chuck Ebbert |
fd554cc |
cleanup code.
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
We then change task_state() in procfs to use get_task_cred() rather than
|
|
Chuck Ebbert |
fd554cc |
calling get_cred() on the result of __task_cred(), as that suffers from the
|
|
Chuck Ebbert |
fd554cc |
same problem.
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
Without this change, a BUG_ON in __put_cred() or in put_cred_rcu() can be
|
|
Chuck Ebbert |
fd554cc |
tripped when it is noticed that the usage count is not zero as it ought to be,
|
|
Chuck Ebbert |
fd554cc |
for example:
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
kernel BUG at kernel/cred.c:168!
|
|
Chuck Ebbert |
fd554cc |
invalid opcode: 0000 [#1] SMP
|
|
Chuck Ebbert |
fd554cc |
last sysfs file: /sys/kernel/mm/ksm/run
|
|
Chuck Ebbert |
fd554cc |
CPU 0
|
|
Chuck Ebbert |
fd554cc |
Pid: 2436, comm: master Not tainted 2.6.33.3-85.fc13.x86_64 #1 0HR330/OptiPlex
|
|
Chuck Ebbert |
fd554cc |
745
|
|
Chuck Ebbert |
fd554cc |
RIP: 0010:[<ffffffff81069881>] [<ffffffff81069881>] __put_cred+0xc/0x45
|
|
Chuck Ebbert |
fd554cc |
RSP: 0018:ffff88019e7e9eb8 EFLAGS: 00010202
|
|
Chuck Ebbert |
fd554cc |
RAX: 0000000000000001 RBX: ffff880161514480 RCX: 00000000ffffffff
|
|
Chuck Ebbert |
fd554cc |
RDX: 00000000ffffffff RSI: ffff880140c690c0 RDI: ffff880140c690c0
|
|
Chuck Ebbert |
fd554cc |
RBP: ffff88019e7e9eb8 R08: 00000000000000d0 R09: 0000000000000000
|
|
Chuck Ebbert |
fd554cc |
R10: 0000000000000001 R11: 0000000000000040 R12: ffff880140c690c0
|
|
Chuck Ebbert |
fd554cc |
R13: ffff88019e77aea0 R14: 00007fff336b0a5c R15: 0000000000000001
|
|
Chuck Ebbert |
fd554cc |
FS: 00007f12f50d97c0(0000) GS:ffff880007400000(0000) knlGS:0000000000000000
|
|
Chuck Ebbert |
fd554cc |
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
|
|
Chuck Ebbert |
fd554cc |
CR2: 00007f8f461bc000 CR3: 00000001b26ce000 CR4: 00000000000006f0
|
|
Chuck Ebbert |
fd554cc |
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
|
|
Chuck Ebbert |
fd554cc |
DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
|
|
Chuck Ebbert |
fd554cc |
Process master (pid: 2436, threadinfo ffff88019e7e8000, task ffff88019e77aea0)
|
|
Chuck Ebbert |
fd554cc |
Stack:
|
|
Chuck Ebbert |
fd554cc |
ffff88019e7e9ec8 ffffffff810698cd ffff88019e7e9ef8 ffffffff81069b45
|
|
Chuck Ebbert |
fd554cc |
<0> ffff880161514180 ffff880161514480 ffff880161514180 0000000000000000
|
|
Chuck Ebbert |
fd554cc |
<0> ffff88019e7e9f28 ffffffff8106aace 0000000000000001 0000000000000246
|
|
Chuck Ebbert |
fd554cc |
Call Trace:
|
|
Chuck Ebbert |
fd554cc |
[<ffffffff810698cd>] put_cred+0x13/0x15
|
|
Chuck Ebbert |
fd554cc |
[<ffffffff81069b45>] commit_creds+0x16b/0x175
|
|
Chuck Ebbert |
fd554cc |
[<ffffffff8106aace>] set_current_groups+0x47/0x4e
|
|
Chuck Ebbert |
fd554cc |
[<ffffffff8106ac89>] sys_setgroups+0xf6/0x105
|
|
Chuck Ebbert |
fd554cc |
[<ffffffff81009b02>] system_call_fastpath+0x16/0x1b
|
|
Chuck Ebbert |
fd554cc |
Code: 48 8d 71 ff e8 7e 4e 15 00 85 c0 78 0b 8b 75 ec 48 89 df e8 ef 4a 15 00
|
|
Chuck Ebbert |
fd554cc |
48 83 c4 18 5b c9 c3 55 8b 07 8b 07 48 89 e5 85 c0 74 04 <0f> 0b eb fe 65 48 8b
|
|
Chuck Ebbert |
fd554cc |
04 25 00 cc 00 00 48 3b b8 58 04 00 00 75
|
|
Chuck Ebbert |
fd554cc |
RIP [<ffffffff81069881>] __put_cred+0xc/0x45
|
|
Chuck Ebbert |
fd554cc |
RSP <ffff88019e7e9eb8>
|
|
Chuck Ebbert |
fd554cc |
---[ end trace df391256a100ebdd ]---
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
Signed-off-by: David Howells <dhowells@redhat.com>
|
|
Chuck Ebbert |
fd554cc |
Acked-by: Jiri Olsa <jolsa@redhat.com>
|
|
Chuck Ebbert |
fd554cc |
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
Chuck Ebbert |
fd554cc |
---
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
diff --git a/fs/proc/array.c b/fs/proc/array.c
|
|
Chuck Ebbert |
fd554cc |
index 9b58d38..fff6572 100644
|
|
Chuck Ebbert |
fd554cc |
--- a/fs/proc/array.c
|
|
Chuck Ebbert |
fd554cc |
+++ b/fs/proc/array.c
|
|
Chuck Ebbert |
fd554cc |
@@ -176,7 +176,7 @@ static inline void task_state(struct seq_file *m, struct pid_namespace *ns,
|
|
Chuck Ebbert |
fd554cc |
if (tracer)
|
|
Chuck Ebbert |
fd554cc |
tpid = task_pid_nr_ns(tracer, ns);
|
|
Chuck Ebbert |
fd554cc |
}
|
|
Chuck Ebbert |
fd554cc |
- cred = get_cred((struct cred *) __task_cred(p));
|
|
Chuck Ebbert |
fd554cc |
+ cred = get_task_cred(p);
|
|
Chuck Ebbert |
fd554cc |
seq_printf(m,
|
|
Chuck Ebbert |
fd554cc |
"State:\t%s\n"
|
|
Chuck Ebbert |
fd554cc |
"Tgid:\t%d\n"
|
|
Chuck Ebbert |
fd554cc |
diff --git a/include/linux/cred.h b/include/linux/cred.h
|
|
Chuck Ebbert |
fd554cc |
index 75c0fa8..ce40cbc 100644
|
|
Chuck Ebbert |
fd554cc |
--- a/include/linux/cred.h
|
|
Chuck Ebbert |
fd554cc |
+++ b/include/linux/cred.h
|
|
Chuck Ebbert |
fd554cc |
@@ -153,6 +153,7 @@ struct cred {
|
|
Chuck Ebbert |
fd554cc |
extern void __put_cred(struct cred *);
|
|
Chuck Ebbert |
fd554cc |
extern void exit_creds(struct task_struct *);
|
|
Chuck Ebbert |
fd554cc |
extern int copy_creds(struct task_struct *, unsigned long);
|
|
Chuck Ebbert |
fd554cc |
+extern const struct cred *get_task_cred(struct task_struct *);
|
|
Chuck Ebbert |
fd554cc |
extern struct cred *cred_alloc_blank(void);
|
|
Chuck Ebbert |
fd554cc |
extern struct cred *prepare_creds(void);
|
|
Chuck Ebbert |
fd554cc |
extern struct cred *prepare_exec_creds(void);
|
|
Chuck Ebbert |
fd554cc |
@@ -282,26 +283,6 @@ static inline void put_cred(const struct cred *_cred)
|
|
Chuck Ebbert |
fd554cc |
((const struct cred *)(rcu_dereference_check((task)->real_cred, rcu_read_lock_held() || lockdep_tasklist_lock_is_held())))
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
/**
|
|
Chuck Ebbert |
fd554cc |
- * get_task_cred - Get another task's objective credentials
|
|
Chuck Ebbert |
fd554cc |
- * @task: The task to query
|
|
Chuck Ebbert |
fd554cc |
- *
|
|
Chuck Ebbert |
fd554cc |
- * Get the objective credentials of a task, pinning them so that they can't go
|
|
Chuck Ebbert |
fd554cc |
- * away. Accessing a task's credentials directly is not permitted.
|
|
Chuck Ebbert |
fd554cc |
- *
|
|
Chuck Ebbert |
fd554cc |
- * The caller must make sure task doesn't go away, either by holding a ref on
|
|
Chuck Ebbert |
fd554cc |
- * task or by holding tasklist_lock to prevent it from being unlinked.
|
|
Chuck Ebbert |
fd554cc |
- */
|
|
Chuck Ebbert |
fd554cc |
-#define get_task_cred(task) \
|
|
Chuck Ebbert |
fd554cc |
-({ \
|
|
Chuck Ebbert |
fd554cc |
- struct cred *__cred; \
|
|
Chuck Ebbert |
fd554cc |
- rcu_read_lock(); \
|
|
Chuck Ebbert |
fd554cc |
- __cred = (struct cred *) __task_cred((task)); \
|
|
Chuck Ebbert |
fd554cc |
- get_cred(__cred); \
|
|
Chuck Ebbert |
fd554cc |
- rcu_read_unlock(); \
|
|
Chuck Ebbert |
fd554cc |
- __cred; \
|
|
Chuck Ebbert |
fd554cc |
-})
|
|
Chuck Ebbert |
fd554cc |
-
|
|
Chuck Ebbert |
fd554cc |
-/**
|
|
Chuck Ebbert |
fd554cc |
* get_current_cred - Get the current task's subjective credentials
|
|
Chuck Ebbert |
fd554cc |
*
|
|
Chuck Ebbert |
fd554cc |
* Get the subjective credentials of the current task, pinning them so that
|
|
Chuck Ebbert |
fd554cc |
diff --git a/kernel/cred.c b/kernel/cred.c
|
|
Chuck Ebbert |
fd554cc |
index a2d5504..60bc8b1 100644
|
|
Chuck Ebbert |
fd554cc |
--- a/kernel/cred.c
|
|
Chuck Ebbert |
fd554cc |
+++ b/kernel/cred.c
|
|
Chuck Ebbert |
fd554cc |
@@ -209,6 +209,31 @@ void exit_creds(struct task_struct *tsk)
|
|
Chuck Ebbert |
fd554cc |
}
|
|
Chuck Ebbert |
fd554cc |
}
|
|
Chuck Ebbert |
fd554cc |
|
|
Chuck Ebbert |
fd554cc |
+/**
|
|
Chuck Ebbert |
fd554cc |
+ * get_task_cred - Get another task's objective credentials
|
|
Chuck Ebbert |
fd554cc |
+ * @task: The task to query
|
|
Chuck Ebbert |
fd554cc |
+ *
|
|
Chuck Ebbert |
fd554cc |
+ * Get the objective credentials of a task, pinning them so that they can't go
|
|
Chuck Ebbert |
fd554cc |
+ * away. Accessing a task's credentials directly is not permitted.
|
|
Chuck Ebbert |
fd554cc |
+ *
|
|
Chuck Ebbert |
fd554cc |
+ * The caller must also make sure task doesn't get deleted, either by holding a
|
|
Chuck Ebbert |
fd554cc |
+ * ref on task or by holding tasklist_lock to prevent it from being unlinked.
|
|
Chuck Ebbert |
fd554cc |
+ */
|
|
Chuck Ebbert |
fd554cc |
+const struct cred *get_task_cred(struct task_struct *task)
|
|
Chuck Ebbert |
fd554cc |
+{
|
|
Chuck Ebbert |
fd554cc |
+ const struct cred *cred;
|
|
Chuck Ebbert |
fd554cc |
+
|
|
Chuck Ebbert |
fd554cc |
+ rcu_read_lock();
|
|
Chuck Ebbert |
fd554cc |
+
|
|
Chuck Ebbert |
fd554cc |
+ do {
|
|
Chuck Ebbert |
fd554cc |
+ cred = __task_cred((task));
|
|
Chuck Ebbert |
fd554cc |
+ BUG_ON(!cred);
|
|
Chuck Ebbert |
fd554cc |
+ } while (!atomic_inc_not_zero(&((struct cred *)cred)->usage));
|
|
Chuck Ebbert |
fd554cc |
+
|
|
Chuck Ebbert |
fd554cc |
+ rcu_read_unlock();
|
|
Chuck Ebbert |
fd554cc |
+ return cred;
|
|
Chuck Ebbert |
fd554cc |
+}
|
|
Chuck Ebbert |
fd554cc |
+
|
|
Chuck Ebbert |
fd554cc |
/*
|
|
Chuck Ebbert |
fd554cc |
* Allocate blank credentials, such that the credentials can be filled in at a
|
|
Chuck Ebbert |
fd554cc |
* later date without risk of ENOMEM.
|