Mateusz Jurczyk, Project Zero (Originally posted on Project Zero blog 2021-01-12)

The Basics

Disclosure or Patch Date:

  • 23 March 2020 – advisory without technical details
  • 14 April 2020 – security bulletin and patch release

Product: Microsoft Windows

Advisory:

Affected Versions: Windows 7 through 10, prior to the April 2020 patch

First Patched Version: Windows with April 2020 patch (e.g. for Windows 10 1909/1903, KB4549951).

Issue/Bug Report: N/A

Patch CL: N/A

Bug-Introducing CL: N/A

Reporter(s): Google: Project Zero and Threat Analysis Group

The Code

Proof-of-concept: See the POC included in the "Vulnerability Details" section.

Exploit sample: N/A

Did you have access to the exploit sample when doing the analysis? Yes

The Vulnerability

Bug class: Continuous stack-based buffer overflow leading to a write-what-where condition

Vulnerability details: The issue was found in the processing of the VToHOrigin PostScript font object. I suspect that this object had only been defined in one of the early drafts of the Multiple Master extension, as it is very poorly documented today and hard to find any official information on. There is a corresponding ParseBlendVToHOrigin handler in both the kernel-mode font driver (atmfd.dll on Windows 8.1 and earlier), and user-mode font driver (fontdrvhost.exe, Windows 10). The C-like pseudo code of the vulnerable function is as follows:

int ParseBlendVToHOrigin(void *arg) {
  Fixed16_16 *ptrs[2];
  Fixed16_16 values[2];

  for (int i = 0; i < g_font->numMasters; i++) {
    ptrs[i] = &g_font->SomeArray[arg->SomeField + i];
  }

  for (int i = 0; i < 2; i++) {
    int values_read = GetOpenFixedArray(values, g_font->numMasters);
    if (values_read != g_font->numMasters) {
      return -8;
    }

    for (int num = 0; num < g_font->numMasters; num++) {
      ptrs[num][i] = values[num];
    }
  }

  return 0;
}

In summary, the function initializes numMasters pointers on the stack, then reads the same-sized array of fixed point values from the input stream, and writes each of them to the corresponding pointer. The root cause of the problem is that numMasters may be set to any value between 0–16, but both the ptrs and values arrays are only 2 items long. This means that with 3 or more masters specified in the font, accesses to ptrs[2] and values[2] and larger indexes corrupt memory on the stack. Notably, in the fifth iteration of the loop, the code tries to write values[4] to ptrs[4], an out-of-bounds index which overlaps with ((values[0]<<32)|values[1]). Because all data in the values buffer comes from the input file, this leads to a classic write-what-where condition.

To trigger the bug, it is sufficient to load a Type 1 font that includes specially crafted WeightVector and VToHOrigin objects, for example:

~%!PS-AdobeFont-1.0: Test 001.001
dict begin
/FontInfo begin
/FullName (Test) def
end
/FontType 1 def
/FontMatrix [0.001 0 0 0.001 0 0] def
/WeightVector [0 0 0 0 0] def
/Private begin
/Blend begin
/VToHOrigin[[16705.25490 -0.00001 0 0 16962.25882]]
/end
end
currentdict end
%currentfile eexec /Private begin
/CharStrings 1 begin
/.notdef ## -| { endchar } |-
end
end
mark %currentfile closefile
cleartomark

The first highlighted line sets numMasters to 5, and the second one triggers a write of 0x42424242 (represented as 16962.25882) to 0xffffffff41414141 (16705.25490 and -0.00001).

Patch analysis: N/A

Thoughts on how this vuln might have been found (fuzzing, code auditing, variant analysis, etc.):

The bug itself was trivial in nature, but the relevant VToHOrigin object is very rare and it's unclear if there are any legitimate Type 1 fonts that include it. This likely rules out dumb fuzzing as an option, leaving us with the following alternatives:

  1. Semi-smart fuzzing: For example, using a PostScript syntax-aware font generator with object names scraped from atmfd.dll or fontdrvhost.exe.
  2. Manual audit: The bug is easy to spot in the code manually, so it could have been found during a thorough audit of all PostScript object handlers defined in the font driver.
  3. Binary diffing: Forks of the same font engine exist in other products, e.g. the Adobe CoolType library (where the bug had been fixed for several years). The issue could have been identified by cross-diffing two copies of the same code and looking for security fixes present in one of them but not the other.

Out of these options, I consider 1) and 2) to be the most likely.

(Historical/present/future) context of bug:

The vulnerability is one of many bugs discovered in the Adobe Type Manager Font Driver (ATMFD) in Windows over the years. While the Type 1 CharString interpreter and the parsing of OpenType fonts had been relatively well-tested in 2015-2017, the processing of textual PostScript objects seems to have remained a blind spot in vulnerability research, enabling the bug to survive for a long period of time.

Thanks to Microsoft's efforts to mitigate attacks against the graphical subsystem (win32k lockdown) and fonts in particular (the usermode font driver architecture), the GDI font-related attack surface lost its value for sandbox escapes, and I don't expect many more such bugs to be exploited in the wild in the coming years.

This vulnerability was used with CVE-2020-0938. They are both very similar vulnerabilities.

The Exploit

Is the exploit method known? Yes

Exploit method:

On Windows 8.1 and earlier versions, the vulnerability was repeatedly used to set up a second stage payload in RWX kernel memory at a known address, and then chained with CVE-2020-0938 (another stack corruption) to return to the payload. The exploitation process was straightforward because of the simplicity and reliability of the primitive. The bug was not exploited on Windows 10.

The Next Steps

Variant analysis

Areas/approach for variant analysis (and why):

Follow any of the methodologies listed above: run extensive fuzzing of the Type 1 PostScript parser, perform a code audit of the various object handlers present in the font driver, or analyze the binary diff between Microsoft's and Adobe's forks of the code to spot any discrepancies.

Found variants: N/A

Structural improvements

The most important mitigations existing today are the win32k lockdown policy (starting with Windows 8) and moving the font driver to a restricted user-mode process (since Windows 10). Potential ideas for the future include disabling some of the most legacy parts of the font driver (such as Multiple Masters), or hiding them behind a system-wide flag.

0-day detection methods

The usage of any PostScript objects related to the Multiple Master extension in Type1/OpenType fonts. This is a long deprecated feature that has likely no active users today beyond security researchers.

Other References