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
xmlFuzzErrorFunc(void * ctx ATTRIBUTE_UNUSED,const char * msg ATTRIBUTE_UNUSED,...)55 xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
56 ...) {
57 }
58
59 /**
60 * xmlFuzzSErrorFunc:
61 *
62 * A structured error function that simply discards all errors.
63 */
64 void
xmlFuzzSErrorFunc(void * ctx ATTRIBUTE_UNUSED,const xmlError * error ATTRIBUTE_UNUSED)65 xmlFuzzSErrorFunc(void *ctx ATTRIBUTE_UNUSED,
66 const xmlError *error ATTRIBUTE_UNUSED) {
67 }
68
69 /*
70 * Malloc failure injection.
71 *
72 * To debug issues involving malloc failures, it's often helpful to set
73 * MALLOC_ABORT to 1. This should provide a backtrace of the failed
74 * allocation.
75 */
76
77 #define XML_FUZZ_MALLOC_ABORT 0
78
79 static void *
xmlFuzzMalloc(size_t size)80 xmlFuzzMalloc(size_t size) {
81 void *ret;
82
83 if (fuzzMaxAllocs > 0) {
84 fuzzNumAllocs += 1;
85 if (fuzzNumAllocs == fuzzMaxAllocs) {
86 #if XML_FUZZ_MALLOC_ABORT
87 abort();
88 #endif
89 fuzzAllocFailed = 1;
90 return NULL;
91 }
92 }
93
94 ret = malloc(size);
95 if (ret == NULL)
96 fuzzAllocFailed = 1;
97
98 return ret;
99 }
100
101 static void *
xmlFuzzRealloc(void * ptr,size_t size)102 xmlFuzzRealloc(void *ptr, size_t size) {
103 void *ret;
104
105 if (fuzzMaxAllocs > 0) {
106 fuzzNumAllocs += 1;
107 if (fuzzNumAllocs == fuzzMaxAllocs) {
108 #if XML_FUZZ_MALLOC_ABORT
109 abort();
110 #endif
111 fuzzAllocFailed = 1;
112 return NULL;
113 }
114 }
115
116 ret = realloc(ptr, size);
117 if (ret == NULL)
118 fuzzAllocFailed = 1;
119
120 return ret;
121 }
122
123 void
xmlFuzzMemSetup(void)124 xmlFuzzMemSetup(void) {
125 xmlMemSetup(free, xmlFuzzMalloc, xmlFuzzRealloc, xmlMemStrdup);
126 }
127
128 void
xmlFuzzMemSetLimit(size_t limit)129 xmlFuzzMemSetLimit(size_t limit) {
130 fuzzNumAllocs = 0;
131 fuzzMaxAllocs = limit;
132 fuzzAllocFailed = 0;
133 }
134
135 int
xmlFuzzMallocFailed(void)136 xmlFuzzMallocFailed(void) {
137 return fuzzAllocFailed;
138 }
139
140 void
xmlFuzzResetMallocFailed(void)141 xmlFuzzResetMallocFailed(void) {
142 fuzzAllocFailed = 0;
143 }
144
145 void
xmlFuzzCheckMallocFailure(const char * func,int error)146 xmlFuzzCheckMallocFailure(const char *func, int error) {
147 if (error >= 0 && fuzzAllocFailed != error) {
148 fprintf(stderr, "%s: malloc failure %s reported\n",
149 func, fuzzAllocFailed ? "not" : "erroneously");
150 abort();
151 }
152 fuzzAllocFailed = 0;
153 }
154
155 /**
156 * xmlFuzzDataInit:
157 *
158 * Initialize fuzz data provider.
159 */
160 void
xmlFuzzDataInit(const char * data,size_t size)161 xmlFuzzDataInit(const char *data, size_t size) {
162 fuzzData.data = data;
163 fuzzData.size = size;
164 fuzzData.ptr = data;
165 fuzzData.remaining = size;
166
167 fuzzData.outBuf = xmlMalloc(size + 1);
168 fuzzData.outPtr = fuzzData.outBuf;
169
170 fuzzData.entities = xmlHashCreate(8);
171 fuzzData.mainUrl = NULL;
172 fuzzData.mainEntity = NULL;
173 }
174
175 /**
176 * xmlFuzzDataFree:
177 *
178 * Cleanup fuzz data provider.
179 */
180 void
xmlFuzzDataCleanup(void)181 xmlFuzzDataCleanup(void) {
182 xmlFree(fuzzData.outBuf);
183 xmlHashFree(fuzzData.entities, xmlHashDefaultDeallocator);
184 }
185
186 /**
187 * xmlFuzzWriteInt:
188 * @out: output file
189 * @v: integer to write
190 * @size: size of integer in bytes
191 *
192 * Write an integer to the fuzz data.
193 */
194 void
xmlFuzzWriteInt(FILE * out,size_t v,int size)195 xmlFuzzWriteInt(FILE *out, size_t v, int size) {
196 int shift;
197
198 while (size > (int) sizeof(size_t)) {
199 putc(0, out);
200 size--;
201 }
202
203 shift = size * 8;
204 while (shift > 0) {
205 shift -= 8;
206 putc((v >> shift) & 255, out);
207 }
208 }
209
210 /**
211 * xmlFuzzReadInt:
212 * @size: size of integer in bytes
213 *
214 * Read an integer from the fuzz data.
215 */
216 size_t
xmlFuzzReadInt(int size)217 xmlFuzzReadInt(int size) {
218 size_t ret = 0;
219
220 while ((size > 0) && (fuzzData.remaining > 0)) {
221 unsigned char c = (unsigned char) *fuzzData.ptr++;
222 fuzzData.remaining--;
223 ret = (ret << 8) | c;
224 size--;
225 }
226
227 return ret;
228 }
229
230 /**
231 * xmlFuzzBytesRemaining:
232 *
233 * Return number of remaining bytes in fuzz data.
234 */
235 size_t
xmlFuzzBytesRemaining(void)236 xmlFuzzBytesRemaining(void) {
237 return(fuzzData.remaining);
238 }
239
240 /**
241 * xmlFuzzReadRemaining:
242 * @size: size of string in bytes
243 *
244 * Read remaining bytes from fuzz data.
245 */
246 const char *
xmlFuzzReadRemaining(size_t * size)247 xmlFuzzReadRemaining(size_t *size) {
248 const char *ret = fuzzData.ptr;
249
250 *size = fuzzData.remaining;
251 fuzzData.ptr += fuzzData.remaining;
252 fuzzData.remaining = 0;
253
254 return(ret);
255 }
256
257 /*
258 * xmlFuzzWriteString:
259 * @out: output file
260 * @str: string to write
261 *
262 * Write a random-length string to file in a format similar to
263 * FuzzedDataProvider. Backslash followed by newline marks the end of the
264 * string. Two backslashes are used to escape a backslash.
265 */
266 void
xmlFuzzWriteString(FILE * out,const char * str)267 xmlFuzzWriteString(FILE *out, const char *str) {
268 for (; *str; str++) {
269 int c = (unsigned char) *str;
270 putc(c, out);
271 if (c == '\\')
272 putc(c, out);
273 }
274 putc('\\', out);
275 putc('\n', out);
276 }
277
278 /**
279 * xmlFuzzReadString:
280 * @size: size of string in bytes
281 *
282 * Read a random-length string from the fuzz data.
283 *
284 * The format is similar to libFuzzer's FuzzedDataProvider but treats
285 * backslash followed by newline as end of string. This makes the fuzz data
286 * more readable. A backslash character is escaped with another backslash.
287 *
288 * Returns a zero-terminated string or NULL if the fuzz data is exhausted.
289 */
290 const char *
xmlFuzzReadString(size_t * size)291 xmlFuzzReadString(size_t *size) {
292 const char *out = fuzzData.outPtr;
293
294 while (fuzzData.remaining > 0) {
295 int c = *fuzzData.ptr++;
296 fuzzData.remaining--;
297
298 if ((c == '\\') && (fuzzData.remaining > 0)) {
299 int c2 = *fuzzData.ptr;
300
301 if (c2 == '\n') {
302 fuzzData.ptr++;
303 fuzzData.remaining--;
304 if (size != NULL)
305 *size = fuzzData.outPtr - out;
306 *fuzzData.outPtr++ = '\0';
307 return(out);
308 }
309 if (c2 == '\\') {
310 fuzzData.ptr++;
311 fuzzData.remaining--;
312 }
313 }
314
315 *fuzzData.outPtr++ = c;
316 }
317
318 if (fuzzData.outPtr > out) {
319 if (size != NULL)
320 *size = fuzzData.outPtr - out;
321 *fuzzData.outPtr++ = '\0';
322 return(out);
323 }
324
325 if (size != NULL)
326 *size = 0;
327 return(NULL);
328 }
329
330 /**
331 * xmlFuzzReadEntities:
332 *
333 * Read entities like the main XML file, external DTDs, external parsed
334 * entities from fuzz data.
335 */
336 void
xmlFuzzReadEntities(void)337 xmlFuzzReadEntities(void) {
338 size_t num = 0;
339
340 while (1) {
341 const char *url, *entity;
342 size_t urlSize, entitySize;
343 xmlFuzzEntityInfo *entityInfo;
344
345 url = xmlFuzzReadString(&urlSize);
346 if (url == NULL) break;
347
348 entity = xmlFuzzReadString(&entitySize);
349 if (entity == NULL) break;
350
351 /*
352 * Cap URL size to avoid quadratic behavior when generating
353 * error messages or looking up entities.
354 */
355 if (urlSize < 50 &&
356 xmlHashLookup(fuzzData.entities, (xmlChar *)url) == NULL) {
357 entityInfo = xmlMalloc(sizeof(xmlFuzzEntityInfo));
358 if (entityInfo == NULL)
359 break;
360 entityInfo->data = entity;
361 entityInfo->size = entitySize;
362
363 xmlHashAddEntry(fuzzData.entities, (xmlChar *)url, entityInfo);
364
365 if (num == 0) {
366 fuzzData.mainUrl = url;
367 fuzzData.mainEntity = entityInfo;
368 }
369
370 num++;
371 }
372 }
373 }
374
375 /**
376 * xmlFuzzMainUrl:
377 *
378 * Returns the main URL.
379 */
380 const char *
xmlFuzzMainUrl(void)381 xmlFuzzMainUrl(void) {
382 return(fuzzData.mainUrl);
383 }
384
385 /**
386 * xmlFuzzMainEntity:
387 * @size: size of the main entity in bytes
388 *
389 * Returns the main entity.
390 */
391 const char *
xmlFuzzMainEntity(size_t * size)392 xmlFuzzMainEntity(size_t *size) {
393 if (fuzzData.mainEntity == NULL)
394 return(NULL);
395 *size = fuzzData.mainEntity->size;
396 return(fuzzData.mainEntity->data);
397 }
398
399 /**
400 * xmlFuzzResourceLoader:
401 *
402 * The resource loader for fuzz data.
403 */
404 int
xmlFuzzResourceLoader(void * data ATTRIBUTE_UNUSED,const char * URL,const char * ID ATTRIBUTE_UNUSED,xmlResourceType type ATTRIBUTE_UNUSED,int flags ATTRIBUTE_UNUSED,xmlParserInputPtr * out)405 xmlFuzzResourceLoader(void *data ATTRIBUTE_UNUSED, const char *URL,
406 const char *ID ATTRIBUTE_UNUSED,
407 xmlResourceType type ATTRIBUTE_UNUSED,
408 int flags ATTRIBUTE_UNUSED, xmlParserInputPtr *out) {
409 xmlParserInputPtr input;
410 xmlFuzzEntityInfo *entity;
411
412 entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL);
413 if (entity == NULL)
414 return(XML_IO_ENOENT);
415
416 input = xmlNewInputFromMemory(URL, entity->data, entity->size,
417 XML_INPUT_BUF_STATIC |
418 XML_INPUT_BUF_ZERO_TERMINATED);
419 if (input == NULL)
420 return(XML_ERR_NO_MEMORY);
421
422 *out = input;
423 return(XML_ERR_OK);
424 }
425
426 /**
427 * xmlFuzzEntityLoader:
428 *
429 * The entity loader for fuzz data.
430 */
431 xmlParserInputPtr
xmlFuzzEntityLoader(const char * URL,const char * ID ATTRIBUTE_UNUSED,xmlParserCtxtPtr ctxt)432 xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
433 xmlParserCtxtPtr ctxt) {
434 xmlParserInputBufferPtr buf;
435 xmlFuzzEntityInfo *entity;
436
437 if (URL == NULL)
438 return(NULL);
439 entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL);
440 if (entity == NULL)
441 return(NULL);
442
443 buf = xmlParserInputBufferCreateMem(entity->data, entity->size,
444 XML_CHAR_ENCODING_NONE);
445 if (buf == NULL) {
446 xmlCtxtErrMemory(ctxt);
447 return(NULL);
448 }
449
450 return(xmlNewIOInputStream(ctxt, buf, XML_CHAR_ENCODING_NONE));
451 }
452
453 char *
xmlSlurpFile(const char * path,size_t * sizeRet)454 xmlSlurpFile(const char *path, size_t *sizeRet) {
455 FILE *file;
456 struct stat statbuf;
457 char *data;
458 size_t size;
459
460 if ((stat(path, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode)))
461 return(NULL);
462 size = statbuf.st_size;
463 file = fopen(path, "rb");
464 if (file == NULL)
465 return(NULL);
466 data = xmlMalloc(size + 1);
467 if (data != NULL) {
468 if (fread(data, 1, size, file) != size) {
469 xmlFree(data);
470 data = NULL;
471 } else {
472 data[size] = 0;
473 if (sizeRet != NULL)
474 *sizeRet = size;
475 }
476 }
477 fclose(file);
478
479 return(data);
480 }
481
482