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