From 65d87fe68abf2fc226a9e96be61160f65d6b4680 Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 May 2012 10:56:56 +0100 Subject: KEYS: Perform RCU synchronisation on keys prior to key destruction Make the keys garbage collector invoke synchronize_rcu() prior to destroying keys with a zero usage count. This means that a key can be examined under the RCU read lock in the safe knowledge that it won't get deallocated until after the lock is released - even if its usage count becomes zero whilst we're looking at it. This is useful in keyring search vs key link. Consider a keyring containing a link to a key. That link can be replaced in-place in the keyring without requiring an RCU copy-and-replace on the keyring contents without breaking a search underway on that keyring when the displaced key is released, provided the key is actually destroyed only after the RCU read lock held by the search algorithm is released. This permits __key_link() to replace a key without having to reallocate the key payload. A key gets replaced if a new key being linked into a keyring has the same type and description. Signed-off-by: David Howells Acked-by: Jeff Layton --- security/keys/gc.c | 73 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 29 deletions(-) (limited to 'security/keys/gc.c') diff --git a/security/keys/gc.c b/security/keys/gc.c index a42b45531aac..27610bf72195 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -168,38 +168,45 @@ do_gc: } /* - * Garbage collect an unreferenced, detached key + * Garbage collect a list of unreferenced, detached keys */ -static noinline void key_gc_unused_key(struct key *key) +static noinline void key_gc_unused_keys(struct list_head *keys) { - key_check(key); - - security_key_free(key); - - /* deal with the user's key tracking and quota */ - if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - spin_lock(&key->user->lock); - key->user->qnkeys--; - key->user->qnbytes -= key->quotalen; - spin_unlock(&key->user->lock); - } + while (!list_empty(keys)) { + struct key *key = + list_entry(keys->next, struct key, graveyard_link); + list_del(&key->graveyard_link); + + kdebug("- %u", key->serial); + key_check(key); + + security_key_free(key); + + /* deal with the user's key tracking and quota */ + if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { + spin_lock(&key->user->lock); + key->user->qnkeys--; + key->user->qnbytes -= key->quotalen; + spin_unlock(&key->user->lock); + } - atomic_dec(&key->user->nkeys); - if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) - atomic_dec(&key->user->nikeys); + atomic_dec(&key->user->nkeys); + if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) + atomic_dec(&key->user->nikeys); - key_user_put(key->user); + key_user_put(key->user); - /* now throw away the key memory */ - if (key->type->destroy) - key->type->destroy(key); + /* now throw away the key memory */ + if (key->type->destroy) + key->type->destroy(key); - kfree(key->description); + kfree(key->description); #ifdef KEY_DEBUGGING - key->magic = KEY_DEBUG_MAGIC_X; + key->magic = KEY_DEBUG_MAGIC_X; #endif - kmem_cache_free(key_jar, key); + kmem_cache_free(key_jar, key); + } } /* @@ -211,6 +218,7 @@ static noinline void key_gc_unused_key(struct key *key) */ static void key_garbage_collector(struct work_struct *work) { + static LIST_HEAD(graveyard); static u8 gc_state; /* Internal persistent state */ #define KEY_GC_REAP_AGAIN 0x01 /* - Need another cycle */ #define KEY_GC_REAPING_LINKS 0x02 /* - We need to reap links */ @@ -316,15 +324,22 @@ maybe_resched: key_schedule_gc(new_timer); } - 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. + if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2) || + !list_empty(&graveyard)) { + /* Make sure that all pending keyring payload destructions are + * fulfilled and that people aren't now looking at dead or + * dying keys that they don't have a reference upon or a link + * to. */ - kdebug("dead sync"); + kdebug("gc sync"); synchronize_rcu(); } + if (!list_empty(&graveyard)) { + kdebug("gc keys"); + key_gc_unused_keys(&graveyard); + } + if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2))) { if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { @@ -359,7 +374,7 @@ found_unreferenced_key: rb_erase(&key->serial_node, &key_serial_tree); spin_unlock(&key_serial_lock); - key_gc_unused_key(key); + list_add_tail(&key->graveyard_link, &graveyard); gc_state |= KEY_GC_REAP_AGAIN; goto maybe_resched; -- cgit v1.2.3 From 233e4735f2a45d9e641c2488b8d7afeb1f377dac Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 May 2012 10:56:56 +0100 Subject: KEYS: Permit in-place link replacement in keyring list Make use of the previous patch that makes the garbage collector perform RCU synchronisation before destroying defunct keys. Key pointers can now be replaced in-place without creating a new keyring payload and replacing the whole thing as the discarded keys will not be destroyed until all currently held RCU read locks are released. If the keyring payload space needs to be expanded or contracted, then a replacement will still need allocating, and the original will still have to be freed by RCU. Signed-off-by: David Howells --- security/keys/gc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security/keys/gc.c') diff --git a/security/keys/gc.c b/security/keys/gc.c index 27610bf72195..adddaa258d50 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -148,7 +148,7 @@ static void key_gc_keyring(struct key *keyring, time_t limit) loop = klist->nkeys; smp_rmb(); for (loop--; loop >= 0; loop--) { - key = klist->keys[loop]; + key = rcu_dereference(klist->keys[loop]); if (test_bit(KEY_FLAG_DEAD, &key->flags) || (key->expiry > 0 && key->expiry <= limit)) goto do_gc; -- cgit v1.2.3 From fd75815f727f157a05f4c96b5294a4617c0557da Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 11 May 2012 10:56:56 +0100 Subject: KEYS: Add invalidation support Add support for invalidating a key - which renders it immediately invisible to further searches and causes the garbage collector to immediately wake up, remove it from keyrings and then destroy it when it's no longer referenced. It's better not to do this with keyctl_revoke() as that marks the key to start returning -EKEYREVOKED to searches when what is actually desired is to have the key refetched. To invalidate a key the caller must be granted SEARCH permission by the key. This may be too strict. It may be better to also permit invalidation if the caller has any of READ, WRITE or SETATTR permission. The primary use for this is to evict keys that are cached in special keyrings, such as the DNS resolver or an ID mapper. Signed-off-by: David Howells --- security/keys/gc.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'security/keys/gc.c') diff --git a/security/keys/gc.c b/security/keys/gc.c index adddaa258d50..61ab7c82ebb1 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -71,6 +71,15 @@ void key_schedule_gc(time_t gc_at) } } +/* + * Schedule a dead links collection run. + */ +void key_schedule_gc_links(void) +{ + set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); + queue_work(system_nrt_wq, &key_gc_work); +} + /* * Some key's cleanup time was met after it expired, so we need to get the * reaper to go through a cycle finding expired keys. @@ -79,8 +88,7 @@ static void key_gc_timer_func(unsigned long data) { kenter(""); key_gc_next_run = LONG_MAX; - set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); - queue_work(system_nrt_wq, &key_gc_work); + key_schedule_gc_links(); } /* @@ -131,12 +139,12 @@ void key_gc_keytype(struct key_type *ktype) static void key_gc_keyring(struct key *keyring, time_t limit) { struct keyring_list *klist; - struct key *key; int loop; kenter("%x", key_serial(keyring)); - if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) + if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) goto dont_gc; /* scan the keyring looking for dead keys */ @@ -148,9 +156,8 @@ static void key_gc_keyring(struct key *keyring, time_t limit) loop = klist->nkeys; smp_rmb(); for (loop--; loop >= 0; loop--) { - key = rcu_dereference(klist->keys[loop]); - if (test_bit(KEY_FLAG_DEAD, &key->flags) || - (key->expiry > 0 && key->expiry <= limit)) + struct key *key = rcu_dereference(klist->keys[loop]); + if (key_is_dead(key, limit)) goto do_gc; } -- cgit v1.2.3