Coverage Report

Created: 2026-01-09 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxml2/fuzz/xml.c
Line
Count
Source
1
/*
2
 * xml.c: a libFuzzer target to test several XML parser interfaces.
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
11
#include <libxml/catalog.h>
12
#include <libxml/parser.h>
13
#include <libxml/tree.h>
14
#include <libxml/xmlerror.h>
15
#include <libxml/xmlsave.h>
16
#include "fuzz.h"
17
18
int
19
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
20
2
                     char ***argv ATTRIBUTE_UNUSED) {
21
2
    xmlFuzzMemSetup();
22
2
    xmlInitParser();
23
2
#ifdef LIBXML_CATALOG_ENABLED
24
2
    xmlInitializeCatalog();
25
2
    xmlCatalogSetDefaults(XML_CATA_ALLOW_NONE);
26
2
#endif
27
28
2
    return 0;
29
2
}
30
31
int
32
26.5k
LLVMFuzzerTestOneInput(const char *data, size_t size) {
33
26.5k
    xmlParserCtxtPtr ctxt;
34
26.5k
    xmlDocPtr doc;
35
26.5k
    const char *docBuffer, *docUrl;
36
26.5k
    size_t failurePos, docSize, maxChunkSize;
37
26.5k
    int opts;
38
26.5k
    int errorCode;
39
26.5k
#ifdef LIBXML_OUTPUT_ENABLED
40
26.5k
    xmlBufferPtr outbuf = NULL;
41
26.5k
    const char *saveEncoding;
42
26.5k
    int saveOpts;
43
26.5k
#endif
44
45
26.5k
    xmlFuzzDataInit(data, size);
46
26.5k
    opts = (int) xmlFuzzReadInt(4);
47
    /*
48
     * Disable options that are known to cause timeouts
49
     */
50
26.5k
    opts &= ~XML_PARSE_DTDVALID &
51
26.5k
            ~XML_PARSE_SAX1;
52
26.5k
    failurePos = xmlFuzzReadInt(4) % (size + 100);
53
54
26.5k
    maxChunkSize = xmlFuzzReadInt(4) % (size + size / 8 + 1);
55
26.5k
    if (maxChunkSize == 0)
56
2.31k
        maxChunkSize = 1;
57
58
26.5k
#ifdef LIBXML_OUTPUT_ENABLED
59
    /* TODO: Take from fuzz data */
60
26.5k
    saveOpts = 0;
61
26.5k
    saveEncoding = NULL;
62
26.5k
#endif
63
64
26.5k
    xmlFuzzReadEntities();
65
26.5k
    docBuffer = xmlFuzzMainEntity(&docSize);
66
26.5k
    docUrl = xmlFuzzMainUrl();
67
26.5k
    if (docBuffer == NULL)
68
133
        goto exit;
69
70
    /* Pull parser */
71
72
26.4k
    xmlFuzzInjectFailure(failurePos);
73
26.4k
    ctxt = xmlNewParserCtxt();
