CRED: Separate per-task-group keyrings from signal_struct
David Howells [Thu, 13 Nov 2008 23:39:20 +0000 (10:39 +1100)]
Separate per-task-group keyrings from signal_struct and dangle their anchor
from the cred struct rather than the signal_struct.

Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: James Morris <jmorris@namei.org>
Signed-off-by: James Morris <jmorris@namei.org>

include/linux/cred.h
include/linux/key.h
include/linux/sched.h
kernel/cred.c
kernel/fork.c
security/keys/process_keys.c
security/keys/request_key.c

index 166ce4d..62b9e53 100644 (file)
@@ -72,6 +72,21 @@ extern int in_group_p(gid_t);
 extern int in_egroup_p(gid_t);
 
 /*
+ * The common credentials for a thread group
+ * - shared by CLONE_THREAD
+ */
+#ifdef CONFIG_KEYS
+struct thread_group_cred {
+       atomic_t        usage;
+       pid_t           tgid;                   /* thread group process ID */
+       spinlock_t      lock;
+       struct key      *session_keyring;       /* keyring inherited over fork */
+       struct key      *process_keyring;       /* keyring private to this process */
+       struct rcu_head rcu;                    /* RCU deletion hook */
+};
+#endif
+
+/*
  * The security context of a task
  *
  * The parts of the context break down into two categories:
@@ -114,6 +129,7 @@ struct cred {
                                         * keys to */
        struct key      *thread_keyring; /* keyring private to this thread */
        struct key      *request_key_auth; /* assumed request_key authority */
+       struct thread_group_cred *tgcred; /* thread-group shared credentials */
 #endif
 #ifdef CONFIG_SECURITY
        void            *security;      /* subjective LSM security */
index df709e1..0836cc8 100644 (file)
@@ -278,9 +278,7 @@ extern ctl_table key_sysctls[];
  */
 extern void switch_uid_keyring(struct user_struct *new_user);
 extern int copy_keys(unsigned long clone_flags, struct task_struct *tsk);
-extern int copy_thread_group_keys(struct task_struct *tsk);
 extern void exit_keys(struct task_struct *tsk);
-extern void exit_thread_group_keys(struct signal_struct *tg);
 extern int suid_keys(struct task_struct *tsk);
 extern int exec_keys(struct task_struct *tsk);
 extern void key_fsuid_changed(struct task_struct *tsk);
@@ -289,8 +287,8 @@ extern void key_init(void);
 
 #define __install_session_keyring(keyring)                             \
 ({                                                                     \
-       struct key *old_session = current->signal->session_keyring;     \
-       current->signal->session_keyring = keyring;                     \
+       struct key *old_session = current->cred->tgcred->session_keyring; \
+       current->cred->tgcred->session_keyring = keyring;               \
        old_session;                                                    \
 })
 
@@ -308,9 +306,7 @@ extern void key_init(void);
 #define switch_uid_keyring(u)          do { } while(0)
 #define __install_session_keyring(k)   ({ NULL; })
 #define copy_keys(f,t)                 0
-#define copy_thread_group_keys(t)      0
 #define exit_keys(t)                   do { } while(0)
-#define exit_thread_group_keys(tg)     do { } while(0)
 #define suid_keys(t)                   do { } while(0)
 #define exec_keys(t)                   do { } while(0)
 #define key_fsuid_changed(t)           do { } while(0)
