/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 |