Talos Vulnerability Report


libxls xls_addCell Formula Code Execution Vulnerability

November 15, 2017
CVE Number



An exploitable out-of-bounds vulnerability exists in the xls_addCell function of libxls 1.4. A specially crafted XLS file with a formula record can cause memory corruption resulting in remote code execution. An attacker can send a malicious XLS file to trigger this vulnerability.

Tested Versions

libxls 1.4

Product URLs


CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0


CWE-787: Out-of-bounds Write


libxls is a C library supported on windows, mac, cygwin which can read Microsoft Excel File Format ( XLS ) files. The library is used by the readxl package that can be installed in the R programming language. However this particular vulnerability does not impact the readxl package.

To analyze this vulnerability, we need to start at the xls_preparseWorkSheet function.

Line 970	void xls_preparseWorkSheet(xlsWorkSheet* pWS)
Line 971	{
Line 1018			case 0x0203:        //NUMBER
Line 1019			case 0x027e:        //RK
Line 1020			case 0x00FD:        //LABELSST
Line 1021			case 0x0201:        //BLANK
Line 1022			case 0x0204:        //LABEL
Line 1023			case 0x0006:        //FORMULA
Line 1024				if (pWS->rows.lastcol<xlsShortVal(((COL*)buf)->col))
Line 1025					pWS->rows.lastcol=xlsShortVal(((COL*)buf)->col);
Line 1026				if (pWS->rows.lastrow<xlsShortVal(((COL*)buf)->row))
Line 1027					pWS->rows.lastrow=xlsShortVal(((COL*)buf)->row);
Line 1028				break;
Line 1029			}

The xls_preparseWorkSheet function iterates over all existing records and tries to find a maximum value for the col and row fields, these values are stored in pWS->rows.lastcol and pWS->rows.lastcol, respectively. These values are later used for buffers allocations inside the xls_makeTable function:

