CRED: Use RCU to access another task's creds and to release a task's own creds

Use RCU to access another task's creds and to release a task's own creds.
This means that it will be possible for the credentials of a task to be
replaced without another task (a) requiring a full lock to read them, and (b)
seeing deallocated memory.

Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: James Morris <jmorris@namei.org>
Acked-by: Serge Hallyn <serue@us.ibm.com>
Signed-off-by: James Morris <jmorris@namei.org>
This commit is contained in:
David Howells 2008-11-14 10:39:19 +11:00 committed by James Morris
parent 86a264abe5
commit c69e8d9c01
28 changed files with 353 additions and 202 deletions

View file

@ -177,6 +177,11 @@ int next_signal(struct sigpending *pending, sigset_t *mask)
return sig;
}
/*
* allocate a new signal queue record
* - this may be called without locks if and only if t == current, otherwise an
* appopriate lock must be held to protect t's user_struct
*/
static struct sigqueue *__sigqueue_alloc(struct task_struct *t, gfp_t flags,
int override_rlimit)
{
@ -184,11 +189,12 @@ static struct sigqueue *__sigqueue_alloc(struct task_struct *t, gfp_t flags,
struct user_struct *user;
/*
* In order to avoid problems with "switch_user()", we want to make
* sure that the compiler doesn't re-load "t->user"
* We won't get problems with the target's UID changing under us
* because changing it requires RCU be used, and if t != current, the
* caller must be holding the RCU readlock (by way of a spinlock) and
* we use RCU protection here
*/
user = t->cred->user;
barrier();
user = __task_cred(t)->user;
atomic_inc(&user->sigpending);
if (override_rlimit ||
atomic_read(&user->sigpending) <=
@ -562,12 +568,13 @@ static int rm_from_queue(unsigned long mask, struct sigpending *s)
/*
* Bad permissions for sending the signal
* - the caller must hold at least the RCU read lock
*/
static int check_kill_permission(int sig, struct siginfo *info,
struct task_struct *t)
{
const struct cred *cred = current_cred(), *tcred;
struct pid *sid;
uid_t uid, euid;
int error;
if (!valid_signal(sig))
@ -580,10 +587,11 @@ static int check_kill_permission(int sig, struct siginfo *info,
if (error)
return error;
uid = current_uid();
euid = current_euid();
if ((euid ^ t->cred->suid) && (euid ^ t->cred->uid) &&
(uid ^ t->cred->suid) && (uid ^ t->cred->uid) &&
tcred = __task_cred(t);
if ((cred->euid ^ tcred->suid) &&
(cred->euid ^ tcred->uid) &&
(cred->uid ^ tcred->suid) &&
(cred->uid ^ tcred->uid) &&
!capable(CAP_KILL)) {
switch (sig) {
case SIGCONT:
@ -1011,6 +1019,10 @@ struct sighand_struct *lock_task_sighand(struct task_struct *tsk, unsigned long
return sighand;
}
/*
* send signal info to all the members of a group
* - the caller must hold the RCU read lock at least
*/
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
unsigned long flags;
@ -1032,8 +1044,8 @@ int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
/*
* __kill_pgrp_info() sends a signal to a process group: this is what the tty
* control characters do (^C, ^Z etc)
* - the caller must hold at least a readlock on tasklist_lock
*/
int __kill_pgrp_info(int sig, struct siginfo *info, struct pid *pgrp)
{
struct task_struct *p = NULL;
@ -1089,6 +1101,7 @@ int kill_pid_info_as_uid(int sig, struct siginfo *info, struct pid *pid,
{
int ret = -EINVAL;
struct task_struct *p;
const struct cred *pcred;
if (!valid_signal(sig))
return ret;
@ -1099,9 +1112,11 @@ int kill_pid_info_as_uid(int sig, struct siginfo *info, struct pid *pid,
ret = -ESRCH;
goto out_unlock;
}
if ((info == SEND_SIG_NOINFO || (!is_si_special(info) && SI_FROMUSER(info)))
&& (euid != p->cred->suid) && (euid != p->cred->uid)
&& (uid != p->cred->suid) && (uid != p->cred->uid)) {
pcred = __task_cred(p);
if ((info == SEND_SIG_NOINFO ||
(!is_si_special(info) && SI_FROMUSER(info))) &&
euid != pcred->suid && euid != pcred->uid &&
uid != pcred->suid && uid != pcred->uid) {
ret = -EPERM;
goto out_unlock;
}
@ -1372,10 +1387,9 @@ int do_notify_parent(struct task_struct *tsk, int sig)
*/
rcu_read_lock();
info.si_pid = task_pid_nr_ns(tsk, tsk->parent->nsproxy->pid_ns);
info.si_uid = __task_cred(tsk)->uid;
rcu_read_unlock();
info.si_uid = tsk->cred->uid;
thread_group_cputime(tsk, &cputime);
info.si_utime = cputime_to_jiffies(cputime.utime);
info.si_stime = cputime_to_jiffies(cputime.stime);
@ -1443,10 +1457,9 @@ static void do_notify_parent_cldstop(struct task_struct *tsk, int why)
*/
rcu_read_lock();
info.si_pid = task_pid_nr_ns(tsk, tsk->parent->nsproxy->pid_ns);
info.si_uid = __task_cred(tsk)->uid;
rcu_read_unlock();
info.si_uid = tsk->cred->uid;
info.si_utime = cputime_to_clock_t(tsk->utime);
info.si_stime = cputime_to_clock_t(tsk->stime);
@ -1713,7 +1726,7 @@ static int ptrace_signal(int signr, siginfo_t *info,
info->si_errno = 0;
info->si_code = SI_USER;
info->si_pid = task_pid_vnr(current->parent);
info->si_uid = current->parent->cred->uid;
info->si_uid = task_uid(current->parent);
}
/* If the (new) signal is now blocked, requeue it. */