xref: /freebsd/contrib/expat/fuzz/xml_lpm_fuzzer.cpp (revision c7b67985633c408cae69703ca443cbfd84d326a8)
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
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 *
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 *
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
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
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
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
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
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
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
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
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
239 EndElementHandler(void *userData, const XML_Char *name) {
240   (void)userData;
241   TouchString(name);
242 }
243 
244 static void XMLCALL
245 CharacterDataHandler(void *userData, const XML_Char *s, int len) {
246   (void)userData;
247   TouchString(s, len);
248 }
249 
250 static void XMLCALL
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
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
267 StartCdataSectionHandler(void *userData) {
268   (void)userData;
269 }
270 
271 static void XMLCALL
272 EndCdataSectionHandler(void *userData) {
273   (void)userData;
274 }
275 
276 static void XMLCALL
277 DefaultHandler(void *userData, const XML_Char *s, int len) {
278   (void)userData;
279   TouchString(s, len);
280 }
281 
282 static void XMLCALL
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
294 EndDoctypeDeclHandler(void *userData) {
295   (void)userData;
296 }
297 
298 static void XMLCALL
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
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
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
334 EndNamespaceDeclHandler(void *userData, const XML_Char *prefix) {
335   (void)userData;
336   TouchString(prefix);
337 }
338 
339 static int XMLCALL
340 NotStandaloneHandler(void *userData) {
341   (void)userData;
342   return XML_STATUS_OK;
343 }
344 
345 static int XMLCALL
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
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
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
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 
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