/src/selinux/libsepol/src/optimize.c
Line | Count | Source |
1 | | /* |
2 | | * Author: Ondrej Mosnacek <omosnacek@gmail.com> |
3 | | * |
4 | | * Copyright (C) 2019 Red Hat Inc. |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
19 | | */ |
20 | | |
21 | | /* |
22 | | * Binary policy optimization. |
23 | | * |
24 | | * Defines the policydb_optimize() function, which finds and removes |
25 | | * redundant rules from the binary policy to reduce its size and potentially |
26 | | * improve rule matching times. Only rules that are already covered by a |
27 | | * more general rule are removed. The resulting policy is functionally |
28 | | * equivalent to the original one. |
29 | | */ |
30 | | |
31 | | #include <sepol/policydb/policydb.h> |
32 | | #include <sepol/policydb/conditional.h> |
33 | | |
34 | | #include "debug.h" |
35 | | #include "private.h" |
36 | | |
37 | 394k | #define TYPE_VEC_INIT_SIZE 16 |
38 | | |
39 | | struct type_vec { |
40 | | uint32_t *types; |
41 | | unsigned int count, capacity; |
42 | | }; |
43 | | |
44 | | static int type_vec_init(struct type_vec *v) |
45 | 394k | { |
46 | 394k | v->capacity = TYPE_VEC_INIT_SIZE; |
47 | 394k | v->count = 0; |
48 | 394k | v->types = calloc(v->capacity, sizeof(*v->types)); |
49 | 394k | if (!v->types) |
50 | 0 | return -1; |
51 | 394k | return 0; |
52 | 394k | } |
53 | | |
54 | | static void type_vec_destroy(struct type_vec *v) |
55 | 394k | { |
56 | 394k | free(v->types); |
57 | 394k | } |
58 | | |
59 | | static int type_vec_append(struct type_vec *v, uint32_t type) |
60 | 11.6M | { |
61 | 11.6M | if (v->capacity == v->count) { |
62 | 87.2k | unsigned int new_capacity = v->capacity * 2; |
63 | 87.2k | uint32_t *new_types = |
64 | 87.2k | reallocarray(v->types, new_capacity, sizeof(*v->types)); |
65 | 87.2k | if (!new_types) |
66 | 0 | return -1; |
67 | | |
68 | 87.2k | v->types = new_types; |
69 | 87.2k | v->capacity = new_capacity; |
70 | 87.2k | } |
71 | | |
72 | 11.6M | v->types[v->count++] = type; |
73 | 11.6M | return 0; |
74 | 11.6M | } |
75 | | |
76 | | static int type_vec_contains(const struct type_vec *v, uint32_t type) |
77 | 167M | { |
78 | 167M | unsigned int s = 0, e = v->count; |
79 | | |
80 | 516M | while (s != e) { |
81 | 401M | unsigned int mid = (s + e) / 2; |
82 | | |
83 | 401M | if (v->types[mid] == type) |
84 | 52.4M | return 1; |
85 | | |
86 | 349M | if (v->types[mid] < type) |
87 | 107M | s = mid + 1; |
88 | 241M | else |
89 | 241M | e = mid; |
90 | 349M | } |
91 | 115M | return 0; |
92 | 167M | } |
93 | | |
94 | | /* builds map: type/attribute -> {all attributes that are a superset of it} */ |
95 | | static struct type_vec *build_type_map(const policydb_t *p) |
96 | 4.05k | { |
97 | 4.05k | unsigned int i, k; |
98 | 4.05k | ebitmap_node_t *n; |
99 | 4.05k | struct type_vec *map = calloc(p->p_types.nprim, sizeof(*map)); |
100 | 4.05k | if (!map) |
101 | 0 | return NULL; |
102 | | |
103 | 398k | for (i = 0; i < p->p_types.nprim; i++) { |
104 | 394k | if (type_vec_init(&map[i])) |
105 | 0 | goto err; |
106 | | |
107 | 394k | if (!p->type_val_to_struct[i]) |
108 | 75 | continue; |
109 | | |
110 | 394k | if (p->type_val_to_struct[i]->flavor != TYPE_ATTRIB) { |
111 | 25.3M | ebitmap_for_each_positive_bit(&p->type_attr_map[i], n, |
112 | 25.3M | k) { |
113 | 599k | if (type_vec_append(&map[i], k)) |
114 | 0 | goto err; |
115 | 599k | } |
116 | 367k | } else { |
117 | 27.1k | ebitmap_t *types_i = &p->attr_type_map[i]; |
118 | | |
119 | 124M | for (k = 0; k < p->p_types.nprim; k++) { |
120 | 124M | const ebitmap_t *types_k; |
121 | | |
122 | 124M | if (!p->type_val_to_struct[k] || |
123 | 124M | p->type_val_to_struct[k]->flavor != |
124 | 124M | TYPE_ATTRIB) |
125 | 103M | continue; |
126 | | |
127 | 21.8M | types_k = &p->attr_type_map[k]; |
128 | | |
129 | 21.8M | if (ebitmap_contains(types_k, types_i)) { |
130 | 11.0M | if (type_vec_append(&map[i], k)) |
131 | 0 | goto err; |
132 | 11.0M | } |
133 | 21.8M | } |
134 | 27.1k | } |
135 | 394k | } |
136 | 4.05k | return map; |
137 | 0 | err: |
138 | 0 | for (k = 0; k <= i; k++) |
139 | 0 | type_vec_destroy(&map[k]); |
140 | 0 | free(map); |
141 | 0 | return NULL; |
142 | 4.05k | } |
143 | | |
144 | | static void destroy_type_map(const policydb_t *p, struct type_vec *type_map) |
145 | 4.05k | { |
146 | 4.05k | unsigned int i; |
147 | 398k | for (i = 0; i < p->p_types.nprim; i++) |
148 | 394k | type_vec_destroy(&type_map[i]); |
149 | 4.05k | free(type_map); |
150 | 4.05k | } |
151 | | |
152 | | static int process_xperms(uint32_t *p1, const uint32_t *p2) |
153 | 440 | { |
154 | 440 | size_t i; |
155 | 440 | int ret = 1; |
156 | | |
157 | 3.96k | for (i = 0; i < EXTENDED_PERMS_LEN; i++) { |
158 | 3.52k | p1[i] &= ~p2[i]; |
159 | 3.52k | if (p1[i] != 0) |
160 | 2.24k | ret = 0; |
161 | 3.52k | } |
162 | 440 | return ret; |
163 | 440 | } |
164 | | |
165 | | static int process_avtab_datum(uint16_t specified, avtab_datum_t *d1, |
166 | | const avtab_datum_t *d2) |
167 | 160k | { |
168 | | /* inverse logic needed for AUDITDENY rules */ |
169 | 160k | if (specified & AVTAB_AUDITDENY) |
170 | 613 | return (d1->data |= ~d2->data) == UINT32_C(0xFFFFFFFF); |
171 | | |
172 | 159k | if (specified & AVTAB_AV) |
173 | 157k | return (d1->data &= ~d2->data) == 0; |
174 | | |
175 | 1.77k | if (specified & AVTAB_XPERMS) { |
176 | 1.77k | avtab_extended_perms_t *x1 = d1->xperms; |
177 | 1.77k | const avtab_extended_perms_t *x2 = d2->xperms; |
178 | | |
179 | 1.77k | if (x1->specified == AVTAB_XPERMS_IOCTLFUNCTION) { |
180 | 1.00k | if (x2->specified == AVTAB_XPERMS_IOCTLFUNCTION) { |
181 | 382 | if (x1->driver != x2->driver) |
182 | 229 | return 0; |
183 | 153 | return process_xperms(x1->perms, x2->perms); |
184 | 382 | } |
185 | 624 | if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER) |
186 | 465 | return xperm_test(x1->driver, x2->perms); |
187 | 773 | } else if (x1->specified == AVTAB_XPERMS_IOCTLDRIVER) { |
188 | 334 | if (x2->specified == AVTAB_XPERMS_IOCTLFUNCTION) |
189 | 82 | return 0; |
190 | | |
191 | 252 | if (x2->specified == AVTAB_XPERMS_IOCTLDRIVER) |
192 | 197 | return process_xperms(x1->perms, x2->perms); |
193 | 439 | } else if (x1->specified == AVTAB_XPERMS_NLMSG && |
194 | 439 | x2->specified == AVTAB_XPERMS_NLMSG) { |
195 | 243 | if (x1->driver != x2->driver) |
196 | 153 | return 0; |
197 | 90 | return process_xperms(x1->perms, x2->perms); |
198 | 243 | } |
199 | 410 | return 0; |
200 | 1.77k | } |
201 | 0 | return 0; |
202 | 1.77k | } |
203 | | |
204 | | /* checks if avtab contains a rule that covers the given rule */ |
205 | | static int is_avrule_redundant(avtab_ptr_t entry, avtab_t *tab, |
206 | | const struct type_vec *type_map, |
207 | | unsigned char not_cond) |
208 | 2.71M | { |
209 | 2.71M | unsigned int i, k, s_idx, t_idx; |
210 | 2.71M | uint32_t st, tt; |
211 | 2.71M | avtab_datum_t *d1, *d2; |
212 | 2.71M | avtab_key_t key; |
213 | | |
214 | | /* we only care about AV rules */ |
215 | 2.71M | if (!(entry->key.specified & (AVTAB_AV | AVTAB_XPERMS))) |
216 | 1.42M | return 0; |
217 | | |
218 | 1.28M | s_idx = entry->key.source_type - 1; |
219 | 1.28M | t_idx = entry->key.target_type - 1; |
220 | | |
221 | 1.28M | key.target_class = entry->key.target_class; |
222 | 1.28M | key.specified = entry->key.specified; |
223 | | |
224 | 1.28M | d1 = &entry->datum; |
225 | | |
226 | 44.7M | for (i = 0; i < type_map[s_idx].count; i++) { |
227 | 43.5M | st = type_map[s_idx].types[i]; |
228 | 43.5M | key.source_type = st + 1; |
229 | | |
230 | 383M | for (k = 0; k < type_map[t_idx].count; k++) { |
231 | 339M | tt = type_map[t_idx].types[k]; |
232 | | |
233 | 339M | if (not_cond && s_idx == st && t_idx == tt) |
234 | 956k | continue; |
235 | | |
236 | 338M | key.target_type = tt + 1; |
237 | | |
238 | 338M | d2 = avtab_search(tab, &key); |
239 | 338M | if (!d2) |
240 | 338M | continue; |
241 | | |
242 | 153k | if (process_avtab_datum(key.specified, d1, d2)) |
243 | 65.5k | return 1; |
244 | 153k | } |
245 | 43.5M | } |
246 | 1.22M | return 0; |
247 | 1.28M | } |
248 | | |
249 | | static int is_type_attr(policydb_t *p, unsigned int id) |
250 | 605k | { |
251 | 605k | return p->type_val_to_struct[id]->flavor == TYPE_ATTRIB; |
252 | 605k | } |
253 | | |
254 | | static int is_avrule_with_attr(avtab_ptr_t entry, policydb_t *p) |
255 | 325k | { |
256 | 325k | unsigned int s_idx = entry->key.source_type - 1; |
257 | 325k | unsigned int t_idx = entry->key.target_type - 1; |
258 | | |
259 | 325k | return is_type_attr(p, s_idx) || is_type_attr(p, t_idx); |
260 | 325k | } |
261 | | |
262 | | /* checks if conditional list contains a rule that covers the given rule */ |
263 | | static int is_cond_rule_redundant(avtab_ptr_t e1, cond_av_list_t *list, |
264 | | const struct type_vec *type_map) |
265 | 266k | { |
266 | 266k | unsigned int s1, t1, c1, k1, s2, t2, c2, k2; |
267 | | |
268 | | /* we only care about AV rules */ |
269 | 266k | if (!(e1->key.specified & (AVTAB_AV | AVTAB_XPERMS))) |
270 | 1.15k | return 0; |
271 | | |
272 | 265k | s1 = e1->key.source_type - 1; |
273 | 265k | t1 = e1->key.target_type - 1; |
274 | 265k | c1 = e1->key.target_class; |
275 | 265k | k1 = e1->key.specified; |
276 | | |
277 | 199M | for (; list; list = list->next) { |
278 | 199M | avtab_ptr_t e2 = list->node; |
279 | | |
280 | 199M | s2 = e2->key.source_type - 1; |
281 | 199M | t2 = e2->key.target_type - 1; |
282 | 199M | c2 = e2->key.target_class; |
283 | 199M | k2 = e2->key.specified; |
284 | | |
285 | 199M | if (k1 != k2 || c1 != c2) |
286 | 84.2M | continue; |
287 | | |
288 | 115M | if (s1 == s2 && t1 == t2) |
289 | 233k | continue; |
290 | 115M | if (!type_vec_contains(&type_map[s1], s2)) |
291 | 62.8M | continue; |
292 | 52.3M | if (!type_vec_contains(&type_map[t1], t2)) |
293 | 52.3M | continue; |
294 | | |
295 | 6.78k | if (process_avtab_datum(k1, &e1->datum, &e2->datum)) |
296 | 5.99k | return 1; |
297 | 6.78k | } |
298 | 259k | return 0; |
299 | 265k | } |
300 | | |
301 | | static void optimize_avtab(policydb_t *p, const struct type_vec *type_map) |
302 | 4.05k | { |
303 | 4.05k | avtab_t *tab = &p->te_avtab; |
304 | 4.05k | unsigned int i; |
305 | 4.05k | avtab_ptr_t *cur; |
306 | | |
307 | 3.39G | for (i = 0; i < tab->nslot; i++) { |
308 | 3.39G | cur = &tab->htable[i]; |
309 | 3.39G | while (*cur) { |
310 | 2.38M | if (is_avrule_redundant(*cur, tab, type_map, 1)) { |
311 | | /* redundant rule -> remove it */ |
312 | 7.18k | avtab_ptr_t tmp = *cur; |
313 | | |
314 | 7.18k | *cur = tmp->next; |
315 | 7.18k | if (tmp->key.specified & AVTAB_XPERMS) |
316 | 10 | free(tmp->datum.xperms); |
317 | 7.18k | free(tmp); |
318 | | |
319 | 7.18k | tab->nel--; |
320 | 2.37M | } else { |
321 | | /* rule not redundant -> move to next rule */ |
322 | 2.37M | cur = &(*cur)->next; |
323 | 2.37M | } |
324 | 2.38M | } |
325 | 3.39G | } |
326 | 4.05k | } |
327 | | |
328 | | /* find redundant rules in (*cond) and put them into (*del) */ |
329 | | static void optimize_cond_av_list(cond_av_list_t **cond, cond_av_list_t **del, |
330 | | policydb_t *p, |
331 | | const struct type_vec *type_map) |
332 | 5.09k | { |
333 | 5.09k | cond_av_list_t **listp = cond; |
334 | 5.09k | cond_av_list_t *pcov = NULL; |
335 | 5.09k | cond_av_list_t **pcov_cur; |
336 | | |
337 | | /* |
338 | | * Separate out all "potentially covering" rules (src or tgt is an attr) |
339 | | * and move them to the end of the list. This is needed to avoid |
340 | | * polynomial complexity when almost all rules are expanded. |
341 | | */ |
342 | 330k | while (*cond) { |
343 | 325k | if (is_avrule_with_attr((*cond)->node, p)) { |
344 | 72.4k | cond_av_list_t *tmp = *cond; |
345 | | |
346 | 72.4k | *cond = tmp->next; |
347 | 72.4k | tmp->next = pcov; |
348 | 72.4k | pcov = tmp; |
349 | 252k | } else { |
350 | 252k | cond = &(*cond)->next; |
351 | 252k | } |
352 | 325k | } |
353 | | /* link the "potentially covering" rules to the end of the list */ |
354 | 5.09k | *cond = pcov; |
355 | | |
356 | | /* now go through the list and find the redundant rules */ |
357 | 5.09k | cond = listp; |
358 | 5.09k | pcov_cur = &pcov; |
359 | 330k | while (*cond) { |
360 | | /* needed because pcov itself may get deleted */ |
361 | 325k | if (*cond == pcov) |
362 | 947 | pcov_cur = cond; |
363 | | /* |
364 | | * First check if covered by an unconditional rule, then also |
365 | | * check if covered by another rule in the same list. |
366 | | */ |
367 | 325k | if (is_avrule_redundant((*cond)->node, &p->te_avtab, type_map, |
368 | 325k | 0) || |
369 | 266k | is_cond_rule_redundant((*cond)->node, *pcov_cur, |
370 | 266k | type_map)) { |
371 | 64.3k | cond_av_list_t *tmp = *cond; |
372 | | |
373 | 64.3k | *cond = tmp->next; |
374 | 64.3k | tmp->next = *del; |
375 | 64.3k | *del = tmp; |
376 | 260k | } else { |
377 | 260k | cond = &(*cond)->next; |
378 | 260k | } |
379 | 325k | } |
380 | 5.09k | } |
381 | | |
382 | | static void optimize_cond_avtab(policydb_t *p, const struct type_vec *type_map) |
383 | 4.05k | { |
384 | 4.05k | avtab_t *tab = &p->te_cond_avtab; |
385 | 4.05k | unsigned int i; |
386 | 4.05k | avtab_ptr_t *cur; |
387 | 4.05k | cond_node_t **cond; |
388 | 4.05k | cond_av_list_t **avcond, *del = NULL; |
389 | | |
390 | | /* First go through all conditionals and collect redundant rules. */ |
391 | 4.05k | cond = &p->cond_list; |
392 | 6.60k | while (*cond) { |
393 | 2.54k | optimize_cond_av_list(&(*cond)->true_list, &del, p, type_map); |
394 | 2.54k | optimize_cond_av_list(&(*cond)->false_list, &del, p, type_map); |
395 | | /* TODO: maybe also check for rules present in both lists */ |
396 | | |
397 | | /* nothing left in both lists -> remove the whole conditional */ |
398 | 2.54k | if (!(*cond)->true_list && !(*cond)->false_list) { |
399 | 591 | cond_node_t *cond_tmp = *cond; |
400 | | |
401 | 591 | *cond = cond_tmp->next; |
402 | 591 | cond_node_destroy(cond_tmp); |
403 | 591 | free(cond_tmp); |
404 | 1.95k | } else { |
405 | 1.95k | cond = &(*cond)->next; |
406 | 1.95k | } |
407 | 2.54k | } |
408 | | |
409 | 4.05k | if (!del) |
410 | 3.81k | return; |
411 | | |
412 | | /* |
413 | | * Now go through the whole cond_avtab and remove all rules that are |
414 | | * found in the 'del' list. |
415 | | */ |
416 | 187M | for (i = 0; i < tab->nslot; i++) { |
417 | 187M | cur = &tab->htable[i]; |
418 | 187M | while (*cur) { |
419 | 287k | int redundant = 0; |
420 | 287k | avcond = &del; |
421 | 6.59G | while (*avcond) { |
422 | 6.59G | if ((*avcond)->node == *cur) { |
423 | 64.3k | cond_av_list_t *cond_tmp = *avcond; |
424 | | |
425 | 64.3k | *avcond = cond_tmp->next; |
426 | 64.3k | free(cond_tmp); |
427 | 64.3k | redundant = 1; |
428 | 64.3k | break; |
429 | 6.59G | } else { |
430 | 6.59G | avcond = &(*avcond)->next; |
431 | 6.59G | } |
432 | 6.59G | } |
433 | 287k | if (redundant) { |
434 | 64.3k | avtab_ptr_t tmp = *cur; |
435 | | |
436 | 64.3k | *cur = tmp->next; |
437 | 64.3k | if (tmp->key.specified & AVTAB_XPERMS) |
438 | 315 | free(tmp->datum.xperms); |
439 | 64.3k | free(tmp); |
440 | | |
441 | 64.3k | tab->nel--; |
442 | 223k | } else { |
443 | 223k | cur = &(*cur)->next; |
444 | 223k | } |
445 | 287k | } |
446 | 187M | } |
447 | 246 | } |
448 | | |
449 | | /* |
450 | | * Older policy compilers could set bits higher than any of the permissions |
451 | | * in the class. These junk bits must be removed before optimization to keep |
452 | | * redundant rules from being kept because of junk bits and to prevent the |
453 | | * failure of policy validation because no actual permission bit is set. |
454 | | */ |
455 | | static int remove_junk_permissions(avtab_key_t *k, avtab_datum_t *d, void *args) |
456 | 2.71M | { |
457 | 2.71M | const policydb_t *p = args; |
458 | 2.71M | const class_datum_t *tclass; |
459 | 2.71M | uint32_t mask; |
460 | | |
461 | 2.71M | if (!(k->specified & AVTAB_AV)) |
462 | 1.43M | return 0; |
463 | | |
464 | 1.27M | tclass = p->class_val_to_struct[k->target_class - 1]; |
465 | 1.27M | mask = PERMISSION_MASK(tclass->permissions.nprim); |
466 | | |
467 | 1.27M | if ((k->specified & ~AVTAB_ENABLED) == AVTAB_AUDITDENY) |
468 | 16.7k | d->data |= ~mask; |
469 | 1.25M | else |
470 | 1.25M | d->data &= mask; |
471 | | |
472 | 1.27M | return 0; |
473 | 2.71M | } |
474 | | |
475 | | int policydb_optimize(policydb_t *p) |
476 | 4.05k | { |
477 | 4.05k | struct type_vec *type_map; |
478 | | |
479 | 4.05k | if (p->policy_type != POLICY_KERN) |
480 | 0 | return -1; |
481 | | |
482 | 4.05k | avtab_map(&p->te_avtab, remove_junk_permissions, p); |
483 | 4.05k | avtab_map(&p->te_cond_avtab, remove_junk_permissions, p); |
484 | | |
485 | 4.05k | type_map = build_type_map(p); |
486 | 4.05k | if (!type_map) |
487 | 0 | return -1; |
488 | | |
489 | 4.05k | optimize_avtab(p, type_map); |
490 | 4.05k | optimize_cond_avtab(p, type_map); |
491 | | |
492 | 4.05k | destroy_type_map(p, type_map); |
493 | 4.05k | return 0; |
494 | 4.05k | } |