This is a white-box fuzzing libxml2 v2.9.2 challenge[1] to reproduce CVE-2015-8317[2]
Congrats to QuiHao by the way, a brand new qemu 0day Orzzzz!!!
The xmlParseXMLDecl function in parser.c in libxml2 before 2.9.3 allows context-dependent attackers to obtain sensitive information via an (1) unterminated encoding value or (2) incomplete XML declaration in XML data, which triggers an out-of-bounds heap read.
Environment Setup
Download libxml2 @ 726f67e
and check the version, build and instrument by running:
1
2
3
|
cd libxml2
CC=afl-clang-fast ./autogen.sh
AFL_USE_ASAN=1 make -j 4
|
Here we go
1
2
|
x1do0@x1do0:~/fuzzing/afl-training/challenges/libxml2/libxml2.9.2$ ./testModule
Success!
|
Find target
According to NVD, function /parse.c/xmlParseXMLDecl
is to blame. But we cannot simply test this function because this is not the entrance for users. Hint:
This functionality is exposed in the parser API, and whilst you could dig
through this documentation, the easiest approach is to look at an example.
parse1.c
(also in the repo under doc/examples/parse1.c) shows two core
functions: xmlReadFile
followed by xmlFreeDoc
.
Write harness
To avoid forking process every time, AFL has a keyword __AFL_LOOP()
to largely speed things up.
__AFL_LOOP(1000)
is a macro that detects if the program is running under AFL. If it is, the loop will run 1000 times and 1000 different inputs will be fed to the library. After that, the process is torn down then restarted by AFL. This ensures we regularly replace the process to avoid memory leaks.
But if the program runs on his own (i.e. launched with ./harness_persistent
and not AFL) the loop runs only once. This way we can process testcases from the command line without looping a thousand time. This mean we can use gdb or automated tools to inspect the crashes found by the fuzzer using the same binary.
In libxml2, parse1
allow us to parse from a file.
parse1.c: Parse an XML file to a tree and free it
Demonstrate the use of xmlReadFile() to read an XML file into a tree and xmlFreeDoc() to free the resulting tree
Includes:
Uses:
Usage:
parse1 test1.xml
Author: Daniel Veillard
I wrote a simple harness and start to fuzz.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
int main(int argc, char* argv[])
{
if(argc==2){
while (__AFL_LOOP(1000)) {
xmlDocPtr doc = xmlReadFile(argv[1], NULL, 0);
if (doc != NULL)
xmlFreeDoc(doc);
}
xmlCleanupParser();
}
else
printf("Usage: %s filename\n", argv[0]);
return 1;
}
|
compile command line:
AFL_USE_ASAN=1 afl-clang-fast ./harness.c -I libxml2.9.2/include libxml2.9.2/.libs/libxml2.a -lz -lm -o fuzzer
Startup command line:
afl-fuzz -i ./in -o ./out ./fuzzer @@
Use stdin
to fuzz
Another way, according to the HINTS, is to use parse3
parse3.c: Parse an XML document in memory to a tree and free it
Demonstrate the use of xmlReadMemory() to read an XML file into a tree and xmlFreeDoc() to free the resulting tree
Includes:
Uses:
Usage:
parse3
Author: Daniel Veillard
Check [4] for more information about how to use these functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
int SIZE = 1000; // fix buf size based on assumptions
int main()
{
char buf[SIZE];
ssize_t length;
while (__AFL_LOOP(1000)) {
length = read(STDIN_FILENO, buf, SIZE);
xmlDocPtr doc = xmlReadMemory(buf, SIZE, NULL, NULL, 0);
if( doc!= NULL)
xmlFreeDoc(doc);
}
xmlCleanupParser();
return 1;
}
|
Compile command line:
AFL_USE_ASAN=1 afl-clang-fast ./harness2.c -I libxml2.9.2/include libxml2.9.2/.libs/libxml2.a -lz -lm -o fuzzer2
Startup command line:
afl-fuzz -i ./in -o ./out2 ./fuzzer2
Results
In merely one hour, our first harness produces 5 unique crashes, which is actually the same one. ASAN reports a heap-buffer-overflow in xmlDictComputeFastQKey /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/dict.c:489
Check for the source code, we found a reference of array name
with unchecked index, causing this OOB.
So actually, we found CVE-2015-7497[5] accidentally XD, see patchwork[6]
After 2 hours, another unique crash pops out!
umm, sounds like this shallow bug hides CVE-2015-8317, according to [7]. Maybe AFL need more time.
Final Statistic
These are the screen shot for two harnesses right before I stopped.
Harness 1 has a stability of 91.39% and find more crashes in less time.
Harness 2 run for about 1.5 hour but find less crashes with pretty low stability.
Also, I found 6 crashes which did not crash at all.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
x1do0@x1do0:~/fuzzing/afl-training/challenges/libxml2$ cat ./out2/default/crashes/id\:000000\,sig\:06\,src\:000803\,time\:1266536\,op\:havoc\,rep\:4 |
> ./fuzzer2
Entity: line 1: parser error : Char 0x0 out of allowed range
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh:
^
namespace error : Failed to parse QName 'SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSjSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh:'
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh:
^
Entity: line 1: parser error : Char 0x0 out of allowed range
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh:
^
Entity: line 1: parser error : Couldn't find end of Start Tag SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSjSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSCSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh: line 1
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSh:
|
And a bug where I did not figure out what happened.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1053695==ERROR: AddressSanitizer: SEGV on unknown address 0x618ffffffef6 (pc 0x5592fbf3a477 bp 0x000000000006 sp 0x7ffd30ec62e0 T0)
==1053695==The signal is caused by a READ memory access.
#0 0x5592fbf3a476 in xmlDictComputeFastQKey /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/dict.c:489
#1 0x5592fbf42942 in xmlDictQLookup /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/dict.c:1093
#2 0x5592fbf5813f in xmlSAX2StartElementNs /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/SAX2.c:2238
#3 0x5592fbb9495f in xmlParseStartTag2 /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9707
#4 0x5592fbbbae5e in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10069
#5 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#6 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#7 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#8 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#9 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#10 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#11 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#12 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#13 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#14 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#15 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#16 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#17 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#18 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#19 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#20 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#21 0x5592fbbb763d in xmlParseContent /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:9982
#22 0x5592fbbbb5d9 in xmlParseElement /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10155
#23 0x5592fbbdb56b in xmlParseDocument /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:10841
#24 0x5592fbbde0ba in xmlDoRead /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/parser.c:15298
#25 0x5592fbb0e99b in main harness.c:12
#26 0x7f460f7960b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
#27 0x5592fbb0f45d in _start (/home/x1do0/fuzzing/afl-training/challenges/libxml2/fuzzer+0x6e45d)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/x1do0/fuzzing/afl-training/challenges/libxml2/libxml2.9.2/dict.c:489 in xmlDictComputeFastQKey
==1053695==ABORTING
|
So please let me know if my harness2 is wrong. What’s more, someone found two more bugs. Check [7] for more details.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Indirect leak of 48 byte(s) in 1 object(s) allocated from:
#0 0x4c250c in __interceptor_malloc
#1 0x5ef0fd in xmlNewDocElementContent valid.c:952:34
#2 0x532c2b in xmlParseElementMixedContentDecl parser.c:6200:16
#3 0x5367cd in xmlParseElementContentDecl parser.c:6624:16
#4 0x537843 in xmlParseElementDecl parser.c:6691:12
#5 0x538b84 in xmlParseMarkupDecl parser.c:6934:4
#6 0x562fd7 in xmlParseInternalSubset parser.c:8401:6
#7 0x56166e in xmlParseDocument parser.c:10809:6
#8 0x57fe49 in xmlDoRead parser.c:15298:5
#9 0x4f0f87 in LLVMFuzzerTestOneInput
==235609==ERROR: AddressSanitizer: heap-use-after-free ...
READ of size 1 at 0x625000002908 thread T0
#0 0x77503e in xmlDictComputeFastKey dict.c:448:13
#1 0x77503e in xmlDictLookup dict.c:848
#2 0x573b6f in xmlParseNCNameComplex parser.c:3506:12
#3 0x573b6f in xmlParseNCName parser.c:3565
#4 0x57283c in xmlParseQName parser.c:8815:9
#5 0x548877 in xmlParseStartTag2 parser.c:9336:17
#6 0x544bd0 in xmlParseElement parser.c:10069:16
#7 0x543a85 in xmlParseContent parser.c:9982:6
#8 0x5457bb in xmlParseElement parser.c:10155:5
#9 0x55540b in xmlParseDocument parser.c:10841:2
#10 0x5706c1 in xmlDoRead parser.c:15298:5
#11 0x4f7997 in LLVMFuzzerTestOneInput
|
References
[1] https://github.com/mykter/afl-training/tree/main/challenges/libxml2
[2] https://nvd.nist.gov/vuln/detail/CVE-2015-8317
[3] https://toastedcornflakes.github.io/articles/fuzzing_capstone_with_afl.html
[4] http://xmlsoft.org/examples/index.html
[5] https://www.cvedetails.com/cve/CVE-2015-7497/
[6] https://github.com/GNOME/libxml2/compare/v2.9.2...CVE-2015-7497
[7] https://github.com/google/fuzzer-test-suite/tree/master/libxml2-v2.9.2