Coverage Report

Created: 2026-08-31 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/selinux/libselinux/fuzz/selabel_file_compiled-fuzzer.c
Line
Count
Source
1
#include <errno.h>
2
#include <stdint.h>
3
#include <stdio.h>
4
#include <sys/mman.h>
5
#include <unistd.h>
6
7
#include <selinux/label.h>
8
9
#include "../src/label_file.h"
10
11
extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
12
13
22.1k
#define MEMFD_FILE_NAME "file_contexts"
14
21.0k
#define CTRL_PARTIAL (1U << 0)
15
21.0k
#define CTRL_FIND_ALL (1U << 1)
16
21.0k
#define CTRL_MODE (1U << 2)
17
18
#ifndef VERBOSE
19
#define VERBOSE 0
20
#endif
21
22
#if !VERBOSE
23
__attribute__((format(printf, 2, 3))) static int
24
null_log(int type __attribute__((unused)),
25
   const char *fmt __attribute__((unused)), ...)
26
6.84k
{
27
6.84k
  return 0;
28
6.84k
}
29
#endif
30
31
static int validate_context(char **ctxp)
32
10.3k
{
33
10.3k
  assert(strcmp(*ctxp, "<<none>>") != 0);
34
35
10.3k
  if (*ctxp[0] == '\0') {
36
5
    errno = EINVAL;
37
5
    return -1;
38
5
  }
39
40
10.3k
  return 0;
41
10.3k
}
42
43
static int write_full(int fd, const void *data, size_t size)
44
11.0k
{
45
11.0k
  ssize_t rc;
46
11.0k
  const unsigned char *p = data;
47
48
22.1k
  while (size > 0) {
49
11.0k
    rc = write(fd, p, size);
50
11.0k
    if (rc == -1) {
51
0
      if (errno == EINTR)
52
0
        continue;
53
54
0
      return -1;
55
0
    }
56
57
11.0k
    p += rc;
58
11.0k
    size -= rc;
59
11.0k
  }
60
61
11.0k
  return 0;
62
11.0k
}
63
64
static FILE *convert_data(const uint8_t *data, size_t size)
65
11.0k
{
66
11.0k
  FILE *stream;
67
11.0k
  int fd, rc;
68
69
11.0k
  fd = memfd_create(MEMFD_FILE_NAME, MFD_CLOEXEC);
70
11.0k
  if (fd == -1)
71
0
    return NULL;
72
73
11.0k
  rc = write_full(fd, data, size);
74
11.0k
  if (rc == -1) {
75
0
    close(fd);
76
0
    return NULL;
77
0
  }
78
79
11.0k
  stream = fdopen(fd, "r");
80
11.0k
  if (!stream) {
81
0
    close(fd);
82
0
    return NULL;
83
0
  }
84
85
11.0k
  rc = fseek(stream, 0L, SEEK_SET);
86
11.0k
  if (rc == -1) {
87
0
    fclose(stream);
88
0
    return NULL;
89
0
  }
90
91
11.0k
  return stream;
92
11.0k
}
93
94
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
95
10.5k
{
96
10.5k
  struct selabel_handle rec;
97
10.5k
  struct saved_data sdata = {};
98
10.5k
  struct spec_node *root = NULL;
99
10.5k
  FILE *fp = NULL;
100
10.5k
  struct lookup_result *result = NULL;
101
10.5k
  uint8_t control;
102
10.5k
  uint8_t *fcontext_data1 = NULL, *fcontext_data2 = NULL,
103
10.5k
    *fcontext_data3 = NULL;
104
10.5k
  char *key = NULL;
105
10.5k
  size_t fcontext_data1_len, fcontext_data2_len = 0,
106
10.5k
           fcontext_data3_len = 0, key_len;
107
10.5k
  bool partial, find_all;
108
10.5k
  mode_t mode;
109
10.5k
  int rc;
110
111
  /*
112
   * Treat first byte as control byte, whether to use partial mode, find all matches or mode to lookup
113
   */
114
10.5k
  if (size == 0)
115
0
    return 0;
116
117
10.5k
  control = data[0];
118
10.5k
  data++;
119
10.5k
  size--;
120
121
10.5k
  if (control & ~(CTRL_PARTIAL | CTRL_FIND_ALL | CTRL_MODE))
122
4
    return 0;
123
124
10.5k
  partial = control & CTRL_PARTIAL;
125
10.5k
  find_all = control & CTRL_FIND_ALL;
126
  /* S_IFSOCK has the highest integer value */
127
10.5k
  mode = (control & CTRL_MODE) ? S_IFSOCK : 0;
128
129
#if VERBOSE
130
  printf("partial=%s, find_all=%s, mode=%s\n", partial ? "true" : "false",
131
         find_all ? "true" : "false", mode ? "true" : "false");
132
#endif
133
134
  /*
135
   * Split the fuzzer input into up to four pieces: one to three compiled fcontext
136
   * definitions (to mimic file_contexts, file_contexts.homedirs and file_contexts.local,
137
   * and the lookup key
138
   */
139
10.5k
  const unsigned char separator[4] = { 0xde, 0xad, 0xbe, 0xef };
140
10.5k
  const uint8_t *sep = memmem(data, size, separator, 4);
141
10.5k
  if (!sep || sep == data)
142
11
    return 0;
143
144
10.5k
  fcontext_data1_len = sep - data;
145
10.5k
  fcontext_data1 = malloc(fcontext_data1_len);
146
10.5k
  if (!fcontext_data1)
147
0
    goto cleanup;
148
149
10.5k
  memcpy(fcontext_data1, data, fcontext_data1_len);
150
10.5k
  data += fcontext_data1_len + 4;
151
10.5k
  size -= fcontext_data1_len + 4;
152
153
10.5k
  sep = memmem(data, size, separator, 4);
154
10.5k
  if (sep) {
155
471
    fcontext_data2_len = sep - data;
156
471
    if (fcontext_data2_len) {
157
435
      fcontext_data2 = malloc(fcontext_data2_len);
158
435
      if (!fcontext_data2)
159
0
        goto cleanup;
160
161
435
      memcpy(fcontext_data2, data, fcontext_data2_len);
162
435
    }
163
164
471
    data += fcontext_data2_len + 4;
165
471
    size -= fcontext_data2_len + 4;
166
471
  }
167
168
10.5k
  sep = memmem(data, size, separator, 4);
169
10.5k
  if (sep) {
170
189
    fcontext_data3_len = sep - data;
171
189
    if (fcontext_data3_len) {
172
187
      fcontext_data3 = malloc(fcontext_data3_len);
173
187
      if (!fcontext_data3)
174
0
        goto cleanup;
175
176
187
      memcpy(fcontext_data3, data, fcontext_data3_len);
177
187
    }
178
179
189
    data += fcontext_data3_len + 4;
180
189
    size -= fcontext_data3_len + 4;
181
189
  }
182
183
10.5k
  key_len = size;
184
10.5k
  key = malloc(key_len + 1);
185
10.5k
  if (!key)
186
0
    goto cleanup;
187
188
10.5k
  memcpy(key, data, key_len);
189
10.5k
  key[key_len] = '\0';
190
191
  /*
192
   * Mock selabel handle
193
   */
194
10.5k
  rec = (struct selabel_handle){
195
10.5k
    .backend = SELABEL_CTX_FILE,
196
10.5k
    .validating = 1,
197
10.5k
    .data = &sdata,
198
10.5k
  };
199
200
10.5k
#if !VERBOSE
201
10.5k
  selinux_set_callback(SELINUX_CB_LOG,
202
10.5k
           (union selinux_callback){ .func_log = &null_log });
203
10.5k
#endif
204
  /* validate to pre-compile regular expressions */
205
10.5k
  selinux_set_callback(
206
10.5k
    SELINUX_CB_VALIDATE,
207
10.5k
    (union selinux_callback){ .func_validate = &validate_context });
208
209
10.5k
  root = calloc(1, sizeof(*root));
210
10.5k
  if (!root)
211
0
    goto cleanup;
212
213
10.5k
  sdata.root = root;
214
215
10.5k
  fp = convert_data(fcontext_data1, fcontext_data1_len);
216
10.5k
  if (!fp)
217
0
    goto cleanup;
218
219
10.5k
  errno = 0;
220
10.5k
  rc = load_mmap(fp, fcontext_data1_len, &rec, MEMFD_FILE_NAME, 0);
221
10.5k
  if (rc) {
222
1.10k
    assert(errno != 0);
223
1.10k
    goto cleanup;
224
1.10k
  }
225
226
9.40k
  fclose(fp);
227
9.40k
  fp = NULL;
228
229
9.40k
  if (fcontext_data2_len) {
230
413
    fp = convert_data(fcontext_data2, fcontext_data2_len);
231
413
    if (!fp)
232
0
      goto cleanup;
233
234
413
    errno = 0;
235
413
    rc = load_mmap(fp, fcontext_data2_len, &rec, MEMFD_FILE_NAME,
236
413
             1);
237
413
    if (rc) {
238
17
      assert(errno != 0);
239
17
      goto cleanup;
240
17
    }
241
242
396
    fclose(fp);
243
396
    fp = NULL;
244
396
  }
245
246
9.38k
  if (fcontext_data3_len) {
247
172
    fp = convert_data(fcontext_data3, fcontext_data3_len);
248
172
    if (!fp)
249
0
      goto cleanup;
250
251
172
    errno = 0;
252
172
    rc = load_mmap(fp, fcontext_data3_len, &rec, MEMFD_FILE_NAME,
253
172
             2);
254
172
    if (rc) {
255
24
      assert(errno != 0);
256
24
      goto cleanup;
257
24
    }
258
259
148
    fclose(fp);
260
148
    fp = NULL;
261
148
  }
262
263
9.36k
  sort_specs(&sdata);
264
265
9.36k
  assert(cmp(&rec, &rec) == SELABEL_EQUAL);
266
267
9.36k
  errno = 0;
268
9.36k
  result = lookup_all(&rec, key, mode, partial, find_all, NULL);
269
270
9.36k
  if (!result)
271
9.36k
    assert(errno != 0);
272
273
15.7k
  for (const struct lookup_result *res = result; res; res = res->next) {
274
6.38k
    assert(res->regex_str);
275
6.38k
    assert(res->regex_str[0] != '\0');
276
6.38k
    assert(res->lr->ctx_raw);
277
6.38k
    assert(res->lr->ctx_raw[0] != '\0');
278
6.38k
    assert(strcmp(res->lr->ctx_raw, "<<none>>") != 0);
279
6.38k
    assert(!res->lr->ctx_trans);
280
6.38k
    assert(res->lr->validated);
281
6.38k
    assert(res->prefix_len <= strlen(res->regex_str));
282
6.38k
  }
283
284
10.5k
cleanup:
285
10.5k
  free_lookup_result(result);
286
10.5k
  if (fp)
287
1.14k
    fclose(fp);
288
10.5k
  if (sdata.root) {
289
10.5k
    free_spec_node(sdata.root);
290
10.5k
    free(sdata.root);
291
10.5k
  }
292
293
10.5k
  {
294
10.5k
    struct mmap_area *area, *last_area;
295
296
10.5k
    area = sdata.mmap_areas;
297
20.4k
    while (area) {
298
9.94k
      rc = munmap(area->addr, area->len);
299
9.94k
      assert(rc == 0);
300
9.94k
      last_area = area;
301
9.94k
      area = area->next;
302
9.94k
      free(last_area);
303
9.94k
    }
304
10.5k
  }
305
306
10.5k
  free(key);
307
10.5k
  free(fcontext_data3);
308
10.5k
  free(fcontext_data2);
309
10.5k
  free(fcontext_data1);
310
311
  /* Non-zero return values are reserved for future use. */
312
10.5k
  return 0;
313
10.5k
}
314
315
#ifdef DEFINEMAIN
316
int main(int argc, char **argv)
317
{
318
  if (argc < 2) {
319
    fprintf(stderr, "usage: %s fuzzer-input-file\n", argv[0]);
320
    exit(1);
321
  }
322
323
  FILE *fp = fopen(argv[1], "rb");
324
  if (!fp) {
325
    perror(argv[1]);
326
    exit(1);
327
  }
328
329
  struct stat sb;
330
  int rc;
331
332
  rc = fstat(fileno(fp), &sb);
333
  if (rc < 0) {
334
    perror("fstat");
335
    exit(1);
336
  }
337
338
  void *address = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE,
339
           MAP_PRIVATE, fileno(fp), 0);
340
  if (address == MAP_FAILED) {
341
    perror("mmap");
342
    exit(1);
343
  }
344
345
  return LLVMFuzzerTestOneInput(address, sb.st_size);
346
}
347
#endif