CVE-2016-8389
An exploitable integer-overflow vulnerability exists within Iceni Argus. When it attempts to convert a malformed PDF to XML, it will attempt to convert each character from a font into a polygon and then attempt to rasterize these shapes. When rasterizing these shapes, the tool will perform a multiplication to determine the bounds at which the shape can be filled as well as use it to perform an allocation. Due to a lack of bounds checking, this multiplication can result in an integer larger than 32-bits which is an integer overflow. As the application attempts to iterate through the rows and initializing the polygon shape in the buffer, it will write outside of the bounds of said buffer. This can lead to code execution under the context of the account running it.
Iceni Argus Version 6.6.04 (Sep 7 2012) NK
http://www.iceni.com/legacy.htm
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
This is an integer overflow that occurs in Iceni Argus. This tool is used primarily by MarkLogic Server to convert PDF files to (X)HTML form. Due to the integer overflow, two different lengths will be used for an allocation and for a copy. The object that contains the objects with the sizes which are used in the overflow is created in the ripPDF
function. Inside this function an object of size 0xadf8 is allocated.
8175f06: c7 04 24 f8 ad 00 00 movl $0xadf8,(%esp)
8175f0d: 89 85 d8 f9 ff ff mov %eax,-0x628(%ebp) ; size
8175f13: 8d 83 dd f1 49 ff lea -0xb60e23(%ebx),%eax ; "RENDER"
8175f19: 89 44 24 04 mov %eax,0x4(%esp)
8175f1d: e8 4e b6 02 00 call 81a1570 <icnMalloc>
8175f22: 85 c0 test %eax,%eax
8175f24: 89 c7 mov %eax,%edi
8175f26: 0f 84 be 03 00 00 je 81762ea <ripPDF+0x40a>
For every single text item inside the document that uses the showTextOld opcode, the showTextOld
function is called. Inside this function the addFontToStoredFonts
function will copy a pointer from the +4 field (+0xaa0c:4) into the +8 field (+0xaa0c:8) of the 0xadf8 sized object.
817888c: 8b 45 e4 mov -0x1c(%ebp),%eax
817888f: 89 44 24 14 mov %eax,0x14(%esp)
8178893: 8b 8d ec fd ff ff mov -0x214(%ebp),%ecx
8178899: 89 4c 24 10 mov %ecx,0x10(%esp)
817889d: 8b 45 08 mov 0x8(%ebp),%eax ; 0xadf8 object
81788a0: 89 44 24 0c mov %eax,0xc(%esp)
81788a4: 8b 55 18 mov 0x18(%ebp),%edx
81788a7: 89 54 24 08 mov %edx,0x8(%esp)
81788ab: 8b 4d 0c mov 0xc(%ebp),%ecx
81788ae: 89 4c 24 04 mov %ecx,0x4(%esp)
81788b2: 8b 45 14 mov 0x14(%ebp),%eax
81788b5: 89 04 24 mov %eax,(%esp)
81788b8: e8 63 26 15 00 call 82caf20 <addFontToStoredFonts>
\
82cb0fa: 8b 45 14 mov 0x14(%ebp),%eax
82cb0fd: 8b 90 0c aa 00 00 mov 0xaa0c(%eax),%edx ; +0xaa0c
82cb103: 8b 45 1c mov 0x1c(%ebp),%eax
82cb106: 8b 4a 04 mov 0x4(%edx),%ecx ; +0xaa0c:4 to %ecx
82cb109: 89 81 d4 00 00 00 mov %eax,0xd4(%ecx)
82cb10f: 8b 02 mov (%edx),%eax
82cb111: 89 4a 08 mov %ecx,0x8(%edx) ; write to +0xaa0c:8
82cb114: 85 c0 test %eax,%eax
82cb116: 74 08 je 82cb120 <addFontToStoredFonts+0x200>
For each character, the tool will processor a character, convert it into a TextPath and then convert it into a polygon. After a polygon is completed, the tool will call enumPolys
to fill each of the polygons. While calling enumPolys
the application will execute the following code in processText
. This code will take the product of the dword at 0x108(%esi) and the one at 0x10c(%esi) and store it in %edi at 0x821432d. Later this register will be passed as an argument to icnMalloc
at 0x821434d which will allocate space that will later be written to. Due to a missing check that the product of these two values are not larger than 31-bits, the resulting product may wrap which will cause the allocation size to be smaller than expected. The buffer that’s allocated will then be written to +0x108 (+0xaa0c:8:108) of the 0xadf8 object. These lengths will later be used as boundaries for rasterizing a polygon.
8214fc5: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8214fcc: 8b 55 08 mov 0x8(%ebp),%edx
8214fcf: 89 f1 mov %esi,%ecx
8214fd1: 8b 45 14 mov 0x14(%ebp),%eax
8214fd4: e8 f7 eb ff ff call 8213bd0 <processText> ; \
\
8213c9d: 8b 40 08 mov 0x8(%eax),%eax
8213ca0: 85 f6 test %esi,%esi
8213ca2: 89 45 e0 mov %eax,-0x20(%ebp)
...
8214317: 6b 45 dc 54 imul $0x54,-0x24(%ebp),%eax
821431b: 8b 55 e0 mov -0x20(%ebp),%edx
821431e: 8d 34 10 lea (%eax,%edx,1),%esi
8214321: 8b 86 08 01 00 00 mov 0x108(%esi),%eax
8214327: 8b be 10 01 00 00 mov 0x110(%esi),%edi ; XXX: size > 16-bits
821432d: 0f af be 0c 01 00 00 imul 0x10c(%esi),%edi ; XXX: size > 16-bits
8214334: 85 c0 test %eax,%eax
8214336: 74 08 je 8214340 <processText+0x770>
...
8214340: 8d 83 3f 21 4a ff lea -0xb5dec1(%ebx),%eax
8214346: 89 44 24 04 mov %eax,0x4(%esp) ; XXX: allocation size
821434a: 89 3c 24 mov %edi,(%esp)
821434d: e8 1e d2 f8 ff call 81a1570 <icnMalloc>
8214352: 85 c0 test %eax,%eax
8214354: 89 86 08 01 00 00 mov %eax,0x108(%esi)
Later while filling a polygon, the tool will load these values into the floating point co-processor to perform some math. Afterwards, the instructions will store the results as integers into variables on the stack at -0x98(%ebp) and -0xa0(%ebp). The first Y index will also be stored in integer form at -0x14(%ebp) and within the %esi register.
82b0fff: d9 07 flds (%edi)
82b1001: d9 95 5c ff ff ff fsts -0xa4(%ebp) ; X
82b1007: d9 47 10 flds 0x10(%edi)
82b100a: d9 7d f2 fnstcw -0xe(%ebp) ; fpcw
82b100d: d9 95 60 ff ff ff fsts -0xa0(%ebp) ; Y
82b1013: d9 c9 fxch %st(1)
82b1015: d8 83 28 2a 44 ff fadds -0xbbd5d8(%ebx)
82b101b: 0f b7 45 f2 movzwl -0xe(%ebp),%eax ; fpcw
82b101f: b4 0c mov $0xc,%ah
82b1021: 66 89 45 f0 mov %ax,-0x10(%ebp)
82b1025: d9 6d f0 fldcw -0x10(%ebp)
82b1028: db 5d ec fistpl -0x14(%ebp) ; start Y (integer)
82b102b: d9 6d f2 fldcw -0xe(%ebp) ; fpcw
82b102e: 8b 75 ec mov -0x14(%ebp),%esi ; start Y (integer)
82b1031: d9 6d f0 fldcw -0x10(%ebp)
82b1034: db 9d 68 ff ff ff fistpl -0x98(%ebp) ; X
82b103a: d9 6d f2 fldcw -0xe(%ebp) ; fpcw
Aftewards, the code will enter a loop that will iterate through using these values. Due to the only the product of these integers overflowing, the call to drawPixel
will be called for every pixel inside the under-allocated buffer as defined by the integers stored in -0x38(%ebp) and -0xa4(%ebp). The drawPixel
function will essentially wrap setCharMapPixel
which actually performs the write..
82b10a2: 89 44 24 0c mov %eax,0xc(%esp)
82b10a6: 8b 55 c8 mov -0x38(%ebp),%edx ; XXX: Y larger than 16-bits
82b10a9: 89 54 24 08 mov %edx,0x8(%esp)
82b10ad: d9 85 5c ff ff ff flds -0xa4(%ebp) ; XXX: X larger than 16-bits
82b10b3: d9 6d f0 fldcw -0x10(%ebp)
82b10b6: db 5c 24 04 fistpl 0x4(%esp) ; X
82b10ba: d9 6d f2 fldcw -0xe(%ebp)
82b10bd: 8b 45 08 mov 0x8(%ebp),%eax
82b10c0: 89 04 24 mov %eax,(%esp) ; object
82b10c3: e8 18 d5 00 00 call 82be5e0 <drawPixel>
Within setCharMapPixel
the application will check to see if the current values are within the correct bounds, and then later use these values to write a null byte relative to the buffer at +0xaa0c:8:108. Due to the buffer being undersized while the tool attempts to fill the rasterized polygon, a buffer overflow may occur.
82becc6: 8b 55 08 mov 0x8(%ebp),%edx
82becc9: 8b 4d 10 mov 0x10(%ebp),%ecx ; XXX: y value larger than 16-bits
82beccc: 89 34 24 mov %esi,(%esp)
82beccf: 8b 75 0c mov 0xc(%ebp),%esi ; XXX: x value larger than 16-bits
82becd2: 89 7c 24 04 mov %edi,0x4(%esp)
82becd6: 8b 82 0c aa 00 00 mov 0xaa0c(%edx),%eax ; XXX: pointer to object (+0xaa0c)
82becdc: 85 c9 test %ecx,%ecx
82becde: 8b 78 08 mov 0x8(%eax),%edi ; XXX: pointer to object (+0xaa0c:8)
82bece1: 0f b7 82 1c aa 00 00 movzwl 0xaa1c(%edx),%eax
82bece8: 78 2b js 82bed15 <setCharMapPixel+0x55>
82becea: 0f b7 c0 movzwl %ax,%eax
...
82becf0: 01 f8 add %edi,%eax ; XXX: base
...
82bed08: 0f af ca imul %edx,%ecx
82bed0b: 03 b0 08 01 00 00 add 0x108(%eax),%esi ; XXX: under-allocated buffer
82bed11: c6 04 0e 00 movb $0x0,(%esi,%ecx,1) ; XXX: write \x00 into buffer
$ gdb --quiet --args /opt/MarkLogic/converters/cvtpdf/convert ~/config/
Reading symbols from /opt/MarkLogic/Converters/cvtpdf/convert...done.
(gdb) r
Starting program: /opt/MarkLogic/Converters/cvtpdf/convert /home/user/config/
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Loading configuration...
Parsing macros...
Macro synth-bookmarks='true'
Macro image-output='true'
Macro text-output='true'
Macro zones='false'
Macro ignore-text='true'
Macro remove-overprint='false'
Macro illustrations='true'
Macro line-breaks='true'
Macro image-quality='75'
Macro page-start=''
Macro page-end=''
Macro document-start=''
Macro document-end=''
features='11140221'
Processing...
Analysing '/home/user/poc.pdf'
Pages 1 to 2
Processing page 1
Catchpoint 4 (signal SIGSEGV), 0x082bed11 in setCharMapPixel ()
(gdb) bt 10
#0 0x082bed11 in setCharMapPixel ()
#1 0x082be68e in drawPixel ()
#2 0x082b10c8 in rendFillPoly ()
#3 0x08176fd5 in fillPoly ()
#4 0x082154c9 in enumPolys ()
#5 0x082cb837 in convertTextPathToPolys ()
#6 0x0817b351 in convertPathToPolys ()
#7 0x082cc5e1 in ProcessCharacter ()
#8 0x08179993 in showTextOld ()
#9 0x08179b39 in showText ()
#10 0x08183fb2 in processTextItem ()
#11 0x0818497d in icnProcessDisplayListX ()
#12 0x081856c0 in icnProcessDisplayList ()
#13 0x0818605d in icnRenderPage ()
#14 0x0817635d in ripPDF ()
#15 0x0817576e in renderZone ()
(More stack frames follow...)
(gdb) h
-=[registers]=-
[eax: 0xf7237e40] [ebx: 0x08f57000] [ecx: 0xfffdaab0] [edx: 0x000269c2]
[esi: 0x9f23a8e9] [edi: 0xf7237e40] [esp: 0xfffbe820] [ebp: 0xfffbe828]
[eflags: NZ SF OF NC ND NI]
-=[stack]=-
fffbe820 | 0001a858 098ddfc0 fffbe858 082be68e | X.......X.....+.
fffbe830 | 098ddfc0 000238e1 0001a858 fffbeb10 | .....8..X.......
fffbe840 | 00000010 57fe85a8 f7200040 08f57000 | .......W@. ..p..
fffbe850 | 000238e2 f7236628 fffbe958 082b10c8 | .8..(f#.X.....+.
-=[disassembly]=-
=> 0x82bed11 <setCharMapPixel+81>: movb $0x0,(%esi,%ecx,1)
0x82bed15 <setCharMapPixel+85>: mov (%esp),%esi
0x82bed18 <setCharMapPixel+88>: mov 0x4(%esp),%edi
0x82bed1c <setCharMapPixel+92>: mov %ebp,%esp
0x82bed1e <setCharMapPixel+94>: pop %ebp
0x82bed1f <setCharMapPixel+95>: ret
2016-10-10 - Vendor Disclosure
2017-02-27 - Public Release
Discovered by Marcin Noga of Cisco Talos.