Exploit-DB #47095
Description: Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow in OpenType Font Handling in readCharset
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 ]=====-----

The readCharset() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read and parse the charset information of an input OpenType font. It is called by cfrBegFont(), the standard entry point function for the "cfr" (CFF Reader) module of AFDKO. The relevant part of the function is shown below:

--- cut ---
[...]
  2179                  case 1:
  2180                      size = 1;
  2181                      /* Fall through */
  2182                  case 2:
  2183                      while (gid < h->glyphs.cnt) {
  2184                          unsigned short id = read2(h);
  2185                          long nLeft = readN(h, size);
  2186                          while (nLeft-- >= 0)
  2187                              addID(h, gid++, id++);
  2188                      }
  2189                      break;
--- cut ---

whereas addID() is defined as follows:

--- cut ---
  1838  /* Add SID/CID to charset */
  1839  static void addID(cfrCtx h, long gid, unsigned short id) {
  1840      abfGlyphInfo *info = &h->glyphs.array[gid];
  1841      if (h->flags & CID_FONT)
  1842          /* Save CID */
  1843          info->cid = id;
  1844      else {
  1845          /* Save SID */
  1846          info->gname.impl = id;
  1847          info->gname.ptr = sid2str(h, id);
  1848
  1849          /* Non-CID font so select FD[0] */
  1850          info->iFD = 0;
[...]
  1859      }
  1860  }
--- cut ---

The readCharset() routine doesn't consider the size of the h->glyphs array and writes to it solely based on the charset information. If the value read from the input stream in line 2185 exceeds the number of glyphs in the font, the array may be overflown in addID() (line 1843 or 1846, 1847, 1850), corrupting adjacent objects on the heap. The h->glyphs array is initialized in readCharStringsINDEX() according to the number of CharStrings found in the font:

--- cut ---
  1791      dnaSET_CNT(h->glyphs, index.count);
--- cut ---

-----=====[ Proof of Concept ]=====-----

The proof of concept font contains a charset descriptor with the following initial values:

- width = 0x02 (changed from 0x01)
- id = 0x4141 (changed from 0x0001)
- nLeft = 0xffff (changed from 0x15)

By increasing the value of "nLeft" from 21 to 65535, we cause the loop in lines 2386-2387 to go largely out of bounds and overflow the h->glyphs array.

In theory, the vulnerability shouldn't be possible to reach in Microsoft DirectWrite and its client applications, because AFDKO is only used there for instancing variable fonts, whereas such CFF2 fonts follow another execution path in the readCharset() function:

--- cut ---
  2138      if (h->header.major == 2) {
  2139          postRead(h);
  2140          if (h->cff2.mvar)
  2141              MVARread(h);
  2142          if (!(h->flags & CID_FONT))
  2143              readCharSetFromPost(h);
  2144          else {
  2145              long gid;
  2146              for (gid = 0; gid < h->glyphs.cnt; gid++) {
  2147                  abfGlyphInfo *info = &h->glyphs.array[gid];
  2148                  info->cid = (unsigned short)gid;
  2149              }
  2150          }
  2151          return;
  2152      }
--- cut ---

However, we have found that it is in fact possible to trigger the handling of CFFv1 in AFDKO, by appending the old style "CFF " table to a variable font which already includes a "CFF2" one. This causes DirectWrite to correctly load the variable f