1 | // SPDX-License-Identifier: GPL-2.0-only |
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) |
15 | */ |
16 | |
17 | /* |
18 | * (c) Mellanox Technologies, 2016 |
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 | |
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: 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: 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( |
107 | list_tail_rcu(&sel_ib_pkey_hash[idx].list), |
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(lock: &sel_ib_pkey_lock, flags); |
141 | return 0; |
142 | } |
143 | |
144 | ret = security_ib_pkey_sid(subnet_prefix, pkey_num, |
145 | sid); |
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); |
153 | if (!new) { |
154 | ret = -ENOMEM; |
155 | goto out; |
156 | } |
157 | |
158 | new->psec.subnet_prefix = subnet_prefix; |
159 | new->psec.pkey = pkey_num; |
160 | new->psec.sid = *sid; |
161 | sel_ib_pkey_insert(pkey: new); |
162 | |
163 | out: |
164 | spin_unlock_irqrestore(lock: &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(lock: &sel_ib_pkey_lock, flags); |
220 | } |
221 | |
222 | static __init int sel_ib_pkey_init(void) |
223 | { |
224 | int iter; |
225 | |
226 | if (!selinux_enabled_boot) |
227 | return 0; |
228 | |
229 | for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) { |
230 | INIT_LIST_HEAD(list: &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); |
238 | |