ezXML Bugs
Status: Beta
Brought to you by:
voisine
Function ezxml_decode() performs incorrect memory handling while parsing crafted XML files which may lead to an out-of-bounds write.
The OOB write corrupts heap memory by overwriting adjacent heap chunk size meta data. When attempting to free the corrupt heap memory the target program crashes.
The invalid write occurs in ezxml.c:201 during an attempt to copy memory contents:
memmove(s + c, e + 1, strlen(e)); // shift rest of string strncpy(s, ent[b], c); // copy in replacement text
MITRE assigned CVE-2021-31598 for this issue.
$ gdb ~/tmp/ezxml/ezxml_test $ r CVE-2021-31598-HeapOvw-000.sample Program received signal SIGABRT, Aborted. 0x00007ffff7debef5 in raise () from /usr/lib/libc.so.6 >>> bt #0 0x00007ffff7debef5 in raise () from /usr/lib/libc.so.6 #1 0x00007ffff7dd5862 in abort () from /usr/lib/libc.so.6 #2 0x00007ffff7e2df38 in __libc_message () from /usr/lib/libc.so.6 #3 0x00007ffff7e35bea in malloc_printerr () from /usr/lib/libc.so.6 #4 0x00007ffff7e3601c in munmap_chunk () from /usr/lib/libc.so.6 #5 0x00007ffff7e3acdb in free () from /usr/lib/libc.so.6 #6 0x0000555555555639 in ezxml_free (xml=0x55555555d2a0) at ezxml.c:808 #7 0x00005555555552cd in ezxml_free (xml=0x55555555d2a0) at ezxml.c:790 #8 main (argc=<optimized out>, argv=<optimized out>) at ezxml.c:1012 >>> watch *0x000055555555d5a8 Hardware wathcpoint 1 at 0x000055555555d5a8 >>> r 0x00007ffff7e39676 in _int_malloc () from /usr/lib/libc.so.6 Memory 0x000055555555d4d0 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 ········1······· 0x000055555555d4e0 22 a0 fb f7 ff 7f 00 00 2b a0 fb f7 ff 7f 00 00 "·······+······· 0x000055555555d4f0 00 00 00 00 00 00 00 00 c0 d4 55 55 55 55 00 00 ··········UUUU·· 0x000055555555d500 00 00 00 00 00 00 00 00 a1 00 00 00 00 00 00 00 ················ 0x000055555555d510 0a 20 75 6c 64 31 3e 0a c4 91 6d 3e 0a 26 23 09 ··uld1>···m>·&#· 0x000055555555d520 31 1c 20 ec f0 0a 20 3e 0a ff 81 84 91 86 a1 84 1······>········ 0x000055555555d530 91 6d 3e 0a ff bf 87 bf bf bf bf bf bf bf bf bf ·m>············· 0x000055555555d540 bf 6d 3e 0a 26 23 78 31 31 31 31 31 35 35 35 35 ·m>·� 0x000055555555d550 35 35 35 35 35 27 35 35 35 35 35 35 35 35 35 35 55555'5555555555 0x000055555555d560 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 5555555555555555 0x000055555555d570 35 3e 0a ff 81 84 91 86 a1 84 91 6d 3e 0a ff bf 5>·········m>··· 0x000055555555d580 87 bf bf bf bf bf bf bf bf bf bf 6d 3e 0a 26 23 ···········m>·&# 0x000055555555d590 78 31 1d 31 35 35 35 35 35 35 35 35 35 31 31 31 x1·1555555555111 0x000055555555d5a0 31 31 31 3b 6d 7f 0a 00[21]00 00 00 00 00 00 00 111;m···!······· < Note chunk meta data 0x000055555555d5b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················ 0x000055555555d5c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················ >>> c Hardware watchpoint 1: *0x000055555555d5a8 Old value = 33 New value = 10 0x00007ffff7f135b5 in __memmove_avx_unaligned_erms () from /usr/lib/libc.so.6 Memory 0x000055555555d4d0 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 ········1······· 0x000055555555d4e0 22 a0 fb f7 ff 7f 00 00 2b a0 fb f7 ff 7f 00 00 "·······+······· 0x000055555555d4f0 00 00 00 00 00 00 00 00 c0 d4 55 55 55 55 00 00 ··········UUUU·· 0x000055555555d500 00 00 00 00 00 00 00 00 a1 00 00 00 00 00 00 00 ················ 0x000055555555d510 0a 20 3e 0a ff 81 84 91 86 a1 84 91 6d 3e 0a ff ··>·········m>·· 0x000055555555d520 bf 87 bf bf bf bf bf bf bf bf bf bf 6d 3e 0a 26 ············m>·& 0x000055555555d530 80 3b 5d 54 41 37 5d 3e 23 78 31 31 31 31 31 35 ·;]TA7]>#x111115 0x000055555555d540 35 35 35 35 35 35 35 35 27 35 35 35 35 35 35 35 55555555'5555555 0x000055555555d550 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 5555555555555555 0x000055555555d560 35 35 35 35 3e 0a ff 81 84 91 86 a1 84 91 6d 3e 5555>·········m> 0x000055555555d570 0a ff bf 87 bf bf bf bf bf bf bf bf bf bf 6d 3e ··············m> 0x000055555555d580 0a ff bf 87 bf bf bf bf bf bf bf bf bf bf 6d 7f ··············m· 0x000055555555d590 0a 00 1d 31 35 35 35 35 35 35 35 35 35 31 31 31 ···1555555555111 0x000055555555d5a0 31 31 31 3b 6d 7f[6d 7f 0a]00 00 00 00 00 00 00 111;m·m········· < Overwritten 0x000055555555d5b0 3e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >··············· 0x000055555555d5c0 00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 ········1······· >>> bt #0 0x00007ffff7f135b5 in __memmove_avx_unaligned_erms () from /usr/lib/libc.so.6 #1 0x0000555555555ec1 in ezxml_decode (s=0x55555555d5a1 "11;m\177m\177\n", s@entry=0x7ffff7fbaefc "\n >\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n&\200;]TA7]>#x11111555555555'", '5' <repeats 27 times>, ">\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m\177\n", ent=0x55555555d3d0, t=t@entry=38 '&') at ezxml.c:201 #2 0x00005555555560f3 in ezxml_char_content (root=root@entry=0x55555555d2a0, s=s@entry=0x7ffff7fbaefc "\n >\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n&\200;]TA7]>#x11111555555555'", '5' <repeats 27 times>, ">\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m\177\n", len=<optimized out>, t=t@entry=38 '&') at ezxml.c:242 #3 0x000055555555870a in ezxml_parse_str (s=<optimized out>, s@entry=0x7ffff7fba000 "<L", len=<optimized out>) at ezxml.c:591 #4 0x0000555555558b59 in ezxml_parse_fd (fd=fd@entry=3) at ezxml.c:641 #5 0x0000555555558bfb in ezxml_parse_file (file=<optimized out>) at ezxml.c:659 #6 0x000055555555526a in main (argc=<optimized out>, argv=<optimized out>) at ezxml.c:1008 >>> up #1 0x0000555555555ec1 in ezxml_decode (s=0x55555555d5a1 "11;m\177m\177\n", s@entry=0x7ffff7fbaefc "\n >\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n&\200;]TA7]>#x11111555555555'", '5' <repeats 27 times>, ">\n\377\201\204\221\206\241\204\221m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m>\n\377\277\207\277\277\277\277\277\277\277\277\277\277m\177\n", ent=0x55555555d3d0, t=t@entry=38 '&') at ezxml.c:201 201 memmove(s + c, e + 1, strlen(e)); // shift rest of string >>> p s+c $10 = 0x55555555d5a6 "m\177\n" >>> p e+1 $11 = 0x55555555d5a4 "m\177m\177\n"
$ cd ~/tmp/ezxml
$ gcc -Wall -O2 -DEZXML_TEST -g -ggdb -o ezxml_test ezxml.c
$ ~/tmp/ezxml/ezxml_test CVE-2021-31598-HeapOvw-000.sample
--- ezxml.c 2006-06-08 04:33:38.000000000 +0200 +++ ezxml-fixed.c 2021-04-19 16:25:07.905073070 +0200 @@ -198,6 +198,11 @@ e = strchr((s = r + d), ';'); // fix up pointers } + if(c > strlen(s) || strlen(e) > strlen(s + c)) { + fprintf(stderr, "Error: ezxml_decode(): memmove() past end of buffer!"); + exit(-1); + } + memmove(s + c, e + 1, strlen(e)); // shift rest of string strncpy(s, ent[b], c); // copy in replacement text }
memmove())
The proposed patch is incorrect: the memove() destination (s + c) does not have to contain valid data. The calculation performed seems to ensure that the move will fit into the bounds of the memory (re)allocated.
The patch causes issues with perfectly valid XML.
Also, performing an exit(-1) when the test condition fails would be the wrong way to handle an error condition anyway.
In my tests the issue demonstrated by the test case was resolved by the fix for CVE-2019-20006 in bug 15.