KEYS: The dead key link reaper should be non-reentrant
[linux-2.6.git] / security / keys / gc.c
index 1e616ae..d67e88b 100644 (file)
@@ -10,6 +10,8 @@
  */
 
 #include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/security.h>
 #include <keys/keyring-type.h>
 #include "internal.h"
 
 unsigned key_gc_delay = 5 * 60;
 
 /*
- * Reaper
+ * Reaper for unused keys.
+ */
+static void key_gc_unused_keys(struct work_struct *work);
+DECLARE_WORK(key_gc_unused_work, key_gc_unused_keys);
+
+/*
+ * Reaper for links from keyrings to dead keys.
  */
 static void key_gc_timer_func(unsigned long);
-static void key_garbage_collector(struct work_struct *);
+static void key_gc_dead_links(struct work_struct *);
 static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0);
-static DECLARE_WORK(key_gc_work, key_garbage_collector);
+static DECLARE_WORK(key_gc_work, key_gc_dead_links);
 static key_serial_t key_gc_cursor; /* the last key the gc considered */
+static bool key_gc_again;
 static unsigned long key_gc_executing;
 static time_t key_gc_next_run = LONG_MAX;
+static time_t key_gc_new_timer;
 
 /*
- * Schedule a garbage collection run
- * - precision isn't particularly important
+ * Schedule a garbage collection run.
+ * - time precision isn't particularly important
  */
 void key_schedule_gc(time_t gc_at)
 {
@@ -40,10 +50,8 @@ void key_schedule_gc(time_t gc_at)
 
        kenter("%ld", gc_at - now);
 
-       gc_at += key_gc_delay;
-
-       if (now >= gc_at) {
-               schedule_work(&key_gc_work);
+       if (gc_at <= now) {
+               queue_work(system_nrt_wq, &key_gc_work);
        } else if (gc_at < key_gc_next_run) {
                expires = jiffies + (gc_at - now) * HZ;
                mod_timer(&key_gc_timer, expires);
@@ -57,12 +65,13 @@ static void key_gc_timer_func(unsigned long data)
 {
        kenter("");
        key_gc_next_run = LONG_MAX;
-       schedule_work(&key_gc_work);
+       queue_work(system_nrt_wq, &key_gc_work);
 }
 
 /*
- * Garbage collect pointers from a keyring
- * - return true if we altered the keyring
+ * Garbage collect pointers from a keyring.
+ *
+ * Return true if we altered the keyring.
  */
 static bool key_gc_keyring(struct key *keyring, time_t limit)
        __releases(key_serial_lock)
@@ -77,9 +86,10 @@ static bool key_gc_keyring(struct key *keyring, time_t limit)
                goto dont_gc;
 
        /* scan the keyring looking for dead keys */
+       rcu_read_lock();
        klist = rcu_dereference(keyring->payload.subscriptions);
        if (!klist)
-               goto dont_gc;
+               goto unlock_dont_gc;
 
        for (loop = klist->nkeys - 1; loop >= 0; loop--) {
                key = klist->keys[loop];
@@ -88,11 +98,14 @@ static bool key_gc_keyring(struct key *keyring, time_t limit)
                        goto do_gc;
        }
 
+unlock_dont_gc:
+       rcu_read_unlock();
 dont_gc:
        kleave(" = false");
        return false;
 
 do_gc:
+       rcu_read_unlock();
        key_gc_cursor = keyring->serial;
        key_get(keyring);
        spin_unlock(&key_serial_lock);
@@ -103,25 +116,28 @@ do_gc:
 }
 
 /*
- * Garbage collector for keys
- * - this involves scanning the keyrings for dead, expired and revoked keys
- *   that have overstayed their welcome
+ * Garbage collector for links to dead keys.
+ *
+ * This involves scanning the keyrings for dead, expired and revoked keys that
+ * have overstayed their welcome
  */
-static void key_garbage_collector(struct work_struct *work)
+static void key_gc_dead_links(struct work_struct *work)
 {
        struct rb_node *rb;
        key_serial_t cursor;
        struct key *key, *xkey;
-       time_t new_timer = LONG_MAX, limit;
+       time_t new_timer = LONG_MAX, limit, now;
 
-       kenter("");
+       now = current_kernel_time().tv_sec;
+       kenter("[%x,%ld]", key_gc_cursor, key_gc_new_timer - now);
 
        if (test_and_set_bit(0, &key_gc_executing)) {
-               key_schedule_gc(current_kernel_time().tv_sec);
+               key_schedule_gc(current_kernel_time().tv_sec + 1);
+               kleave(" [busy; deferring]");
                return;
        }
 
-       limit = current_kernel_time().tv_sec;
+       limit = now;
        if (limit > key_gc_delay)
                limit -= key_gc_delay;
        else
@@ -129,12 +145,19 @@ static void key_garbage_collector(struct work_struct *work)
 
        spin_lock(&key_serial_lock);
 
-       if (RB_EMPTY_ROOT(&key_serial_tree))
-               goto reached_the_end;
+       if (unlikely(RB_EMPTY_ROOT(&key_serial_tree))) {
+               spin_unlock(&key_serial_lock);
+               clear_bit(0, &key_gc_executing);
+               return;
+       }
 
        cursor = key_gc_cursor;
        if (cursor < 0)
                cursor = 0;
+       if (cursor > 0)
+               new_timer = key_gc_new_timer;
+       else
+               key_gc_again = false;
 
        /* find the first key above the cursor */
        key = NULL;
@@ -160,35 +183,115 @@ static void key_garbage_collector(struct work_struct *work)
 
        /* trawl through the keys looking for keyrings */
        for (;;) {
-               if (key->expiry > 0 && key->expiry < new_timer)
+               if (key->expiry > limit && key->expiry < new_timer) {
+                       kdebug("will expire %x in %ld",
+                              key_serial(key), key->expiry - limit);
                        new_timer = key->expiry;
+               }
 
                if (key->type == &key_type_keyring &&
-                   key_gc_keyring(key, limit)) {
-                       /* the gc ate our lock */
-                       schedule_work(&key_gc_work);
-                       goto no_unlock;
-               }
+                   key_gc_keyring(key, limit))
+                       /* the gc had to release our lock so that the keyring
+                        * could be modified, so we have to get it again */
+                       goto gc_released_our_lock;
 
                rb = rb_next(&key->serial_node);
-               if (!rb) {
-                       key_gc_cursor = 0;
-                       break;
-               }
+               if (!rb)
+                       goto reached_the_end;
                key = rb_entry(rb, struct key, serial_node);
        }
 
-out:
+gc_released_our_lock:
+       kdebug("gc_released_our_lock");
+       key_gc_new_timer = new_timer;
+       key_gc_again = true;
+       clear_bit(0, &key_gc_executing);
+       queue_work(system_nrt_wq, &key_gc_work);
+       kleave(" [continue]");
+       return;
+
+       /* when we reach the end of the run, we set the timer for the next one */
+reached_the_end:
+       kdebug("reached_the_end");
        spin_unlock(&key_serial_lock);
-no_unlock:
+       key_gc_new_timer = new_timer;
+       key_gc_cursor = 0;
        clear_bit(0, &key_gc_executing);
-       if (new_timer < LONG_MAX)
+
+       if (key_gc_again) {
+               /* there may have been a key that expired whilst we were
+                * scanning, so if we discarded any links we should do another
+                * scan */
+               new_timer = now + 1;
                key_schedule_gc(new_timer);
+       } else if (new_timer < LONG_MAX) {
+               new_timer += key_gc_delay;
+               key_schedule_gc(new_timer);
+       }
+       kleave(" [end]");
+}
 
-       kleave("");
+/*
+ * Garbage collector for unused keys.
+ *
+ * This is done in process context so that we don't have to disable interrupts
+ * all over the place.  key_put() schedules this rather than trying to do the
+ * cleanup itself, which means key_put() doesn't have to sleep.
+ */
+static void key_gc_unused_keys(struct work_struct *work)
+{
+       struct rb_node *_n;
+       struct key *key;
+
+go_again:
+       /* look for a dead key in the tree */
+       spin_lock(&key_serial_lock);
+
+       for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) {
+               key = rb_entry(_n, struct key, serial_node);
+
+               if (atomic_read(&key->usage) == 0)
+                       goto found_dead_key;
+       }
+
+       spin_unlock(&key_serial_lock);
        return;
 
-reached_the_end:
-       key_gc_cursor = 0;
-       goto out;
+found_dead_key:
+       /* we found a dead key - once we've removed it from the tree, we can
+        * drop the lock */
+       rb_erase(&key->serial_node, &key_serial_tree);
+       spin_unlock(&key_serial_lock);
+
+       key_check(key);
+
+       security_key_free(key);
+
+       /* deal with the user's key tracking and quota */
+       if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) {
+               spin_lock(&key->user->lock);
+               key->user->qnkeys--;
+               key->user->qnbytes -= key->quotalen;
+               spin_unlock(&key->user->lock);
+       }
+
+       atomic_dec(&key->user->nkeys);
+       if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags))
+               atomic_dec(&key->user->nikeys);
+
+       key_user_put(key->user);
+
+       /* now throw away the key memory */
+       if (key->type->destroy)
+               key->type->destroy(key);
+
+       kfree(key->description);
+
+#ifdef KEY_DEBUGGING
+       key->magic = KEY_DEBUG_MAGIC_X;
+#endif
+       kmem_cache_free(key_jar, key);
+
+       /* there may, of course, be more than one key to destroy */
+       goto go_again;
 }