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 | | |