Detailed Analysis of macOS/iOS Vulnerability CVE-2019-6231 - Security Boulevard

Detailed Analysis of macOS/iOS Vulnerability CVE-2019-6231

FortiGuard Labs Threat Analysis    


The QuartzCore Image Handling Integer Overflow Vulnerability in CA::Render::Image::decode()

On Jan 22, 2019, Apple released macOS Mojave 10.14.3 and iOS 12.1.3. These two updates fixed a number of security vulnerabilities, including CVE-2019-6231 found in QuartzCore (aka. CoreAnimation). (For more details on the Apple updates, please refer to: and

I found this issue in macOS Mojave 10.14.2 on Dec 14, 2018 and reported it to Apple on Dec 21, 2018. However, Apple responded that said this issue had been fixed in the macOS Mojave 10.14.3 beta that was released on Dec 19, 2018. In this blog I will provide a detailed analysis of this issue on macOS.

A Quick Look

QuartzCore, also known as CoreAnimation, is a framework used by macOS and iOS to create animatable scene graphics. CoreAnimation uses a unique rendering model where the graphics operations are run in a separate process. On macOS, the process is WindowServer. On iOS, the process is backboard.

The service named in QuartzCore is usually referenced as CARenderServer. This service exists in both macOS and iOS, and can be accessed from the Safari Sandbox. There also exists an integer overflow when QuartzCore handles image objects in the function CA::Render::Image::decode(). This could allow a malicious application to be able to read restricted memory.

The following is the crash log of the process WindowServer when this issue is triggered.

As can be seen, the crash occured in the thread “”. The mach service “” is implemented in /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore.  In the function CA::Render::Server::register_name(CA::Render::Server *this, const char *a2) , it is able to register the service “”.

The server thread is implemented in the function CA::Render::Server::server_thread. It’s used to receive the mach messages from clients and then handle these messages. When the thread received a mach message with msgh_id 40002 or 40003, this could invoke the function CA::Render::Server::ReceivedMessage::run_command_stream(CA::Render::Server::ReceivedMessage *this) to handle the command stream. 

This vulnerability exists right in the process of handling the command stream in the function CA::Render::Server::ReceivedMessage::run_command_stream

Proof of Concept

In this next section I will demonstrate a PoC to trigger this issue. The PoC is shown below.

The comparation between the original mach message and the crafted mach message is shown below.

Through binary diff, we only need to modify one byte at offset 0x142 from 0x00 to 0x80 in order to trigger this vulnerability.

As shown in the PoC’s C code, in order to send a crafted mach message to trigger this issue, we first need to send a mach message with msgh_id 40202 (the corresponding handler in the server is _XRegisterClient) to retrieve the connection id for every new connected client.

Once we obtain the value of the connection id, we set this value to the corresponding offset(0x2C) in a crafted mach message. Finally, we send this message to trigger the vulnerability.

Analysis and Root of Cause


In this section, I will dynamically debug this vulnerability with LLDB and figure out the root cause. Note that you need to debug the WindowServer process via SSH mode.

Based on the stack backtrace of the crashed thread from the crash log, we could set a conditional breakpoint at the function CA::Render::Server::ReceivedMessage::run_command_stream using the following commands.

The value of conn_id can be obtained through setting a breakpoint at line 112 in the PoC’s C code.

After this breakpoint is hit, we can read the buffer data of the crafted mach message I sent. The register r13 points to the crafted mach message.

The function CA::Render::Decoder::decode_object(CA::Render::Decoder *this, CA::Render::Decoder *a2) is used to decode all kinds of object data. The buffer data starting at offset 0x70000cc51d6e is a Layer object (marked in green).

The following code branch is used to parse the Layer object data. 

Let’s take a look at how this Layer object is handled. The following list explains what each field in the Layer object means.

The implementation of the function CA::Render::Layer::Layer(CA::Render::Layer *this, CA::Render::Decoder *a2) is shown below.

We can see that the next data still represents an object. Next, let’s continue to trace how the next data is handled.

As shown in Figure 8, the next data still represents an object. The first byte in this object indicates the type of object. The byte 0x16 indicates that this object is an Image object, as follows.

Next, let’s look at how the function CA::Render::Image::decode() decodes an Image object.

The following list explains what each field in the Image object means.

We can see that the 8 bytes (00 03 00 00 00 00 00 80) of data is decoded as the size_t type, and its value is set with an abnormal one.

In Figure 10, the variable v9 is equal to 0x8000000000000300, which is passed as an argument to the function CA::Render::validate_rowbytes.

Now let’s take a closer look at how the function CA::Render::validate_rowbytes handles this value.

It’s easy to confirm that the arithmetic operation a2 * *(_QWORD *)(a3 + 8LL * v4) exists as an integer overflow. At that point, the variable a2 is equal to 0x24 and can be obtained by invoking CA::Render::Decoder::decode_int32(), as shown in Figure 11. So the value of variable v6 is equal to 0 due to an integer overflow. This function could then return 0, causing the change in the next program execution flow. Normally, it should return 1.  Let’s go back to Figure 10 to look at the change of execution flow.

Because the function CA::Render::validate_rowbytes returns 0 due to an integer overflow, it could later go to LABEL_31. It could then invoke the function CA::Render::Texture::decode() to decode the next buffer data. The following is the implementation of the function CA::Render::Texture::decode.

It could  then invoke the function CA::Render::Decoder::decode_colorspace to decode the color space data.

Let’s take a closer look at this function. It first decodes an integer with int8 type. The result is 0x01. It could then execute the case 1 branch. The value of variable v3 is equal to 0xFE. It can then invoke the function CAGetColorSpace to obtain the color space data. 

The index value is equal to 0xfe, which is actually larger than the maximum index of the array colorspaces, enabling the restricted memory data to be read.

The address of restricted memory to be read is equal to 0x291EE0(0x2916F0+0xFE*8).

So the returned value of the function CAGetColorSpace is equal to 0x8000000010. Obviously, this is an invalid memory address. When this address is passed as an argument to the function CFRetain, it can cause an EXC_BAD_ACCESS exception.


We have now finished the detailed analysis of this vulnerability. While this vulnerability affects both macOS and iOS, in this blog, I only demonstrated and analyzed it in macOS.

Affected Versions

macOS Sierra 10.12.6, macOS High Sierra 10.13.6, macOS Mojave 10.14.2

iPhone 5s and later, iPad Air and later, and iPod touch 6th generation



Learn more about FortiGuard Labs and the FortiGuard Security Services portfolioSign up for our weekly FortiGuard Threat Brief. 

Know your vulnerabilities – get the facts about your network security. A Fortinet Cyber Threat Assessment can help you better understand: Security and Threat Prevention, User Productivity, and Network Utilization and Performance.

Read about the FortiGuard Security Rating Service, which provides security audits and best practices.

*** This is a Security Bloggers Network syndicated blog from Fortinet All Blogs authored by Fortinet All Blogs. Read the original post at: