Exploit-DB #47260
Description: Adobe Acrobat CoolType (AFDKO) - Call from Uninitialized Memory due to Empty FDArray in Type 1 Fonts
Type: dos
Platform: windows
Date: 2019-08-15
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.

We have recently discovered that parts of AFDKO are compiled in in Adobe's desktop software such as Adobe Acrobat. Within a single installation of Acrobat, we have found traces of AFDKO in four different libraries: acrodistdll.dll, Acrobat.dll, CoolType.dll and AdobePDFL.dll. According to our brief analysis, AFDKO is not used for font rasterization (there is a different engine for that), but rather for the conversion between font formats. For example, it is possible to execute the AFDKO copy in CoolType.dll by opening a PDF file with an embedded font, and exporting it to a PostScript (.ps) or Encapsulated PostScript (.eps) document. It is uncertain if the AFDKO copies in other libraries are reachable as an attack surface and how.

It is also interesting to note that the AFDKO copies in the above DLLs are much older than the latest version of the code on GitHub. This can be easily recognized thanks to the fact that each component of the library (e.g. the Type 1 Reader - t1r, Type 1 Writer - t1w, CFF reader - cfr etc.) has its own version number included in the source code, and they change over time. For example, CoolType's version of the "cfr" module is 2.0.44, whereas the first open-sourced commit of AFDKO from September 2014 has version 2.0.46 (currently 2.1.0), so we can conclude that the CoolType fork is at least about ~5 years old. Furthermore, the forks in Acrobat.dll and AdobePDFL.dll are even older, with a "cfr" version of 2.0.31.

Despite the fact that CoolType contains an old fork of the library, it includes multiple non-public fixes for various vulnerabilities, particularly a number of important bounds checks in read*() functions declared in cffread/cffread.c (e.g. readFDSelect, readCharset etc.). These checks were first introduced in CoolType.dll shipped with Adobe Reader 9.1.2, which was released on 28 May 2009. This means that the internal fork of the code has had many bugs fixed for the last 10 years, which are still not addressed in the open-source branch of the code. Nevertheless, we found more security vulnerabilities which affect the AFDKO used by CoolType, through analysis of the publicly available code. This report describes one such issue reachable through the Adobe Acrobat file export functionality.

-----=====[ Description ]=====-----

The Type 1 font parsing code in AFDKO resides in c/public/lib/source/t1read/t1read.c, and the main context structure is t1rCtx, also declared in that file. t1rCtx contains a dynamic array FDArray of FDInfo structures:

--- cut ---
    70  typedef struct /* FDArray element */
    71  {
    72      abfFontDict *fdict; /* Font dict */
    73      struct              /* Subrs */
    74      {
    75          ctlRegion region; /* cstr data region */
    76          dnaDCL(long, offset);
    77      } subrs;
    78      t1cAuxData aux; /* Auxiliary charstring data */
    79      struct          /* Dict key info */
    80      {
    81          long lenIV;                /* Length random cipher bytes */
    82          long SubrMapOffset;        /* CID-specific key */
    83          unsigned short SubrCount;  /* CID-specific key */
    84          unsigned short SDBytes;    /* CID-specific key */
    85          unsigned short BlueValues; /* Flags /BlueValues seen */
    86      } key;
    87      t1cDecryptFunc decrypt; /* Charstring decryption function */
    88  } FDInfo;
    89
[...]
   110      dnaDCL(FDInfo, FDArray);       /* FDArray */
--- cut ---

The array is initially set to 1 element at the beginning of t1rBegFont():

--- cut ---
  3035  /* Parse PostScript font. */
  3036  int t1rBegFont(t1rCtx h, long flags, long origin, abfTopDict **top, float *UDV) {
[...]
  3045      dnaSET_CNT(h->FDArray, 1);
--- cut ---

Later on, the array can be resized to any number of elements in the range of 0-256 using the /FDArray operator, which is handled by the initFDArray() function:

--- cut ---
  2041  /* Initialize FDArray. */
  2042  static void initFDArray(t1rCtx h, long cnt) {
  2043      int i;
  2044      if (cnt < 0 || cnt > 256)
  2045          badKeyValue(h, kFDArray);
  2046      dnaSET_CNT(h->FDArray, cnt);
  2047      dnaSET_CNT(h->fdicts, cnt);
  2048      for (i = 0; i < h->FDArray.cnt; i++)
  2049          initFDInfo(h, i);
  2050      h->fd = &h->FDArray.array[0];
  2051  }
  2052
[...]
  2318          case kFDArray:
  2319              initFDArray(h, parseInt(h, kFDArray));
  2320              break;
--- cut ---

Parts of the FDInfo structures (specifically the "aux" nested structure) are initialized later on, in prepClientData():

--- cut ---
  2949      /* Prepare auxiliary data */
  2950      for (i = 0; i < h->FDArray.cnt; i++) {
  2951          FDInfo *fd = &h->FDArray.array[i];
  2952          fd->aux.flags = 0;
  2953          if (h->flags & T1R_UPDATE_OPS)
  2954              fd->aux.flags |= T1C_UPDATE_OPS;
  2955          fd->aux.src = h->stm.tmp;
  2956          fd->aux.subrs.cnt = fd->subrs.offset.cnt;
  2957          fd->aux.subrs.offset = fd->subrs.offset.array;
  2958          fd->aux.subrsEnd = fd->subrs.region.end;
  2959          fd->aux.stm = &h->cb.stm;
[...]
--- cut ---

The problem with the code is that it assumes that FDArray always contains at least 1 element, whereas initFDArray() allows us to truncate it to 0 items. 

When the cl