Dear Maintainer,
I am reporting on behalf of an engineer at Quarkslab, an out-of-bounds read and heap out-of-bounds write in antiword 0.37-17 when decoding OLE PPS directory entry names.
In wordole.c, bGetPPS obtains the name length directly from offset 0x40 of the attacker-controlled 128-byte PPS directory entry:
tNameSize = (size_t)usGetWord(0x40, aucBytes);
tNameSize = (tNameSize + 1) / 2;
vName2String(atPPSlist[iIndex].szName, aucBytes, tNameSize);
The resulting tNameSize is then passed to vName2String without validating it against either the input PPS entry size or the size of the destination buffer.
The destination, szName, is a fixed 32-byte field:
for (tIndex = 0, pcChar = szName;
tIndex < 2 * tNameSize;
tIndex += 2, pcChar++)
*pcChar = (char)aucBytes[tIndex];
szName[tNameSize - 1] = '\0';
Since the source length is a 16-bit document-controlled value, the loop can both read beyond the 128-byte PPS entry and write far beyond the 32-byte destination field.
CWE: CWE-787 (Out-of-bounds Write)
CWE-125 (Out-of-bounds Read)
CWE-1284 (Improper Validation of Specified Quantity in Input)
CWE-120 (Buffer Copy without Checking Size of Input)
Security impact
===============
The out-of-bounds write can corrupt heap memory following the PPS list.
When the affected entry is the final element in the allocated array, the write can reach metadata belonging to the following heap chunk.
Our analysis indicates that this constitutes a potentially exploitable heap-corruption primitive. Reliable arbitrary code execution has not been demonstrated.
Reproduction
============
With the attached PoC document:
antiword -a a4 ./sample_oob_write_3.doc
Expected behavior:
The PPS name length should be validated against both the source entry and the fixed-size destination before copying.
The issue was reproduced with antiword 0.37-17.
I have attached the PoC document and a detailed technical report.
Regards,
Salwa Souaf
Quarkslab VulnReport Team
Control: tags -1 -pending
That is what the upstream 0.37 code looks like, but the Debian package
has been patching this since 0.37-5 (2009-06-03) to clamp the value:
tNameSize = (size_t)usGetWord(0x40, aucBytes);
tNameSize = (tNameSize + 1) / 2;
+ if ( tNameSize > sizeof(atPPSlist[iIndex].szName)) {
+ werr(0, "Name Size of PPS %d is too large", iIndex);
+ tNameSize = sizeof(atPPSlist[iIndex].szName);
+ }
vName2String(atPPSlist[iIndex].szName, aucBytes, tNameSize);
See debian/patches/10_fix_buffer_overflow_wordole_c.patch
This problem was rediscovered in 2014 and became CVE-2014-8123 (but we'd
already fixed it in Debian).
Your file does seem to trigger a problem, but I don't think it's what
your analysis suggests as the patch above is in the version you say
you're testing and should address the problem you identified.
If you can see what's going on, please do let me know.
Cheers,
Olly
Putting the summary first for the benefit of the security team:
I think you probably tested antiword without any of the existing Debian
patches applied, and I'm pretty sure you're re-reporting CVE-2014-8123
which we patched in Debian in 2009. The reproducer actually shows an
OOB read (not write) which doesn't look like one that's been reported
before. So unless I'm missing something, this isn't something we'd
handle as a security bug in Debian; if a new CVE has been requested
for this then the details should be updated there (or mark it as a
duplicate of CVE-2014-8123 and request a fresh one).
applied (which can be obtained using `apt source antiword`), your reproducer
gives:
$ LC_ALL= LANG= ./antiword -a a4 ~/Downloads/sample_oob_write_3.doc > /dev/null
Name Size of PPS 15 is too large
=================================================================
==2988356==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b740d101282 at pc 0x563b004f41a3 bp 0x7ffd22969780 sp 0x7ffd22969778
READ of size 1 at 0x7b740d101282 thread T0
#0 0x563b004f41a2 in vGet8FontInfo /home/olly/git/antiword-0.37/prop8.c:1084:11
#1 0x563b004f6e89 in vGet8ChrInfo /home/olly/git/antiword-0.37/prop8.c:1474:5
#2 0x563b004f8920 in vGetPropertyInfo /home/olly/git/antiword-0.37/properties.c:136:4
#3 0x563b0051a970 in iInitDocumentOLE /home/olly/git/antiword-0.37/wordole.c:796:3
#4 0x563b0050da61 in bWordDecryptor /home/olly/git/antiword-0.37/word2text.c:665:17
#5 0x563b0047ab7b in bProcessFile /home/olly/git/antiword-0.37/main_u.c:233:12
#6 0x563b0047ab7b in main /home/olly/git/antiword-0.37/main_u.c:329:7
#7 0x7f740ee38fb9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:59:16
#8 0x7f740ee390d6 in __libc_start_main csu/../csu/libc-start.c:360:3
#9 0x563b00390d30 in _start (/home/olly/git/antiword-0.37/antiword+0x86d30) (BuildId: b92e0720cfa4bee2cca588784e4154a1a7a2e782)
Address 0x7b740d101282 is located in stack of thread T0 at offset 642 in frame
#0 0x563b004f6317 in vGet8ChrInfo /home/olly/git/antiword-0.37/prop8.c:1408
This frame has 3 object(s):
[32, 48) 'tFont' (line 1409)
[64, 88) 'tPicture' (line 1410)
[128, 640) 'aucFpage' (line 1417) <== Memory access at offset 642 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/olly/git/antiword-0.37/prop8.c:1084:11 in vGet8FontInfo
Shadow bytes around the buggy address:
0x7b740d101000: f1 f1 f1 f1 00 00 f2 f2 00 00 00 f2 f2 f2 f2 f2
0x7b740d101080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b740d101100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b740d101180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b740d101200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7b740d101280:[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b740d101300: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b740d101380: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
0x7b740d101400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b740d101480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x7b740d101500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==2988356==ABORTING
Note especially "READ of size 1" rather than an "out-of-bounds write".
I've cloned your bug report to create a new bug for the OOB read,
leaving #1144644 to track the originally reported problem.
Cheers,
Olly