74
26.4k
    if (ctxt == NULL) {
75
22
        errorCode = XML_ERR_NO_MEMORY;
76
26.3k
    } else {
77
26.3k
        xmlCtxtSetErrorHandler(ctxt, xmlFuzzSErrorFunc, NULL);
78
26.3k
        xmlCtxtSetResourceLoader(ctxt, xmlFuzzResourceLoader, NULL);
79
26.3k
        doc = xmlCtxtReadMemory(ctxt, docBuffer, docSize, docUrl, NULL, opts);
80
26.3k
        errorCode = ctxt->errNo;
81
26.3k
        xmlFuzzCheckFailureReport("xmlCtxtReadMemory",
82
26.3k
                doc == NULL && errorCode == XML_ERR_NO_MEMORY,
83
26.3k
                doc == NULL && errorCode == XML_IO_EIO);
84
85
26.3k
        if (doc != NULL) {
86
14.3k
#ifdef LIBXML_OUTPUT_ENABLED
87
14.3k
            xmlSaveCtxtPtr save;
88
89
14.3k
            outbuf = xmlBufferCreate();
90
91
            /* Also test the serializer. */
92
14.3k
            save = xmlSaveToBuffer(outbuf, saveEncoding, saveOpts);
93
94
14.3k
            if (save == NULL) {
95
421
                xmlBufferFree(outbuf);
96
421
                outbuf = NULL;
97
13.9k
            } else {
98
13.9k
                int saveErr;
99
100
13.9k
                xmlSaveDoc(save, doc);
101
13.9k
                saveErr = xmlSaveFinish(save);
102
13.9k
                xmlFuzzCheckFailureReport("xmlSaveToBuffer",
103
13.9k
                                          saveErr == XML_ERR_NO_MEMORY,
104
13.9k
                                          saveErr == XML_IO_EIO);
105
13.9k
                if (saveErr != XML_ERR_OK) {
106
57
                    xmlBufferFree(outbuf);
107
57
                    outbuf = NULL;
108
57
                }
109
13.9k
            }
110
14.3k
#endif
111
14.3k
            xmlFreeDoc(doc);
112
14.3k
        }
113
114
26.3k
        xmlFreeParserCtxt(ctxt);
115
26.3k
    }
116
117
    /* Push parser */
118
119
26.4k
#ifdef LIBXML_PUSH_ENABLED
120
26.4k
    xmlFuzzInjectFailure(failurePos);
121
    /*
122
     * FIXME: xmlCreatePushParserCtxt can still report OOM errors
123
     * to stderr.
124
     */
125
26.4k
    xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
126
26.4k
    ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, docUrl);
127
26.4k
    xmlSetGenericErrorFunc(NULL, NULL);