index 740cf94..2913252 100644 (file)
@@ -571,12 +571,6 @@ struct signal_struct {
         */
        struct rlimit rlim[RLIM_NLIMITS];
 
-       /* keep the process-shared keyrings here so that they do the right
-        * thing in threads created with CLONE_THREAD */
-#ifdef CONFIG_KEYS
-       struct key *session_keyring;    /* keyring inherited over fork */
-       struct key *process_keyring;    /* keyring private to this process */
-#endif
 #ifdef CONFIG_BSD_PROCESS_ACCT
        struct pacct_struct pacct;      /* per-process accounting information */
 #endif
index 833244a..ac73e36 100644 (file)
 #include <linux/security.h>
 
 /*
+ * The common credentials for the initial task's thread group
+ */
+#ifdef CONFIG_KEYS
+static struct thread_group_cred init_tgcred = {
+       .usage  = ATOMIC_INIT(2),
+       .tgid   = 0,
+       .lock   = SPIN_LOCK_UNLOCKED,
+};
+#endif
+
+/*
  * The initial credentials for the initial task
  */
 struct cred init_cred = {
@@ -28,9 +39,42 @@ struct cred init_cred = {
        .cap_bset               = CAP_INIT_BSET,
        .user                   = INIT_USER,
        .group_info             = &init_groups,
+#ifdef CONFIG_KEYS
+       .tgcred                 = &init_tgcred,
+#endif
 };
 
 /*
+ * Dispose of the shared task group credentials
+ */
+#ifdef CONFIG_KEYS
+static void release_tgcred_rcu(struct rcu_head *rcu)
+{
+       struct thread_group_cred *tgcred =
+               container_of(rcu, struct thread_group_cred, rcu);
+
+       BUG_ON(atomic_read(&tgcred->usage) != 0);
+
+       key_put(tgcred->session_keyring);
+       key_put(tgcred->process_keyring);
+       kfree(tgcred);
+}
+#endif
+
+/*
+ * Release a set of thread group credentials.
+ */
+static void release_tgcred(struct cred *cred)
+{
+#ifdef CONFIG_KEYS
+       struct thread_group_cred *tgcred = cred->tgcred;
+
+       if (atomic_dec_and_test(&tgcred->usage))
+               call_rcu(&tgcred->rcu, release_tgcred_rcu);
+#endif
+}
+
+/*
  * The RCU callback to actually dispose of a set of credentials
  */
 static void put_cred_rcu(struct rcu_head *rcu)
@@ -41,6 +85,7 @@ static void put_cred_rcu(struct rcu_head *rcu)
 
        key_put(cred->thread_keyring);
        key_put(cred->request_key_auth);
+       release_tgcred(cred);
        put_group_info(cred->group_info);
        free_uid(cred->user);
        security_cred_free(cred);
@@ -71,12 +116,30 @@ int copy_creds(struct task_struct *p, unsigned long clone_flags)
        if (!pcred)
                return -ENOMEM;
 
+#ifdef CONFIG_KEYS
+       if (clone_flags & CLONE_THREAD) {
+               atomic_inc(&pcred->tgcred->usage);
+       } else {
+               pcred->tgcred = kmalloc(sizeof(struct cred), GFP_KERNEL);
+               if (!pcred->tgcred) {
+                       kfree(pcred);
+                       return -ENOMEM;
+               }
+               atomic_set(&pcred->tgcred->usage, 1);
+               spin_lock_init(&pcred->tgcred->lock);
+               pcred->tgcred->process_keyring = NULL;
+               pcred->tgcred->session_keyring =
+                       key_get(p->cred->tgcred->session_keyring);
+       }
+#endif
+
 #ifdef CONFIG_SECURITY
        pcred->security = NULL;
 #endif
 
        ret = security_cred_alloc(pcred);
        if (ret < 0) {
+               release_tgcred(pcred);
                kfree(pcred);
                return ret;
        }
index c932e28..ded1972 100644 (file)
@@ -802,12 +802,6 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
        if (!sig)
                return -ENOMEM;
 
-       ret = copy_thread_group_keys(tsk);
-       if (ret < 0) {
-               kmem_cache_free(signal_cachep, sig);
-               return ret;
-       }
-
        atomic_set(&sig->count, 1);
        atomic_set(&sig->live, 1);
        init_waitqueue_head(&sig->wait_chldexit);
@@ -852,7 +846,6 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
 void __cleanup_signal(struct signal_struct *sig)
 {
        thread_group_cputime_free(sig);
-       exit_thread_group_keys(sig);
        tty_kref_put(sig->tty);
        kmem_cache_free(signal_cachep, sig);
 }
index 212601e..70ee934 100644 (file)
@@ -189,7 +189,7 @@ int install_process_keyring(void)
 
        might_sleep();
 
-       if (!tsk->signal->process_keyring) {
+       if (!tsk->cred->tgcred->process_keyring) {
                sprintf(buf, "_pid.%u", tsk->tgid);
 
                keyring = keyring_alloc(buf, tsk->cred->uid, tsk->cred->gid, tsk,
@@ -200,12 +200,12 @@ int install_process_keyring(void)
                }
 
                /* attach keyring */
-               spin_lock_irq(&tsk->sighand->siglock);
-               if (!tsk->signal->process_keyring) {
-                       tsk->signal->process_keyring = keyring;
+               spin_lock_irq(&tsk->cred->tgcred->lock);
+               if (!tsk->cred->tgcred->process_keyring) {
+                       tsk->cred->tgcred->process_keyring = keyring;
                        keyring = NULL;
                }
-               spin_unlock_irq(&tsk->sighand->siglock);
+               spin_unlock_irq(&tsk->cred->tgcred->lock);
 
                key_put(keyring);
        }
@@ -235,11 +235,11 @@ static int install_session_keyring(struct key *keyring)
                sprintf(buf, "_ses.%u", tsk->tgid);
 
                flags = KEY_ALLOC_QUOTA_OVERRUN;
-               if (tsk->signal->session_keyring)
+               if (tsk->cred->tgcred->session_keyring)
                        flags = KEY_ALLOC_IN_QUOTA;
 
-               keyring = keyring_alloc(buf, tsk->cred->uid, tsk->cred->gid, tsk,
-                                       flags, NULL);
+               keyring = keyring_alloc(buf, tsk->cred->uid, tsk->cred->gid,
+                                       tsk, flags, NULL);
                if (IS_ERR(keyring))
                        return PTR_ERR(keyring);
        }
@@ -248,10 +248,10 @@ static int install_session_keyring(struct key *keyring)
        }
 
        /* install the keyring */
-       spin_lock_irq(&tsk->sighand->siglock);
-       old = tsk->signal->session_keyring;
-       rcu_assign_pointer(tsk->signal->session_keyring, keyring);
-       spin_unlock_irq(&tsk->sighand->siglock);
+       spin_lock_irq(&tsk->cred->tgcred->lock);
+       old = tsk->cred->tgcred->session_keyring;
+       rcu_assign_pointer(tsk->cred->tgcred->session_keyring, keyring);
+       spin_unlock_irq(&tsk->cred->tgcred->lock);
 
        /* we're using RCU on the pointer, but there's no point synchronising
         * on it if it didn't previously point to anything */
@@ -266,28 +266,6 @@ static int install_session_keyring(struct key *keyring)
 
 /*****************************************************************************/
 /*
- * copy the keys in a thread group for fork without CLONE_THREAD
- */
-int copy_thread_group_keys(struct task_struct *tsk)
-{
-       key_check(current->thread_group->session_keyring);
-       key_check(current->thread_group->process_keyring);
-
-       /* no process keyring yet */
-       tsk->signal->process_keyring = NULL;
-
-       /* same session keyring */
-       rcu_read_lock();
-       tsk->signal->session_keyring =
-               key_get(rcu_dereference(current->signal->session_keyring));
-       rcu_read_unlock();
-
-       return 0;
-
-} /* end copy_thread_group_keys() */
-
-/*****************************************************************************/
-/*
  * copy the keys for fork
  */
 int copy_keys(unsigned long clone_flags, struct task_struct *tsk)
@@ -307,17 +285,6 @@ int copy_keys(unsigned long clone_flags, struct task_struct *tsk)
 
 /*****************************************************************************/
 /*
- * dispose of thread group keys upon thread group destruction
- */
-void exit_thread_group_keys(struct signal_struct *tg)
-{
-       key_put(tg->session_keyring);
-       key_put(tg->process_keyring);
-
-} /* end exit_thread_group_keys() */
-
-/*****************************************************************************/
-/*
  * dispose of per-thread keys upon thread exit
  */
 void exit_keys(struct task_struct *tsk)
@@ -344,10 +311,10 @@ int exec_keys(struct task_struct *tsk)
        key_put(old);
 
        /* discard the process keyring from a newly exec'd task */
-       spin_lock_irq(&tsk->sighand->siglock);
-       old = tsk->signal->process_keyring;
-       tsk->signal->process_keyring = NULL;
-       spin_unlock_irq(&tsk->sighand->siglock);
+       spin_lock_irq(&tsk->cred->tgcred->lock);
+       old = tsk->cred->tgcred->process_keyring;
+       tsk->cred->tgcred->process_keyring = NULL;
+       spin_unlock_irq(&tsk->cred->tgcred->lock);
 
        key_put(old);
 
@@ -452,9 +419,9 @@ key_ref_t search_process_keyrings(struct key_type *type,
        }
 
        /* search the process keyring second */
-       if (context->signal->process_keyring) {
+       if (cred->tgcred->process_keyring) {
                key_ref = keyring_search_aux(
-                       make_key_ref(context->signal->process_keyring, 1),
+                       make_key_ref(cred->tgcred->process_keyring, 1),
                        context, type, description, match);
                if (!IS_ERR(key_ref))
                        goto found;
@@ -473,11 +440,11 @@ key_ref_t search_process_keyrings(struct key_type *type,
        }
 
        /* search the session keyring */
-       if (context->signal->session_keyring) {
+       if (cred->tgcred->session_keyring) {
                rcu_read_lock();
                key_ref = keyring_search_aux(
                        make_key_ref(rcu_dereference(
-                                            context->signal->session_keyring),
+                                            cred->tgcred->session_keyring),
                                     1),
                        context, type, description, match);
                rcu_read_unlock();
@@ -586,11 +553,13 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
 {
        struct request_key_auth *rka;
        struct task_struct *t = current;
-       struct cred *cred = current_cred();
+       struct cred *cred;
        struct key *key;
        key_ref_t key_ref, skey_ref;
        int ret;
 
+try_again:
+       cred = get_current_cred();
        key_ref = ERR_PTR(-ENOKEY);
 
        switch (id) {
@@ -604,6 +573,7 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                                key = ERR_PTR(ret);
                                goto error;
                        }
+                       goto reget_creds;
                }
 
                key = cred->thread_keyring;
@@ -612,7 +582,7 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                break;
 
        case KEY_SPEC_PROCESS_KEYRING:
-               if (!t->signal->process_keyring) {
+               if (!cred->tgcred->process_keyring) {
                        if (!create)
                                goto error;
 
@@ -621,15 +591,16 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                                key = ERR_PTR(ret);
                                goto error;
                        }
+                       goto reget_creds;
                }
 
-               key = t->signal->process_keyring;
+               key = cred->tgcred->process_keyring;
                atomic_inc(&key->usage);
                key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_SESSION_KEYRING:
-               if (!t->signal->session_keyring) {
+               if (!cred->tgcred->session_keyring) {
                        /* always install a session keyring upon access if one
                         * doesn't exist yet */
                        ret = install_user_keyrings();
@@ -639,10 +610,11 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                                cred->user->session_keyring);
                        if (ret < 0)
                                goto error;
+                       goto reget_creds;
                }
 
                rcu_read_lock();
-               key = rcu_dereference(t->signal->session_keyring);
+               key = rcu_dereference(cred->tgcred->session_keyring);
                atomic_inc(&key->usage);
                rcu_read_unlock();
                key_ref = make_key_ref(key, 1);
@@ -758,6 +730,7 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                goto invalid_key;
 
 error:
+       put_cred(cred);
        return key_ref;
 
 invalid_key:
@@ -765,6 +738,12 @@ invalid_key:
        key_ref = ERR_PTR(ret);
        goto error;
 
+       /* if we attempted to install a keyring, then it may have caused new
+        * creds to be installed */
+reget_creds:
+       put_cred(cred);
+       goto try_again;
+
 } /* end lookup_user_key() */
 
 /*****************************************************************************/
@@ -777,6 +756,7 @@ invalid_key:
 long join_session_keyring(const char *name)
 {
        struct task_struct *tsk = current;
+       struct cred *cred = current->cred;
        struct key *keyring;
        long ret;
 
@@ -787,7 +767,7 @@ long join_session_keyring(const char *name)
                        goto error;
 
                rcu_read_lock();
-               ret = rcu_dereference(tsk->signal->session_keyring)->serial;
+               ret = rcu_dereference(cred->tgcred->session_keyring)->serial;
                rcu_read_unlock();
                goto error;
        }
@@ -799,7 +779,7 @@ long join_session_keyring(const char *name)
        keyring = find_keyring_by_name(name, false);
        if (PTR_ERR(keyring) == -ENOKEY) {
                /* not found - try and create a new one */
-               keyring = keyring_alloc(name, tsk->cred->uid, tsk->cred->gid, tsk,
+               keyring = keyring_alloc(name, cred->uid, cred->gid, tsk,
                                        KEY_ALLOC_IN_QUOTA, NULL);
                if (IS_ERR(keyring)) {
                        ret = PTR_ERR(keyring);
index 0488b0a..3d12558 100644 (file)
@@ -66,7 +66,6 @@ static int call_sbin_request_key(struct key_construction *cons,
                                 const char *op,
                                 void *aux)
 {
-       struct task_struct *tsk = current;
        const struct cred *cred = current_cred();
        key_serial_t prkey, sskey;
        struct key *key = cons->key, *authkey = cons->authkey, *keyring;
@@ -109,18 +108,13 @@ static int call_sbin_request_key(struct key_construction *cons,
                cred->thread_keyring->serial : 0);
 
        prkey = 0;
-       if (tsk->signal->process_keyring)
-               prkey = tsk->signal->process_keyring->serial;
+       if (cred->tgcred->process_keyring)
+               prkey = cred->tgcred->process_keyring->serial;
 
-       sprintf(keyring_str[1], "%d", prkey);
-
-       if (tsk->signal->session_keyring) {
-               rcu_read_lock();
-               sskey = rcu_dereference(tsk->signal->session_keyring)->serial;
-               rcu_read_unlock();
-       } else {
+       if (cred->tgcred->session_keyring)
+               sskey = rcu_dereference(cred->tgcred->session_keyring)->serial;
+       else
                sskey = cred->user->session_keyring->serial;
-       }
 
        sprintf(keyring_str[2], "%d", sskey);
 
@@ -222,7 +216,7 @@ static int construct_key(struct key *key, const void *callout_info,
 static void construct_get_dest_keyring(struct key **_dest_keyring)
 {
        struct request_key_auth *rka;
-       struct task_struct *tsk = current;
+       const struct cred *cred = current_cred();
        struct key *dest_keyring = *_dest_keyring, *authkey;
 
        kenter("%p", dest_keyring);
@@ -234,11 +228,11 @@ static void construct_get_dest_keyring(struct key **_dest_keyring)
        } else {
                /* use a default keyring; falling through the cases until we
                 * find one that we actually have */
-               switch (tsk->cred->jit_keyring) {
+               switch (cred->jit_keyring) {
                case KEY_REQKEY_DEFL_DEFAULT:
                case KEY_REQKEY_DEFL_REQUESTOR_KEYRING:
-                       if (tsk->cred->request_key_auth) {
-                               authkey = tsk->cred->request_key_auth;
+                       if (cred->request_key_auth) {
+                               authkey = cred->request_key_auth;
                                down_read(&authkey->sem);
                                rka = authkey->payload.data;
                                if (!test_bit(KEY_FLAG_REVOKED,
@@ -251,19 +245,19 @@ static void construct_get_dest_keyring(struct key **_dest_keyring)
                        }
 
                case KEY_REQKEY_DEFL_THREAD_KEYRING:
-                       dest_keyring = key_get(tsk->cred->thread_keyring);
+                       dest_keyring = key_get(cred->thread_keyring);
                        if (dest_keyring)
                                break;
 
                case KEY_REQKEY_DEFL_PROCESS_KEYRING:
-                       dest_keyring = key_get(tsk->signal->process_keyring);
+                       dest_keyring = key_get(cred->tgcred->process_keyring);
                        if (dest_keyring)
                                break;
 
                case KEY_REQKEY_DEFL_SESSION_KEYRING:
                        rcu_read_lock();
                        dest_keyring = key_get(
-                               rcu_dereference(tsk->signal->session_keyring));
+                               rcu_dereference(cred->tgcred->session_keyring));
                        rcu_read_unlock();
 
                        if (dest_keyring)
@@ -271,11 +265,11 @@ static void construct_get_dest_keyring(struct key **_dest_keyring)
 
                case KEY_REQKEY_DEFL_USER_SESSION_KEYRING:
                        dest_keyring =
-                               key_get(tsk->cred->user->session_keyring);
+                               key_get(cred->user->session_keyring);
                        break;
 
                case KEY_REQKEY_DEFL_USER_KEYRING:
-                       dest_keyring = key_get(tsk->cred->user->uid_keyring);
+                       dest_keyring = key_get(cred->user->uid_keyring);
                        break;
 
                case KEY_REQKEY_DEFL_GROUP_KEYRING: