Talos Vulnerability Report

TALOS-2017-0430

FreeXL read_biff_next_record Code Execution Vulnerability

September 11, 2017
CVE Number

CVE-2017-2923

Summary

An exploitable heap based buffer overflow vulnerability exists in the read_biff_next_record 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. A heap-based buffer overflow appears in the read_biff_next_record function. The vulnerability appears in a situation when a BIFF record size is bigger than workbook->record field. The following code contains the vulnerability:

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

freexl.c
Line 3723   static int
Line 3724   read_biff_next_record (biff_workbook * workbook, int swap, int *errcode)
Line 3725   {
(...)
Line 3772    /* fetching record-type and record-size */
Line 3773    memcpy (record_type.bytes, workbook->p_in, 2);
Line 3774    workbook->p_in += 2;
Line 3775    memcpy (record_size.bytes, workbook->p_in, 2);
Line 3776    workbook->p_in += 2;
(...)
Line 3808     while (already_done < workbook->record_size)
Line 3809       {
Line 3810       /* reading a further sector */
Line 3811       ret = read_cfbf_next_sector (workbook, errcode);
Line 3812       if (ret == -1)
Line 3813           return -1;  /* EOF found */
Line 3814       if (ret == 0)
Line 3815           return 0;
Line 3816       chunk = workbook->record_size - already_done;
Line 3817       if (chunk <= workbook->fat->sector_size)
Line 3818         {
Line 3819             /* ok, finished: whole record reassembled */
Line 3820             memcpy (workbook->record + already_done, workbook->p_in,
Line 3821                 chunk);
Line 3822             workbook->p_in += chunk;
Line 3823             goto record_done;
Line 3824         }
Line 3825       /* record still spanning on the following sector */
Line 3826       memcpy (workbook->record + already_done, workbook->p_in,
Line 3827           workbook->fat->sector_size);
Line 3828       workbook->p_in += workbook->fat->sector_size;
Line 3829       already_done += workbook->fat->sector_size;
Line 3830       }

At line 3775 the record_size is read directly from the file, which is used to control the loop at line 3808. Then at lines 3820 and 3826 data will be copied into the record which has a fixed size of 8224. Since there are no checks to ensure that the record_size is smaller than this declared value, this will result in a heap-based buffer overflow.

Crash Information

[email protected]:~/bugs/freexl-1.0.3/bin$ valgrind ./test_xl ./crashes/ef69e246113c046810b7ef908cc7a09f  
==99598== Memcheck, a memory error detector
==99598== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==99598== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==99598== Command: ./test_xl ./crashes/ef69e246113c046810b7ef908cc7a09f
==99598== 
==99598== Invalid write of size 2
==99598==    at 0x4C32723: [email protected]@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==99598==    by 0x4E43F66: read_biff_next_record (freexl.c:3826)
==99598==    by 0x4E448A7: common_open (freexl.c:4102)
==99598==    by 0x4E44B39: freexl_open (freexl.c:4202)
==99598==    by 0x400C3B: main (test_xl.c:84)
==99598==  Address 0x572b128 is 0 bytes after a block of size 65,768 alloc'd
==99598==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==99598==    by 0x4E3D4E8: alloc_workbook (freexl.c:1193)
==99598==    by 0x4E44720: common_open (freexl.c:4037)
==99598==    by 0x4E44B39: freexl_open (freexl.c:4202)
==99598==    by 0x400C3B: main (test_xl.c:84)
==99598== 
==99598== Invalid read of size 8
==99598==    at 0x50BDF20: fseek (fseek.c:35)
==99598==    by 0x4E4394A: read_cfbf_sector (freexl.c:3666)
==99598==    by 0x4E43AC5: read_cfbf_next_sector (freexl.c:3701)
==99598==    by 0x4E43E9E: read_biff_next_record (freexl.c:3811)
==99598==    by 0x4E448A7: common_open (freexl.c:4102)
==99598==    by 0x4E44B39: freexl_open (freexl.c:4202)
==99598==    by 0x400C3B: main (test_xl.c:84)
==99598==  Address 0x8670017086708 is not stack'd, malloc'd or (recently) free'd
==99598== 
==99598== 
==99598== Process terminating with default action of signal 11 (SIGSEGV)
==99598==  General Protection Fault
==99598==    at 0x50BDF20: fseek (fseek.c:35)
==99598==    by 0x4E4394A: read_cfbf_sector (freexl.c:3666)
==99598==    by 0x4E43AC5: read_cfbf_next_sector (freexl.c:3701)
==99598==    by 0x4E43E9E: read_biff_next_record (freexl.c:3811)
==99598==    by 0x4E448A7: common_open (freexl.c:4102)
==99598==    by 0x4E44B39: freexl_open (freexl.c:4202)
==99598==    by 0x400C3B: main (test_xl.c:84)
==99598== Invalid read of size 8
==99598==    at 0x50C4193: _IO_flush_all_lockp (genops.c:786)
==99598==    by 0x50C4329: _IO_cleanup (genops.c:951)
==99598==    by 0x51BC338: __libc_freeres (in /lib/x86_64-linux-gnu/libc-2.23.so)
==99598==    by 0x4A2868C: _vgnU_freeres (in /usr/lib/valgrind/vgpreload_core-amd64-linux.so)
==99598==  Address 0x2000f0064001a is not stack'd, malloc'd or (recently) free'd
==99598== 
==99598== 
==99598== Process terminating with default action of signal 11 (SIGSEGV)
==99598==  General Protection Fault
==99598==    at 0x50C4193: _IO_flush_all_lockp (genops.c:786)
==99598==    by 0x50C4329: _IO_cleanup (genops.c:951)
==99598==    by 0x51BC338: __libc_freeres (in /lib/x86_64-linux-gnu/libc-2.23.so)
==99598==    by 0x4A2868C: _vgnU_freeres (in /usr/lib/valgrind/vgpreload_core-amd64-linux.so)
==99598== 
==99598== HEAP SUMMARY:
==99598==     in use at exit: 114,272 bytes in 401 blocks
==99598==   total heap usage: 403 allocs, 2 frees, 114,432 bytes allocated
==99598== 
==99598== LEAK SUMMARY:
==99598==    definitely lost: 4,096 bytes in 1 blocks
==99598==    indirectly lost: 0 bytes in 0 blocks
==99598==      possibly lost: 0 bytes in 0 blocks
==99598==    still reachable: 110,176 bytes in 400 blocks
==99598==         suppressed: 0 bytes in 0 blocks
==99598== Rerun with --leak-check=full to see details of leaked memory
==99598== 
==99598== For counts of detected and suppressed errors, rerun with: -v
==99598== ERROR SUMMARY: 74 errors from 3 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

Timeline

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

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.