Exploit-DB #47103
Description: Microsoft DirectWrite / AFDKO - Heap-Based Out-of-Bounds Read/Write in OpenType Font Handling Due to Empty ROS Strings
Type: dos
Platform: windows
Date: 2019-07-10
Author: Google Security Research

Exploit:
-----=====[ Background ]=====-----

AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree.

At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification.

We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality.

One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser.

-----=====[ Description ]=====-----

While fuzzing the standard "tx" AFDKO utility using a "tx -cff <input file> /dev/null" command, we have encountered multiple crashes in the CFF Writer (cfw) component of the FDK. These crashes are triggered in the cfwSindexAssignSID() function in the afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c file:

--- cut ---
   158  /* Assign the next custom SID to the specified custom string. */
   159  SID cfwSindexAssignSID(cfwCtx g, SRI index) {
   160      sindexCtx h = g->ctx.sindex;
   161      if (index < STD_STR_CNT) {
   162          return index;
   163      } else {
   164          CustomRec *custom = &h->custom.array[index - STD_STR_CNT];
   165          if (custom->sid == SID_UNDEF) {
   166              custom->sid = h->nextid++;
   167          }
   168          return custom->sid;
   169      }
   170  }
--- cut ---

In all cases, the exception is thrown in line 165, and is caused by an out-of-bounds access to h->custom.array[] due to the "index" argument being equal to 65535 (0xffff). The two different invocations of cfwSindexAssignSID() which trigger the crash are found in the cfwDictFillTop() function in cffwrite/cffwrite_dict.c (lines 520 and 522):

--- cut ---
   517      /* ROS */
   518      if (top->sup.flags & ABF_CID_FONT) {
   519          cfwDictSaveInt(dst,
   520                         cfwSindexAssignSID(g, (SRI)top->cid.Registry.impl));
   521          cfwDictSaveInt(dst,
   522                         cfwSindexAssignSID(g, (SRI)top->cid.Ordering.impl));
   523          cfwDictSaveInt(dst, top->cid.Supplement);
   524          cfwDictSaveOp(dst, cff_ROS);
   525      }
--- cut ---

The cause of the problem is that the top->cid.Registry.impl and/or top->cid.Ordering.impl fields are set to 0xffff while executing the above code, and they are treated as valid indexes into h->custom.array, even though they contain the special marker values.

The "Registry" and "Ordering" strings are initialized when a cff_ROS operator is encountered while loading an input DICT structure in readDICT (cffread/cffread.c):

--- cut ---
  1287                      case cff_ROS:
  1288                          CHKUFLOW(3);
  1289                          top->cid.Registry.ptr = sid2str(h, (SID)INDEX_INT(0));
  1290                          top->cid.Ordering.ptr = sid2str(h, (SID)INDEX_INT(1));
  1291                          top->cid.Supplement = INDEX_INT(2);
  1292                          h->flags |= CID_FONT;
  1293                          break;
--- cut ---

Later on, these strings are added to the string index of the output font in cfwDictCopyTop (cffwrite/cffwrite_dict.c):

--- cut ---
   193      /* Add strings to index */
   194      addString(g, &dst->version);
[...]
   204      addString(g, &dst->cid.Registry);
   205      addString(g, &dst->cid.Ordering);
   206  }
--- cut ---

where addString() is defined as:

--- cut ---
    59  /* Add string to string index. */
    60  static void addString(cfwCtx g, abfString *str) {
    61      str->impl = cfwSindexAddString(g, str->ptr);
    62  }
--- cut ---

where in turn cfwSindexAddString() is defined as (cffwrite/cffwrite_sindex.c):

--- cut ---
    99  /* Add string. If standard string return its SID, otherwise if in table return
   100     existing record index, else add to table and return new record index. If
   101     string is empty return SRI_UNDEF. */
   102  SRI cfwSindexAddString(cfwCtx g, char *string) {
   103