1 /*
2 __ __ _
3 ___\ \/ /_ __ __ _| |_
4 / _ \\ /| '_ \ / _` | __|
5 | __// \| |_) | (_| | |_
6 \___/_/\_\ .__/ \__,_|\__|
7 |_| XML parser
8
9 Copyright (c) 2022 Mark Brand <markbrand@google.com>
10 Copyright (c) 2025 Sebastian Pipping <sebastian@pipping.org>
11 Copyright (c) 2026 Braian Plaku <braianplaku@gmail.com>
12 Licensed under the MIT license:
13
14 Permission is hereby granted, free of charge, to any person obtaining
15 a copy of this software and associated documentation files (the
16 "Software"), to deal in the Software without restriction, including
17 without limitation the rights to use, copy, modify, merge, publish,
18 distribute, sublicense, and/or sell copies of the Software, and to permit
19 persons to whom the Software is furnished to do so, subject to the
20 following conditions:
21
22 The above copyright notice and this permission notice shall be included
23 in all copies or substantial portions of the Software.
24
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
28 NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
29 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
30 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
31 USE OR OTHER DEALINGS IN THE SOFTWARE.
32 */
33
34 #if defined(NDEBUG)
35 # undef NDEBUG // because checks below rely on assert(...)
36 #endif
37
38 #include <assert.h>
39 #include <stdint.h>
40 #include <vector>
41
42 #include "expat.h"
43 #include "xml_lpm_fuzzer.pb.h"
44 #include "src/libfuzzer/libfuzzer_macro.h"
45
46 static const char *g_encoding = nullptr;
47 static const char *g_external_entity = nullptr;
48 static size_t g_external_entity_size = 0;
49
50 void
SetEncoding(const xml_lpm_fuzzer::Encoding & e)51 SetEncoding(const xml_lpm_fuzzer::Encoding &e) {
52 switch (e) {
53 case xml_lpm_fuzzer::Encoding::UTF8:
54 g_encoding = "UTF-8";
55 break;
56
57 case xml_lpm_fuzzer::Encoding::UTF16:
58 g_encoding = "UTF-16";
59 break;
60
61 case xml_lpm_fuzzer::Encoding::ISO88591:
62 g_encoding = "ISO-8859-1";
63 break;
64
65 case xml_lpm_fuzzer::Encoding::ASCII:
66 g_encoding = "US-ASCII";
67 break;
68
69 case xml_lpm_fuzzer::Encoding::NONE:
70 g_encoding = NULL;
71 break;
72
73 default:
74 g_encoding = "UNKNOWN";
75 break;
76 }
77 }
78
79 static int g_allocation_count = 0;
80 static std::vector<int> g_fail_allocations = {};
81
82 void *
MallocHook(size_t size)83 MallocHook(size_t size) {
84 g_allocation_count += 1;
85 for (auto index : g_fail_allocations) {
86 if (index == g_allocation_count) {
87 return NULL;
88 }
89 }
90 return malloc(size);
91 }
92
93 void *
ReallocHook(void * ptr,size_t size)94 ReallocHook(void *ptr, size_t size) {
95 g_allocation_count += 1;
96 for (auto index : g_fail_allocations) {
97 if (index == g_allocation_count) {
98 return NULL;
99 }
100 }
101 return realloc(ptr, size);
102 }
103
104 void
FreeHook(void * ptr)105 FreeHook(void *ptr) {
106 free(ptr);
107 }
108
109 XML_Memory_Handling_Suite memory_handling_suite
110 = {MallocHook, ReallocHook, FreeHook};
111
112 void InitializeParser(XML_Parser parser);
113
114 // We want a parse function that supports resumption, so that we can cover the
115 // suspend/resume code.
116 enum XML_Status
Parse(XML_Parser parser,const char * input,int input_len,int is_final)117 Parse(XML_Parser parser, const char *input, int input_len, int is_final) {
118 enum XML_Status status = XML_Parse(parser, input, input_len, is_final);
119 while (status == XML_STATUS_SUSPENDED) {
120 status = XML_ResumeParser(parser);
121 }
122 return status;
123 }
124
125 // When the fuzzer is compiled with instrumentation such as ASan, then the
126 // accesses in TouchString will fault if they access invalid memory (ie. detect
127 // either a use-after-free or buffer-overflow). By calling TouchString in each
128 // of the callbacks, we can check that the arguments meet the API specifications
129 // in terms of length/null-termination. no_optimize is used to ensure that the
130 // compiler has to emit actual memory reads, instead of removing them.
131 static volatile size_t no_optimize = 0;
132 static void
TouchString(const XML_Char * ptr,int len=-1)133 TouchString(const XML_Char *ptr, int len = -1) {
134 if (! ptr) {
135 return;
136 }
137
138 if (len == -1) {
139 for (XML_Char value = *ptr++; value; value = *ptr++) {
140 no_optimize += value;
141 }
142 } else {
143 for (int i = 0; i < len; ++i) {
144 no_optimize += ptr[i];
145 }
146 }
147 }
148
149 static void
TouchNodeAndRecurse(XML_Content * content)150 TouchNodeAndRecurse(XML_Content *content) {
151 switch (content->type) {
152 case XML_CTYPE_EMPTY:
153 case XML_CTYPE_ANY:
154 assert(content->quant == XML_CQUANT_NONE);
155 assert(content->name == NULL);
156 assert(content->numchildren == 0);
157 assert(content->children == NULL);
158 break;
159
160 case XML_CTYPE_MIXED:
161 assert(content->quant == XML_CQUANT_NONE
162 || content->quant == XML_CQUANT_REP);
163 assert(content->name == NULL);
164 for (unsigned int i = 0; i < content->numchildren; ++i) {
165 assert(content->children[i].type == XML_CTYPE_NAME);
166 assert(content->children[i].quant == XML_CQUANT_NONE);
167 assert(content->children[i].numchildren == 0);
168 assert(content->children[i].children == NULL);
169 TouchString(content->children[i].name);
170 }
171 break;
172
173 case XML_CTYPE_NAME:
174 assert((content->quant == XML_CQUANT_NONE)
175 || (content->quant == XML_CQUANT_OPT)
176 || (content->quant == XML_CQUANT_REP)
177 || (content->quant == XML_CQUANT_PLUS));
178 assert(content->numchildren == 0);
179 assert(content->children == NULL);
180 TouchString(content->name);
181 break;
182
183 case XML_CTYPE_CHOICE:
184 case XML_CTYPE_SEQ:
185 assert((content->quant == XML_CQUANT_NONE)
186 || (content->quant == XML_CQUANT_OPT)
187 || (content->quant == XML_CQUANT_REP)
188 || (content->quant == XML_CQUANT_PLUS));
189 assert(content->name == NULL);
190 for (unsigned int i = 0; i < content->numchildren; ++i) {
191 TouchNodeAndRecurse(&content->children[i]);
192 }
193 break;
194
195 default:
196 assert(false);
197 }
198 }
199
200 static void XMLCALL
ElementDeclHandler(void * userData,const XML_Char * name,XML_Content * model)201 ElementDeclHandler(void *userData, const XML_Char *name, XML_Content *model) {
202 TouchString(name);
203 TouchNodeAndRecurse(model);
204 XML_FreeContentModel((XML_Parser)userData, model);
205 }
206
207 static void XMLCALL
AttlistDeclHandler(void * userData,const XML_Char * elname,const XML_Char * attname,const XML_Char * atttype,const XML_Char * dflt,int isrequired)208 AttlistDeclHandler(void *userData, const XML_Char *elname,
209 const XML_Char *attname, const XML_Char *atttype,
210 const XML_Char *dflt, int isrequired) {
211 (void)userData;
212 TouchString(elname);
213 TouchString(attname);
214 TouchString(atttype);
215 TouchString(dflt);
216 (void)isrequired;
217 }
218
219 static void XMLCALL
XmlDeclHandler(void * userData,const XML_Char * version,const XML_Char * encoding,int standalone)220 XmlDeclHandler(void *userData, const XML_Char *version,
221 const XML_Char *encoding, int standalone) {
222 (void)userData;
223 TouchString(version);
224 TouchString(encoding);
225 (void)standalone;
226 }
227
228 static void XMLCALL
StartElementHandler(void * userData,const XML_Char * name,const XML_Char ** atts)229 StartElementHandler(void *userData, const XML_Char *name,
230 const XML_Char **atts) {
231 (void)userData;
232 TouchString(name);
233 for (size_t i = 0; atts[i] != NULL; ++i) {
234 TouchString(atts[i]);
235 }
236 }
237
238 static void XMLCALL
EndElementHandler(void * userData,const XML_Char * name)239 EndElementHandler(void *userData, const XML_Char *name) {
240 (void)userData;
241 TouchString(name);
242 }
243
244 static void XMLCALL
CharacterDataHandler(void * userData,const XML_Char * s,int len)245 CharacterDataHandler(void *userData, const XML_Char *s, int len) {
246 (void)userData;
247 TouchString(s, len);
248 }
249
250 static void XMLCALL
ProcessingInstructionHandler(void * userData,const XML_Char * target,const XML_Char * data)251 ProcessingInstructionHandler(void *userData, const XML_Char *target,
252 const XML_Char *data) {
253 (void)userData;
254 TouchString(target);
255 TouchString(data);
256 }
257
258 static void XMLCALL
CommentHandler(void * userData,const XML_Char * data)259 CommentHandler(void *userData, const XML_Char *data) {
260 TouchString(data);
261 // Use the comment handler to trigger parser suspend, so that we can get
262 // coverage of that code.
263 XML_StopParser((XML_Parser)userData, XML_TRUE);
264 }
265
266 static void XMLCALL
StartCdataSectionHandler(void * userData)267 StartCdataSectionHandler(void *userData) {
268 (void)userData;
269 }
270
271 static void XMLCALL
EndCdataSectionHandler(void * userData)272 EndCdataSectionHandler(void *userData) {
273 (void)userData;
274 }
275
276 static void XMLCALL
DefaultHandler(void * userData,const XML_Char * s,int len)277 DefaultHandler(void *userData, const XML_Char *s, int len) {
278 (void)userData;
279 TouchString(s, len);
280 }
281
282 static void XMLCALL
StartDoctypeDeclHandler(void * userData,const XML_Char * doctypeName,const XML_Char * sysid,const XML_Char * pubid,int has_internal_subset)283 StartDoctypeDeclHandler(void *userData, const XML_Char *doctypeName,
284 const XML_Char *sysid, const XML_Char *pubid,
285 int has_internal_subset) {
286 (void)userData;
287 TouchString(doctypeName);
288 TouchString(sysid);
289 TouchString(pubid);
290 (void)has_internal_subset;
291 }
292
293 static void XMLCALL
EndDoctypeDeclHandler(void * userData)294 EndDoctypeDeclHandler(void *userData) {
295 (void)userData;
296 }
297
298 static void XMLCALL
EntityDeclHandler(void * userData,const XML_Char * entityName,int is_parameter_entity,const XML_Char * value,int value_length,const XML_Char * base,const XML_Char * systemId,const XML_Char * publicId,const XML_Char * notationName)299 EntityDeclHandler(void *userData, const XML_Char *entityName,
300 int is_parameter_entity, const XML_Char *value,
301 int value_length, const XML_Char *base,
302 const XML_Char *systemId, const XML_Char *publicId,
303 const XML_Char *notationName) {
304 (void)userData;
305 TouchString(entityName);
306 (void)is_parameter_entity;
307 TouchString(value, value_length);
308 TouchString(base);
309 TouchString(systemId);
310 TouchString(publicId);
311 TouchString(notationName);
312 }
313
314 static void XMLCALL
NotationDeclHandler(void * userData,const XML_Char * notationName,const XML_Char * base,const XML_Char * systemId,const XML_Char * publicId)315 NotationDeclHandler(void *userData, const XML_Char *notationName,
316 const XML_Char *base, const XML_Char *systemId,
317 const XML_Char *publicId) {
318 (void)userData;
319 TouchString(notationName);
320 TouchString(base);
321 TouchString(systemId);
322 TouchString(publicId);
323 }
324
325 static void XMLCALL
StartNamespaceDeclHandler(void * userData,const XML_Char * prefix,const XML_Char * uri)326 StartNamespaceDeclHandler(void *userData, const XML_Char *prefix,
327 const XML_Char *uri) {
328 (void)userData;
329 TouchString(prefix);
330 TouchString(uri);
331 }
332
333 static void XMLCALL
EndNamespaceDeclHandler(void * userData,const XML_Char * prefix)334 EndNamespaceDeclHandler(void *userData, const XML_Char *prefix) {
335 (void)userData;
336 TouchString(prefix);
337 }
338
339 static int XMLCALL
NotStandaloneHandler(void * userData)340 NotStandaloneHandler(void *userData) {
341 (void)userData;
342 return XML_STATUS_OK;
343 }
344
345 static int XMLCALL
ExternalEntityRefHandler(XML_Parser parser,const XML_Char * context,const XML_Char * base,const XML_Char * systemId,const XML_Char * publicId)346 ExternalEntityRefHandler(XML_Parser parser, const XML_Char *context,
347 const XML_Char *base, const XML_Char *systemId,
348 const XML_Char *publicId) {
349 int rc = XML_STATUS_ERROR;
350 TouchString(context);
351 TouchString(base);
352 TouchString(systemId);
353 TouchString(publicId);
354
355 if (g_external_entity) {
356 XML_Parser ext_parser
357 = XML_ExternalEntityParserCreate(parser, context, g_encoding);
358 if (ext_parser != NULL) {
359 rc = Parse(ext_parser, g_external_entity, g_external_entity_size, 1);
360 XML_ParserFree(ext_parser);
361 }
362 }
363
364 return rc;
365 }
366
367 static void XMLCALL
SkippedEntityHandler(void * userData,const XML_Char * entityName,int is_parameter_entity)368 SkippedEntityHandler(void *userData, const XML_Char *entityName,
369 int is_parameter_entity) {
370 (void)userData;
371 TouchString(entityName);
372 (void)is_parameter_entity;
373 }
374
375 static int XMLCALL
UnknownEncodingHandler(void * encodingHandlerData,const XML_Char * name,XML_Encoding * info)376 UnknownEncodingHandler(void *encodingHandlerData, const XML_Char *name,
377 XML_Encoding *info) {
378 (void)encodingHandlerData;
379 TouchString(name);
380 (void)info;
381 return XML_STATUS_ERROR;
382 }
383
384 void
InitializeParser(XML_Parser parser)385 InitializeParser(XML_Parser parser) {
386 XML_SetUserData(parser, (void *)parser);
387 const uint8_t entropy[16] = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
388 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41};
389 XML_SetHashSalt16Bytes(parser, entropy);
390 XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
391
392 XML_SetElementDeclHandler(parser, ElementDeclHandler);
393 XML_SetAttlistDeclHandler(parser, AttlistDeclHandler);
394 XML_SetXmlDeclHandler(parser, XmlDeclHandler);
395 XML_SetElementHandler(parser, StartElementHandler, EndElementHandler);
396 XML_SetCharacterDataHandler(parser, CharacterDataHandler);
397 XML_SetProcessingInstructionHandler(parser, ProcessingInstructionHandler);
398 XML_SetCommentHandler(parser, CommentHandler);
399 XML_SetCdataSectionHandler(parser, StartCdataSectionHandler,
400 EndCdataSectionHandler);
401 // XML_SetDefaultHandler disables entity expansion
402 XML_SetDefaultHandlerExpand(parser, DefaultHandler);
403 XML_SetDoctypeDeclHandler(parser, StartDoctypeDeclHandler,
404 EndDoctypeDeclHandler);
405 // Note: This is mutually exclusive with XML_SetUnparsedEntityDeclHandler,
406 // and there isn't any significant code change between the two.
407 XML_SetEntityDeclHandler(parser, EntityDeclHandler);
408 XML_SetNotationDeclHandler(parser, NotationDeclHandler);
409 XML_SetNamespaceDeclHandler(parser, StartNamespaceDeclHandler,
410 EndNamespaceDeclHandler);
411 XML_SetNotStandaloneHandler(parser, NotStandaloneHandler);
412 XML_SetExternalEntityRefHandler(parser, ExternalEntityRefHandler);
413 XML_SetSkippedEntityHandler(parser, SkippedEntityHandler);
414 XML_SetUnknownEncodingHandler(parser, UnknownEncodingHandler, (void *)parser);
415 }
416
DEFINE_TEXT_PROTO_FUZZER(const xml_lpm_fuzzer::Testcase & testcase)417 DEFINE_TEXT_PROTO_FUZZER(const xml_lpm_fuzzer::Testcase &testcase) {
418 g_external_entity = nullptr;
419
420 if (! testcase.actions_size()) {
421 return;
422 }
423
424 g_allocation_count = 0;
425 g_fail_allocations.clear();
426 for (int i = 0; i < testcase.fail_allocations_size(); ++i) {
427 g_fail_allocations.push_back(testcase.fail_allocations(i));
428 }
429
430 SetEncoding(testcase.encoding());
431 XML_Parser parser
432 = XML_ParserCreate_MM(g_encoding, &memory_handling_suite, "|");
433 InitializeParser(parser);
434
435 for (int i = 0; i < testcase.actions_size(); ++i) {
436 const auto &action = testcase.actions(i);
437 switch (action.action_case()) {
438 case xml_lpm_fuzzer::Action::kChunk:
439 if (XML_STATUS_ERROR
440 == Parse(parser, action.chunk().data(), action.chunk().size(), 0)) {
441 // Force a reset after parse error.
442 XML_ParserReset(parser, g_encoding);
443 InitializeParser(parser);
444 }
445 break;
446
447 case xml_lpm_fuzzer::Action::kLastChunk:
448 Parse(parser, action.last_chunk().data(), action.last_chunk().size(), 1);
449 XML_ParserReset(parser, g_encoding);
450 InitializeParser(parser);
451 break;
452
453 case xml_lpm_fuzzer::Action::kReset:
454 XML_ParserReset(parser, g_encoding);
455 InitializeParser(parser);
456 break;
457
458 case xml_lpm_fuzzer::Action::kExternalEntity:
459 g_external_entity = action.external_entity().data();
460 g_external_entity_size = action.external_entity().size();
461 break;
462
463 default:
464 break;
465 }
466 }
467
468 XML_ParserFree(parser);
469 }
470