Coverage Report

Created: 2025-07-01 06:27

/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
    const char *secondaryUrl;
44
    xmlFuzzEntityInfo *secondaryEntity;
45
} fuzzData;
46
47
size_t fuzzNumAttempts;
48
size_t fuzzFailurePos;
49
int fuzzAllocFailed;
50
int fuzzIoFailed;
51
52
/**
53
 * xmlFuzzErrorFunc:
54
 *
55
 * An error function that simply discards all errors.
56
 */
57
void
58
xmlFuzzErrorFunc(void *ctx ATTRIBUTE_UNUSED, const char *msg ATTRIBUTE_UNUSED,
59
2.44M
                 ...) {
60
2.44M
}
61
62
/**
63
 * xmlFuzzSErrorFunc:
64
 *
65
 * A structured error function that simply discards all errors.
66
 */
67
void
68
xmlFuzzSErrorFunc(void *ctx ATTRIBUTE_UNUSED,
69
0
                  const xmlError *error ATTRIBUTE_UNUSED) {
70
0
}
71
72
/*
73
 * Failure injection.
74
 *
75
 * To debug issues involving injected failures, it's often helpful to set
76
 * FAILURE_ABORT to 1. This should provide a backtrace of the failed
77
 * operation.
78
 */
79
80
#define XML_FUZZ_FAILURE_ABORT   0
81
82
void
83
48.7k
xmlFuzzInjectFailure(size_t failurePos) {
84
48.7k
    fuzzNumAttempts = 0;
85
48.7k
    fuzzFailurePos = failurePos;
86
48.7k
    fuzzAllocFailed = 0;
87
48.7k
    fuzzIoFailed = 0;
88
48.7k
}
89
90
static int
91
30.6M
xmlFuzzTryMalloc(void) {
92
30.6M
    if (fuzzFailurePos > 0) {
93
18.8M
        fuzzNumAttempts += 1;
94
18.8M
        if (fuzzNumAttempts == fuzzFailurePos) {
95
#if XML_FUZZ_FAILURE_ABORT
96
            abort();
97
#endif
98
2.36k
            fuzzAllocFailed = 1;
99
2.36k
            return -1;
100
2.36k
        }
101
18.8M
    }
102
103
30.6M
    return 0;
104
30.6M
}
105
106
static int
107
0
xmlFuzzTryIo(void) {
108
0
    if (fuzzFailurePos > 0) {
109
0
        fuzzNumAttempts += 1;
110
0
        if (fuzzNumAttempts == fuzzFailurePos) {
111
#if XML_FUZZ_FAILURE_ABORT
112
            abort();
113
#endif
114
0
            fuzzIoFailed = 1;
115
0
            return -1;
116
0
        }
117
0
    }
118
119
0
    return 0;
120
0
}
121
122
static void *
123
27.0M
xmlFuzzMalloc(size_t size) {
124
27.0M
    void *ret;
125
126
27.0M
    if (xmlFuzzTryMalloc() < 0)
127
2.07k
        return NULL;
128
129
27.0M
    ret = malloc(size);
130
27.0M
    if (ret == NULL)
131
0
        fuzzAllocFailed = 1;
132
133
27.0M
    return ret;
134
27.0M
}
135
136
static void *
137
3.58M
xmlFuzzRealloc(void *ptr, size_t size) {
138
3.58M
    void *ret;
139
140
3.58M
    if (xmlFuzzTryMalloc() < 0)
141
292
        return NULL;
142
143
3.58M
    ret = realloc(ptr, size);
144
3.58M
    if (ret == NULL)
145
0
        fuzzAllocFailed = 1;
146
147
3.58M
    return ret;
148
3.58M
}
149
150
void
151
2
xmlFuzzMemSetup(void) {
152
2
    xmlMemSetup(free, xmlFuzzMalloc, xmlFuzzRealloc, xmlMemStrdup);
153
2
}
154
155
int
156
0
xmlFuzzMallocFailed(void) {
157
0
    return fuzzAllocFailed;
158
0
}
159
160
void
161
24.2k
xmlFuzzResetFailure(void) {
162
24.2k
    fuzzAllocFailed = 0;
163
24.2k
    fuzzIoFailed = 0;
164
24.2k
}
165
166
void
167
48.4k
xmlFuzzCheckFailureReport(const char *func, int oomReport, int ioReport) {
168
48.4k
    if (oomReport >= 0 && fuzzAllocFailed != oomReport) {
169
0
        fprintf(stderr, "%s: malloc failure %s reported\n",
170
0
                func, fuzzAllocFailed ? "not" : "erroneously");
171
0
        abort();
172
0
    }
173
48.4k
    if (ioReport >= 0 && fuzzIoFailed != ioReport) {
174
0
        fprintf(stderr, "%s: IO failure %s reported\n",
175
0
                func, fuzzIoFailed ? "not" : "erroneously");
176
0
        abort();
177
0
    }
178
48.4k
    fuzzAllocFailed = 0;
179
48.4k
    fuzzIoFailed = 0;
180
48.4k
}
181
182
/**
183
 * xmlFuzzDataInit:
184
 *
185
 * Initialize fuzz data provider.
186
 */
