Commit 0c061b57 authored by David Howells's avatar David Howells Committed by James Morris

KEYS: Correctly destroy key payloads when their keytype is removed

unregister_key_type() has code to mark a key as dead and make it unavailable in
one loop and then destroy all those unavailable key payloads in the next loop.
However, the loop to mark keys dead renders the key undetectable to the second
loop by changing the key type pointer also.

Fix this by the following means:

 (1) The key code has two garbage collectors: one deletes unreferenced keys and
     the other alters keyrings to delete links to old dead, revoked and expired
     keys.  They can end up holding each other up as both want to scan the key
     serial tree under spinlock.  Combine these into a single routine.

 (2) Move the dead key marking, dead link removal and dead key removal into the
     garbage collector as a three phase process running over the three cycles
     of the normal garbage collection procedure.  This is tracked by the
     KEY_GC_REAPING_DEAD_1, _2 and _3 state flags.

     unregister_key_type() then just unlinks the key type from the list, wakes
     up the garbage collector and waits for the third phase to complete.

 (3) Downgrade the key types sem in unregister_key_type() once it has deleted
     the key type from the list so that it doesn't block the keyctl() syscall.

 (4) Dead keys that cannot be simply removed in the third phase have their
     payloads destroyed with the key's semaphore write-locked to prevent
     interference by the keyctl() syscall.  There should be no in-kernel users
     of dead keys of that type by the point of unregistration, though keyctl()
     may be holding a reference.

 (5) Only perform timer recalculation in the GC if the timer actually expired.
     If it didn't, we'll get another cycle when it goes off - and if the key
     that actually triggered it has been removed, it's not a problem.

 (6) Only garbage collect link if the timer expired or if we're doing dead key
     clean up phase 2.

 (7) As only key_garbage_collector() is permitted to use rb_erase() on the key
     serial tree, it doesn't need to revalidate its cursor after dropping the
     spinlock as the node the cursor points to must still exist in the tree.

 (8) Drop the spinlock in the GC if there is contention on it or if we need to
     reschedule.  After dealing with that, get the spinlock again and resume
     scanning.

This has been tested in the following ways:

 (1) Run the keyutils testsuite against it.

 (2) Using the AF_RXRPC and RxKAD modules to test keytype removal:

     Load the rxrpc_s key type:

	# insmod /tmp/af-rxrpc.ko
	# insmod /tmp/rxkad.ko

     Create a key (http://people.redhat.com/~dhowells/rxrpc/listen.c):

	# /tmp/listen &
	[1] 8173

     Find the key:

	# grep rxrpc_s /proc/keys
	091086e1 I--Q--     1 perm 39390000     0     0 rxrpc_s   52:2

     Link it to a session keyring, preferably one with a higher serial number:

	# keyctl link 0x20e36251 @s

     Kill the process (the key should remain as it's linked to another place):

	# fg
	/tmp/listen
	^C

     Remove the key type:

	rmmod rxkad
	rmmod af-rxrpc

     This can be made a more effective test by altering the following part of
     the patch:

	if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) {
		/* Make sure everyone revalidates their keys if we marked a
		 * bunch as being dead and make sure all keyring ex-payloads
		 * are destroyed.
		 */
		kdebug("dead sync");
		synchronize_rcu();

     To call synchronize_rcu() in GC phase 1 instead.  That causes that the
     keyring's old payload content to hang around longer until it's RCU
     destroyed - which usually happens after GC phase 3 is complete.  This
     allows the destroy_dead_key branch to be tested.
Reported-by: default avatarBenjamin Coddington <bcodding@gmail.com>
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Signed-off-by: default avatarJames Morris <jmorris@namei.org>
parent d199798b
This diff is collapsed.
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__) no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__)
#endif #endif
extern struct key_type key_type_dead;
extern struct key_type key_type_user; extern struct key_type key_type_user;
/*****************************************************************************/ /*****************************************************************************/
...@@ -147,10 +148,11 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, ...@@ -147,10 +148,11 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
extern long join_session_keyring(const char *name); extern long join_session_keyring(const char *name);
extern struct work_struct key_gc_unused_work; extern struct work_struct key_gc_work;
extern unsigned key_gc_delay; extern unsigned key_gc_delay;
extern void keyring_gc(struct key *keyring, time_t limit); extern void keyring_gc(struct key *keyring, time_t limit);
extern void key_schedule_gc(time_t expiry_at); extern void key_schedule_gc(time_t expiry_at);
extern void key_gc_keytype(struct key_type *ktype);
extern int key_task_permission(const key_ref_t key_ref, extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred, const struct cred *cred,
......
...@@ -39,11 +39,6 @@ static DECLARE_RWSEM(key_types_sem); ...@@ -39,11 +39,6 @@ static DECLARE_RWSEM(key_types_sem);
/* We serialise key instantiation and link */ /* We serialise key instantiation and link */
DEFINE_MUTEX(key_construction_mutex); DEFINE_MUTEX(key_construction_mutex);
/* Any key who's type gets unegistered will be re-typed to this */
static struct key_type key_type_dead = {
.name = "dead",
};
#ifdef KEY_DEBUGGING #ifdef KEY_DEBUGGING
void __key_check(const struct key *key) void __key_check(const struct key *key)
{ {
...@@ -602,7 +597,7 @@ void key_put(struct key *key) ...@@ -602,7 +597,7 @@ void key_put(struct key *key)
key_check(key); key_check(key);
if (atomic_dec_and_test(&key->usage)) if (atomic_dec_and_test(&key->usage))
queue_work(system_nrt_wq, &key_gc_unused_work); queue_work(system_nrt_wq, &key_gc_work);
} }
} }
EXPORT_SYMBOL(key_put); EXPORT_SYMBOL(key_put);
...@@ -980,49 +975,11 @@ EXPORT_SYMBOL(register_key_type); ...@@ -980,49 +975,11 @@ EXPORT_SYMBOL(register_key_type);
*/ */
void unregister_key_type(struct key_type *ktype) void unregister_key_type(struct key_type *ktype)
{ {
struct rb_node *_n;
struct key *key;
down_write(&key_types_sem); down_write(&key_types_sem);
/* withdraw the key type */
list_del_init(&ktype->link); list_del_init(&ktype->link);
downgrade_write(&key_types_sem);
/* mark all the keys of this type dead */ key_gc_keytype(ktype);
spin_lock(&key_serial_lock); up_read(&key_types_sem);
for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) {
key = rb_entry(_n, struct key, serial_node);
if (key->type == ktype) {
key->type = &key_type_dead;
set_bit(KEY_FLAG_DEAD, &key->flags);
}
}
spin_unlock(&key_serial_lock);
/* make sure everyone revalidates their keys */
synchronize_rcu();
/* we should now be able to destroy the payloads of all the keys of
* this type with impunity */
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 (key->type == ktype) {
if (ktype->destroy)
ktype->destroy(key);
memset(&key->payload, KEY_DESTROY, sizeof(key->payload));
}
}
spin_unlock(&key_serial_lock);
up_write(&key_types_sem);
key_schedule_gc(0);
} }
EXPORT_SYMBOL(unregister_key_type); EXPORT_SYMBOL(unregister_key_type);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment