Coverage Report

Created: 2024-02-04 06:18

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