Headline
CVE-2017-2923: TALOS-2017-0430 || Cisco Talos Intelligence Group
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.
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
icewall@ubuntu:~/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: memcpy@@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
Discovered by Marcin ‘Icewall’ Noga of Cisco Talos.