187
void
188
24.4k
xmlFuzzDataInit(const char *data, size_t size) {
189
24.4k
    fuzzData.data = data;
190
24.4k
    fuzzData.size = size;
191
24.4k
    fuzzData.ptr = data;
192
24.4k
    fuzzData.remaining = size;
193
194
24.4k
    fuzzData.outBuf = xmlMalloc(size + 1);
195
24.4k
    fuzzData.outPtr = fuzzData.outBuf;
196
197
24.4k
    fuzzData.entities = xmlHashCreate(8);
198
24.4k
    fuzzData.mainUrl = NULL;
199
24.4k
    fuzzData.mainEntity = NULL;
200
24.4k
    fuzzData.secondaryUrl = NULL;
201
24.4k
    fuzzData.secondaryEntity = NULL;
202
24.4k
}
203
204
/**
205
 * xmlFuzzDataFree:
206
 *
207
 * Cleanup fuzz data provider.
208
 */
209
void
210
24.4k
xmlFuzzDataCleanup(void) {
211
24.4k
    xmlFree(fuzzData.outBuf);
212
24.4k
    xmlHashFree(fuzzData.entities, xmlHashDefaultDeallocator);
213
24.4k
}
214
215
/**
216
 * xmlFuzzWriteInt:
217
 * @out:  output file
218
 * @v:  integer to write
219
 * @size:  size of integer in bytes
220
 *
221
 * Write an integer to the fuzz data.
222
 */
223
void
224
0
xmlFuzzWriteInt(FILE *out, size_t v, int size) {
225
0
    int shift;
226
227
0
    while (size > (int) sizeof(size_t)) {
228
0
        putc(0, out);
229
0
        size--;
230
0
    }
231
232
0
    shift = size * 8;
233
0
    while (shift > 0) {
234
0
        shift -= 8;
235
0
        putc((v >> shift) & 255, out);
236
0
    }
237
0
}
238
239
/**
240
 * xmlFuzzReadInt:
241
 * @size:  size of integer in bytes
242
 *
243
 * Read an integer from the fuzz data.
244
 */
245
size_t
246
24.4k
xmlFuzzReadInt(int size) {
247
24.4k
    size_t ret = 0;
248
249
122k
    while ((size > 0) && (fuzzData.remaining > 0)) {
250
97.7k
        unsigned char c = (unsigned char) *fuzzData.ptr++;
251
97.7k
        fuzzData.remaining--;
252
97.7k
        ret = (ret << 8) | c;
253
97.7k
        size--;
254
97.7k
    }
255
256
24.4k
    return ret;
257
24.4k
}
258
259
/**
260
 * xmlFuzzBytesRemaining:
261
 *
262
 * Return number of remaining bytes in fuzz data.
263
 */
264
size_t
265
0
xmlFuzzBytesRemaining(void) {
266
0
    return(fuzzData.remaining);
267
0
}
268
269
/**
270
 * xmlFuzzReadRemaining:
271
 * @size:  size of string in bytes
272
 *
273
 * Read remaining bytes from fuzz data.
274
 */
