| /* 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 <linux/module.h> | |
| #include <keys/keyring-type.h> | |
| #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. | |
| * - time 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 */ | |
| rcu_read_lock(); | |
| klist = rcu_dereference(keyring->payload.subscriptions); | |
| if (!klist) | |
| goto unlock_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; | |
| } | |
| 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); | |
| 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]"); | |
| } |