Talos Vulnerability Report

TALOS-2017-0431

FreeXL BIFF Dimension Marker Code Execution Vulnerability

September 11, 2017
CVE Number

CVE-2017-2924

Summary

An exploitable heap-based buffer overflow vulnerability exists in the read_legacy_biff function of FreeXL 1.0.3. A specially crafted XLS file can cause a memory corruption resulting in remote code execution. An attacker can send malicious XLS file to trigger this vulnerability.

Tested Versions

freexl 1.0.3

Product URLs

https://www.gaia-gis.it/fossil/freexl/index

CVSSv3 Score

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

CWE

CWE-122: Heap-based Buffer Overflow

Details

FreeXL is a C library which can read Microsoft Excel File Format ( XLS ) files. The library is used by the SpatiaLite open source library. The heap-based buffer overflow appears in read_legacy_biff function during parsing DIMENSION record. To reach the vulnerable code, the XLS file needs be in the old BIFF format.

Line 4046	/*
Line 4047	 * the XLS file is internally structured as a FAT-like
Line 4048	 * file-system (Compound File Binary Format, CFBF) 
Line 4049	 * so we'll start by parsing the FAT
Line 4050	 */
Line 4051		chain = read_cfbf_header (workbook, swap, &errcode);
Line 4052		if (!chain)
Line 4053		  {
Line 4054		  /* it's not a CFBF file: testing older BIFF-(2,3,4) formats */
Line 4055		  if (read_legacy_biff (workbook, swap))

At line 2275 record_size is read directly from the file:

Line 2268    while (1)
Line 2269      {
Line 2270	  /* looping on BIFF records */
Line 2271
Line 2272	  if (fread (&buf, 1, 4, workbook->xls) != 4)
Line 2273	      return 0;
Line 2274	  memcpy (record_type.bytes, buf, 2);
Line 2275	  memcpy (record_size.bytes, buf + 2, 2);

The vulnerability occurs in the read_legacy_biff function:

Line 2439	  if ((record_type.value == 0x0000
Line 2440	       && workbook->biff_version == FREEXL_BIFF_VER_2)
Line 2441	      || (record_type.value == BIFF_DIMENSION
Line 2442		  && (workbook->biff_version == FREEXL_BIFF_VER_3
Line 2443		      || workbook->biff_version == FREEXL_BIFF_VER_4)))
Line 2444	    {
Line 2445		/* DIMENSION marker found */
Line 2446		biff_word16 word16;
Line 2447		unsigned int rows;
Line 2448		unsigned short columns;
Line 2449		char *utf8_name;
Line 2450		if (fread
Line 2451		    (workbook->record, 1, record_size.value,
Line 2452		     workbook->xls) != record_size.value)
Line 2453		    return 0;

At line 2450, workbook->record is read from the file, based on the size in the file that was read at line 2275. There are no checks to ensure that this value is smaller than the maximum size that a record can contain:

Line 278 unsigned char record[8224]; /* current record */

This allows an attacker to fully control the heap overflow since both the content and the size are read directly from the file.

The workbook object just after executing line 2450:

(gdb) p/x *workbook 
$6 = {magic1 = 0x63dd0d77, xls = 0x614100, fat = 0x0, cfbf_version = 0x0, cfbf_sector_size = 0x0, start_sector = 
0x0, size = 0x0, current_sector = 0x0, bytes_read = 0x0, current_offset = 0x0, 
sector_buf = {0x0 <repeats 8192 times>}, p_in = 0x604040, sector_end = 0x0, sector_ready = 0x0, ok_bof = 0x1, 
biff_version = 0x2, biff_max_record_size = 0x0, biff_content_type = 0x0, biff_code_page = 0x0, 
biff_book_code_page = 0x0, biff_date_mode = 0x0, biff_obfuscated = 0x0, utf8_converter = 0x0, utf16_converter = 
0x0, record = {0x41 <repeats 8224 times>}, record_type = 0x4141, prev_record_type = 0x4141, 
record_size = 0x41414141, shared_strings = {string_count = 0x41414141, utf8_strings = 0x4141414141414141, 
current_index = 0x41414141, current_utf16_buf = 0x4141414141414141, current_utf16_len = 0x41414141, 
current_utf16_off = 0x41414141, current_utf16_skip = 0x41414141, next_utf16_skip = 0x41414141}, first_sheet = 
0x4141414141414141, last_sheet = 0x4141414141414141, active_sheet = 0x4141414141414141, second_pass = 0x41414141, 
format_array = {{format_index = 0x41414141, is_date = 0x41414141, is_datetime = 0x41414141, is_time = 
0x41414141} <repeats 43 times>, {format_index = 0x41414141, is_date = 0x0, is_datetime = 0x0, is_time = 0x0}, 
{format_index = 0x0, is_date = 0x0, is_datetime = 0x0, is_time = 0x0} <repeats 2004 times>}, max_format_index = 
0x0, biff_xf_array = {0x0 <repeats 8192 times>}, biff_xf_next_index = 0x0, magic2 = 0xa9f5250}

Crash Information

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7bcc34b in add_sheet_to_workbook (workbook=0x604010, offset=0, visible=0 '\000', type=0 '\000', 
name=0x615340 "Worksheet") at freexl.c:1142
1142            workbook->last_sheet->next = sheet;
(gdb) bt
#0  0x00007ffff7bcc34b in add_sheet_to_workbook (workbook=0x604010, offset=0, visible=0 '\000', type=0 '\000', 
name=0x615340 "Worksheet") at freexl.c:1142
#1  0x00007ffff7bcf5ca in read_legacy_biff (workbook=0x604010, swap=0) at freexl.c:2465
#2  0x00007ffff7bd37a7 in common_open (path=0x7fffffffe19c "./crashes/82c4a4b19c5ef5b72d11dcf34b9b936a", 
xls_handle=0x7fffffffdcf8, magic=1675431287) at freexl.c:4055
#3  0x00007ffff7bd3b3a in freexl_open (path=0x7fffffffe19c "./crashes/82c4a4b19c5ef5b72d11dcf34b9b936a", 
xls_handle=0x7fffffffdcf8) at freexl.c:4202
#4  0x0000000000400c3c in main (argc=2, argv=0x7fffffffddf8) at test_xl.c:84
(gdb) p workbook->last_sheet 
$7 = (biff_sheet *) 0x4141414141414141

Timeline

2017-09-06 - Vendor Disclosure
2017-09-11 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.