275
const char *
276
0
xmlFuzzReadRemaining(size_t *size) {
277
0
    const char *ret = fuzzData.ptr;
278
279
0
    *size = fuzzData.remaining;
280
0
    fuzzData.ptr += fuzzData.remaining;
281
0
    fuzzData.remaining = 0;
282
283
0
    return(ret);
284
0
}
285
286
/*
287
 * xmlFuzzWriteString:
288
 * @out:  output file
289
 * @str:  string to write
290
 *
291
 * Write a random-length string to file in a format similar to
292
 * FuzzedDataProvider. Backslash followed by newline marks the end of the
293
 * string. Two backslashes are used to escape a backslash.
294
 */
295
void
296
0
xmlFuzzWriteString(FILE *out, const char *str) {
297
0
    for (; *str; str++) {
298
0
        int c = (unsigned char) *str;
299
0
        putc(c, out);
300
0
        if (c == '\\')
301
0
            putc(c, out);
302
0
    }
303
0
    putc('\\', out);
304
0
    putc('\n', out);
305
0
}
306
307
/**
308
 * xmlFuzzReadString:
309
 * @size:  size of string in bytes
310
 *
311
 * Read a random-length string from the fuzz data.
312
 *
313
 * The format is similar to libFuzzer's FuzzedDataProvider but treats
314
 * backslash followed by newline as end of string. This makes the fuzz data
315
 * more readable. A backslash character is escaped with another backslash.
316
 *
317
 * Returns a zero-terminated string or NULL if the fuzz data is exhausted.
318
 */
319
const char *
320
48.8k
xmlFuzzReadString(size_t *size) {
321
48.8k
    const char *out = fuzzData.outPtr;
322
323
6.24M
    while (fuzzData.remaining > 0) {
324
6.21M
        int c = *fuzzData.ptr++;
325
6.21M
        fuzzData.remaining--;
326
327
6.21M
        if ((c == '\\') && (fuzzData.remaining > 0)) {
328
26.9k
            int c2 = *fuzzData.ptr;
329
330
26.9k
            if (c2 == '\n') {
331
24.7k
                fuzzData.ptr++;
332
24.7k
                fuzzData.remaining--;
333
24.7k
                if (size != NULL)
334
24.7k
                    *size = fuzzData.outPtr - out;
335
24.7k
                *fuzzData.outPtr++ = '\0';
336
24.7k
                return(out);
337
24.7k
            }
338
2.14k
            if (c2 == '\\') {
339
413
                fuzzData.ptr++;
340
413
                fuzzData.remaining--;
341
413
            }
342
2.14k
        }
343
344
6.19M
        *fuzzData.outPtr++ = c;
345
6.19M
    }
346
347
24.1k
    if (fuzzData.outPtr > out) {
348
24.0k
        if (size != NULL)
349
24.0k
            *size = fuzzData.outPtr - out;
350
24.0k
        *fuzzData.outPtr++ = '\0';
351
24.0k
        return(out);
352
24.0k
    }
353
354
71
    if (size != NULL)
355
71
        *size = 0;
356
71
    return(NULL);
357
24.1k
}
358
359
/**
360
 * xmlFuzzReadEntities:
361
 *
362
 * Read entities like the main XML file, external DTDs, external parsed
363
 * entities from fuzz data.
364
 */
