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.