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