Exploit-DB #47100
Description: Microsoft DirectWrite / AFDKO - Out-of-Bounds Read in OpenType Font Handling Due to Undefined FontName Index
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 cfwSindexGetString() function in the afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c file:

--- cut ---
   148  /* Get string from SRI. */
   149  char *cfwSindexGetString(cfwCtx g, SRI index) {
   150      sindexCtx h = g->ctx.sindex;
   151      if (index < STD_STR_CNT) {
   152          return sid2std[index];
   153      } else {
   154          return &h->strings.array[h->custom.array[index - STD_STR_CNT].iString];
   155      }
   156  }
--- cut ---

In all cases, the exception is thrown in line 154, and is caused by an out-of-bounds access to h->custom.array[] due to the "index" argument being equal to 65535 (0xffff). For some reproducers, the h->custom dynamic array is correctly initialized and h->custom.array points into a heap allocation; in other cases h->custom is an empty array and h->custom.array has a near-NULL value. Even in the latter case, accessing h->custom.array[65144] translates to an address around 0x7f4c4 (on x86) or 0xfe884 (on x64), both of which can be mapped on many operating systems.

The cfwSindexGetString() function is called from fillNameINDEX() in cffwrite/cffwrite.c:

--- cut ---
   845      for (i = 0; i < h->FontSet.cnt; i++) {
   846          cff_Font *font = &h->FontSet.array[i];
   847          if (font->FDArray.cnt > 0) {
   848              char *name =
   849                  cfwSindexGetString(h->g, (SRI)((font->flags & FONT_CID) ? font->top.cid.CIDFontName.impl : font->FDArray.array[0].dict.FontName.impl));
   850              /* 64-bit warning fixed by cast here */
   851              h->name.datasize += (long)strlen(name);
   852          }
   853      }
--- cut ---

We can see that the "index" argument is passed from font->top.cid.CIDFontName.impl or font->FDArray.array[0].dict.FontName.impl. Both CIDFontName and FontName objects are abfString structures, defined in afdko/c/public/lib/api/absfont.h:

--- cut ---
    44  typedef struct /* String */
    45  {
    46      char *ptr; /* ABF_UNSET_PTR */
    47      long impl; /* ABF_UNSET_INT */
    48  } abfString;
--- cut ---

The "impl" field is used to store a SRI-typed value, which takes the default value of SRI_UNDEF if it is uninitialized or invalid:

--- cut ---
    22  #define SRI_UNDEF 0xffff    /* SRI of undefined string */
--- cut ---

This indicates that the font name-related structures are not properly initialized before being used to generate the output CFF font. As the string returned by cfwSindexGetString() is later saved to the output file, this out-of-bounds read could lead to the disclosure of the AFDKO client process memory.

-----=====[ Proof of Concept ]=====-----

There are two proof of concept files, poc1.otf and poc2.otf. The first one triggers an access to h->custom.array[65144] for an empty h->custom array, while the second one results in a similar OOB read but with a non-empty h->custom object.

The fonts are specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and a second copy of it with the modified DICT was inserted at the end of the fonts with the tag "CFF ". This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table tak