[PATCH] Keys: Replace duplicate non-updateable keys rather than failing
[linux-2.6.git] / security / keys / process_keys.c
index 059c350..f694061 100644 (file)
@@ -1,6 +1,6 @@
 /* process_keys.c: management of a process's keyrings
  *
- * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2004-5 Red Hat, Inc. All Rights Reserved.
  * Written by David Howells (dhowells@redhat.com)
  *
  * This program is free software; you can redistribute it and/or
 #include <linux/keyctl.h>
 #include <linux/fs.h>
 #include <linux/err.h>
+#include <linux/mutex.h>
 #include <asm/uaccess.h>
 #include "internal.h"
 
 /* session keyring create vs join semaphore */
-static DECLARE_MUTEX(key_session_sem);
+static DEFINE_MUTEX(key_session_mutex);
 
 /* the root user's tracking struct */
 struct key_user root_key_user = {
@@ -39,7 +40,7 @@ struct key root_user_keyring = {
        .type           = &key_type_keyring,
        .user           = &root_key_user,
        .sem            = __RWSEM_INITIALIZER(root_user_keyring.sem),
-       .perm           = KEY_USR_ALL,
+       .perm           = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL,
        .flags          = 1 << KEY_FLAG_INSTANTIATED,
        .description    = "_uid.0",
 #ifdef KEY_DEBUGGING
@@ -54,7 +55,7 @@ struct key root_session_keyring = {
        .type           = &key_type_keyring,
        .user           = &root_key_user,
        .sem            = __RWSEM_INITIALIZER(root_session_keyring.sem),
-       .perm           = KEY_USR_ALL,
+       .perm           = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL,
        .flags          = 1 << KEY_FLAG_INSTANTIATED,
        .description    = "_uid_ses.0",
 #ifdef KEY_DEBUGGING
@@ -98,7 +99,7 @@ int alloc_uid_keyring(struct user_struct *user)
        user->session_keyring = session_keyring;
        ret = 0;
 
- error:
+error:
        return ret;
 
 } /* end alloc_uid_keyring() */
@@ -156,7 +157,7 @@ int install_thread_keyring(struct task_struct *tsk)
        ret = 0;
 
        key_put(old);
- error:
+error:
        return ret;
 
 } /* end install_thread_keyring() */
@@ -165,7 +166,7 @@ int install_thread_keyring(struct task_struct *tsk)
 /*
  * make sure a process keyring is installed
  */
-static int install_process_keyring(struct task_struct *tsk)
+int install_process_keyring(struct task_struct *tsk)
 {
        unsigned long flags;
        struct key *keyring;
@@ -181,7 +182,7 @@ static int install_process_keyring(struct task_struct *tsk)
                        goto error;
                }
 
-               /* attach or swap keyrings */
+               /* attach keyring */
                spin_lock_irqsave(&tsk->sighand->siglock, flags);
                if (!tsk->signal->process_keyring) {
                        tsk->signal->process_keyring = keyring;
@@ -193,7 +194,7 @@ static int install_process_keyring(struct task_struct *tsk)
        }
 
        ret = 0;
- error:
+error:
        return ret;
 
 } /* end install_process_keyring() */
@@ -227,14 +228,16 @@ static int install_session_keyring(struct task_struct *tsk,
 
        /* install the keyring */
        spin_lock_irqsave(&tsk->sighand->siglock, flags);
-       old = tsk->signal->session_keyring;
-       tsk->signal->session_keyring = keyring;
+       old = rcu_dereference(tsk->signal->session_keyring);
+       rcu_assign_pointer(tsk->signal->session_keyring, keyring);
        spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
 
        ret = 0;
 
+       /* we're using RCU on the pointer */
+       synchronize_rcu();
        key_put(old);
- error:
+error:
        return ret;
 
 } /* end install_session_keyring() */
