]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - security/keys/process_keys.c
CRED: Inaugurate COW credentials
[net-next-2.6.git] / security / keys / process_keys.c
index 70ee93406f309cdd0ae2338651f520267229c0fb..df329f684a6545cd49f9e47618eaf9725a87afd9 100644 (file)
@@ -42,11 +42,15 @@ struct key_user root_key_user = {
  */
 int install_user_keyrings(void)
 {
-       struct user_struct *user = current->cred->user;
+       struct user_struct *user;
+       const struct cred *cred;
        struct key *uid_keyring, *session_keyring;
        char buf[20];
        int ret;
 
+       cred = current_cred();
+       user = cred->user;
+
        kenter("%p{%u}", user, user->uid);
 
        if (user->uid_keyring) {
@@ -67,7 +71,7 @@ int install_user_keyrings(void)
                uid_keyring = find_keyring_by_name(buf, true);
                if (IS_ERR(uid_keyring)) {
                        uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1,
-                                                   current, KEY_ALLOC_IN_QUOTA,
+                                                   cred, KEY_ALLOC_IN_QUOTA,
                                                    NULL);
                        if (IS_ERR(uid_keyring)) {
                                ret = PTR_ERR(uid_keyring);
@@ -83,8 +87,7 @@ int install_user_keyrings(void)
                if (IS_ERR(session_keyring)) {
                        session_keyring =
                                keyring_alloc(buf, user->uid, (gid_t) -1,
-                                             current, KEY_ALLOC_IN_QUOTA,
-                                             NULL);
+                                             cred, KEY_ALLOC_IN_QUOTA, NULL);
                        if (IS_ERR(session_keyring)) {
                                ret = PTR_ERR(session_keyring);
                                goto error_release;
@@ -116,142 +119,128 @@ error:
        return ret;
 }
 
-/*****************************************************************************/
 /*
- * deal with the UID changing
+ * install a fresh thread keyring directly to new credentials
  */
-void switch_uid_keyring(struct user_struct *new_user)
+int install_thread_keyring_to_cred(struct cred *new)
 {
-#if 0 /* do nothing for now */
-       struct key *old;
-
-       /* switch to the new user's session keyring if we were running under
-        * root's default session keyring */
-       if (new_user->uid != 0 &&
-           current->session_keyring == &root_session_keyring
-           ) {
-               atomic_inc(&new_user->session_keyring->usage);
-
-               task_lock(current);
-               old = current->session_keyring;
-               current->session_keyring = new_user->session_keyring;
-               task_unlock(current);
+       struct key *keyring;
 
-               key_put(old);
-       }
-#endif
+       keyring = keyring_alloc("_tid", new->uid, new->gid, new,
+                               KEY_ALLOC_QUOTA_OVERRUN, NULL);
+       if (IS_ERR(keyring))
+               return PTR_ERR(keyring);
 
-} /* end switch_uid_keyring() */
+       new->thread_keyring = keyring;
+       return 0;
+}
 
-/*****************************************************************************/
 /*
  * install a fresh thread keyring, discarding the old one
  */
-int install_thread_keyring(void)
+static int install_thread_keyring(void)
 {
-       struct task_struct *tsk = current;
-       struct key *keyring, *old;
-       char buf[20];
+       struct cred *new;
        int ret;
 
-       sprintf(buf, "_tid.%u", tsk->pid);
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
 
-       keyring = keyring_alloc(buf, tsk->cred->uid, tsk->cred->gid, tsk,
-                               KEY_ALLOC_QUOTA_OVERRUN, NULL);
-       if (IS_ERR(keyring)) {
-               ret = PTR_ERR(keyring);
-               goto error;
+       BUG_ON(new->thread_keyring);
+
+       ret = install_thread_keyring_to_cred(new);
+       if (ret < 0) {
+               abort_creds(new);
+               return ret;
        }
 
-       task_lock(tsk);
-       old = tsk->cred->thread_keyring;
-       tsk->cred->thread_keyring = keyring;
-       task_unlock(tsk);
+       return commit_creds(new);
+}
 
-       ret = 0;
+/*
+ * install a process keyring directly to a credentials struct
+ * - returns -EEXIST if there was already a process keyring, 0 if one installed,
+ *   and other -ve on any other error
+ */
+int install_process_keyring_to_cred(struct cred *new)
+{
+       struct key *keyring;
+       int ret;
 
-       key_put(old);
-error:
+       if (new->tgcred->process_keyring)
+               return -EEXIST;
+
+       keyring = keyring_alloc("_pid", new->uid, new->gid,
+                               new, KEY_ALLOC_QUOTA_OVERRUN, NULL);
+       if (IS_ERR(keyring))
+               return PTR_ERR(keyring);
+
+       spin_lock_irq(&new->tgcred->lock);
+       if (!new->tgcred->process_keyring) {
+               new->tgcred->process_keyring = keyring;
+               keyring = NULL;
+               ret = 0;
+       } else {
+               ret = -EEXIST;
+       }
+       spin_unlock_irq(&new->tgcred->lock);
+       key_put(keyring);
        return ret;
+}
 
-} /* end install_thread_keyring() */
-
-/*****************************************************************************/
 /*
  * make sure a process keyring is installed
+ * - we
  */
-int install_process_keyring(void)
+static int install_process_keyring(void)
 {
-       struct task_struct *tsk = current;
-       struct key *keyring;
-       char buf[20];
+       struct cred *new;
        int ret;
 
-       might_sleep();
-
-       if (!tsk->cred->tgcred->process_keyring) {
-               sprintf(buf, "_pid.%u", tsk->tgid);
-
-               keyring = keyring_alloc(buf, tsk->cred->uid, tsk->cred->gid, tsk,
-                                       KEY_ALLOC_QUOTA_OVERRUN, NULL);
-               if (IS_ERR(keyring)) {
-                       ret = PTR_ERR(keyring);
-                       goto error;
-               }
-
-               /* attach 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->cred->tgcred->lock);
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
 
-               key_put(keyring);
+       ret = install_process_keyring_to_cred(new);
+       if (ret < 0) {
+               abort_creds(new);
+               return ret != -EEXIST ?: 0;
        }
 
-       ret = 0;
-error:
-       return ret;
-
-} /* end install_process_keyring() */
+       return commit_creds(new);
+}
 
-/*****************************************************************************/
 /*
- * install a session keyring, discarding the old one
- * - if a keyring is not supplied, an empty one is invented
+ * install a session keyring directly to a credentials struct
  */
-static int install_session_keyring(struct key *keyring)
+static int install_session_keyring_to_cred(struct cred *cred,
+                                          struct key *keyring)
 {
-       struct task_struct *tsk = current;
        unsigned long flags;
        struct key *old;
-       char buf[20];
 
        might_sleep();
 
        /* create an empty session keyring */
        if (!keyring) {
-               sprintf(buf, "_ses.%u", tsk->tgid);
-
                flags = KEY_ALLOC_QUOTA_OVERRUN;
-               if (tsk->cred->tgcred->session_keyring)
+               if (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("_ses", cred->uid, cred->gid,
+                                       cred, flags, NULL);
                if (IS_ERR(keyring))
                        return PTR_ERR(keyring);
-       }
-       else {
+       } else {
                atomic_inc(&keyring->usage);
        }
 
        /* install the keyring */
-       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);
+       spin_lock_irq(&cred->tgcred->lock);
+       old = cred->tgcred->session_keyring;
+       rcu_assign_pointer(cred->tgcred->session_keyring, keyring);
+       spin_unlock_irq(&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 */
@@ -261,38 +250,29 @@ static int install_session_keyring(struct key *keyring)
        }
 
        return 0;
+}
 
-} /* end install_session_keyring() */
-
-/*****************************************************************************/
 /*
- * copy the keys for fork
+ * install a session keyring, discarding the old one
+ * - if a keyring is not supplied, an empty one is invented
  */
-int copy_keys(unsigned long clone_flags, struct task_struct *tsk)
+static int install_session_keyring(struct key *keyring)
 {
-       key_check(tsk->cred->thread_keyring);
-       key_check(tsk->cred->request_key_auth);
-
-       /* no thread keyring yet */
-       tsk->cred->thread_keyring = NULL;
-
-       /* copy the request_key() authorisation for this thread */
-       key_get(tsk->cred->request_key_auth);
-
-       return 0;
+       struct cred *new;
+       int ret;
 
-} /* end copy_keys() */
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
 
-/*****************************************************************************/
-/*
- * dispose of per-thread keys upon thread exit
- */
-void exit_keys(struct task_struct *tsk)
-{
-       key_put(tsk->cred->thread_keyring);
-       key_put(tsk->cred->request_key_auth);
+       ret = install_session_keyring_to_cred(new, NULL);
+       if (ret < 0) {
+               abort_creds(new);
+               return ret;
+       }
 
-} /* end exit_keys() */
+       return commit_creds(new);
+}
 
 /*****************************************************************************/
 /*
@@ -300,38 +280,41 @@ void exit_keys(struct task_struct *tsk)
  */
 int exec_keys(struct task_struct *tsk)
 {
-       struct key *old;
+       struct thread_group_cred *tgcred = NULL;
+       struct cred *new;
 
-       /* newly exec'd tasks don't get a thread keyring */
-       task_lock(tsk);
-       old = tsk->cred->thread_keyring;
-       tsk->cred->thread_keyring = NULL;
-       task_unlock(tsk);
+#ifdef CONFIG_KEYS
+       tgcred = kmalloc(sizeof(*tgcred), GFP_KERNEL);
+       if (!tgcred)
+               return -ENOMEM;
+#endif
 
-       key_put(old);
+       new = prepare_creds();
+       if (new < 0)
+               return -ENOMEM;
 
-       /* discard the process keyring from a newly exec'd task */
-       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);
+       /* newly exec'd tasks don't get a thread keyring */
+       key_put(new->thread_keyring);
+       new->thread_keyring = NULL;
 
-       key_put(old);
+       /* create a new per-thread-group creds for all this set of threads to
+        * share */
+       memcpy(tgcred, new->tgcred, sizeof(struct thread_group_cred));
 
-       return 0;
+       atomic_set(&tgcred->usage, 1);
+       spin_lock_init(&tgcred->lock);
 
-} /* end exec_keys() */
+       /* inherit the session keyring; new process keyring */
+       key_get(tgcred->session_keyring);
+       tgcred->process_keyring = NULL;
 
-/*****************************************************************************/
-/*
- * deal with SUID programs
- * - we might want to make this invent a new session keyring
- */
-int suid_keys(struct task_struct *tsk)
-{
+       release_tgcred(new);
+       new->tgcred = tgcred;
+
+       commit_creds(new);
        return 0;
 
-} /* end suid_keys() */
+} /* end exec_keys() */
 
 /*****************************************************************************/
 /*
@@ -376,16 +359,13 @@ void key_fsgid_changed(struct task_struct *tsk)
 key_ref_t search_process_keyrings(struct key_type *type,
                                  const void *description,
                                  key_match_func_t match,
-                                 struct task_struct *context)
+                                 const struct cred *cred)
 {
        struct request_key_auth *rka;
-       struct cred *cred;
        key_ref_t key_ref, ret, err;
 
        might_sleep();
 
-       cred = get_task_cred(context);
-
        /* 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;
         * otherwise we want to return a sample error (probably -EACCES) if
@@ -401,7 +381,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
        if (cred->thread_keyring) {
                key_ref = keyring_search_aux(
                        make_key_ref(cred->thread_keyring, 1),
-                       context, type, description, match);
+                       cred, type, description, match);
                if (!IS_ERR(key_ref))
                        goto found;
 
@@ -422,7 +402,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
        if (cred->tgcred->process_keyring) {
                key_ref = keyring_search_aux(
                        make_key_ref(cred->tgcred->process_keyring, 1),
-                       context, type, description, match);
+                       cred, type, description, match);
                if (!IS_ERR(key_ref))
                        goto found;
 
@@ -446,7 +426,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
                        make_key_ref(rcu_dereference(
                                             cred->tgcred->session_keyring),
                                     1),
-                       context, type, description, match);
+                       cred, type, description, match);
                rcu_read_unlock();
 
                if (!IS_ERR(key_ref))
@@ -468,7 +448,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
        else if (cred->user->session_keyring) {
                key_ref = keyring_search_aux(
                        make_key_ref(cred->user->session_keyring, 1),
-                       context, type, description, match);
+                       cred, type, description, match);
                if (!IS_ERR(key_ref))
                        goto found;
 
@@ -490,7 +470,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
         * - we don't permit access to request_key auth keys via this method
         */
        if (cred->request_key_auth &&
-           context == current &&
+           cred == current_cred() &&
            type != &key_type_request_key_auth
            ) {
                /* defend against the auth key being revoked */
@@ -500,7 +480,7 @@ key_ref_t search_process_keyrings(struct key_type *type,
                        rka = cred->request_key_auth->payload.data;
 
                        key_ref = search_process_keyrings(type, description,
-                                                         match, rka->context);
+                                                         match, rka->cred);
 
                        up_read(&cred->request_key_auth->sem);
 
@@ -527,7 +507,6 @@ key_ref_t search_process_keyrings(struct key_type *type,
        key_ref = ret ? ret : err;
 
 found:
-       put_cred(cred);
        return key_ref;
 
 } /* end search_process_keyrings() */
@@ -552,8 +531,7 @@ key_ref_t lookup_user_key(key_serial_t id, int create, int partial,
                          key_perm_t perm)
 {
        struct request_key_auth *rka;
-       struct task_struct *t = current;
-       struct cred *cred;
+       const struct cred *cred;
        struct key *key;
        key_ref_t key_ref, skey_ref;
        int ret;
@@ -608,6 +586,7 @@ try_again:
                                goto error;
                        ret = install_session_keyring(
                                cred->user->session_keyring);
+
                        if (ret < 0)
                                goto error;
                        goto reget_creds;
@@ -693,7 +672,7 @@ try_again:
                /* check to see if we possess the key */
                skey_ref = search_process_keyrings(key->type, key,
                                                   lookup_user_key_possessed,
-                                                  current);
+                                                  cred);
 
                if (!IS_ERR(skey_ref)) {
                        key_put(key);
@@ -725,7 +704,7 @@ try_again:
                goto invalid_key;
 
        /* check the permissions */
-       ret = key_task_permission(key_ref, t, perm);
+       ret = key_task_permission(key_ref, cred, perm);
        if (ret < 0)
                goto invalid_key;
 
@@ -755,21 +734,33 @@ reget_creds:
  */
 long join_session_keyring(const char *name)
 {
-       struct task_struct *tsk = current;
-       struct cred *cred = current->cred;
+       const struct cred *old;
+       struct cred *new;
        struct key *keyring;
-       long ret;
+       long ret, serial;
+
+       /* only permit this if there's a single thread in the thread group -
+        * this avoids us having to adjust the creds on all threads and risking
+        * ENOMEM */
+       if (!is_single_threaded(current))
+               return -EMLINK;
+
+       new = prepare_creds();
+       if (!new)
+               return -ENOMEM;
+       old = current_cred();
 
        /* if no name is provided, install an anonymous keyring */
        if (!name) {
-               ret = install_session_keyring(NULL);
+               ret = install_session_keyring_to_cred(new, NULL);
                if (ret < 0)
                        goto error;
 
-               rcu_read_lock();
-               ret = rcu_dereference(cred->tgcred->session_keyring)->serial;
-               rcu_read_unlock();
-               goto error;
+               serial = new->tgcred->session_keyring->serial;
+               ret = commit_creds(new);
+               if (ret == 0)
+                       ret = serial;
+               goto okay;
        }
 
        /* allow the user to join or create a named keyring */
@@ -779,29 +770,33 @@ 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, cred->uid, cred->gid, tsk,
+               keyring = keyring_alloc(name, old->uid, old->gid, old,
                                        KEY_ALLOC_IN_QUOTA, NULL);
                if (IS_ERR(keyring)) {
                        ret = PTR_ERR(keyring);
                        goto error2;
                }
-       }
-       else if (IS_ERR(keyring)) {
+       } else if (IS_ERR(keyring)) {
                ret = PTR_ERR(keyring);
                goto error2;
        }
 
        /* we've got a keyring - now to install it */
-       ret = install_session_keyring(keyring);
+       ret = install_session_keyring_to_cred(new, keyring);
        if (ret < 0)
                goto error2;
 
+       commit_creds(new);
+       mutex_unlock(&key_session_mutex);
+
        ret = keyring->serial;
        key_put(keyring);
+okay:
+       return ret;
 
 error2:
        mutex_unlock(&key_session_mutex);
 error:
+       abort_creds(new);
        return ret;
-
-} /* end join_session_keyring() */
+}