[CVE-2024-25062] Use-after-free in xmlValidatePopElement() using XMLReader API
I wrote a really dumb fuzzer that tries to call some of the XMLReader API randomly, and found a use-after-free crash while running the fuzzer.
To create a stand-alone reproducer, I also made it possible for the fuzzer to write out C code for all the XMLReader API calls it would make for the given input, before actually trying to reproduce the crash.
So the attached schema.c can be copied over fuzz/schema.c
in the libxml2 repository, and then build the schema fuzzer to create the test case. I've also attached repro.bin as the input file to reproduce it.
Here's the configure
invocation as written to config.log
:
$ ./configure --prefix=/usr --without-iconv --with-icu --without-lzma --without-python --with-xptr-locs --with-zlib 'CC=xcrun -sdk macosx cc -fsanitize=address,fuzzer -fsanitize-coverage=inline-8bit-counters,trace-cmp -fno-sanitize-coverage=pc-table -g -fno-omit-frame-pointer -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION' --no-create --no-recursion
Here's the ASan stack traces:
=================================================================
==55584==ERROR: AddressSanitizer: heap-use-after-free on address 0x000108603b90 at pc 0x000105921fc4 bp 0x00016b19acb0 sp 0x00016b19aca8
READ of size 8 at 0x000108603b90 thread T0
#0 0x105921fc0 in xmlValidatePopElement valid.c:5810
#1 0x1059c4640 in xmlTextReaderValidatePop xmlreader.c:920
#2 0x1059c1b98 in xmlTextReaderRead xmlreader.c:1319
#3 0x104c6d064 in LLVMFuzzerTestOneInput schema.c:1486
#4 0x104c87bd4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:617
#5 0x104c73a14 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) FuzzerDriver.cpp:324
#6 0x104c78de8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:860
#7 0x104ca5668 in main FuzzerMain.cpp:20
#8 0x187941054 (<unknown module>)
0x000108603b90 is located 16 bytes inside of 120-byte region [0x000108603b80,0x000108603bf8)
freed by thread T0 here:
#0 0x105f12fe4 in wrap_free+0x98 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x52fe4)
#1 0x1059c2600 in xmlTextReaderRead xmlreader.c:1362
#2 0x104c6cfb4 in LLVMFuzzerTestOneInput schema.c:1475
#3 0x104c87bd4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:617
#4 0x104c73a14 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) FuzzerDriver.cpp:324
#5 0x104c78de8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:860
#6 0x104ca5668 in main FuzzerMain.cpp:20
#7 0x187941054 (<unknown module>)
previously allocated by thread T0 here:
#0 0x105f12ea8 in wrap_malloc+0x94 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x52ea8)
#1 0x1058d42c4 in xmlNewDocNodeEatName tree.c:2317
#2 0x1058bd784 in xmlSAX2StartElementNs SAX2.c
#3 0x1059ca35c in xmlTextReaderStartElementNs xmlreader.c:627
#4 0x10589ffa8 in xmlParseStartTag2 parser.c:10063
#5 0x1058887d8 in xmlParseChunk parser.c:12207
#6 0x1059c39e0 in xmlTextReaderPushData xmlreader.c:751
#7 0x1059c0d94 in xmlTextReaderRead xmlreader.c:1183
#8 0x104c67efc in LLVMFuzzerTestOneInput schema.c:36
#9 0x104c87bd4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:617
#10 0x104c73a14 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) FuzzerDriver.cpp:324
#11 0x104c78de8 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:860
#12 0x104ca5668 in main FuzzerMain.cpp:20
#13 0x187941054 (<unknown module>)
SUMMARY: AddressSanitizer: heap-use-after-free valid.c:5810 in xmlValidatePopElement
Shadow bytes around the buggy address:
0x000108603900: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x000108603980: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
0x000108603a00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
0x000108603a80: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x000108603b00: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
=>0x000108603b80: fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fa
0x000108603c00: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x000108603c80: 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
0x000108603d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa
0x000108603d80: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
0x000108603e00: 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==55584==ABORTING
Abort trap: 6