Coverage Report

Created: 2025-07-12 06:33

/src/libxml2/fuzz/lint.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * lint.c: a libFuzzer target to test the xmllint executable.
3
 *
4
 * See Copyright for the status of this software.
5
 */
6
7
#include <fcntl.h>
8
#include <stdlib.h>
9
#include <stdio.h>
10
#include <unistd.h>
11
12
#include <libxml/catalog.h>
13
#include <libxml/parser.h>
14
#include <libxml/xmlerror.h>
15
#include <libxml/xmlmemory.h>
16
17
#include "private/lint.h"
18
19
#include "fuzz.h"
20
21
/*
22
 * Untested options:
23
 *
24
 * --memory: Requires temp file
25
 *
26
 * --catalogs: Requires XML catalogs
27
 *
28
 * --dtdvalid:
29
 * --dtdvalidfpi: Requires an external DTD
30
 *
31
 * --output: Writes to disk
32
 *
33
 * --path: Requires cooperation with resource loader
34
 *
35
 * --relaxng:
36
 * --schema:
37
 * --schematron: Requires schemas
38
 *
39
 * --shell: We could pipe fuzz data to stdin but this is probably
40
 *          not worth it.
41
 */
42
43
static const char *const switches[] = {
44
    "--auto",
45
    "--c14n",
46
    "--c14n11",
47
    "--compress",
48
    "--copy",
49
    "--debug",
50
    NULL,
51
    "--dropdtd",
52
    "--dtdattr",
53
    "--exc-c14n",
54
    "--format",
55
    NULL,
56
    "--huge",
57
    "--insert",
58
    "--loaddtd",
59
    "--load-trace",
60
    NULL,
61
    "--noblanks",
62
    "--nocdata",
63
    "--nocompact",
64
    "--nodefdtd",
65
    "--nodict",
66
    "--noenc",
67
    "--noent",
68
    "--nofixup-base-uris",
69
    "--nonet",
70
    "--noout",
71
    "--nowarning",
72
    NULL,
73
    "--noxincludenode",
74
    "--nsclean",
75
    "--oldxml10",
76
    "--pedantic",
77
    "--postvalid",
78
    "--push",
79
    "--pushsmall",
80
    "--quiet",
81
    "--recover",
82
    "--repeat",
83
    "--sax1",
84
    NULL,
85
    "--timing",
86
    "--valid",
87
    "--version",
88
    "--walker",
89
    "--xinclude",
90
    "--xmlout"
91
};
92
static const size_t numSwitches = sizeof(switches) / sizeof(switches[0]);
93
94
struct {
95
    const char **argv;
96
    size_t argi;
97
} vars;
98
99
static void
100
219k
pushArg(const char *str) {
101
219k
    vars.argv[vars.argi++] = str;
102
219k
}
103
104
int
105
LLVMFuzzerInitialize(int *argc ATTRIBUTE_UNUSED,
106
2
                     char ***argv ATTRIBUTE_UNUSED) {
107
2
    int fd;
108
109
    /* Redirect stdout to /dev/null */
110
2
    fd = open("/dev/null", O_WRONLY);
111
2
    if (fd == -1) {
112
0
        perror("/dev/null");
113
0
        abort();
114
0
    }
115
2
    if (dup2(fd, STDOUT_FILENO) == -1) {
116
0
        perror("dup2");
117
0
        abort();
118
0
    }
119
2
    close(fd);
120
121
2
    return 0;
122
2
}
123
124
int
125
10.6k
LLVMFuzzerTestOneInput(const char *data, size_t size) {
126
10.6k
    char maxmemBuf[20];
127
10.6k
    char maxAmplBuf[20];
128
10.6k
    char prettyBuf[20];
129
10.6k
    const char *sval, *docBuffer, *docUrl;
130
10.6k
    size_t ssize, docSize, i;
131
10.6k
    unsigned uval;
132
10.6k
    int ival;
133
134
10.6k
    if (xmlMemUsed() != 0) {
135
0
        fprintf(stderr, "Undetected leak in previous iteration\n");
136
0
        abort();
137
0
    }
138
139
10.6k
    vars.argv = malloc((numSwitches + 5 + 6 * 2) * sizeof(vars.argv[0]));
140
10.6k
    vars.argi = 0;
141
10.6k
    pushArg("xmllint"),
142
10.6k
    pushArg("--nocatalogs");
143
144
10.6k
    xmlFuzzDataInit(data, size);
145
146
513k
    for (i = 0; i < numSwitches; i++) {
147
502k
        if (i % 32 == 0)
148
21.3k
            uval = xmlFuzzReadInt(4);
149
502k
        if ((uval & 1) && (switches[i] != NULL))
150
138k
            pushArg(switches[i]);
151
502k
        uval >>= 1;
152
502k
    }
153
154
    /*
155
     * Use four main parsing modes with equal probability
156
     */
157
10.6k
    switch (uval & 3) {
158
5.82k
        case 0:
159
            /* XML parser */
160
5.82k
            break;
161
1.25k
        case 1:
162
            /* HTML parser */
163
1.25k
            pushArg("--html");
164
1.25k
            break;
165
2.97k
        case 2:
166
            /* XML reader */
167
2.97k
            pushArg("--stream");
168
2.97k
            break;
169
631
        case 3:
170
            /* SAX parser */
171
631
            pushArg("--sax");
172
631
            break;
173
10.6k
    }
174
175
10.6k
    uval = xmlFuzzReadInt(4);
176
10.6k
    if (uval > 0) {
177
618
        if (size <= (INT_MAX - 2000) / 20)
178
618
            uval %= size * 20 + 2000;
179
0
        else
180
0
            uval %= INT_MAX;
181
618
        snprintf(maxmemBuf, 20, "%u", uval);
182
618
        pushArg("--maxmem");
183
618
        pushArg(maxmemBuf);
184
618
    }
185
186
10.6k
    ival = xmlFuzzReadInt(1);
187
10.6k
    if (ival >= 1 && ival <= 5) {
188
897
        snprintf(maxAmplBuf, 20, "%d", ival);
189
897
        pushArg("--max-ampl");
190
897
        pushArg(maxAmplBuf);
191
897
    }
192
193
10.6k
    ival = xmlFuzzReadInt(1);
194
10.6k
    if (ival != 0) {
195
3.97k
        snprintf(prettyBuf, 20, "%d", ival % 4);
196
3.97k
        pushArg("--pretty");
197
3.97k
        pushArg(prettyBuf);
198
3.97k
    }
199
200
10.6k
    sval = xmlFuzzReadString(&ssize);
201
10.6k
    if (ssize > 0) {
202
1.19k
        pushArg("--encode");
203
1.19k
        pushArg(sval);
204
1.19k
    }
205
206
10.6k
    sval = xmlFuzzReadString(&ssize);
207
10.6k
    if (ssize > 0) {
208
1.80k
        pushArg("--pattern");
209
1.80k
        pushArg(sval);
210
1.80k
    }
211
212
10.6k
    sval = xmlFuzzReadString(&ssize);
213
10.6k
    if (ssize > 0) {
214
8.57k
        pushArg("--xpath");
215
8.57k
        pushArg(sval);
216
8.57k
    }
217
218
10.6k
    xmlFuzzReadEntities();
219
10.6k
    docBuffer = xmlFuzzMainEntity(&docSize);
220
10.6k
    docUrl = xmlFuzzMainUrl();
221
10.6k
    if (docBuffer == NULL || docUrl[0] == '-')
222
236
        goto exit;
223
10.4k
    pushArg(docUrl);
224
225
10.4k
    pushArg(NULL);
226
227
10.4k
    xmlSetGenericErrorFunc(NULL, xmlFuzzErrorFunc);
228
10.4k
#ifdef LIBXML_CATALOG_ENABLED
229
10.4k
    xmlCatalogSetDefaults(XML_CATA_ALLOW_NONE);
230
10.4k
#endif
231
232
10.4k
    xmllintMain(vars.argi - 1, vars.argv, stdout, xmlFuzzResourceLoader);
233
234
10.4k
    xmlMemSetup(free, malloc, realloc, xmlMemStrdup);
235
236
10.6k
exit:
237
10.6k
    xmlFuzzDataCleanup();
238
10.6k
    free(vars.argv);
239
10.6k
    return(0);
240
10.4k
}
241
242
size_t
243
LLVMFuzzerCustomMutator(char *data, size_t size, size_t maxSize,
244
0
                        unsigned seed) {
245
0
    static const xmlFuzzChunkDesc chunks[] = {
246
0
        { 8, XML_FUZZ_PROB_ONE / 10  }, /* switches */
247
0
        { 4, XML_FUZZ_PROB_ONE / 10  }, /* maxmem */
248
0
        { 1, XML_FUZZ_PROB_ONE / 100 }, /* maxAmpl */
249
0
        { 1, XML_FUZZ_PROB_ONE / 100 }, /* pretty */
250
0
        { 0, 0 }
251
0
    };
252
253
0
    return xmlFuzzMutateChunks(chunks, data, size, maxSize, seed,
254
0
                               LLVMFuzzerMutate);
255
0
}
256