Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * fuzz.c: Common functions for fuzzing. |
3 | | * |
4 | | * See Copyright for the status of this software. |
5 | | */ |
6 | | |
7 | | #include <stdio.h> |
8 | | #include <stdlib.h> |
9 | | #include <string.h> |
10 | | #include <sys/stat.h> |
11 | | |
12 | | #include <libxml/hash.h> |
13 | | #include <libxml/parser.h> |
14 | | #include <libxml/parserInternals.h> |
15 | | #include <libxml/tree.h> |
16 | | #include <libxml/xmlIO.h> |
17 | | #include "fuzz.h" |
18 | | |
19 | | typedef struct { |
20 | | const char *data; |
21 | | size_t size; |
22 | | } xmlFuzzEntityInfo; |
23 | | |
24 | | /* Single static instance for now */ |
25 | | static struct { |
26 | | /* Original data */ |
27 | | const char *data; |
28 | | size_t size; |
29 | | |
30 | | /* Remaining data */ |
31 | | const char *ptr; |
32 | | size_t remaining; |
33 | | |
34 | | /* Buffer for unescaped strings */ |
35 | | char *outBuf; |
36 | | char *outPtr; /* Free space at end of buffer */ |
37 | | |
38 | | xmlHashTablePtr entities; /* Maps URLs to xmlFuzzEntityInfos */ |
39 | | |
40 | | /* The first entity is the main entity. */ |
41 | | const char *mainUrl; |
42 | | xmlFuzzEntityInfo *mainEntity; |
43 | | } fuzzData; |
44 | | |
45 | | size_t fuzzNumAllocs; |
46 | | size_t fuzzMaxAllocs; |
47 | | int fuzzAllocFailed; |
48 | | |
49 | | /** |
50 | | * xmlFuzzErrorFunc: |
51 | | * |
52 | | * An error function that simply discards all errors. |
53 | | */ |
54 | | void |
55 | | xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED, |
56 | 0 | ...) { |
57 | 0 | } |
58 | | |
59 | | /* |
60 | | * Malloc failure injection. |
61 | | * |
62 | | * To debug issues involving malloc failures, it's often helpful to set |
63 | | * MALLOC_ABORT to 1. This should provide a backtrace of the failed |
64 | | * allocation. |
65 | | */ |
66 | | |
67 | | #define XML_FUZZ_MALLOC_ABORT 0 |
68 | | |
69 | | static void * |
70 | 106k | xmlFuzzMalloc(size_t size) { |
71 | 106k | if (fuzzMaxAllocs > 0) { |
72 | 97.3k | fuzzNumAllocs += 1; |
73 | 97.3k | if (fuzzNumAllocs == fuzzMaxAllocs) { |
74 | | #if XML_FUZZ_MALLOC_ABORT |
75 | | abort(); |
76 | | #endif |
77 | 955 | fuzzAllocFailed = 1; |
78 | 955 | return(NULL); |
79 | 955 | } |
80 | 97.3k | } |
81 | 105k | return malloc(size); |
82 | 106k | } |
83 | | |
84 | | static void * |
85 | 8.05k | xmlFuzzRealloc(void *ptr, size_t size) { |
86 | 8.05k | if (fuzzMaxAllocs > 0) { |
87 | 7.94k | fuzzNumAllocs += 1; |
88 | 7.94k | if (fuzzNumAllocs == fuzzMaxAllocs) { |
89 | | #if XML_FUZZ_MALLOC_ABORT |
90 | | abort(); |
91 | | #endif |
92 | 47 | fuzzAllocFailed = 1; |
93 | 47 | return(NULL); |
94 | 47 | } |
95 | 7.94k | } |
96 | 8.00k | return realloc(ptr, size); |
97 | 8.05k | } |
98 | | |
99 | | void |
100 | 2 | xmlFuzzMemSetup(void) { |
101 | 2 | xmlMemSetup(free, xmlFuzzMalloc, xmlFuzzRealloc, xmlMemStrdup); |
102 | 2 | } |
103 | | |
104 | | void |
105 | 7.88k | xmlFuzzMemSetLimit(size_t limit) { |
106 | 7.88k | fuzzNumAllocs = 0; |
107 | 7.88k | fuzzMaxAllocs = limit; |
108 | 7.88k | fuzzAllocFailed = 0; |
109 | 7.88k | } |
110 | | |
111 | | int |
112 | 0 | xmlFuzzMallocFailed(void) { |
113 | 0 | return fuzzAllocFailed; |
114 | 0 | } |
115 | | |
116 | | void |
117 | 30.0k | xmlFuzzResetMallocFailed(void) { |
118 | 30.0k | fuzzAllocFailed = 0; |
119 | 30.0k | } |
120 | | |
121 | | void |
122 | 30.0k | xmlFuzzCheckMallocFailure(const char *func, int expect) { |
123 | 30.0k | if (fuzzAllocFailed != expect) { |
124 | 0 | fprintf(stderr, "%s: malloc failure %s reported\n", |
125 | 0 | func, fuzzAllocFailed ? "not" : "erroneously"); |
126 | 0 | abort(); |
127 | 0 | } |
128 | 30.0k | } |
129 | | |
130 | | /** |
131 | | * xmlFuzzDataInit: |
132 | | * |
133 | | * Initialize fuzz data provider. |
134 | | */ |
135 | | void |
136 | 3.94k | xmlFuzzDataInit(const char *data, size_t size) { |
137 | 3.94k | fuzzData.data = data; |
138 | 3.94k | fuzzData.size = size; |
139 | 3.94k | fuzzData.ptr = data; |
140 | 3.94k | fuzzData.remaining = size; |
141 | | |
142 | 3.94k | fuzzData.outBuf = xmlMalloc(size + 1); |
143 | 3.94k | fuzzData.outPtr = fuzzData.outBuf; |
144 | | |
145 | 3.94k | fuzzData.entities = xmlHashCreate(8); |
146 | 3.94k | fuzzData.mainUrl = NULL; |
147 | 3.94k | fuzzData.mainEntity = NULL; |
148 | 3.94k | } |
149 | | |
150 | | /** |
151 | | * xmlFuzzDataFree: |
152 | | * |
153 | | * Cleanup fuzz data provider. |
154 | | */ |
155 | | void |
156 | 3.94k | xmlFuzzDataCleanup(void) { |
157 | 3.94k | xmlFree(fuzzData.outBuf); |
158 | 3.94k | xmlHashFree(fuzzData.entities, xmlHashDefaultDeallocator); |
159 | 3.94k | } |
160 | | |
161 | | /** |
162 | | * xmlFuzzWriteInt: |
163 | | * @out: output file |
164 | | * @v: integer to write |
165 | | * @size: size of integer in bytes |
166 | | * |
167 | | * Write an integer to the fuzz data. |
168 | | */ |
169 | | void |
170 | 0 | xmlFuzzWriteInt(FILE *out, size_t v, int size) { |
171 | 0 | int shift; |
172 | |
|
173 | 0 | while (size > (int) sizeof(size_t)) { |
174 | 0 | putc(0, out); |
175 | 0 | size--; |
176 | 0 | } |
177 | |
|
178 | 0 | shift = size * 8; |
179 | 0 | while (shift > 0) { |
180 | 0 | shift -= 8; |
181 | 0 | putc((v >> shift) & 255, out); |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | /** |
186 | | * xmlFuzzReadInt: |
187 | | * @size: size of integer in bytes |
188 | | * |
189 | | * Read an integer from the fuzz data. |
190 | | */ |
191 | | size_t |
192 | 3.94k | xmlFuzzReadInt(int size) { |
193 | 3.94k | size_t ret = 0; |
194 | | |
195 | 19.6k | while ((size > 0) && (fuzzData.remaining > 0)) { |
196 | 15.7k | unsigned char c = (unsigned char) *fuzzData.ptr++; |
197 | 15.7k | fuzzData.remaining--; |
198 | 15.7k | ret = (ret << 8) | c; |
199 | 15.7k | size--; |
200 | 15.7k | } |
201 | | |
202 | 3.94k | return ret; |
203 | 3.94k | } |
204 | | |
205 | | /** |
206 | | * xmlFuzzReadRemaining: |
207 | | * @size: size of string in bytes |
208 | | * |
209 | | * Read remaining bytes from fuzz data. |
210 | | */ |
211 | | const char * |
212 | 0 | xmlFuzzReadRemaining(size_t *size) { |
213 | 0 | const char *ret = fuzzData.ptr; |
214 | |
|
215 | 0 | *size = fuzzData.remaining; |
216 | 0 | fuzzData.ptr += fuzzData.remaining; |
217 | 0 | fuzzData.remaining = 0; |
218 | |
|
219 | 0 | return(ret); |
220 | 0 | } |
221 | | |
222 | | /* |
223 | | * xmlFuzzWriteString: |
224 | | * @out: output file |
225 | | * @str: string to write |
226 | | * |
227 | | * Write a random-length string to file in a format similar to |
228 | | * FuzzedDataProvider. Backslash followed by newline marks the end of the |
229 | | * string. Two backslashes are used to escape a backslash. |
230 | | */ |
231 | | void |
232 | 0 | xmlFuzzWriteString(FILE *out, const char *str) { |
233 | 0 | for (; *str; str++) { |
234 | 0 | int c = (unsigned char) *str; |
235 | 0 | putc(c, out); |
236 | 0 | if (c == '\\') |
237 | 0 | putc(c, out); |
238 | 0 | } |
239 | 0 | putc('\\', out); |
240 | 0 | putc('\n', out); |
241 | 0 | } |
242 | | |
243 | | /** |
244 | | * xmlFuzzReadString: |
245 | | * @size: size of string in bytes |
246 | | * |
247 | | * Read a random-length string from the fuzz data. |
248 | | * |
249 | | * The format is similar to libFuzzer's FuzzedDataProvider but treats |
250 | | * backslash followed by newline as end of string. This makes the fuzz data |
251 | | * more readable. A backslash character is escaped with another backslash. |
252 | | * |
253 | | * Returns a zero-terminated string or NULL if the fuzz data is exhausted. |
254 | | */ |
255 | | const char * |
256 | 7.88k | xmlFuzzReadString(size_t *size) { |
257 | 7.88k | const char *out = fuzzData.outPtr; |
258 | | |
259 | 550k | while (fuzzData.remaining > 0) { |
260 | 544k | int c = *fuzzData.ptr++; |
261 | 544k | fuzzData.remaining--; |
262 | | |
263 | 544k | if ((c == '\\') && (fuzzData.remaining > 0)) { |
264 | 3.51k | int c2 = *fuzzData.ptr; |
265 | | |
266 | 3.51k | if (c2 == '\n') { |
267 | 1.47k | fuzzData.ptr++; |
268 | 1.47k | fuzzData.remaining--; |
269 | 1.47k | if (size != NULL) |
270 | 0 | *size = fuzzData.outPtr - out; |
271 | 1.47k | *fuzzData.outPtr++ = '\0'; |
272 | 1.47k | return(out); |
273 | 1.47k | } |
274 | 2.04k | if (c2 == '\\') { |
275 | 1.11k | fuzzData.ptr++; |
276 | 1.11k | fuzzData.remaining--; |
277 | 1.11k | } |
278 | 2.04k | } |
279 | | |
280 | 543k | *fuzzData.outPtr++ = c; |
281 | 543k | } |
282 | | |
283 | 6.41k | if (fuzzData.outPtr > out) { |
284 | 3.91k | if (size != NULL) |
285 | 0 | *size = fuzzData.outPtr - out; |
286 | 3.91k | *fuzzData.outPtr++ = '\0'; |
287 | 3.91k | return(out); |
288 | 3.91k | } |
289 | | |
290 | 2.49k | if (size != NULL) |
291 | 0 | *size = 0; |
292 | 2.49k | return(NULL); |
293 | 6.41k | } |
294 | | |
295 | | /** |
296 | | * xmlFuzzReadEntities: |
297 | | * |
298 | | * Read entities like the main XML file, external DTDs, external parsed |
299 | | * entities from fuzz data. |
300 | | */ |
301 | | void |
302 | 0 | xmlFuzzReadEntities(void) { |
303 | 0 | size_t num = 0; |
304 | |
|
305 | 0 | while (1) { |
306 | 0 | const char *url, *entity; |
307 | 0 | size_t urlSize, entitySize; |
308 | 0 | xmlFuzzEntityInfo *entityInfo; |
309 | |
|
310 | 0 | url = xmlFuzzReadString(&urlSize); |
311 | 0 | if (url == NULL) break; |
312 | | |
313 | 0 | entity = xmlFuzzReadString(&entitySize); |
314 | 0 | if (entity == NULL) break; |
315 | | |
316 | | /* |
317 | | * Cap URL size to avoid quadratic behavior when generating |
318 | | * error messages or looking up entities. |
319 | | */ |
320 | 0 | if (urlSize < 50 && |
321 | 0 | xmlHashLookup(fuzzData.entities, (xmlChar *)url) == NULL) { |
322 | 0 | entityInfo = xmlMalloc(sizeof(xmlFuzzEntityInfo)); |
323 | 0 | if (entityInfo == NULL) |
324 | 0 | break; |
325 | 0 | entityInfo->data = entity; |
326 | 0 | entityInfo->size = entitySize; |
327 | |
|
328 | 0 | xmlHashAddEntry(fuzzData.entities, (xmlChar *)url, entityInfo); |
329 | |
|
330 | 0 | if (num == 0) { |
331 | 0 | fuzzData.mainUrl = url; |
332 | 0 | fuzzData.mainEntity = entityInfo; |
333 | 0 | } |
334 | |
|
335 | 0 | num++; |
336 | 0 | } |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | /** |
341 | | * xmlFuzzMainUrl: |
342 | | * |
343 | | * Returns the main URL. |
344 | | */ |
345 | | const char * |
346 | 0 | xmlFuzzMainUrl(void) { |
347 | 0 | return(fuzzData.mainUrl); |
348 | 0 | } |
349 | | |
350 | | /** |
351 | | * xmlFuzzMainEntity: |
352 | | * @size: size of the main entity in bytes |
353 | | * |
354 | | * Returns the main entity. |
355 | | */ |
356 | | const char * |
357 | 0 | xmlFuzzMainEntity(size_t *size) { |
358 | 0 | if (fuzzData.mainEntity == NULL) |
359 | 0 | return(NULL); |
360 | 0 | *size = fuzzData.mainEntity->size; |
361 | 0 | return(fuzzData.mainEntity->data); |
362 | 0 | } |
363 | | |
364 | | /** |
365 | | * xmlFuzzEntityLoader: |
366 | | * |
367 | | * The entity loader for fuzz data. |
368 | | */ |
369 | | xmlParserInputPtr |
370 | | xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED, |
371 | 0 | xmlParserCtxtPtr ctxt) { |
372 | 0 | xmlParserInputPtr input; |
373 | 0 | xmlFuzzEntityInfo *entity; |
374 | |
|
375 | 0 | if (URL == NULL) |
376 | 0 | return(NULL); |
377 | 0 | entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL); |
378 | 0 | if (entity == NULL) |
379 | 0 | return(NULL); |
380 | | |
381 | 0 | input = xmlNewInputStream(ctxt); |
382 | 0 | if (input == NULL) |
383 | 0 | return(NULL); |
384 | 0 | input->filename = (char *) xmlCharStrdup(URL); |
385 | 0 | if (input->filename == NULL) { |
386 | 0 | xmlCtxtErrMemory(ctxt); |
387 | 0 | xmlFreeInputStream(input); |
388 | 0 | return(NULL); |
389 | 0 | } |
390 | 0 | input->buf = xmlParserInputBufferCreateMem(entity->data, entity->size, |
391 | 0 | XML_CHAR_ENCODING_NONE); |
392 | 0 | if (input->buf == NULL) { |
393 | 0 | xmlCtxtErrMemory(ctxt); |
394 | 0 | xmlFreeInputStream(input); |
395 | 0 | return(NULL); |
396 | 0 | } |
397 | 0 | input->base = input->cur = xmlBufContent(input->buf->buffer); |
398 | 0 | input->end = input->base + xmlBufUse(input->buf->buffer); |
399 | |
|
400 | 0 | return input; |
401 | 0 | } |
402 | | |
403 | | char * |
404 | 0 | xmlSlurpFile(const char *path, size_t *sizeRet) { |
405 | 0 | FILE *file; |
406 | 0 | struct stat statbuf; |
407 | 0 | char *data; |
408 | 0 | size_t size; |
409 | |
|
410 | 0 | if ((stat(path, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode))) |
411 | 0 | return(NULL); |
412 | 0 | size = statbuf.st_size; |
413 | 0 | file = fopen(path, "rb"); |
414 | 0 | if (file == NULL) |
415 | 0 | return(NULL); |
416 | 0 | data = xmlMalloc(size + 1); |
417 | 0 | if (data != NULL) { |
418 | 0 | if (fread(data, 1, size, file) != size) { |
419 | 0 | xmlFree(data); |
420 | 0 | data = NULL; |
421 | 0 | } else { |
422 | 0 | data[size] = 0; |
423 | 0 | if (sizeRet != NULL) |
424 | 0 | *sizeRet = size; |
425 | 0 | } |
426 | 0 | } |
427 | 0 | fclose(file); |
428 | |
|
429 | 0 | return(data); |
430 | 0 | } |
431 | | |