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): Liubenjin and Zhiyi Zhang from Codesafe Team of Legendsec at Qi'anxin Group, 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: Non-continuous stack buffer out-of-bounds write

Vulnerability details:

The issue was found in the processing of the BlendDesignPositions PostScript font object, defined in the Adobe Font Metrics File Format Specification document from 1998. There is a corresponding SetBlendDesignPositions 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 SetBlendDesignPositions(void *arg) {
  int num_master;
  Fixed16_16 values[16][15];

  for (num_master = 0; ; num_master++) {
    if (GetToken() != TOKEN_OPEN) {
      break;
    }

    int values_read = GetOpenFixedArray(&values[num_master], 15);
    SetNumAxes(values_read);
  }

  SetNumMasters(num_master);

  for (int i = 0; i < num_master; i++) {
    procs->BlendDesignPositions(i, &values[i]);
  }


  return 0;
}

There is a trivial stack corruption vulnerability in the above code. In the first for() loop, no upper bound is enforced on the num_master iterator, so it is possible to load data into the arrays at &values[0], &values[1], ..., and then out-of-bounds at &values[16], &values[17] and so on. Importantly, the GetOpenFixedArray function reads between 0 and 15 fixed point 32-bit values depending on the input file, so the attacker may choose to write little or no data at specific offsets. This creates a powerful non-continuous stack corruption primitive, which makes it possible to redirect execution to a specific address or build a whole ROP chain directly on the stack. For example, the SetBlendDesignPositions function itself is compiled with a /GS cookie, but it is possible to overwrite another return address higher up the call chain to hijack the control flow.

To trigger the bug, it is sufficient to load a Type 1 font that includes a specially crafted BlendDesignPositions object, 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
/BlendDesignPositions [[][][][][][][][][][][][][][][][][][][][][][][0 0 0 0 16705.25490 -0.00001]]
/Private begin
/Blend begin
/end
end
currentdict end
%currentfile eexec /Private begin
/CharStrings 1 begin
/.notdef ## -| { endchar } |-
end
end
mark %currentfile closefile
cleartomark

On an unpatched Windows 8.1 x64 machine, this overwrites one of the return values on the stack with 0xffffffff41414141, represented as "16705.25490 -0.00001".

Patch analysis: N/A

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

The bug was trivial in nature and could have been found in a variety of ways:

  1. Dumb fuzzing: The BlendDesignPositions object was present in legitimate Type 1 Multiple Master fonts in the past. A crash could have been found by fuzzing such existing fonts with relatively simple mutation algorithms.
  2. Semi-smart fuzzing: It is also possible that a PostScript font generator was used with object names scraped from atmfd.dll or fontdrvhost.exe. Alternatively, a hybrid approach could have been employed to insert new objects into existing fonts, or truncate/extend/modify objects while still adhering to the PostScript syntax rules.
  3. 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.
  4. 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 2) and 3) 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.

The Exploit

Is the exploit method known? Yes

Exploit method:

On Windows 8.1 and earlier versions, the vulnerability was chained with CVE-2020-1020 (a write-what-where condition) to first set up a second stage payload in RWX kernel memory at a known address, and then jump to it through this bug. The exploitation process was straightforward because of the simplicity of the issue and high degree of control over the kernel stack. 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