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