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.