Fri 21 Nov 2014 12:45:11 PM UTC, original submission:
There is a vulnerability in the CFF CharString interpreter used by FreeType 2.5.3 (the Adobe CFF rasterizer contributed to the project in June 2013, see [1]), which makes it possible for a specially crafted OpenType file to perform arbitrary out-of-bounds writes on the interpreter function stack. This in turn makes it possible to create a font file which takes over the program control flow and reliably executes arbitrary code included in the input file.
Interestingly, this is not a new bug, but a variant of CVE-2014-2240 which I reported on February 24th, 2014 here: https://savannah.nongnu.org/bugs/?41697. As it turns out, the fix submitted in commit 0eae6eb0645264c98812f0095e0f5df4541830e6 [2] is not sufficient to entirely close the loophole. Attached are several testcases which reach the vulnerable "cf2_hintmap_build" function through a different code path ("cf2_glyphpath_moveTo" instead of directly from "cf2_interpT2CharString"). This makes it possible to bypass the original fix, because the if() statement in cf2hints.c:778 never executes, thus leading to the processing a "hStemHintArray" object longer than the maximum 96 allowed entries.
The issue has been reproduced with the current version of freetype2 from master git branch, with a 64-bit build of the ftbench utility compiled with AddressSanitizer:
$ ftbench <file>
I believe the immediate reason why the problem still exists today is that the following TODO comment in cf2hints.c:797 has still not been resolved, while in my opinion it should have been:
796: /* use the hStem hints only, which are first in the mask */
797: /* TODO: compare this to cffhintmaskGetBitCount */
798: bitCount = cf2_arrstack_size( hStemHintArray );
If it was, the fix would be effective and couldn't be bypassed to cause a stack-based non-continuous buffer overflow anymore.
Below is an example AddressSanitizer report illustrating the problem:
=================================================================
==15055==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff2dc05b30 at pc 0x71134e bp 0x7fff2dc055b0 sp 0x7fff2dc055a8
READ of size 1 at 0x7fff2dc05b30 thread T0
#0 0x71134d in cf2_hintmap_build freetype2/src/cff/cf2hints.c:822
#1 0x7048e1 in cf2_glyphpath_moveTo freetype2/src/cff/cf2hints.c:1606
#2 0x6f5259 in cf2_interpT2CharString freetype2/src/cff/cf2intrp.c:1243
#3 0x6e8570 in cf2_getGlyphOutline freetype2/src/cff/cf2font.c:469
#4 0x6e4c5e in cf2_decoder_parse_charstrings freetype2/src/cff/cf2ft.c:367
#5 0x6da446 in cff_slot_load freetype2/src/cff/cffgload.c:2840
#6 0x69dbcc in cff_glyph_load freetype2/src/cff/cffdrivr.c:185
#7 0x4a427e in FT_Load_Glyph freetype2/src/base/ftobjs.c:726
#8 0x491d69 in test_load ft2demos-2.5.3/src/ftbench.c:249
#9 0x492b51 in benchmark ft2demos-2.5.3/src/ftbench.c:216
#10 0x48e962 in main ft2demos-2.5.3/src/ftbench.c:1020
Address 0x7fff2dc05b30 is located in stack of thread T0 at offset 304 in frame
#0 0x7100cf in cf2_hintmap_build freetype2/src/cff/cf2hints.c:754
This frame has 20 object(s):
[32, 40) ''
[64, 72) ''
[96, 104) ''
[128, 136) ''
[160, 164) ''
[176, 177) ''
[192, 200) 'maskPtr'
[224, 232) 'font'
[256, 304) 'tempHintMask' <== Memory access at offset 304 overflows this variable
[336, 344) 'bitCount'
[368, 376) 'i'
[400, 401) 'maskByte'
[416, 448) 'dummy'
[480, 512) 'bottomHintEdge'
[544, 576) 'topHintEdge'
[608, 640) 'edge'
[672, 704) 'invalid'
[736, 768) 'bottomHintEdge1'
[800, 832) 'topHintEdge2'
[864, 872) 'stemhint'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions are supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow freetype2/src/cff/cf2hints.c:822 cf2_hintmap_build
Shadow bytes around the buggy address:
0x100065b78b10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100065b78b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100065b78b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100065b78b40: f1 f1 f1 f1 00 f2 f2 f2 00 f2 f2 f2 00 f2 f2 f2
0x100065b78b50: 00 f2 f2 f2 04 f2 01 f2 00 f2 f2 f2 00 f2 f2 f2
=>0x100065b78b60: 00 00 00 00 00 00[f2]f2 f2 f2 00 f2 f2 f2 00 f2
0x100065b78b70: f2 f2 01 f2 00 00 00 00 f2 f2 f2 f2 00 00 00 00
0x100065b78b80: f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 00 00 00
0x100065b78b90: f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 00 00 00
0x100065b78ba0: f2 f2 f2 f2 00 00 00 00 f2 f2 f2 f2 00 f3 f3 f3
0x100065b78bb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Contiguous container OOB:fc
ASan internal: fe
==15055==ABORTING
References:
[1] http://blog.typekit.com/2013/06/19/adobe-cff-font-rasterizer-accepted-by-freetype/
[2] http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=0eae6eb0645264c98812f0095e0f5df4541830e6
|