/* 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 unsigned long key_gc_executing; static time_t key_gc_next_run = LONG_MAX; /* * 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); gc_at += key_gc_delay; if (now >= gc_at) { 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(keyring->payload.subscriptions); 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; kenter(""); if (test_and_set_bit(0, &key_gc_executing)) { key_schedule_gc(current_kernel_time().tv_sec); return; } limit = current_kernel_time().tv_sec; if (limit > key_gc_delay) limit -= key_gc_delay; else limit = key_gc_delay; spin_lock(&key_serial_lock); if (RB_EMPTY_ROOT(&key_serial_tree)) goto reached_the_end; cursor = key_gc_cursor; if (cursor < 0) cursor = 0; /* 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 > 0 && key->expiry < new_timer) 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; } rb = rb_next(&key->serial_node); if (!rb) { key_gc_cursor = 0; break; } key = rb_entry(rb, struct key, serial_node); } out: spin_unlock(&key_serial_lock); no_unlock: clear_bit(0, &key_gc_executing); if (new_timer < LONG_MAX) key_schedule_gc(new_timer); kleave(""); return; reached_the_end: key_gc_cursor = 0; goto out; }