diff options
author | Linus Torvalds <torvalds@g5.osdl.org> | 2006-11-04 10:06:02 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-11-04 10:06:02 -0800 |
commit | 45c18b0bb579b5c1b89f8c99f1b6ffa4c586ba08 (patch) | |
tree | 2dbd334c763232ce2de46739908054639e5629c8 | |
parent | 80491eb90c750fcd7d13830062f27ae9b7cc5f75 (diff) | |
download | linux-stable-45c18b0bb579b5c1b89f8c99f1b6ffa4c586ba08.tar.gz linux-stable-45c18b0bb579b5c1b89f8c99f1b6ffa4c586ba08.tar.bz2 linux-stable-45c18b0bb579b5c1b89f8c99f1b6ffa4c586ba08.zip |
Fix unlikely (but possible) race condition on task->user access
There's a possible race condition when doing a "switch_uid()" from one
user to another, which could race with another thread doing a signal
allocation and looking at the old thread ->user pointer as it is freed.
This explains an oops reported by Lukasz Trabinski:
http://permalink.gmane.org/gmane.linux.kernel/462241
We fix this by delaying the (reference-counted) freeing of the user
structure until the thread signal handler lock has been released, so
that we know that the signal allocation has either seen the new value or
has properly incremented the reference count of the old one.
Race identified by Oleg Nesterov.
Cc: Lukasz Trabinski <lukasz@wsisiz.edu.pl>
Cc: Oleg Nesterov <oleg@tv-sign.ru>
Cc: Andrew Morton <akpm@osdl.org>
Cc: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r-- | kernel/user.c | 11 |
1 files changed, 11 insertions, 0 deletions
diff --git a/kernel/user.c b/kernel/user.c index 6408c0424291..220e586127a0 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -187,6 +187,17 @@ void switch_uid(struct user_struct *new_user) atomic_dec(&old_user->processes); switch_uid_keyring(new_user); current->user = new_user; + + /* + * We need to synchronize with __sigqueue_alloc() + * doing a get_uid(p->user).. If that saw the old + * user value, we need to wait until it has exited + * its critical region before we can free the old + * structure. + */ + smp_mb(); + spin_unlock_wait(¤t->sighand->siglock); + free_uid(old_user); suid_keys(current); } |