/* Key garbage collector * * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #include #include #include "internal.h" /* * Delay between key revocation/expiry in seconds */ unsigned key_gc_delay = 5 * 60; /* * Reaper */ static void key_gc_timer_func(unsigned long); static void key_garbage_collector(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 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 */ void key_schedule_gc(time_t gc_at) { unsigned long expires; time_t now = current_kernel_time().tv_sec; kenter("%ld", gc_at - now); if (gc_at <= now) { schedule_work(&key_gc_work); } else if (gc_at < key_gc_next_run) { expires = jiffies + (gc_at - now) * HZ; mod_timer(&key_gc_timer, expires); } } /* * The garbage collector timer kicked off */ static void key_gc_timer_func(unsigned long data) { kenter(""); key_gc_next_run = LONG_MAX; schedule_work(&key_gc_work); } /* * 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) { struct keyring_list *klist; struct key *key; int loop; kenter("%x", key_serial(keyring)); if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) goto dont_gc; /* scan the keyring looking for dead keys */ klist = rcu_dereference_check(keyring->payload.subscriptions, lockdep_is_held(&key_serial_lock)); if (!klist) goto dont_gc; for (loop = klist->nkeys - 1; loop >= 0; loop--) { key = klist->keys[loop]; if (test_bit(KEY_FLAG_DEAD, &key->flags) || (key->expiry > 0 && key->expiry <= limit)) goto do_gc; } dont_gc: kleave(" = false"); return false; do_gc: key_gc_cursor = keyring->serial; key_get(keyring); spin_unlock(&key_serial_lock); keyring_gc(keyring, limit); key_put(keyring); kleave(" = true"); return true; } /* * Garbage collector for 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) { struct rb_node *rb; key_serial_t cursor; struct key *key, *xkey; time_t new_timer = LONG_MAX, limit, now; 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 + 1); kleave(" [busy; deferring]"); return; } limit = now; if (limit > key_gc_delay) limit -= key_gc_delay; else limit = key_gc_delay; spin_lock(&key_serial_lock); 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; rb = key_serial_tree.rb_node; while (rb) { xkey = rb_entry(rb, struct key, serial_node); if (cursor < xkey->serial) { key = xkey; rb = rb->rb_left; } else if (cursor > xkey->serial) { rb = rb->rb_right; } else { rb = rb_next(rb); if (!rb) goto reached_the_end; key = rb_entry(rb, struct key, serial_node); break; } } if (!key) goto reached_the_end; /* trawl through the keys looking for keyrings */ for (;;) { 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 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) goto reached_the_end; key = rb_entry(rb, struct key, serial_node); } 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); schedule_work(&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); key_gc_new_timer = new_timer; key_gc_cursor = 0; clear_bit(0, &key_gc_executing); 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]"); }