365
void
366
0
xmlFuzzReadEntities(void) {
367
0
    size_t num = 0;
368
369
0
    while (1) {
370
0
        const char *url, *entity;
371
0
        size_t urlSize, entitySize;
372
0
        xmlFuzzEntityInfo *entityInfo;
373
374
0
        url = xmlFuzzReadString(&urlSize);
375
0
        if (url == NULL) break;
376
377
0
        entity = xmlFuzzReadString(&entitySize);
378
0
        if (entity == NULL) break;
379
380
        /*
381
         * Cap URL size to avoid quadratic behavior when generating
382
         * error messages or looking up entities.
383
         */
384
0
        if (urlSize < 50 &&
385
0
            xmlHashLookup(fuzzData.entities, (xmlChar *)url) == NULL) {
386
0
            entityInfo = xmlMalloc(sizeof(xmlFuzzEntityInfo));
387
0
            if (entityInfo == NULL)
388
0
                break;
389
0
            entityInfo->data = entity;
390
0
            entityInfo->size = entitySize;
391
392
0
            xmlHashAddEntry(fuzzData.entities, (xmlChar *)url, entityInfo);
393
394
0
            if (num == 0) {
395
0
                fuzzData.mainUrl = url;
396
0
                fuzzData.mainEntity = entityInfo;
397
0
            } else if (num == 1) {
398
0
                fuzzData.secondaryUrl = url;
399
0
                fuzzData.secondaryEntity = entityInfo;
400
0
            }
401
402
0
            num++;
403
0
        }
404
0
    }
405
0
}
406
407
/**
408
 * xmlFuzzMainUrl:
409
 *
410
 * Returns the main URL.
411
 */
412
const char *
413
0
xmlFuzzMainUrl(void) {
414
0
    return(fuzzData.mainUrl);
415
0
}
416
417
/**
418
 * xmlFuzzMainEntity:
419
 * @size:  size of the main entity in bytes
420
 *
421
 * Returns the main entity.
422
 */
423
const char *
424
0
xmlFuzzMainEntity(size_t *size) {
425
0
    if (fuzzData.mainEntity == NULL)
426
0
        return(NULL);
427
0
    *size = fuzzData.mainEntity->size;
428
0
    return(fuzzData.mainEntity->data);
429
0
}
430
431
/**
432
 * xmlFuzzSecondaryUrl:
433
 *
434
 * Returns the secondary URL.
435
 */
436
const char *
437
0
xmlFuzzSecondaryUrl(void) {
438
0
    return(fuzzData.secondaryUrl);
439
0
}
440
441
/**
442
 * xmlFuzzSecondaryEntity:
443
 * @size:  size of the secondary entity in bytes
444
 *
445
 * Returns the secondary entity.
446
 */
447
const char *
448
0
xmlFuzzSecondaryEntity(size_t *size) {
449
0
    if (fuzzData.secondaryEntity == NULL)
450
0
        return(NULL);
451
0
    *size = fuzzData.secondaryEntity->size;
452
0
    return(fuzzData.secondaryEntity->data);
453
0
}
454
455
/**
456
 * xmlFuzzResourceLoader:
457
 *
458
 * The resource loader for fuzz data.
459
 */
