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