Coverage Report

Created: 2026-10-17 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}