Half a year ago, I found a vulnerability in libFontParser.dylib, which is a part of CoreGraphics library that is widely used in macOS, iOS, iPadOS to parse and render fonts. This vulnerability was patched in iOS 13.5.1 & macOS 10.15.5. In this writeup, I will describe the bug in detail in hopes that it will help others to better understand this vulnerability. This issue could allow an attacker to execute code during the parsing of a malicious font.

The Root Cause

OpenType Font has two types of opcodes based on the design of a font: CFF, CFF2. CFF (Compact Font Format) uses the glyph opcode of PostScript Type 1 of Adobe. CFF2 is an extension of CFF which allows efficient storage of glyph outlines and metadata.

The vulnerability exists in TType1FontType2CharStringHandler::InterpreteCharString while interpreting the blend opcode of CFF2 Charstring.

Take a look at the disassembly code below:

  • n_blend is the last value on the stack that is popped off

  • k is a number also controlled by the attacker

  • nargs_left : nargs - 1 (exclude n_blend was popped from stack)

According to OTF - CFF2 Charstring document, blend argument is calculated by this formula:

n_blend * k + n_blend

But as we seen the disassembly code above, argument of blend opcode only is checked with v2 = n_blend * k. After that, the blend opcode tries to get the pointer of the argument buffer on Stack VM at buffer = (signed __int64)&stack[-v103] which leads to out-of-bounds access.

Attackers can create an OTF font which contains some opcodes like this to make libFontParser.dylib crash:

Let’s generate a PDF file with this corrupted OTF font and open with Preview. We can see that Preview immediately crashed due to out-of-bounds write:

This flaw allows an attacker to control the return value (eax) of ItemVariationStore::ValueFromBlendRequest.

Following the document of CFF2 table, we can see that the blend opcode uses an array of VarRegionList which is a sub-table of CFF2 and blend arguments to calculate the value return by ItemVariationStore::ValueFromBlendRequest.

Using fonttools, we can dump this table as shown in the image below:

Let’s set a breakpoint at libFontParser.dylib:0x01ED2B and libFontParser.dylib:0x1EE67 and run the payload with modified VarRegionList.

As we can see here, register RAX now points to CharString Stack address, RSI is the number of blend opcode arguments.

We can dump this address by using command x/40g $rax-$rsi

After instruction sub rax,rsi executed, CharString Stack Buffer now points to other TCharString members. Let’s continue again. We can see that RAX now has a value as we expected.

This vulnerability allows us to write 4 bytes each time. We can manipulate it by changing blend arguments and VarRegionList to get suitable values each time to manipulate other pointers to achieve further exploitation steps.

Conclusion

Font engine vulnerability still exists in some environments: Windows, Linux and macOS. I highly encourage readers to try and write a full exploit for this vulnerability. This vulnerability was patched on macOS 10.15.5 and iOS 13.5.1. We highly recommend users to update to the latest version of macOS and iOS.

Further Resources