460
xmlParserErrors
461
xmlFuzzResourceLoader(void *data ATTRIBUTE_UNUSED, const char *URL,
462
                      const char *ID ATTRIBUTE_UNUSED,
463
                      xmlResourceType type ATTRIBUTE_UNUSED,
464
                      xmlParserInputFlags flags ATTRIBUTE_UNUSED,
465
0
                      xmlParserInputPtr *out) {
466
0
    xmlParserInputPtr input;
467
0
    xmlFuzzEntityInfo *entity;
468
469
0
    entity = xmlHashLookup(fuzzData.entities, (xmlChar *) URL);
470
0
    if (entity == NULL)
471
0
        return(XML_IO_ENOENT);
472
473
    /* IO failure injection */
474
0
    if (xmlFuzzTryIo() < 0)
475
0
        return(XML_IO_EIO);
476
477
0
    input = xmlNewInputFromMemory(URL, entity->data, entity->size,
478
0
                                  XML_INPUT_BUF_STATIC |
479
0
                                  XML_INPUT_BUF_ZERO_TERMINATED);
480
0
    if (input == NULL)
481
0
        return(XML_ERR_NO_MEMORY);
482
483
0
    *out = input;
484
0
    return(XML_ERR_OK);
485
0
}
486
487
char *
488
0
xmlSlurpFile(const char *path, size_t *sizeRet) {
489
0
    FILE *file;
490
0
    struct stat statbuf;
491
0
    char *data;
492
0
    size_t size;
493
494
0
    if ((stat(path, &statbuf) != 0) || (!S_ISREG(statbuf.st_mode)))
495
0
        return(NULL);
496
0
    size = statbuf.st_size;
497
0
    file = fopen(path, "rb");
498
0
    if (file == NULL)
499
0
        return(NULL);
500
0
    data = xmlMalloc(size + 1);
501
0
    if (data != NULL) {
502
0
        if (fread(data, 1, size, file) != size) {
503
0
            xmlFree(data);
504
0
            data = NULL;
505
0
        } else {
506
0
            data[size] = 0;
507
0
            if (sizeRet != NULL)
508
0
                *sizeRet = size;
509
0
        }
510
0
    }
511
0
    fclose(file);
512
513
0
    return(data);
514
0
}
515
516
int
517
xmlFuzzOutputWrite(void *ctxt ATTRIBUTE_UNUSED,
518
0
                   const char *buffer ATTRIBUTE_UNUSED, int len) {
519
0
    if (xmlFuzzTryIo() < 0)
520
0
        return -XML_IO_EIO;
521
522
0
    return len;
523
0
}
524
525
int
526
0
xmlFuzzOutputClose(void *ctxt ATTRIBUTE_UNUSED) {
527
0
    if (xmlFuzzTryIo() < 0)
528
0
        return XML_IO_EIO;
529
530
0
    return 0;
531
0
}
532
533
/**
534
 * xmlFuzzMutateChunks:
535
 * @chunks: array of chunk descriptions
536
 * @data: fuzz data (from LLVMFuzzerCustomMutator)
537
 * @size: data size (from LLVMFuzzerCustomMutator)
538
 * @maxSize: max data size (from LLVMFuzzerCustomMutator)
539
 * @seed: seed (from LLVMFuzzerCustomMutator)
540
 * @mutator: mutator function, use LLVMFuzzerMutate
541
 *
542
 * Mutates one of several chunks with a given probability.
543
 *
544
 * Probability is a value between 0 and XML_FUZZ_PROB_ONE.
545
 *
546
 * The last chunk has flexible size and must have size and
547
 * mutateProb set to 0.
548
 *
549
 * Returns the size of the mutated data like LLVMFuzzerCustomMutator.
550
 */
551
size_t
552
xmlFuzzMutateChunks(const xmlFuzzChunkDesc *chunks,
553
                    char *data, size_t size, size_t maxSize, unsigned seed,
554
0
                    xmlFuzzMutator mutator) {
555
0
    size_t off = 0;
556
0
    size_t ret, chunkSize, maxChunkSize, mutSize;
557
0
    unsigned prob = seed % XML_FUZZ_PROB_ONE;
558
0
    unsigned descSize = 0;
559
0
    int i = 0;
560
561
0
    while (1) {
562
0
        unsigned descProb;
563
564
0
        descSize = chunks[i].size;
565
0
        descProb = chunks[i].mutateProb;
566
567
0
        if (descSize == 0 ||
568
0
            off + descSize > size ||
569
0
            off + descSize >= maxSize ||
570
0
            prob < descProb)
571
0
            break;
572
573
0
        off += descSize;
574
0
        prob -= descProb;
575
0
        i += 1;
576
0
    }
577
578
0
    chunkSize = size - off;
579
0
    maxChunkSize = maxSize - off;
580
581
0
    if (descSize != 0) {
582
0
        if (chunkSize > descSize)
583
0
            chunkSize = descSize;
584
0
        if (maxChunkSize > descSize)
585
0
            maxChunkSize = descSize;
586
0
    }
587
588
0
    mutSize = mutator(data + off, chunkSize, maxChunkSize);
589
590
0
    if (size > off + chunkSize) {
591
0
        size_t j;
592
593
0
        for (j = mutSize; j < chunkSize; j++)
594
0
            data[off + j] = 0;
595
596
0
        ret = size;
597
0
    } else {
598
0
        ret = off + mutSize;
599
0
    }
600
601
0
    return ret;
602
0
}
603