aboutsummaryrefslogtreecommitdiffstats
path: root/security/keys/key.c
diff options
context:
space:
mode:
authorDavid Howells <dhowells@redhat.com>2011-08-22 09:09:36 -0400
committerJames Morris <jmorris@namei.org>2011-08-22 19:57:37 -0400
commit0c061b5707ab84ebfe8f18f1c9c3110ae5cd6073 (patch)
treecb6e83458126f3cc9ef9f5504937c8445f790b0f /security/keys/key.c
parentd199798bdf969873f78d48140600ff0a98a87e69 (diff)
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: Benjamin Coddington <bcodding@gmail.com> Signed-off-by: David Howells <dhowells@redhat.com> Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security/keys/key.c')
-rw-r--r--security/keys/key.c51
1 files changed, 4 insertions, 47 deletions
diff --git a/security/keys/key.c b/security/keys/key.c
index 1f3ed44a83c0..4414abddcb5b 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -39,11 +39,6 @@ static DECLARE_RWSEM(key_types_sem);
39/* We serialise key instantiation and link */ 39/* We serialise key instantiation and link */
40DEFINE_MUTEX(key_construction_mutex); 40DEFINE_MUTEX(key_construction_mutex);
41 41
42/* Any key who's type gets unegistered will be re-typed to this */
43static struct key_type key_type_dead = {
44 .name = "dead",
45};
46
47#ifdef KEY_DEBUGGING 42#ifdef KEY_DEBUGGING
48void __key_check(const struct key *key) 43void __key_check(const struct key *key)
49{ 44{
@@ -602,7 +597,7 @@ void key_put(struct key *key)
602 key_check(key); 597 key_check(key);
603 598
604 if (atomic_dec_and_test(&key->usage)) 599 if (atomic_dec_and_test(&key->usage))
605 queue_work(system_nrt_wq, &key_gc_unused_work); 600 queue_work(system_nrt_wq, &key_gc_work);
606 } 601 }
607} 602}
608EXPORT_SYMBOL(key_put); 603EXPORT_SYMBOL(key_put);
@@ -980,49 +975,11 @@ EXPORT_SYMBOL(register_key_type);
980 */ 975 */
981void unregister_key_type(struct key_type *ktype) 976void unregister_key_type(struct key_type *ktype)
982{ 977{
983 struct rb_node *_n;
984 struct key *key;
985
986 down_write(&key_types_sem); 978 down_write(&key_types_sem);
987
988 /* withdraw the key type */
989 list_del_init(&ktype->link); 979 list_del_init(&ktype->link);
990 980 downgrade_write(&key_types_sem);
991 /* mark all the keys of this type dead */ 981 key_gc_keytype(ktype);
992 spin_lock(&key_serial_lock); 982 up_read(&key_types_sem);
993
994 for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) {
995 key = rb_entry(_n, struct key, serial_node);
996
997 if (key->type == ktype) {
998 key->type = &key_type_dead;
999 set_bit(KEY_FLAG_DEAD, &key->flags);
1000 }
1001 }
1002
1003 spin_unlock(&key_serial_lock);
1004
1005 /* make sure everyone revalidates their keys */
1006 synchronize_rcu();
1007
1008 /* we should now be able to destroy the payloads of all the keys of
1009 * this type with impunity */
1010 spin_lock(&key_serial_lock);
1011
1012 for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) {
1013 key = rb_entry(_n, struct key, serial_node);
1014
1015 if (key->type == ktype) {
1016 if (ktype->destroy)
1017 ktype->destroy(key);
1018 memset(&key->payload, KEY_DESTROY, sizeof(key->payload));
1019 }
1020 }
1021
1022 spin_unlock(&key_serial_lock);
1023 up_write(&key_types_sem);
1024
1025 key_schedule_gc(0);
1026} 983}
1027EXPORT_SYMBOL(unregister_key_type); 984EXPORT_SYMBOL(unregister_key_type);
1028 985