articles
articles copied to clipboard
heap-based out-of-bounds read when parsing otf file with undefined glyph name in svg option(AFDKO)
heap-based out-of-bounds read when parsing otf file with undefined glyph name in svg option(AFDKO)
前段时间fuzz出来的,提给adobe的issue,目前已经被修复了,其中指针追踪挺有意思的
0x1 Segment Fault
Please excuse my poor English. I'm not a native speaker. I will do my best to describe this issue.
In lates commit ad786a1bfbaa11a2e14dca3cdd95a66da8f824fc
use clang compile with debug option
compile tx
in c/tx/build/linux/gcc/debug/
make clean && CC=clang make
then use tx
to parse a specific otf file
tx -svg poc.otf
tx
segment fault
tx: --- poc.otf
tx: (cfr) invalid fvar table version
tx: (cfr) invalid/missing hhea table
tx: (cfr) name table missing
tx: (cfr) axis count in variation font region list does not match axis count in fvar table
tx: (cfr) Warning: CharString of GID 2 is 68301 bytes long. CharStrings longer than 65535 bytes might not be supported by some implementations.
Segmentation fault (core dumped)
0x2 Dynamic Analysis
I use pwndbg
to debug
#0 __strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:31
#1 0x0000000000476360 in matchName ()
#2 0x00007ffff773b207 in __GI_bsearch (__key=0x0, __base=0x4ca870 <mapName2UV.agl>, __nmemb=<optimized out>, __size=16, __compar=0x476340 <matchName>) at ../bits/stdlib-bsearch.h:33
#3 0x0000000000475aa7 in mapName2UV ()
#4 0x0000000000478c60 in svg_GlyphBeg ()
#5 0x000000000046f43b in otfGlyphBeg ()
#6 0x0000000000413468 in readGlyph (h=0x6f8f30, gid=0, glyph_cb=0x6f3690) at ../../../../../source/cffread/cffread.c:2877
#7 0x0000000000413395 in cfrIterateGlyphs (h=0x6f8f30, glyph_cb=0x6f3690) at ../../../../../source/cffread/cffread.c:2930
#8 0x0000000000405b91 in cfrReadFont (h=0x6ec010, origin=0, ttcIndex=0) at ../../../../source/tx.c:151
#9 0x00000000004058bf in doFile (h=0x6ec010, srcname=0x7fffffffe6dc "poc.otf") at ../../../../source/tx.c:429
#10 0x0000000000404f3e in doSingleFileSet (h=0x6ec010, srcname=0x7fffffffe6dc "poc.otf") at ../../../../source/tx.c:488
#11 0x0000000000402d89 in parseArgs (h=0x6ec010, argc=2, argv=0x7fffffffe400) at ../../../../source/tx.c:558
#12 0x0000000000401c27 in main (argc=2, argv=0x7fffffffe400) at ../../../../source/tx.c:1587
#13 0x00007ffff7724830 in __libc_start_main (main=0x401a60 <main>, argc=3, argv=0x7fffffffe3f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe3e8) at ../csu/libc-start.c:291
#14 0x0000000000401989 in _start ()
then I do some analysis, I found when tx
parse the specific otf
file with no glyph name
, tx
will occur segment fault.
debug the issue, set breakpoint in cffread.c:2877
hit the breakpoint
In file: /root/tmp/afdko/c/public/lib/source/cffread/cffread.c
2872 abfGlyphInfo *info = &h->glyphs.array[gid];
2873 t2cAuxData *aux = &h->FDArray.array[info->iFD].aux;
2874 cff2GlyphCallbacks *cff2_cb = NULL;
2875
2876 /* Begin glyph and mark it as seen */
► 2877 result = glyph_cb->beg(glyph_cb, info); <====
2878 info->flags |= ABF_GLYPH_SEEN;
2879 info->blendInfo.vsindex = aux->default_vsIndex;
2880
2881 /* Check result */
2882 switch (result) {
─────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffddd0 ◂— 0x4
01:0008│ 0x7fffffffddd8 —▸ 0x46ba90 (mem_manage) ◂— push rbp
02:0010│ 0x7fffffffdde0 ◂— 0x0
03:0018│ 0x7fffffffdde8 —▸ 0x7ffff777c77b (_IO_file_seekoff+699) ◂— cmp r12, rax
04:0020│ 0x7fffffffddf0 ◂— 0x2
05:0028│ 0x7fffffffddf8 ◂— 0x0
... ↓
07:0038│ 0x7fffffffde08 —▸ 0x6fe178 ◂— 0x4
───────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 41344f readGlyph+111
f 1 413395 cfrIterateGlyphs+117
f 2 405b91 cfrReadFont+561
f 3 4058bf doFile+879
f 4 404f3e doSingleFileSet+46
f 5 402d89 parseArgs+425
f 6 401c27 main+455
f 7 7ffff7724830 __libc_start_main+240
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint cffread.c:2877
call a function pointer glyph_cb->beg
, step into, enter otfGlyphBeg
in otfGlyphBeg+121
, it call another function pointer
0x46f404 <otfGlyphBeg+68> je otfGlyphBeg+102 <0x46f426>
↓
0x46f426 <otfGlyphBeg+102> mov rax, qword ptr [rbp - 0x18]
0x46f42a <otfGlyphBeg+106> mov rax, qword ptr [rax + 0x7708]
0x46f431 <otfGlyphBeg+113> mov rdi, qword ptr [rbp - 8]
0x46f435 <otfGlyphBeg+117> mov rsi, qword ptr [rbp - 0x10]
► 0x46f439 <otfGlyphBeg+121> call rax <0x478bb0>
rdi: 0x6f3690 —▸ 0x6f37f0 ◂— 0x0
rsi: 0x70b2d0 ◂— 0x0
0x46f43b <otfGlyphBeg+123> add rsp, 0x20
0x46f43f <otfGlyphBeg+127> pop rbp
0x46f440 <otfGlyphBeg+128> ret
0x46f441 nop word ptr cs:[rax + rax]
0x46f450 <bufSeek> push rbp
step into, in svg_GlyphBeg+171
, it call another function pointer
0x478c46 <svg_GlyphBeg+150> mov rsi, qword ptr [rax + 8]
0x478c4a <svg_GlyphBeg+154> mov rax, qword ptr [rbp - 0x18]
0x478c4e <svg_GlyphBeg+158> add rax, 0x6b78
0x478c54 <svg_GlyphBeg+164> add rax, 0x10
0x478c58 <svg_GlyphBeg+168> mov rdx, rax
► 0x478c5b <svg_GlyphBeg+171> call mapName2UV <0x475a60>
rdi: 0x6ec010 —▸ 0x7fffffffe6d4 ◂— 0x6776732d007874 /* 'tx' */
rsi: 0x0
rdx: 0x6f2b98 ◂— 0xe000
rcx: 0xffff0004
0x478c60 <svg_GlyphBeg+176> movzx ecx, ax
0x478c63 <svg_GlyphBeg+179> mov edx, ecx
0x478c65 <svg_GlyphBeg+181> mov rsi, qword ptr [rbp - 0x10]
0x478c69 <svg_GlyphBeg+185> mov qword ptr [rsi + 0x20], rdx
0x478c6d <svg_GlyphBeg+189> mov rax, qword ptr [svwGlyphCallbacks+24] <0x4be940>
step into, in mapName2UV+66
, it calls a function named bsearch
0x475a91 <mapName2UV+49> mov qword ptr [rbp - 0x20], rdx
0x475a95 <mapName2UV+53> mov rdi, qword ptr [rbp - 0x18]
0x475a99 <mapName2UV+57> mov rsi, rax
0x475a9c <mapName2UV+60> mov rdx, r8
0x475a9f <mapName2UV+63> mov r8, r9
► 0x475aa2 <mapName2UV+66> call bsearch@plt <0x4018e0>
key: 0x0
base: 0x4ca870 (mapName2UV.agl) —▸ 0x4b1ab9 ◂— add byte ptr [rip + 0x462d0045], bpl /* 'A' */
nmemb: 0x41b
size: 0x10
compar: 0x476340 (matchName) ◂— push rbp
0x475aa7 <mapName2UV+71> mov qword ptr [rbp - 0x28], rax
0x475aab <mapName2UV+75> cmp qword ptr [rbp - 0x28], 0
0x475ab0 <mapName2UV+80> je mapName2UV+103 <0x475ac7>
0x475ab6 <mapName2UV+86> mov rax, qword ptr [rbp - 0x28]
0x475aba <mapName2UV+90> mov cx, word ptr [rax + 8]
key
is const char*
type, in bsearch
, it will access the data of key. But key is 0x0
, so it will segment fault.
0x3 Source Code Analysis
After analyzing source code, the issue call order
readGlyph/cffread.c:2877
|=> glyph_cb->beg =point=> otfGlyphBeg(tx_shared.c:4773)
|=> h->cb.saveGlyphBeg =point=> svg_GlyphBeg(tx_shared.c:2637)
|=> mapName2UV (tx_shared.c:1518)
|=> bsearch => Segment Fault
function mapName2UV
/* Map glyph name to Unicode value using simplified assignment algorithm. */
static unsigned short mapName2UV(txCtx h, char *gname, unsigned short *unrec) {
static const Name2UV agl[] =
{
#include "agl2uv.h"
};
Name2UV *map = (Name2UV *)bsearch(gname, agl, ARRAY_LEN(agl),
sizeof(Name2UV), matchName);
if (map != NULL)
return map->uv; /* Match found */
/* Not found */
if (strcmp(gname, ".notdef") == 0)
return 0xFFFF; /* No encoding for .notdef */
if (gname[0] == 'u' &&
gname[1] == 'n' &&
gname[2] == 'i' &&
isxdigit(gname[3]) && !islower(gname[3]) &&
isxdigit(gname[4]) && !islower(gname[4]) &&
isxdigit(gname[5]) && !islower(gname[5]) &&
isxdigit(gname[6]) && !islower(gname[6]) &&
gname[7] == '\0')
/* uni<CODE> name; return hex part */
return (unsigned short)strtol(&gname[3], NULL, 16);
/* return Private Use Area UV */
return (*unrec)++;
}
bsearch
first parameter key
is gname
function bsearch
_extern_inline void *
bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
__compar_fn_t __compar)
{
size_t __l, __u, __idx;
const void *__p;
int __comparison;
__l = 0;
__u = __nmemb;
while (__l < __u)
{
__idx = (__l + __u) / 2;
__p = (void *) (((const char *) __base) + (__idx * __size));
__comparison = (*__compar) (__key, __p); // do not check __key, then crash
if (__comparison < 0)
__u = __idx;
else if (__comparison > 0)
__l = __idx + 1;
else
return (void *) __p;
}
return NULL;
}
gname
is in structure abfGlyphInfo
, the structure defines inabsfont.h
typedef struct /* Glyph information */
{
short flags; /* Attribute flags */
#define ABF_GLYPH_CID (1 << 0) /* Glyph from CID-keyed font */
#define ABF_GLYPH_SEEN (1 << 1) /* Path data already returned to client */
#define ABF_GLYPH_UNICODE (1 << 2) /* Encoding is Unicode */
#define ABF_GLYPH_LANG_1 (1 << 3) /* Render with LanguageGroup 1 rules */
unsigned short tag; /* Unique tag */
abfString gname; /* Name-keyed: glyph name */ <======
abfEncoding encoding; /* Name-keyed: encoding list */
unsigned short cid; /* CID-keyed: CID */
uint16_t iFD; /* CID-keyed: FD index */
ctlRegion sup; /* Supplementary data */
struct {
unsigned short vsindex;
unsigned short maxstack;
unsigned short numRegions;
float *blendDeltaArgs;
} blendInfo; /* Supplementary data */
} abfGlyphInfo;
The gname
come from otf font file, and all data is in heap. If someone use a specific otf
file, it may cause some security issues with out of bound read.
If necessary, I can send you a proof of concept for this issue.