128
129
26.4k
    if (ctxt != NULL) {
130
26.3k
        size_t consumed;
131
26.3k
        int errorCodePush, numChunks, maxChunks;
132
133
26.3k
        xmlCtxtSetErrorHandler(ctxt, xmlFuzzSErrorFunc, NULL);
134
26.3k
        xmlCtxtSetResourceLoader(ctxt, xmlFuzzResourceLoader, NULL);
135
26.3k
        xmlCtxtUseOptions(ctxt, opts);
136
137
26.3k
        consumed = 0;
138
26.3k
        numChunks = 0;
139
26.3k
        maxChunks = 50 + docSize / 100;
140
478k
        while (numChunks == 0 ||
141
452k
               (consumed < docSize && numChunks < maxChunks)) {
142
452k
            size_t chunkSize;
143
452k
            int terminate;
144
145
452k
            numChunks += 1;
146
452k
            chunkSize = docSize - consumed;
147
148
452k
            if (numChunks < maxChunks && chunkSize > maxChunkSize) {
149
425k
                chunkSize = maxChunkSize;
150
425k
                terminate = 0;
151
425k
            } else {
152
26.3k
                terminate = 1;
153
26.3k
            }
154
155
452k
            xmlParseChunk(ctxt, docBuffer + consumed, chunkSize, terminate);
156
452k
            consumed += chunkSize;
157
452k
        }
158
159
26.3k
        errorCodePush = ctxt->errNo;
160
26.3k
        xmlFuzzCheckFailureReport("xmlParseChunk",
161
26.3k
                                  errorCodePush == XML_ERR_NO_MEMORY,
162
26.3k
                                  errorCodePush == XML_IO_EIO);
163
26.3k
        doc = ctxt->myDoc;
164
165
        /*
166
         * Push and pull parser differ in when exactly they
167
         * stop parsing, and the error code is the *last* error
168
         * reported, so we can't check whether the codes match.
169
         */
170
26.3k
        if (errorCode != XML_ERR_NO_MEMORY &&
171
21.3k
            errorCode != XML_IO_EIO &&
172
21.3k
            errorCodePush != XML_ERR_NO_MEMORY &&
173
21.2k
            errorCodePush != XML_IO_EIO &&
174
21.2k
            (errorCode == XML_ERR_OK) != (errorCodePush == XML_ERR_OK)) {
175
0
            fprintf(stderr, "pull/push parser error mismatch: %d != %d\n",
176
0
                    errorCode, errorCodePush);
177
#if 0
178
            FILE *f = fopen("c.xml", "wb");
179
            fwrite(docBuffer, docSize, 1, f);
180
            fclose(f);
181
#endif
182
0
            abort();
183
0
        }
184
185
26.3k
#ifdef LIBXML_OUTPUT_ENABLED
186
        /*
187
         * Verify that pull and push parser produce the same result.
188
         *
189
         * The NOBLANKS option doesn't work reliably in push mode.
190
         */
191
26.3k
        if ((opts & XML_PARSE_NOBLANKS) == 0 &&
192
15.0k
            errorCode == XML_ERR_OK &&
193
133
            errorCodePush == XML_ERR_OK &&
194
133
            outbuf != NULL) {
195
131
            xmlBufferPtr outbufPush;
196
131
            xmlSaveCtxtPtr save;
197
198
131
            outbufPush = xmlBufferCreate();
199
200
131
            save = xmlSaveToBuffer(outbufPush, saveEncoding, saveOpts);
201
202
131
            if (save != NULL) {
203
131
                int saveErr;
204
205
131
                xmlSaveDoc(save, doc);
206
131
                saveErr = xmlSaveFinish(save);
207
208
131
                if (saveErr == XML_ERR_OK) {
209
131
                    int outbufSize = xmlBufferLength(outbuf);
210
211
131
                    if (outbufSize != xmlBufferLength(outbufPush) ||
212
131
                        memcmp(xmlBufferContent(outbuf),
213
131
                               xmlBufferContent(outbufPush),
214
131
                               outbufSize) != 0) {
215
0
                        fprintf(stderr, "pull/push parser roundtrip "
216
0
                                "mismatch\n");
217
#if 0
218
                        FILE *f = fopen("c.xml", "wb");
219
                        fwrite(docBuffer, docSize, 1, f);
220
                        fclose(f);
221
                        fprintf(stderr, "opts: %X\n", opts);
222
                        fprintf(stderr, "---\n%s\n---\n%s\n---\n",
223
                                xmlBufferContent(outbuf),
224
                                xmlBufferContent(outbufPush));
225
#endif
226
0
                        abort();
227
0
                    }
228
131
                }
229
131
            }
230
231
131
            xmlBufferFree(outbufPush);
232
131
        }
233
26.3k
#endif
234
235
26.3k
        xmlFreeDoc(doc);
236
26.3k
        xmlFreeParserCtxt(ctxt);
237
26.3k
    }
238
26.4k
#endif
239
240
26.5k
exit:
241
26.5k
#ifdef LIBXML_OUTPUT_ENABLED
242
26.5k
    xmlBufferFree(outbuf);
243
26.5k
#endif
244
26.5k
    xmlFuzzInjectFailure(0);
245
26.5k
    xmlFuzzDataCleanup();
246
26.5k
    xmlResetLastError();
247
26.5k
    return(0);
248
26.4k
}
249
250
size_t
251
LLVMFuzzerCustomMutator(char *data, size_t size, size_t maxSize,
252
0
                        unsigned seed) {
253
0
    static const xmlFuzzChunkDesc chunks[] = {
254
0
        { 4, XML_FUZZ_PROB_ONE / 10 }, /* opts */
255
0
        { 4, XML_FUZZ_PROB_ONE / 10 }, /* failurePos */
256
0
        { 4, XML_FUZZ_PROB_ONE / 10 }, /* maxChunkSize */
257
0
        { 0, 0 }
258
0
    };
259
260
0
    return xmlFuzzMutateChunks(chunks, data, size, maxSize, seed,
261
0
                               LLVMFuzzerMutate);
262
0
}
263