| /* 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]"); | 
 | } |