@@ -245,8 +248,6 @@ static int install_session_keyring(struct task_struct *tsk,
  */
 int copy_thread_group_keys(struct task_struct *tsk)
 {
-       unsigned long flags;
-
        key_check(current->thread_group->session_keyring);
        key_check(current->thread_group->process_keyring);
 
@@ -254,10 +255,10 @@ int copy_thread_group_keys(struct task_struct *tsk)
        tsk->signal->process_keyring = NULL;
 
        /* same session keyring */
-       spin_lock_irqsave(&current->sighand->siglock, flags);
+       rcu_read_lock();
        tsk->signal->session_keyring =
-               key_get(current->signal->session_keyring);
-       spin_unlock_irqrestore(&current->sighand->siglock, flags);
+               key_get(rcu_dereference(current->signal->session_keyring));
+       rcu_read_unlock();
 
        return 0;
 
@@ -270,9 +271,14 @@ int copy_thread_group_keys(struct task_struct *tsk)
 int copy_keys(unsigned long clone_flags, struct task_struct *tsk)
 {
        key_check(tsk->thread_keyring);
+       key_check(tsk->request_key_auth);
 
        /* no thread keyring yet */
        tsk->thread_keyring = NULL;
+
+       /* copy the request_key() authorisation for this thread */
+       key_get(tsk->request_key_auth);
+
        return 0;
 
 } /* end copy_keys() */
@@ -290,11 +296,12 @@ void exit_thread_group_keys(struct signal_struct *tg)
 
 /*****************************************************************************/
 /*
- * dispose of keys upon thread exit
+ * dispose of per-thread keys upon thread exit
  */
 void exit_keys(struct task_struct *tsk)
 {
        key_put(tsk->thread_keyring);
+       key_put(tsk->request_key_auth);
 
 } /* end exit_keys() */
 
@@ -376,13 +383,13 @@ void key_fsgid_changed(struct task_struct *tsk)
  * - we return -EAGAIN if we didn't find any matching key
  * - we return -ENOKEY if we found only negative matching keys
  */
-struct key *search_process_keyrings_aux(struct key_type *type,
-                                       const void *description,
-                                       key_match_func_t match)
+key_ref_t search_process_keyrings(struct key_type *type,
+                                 const void *description,
+                                 key_match_func_t match,
+                                 struct task_struct *context)
 {
-       struct task_struct *tsk = current;
-       unsigned long flags;
-       struct key *key, *ret, *err, *tmp;
+       struct request_key_auth *rka;
+       key_ref_t key_ref, ret, err;
 
        /* we want to return -EAGAIN or -ENOKEY if any of the keyrings were
         * searchable, but we failed to find a key or we found a negative key;
@@ -391,97 +398,145 @@ struct key *search_process_keyrings_aux(struct key_type *type,
         *
         * in terms of priority: success > -ENOKEY > -EAGAIN > other error
         */
-       key = NULL;
+       key_ref = NULL;
        ret = NULL;
        err = ERR_PTR(-EAGAIN);
 
        /* search the thread keyring first */
-       if (tsk->thread_keyring) {
-               key = keyring_search_aux(tsk->thread_keyring, type,
-                                        description, match);
-               if (!IS_ERR(key))
+       if (context->thread_keyring) {
+               key_ref = keyring_search_aux(
+                       make_key_ref(context->thread_keyring, 1),
+                       context, type, description, match);
+               if (!IS_ERR(key_ref))
                        goto found;
 
-               switch (PTR_ERR(key)) {
+               switch (PTR_ERR(key_ref)) {
                case -EAGAIN: /* no key */
                        if (ret)
                                break;
                case -ENOKEY: /* negative key */
-                       ret = key;
+                       ret = key_ref;
                        break;
                default:
-                       err = key;
+                       err = key_ref;
                        break;
                }
        }
 
        /* search the process keyring second */
-       if (tsk->signal->process_keyring) {
-               key = keyring_search_aux(tsk->signal->process_keyring,
-                                        type, description, match);
-               if (!IS_ERR(key))
+       if (context->signal->process_keyring) {
+               key_ref = keyring_search_aux(
+                       make_key_ref(context->signal->process_keyring, 1),
+                       context, type, description, match);
+               if (!IS_ERR(key_ref))
                        goto found;
 
-               switch (PTR_ERR(key)) {
+               switch (PTR_ERR(key_ref)) {
                case -EAGAIN: /* no key */
                        if (ret)
                                break;
                case -ENOKEY: /* negative key */
-                       ret = key;
+                       ret = key_ref;
                        break;
                default:
-                       err = key;
+                       err = key_ref;
                        break;
                }
        }
 
-       /* search the session keyring last */
-       spin_lock_irqsave(&tsk->sighand->siglock, flags);
+       /* search the session keyring */
+       if (context->signal->session_keyring) {
+               rcu_read_lock();
+               key_ref = keyring_search_aux(
+                       make_key_ref(rcu_dereference(
+                                            context->signal->session_keyring),
+                                    1),
+                       context, type, description, match);
+               rcu_read_unlock();
+
+               if (!IS_ERR(key_ref))
+                       goto found;
 
-       tmp = tsk->signal->session_keyring;
-       if (!tmp)
-               tmp = tsk->user->session_keyring;
-       atomic_inc(&tmp->usage);
+               switch (PTR_ERR(key_ref)) {
+               case -EAGAIN: /* no key */
+                       if (ret)
+                               break;
+               case -ENOKEY: /* negative key */
+                       ret = key_ref;
+                       break;
+               default:
+                       err = key_ref;
+                       break;
+               }
+       }
+       /* or search the user-session keyring */
+       else {
+               key_ref = keyring_search_aux(
+                       make_key_ref(context->user->session_keyring, 1),
+                       context, type, description, match);
+               if (!IS_ERR(key_ref))
+                       goto found;
 
-       spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
+               switch (PTR_ERR(key_ref)) {
+               case -EAGAIN: /* no key */
+                       if (ret)
+                               break;
+               case -ENOKEY: /* negative key */
+                       ret = key_ref;
+                       break;
+               default:
+                       err = key_ref;
+                       break;
+               }
+       }
+
+       /* if this process has an instantiation authorisation key, then we also
+        * search the keyrings of the process mentioned there
+        * - we don't permit access to request_key auth keys via this method
+        */
+       if (context->request_key_auth &&
+           context == current &&
+           type != &key_type_request_key_auth &&
+           key_validate(context->request_key_auth) == 0
+           ) {
+               rka = context->request_key_auth->payload.data;
 
-       key = keyring_search_aux(tmp, type, description, match);
-       key_put(tmp);
-       if (!IS_ERR(key))
-               goto found;
+               key_ref = search_process_keyrings(type, description, match,
+                                                 rka->context);
 
-       switch (PTR_ERR(key)) {
-       case -EAGAIN: /* no key */
-               if (ret)
+               if (!IS_ERR(key_ref))
+                       goto found;
+
+               switch (PTR_ERR(key_ref)) {
+               case -EAGAIN: /* no key */
+                       if (ret)
+                               break;
+               case -ENOKEY: /* negative key */
+                       ret = key_ref;
                        break;
-       case -ENOKEY: /* negative key */
-               ret = key;
-               break;
-       default:
-               err = key;
-               break;
+               default:
+                       err = key_ref;
+                       break;
+               }
        }
 
        /* no key - decide on the error we're going to go for */
-       key = ret ? ret : err;
+       key_ref = ret ? ret : err;
 
- found:
-       return key;
+found:
+       return key_ref;
 
-} /* end search_process_keyrings_aux() */
+} /* end search_process_keyrings() */
 
 /*****************************************************************************/
 /*
- * search the process keyrings for the first matching key
- * - we return -EAGAIN if we didn't find any matching key
- * - we return -ENOKEY if we found only negative matching keys
+ * see if the key we're looking at is the target key
  */
-struct key *search_process_keyrings(struct key_type *type,
-                                   const char *description)
+static int lookup_user_key_possessed(const struct key *key, const void *target)
 {
-       return search_process_keyrings_aux(type, description, type->match);
+       return key == target;
 
-} /* end search_process_keyrings() */
+} /* end lookup_user_key_possessed() */
 
 /*****************************************************************************/
 /*
@@ -489,73 +544,80 @@ struct key *search_process_keyrings(struct key_type *type,
  * - don't create special keyrings unless so requested
  * - partially constructed keys aren't found unless requested
  */
-struct key *lookup_user_key(key_serial_t id, int create, int partial,
-                           key_perm_t perm)
+key_ref_t lookup_user_key(struct task_struct *context, key_serial_t id,
+                         int create, int partial, key_perm_t perm)
 {
-       struct task_struct *tsk = current;
-       unsigned long flags;
+       key_ref_t key_ref, skey_ref;
        struct key *key;
        int ret;
 
-       key = ERR_PTR(-ENOKEY);
+       if (!context)
+               context = current;
+
+       key_ref = ERR_PTR(-ENOKEY);
 
        switch (id) {
        case KEY_SPEC_THREAD_KEYRING:
-               if (!tsk->thread_keyring) {
+               if (!context->thread_keyring) {
                        if (!create)
                                goto error;
 
-                       ret = install_thread_keyring(tsk);
+                       ret = install_thread_keyring(context);
                        if (ret < 0) {
                                key = ERR_PTR(ret);
                                goto error;
                        }
                }
 
-               key = tsk->thread_keyring;
+               key = context->thread_keyring;
                atomic_inc(&key->usage);
+               key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_PROCESS_KEYRING:
-               if (!tsk->signal->process_keyring) {
+               if (!context->signal->process_keyring) {
                        if (!create)
                                goto error;
 
-                       ret = install_process_keyring(tsk);
+                       ret = install_process_keyring(context);
                        if (ret < 0) {
                                key = ERR_PTR(ret);
                                goto error;
                        }
                }
 
-               key = tsk->signal->process_keyring;
+               key = context->signal->process_keyring;
                atomic_inc(&key->usage);
+               key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_SESSION_KEYRING:
-               if (!tsk->signal->session_keyring) {
+               if (!context->signal->session_keyring) {
                        /* always install a session keyring upon access if one
                         * doesn't exist yet */
                        ret = install_session_keyring(
-                              tsk, tsk->user->session_keyring);
+                               context, context->user->session_keyring);
                        if (ret < 0)
                                goto error;
                }
 
-               spin_lock_irqsave(&tsk->sighand->siglock, flags);
-               key = tsk->signal->session_keyring;
+               rcu_read_lock();
+               key = rcu_dereference(context->signal->session_keyring);
                atomic_inc(&key->usage);
-               spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
+               rcu_read_unlock();
+               key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_USER_KEYRING:
-               key = tsk->user->uid_keyring;
+               key = context->user->uid_keyring;
                atomic_inc(&key->usage);
+               key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_USER_SESSION_KEYRING:
-               key = tsk->user->session_keyring;
+               key = context->user->session_keyring;
                atomic_inc(&key->usage);
+               key_ref = make_key_ref(key, 1);
                break;
 
        case KEY_SPEC_GROUP_KEYRING:
@@ -563,18 +625,42 @@ struct key *lookup_user_key(key_serial_t id, int create, int partial,
                key = ERR_PTR(-EINVAL);
                goto error;
 
+       case KEY_SPEC_REQKEY_AUTH_KEY:
+               key = context->request_key_auth;
+               if (!key)
+                       goto error;
+
+               atomic_inc(&key->usage);
+               key_ref = make_key_ref(key, 1);
+               break;
+
        default:
-               key = ERR_PTR(-EINVAL);
+               key_ref = ERR_PTR(-EINVAL);
                if (id < 1)
                        goto error;
 
                key = key_lookup(id);
-               if (IS_ERR(key))
+               if (IS_ERR(key)) {
+                       key_ref = ERR_PTR(PTR_ERR(key));
                        goto error;
+               }
+
+               key_ref = make_key_ref(key, 0);
+
+               /* check to see if we possess the key */
+               skey_ref = search_process_keyrings(key->type, key,
+                                                  lookup_user_key_possessed,
+                                                  current);
+
+               if (!IS_ERR(skey_ref)) {
+                       key_put(key);
+                       key_ref = skey_ref;
+               }
+
                break;
        }
 
-       /* check the status and permissions */
+       /* check the status */
        if (perm) {
                ret = key_validate(key);
                if (ret < 0)
@@ -585,16 +671,17 @@ struct key *lookup_user_key(key_serial_t id, int create, int partial,
        if (!partial && !test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
                goto invalid_key;
 
-       ret = -EACCES;
-       if (!key_permission(key, perm))
+       /* check the permissions */
+       ret = key_task_permission(key_ref, context, perm);
+       if (ret < 0)
                goto invalid_key;
 
- error:
-       return key;
+error:
+       return key_ref;
 
- invalid_key:
-       key_put(key);
-       key = ERR_PTR(ret);
+invalid_key:
+       key_ref_put(key_ref);
+       key_ref = ERR_PTR(ret);
        goto error;
 
 } /* end lookup_user_key() */
@@ -609,7 +696,6 @@ struct key *lookup_user_key(key_serial_t id, int create, int partial,
 long join_session_keyring(const char *name)
 {
        struct task_struct *tsk = current;
-       unsigned long flags;
        struct key *keyring;
        long ret;
 
@@ -619,14 +705,14 @@ long join_session_keyring(const char *name)
                if (ret < 0)
                        goto error;
 
-               spin_lock_irqsave(&tsk->sighand->siglock, flags);
-               ret = tsk->signal->session_keyring->serial;
-               spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
+               rcu_read_lock();
+               ret = rcu_dereference(tsk->signal->session_keyring)->serial;
+               rcu_read_unlock();
                goto error;
        }
 
        /* allow the user to join or create a named keyring */
-       down(&key_session_sem);
+       mutex_lock(&key_session_mutex);
 
        /* look for an existing keyring of this name */
        keyring = find_keyring_by_name(name, 0);
@@ -635,7 +721,7 @@ long join_session_keyring(const char *name)
                keyring = keyring_alloc(name, tsk->uid, tsk->gid, 0, NULL);
                if (IS_ERR(keyring)) {
                        ret = PTR_ERR(keyring);
-                       goto error;
+                       goto error2;
                }
        }
        else if (IS_ERR(keyring)) {
@@ -651,9 +737,9 @@ long join_session_keyring(const char *name)
        ret = keyring->serial;
        key_put(keyring);
 
- error2:
-       up(&key_session_sem);
- error:
+error2:
+       mutex_unlock(&key_session_mutex);
+error:
        return ret;
 
 } /* end join_session_keyring() */