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