Line 409	void xls_makeTable(xlsWorkSheet* pWS)
Line 410	{
Line 415		pWS->rows.row=(struct st_row_data *)calloc((pWS->rows.lastrow+1),sizeof(struct st_row_data));
Line 426			tmp->cells.cell=(struct st_cell_data *)calloc(tmp->cells.count,sizeof(struct st_cell_data));	

Later in the xls_parseWorkSheet function, the xls_addCell function is called for nearly the same set of records that we saw in the xls_preparseWorkSheet but with one exception, FORMULA (Apple Numbers Bug):

Line 1063	void xls_parseWorkSheet(xlsWorkSheet* pWS)
Line 1064	{
Line 1141				case 0x00BD:		//MULRK
Line 1142				case 0x00BE:		//MULBLANK
Line 1143				case 0x0203:		//NUMBER
Line 1144				case 0x027e:		//RK
Line 1145				case 0x00FD:		//LABELSST
Line 1146				case 0x0201:		//BLANK
Line 1147				case 0x0204:		//LABEL
Line 1148				case 0x0006:		//FORMULA
Line 1149				case 0x0406:		//FORMULA (Apple Numbers Bug)
Line 1150					cell = xls_addCell(pWS,&tmp,buf);	

We did not see FORMULA (Apple Numbers Bug) with id 0x0406 in thexls_preparseWorkSheet function. This means that for this record, the fields col and row have not been checked whether their value exceed the current maximum values stored in pWS->rows.lastcol, pWS->rows.lastrow. That lack of check for this type of record leads to an out of bound write in the xls_addCell function. Let’s take a look at the vulnerable code:

Line 446	struct st_cell_data *xls_addCell(xlsWorkSheet* pWS,BOF* bof,BYTE* buf)
Line 447	{
Line 448		struct st_cell_data*	cell;
Line 449		struct st_row_data*		row;
Line 450		int						i;
Line 451
Line 452		verbose ("xls_addCell");
Line 453
Line 454		// printf("ROW: %u COL: %u\n", xlsShortVal(((COL*)buf)->row), xlsShortVal(((COL*)buf)->col));
Line 455		row=&pWS->rows.row[xlsShortVal(((COL*)buf)->row)];
Line 456		//cell=&row->cells.cell[((COL*)buf)->col - row->fcell]; DFH - inconsistent
Line 457		cell=&row->cells.cell[xlsShortVal(((COL*)buf)->col)];
Line 458		cell->id=bof->id;
Line 459		cell->xf=xlsShortVal(((COL*)buf)->xf);	

The col value coming from the record is used as an index for the row->cells.cell array at line 457. The malformed Formula record is located at offset : 0xCB1F

 CB1Fh: 06 04 20 00 00 01 FF FF                          .. ...ÿÿ

 `col` field is set to 0xFFFF

Because this value has not been checked, it can easily exceed array’s range. Write operations at lines 458-459 can subsequently cause an out-of-bounds write which subsequently leads to memory corruption.

Crash Information

Program received signal SIGSEGV, Segmentation fault.
RAX: 0x27fff9 
RBX: 0x21 ('!')
RCX: 0x7fffffffdb40 --> 0x200406 
RDX: 0x406 
RSI: 0x7fffffffdb40 --> 0x200406 
RDI: 0xffffffff 
RBP: 0x7fffffffdb20 --> 0x7fffffffdbc0 --> 0x7fffffffdc30 --> 0x401610 (<__libc_csu_init>:      push   r15)
RSP: 0x7fffffffdac0 --> 0x60f3a0 --> 0x8000000c90b 
RIP: 0x7ffff7bce936 (<xls_addCell+144>: mov    WORD PTR [rax],dx)
R8 : 0x60f360 --> 0x0 
R9 : 0x800000003000000 
R10: 0x6f ('o')
R11: 0x7ffff7bce8a6 (<xls_addCell>:     push   rbp)
R12: 0x400b60 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffdd10 --> 0x2 
R14: 0x0 
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
   0x7ffff7bce92b <xls_addCell+133>:    mov    rax,QWORD PTR [rbp-0x50]
   0x7ffff7bce92f <xls_addCell+137>:    movzx  edx,WORD PTR [rax]
   0x7ffff7bce932 <xls_addCell+140>:    mov    rax,QWORD PTR [rbp-0x28]
=> 0x7ffff7bce936 <xls_addCell+144>:    mov    WORD PTR [rax],dx
   0x7ffff7bce939 <xls_addCell+147>:    mov    rax,QWORD PTR [rbp-0x58]
   0x7ffff7bce93d <xls_addCell+151>:    movzx  eax,WORD PTR [rax+0x4]
   0x7ffff7bce941 <xls_addCell+155>:    cwde   
   0x7ffff7bce942 <xls_addCell+156>:    mov    edi,eax
0000| 0x7fffffffdac0 --> 0x60f3a0 --> 0x8000000c90b 
0008| 0x7fffffffdac8 --> 0x6077d0 --> 0xbbbbbbbbffff0100 
0016| 0x7fffffffdad0 --> 0x7fffffffdb40 --> 0x200406 
0024| 0x7fffffffdad8 --> 0x60f3a0 --> 0x8000000c90b 
0032| 0x7fffffffdae0 --> 0x60f3a0 --> 0x8000000c90b 
0040| 0x7fffffffdae8 --> 0x60f360 --> 0x0 
0048| 0x7fffffffdaf0 --> 0x800000003000000 
0056| 0x7fffffffdaf8 --> 0x27fff9 
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00007ffff7bce936 in xls_addCell (pWS=0x60f3a0, bof=0x7fffffffdb40, buf=0x6077d0 "") at xls.c:458
458         cell->id=bof->id;
gdb-peda$ bt
#0  0x00007ffff7bce936 in xls_addCell (pWS=0x60f3a0, bof=0x7fffffffdb40, buf=0x6077d0 "") at xls.c:458
#1  0x00007ffff7bd0b1a in xls_parseWorkSheet (pWS=0x60f3a0) at xls.c:1150
#2  0x0000000000400ff8 in main (argc=0x2, argv=0x7fffffffdd18) at xls2csv.c:149
#3  0x00007ffff781c830 in __libc_start_main (main=0x400d78 <main>, argc=0x2, argv=0x7fffffffdd18, init=<optimized out>, fini=
out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdd08) at ../csu/libc-start.c:291
#4  0x0000000000400b89 in _start ()
gdb-peda$ source ~/tools/triage_tools/exploitable/exploitable/exploitable.py 
gdb-peda$ exploitable -m
__main__:102: UserWarning: GDB v7.11 may not support required Python API
Warning: machine string printing is deprecated and may be removed in a future release.
DESCRIPTION:Access violation on destination operand
OTHER_RULES:AccessViolation (28/29)
EXPLANATION:The target crashed on an access violation at an address matching the destination operand of the instruction. This   
     likely indicates a write access violation, which means the attacker may control the write address and/or value.
Description: Access violation on destination operand
Short description: DestAv (9/29)
Hash: 34754c1fb7e7f36e18e374f783e4c876.34754c1fb7e7f36e18e374f783e4c876
Exploitability Classification: EXPLOITABLE
Explanation: The target crashed on an access violation at an address matching the destination operand of the instruction. This  
    likely indicates a write access violation, which means the attacker may control the write address and/or value.
Other tags: AccessViolation (28/29)


2017-10-25 - Vendor Disclosure
2017-11-15 - Public Release


Discovered by Marcin 'Icewall' Noga of Cisco Talos.