[PATCH] keys: Discard key spinlock and use RCU for key payload
[linux-3.10.git] / security / keys / keyring.c
index e2ab4f8e7481d21d18efc26d0af1be273b39da8d..c9a5de197487879d1e8ae6900b079a423d75ba57 100644 (file)
@@ -132,10 +132,17 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
                (PAGE_SIZE - sizeof(*klist)) / sizeof(struct key);
 
        ret = 0;
-       sklist = source->payload.subscriptions;
 
-       if (sklist && sklist->nkeys > 0) {
+       /* find out how many keys are currently linked */
+       rcu_read_lock();
+       sklist = rcu_dereference(source->payload.subscriptions);
+       max = 0;
+       if (sklist)
                max = sklist->nkeys;
+       rcu_read_unlock();
+
+       /* allocate a new payload and stuff load with key links */
+       if (max > 0) {
                BUG_ON(max > limit);
 
                max = (max + 3) & ~3;
@@ -148,6 +155,10 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
                if (!klist)
                        goto error;
 
+               /* set links */
+               rcu_read_lock();
+               sklist = rcu_dereference(source->payload.subscriptions);
+
                klist->maxkeys = max;
                klist->nkeys = sklist->nkeys;
                memcpy(klist->keys,
@@ -157,7 +168,9 @@ static int keyring_duplicate(struct key *keyring, const struct key *source)
                for (loop = klist->nkeys - 1; loop >= 0; loop--)
                        atomic_inc(&klist->keys[loop]->usage);
 
-               keyring->payload.subscriptions = klist;
+               rcu_read_unlock();
+
+               rcu_assign_pointer(keyring->payload.subscriptions, klist);
                ret = 0;
        }
 
@@ -192,7 +205,7 @@ static void keyring_destroy(struct key *keyring)
                write_unlock(&keyring_name_lock);
        }
 
-       klist = keyring->payload.subscriptions;
+       klist = rcu_dereference(keyring->payload.subscriptions);
        if (klist) {
                for (loop = klist->nkeys - 1; loop >= 0; loop--)
                        key_put(klist->keys[loop]);
@@ -216,17 +229,20 @@ static void keyring_describe(const struct key *keyring, struct seq_file *m)
                seq_puts(m, "[anon]");
        }
 
-       klist = keyring->payload.subscriptions;
+       rcu_read_lock();
+       klist = rcu_dereference(keyring->payload.subscriptions);
        if (klist)
                seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys);
        else
                seq_puts(m, ": empty");
+       rcu_read_unlock();
 
 } /* end keyring_describe() */
 
 /*****************************************************************************/
 /*
  * read a list of key IDs from the keyring's contents
+ * - the keyring's semaphore is read-locked
  */
 static long keyring_read(const struct key *keyring,
                         char __user *buffer, size_t buflen)
