• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
48 /**
49  * xmlFuzzErrorFunc:
50  *
51  * An error function that simply discards all errors.
52  */
53 void
xmlFuzzErrorFunc(void * ctx ATTRIBUTE_UNUSED,const char * msg ATTRIBUTE_UNUSED,...)54 xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
55                  ...) {
56 }
57 
58 /*
59  * Malloc failure injection.
60  *
61  * Quick tip to debug complicated issues: Increase MALLOC_OFFSET until
62  * the crash disappears (or a different issue is triggered). Then set
63  * the offset to the highest value that produces a crash and set
64  * MALLOC_ABORT to 1 to see which failed memory allocation causes the
65  * issue.
66  */
67 
68 #define XML_FUZZ_MALLOC_OFFSET  0
69 #define XML_FUZZ_MALLOC_ABORT   0
70 
71 static void *
xmlFuzzMalloc(size_t size)72 xmlFuzzMalloc(size_t size) {
73     if (fuzzMaxAllocs > 0) {
74         if (fuzzNumAllocs >= fuzzMaxAllocs - 1)
75 #if XML_FUZZ_MALLOC_ABORT
76             abort();
77 #else
78             return(NULL);
79 #endif
80         fuzzNumAllocs += 1;
81     }
82     return malloc(size);
83 }
84 
85 static void *
xmlFuzzRealloc(void * ptr,size_t size)86 xmlFuzzRealloc(void *ptr, size_t size) {
87     if (fuzzMaxAllocs > 0) {
88         if (fuzzNumAllocs >= fuzzMaxAllocs - 1)
89 #if XML_FUZZ_MALLOC_ABORT
90             abort();
91 #else
92             return(NULL);
93 #endif
94         fuzzNumAllocs += 1;
95     }
96     return realloc(ptr, size);
97 }
98 
99 void
xmlFuzzMemSetup(void)100 xmlFuzzMemSetup(void) {
101     xmlMemSetup(free, xmlFuzzMalloc, xmlFuzzRealloc, xmlMemStrdup);
102 }
103 
104 void
xmlFuzzMemSetLimit(size_t limit)105 xmlFuzzMemSetLimit(size_t limit) {
106     fuzzNumAllocs = 0;
107     fuzzMaxAllocs = limit ? limit + XML_FUZZ_MALLOC_OFFSET : 0;
108 }
109 
110 /**
111  * xmlFuzzDataInit:
112  *
113  * Initialize fuzz data provider.
114  */
115 void
xmlFuzzDataInit(const char * data,size_t size)116 xmlFuzzDataInit(const char *data, size_t size) {
117     fuzzData.data = data;
118     fuzzData.size = size;
119     fuzzData.ptr = data;
120     fuzzData.remaining = size;
121 
122     fuzzData.outBuf = xmlMalloc(size + 1);
123     fuzzData.outPtr = fuzzData.outBuf;
124 
125     fuzzData.entities = xmlHashCreate(8);
126     fuzzData.mainUrl = NULL;
127     fuzzData.mainEntity = NULL;
128 }
129 
130 /**
131  * xmlFuzzDataFree:
132  *
133  * Cleanup fuzz data provider.
134  */
135 void
xmlFuzzDataCleanup(void)136 xmlFuzzDataCleanup(void) {
137     xmlFree(fuzzData.outBuf);
138     xmlHashFree(fuzzData.entities, xmlHashDefaultDeallocator);
139 }
140 
141 /**
142  * xmlFuzzWriteInt:
143  * @out:  output file
144  * @v:  integer to write
145  * @size:  size of integer in bytes
146  *
147  * Write an integer to the fuzz data.
148  */
149 void
xmlFuzzWriteInt(FILE * out,size_t v,int size)150 xmlFuzzWriteInt(FILE *out, size_t v, int size) {
151     int shift;
152 
153     while (size > (int) sizeof(size_t)) {
154         putc(0, out);
155         size--;
156     }
157 
158     shift = size * 8;
159     while (shift > 0) {
160         shift -= 8;
161         putc((v >> shift) & 255, out);
162     }
163 }
164 
165 /**
166  * xmlFuzzReadInt:
167  * @size:  size of integer in bytes
168  *
169  * Read an integer from the fuzz data.
170  */
171 size_t
xmlFuzzReadInt(int size)172 xmlFuzzReadInt(int size) {
173     size_t ret = 0;
174 
175     while ((size > 0) && (fuzzData.remaining > 0)) {
176         unsigned char c = (unsigned char) *fuzzData.ptr++;
177         fuzzData.remaining--;
178         ret = (ret << 8) | c;
179         size--;
180     }
181 
182     return ret;
183 }
184 
185 /**
186  * xmlFuzzReadRemaining:
187  * @size:  size of string in bytes
188  *
189  * Read remaining bytes from fuzz data.
190  */
191 const char *
xmlFuzzReadRemaining(size_t * size)192 xmlFuzzReadRemaining(size_t *size) {
193     const char *ret = fuzzData.ptr;
194 
195     *size = fuzzData.remaining;
196     fuzzData.ptr += fuzzData.remaining;
197     fuzzData.remaining = 0;
198 
199     return(ret);
200 }
201 
202 /*
203  * xmlFuzzWriteString:
204  * @out:  output file
205  * @str:  string to write
206  *
207  * Write a random-length string to file in a format similar to
208  * FuzzedDataProvider. Backslash followed by newline marks the end of the
209  * string. Two backslashes are used to escape a backslash.
210  */
211 void
xmlFuzzWriteString(FILE * out,const char * str)212 xmlFuzzWriteString(FILE *out, const char *str) {
213     for (; *str; str++) {
214         int c = (unsigned char) *str;
215         putc(c, out);
216         if (c == '\\')
217             putc(c, out);
218     }
219     putc('\\', out);
220     putc('\n', out);
221 }
222 
223 /**
224  * xmlFuzzReadString:
225  * @size:  size of string in bytes
226  *
227  * Read a random-length string from the fuzz data.
228  *
229  * The format is similar to libFuzzer's FuzzedDataProvider but treats
230  * backslash followed by newline as end of string. This makes the fuzz data
231  * more readable. A backslash character is escaped with another backslash.
232  *
233  * Returns a zero-terminated string or NULL if the fuzz data is exhausted.
234  */
235 const char *
xmlFuzzReadString(size_t * size)236 xmlFuzzReadString(size_t *size) {
237     const char *out = fuzzData.outPtr;
238 
239     while (fuzzData.remaining > 0) {
240         int c = *fuzzData.ptr++;
241         fuzzData.remaining--;
242 
243         if ((c == '\\') && (fuzzData.remaining > 0)) {
244             int c2 = *fuzzData.ptr;
245 
246             if (c2 == '\n') {
247                 fuzzData.ptr++;
248                 fuzzData.remaining--;
249                 if (size != NULL)
250                     *size = fuzzData.outPtr - out;
251                 *fuzzData.outPtr++ = '\0';
252                 return(out);
253             }
254             if (c2 == '\\') {
255                 fuzzData.ptr++;
256                 fuzzData.remaining--;
257             }
258         }
259 
260         *fuzzData.outPtr++ = c;
261     }
262 
263     if (fuzzData.outPtr > out) {
264         if (size != NULL)
265             *size = fuzzData.outPtr - out;
266         *fuzzData.outPtr++ = '\0';
267         return(out);
268     }
269 
270     if (size != NULL)
271         *size = 0;
272     return(NULL);
273 }
274 
275 /**
276  * xmlFuzzReadEntities:
277  *
278  * Read entities like the main XML file, external DTDs, external parsed
279  * entities from fuzz data.
280  */
281 void
xmlFuzzReadEntities(void)282 xmlFuzzReadEntities(void) {
283     size_t num = 0;
284 
285     while (1) {
286         const char *url, *entity;
287         size_t entitySize;
288         xmlFuzzEntityInfo *entityInfo;
289 
290         url = xmlFuzzReadString(NULL);
291         if (url == NULL) break;
292 
293         entity = xmlFuzzReadString(&entitySize);
294         if (entity == NULL) break;
295 
296         if (xmlHashLookup(fuzzData.entities, (xmlChar *)url) == NULL) {
297             entityInfo = xmlMalloc(sizeof(xmlFuzzEntityInfo));
298             if (entityInfo == NULL)
299                 break;
300             entityInfo->data = entity;
301             entityInfo->size = entitySize;
302 
303             xmlHashAddEntry(fuzzData.entities, (xmlChar *)url, entityInfo);
304 
305             if (num == 0) {
306                 fuzzData.mainUrl = url;
307                 fuzzData.mainEntity = entityInfo;
308             }
309 
310             num++;
311         }
312     }
313 }
314 
315 /**
316  * xmlFuzzMainUrl:
317  *
318  * Returns the main URL.
319  */
320 const char *
xmlFuzzMainUrl(void)321 xmlFuzzMainUrl(void) {
322     return(fuzzData.mainUrl);
323 }
324 
325 /**
326  * xmlFuzzMainEntity:
327  * @size:  size of the main entity in bytes
328  *
329  * Returns the main entity.
330  */
331 const char *
xmlFuzzMainEntity(size_t * size)332 xmlFuzzMainEntity(size_t *size) {
333     if (fuzzData.mainEntity == NULL)
334         return(NULL);
335     *size = fuzzData.mainEntity->size;
336     return(fuzzData.mainEntity->data);
337 }
338 
339 /**
340  * xmlFuzzEntityLoader:
341  *
342  * The entity loader for fuzz data.
343  */
344 xmlParserInputPtr
xmlFuzzEntityLoader(const char * URL,const char * ID ATTRIBUTE_UNUSED,xmlParserCtxtPtr ctxt)345 xmlFuzzEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
346                     xmlParserCtxtPtr ctxt) {
347     xmlParserInputPtr input;
348     xmlFuzzEntityInfo *entity;
349 
350     if (URL == NULL)
351         return(NULL);
352     entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL);
353     if (entity == NULL)
354         return(NULL);
355 
356     input = xmlNewInputStream(ctxt);
357     if (input == NULL)
358         return(NULL);
359     input->filename = (char *) xmlCharStrdup(URL);
360     input->buf = xmlParserInputBufferCreateMem(entity->data, entity->size,
361                                                XML_CHAR_ENCODING_NONE);
362     if (input->buf == NULL) {
363         xmlFreeInputStream(input);
364         return(NULL);
365     }
366     input->base = input->cur = xmlBufContent(input->buf->buffer);
367     input->end = input->base + entity->size;
368 
369     return input;
370 }
371 
372 char *
xmlSlurpFile(const char * path,size_t * sizeRet)373 xmlSlurpFile(const char *path, size_t *sizeRet) {
374     FILE *file;
375     struct stat statbuf;
376     char *data;
377     size_t size;
378 
379     if ((stat(path, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode)))
380         return(NULL);
381     size = statbuf.st_size;
382     file = fopen(path, "rb");
383     if (file == NULL)
384         return(NULL);
385     data = xmlMalloc(size + 1);
386     if (data != NULL) {
387         if (fread(data, 1, size, file) != size) {
388             xmlFree(data);
389             data = NULL;
390         } else {
391             data[size] = 0;
392             if (sizeRet != NULL)
393                 *sizeRet = size;
394         }
395     }
396     fclose(file);
397 
398     return(data);
399 }
400 
401