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