/src/selinux/libselinux/fuzz/selabel_file_compiled-fuzzer.c
Line | Count | Source (jump to first uncovered line) |
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 | 10.3k | #define MEMFD_FILE_NAME "file_contexts" |
14 | 9.04k | #define CTRL_PARTIAL (1U << 0) |
15 | 9.04k | #define CTRL_FIND_ALL (1U << 1) |
16 | 9.04k | #define CTRL_MODE (1U << 2) |
17 | | |
18 | | |
19 | | __attribute__ ((format(printf, 2, 3))) |
20 | | static int null_log(int type __attribute__((unused)), const char *fmt __attribute__((unused)), ...) |
21 | 401 | { |
22 | 401 | return 0; |
23 | 401 | } |
24 | | |
25 | | static int validate_context(char **ctxp) |
26 | 3.82k | { |
27 | 3.82k | assert(strcmp(*ctxp, "<<none>>") != 0); |
28 | | |
29 | 3.82k | if (*ctxp[0] == '\0') { |
30 | 9 | errno = EINVAL; |
31 | 9 | return -1; |
32 | 9 | } |
33 | | |
34 | 3.81k | return 0; |
35 | 3.82k | } |
36 | | |
37 | | static int write_full(int fd, const void *data, size_t size) |
38 | 5.19k | { |
39 | 5.19k | ssize_t rc; |
40 | 5.19k | const unsigned char *p = data; |
41 | | |
42 | 10.3k | while (size > 0) { |
43 | 5.19k | rc = write(fd, p, size); |
44 | 5.19k | if (rc == -1) { |
45 | 0 | if (errno == EINTR) |
46 | 0 | continue; |
47 | | |
48 | 0 | return -1; |
49 | 0 | } |
50 | | |
51 | 5.19k | p += rc; |
52 | 5.19k | size -= rc; |
53 | 5.19k | } |
54 | | |
55 | 5.19k | return 0; |
56 | 5.19k | } |
57 | | |
58 | | static FILE* convert_data(const uint8_t *data, size_t size) |
59 | 5.19k | { |
60 | 5.19k | FILE* stream; |
61 | 5.19k | int fd, rc; |
62 | | |
63 | 5.19k | fd = memfd_create(MEMFD_FILE_NAME, MFD_CLOEXEC); |
64 | 5.19k | if (fd == -1) |
65 | 0 | return NULL; |
66 | | |
67 | 5.19k | rc = write_full(fd, data, size); |
68 | 5.19k | if (rc == -1) { |
69 | 0 | close(fd); |
70 | 0 | return NULL; |
71 | 0 | } |
72 | | |
73 | 5.19k | stream = fdopen(fd, "r"); |
74 | 5.19k | if (!stream) { |
75 | 0 | close(fd); |
76 | 0 | return NULL; |
77 | 0 | } |
78 | | |
79 | 5.19k | rc = fseek(stream, 0L, SEEK_SET); |
80 | 5.19k | if (rc == -1) { |
81 | 0 | fclose(stream); |
82 | 0 | return NULL; |
83 | 0 | } |
84 | | |
85 | 5.19k | return stream; |
86 | 5.19k | } |
87 | | |
88 | | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) |
89 | 4.52k | { |
90 | 4.52k | struct selabel_handle rec; |
91 | 4.52k | struct saved_data sdata = {}; |
92 | 4.52k | struct spec_node *root = NULL; |
93 | 4.52k | FILE* fp = NULL; |
94 | 4.52k | struct lookup_result *result = NULL; |
95 | 4.52k | uint8_t control; |
96 | 4.52k | uint8_t *fcontext_data1 = NULL, *fcontext_data2 = NULL, *fcontext_data3 = NULL; |
97 | 4.52k | char *key = NULL; |
98 | 4.52k | size_t fcontext_data1_len, fcontext_data2_len = 0, fcontext_data3_len = 0, key_len; |
99 | 4.52k | bool partial, find_all; |
100 | 4.52k | mode_t mode; |
101 | 4.52k | int rc; |
102 | | |
103 | | /* |
104 | | * Treat first byte as control byte, whether to use partial mode, find all matches or mode to lookup |
105 | | */ |
106 | 4.52k | if (size == 0) |
107 | 0 | return 0; |
108 | | |
109 | 4.52k | control = data[0]; |
110 | 4.52k | data++; |
111 | 4.52k | size--; |
112 | | |
113 | 4.52k | if (control & ~(CTRL_PARTIAL | CTRL_FIND_ALL | CTRL_MODE)) |
114 | 8 | return 0; |
115 | | |
116 | 4.52k | partial = control & CTRL_PARTIAL; |
117 | 4.52k | find_all = control & CTRL_FIND_ALL; |
118 | | /* S_IFSOCK has the highest integer value */ |
119 | 4.52k | mode = (control & CTRL_MODE) ? S_IFSOCK : 0; |
120 | | |
121 | | |
122 | | /* |
123 | | * Split the fuzzer input into up to four pieces: one to three compiled fcontext |
124 | | * definitions (to mimic file_contexts, file_contexts.homedirs and file_contexts.local, |
125 | | * and the lookup key |
126 | | */ |
127 | 4.52k | const unsigned char separator[4] = { 0xde, 0xad, 0xbe, 0xef }; |
128 | 4.52k | const uint8_t *sep = memmem(data, size, separator, 4); |
129 | 4.52k | if (!sep || sep == data) |
130 | 9 | return 0; |
131 | | |
132 | 4.51k | fcontext_data1_len = sep - data; |
133 | 4.51k | fcontext_data1 = malloc(fcontext_data1_len); |
134 | 4.51k | if (!fcontext_data1) |
135 | 0 | goto cleanup; |
136 | | |
137 | 4.51k | memcpy(fcontext_data1, data, fcontext_data1_len); |
138 | 4.51k | data += fcontext_data1_len + 4; |
139 | 4.51k | size -= fcontext_data1_len + 4; |
140 | | |
141 | 4.51k | sep = memmem(data, size, separator, 4); |
142 | 4.51k | if (sep) { |
143 | 606 | fcontext_data2_len = sep - data; |
144 | 606 | if (fcontext_data2_len) { |
145 | 577 | fcontext_data2 = malloc(fcontext_data2_len); |
146 | 577 | if (!fcontext_data2) |
147 | 0 | goto cleanup; |
148 | | |
149 | 577 | memcpy(fcontext_data2, data, fcontext_data2_len); |
150 | 577 | } |
151 | | |
152 | 606 | data += fcontext_data2_len + 4; |
153 | 606 | size -= fcontext_data2_len + 4; |
154 | 606 | } |
155 | | |
156 | 4.51k | sep = memmem(data, size, separator, 4); |
157 | 4.51k | if (sep) { |
158 | 158 | fcontext_data3_len = sep - data; |
159 | 158 | if (fcontext_data3_len) { |
160 | 157 | fcontext_data3 = malloc(fcontext_data3_len); |
161 | 157 | if (!fcontext_data3) |
162 | 0 | goto cleanup; |
163 | | |
164 | 157 | memcpy(fcontext_data3, data, fcontext_data3_len); |
165 | 157 | } |
166 | | |
167 | 158 | data += fcontext_data3_len + 4; |
168 | 158 | size -= fcontext_data3_len + 4; |
169 | 158 | } |
170 | | |
171 | 4.51k | key_len = size; |
172 | 4.51k | key = malloc(key_len + 1); |
173 | 4.51k | if (!key) |
174 | 0 | goto cleanup; |
175 | | |
176 | 4.51k | memcpy(key, data, key_len); |
177 | 4.51k | key[key_len] = '\0'; |
178 | | |
179 | | |
180 | | /* |
181 | | * Mock selabel handle |
182 | | */ |
183 | 4.51k | rec = (struct selabel_handle) { |
184 | 4.51k | .backend = SELABEL_CTX_FILE, |
185 | 4.51k | .validating = 1, |
186 | 4.51k | .data = &sdata, |
187 | 4.51k | }; |
188 | | |
189 | 4.51k | selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) { .func_log = &null_log }); |
190 | | /* validate to pre-compile regular expressions */ |
191 | 4.51k | selinux_set_callback(SELINUX_CB_VALIDATE, (union selinux_callback) { .func_validate = &validate_context }); |
192 | | |
193 | 4.51k | root = calloc(1, sizeof(*root)); |
194 | 4.51k | if (!root) |
195 | 0 | goto cleanup; |
196 | | |
197 | 4.51k | sdata.root = root; |
198 | | |
199 | 4.51k | fp = convert_data(fcontext_data1, fcontext_data1_len); |
200 | 4.51k | if (!fp) |
201 | 0 | goto cleanup; |
202 | | |
203 | 4.51k | errno = 0; |
204 | 4.51k | rc = load_mmap(fp, fcontext_data1_len, &rec, MEMFD_FILE_NAME, 0); |
205 | 4.51k | if (rc) { |
206 | 2.05k | assert(errno != 0); |
207 | 2.05k | goto cleanup; |
208 | 2.05k | } |
209 | | |
210 | 2.45k | fclose(fp); |
211 | 2.45k | fp = NULL; |
212 | | |
213 | 2.45k | if (fcontext_data2_len) { |
214 | 544 | fp = convert_data(fcontext_data2, fcontext_data2_len); |
215 | 544 | if (!fp) |
216 | 0 | goto cleanup; |
217 | | |
218 | 544 | errno = 0; |
219 | 544 | rc = load_mmap(fp, fcontext_data2_len, &rec, MEMFD_FILE_NAME, 1); |
220 | 544 | if (rc) { |
221 | 53 | assert(errno != 0); |
222 | 53 | goto cleanup; |
223 | 53 | } |
224 | | |
225 | 491 | fclose(fp); |
226 | 491 | fp = NULL; |
227 | 491 | } |
228 | | |
229 | 2.40k | if (fcontext_data3_len) { |
230 | 136 | fp = convert_data(fcontext_data3, fcontext_data3_len); |
231 | 136 | if (!fp) |
232 | 0 | goto cleanup; |
233 | | |
234 | 136 | errno = 0; |
235 | 136 | rc = load_mmap(fp, fcontext_data3_len, &rec, MEMFD_FILE_NAME, 2); |
236 | 136 | if (rc) { |
237 | 36 | assert(errno != 0); |
238 | 36 | goto cleanup; |
239 | 36 | } |
240 | | |
241 | 100 | fclose(fp); |
242 | 100 | fp = NULL; |
243 | 100 | } |
244 | | |
245 | 2.36k | sort_specs(&sdata); |
246 | | |
247 | 2.36k | assert(cmp(&rec, &rec) == SELABEL_EQUAL); |
248 | | |
249 | 2.36k | errno = 0; |
250 | 2.36k | result = lookup_all(&rec, key, mode, partial, find_all, NULL); |
251 | | |
252 | 2.36k | if (!result) |
253 | 1.81k | assert(errno != 0); |
254 | | |
255 | 3.39k | for (const struct lookup_result *res = result; res; res = res->next) { |
256 | 1.03k | assert(res->regex_str); |
257 | 1.03k | assert(res->regex_str[0] != '\0'); |
258 | 1.03k | assert(res->lr->ctx_raw); |
259 | 1.03k | assert(res->lr->ctx_raw[0] != '\0'); |
260 | 1.03k | assert(strcmp(res->lr->ctx_raw, "<<none>>") != 0); |
261 | 1.03k | assert(!res->lr->ctx_trans); |
262 | 1.03k | assert(res->lr->validated); |
263 | 1.03k | assert(res->prefix_len <= strlen(res->regex_str)); |
264 | 1.03k | } |
265 | | |
266 | | |
267 | 4.51k | cleanup: |
268 | 4.51k | free_lookup_result(result); |
269 | 4.51k | if (fp) |
270 | 2.14k | fclose(fp); |
271 | 4.51k | if (sdata.root) { |
272 | 4.51k | free_spec_node(sdata.root); |
273 | 4.51k | free(sdata.root); |
274 | 4.51k | } |
275 | | |
276 | 4.51k | { |
277 | 4.51k | struct mmap_area *area, *last_area; |
278 | | |
279 | 4.51k | area = sdata.mmap_areas; |
280 | 7.55k | while (area) { |
281 | 3.04k | rc = munmap(area->addr, area->len); |
282 | 3.04k | assert(rc == 0); |
283 | 3.04k | last_area = area; |
284 | 3.04k | area = area->next; |
285 | 3.04k | free(last_area); |
286 | 3.04k | } |
287 | 4.51k | } |
288 | | |
289 | 4.51k | free(key); |
290 | 4.51k | free(fcontext_data3); |
291 | 4.51k | free(fcontext_data2); |
292 | 4.51k | free(fcontext_data1); |
293 | | |
294 | | /* Non-zero return values are reserved for future use. */ |
295 | 4.51k | return 0; |
296 | 4.51k | } |