@@ -237,7 +253,7 @@ static long keyring_read(const struct key *keyring,
        int loop, ret;
 
        ret = 0;
-       klist = keyring->payload.subscriptions;
+       klist = rcu_dereference(keyring->payload.subscriptions);
 
        if (klist) {
                /* calculate how much data we could return */
@@ -320,7 +336,7 @@ struct key *keyring_search_aux(struct key *keyring,
                               key_match_func_t match)
 {
        struct {
-               struct key *keyring;
+               struct keyring_list *keylist;
                int kix;
        } stack[KEYRING_SEARCH_MAX_DEPTH];
 
@@ -328,10 +344,12 @@ struct key *keyring_search_aux(struct key *keyring,
        struct timespec now;
        struct key *key;
        long err;
-       int sp, psp, kix;
+       int sp, kix;
 
        key_check(keyring);
 
+       rcu_read_lock();
+
        /* top keyring must have search permission to begin the search */
        key = ERR_PTR(-EACCES);
        if (!key_permission(keyring, KEY_SEARCH))
@@ -347,11 +365,10 @@ struct key *keyring_search_aux(struct key *keyring,
 
        /* start processing a new keyring */
  descend:
-       read_lock(&keyring->lock);
-       if (keyring->flags & KEY_FLAG_REVOKED)
+       if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
                goto not_this_keyring;
 
-       keylist = keyring->payload.subscriptions;
+       keylist = rcu_dereference(keyring->payload.subscriptions);
        if (!keylist)
                goto not_this_keyring;
 
@@ -364,7 +381,7 @@ struct key *keyring_search_aux(struct key *keyring,
                        continue;
 
                /* skip revoked keys and expired keys */
-               if (key->flags & KEY_FLAG_REVOKED)
+               if (test_bit(KEY_FLAG_REVOKED, &key->flags))
                        continue;
 
                if (key->expiry && now.tv_sec >= key->expiry)
@@ -379,7 +396,7 @@ struct key *keyring_search_aux(struct key *keyring,
                        continue;
 
                /* we set a different error code if we find a negative key */
-               if (key->flags & KEY_FLAG_NEGATIVE) {
+               if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) {
                        err = -ENOKEY;
                        continue;
                }
@@ -390,48 +407,37 @@ struct key *keyring_search_aux(struct key *keyring,
        /* search through the keyrings nested in this one */
        kix = 0;
  ascend:
-       while (kix < keylist->nkeys) {
+       for (; kix < keylist->nkeys; kix++) {
                key = keylist->keys[kix];
                if (key->type != &key_type_keyring)
-                       goto next;
+                       continue;
 
                /* recursively search nested keyrings
                 * - only search keyrings for which we have search permission
                 */
                if (sp >= KEYRING_SEARCH_MAX_DEPTH)
-                       goto next;
+                       continue;
 
                if (!key_permission(key, KEY_SEARCH))
-                       goto next;
-
-               /* evade loops in the keyring tree */
-               for (psp = 0; psp < sp; psp++)
-                       if (stack[psp].keyring == keyring)
-                               goto next;
+                       continue;
 
                /* stack the current position */
-               stack[sp].keyring = keyring;
+               stack[sp].keylist = keylist;
                stack[sp].kix = kix;
                sp++;
 
                /* begin again with the new keyring */
                keyring = key;
                goto descend;
-
-       next:
-               kix++;
        }
 
        /* the keyring we're looking at was disqualified or didn't contain a
         * matching key */
  not_this_keyring:
-       read_unlock(&keyring->lock);
-
        if (sp > 0) {
                /* resume the processing of a keyring higher up in the tree */
                sp--;
-               keyring = stack[sp].keyring;
-               keylist = keyring->payload.subscriptions;
+               keylist = stack[sp].keylist;
                kix = stack[sp].kix + 1;
                goto ascend;
        }
@@ -442,16 +448,9 @@ struct key *keyring_search_aux(struct key *keyring,
        /* we found a viable match */
  found:
        atomic_inc(&key->usage);
-       read_unlock(&keyring->lock);
-
-       /* unwind the keyring stack */
-       while (sp > 0) {
-               sp--;
-               read_unlock(&stack[sp].keyring->lock);
-       }
-
        key_check(key);
  error:
+       rcu_read_unlock();
        return key;
 
 } /* end keyring_search_aux() */
@@ -489,7 +488,9 @@ struct key *__keyring_search_one(struct key *keyring,
        struct key *key;
        int loop;
 
-       klist = keyring->payload.subscriptions;
+       rcu_read_lock();
+
+       klist = rcu_dereference(keyring->payload.subscriptions);
        if (klist) {
                for (loop = 0; loop < klist->nkeys; loop++) {
                        key = klist->keys[loop];
@@ -497,7 +498,7 @@ struct key *__keyring_search_one(struct key *keyring,
                        if (key->type == ktype &&
                            key->type->match(key, description) &&
                            key_permission(key, perm) &&
-                           !(key->flags & KEY_FLAG_REVOKED)
+                           !test_bit(KEY_FLAG_REVOKED, &key->flags)
                            )
                                goto found;
                }
@@ -509,6 +510,7 @@ struct key *__keyring_search_one(struct key *keyring,
  found:
        atomic_inc(&key->usage);
  error:
+       rcu_read_unlock();
        return key;
 
 } /* end __keyring_search_one() */
@@ -540,7 +542,7 @@ struct key *find_keyring_by_name(const char *name, key_serial_t bound)
                                    &keyring_name_hash[bucket],
                                    type_data.link
                                    ) {
-                       if (keyring->flags & KEY_FLAG_REVOKED)
+                       if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
                                continue;
 
                        if (strcmp(keyring->description, name) != 0)
@@ -579,7 +581,7 @@ struct key *find_keyring_by_name(const char *name, key_serial_t bound)
 static int keyring_detect_cycle(struct key *A, struct key *B)
 {
        struct {
-               struct key *subtree;
+               struct keyring_list *keylist;
                int kix;
        } stack[KEYRING_SEARCH_MAX_DEPTH];
 
@@ -587,20 +589,21 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
        struct key *subtree, *key;
        int sp, kix, ret;
 
+       rcu_read_lock();
+
        ret = -EDEADLK;
        if (A == B)
-               goto error;
+               goto cycle_detected;
 
        subtree = B;
        sp = 0;
 
        /* start processing a new keyring */
  descend:
-       read_lock(&subtree->lock);
-       if (subtree->flags & KEY_FLAG_REVOKED)
+       if (test_bit(KEY_FLAG_REVOKED, &subtree->flags))
                goto not_this_keyring;
 
-       keylist = subtree->payload.subscriptions;
+       keylist = rcu_dereference(subtree->payload.subscriptions);
        if (!keylist)
                goto not_this_keyring;
        kix = 0;
@@ -619,7 +622,7 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
                                goto too_deep;
 
                        /* stack the current position */
-                       stack[sp].subtree = subtree;
+                       stack[sp].keylist = keylist;
                        stack[sp].kix = kix;
                        sp++;
 
@@ -632,13 +635,10 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
        /* the keyring we're looking at was disqualified or didn't contain a
         * matching key */
  not_this_keyring:
-       read_unlock(&subtree->lock);
-
        if (sp > 0) {
                /* resume the checking of a keyring higher up in the tree */
                sp--;
-               subtree = stack[sp].subtree;
-               keylist = subtree->payload.subscriptions;
+               keylist = stack[sp].keylist;
                kix = stack[sp].kix + 1;
                goto ascend;
        }
@@ -646,30 +646,36 @@ static int keyring_detect_cycle(struct key *A, struct key *B)
        ret = 0; /* no cycles detected */
 
  error:
+       rcu_read_unlock();
        return ret;
 
  too_deep:
        ret = -ELOOP;
-       goto error_unwind;
+       goto error;
+
  cycle_detected:
        ret = -EDEADLK;
- error_unwind:
-       read_unlock(&subtree->lock);
-
-       /* unwind the keyring stack */
-       while (sp > 0) {
-               sp--;
-               read_unlock(&stack[sp].subtree->lock);
-       }
-
        goto error;
 
 } /* end keyring_detect_cycle() */
 
+/*****************************************************************************/
+/*
+ * dispose of a keyring list after the RCU grace period
+ */
+static void keyring_link_rcu_disposal(struct rcu_head *rcu)
+{
+       struct keyring_list *klist =
+               container_of(rcu, struct keyring_list, rcu);
+
+       kfree(klist);
+
+} /* end keyring_link_rcu_disposal() */
+
 /*****************************************************************************/
 /*
  * link a key into to a keyring
- * - must be called with the keyring's semaphore held
+ * - must be called with the keyring's semaphore write-locked
  */
 int __key_link(struct key *keyring, struct key *key)
 {
@@ -679,7 +685,7 @@ int __key_link(struct key *keyring, struct key *key)
        int ret;
 
        ret = -EKEYREVOKED;
-       if (keyring->flags & KEY_FLAG_REVOKED)
+       if (test_bit(KEY_FLAG_REVOKED, &keyring->flags))
                goto error;
 
        ret = -ENOTDIR;
@@ -710,9 +716,10 @@ int __key_link(struct key *keyring, struct key *key)
                /* there's sufficient slack space to add directly */
                atomic_inc(&key->usage);
 
-               write_lock(&keyring->lock);
-               klist->keys[klist->nkeys++] = key;
-               write_unlock(&keyring->lock);
+               klist->keys[klist->nkeys] = key;
+               smp_wmb();
+               klist->nkeys++;
+               smp_wmb();
 
                ret = 0;
        }
@@ -723,6 +730,8 @@ int __key_link(struct key *keyring, struct key *key)
                        max += klist->maxkeys;
 
                ret = -ENFILE;
+               if (max > 65535)
+                       goto error3;
                size = sizeof(*klist) + sizeof(*key) * max;
                if (size > PAGE_SIZE)
                        goto error3;
@@ -743,14 +752,13 @@ int __key_link(struct key *keyring, struct key *key)
 
                /* add the key into the new space */
                atomic_inc(&key->usage);
-
-               write_lock(&keyring->lock);
-               keyring->payload.subscriptions = nklist;
                nklist->keys[nklist->nkeys++] = key;
-               write_unlock(&keyring->lock);
+
+               rcu_assign_pointer(keyring->payload.subscriptions, nklist);
 
                /* dispose of the old keyring list */
-               kfree(klist);
+               if (klist)
+                       call_rcu(&klist->rcu, keyring_link_rcu_disposal);
 
                ret = 0;
        }
@@ -789,13 +797,28 @@ int key_link(struct key *keyring, struct key *key)
 
 EXPORT_SYMBOL(key_link);
 
+/*****************************************************************************/
+/*
+ * dispose of a keyring list after the RCU grace period, freeing the unlinked
+ * key
+ */
+static void keyring_unlink_rcu_disposal(struct rcu_head *rcu)
+{
+       struct keyring_list *klist =
+               container_of(rcu, struct keyring_list, rcu);
+
+       key_put(klist->keys[klist->delkey]);
+       kfree(klist);
+
+} /* end keyring_unlink_rcu_disposal() */
+
 /*****************************************************************************/
 /*
  * unlink the first link to a key from a keyring
  */
 int key_unlink(struct key *keyring, struct key *key)
 {
-       struct keyring_list *klist;
+       struct keyring_list *klist, *nklist;
        int loop, ret;
 
        key_check(keyring);
@@ -819,36 +842,69 @@ int key_unlink(struct key *keyring, struct key *key)
        ret = -ENOENT;
        goto error;
 
- key_is_present:
+key_is_present:
+       /* we need to copy the key list for RCU purposes */
+       nklist = kmalloc(sizeof(*klist) + sizeof(*key) * klist->maxkeys,
+                        GFP_KERNEL);
+       if (!nklist)
+               goto nomem;
+       nklist->maxkeys = klist->maxkeys;
+       nklist->nkeys = klist->nkeys - 1;
+
+       if (loop > 0)
+               memcpy(&nklist->keys[0],
+                      &klist->keys[0],
+                      loop * sizeof(klist->keys[0]));
+
+       if (loop < nklist->nkeys)
+               memcpy(&nklist->keys[loop],
+                      &klist->keys[loop + 1],
+                      (nklist->nkeys - loop) * sizeof(klist->keys[0]));
+
        /* adjust the user's quota */
        key_payload_reserve(keyring,
                            keyring->datalen - KEYQUOTA_LINK_BYTES);
 
-       /* shuffle down the key pointers
-        * - it might be worth shrinking the allocated memory, but that runs
-        *   the risk of ENOMEM as we would have to copy
-        */
-       write_lock(&keyring->lock);
+       rcu_assign_pointer(keyring->payload.subscriptions, nklist);
 
-       klist->nkeys--;
-       if (loop < klist->nkeys)
-               memcpy(&klist->keys[loop],
-                      &klist->keys[loop + 1],
-                      (klist->nkeys - loop) * sizeof(struct key *));
+       up_write(&keyring->sem);
 
-       write_unlock(&keyring->lock);
+       /* schedule for later cleanup */
+       klist->delkey = loop;
+       call_rcu(&klist->rcu, keyring_unlink_rcu_disposal);
 
-       up_write(&keyring->sem);
-       key_put(key);
        ret = 0;
 
- error:
+error:
        return ret;
+nomem:
+       ret = -ENOMEM;
+       up_write(&keyring->sem);
+       goto error;
 
 } /* end key_unlink() */
 
 EXPORT_SYMBOL(key_unlink);
 
+/*****************************************************************************/
+/*
+ * dispose of a keyring list after the RCU grace period, releasing the keys it
+ * links to
+ */
+static void keyring_clear_rcu_disposal(struct rcu_head *rcu)
+{
+       struct keyring_list *klist;
+       int loop;
+
+       klist = container_of(rcu, struct keyring_list, rcu);
+
+       for (loop = klist->nkeys - 1; loop >= 0; loop--)
+               key_put(klist->keys[loop]);
+
+       kfree(klist);
+
+} /* end keyring_clear_rcu_disposal() */
+
 /*****************************************************************************/
 /*
  * clear the specified process keyring
@@ -857,7 +913,7 @@ EXPORT_SYMBOL(key_unlink);
 int keyring_clear(struct key *keyring)
 {
        struct keyring_list *klist;
-       int loop, ret;
+       int ret;
 
        ret = -ENOTDIR;
        if (keyring->type == &key_type_keyring) {
@@ -870,20 +926,15 @@ int keyring_clear(struct key *keyring)
                        key_payload_reserve(keyring,
                                            sizeof(struct keyring_list));
 
-                       write_lock(&keyring->lock);
-                       keyring->payload.subscriptions = NULL;
-                       write_unlock(&keyring->lock);
+                       rcu_assign_pointer(keyring->payload.subscriptions,
+                                          NULL);
                }
 
                up_write(&keyring->sem);
 
                /* free the keys after the locks have been dropped */
-               if (klist) {
-                       for (loop = klist->nkeys - 1; loop >= 0; loop--)
-                               key_put(klist->keys[loop]);
-
-                       kfree(klist);
-               }
+               if (klist)
+                       call_rcu(&klist->rcu, keyring_clear_rcu_disposal);
 
                ret = 0;
        }