articles icon indicating copy to clipboard operation
articles copied to clipboard

heap-based out-of-bounds read when parsing otf file with undefined glyph name in svg option(AFDKO)

Open xinali opened this issue 5 years ago • 0 comments

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.

xinali avatar Aug 22 '19 01:08 xinali