Commit | Line | Data |
---|---|---|
409dcf31 DJ |
1 | /* |
2 | * Pkey table | |
3 | * | |
4 | * SELinux must keep a mapping of Infinband PKEYs to labels/SIDs. This | |
5 | * mapping is maintained as part of the normal policy but a fast cache is | |
6 | * needed to reduce the lookup overhead. | |
7 | * | |
8 | * This code is heavily based on the "netif" and "netport" concept originally | |
9 | * developed by | |
10 | * James Morris <jmorris@redhat.com> and | |
11 | * Paul Moore <paul@paul-moore.com> | |
12 | * (see security/selinux/netif.c and security/selinux/netport.c for more | |
13 | * information) | |
14 | * | |
15 | */ | |
16 | ||
17 | /* | |
18 | * (c) Mellanox Technologies, 2016 | |
19 | * | |
20 | * This program is free software: you can redistribute it and/or modify | |
21 | * it under the terms of version 2 of the GNU General Public License as | |
22 | * published by the Free Software Foundation. | |
23 | * | |
24 | * This program is distributed in the hope that it will be useful, | |
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
27 | * GNU General Public License for more details. | |
28 | * | |
29 | */ | |
30 | ||
31 | #include <linux/types.h> | |
32 | #include <linux/rcupdate.h> | |
33 | #include <linux/list.h> | |
34 | #include <linux/spinlock.h> | |
35 | ||
36 | #include "ibpkey.h" | |
37 | #include "objsec.h" | |
38 | ||
39 | #define SEL_PKEY_HASH_SIZE 256 | |
40 | #define SEL_PKEY_HASH_BKT_LIMIT 16 | |
41 | ||
42 | struct sel_ib_pkey_bkt { | |
43 | int size; | |
44 | struct list_head list; | |
45 | }; | |
46 | ||
47 | struct sel_ib_pkey { | |
48 | struct pkey_security_struct psec; | |
49 | struct list_head list; | |
50 | struct rcu_head rcu; | |
51 | }; | |
52 | ||
53 | static LIST_HEAD(sel_ib_pkey_list); | |
54 | static DEFINE_SPINLOCK(sel_ib_pkey_lock); | |
55 | static struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE]; | |
56 | ||
57 | /** | |
58 | * sel_ib_pkey_hashfn - Hashing function for the pkey table | |
59 | * @pkey: pkey number | |
60 | * | |
61 | * Description: | |
62 | * This is the hashing function for the pkey table, it returns the bucket | |
63 | * number for the given pkey. | |
64 | * | |
65 | */ | |
66 | static unsigned int sel_ib_pkey_hashfn(u16 pkey) | |
67 | { | |
68 | return (pkey & (SEL_PKEY_HASH_SIZE - 1)); | |
69 | } | |
70 | ||
71 | /** | |
72 | * sel_ib_pkey_find - Search for a pkey record | |
73 | * @subnet_prefix: subnet_prefix | |
74 | * @pkey_num: pkey_num | |
75 | * | |
76 | * Description: | |
77 | * Search the pkey table and return the matching record. If an entry | |
78 | * can not be found in the table return NULL. | |
79 | * | |
80 | */ | |
81 | static struct sel_ib_pkey *sel_ib_pkey_find(u64 subnet_prefix, u16 pkey_num) | |
82 | { | |
83 | unsigned int idx; | |
84 | struct sel_ib_pkey *pkey; | |
85 | ||
86 | idx = sel_ib_pkey_hashfn(pkey_num); | |
87 | list_for_each_entry_rcu(pkey, &sel_ib_pkey_hash[idx].list, list) { | |
88 | if (pkey->psec.pkey == pkey_num && | |
89 | pkey->psec.subnet_prefix == subnet_prefix) | |
90 | return pkey; | |
91 | } | |
92 | ||
93 | return NULL; | |
94 | } | |
95 | ||
96 | /** | |
97 | * sel_ib_pkey_insert - Insert a new pkey into the table | |
98 | * @pkey: the new pkey record | |
99 | * | |
100 | * Description: | |
101 | * Add a new pkey record to the hash table. | |
102 | * | |
103 | */ | |
104 | static void sel_ib_pkey_insert(struct sel_ib_pkey *pkey) | |
105 | { | |
106 | unsigned int idx; | |
107 | ||
108 | /* we need to impose a limit on the growth of the hash table so check | |
109 | * this bucket to make sure it is within the specified bounds | |
110 | */ | |
111 | idx = sel_ib_pkey_hashfn(pkey->psec.pkey); | |
112 | list_add_rcu(&pkey->list, &sel_ib_pkey_hash[idx].list); | |
113 | if (sel_ib_pkey_hash[idx].size == SEL_PKEY_HASH_BKT_LIMIT) { | |
114 | struct sel_ib_pkey *tail; | |
115 | ||
116 | tail = list_entry( | |
117 | rcu_dereference_protected( | |
118 | sel_ib_pkey_hash[idx].list.prev, | |
119 | lockdep_is_held(&sel_ib_pkey_lock)), | |
120 | struct sel_ib_pkey, list); | |
121 | list_del_rcu(&tail->list); | |
122 | kfree_rcu(tail, rcu); | |
123 | } else { | |
124 | sel_ib_pkey_hash[idx].size++; | |
125 | } | |
126 | } | |
127 | ||
128 | /** | |
129 | * sel_ib_pkey_sid_slow - Lookup the SID of a pkey using the policy | |
130 | * @subnet_prefix: subnet prefix | |
131 | * @pkey_num: pkey number | |
132 | * @sid: pkey SID | |
133 | * | |
134 | * Description: | |
135 | * This function determines the SID of a pkey by querying the security | |
136 | * policy. The result is added to the pkey table to speedup future | |
137 | * queries. Returns zero on success, negative values on failure. | |
138 | * | |
139 | */ | |
140 | static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) | |
141 | { | |
142 | int ret; | |
143 | struct sel_ib_pkey *pkey; | |
144 | struct sel_ib_pkey *new = NULL; | |
145 | unsigned long flags; | |
146 | ||
147 | spin_lock_irqsave(&sel_ib_pkey_lock, flags); | |
148 | pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); | |
149 | if (pkey) { | |
150 | *sid = pkey->psec.sid; | |
151 | spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); | |
152 | return 0; | |
153 | } | |
154 | ||
155 | ret = security_ib_pkey_sid(subnet_prefix, pkey_num, sid); | |
156 | if (ret) | |
157 | goto out; | |
158 | ||
159 | /* If this memory allocation fails still return 0. The SID | |
160 | * is valid, it just won't be added to the cache. | |
161 | */ | |
162 | new = kzalloc(sizeof(*new), GFP_ATOMIC); | |
163 | if (!new) | |
164 | goto out; | |
165 | ||
166 | new->psec.subnet_prefix = subnet_prefix; | |
167 | new->psec.pkey = pkey_num; | |
168 | new->psec.sid = *sid; | |
169 | sel_ib_pkey_insert(new); | |
170 | ||
171 | out: | |
172 | spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); | |
173 | return ret; | |
174 | } | |
175 | ||
176 | /** | |
177 | * sel_ib_pkey_sid - Lookup the SID of a PKEY | |
178 | * @subnet_prefix: subnet_prefix | |
179 | * @pkey_num: pkey number | |
180 | * @sid: pkey SID | |
181 | * | |
182 | * Description: | |
183 | * This function determines the SID of a PKEY using the fastest method | |
184 | * possible. First the pkey table is queried, but if an entry can't be found | |
185 | * then the policy is queried and the result is added to the table to speedup | |
186 | * future queries. Returns zero on success, negative values on failure. | |
187 | * | |
188 | */ | |
189 | int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid) | |
190 | { | |
191 | struct sel_ib_pkey *pkey; | |
192 | ||
193 | rcu_read_lock(); | |
194 | pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); | |
195 | if (pkey) { | |
196 | *sid = pkey->psec.sid; | |
197 | rcu_read_unlock(); | |
198 | return 0; | |
199 | } | |
200 | rcu_read_unlock(); | |
201 | ||
202 | return sel_ib_pkey_sid_slow(subnet_prefix, pkey_num, sid); | |
203 | } | |
204 | ||
205 | /** | |
206 | * sel_ib_pkey_flush - Flush the entire pkey table | |
207 | * | |
208 | * Description: | |
209 | * Remove all entries from the pkey table | |
210 | * | |
211 | */ | |
212 | void sel_ib_pkey_flush(void) | |
213 | { | |
214 | unsigned int idx; | |
215 | struct sel_ib_pkey *pkey, *pkey_tmp; | |
216 | unsigned long flags; | |
217 | ||
218 | spin_lock_irqsave(&sel_ib_pkey_lock, flags); | |
219 | for (idx = 0; idx < SEL_PKEY_HASH_SIZE; idx++) { | |
220 | list_for_each_entry_safe(pkey, pkey_tmp, | |
221 | &sel_ib_pkey_hash[idx].list, list) { | |
222 | list_del_rcu(&pkey->list); | |
223 | kfree_rcu(pkey, rcu); | |
224 | } | |
225 | sel_ib_pkey_hash[idx].size = 0; | |
226 | } | |
227 | spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); | |
228 | } | |
229 | ||
230 | static __init int sel_ib_pkey_init(void) | |
231 | { | |
232 | int iter; | |
233 | ||
234 | if (!selinux_enabled) | |
235 | return 0; | |
236 | ||
237 | for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) { | |
238 | INIT_LIST_HEAD(&sel_ib_pkey_hash[iter].list); | |
239 | sel_ib_pkey_hash[iter].size = 0; | |
240 | } | |
241 | ||
242 | return 0; | |
243 | } | |
244 | ||
245 | subsys_initcall(sel_ib_pkey_init); |