Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
xinali opened this issue Aug 22, 2019 · 0 comments

Comments

@xinali
Copy link
Owner

xinali commented Aug 22, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant