diff options
Diffstat (limited to 'security/keys')
-rw-r--r-- | security/keys/Makefile | 1 | ||||
-rw-r--r-- | security/keys/gc.c | 193 | ||||
-rw-r--r-- | security/keys/internal.h | 4 | ||||
-rw-r--r-- | security/keys/key.c | 14 | ||||
-rw-r--r-- | security/keys/keyctl.c | 1 | ||||
-rw-r--r-- | security/keys/keyring.c | 85 | ||||
-rw-r--r-- | security/keys/sysctl.c | 28 |
7 files changed, 322 insertions, 4 deletions
diff --git a/security/keys/Makefile b/security/keys/Makefile index 747a464943af..74d5447d7df7 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile | |||
@@ -3,6 +3,7 @@ | |||
3 | # | 3 | # |
4 | 4 | ||
5 | obj-y := \ | 5 | obj-y := \ |
6 | gc.o \ | ||
6 | key.o \ | 7 | key.o \ |
7 | keyring.o \ | 8 | keyring.o \ |
8 | keyctl.o \ | 9 | keyctl.o \ |
diff --git a/security/keys/gc.c b/security/keys/gc.c new file mode 100644 index 000000000000..44adc325e15c --- /dev/null +++ b/security/keys/gc.c | |||
@@ -0,0 +1,193 @@ | |||
1 | /* Key garbage collector | ||
2 | * | ||
3 | * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved. | ||
4 | * Written by David Howells (dhowells@redhat.com) | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or | ||
7 | * modify it under the terms of the GNU General Public Licence | ||
8 | * as published by the Free Software Foundation; either version | ||
9 | * 2 of the Licence, or (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <keys/keyring-type.h> | ||
14 | #include "internal.h" | ||
15 | |||
16 | /* | ||
17 | * Delay between key revocation/expiry in seconds | ||
18 | */ | ||
19 | unsigned key_gc_delay = 5 * 60; | ||
20 | |||
21 | /* | ||
22 | * Reaper | ||
23 | */ | ||
24 | static void key_gc_timer_func(unsigned long); | ||
25 | static void key_garbage_collector(struct work_struct *); | ||
26 | static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); | ||
27 | static DECLARE_WORK(key_gc_work, key_garbage_collector); | ||
28 | static key_serial_t key_gc_cursor; /* the last key the gc considered */ | ||
29 | static unsigned long key_gc_executing; | ||
30 | static time_t key_gc_next_run = LONG_MAX; | ||
31 | |||
32 | /* | ||
33 | * Schedule a garbage collection run | ||
34 | * - precision isn't particularly important | ||
35 | */ | ||
36 | void key_schedule_gc(time_t gc_at) | ||
37 | { | ||
38 | unsigned long expires; | ||
39 | time_t now = current_kernel_time().tv_sec; | ||
40 | |||
41 | kenter("%ld", gc_at - now); | ||
42 | |||
43 | gc_at += key_gc_delay; | ||
44 | |||
45 | if (now >= gc_at) { | ||
46 | schedule_work(&key_gc_work); | ||
47 | } else if (gc_at < key_gc_next_run) { | ||
48 | expires = jiffies + (gc_at - now) * HZ; | ||
49 | mod_timer(&key_gc_timer, expires); | ||
50 | } | ||
51 | } | ||
52 | |||
53 | /* | ||
54 | * The garbage collector timer kicked off | ||
55 | */ | ||
56 | static void key_gc_timer_func(unsigned long data) | ||
57 | { | ||
58 | kenter(""); | ||
59 | key_gc_next_run = LONG_MAX; | ||
60 | schedule_work(&key_gc_work); | ||
61 | } | ||
62 | |||
63 | /* | ||
64 | * Garbage collect pointers from a keyring | ||
65 | * - return true if we altered the keyring | ||
66 | */ | ||
67 | static bool key_gc_keyring(struct key *keyring, time_t limit) | ||
68 | { | ||
69 | struct keyring_list *klist; | ||
70 | struct key *key; | ||
71 | int loop; | ||
72 | |||
73 | kenter("%x", key_serial(keyring)); | ||
74 | |||
75 | if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) | ||
76 | goto dont_gc; | ||
77 | |||
78 | /* scan the keyring looking for dead keys */ | ||
79 | klist = rcu_dereference(keyring->payload.subscriptions); | ||
80 | if (!klist) | ||
81 | goto dont_gc; | ||
82 | |||
83 | for (loop = klist->nkeys - 1; loop >= 0; loop--) { | ||
84 | key = klist->keys[loop]; | ||
85 | if (test_bit(KEY_FLAG_DEAD, &key->flags) || | ||
86 | (key->expiry > 0 && key->expiry <= limit)) | ||
87 | goto do_gc; | ||
88 | } | ||
89 | |||
90 | dont_gc: | ||
91 | kleave(" = false"); | ||
92 | return false; | ||
93 | |||
94 | do_gc: | ||
95 | key_gc_cursor = keyring->serial; | ||
96 | key_get(keyring); | ||
97 | spin_unlock(&key_serial_lock); | ||
98 | keyring_gc(keyring, limit); | ||
99 | key_put(keyring); | ||
100 | kleave(" = true"); | ||
101 | return true; | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Garbage collector for keys | ||
106 | * - this involves scanning the keyrings for dead, expired and revoked keys | ||
107 | * that have overstayed their welcome | ||
108 | */ | ||
109 | static void key_garbage_collector(struct work_struct *work) | ||
110 | { | ||
111 | struct rb_node *rb; | ||
112 | key_serial_t cursor; | ||
113 | struct key *key, *xkey; | ||
114 | time_t new_timer = LONG_MAX, limit; | ||
115 | |||
116 | kenter(""); | ||
117 | |||
118 | if (test_and_set_bit(0, &key_gc_executing)) { | ||
119 | key_schedule_gc(current_kernel_time().tv_sec); | ||
120 | return; | ||
121 | } | ||
122 | |||
123 | limit = current_kernel_time().tv_sec; | ||
124 | if (limit > key_gc_delay) | ||
125 | limit -= key_gc_delay; | ||
126 | else | ||
127 | limit = key_gc_delay; | ||
128 | |||
129 | spin_lock(&key_serial_lock); | ||
130 | |||
131 | if (RB_EMPTY_ROOT(&key_serial_tree)) | ||
132 | goto reached_the_end; | ||
133 | |||
134 | cursor = key_gc_cursor; | ||
135 | if (cursor < 0) | ||
136 | cursor = 0; | ||
137 | |||
138 | /* find the first key above the cursor */ | ||
139 | key = NULL; | ||
140 | rb = key_serial_tree.rb_node; | ||
141 | while (rb) { | ||
142 | xkey = rb_entry(rb, struct key, serial_node); | ||
143 | if (cursor < xkey->serial) { | ||
144 | key = xkey; | ||
145 | rb = rb->rb_left; | ||
146 | } else if (cursor > xkey->serial) { | ||
147 | rb = rb->rb_right; | ||
148 | } else { | ||
149 | rb = rb_next(rb); | ||
150 | if (!rb) | ||
151 | goto reached_the_end; | ||
152 | key = rb_entry(rb, struct key, serial_node); | ||
153 | break; | ||
154 | } | ||
155 | } | ||
156 | |||
157 | if (!key) | ||
158 | goto reached_the_end; | ||
159 | |||
160 | /* trawl through the keys looking for keyrings */ | ||
161 | for (;;) { | ||
162 | if (key->expiry > 0 && key->expiry < new_timer) | ||
163 | new_timer = key->expiry; | ||
164 | |||
165 | if (key->type == &key_type_keyring && | ||
166 | key_gc_keyring(key, limit)) { | ||
167 | /* the gc ate our lock */ | ||
168 | schedule_work(&key_gc_work); | ||
169 | goto no_unlock; | ||
170 | } | ||
171 | |||
172 | rb = rb_next(&key->serial_node); | ||
173 | if (!rb) { | ||
174 | key_gc_cursor = 0; | ||
175 | break; | ||
176 | } | ||
177 | key = rb_entry(rb, struct key, serial_node); | ||
178 | } | ||
179 | |||
180 | out: | ||
181 | spin_unlock(&key_serial_lock); | ||
182 | no_unlock: | ||
183 | clear_bit(0, &key_gc_executing); | ||
184 | if (new_timer < LONG_MAX) | ||
185 | key_schedule_gc(new_timer); | ||
186 | |||
187 | kleave(""); | ||
188 | return; | ||
189 | |||
190 | reached_the_end: | ||
191 | key_gc_cursor = 0; | ||
192 | goto out; | ||
193 | } | ||
diff --git a/security/keys/internal.h b/security/keys/internal.h index a7252e7b2e05..fb830514c337 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h | |||
@@ -132,6 +132,10 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags, | |||
132 | 132 | ||
133 | extern long join_session_keyring(const char *name); | 133 | extern long join_session_keyring(const char *name); |
134 | 134 | ||
135 | extern unsigned key_gc_delay; | ||
136 | extern void keyring_gc(struct key *keyring, time_t limit); | ||
137 | extern void key_schedule_gc(time_t expiry_at); | ||
138 | |||
135 | /* | 139 | /* |
136 | * check to see whether permission is granted to use a key in the desired way | 140 | * check to see whether permission is granted to use a key in the desired way |
137 | */ | 141 | */ |
diff --git a/security/keys/key.c b/security/keys/key.c index bd9d2670e9c4..08531ad0f252 100644 --- a/security/keys/key.c +++ b/security/keys/key.c | |||
@@ -500,6 +500,7 @@ int key_negate_and_link(struct key *key, | |||
500 | set_bit(KEY_FLAG_INSTANTIATED, &key->flags); | 500 | set_bit(KEY_FLAG_INSTANTIATED, &key->flags); |
501 | now = current_kernel_time(); | 501 | now = current_kernel_time(); |
502 | key->expiry = now.tv_sec + timeout; | 502 | key->expiry = now.tv_sec + timeout; |
503 | key_schedule_gc(key->expiry); | ||
503 | 504 | ||
504 | if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) | 505 | if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags)) |
505 | awaken = 1; | 506 | awaken = 1; |
@@ -888,6 +889,9 @@ EXPORT_SYMBOL(key_update); | |||
888 | */ | 889 | */ |
889 | void key_revoke(struct key *key) | 890 | void key_revoke(struct key *key) |
890 | { | 891 | { |
892 | struct timespec now; | ||
893 | time_t time; | ||
894 | |||
891 | key_check(key); | 895 | key_check(key); |
892 | 896 | ||
893 | /* make sure no one's trying to change or use the key when we mark it | 897 | /* make sure no one's trying to change or use the key when we mark it |
@@ -900,6 +904,14 @@ void key_revoke(struct key *key) | |||
900 | key->type->revoke) | 904 | key->type->revoke) |
901 | key->type->revoke(key); | 905 | key->type->revoke(key); |
902 | 906 | ||
907 | /* set the death time to no more than the expiry time */ | ||
908 | now = current_kernel_time(); | ||
909 | time = now.tv_sec; | ||
910 | if (key->revoked_at == 0 || key->revoked_at > time) { | ||
911 | key->revoked_at = time; | ||
912 | key_schedule_gc(key->revoked_at); | ||
913 | } | ||
914 | |||
903 | up_write(&key->sem); | 915 | up_write(&key->sem); |
904 | 916 | ||
905 | } /* end key_revoke() */ | 917 | } /* end key_revoke() */ |
@@ -984,6 +996,8 @@ void unregister_key_type(struct key_type *ktype) | |||
984 | spin_unlock(&key_serial_lock); | 996 | spin_unlock(&key_serial_lock); |
985 | up_write(&key_types_sem); | 997 | up_write(&key_types_sem); |
986 | 998 | ||
999 | key_schedule_gc(0); | ||
1000 | |||
987 | } /* end unregister_key_type() */ | 1001 | } /* end unregister_key_type() */ |
988 | 1002 | ||
989 | EXPORT_SYMBOL(unregister_key_type); | 1003 | EXPORT_SYMBOL(unregister_key_type); |
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 1160b644dace..736d7800f97f 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c | |||
@@ -1115,6 +1115,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout) | |||
1115 | } | 1115 | } |
1116 | 1116 | ||
1117 | key->expiry = expiry; | 1117 | key->expiry = expiry; |
1118 | key_schedule_gc(key->expiry); | ||
1118 | 1119 | ||
1119 | up_write(&key->sem); | 1120 | up_write(&key->sem); |
1120 | key_put(key); | 1121 | key_put(key); |
diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 3dba81c2eba3..ac977f661a79 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c | |||
@@ -1000,3 +1000,88 @@ static void keyring_revoke(struct key *keyring) | |||
1000 | } | 1000 | } |
1001 | 1001 | ||
1002 | } /* end keyring_revoke() */ | 1002 | } /* end keyring_revoke() */ |
1003 | |||
1004 | /* | ||
1005 | * Determine whether a key is dead | ||
1006 | */ | ||
1007 | static bool key_is_dead(struct key *key, time_t limit) | ||
1008 | { | ||
1009 | return test_bit(KEY_FLAG_DEAD, &key->flags) || | ||
1010 | (key->expiry > 0 && key->expiry <= limit); | ||
1011 | } | ||
1012 | |||
1013 | /* | ||
1014 | * Collect garbage from the contents of a keyring | ||
1015 | */ | ||
1016 | void keyring_gc(struct key *keyring, time_t limit) | ||
1017 | { | ||
1018 | struct keyring_list *klist, *new; | ||
1019 | struct key *key; | ||
1020 | int loop, keep, max; | ||
1021 | |||
1022 | kenter("%x", key_serial(keyring)); | ||
1023 | |||
1024 | down_write(&keyring->sem); | ||
1025 | |||
1026 | klist = keyring->payload.subscriptions; | ||
1027 | if (!klist) | ||
1028 | goto just_return; | ||
1029 | |||
1030 | /* work out how many subscriptions we're keeping */ | ||
1031 | keep = 0; | ||
1032 | for (loop = klist->nkeys - 1; loop >= 0; loop--) | ||
1033 | if (!key_is_dead(klist->keys[loop], limit)); | ||
1034 | keep++; | ||
1035 | |||
1036 | if (keep == klist->nkeys) | ||
1037 | goto just_return; | ||
1038 | |||
1039 | /* allocate a new keyring payload */ | ||
1040 | max = roundup(keep, 4); | ||
1041 | new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *), | ||
1042 | GFP_KERNEL); | ||
1043 | if (!new) | ||
1044 | goto just_return; | ||
1045 | new->maxkeys = max; | ||
1046 | new->nkeys = 0; | ||
1047 | new->delkey = 0; | ||
1048 | |||
1049 | /* install the live keys | ||
1050 | * - must take care as expired keys may be updated back to life | ||
1051 | */ | ||
1052 | keep = 0; | ||
1053 | for (loop = klist->nkeys - 1; loop >= 0; loop--) { | ||
1054 | key = klist->keys[loop]; | ||
1055 | if (!key_is_dead(key, limit)) { | ||
1056 | if (keep >= max) | ||
1057 | goto discard_new; | ||
1058 | new->keys[keep++] = key_get(key); | ||
1059 | } | ||
1060 | } | ||
1061 | new->nkeys = keep; | ||
1062 | |||
1063 | /* adjust the quota */ | ||
1064 | key_payload_reserve(keyring, | ||
1065 | sizeof(struct keyring_list) + | ||
1066 | KEYQUOTA_LINK_BYTES * keep); | ||
1067 | |||
1068 | if (keep == 0) { | ||
1069 | rcu_assign_pointer(keyring->payload.subscriptions, NULL); | ||
1070 | kfree(new); | ||
1071 | } else { | ||
1072 | rcu_assign_pointer(keyring->payload.subscriptions, new); | ||
1073 | } | ||
1074 | |||
1075 | up_write(&keyring->sem); | ||
1076 | |||
1077 | call_rcu(&klist->rcu, keyring_clear_rcu_disposal); | ||
1078 | kleave(" [yes]"); | ||
1079 | return; | ||
1080 | |||
1081 | discard_new: | ||
1082 | new->nkeys = keep; | ||
1083 | keyring_clear_rcu_disposal(&new->rcu); | ||
1084 | just_return: | ||
1085 | up_write(&keyring->sem); | ||
1086 | kleave(" [no]"); | ||
1087 | } | ||
diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c index b611d493c2d8..5e05dc09e2db 100644 --- a/security/keys/sysctl.c +++ b/security/keys/sysctl.c | |||
@@ -13,6 +13,8 @@ | |||
13 | #include <linux/sysctl.h> | 13 | #include <linux/sysctl.h> |
14 | #include "internal.h" | 14 | #include "internal.h" |
15 | 15 | ||
16 | static const int zero, one = 1, max = INT_MAX; | ||
17 | |||
16 | ctl_table key_sysctls[] = { | 18 | ctl_table key_sysctls[] = { |
17 | { | 19 | { |
18 | .ctl_name = CTL_UNNUMBERED, | 20 | .ctl_name = CTL_UNNUMBERED, |
@@ -20,7 +22,9 @@ ctl_table key_sysctls[] = { | |||
20 | .data = &key_quota_maxkeys, | 22 | .data = &key_quota_maxkeys, |
21 | .maxlen = sizeof(unsigned), | 23 | .maxlen = sizeof(unsigned), |
22 | .mode = 0644, | 24 | .mode = 0644, |
23 | .proc_handler = &proc_dointvec, | 25 | .proc_handler = &proc_dointvec_minmax, |
26 | .extra1 = (void *) &one, | ||
27 | .extra2 = (void *) &max, | ||
24 | }, | 28 | }, |
25 | { | 29 | { |
26 | .ctl_name = CTL_UNNUMBERED, | 30 | .ctl_name = CTL_UNNUMBERED, |
@@ -28,7 +32,9 @@ ctl_table key_sysctls[] = { | |||
28 | .data = &key_quota_maxbytes, | 32 | .data = &key_quota_maxbytes, |
29 | .maxlen = sizeof(unsigned), | 33 | .maxlen = sizeof(unsigned), |
30 | .mode = 0644, | 34 | .mode = 0644, |
31 | .proc_handler = &proc_dointvec, | 35 | .proc_handler = &proc_dointvec_minmax, |
36 | .extra1 = (void *) &one, | ||
37 | .extra2 = (void *) &max, | ||
32 | }, | 38 | }, |
33 | { | 39 | { |
34 | .ctl_name = CTL_UNNUMBERED, | 40 | .ctl_name = CTL_UNNUMBERED, |
@@ -36,7 +42,9 @@ ctl_table key_sysctls[] = { | |||
36 | .data = &key_quota_root_maxkeys, | 42 | .data = &key_quota_root_maxkeys, |
37 | .maxlen = sizeof(unsigned), | 43 | .maxlen = sizeof(unsigned), |
38 | .mode = 0644, | 44 | .mode = 0644, |
39 | .proc_handler = &proc_dointvec, | 45 | .proc_handler = &proc_dointvec_minmax, |
46 | .extra1 = (void *) &one, | ||
47 | .extra2 = (void *) &max, | ||
40 | }, | 48 | }, |
41 | { | 49 | { |
42 | .ctl_name = CTL_UNNUMBERED, | 50 | .ctl_name = CTL_UNNUMBERED, |
@@ -44,7 +52,19 @@ ctl_table key_sysctls[] = { | |||
44 | .data = &key_quota_root_maxbytes, | 52 | .data = &key_quota_root_maxbytes, |
45 | .maxlen = sizeof(unsigned), | 53 | .maxlen = sizeof(unsigned), |
46 | .mode = 0644, | 54 | .mode = 0644, |
47 | .proc_handler = &proc_dointvec, | 55 | .proc_handler = &proc_dointvec_minmax, |
56 | .extra1 = (void *) &one, | ||
57 | .extra2 = (void *) &max, | ||
58 | }, | ||
59 | { | ||
60 | .ctl_name = CTL_UNNUMBERED, | ||
61 | .procname = "gc_delay", | ||
62 | .data = &key_gc_delay, | ||
63 | .maxlen = sizeof(unsigned), | ||
64 | .mode = 0644, | ||
65 | .proc_handler = &proc_dointvec_minmax, | ||
66 | .extra1 = (void *) &zero, | ||
67 | .extra2 = (void *) &max, | ||
48 | }, | 68 | }, |
49 | { .ctl_name = 0 } | 69 | { .ctl_name = 0 } |
50 | }; | 70 | }; |