Microsoft DirectWrite AFDKO OpenType Read Of Uninitialized Memory

Microsoft DirectWrite / AFDKO read of uninitialized BuildCharArray memory in OpenType font handling

-----=====[ Background ]===== Microsoft DirectWrite / AFDKO read of uninitialized BuildCharArray memory in OpenType font handling

-----=====[ 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 CFF charstring interpreter context is stored in a t2cCtx structure, which is defined in afdko/c/public/lib/source/t2cstr/t2cstr.c:

--- cut ---
50 /* Module context */
51 typedef struct _t2cCtx *t2cCtx;
52 struct _t2cCtx {
53 long flags; /* Control flags */
130 ctlMemoryCallbacks *mem; /* Glyph callbacks */
131 struct /* Error handling */
132 {
133 _Exc_Buf env;
134 } err;
135 };
--- cut ---

The t2cCtx structure is allocated in the stack frame of the t2cParse() function, which then calls t2Decode(), the main interpreter loop:

--- cut ---
2522 /* Parse Type 2 charstring. */
2523 int t2cParse(long offset, long endOffset, t2cAuxData *aux, unsigned short gid, cff2GlyphCallbacks *cff2, abfGlyphCallbacks *glyph, ctlMemoryCallbacks *mem) {
2524 struct _t2cCtx h;
2525 int retVal;
2526 /* Initialize */
2527 h.flags = PEND_WIDTH | PEND_MASK;
2528 h.stack.cnt = 0;
2588 if (aux->flags & T2C_CUBE_GSUBR)
2589 retVal = t2DecodeSubr(&h, offset);
2590 else
2591 retVal = t2Decode(&h, offset);
2601 return retVal;
2602 }
--- cut ---

Part of t2cCtx is the "BuildCharArray" (BCA), a helper array that can be arbitrarily read from and written to by the CharString program:

--- cut ---
80 float BCA[TX_BCA_LENGTH]; /* BuildCharArray */
--- cut ---

The length of the array is constant and equal to 32 (see c/public/lib/resource/txops.h):

--- cut ---
208 #define TX_BCA_LENGTH 32 /* BuildCharArray length (elements) */
--- cut ---

The two instructions designated to move data between the interpreter stack and the BCA are tx_get and tx_put:

--- cut ---
1666 case tx_put:
1667 CHKUFLOW(h, 2);
1668 {
1669 i = (int)POP();
1670 if (i < 0 || i >= TX_BCA_LENGTH)
1671 return t2cErrPutBounds;
1672 h->BCA[i] = POP();
1673 }
1674 continue;
1675 case tx_get:
1676 CHKUFLOW(h, 1);
1677 {
1678 i = (int)POP();
1679 if (i < 0 || i >= TX_BCA_LENGTH)
1680 return t2cErrGetBounds;
1681 PUSH(h->BCA[i]);
1682 }
1683 continue;
--- cut ---

Due to the fact that the t2cCtx.BCA array is never zero-ed out, and the implementation of the tx_get instruction doesn't verify if a specific element of the BCA has been previously written to before reading from it, it is possible to use tx_get to load up to 32 uninitialized values in the form of 32-bit floats from the native thread stack to the interpreter stack. Depending on the nature of such leftover data, it could be either leaked through glyph shapes, or used directly to facilitate the exploitation of another, more serious vulnerability (e.g. by leaking a module image base and thus defeating ASLR).

Similar leaks from the BCA (also known as "transient array") were discovered in the past in various font engines, such as in the Windows ATMFD.dll font driver [5], Adobe CoolType library [6], Microsoft DirectWrite library [7], Windows Presentation Foundation [8] or Oracle Java [9]. The exploitation of the vulnerability in Internet Explorer (which internally uses DirectWrite) was described in detail in a Project Zero blog post in 2015 [10].

-----=====[ References ]=====-----


This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available (whichever is earlier), the bug report will become visible to the public.

Found by:

Leave a comment