We discovered a double free vulnerability (assigned as CVE-2019-8635) in macOS. The vulnerability is caused by a memory corruption flaw in the AMD component. If successfully exploited, an attacker can implement privilege escalation and execute malicious code on the system with root privileges. We disclosed our findings to Apple, which has since released a patch.
The CVE ID covers two flaws, namely in the discard_StretchTex2Tex method and processing of sideband tokens in an AMD Radeon class called AMDRadeonX400_AMDSIGLContext. It is the derived class of IOAccelGLContext2, which extends from IOAccelContext2 class. These classes are used to render graphics on macOS machines.
The vulnerability here occurs in the discard_StretchTex2Tex and AMDSIGLContext::process_StretchTex2Tex functions of the AMDRadeonX4000_AMDSIGLContext class, which can be accessed using the AMDRadeonX4000_AMDSIGLContext userclient with selector 2 function IOAccelContext2::submit_data_buffers, while the AMDRadeonX4000_AMDGraphicsAccelerator client can be opened with connect type 1.
AMDRadeonX4000_AMDSIGLContext discard_StretchTex2Tex Double Free Privilege Escalation Vulnerability
This vulnerability allows local attackers to execute code on userspace. To exploit this vulnerability, an attacker must first obtain the ability to execute low-privileged code on the target macOS system.
The flaw results from the lack of proper validation of user-supplied data, which in turn results in a read past the end of an allocated data structure. An attacker can take advantage of this, together with other vulnerabilities, to escalate privileges to the kernel level.
AMDRadeonX4000_AMDSIGLContext Double Free Privilege Escalation Vulnerability
A double free vulnerability also exists in the processing of sideband tokens in the same AMD class. The vulnerability allows local attacks to execute arbitrary code on affected installations of Apple macOS. As with the aforementioned flaw, an attacker must first obtain the ability to execute low-privileged code on the target system to take advantage of this one.
While the previously mentioned flaw occurs in the AMDRadeonX4000_AMDSIGLContext: discard_StretchTex2Tex function, this one is in the AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex function. It results from the lack of validation in the existence of an object prior to performing operations on the said object. An attacker can take advantage of this flaw to escalate privileges to the kernel level.
Essentially, the two flaws are similar in terms of possible exploitation, the difference is in the function taken advantage of.
Root Cause Analysis
Figure 1. The pseudo code snippet of AMDRadeonX4000_AMDSIGLContext: discard_StretchTex2Tex function (top), the pseudo code snippet of AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex function (bottom)
Figure 1 (top) shows that, if (cmdinfo+32) is equal to 0x8c00, the IOAccelResource v10 and v11 are both taken from IOAccelShared2, with *(shareMem_start_address_187_offset16+8) and *(shareMem_start_address_187_offset16+12) values as indexes. This function will then release two accelerator resources using the IOAccelResource2::clientRelease() function. However, these two indexes can be directly controlled from the userspace through map memory, with the IOAccelContext2 userclient. If userspace maps the same index for lookupResource function, clientRelease will release the same resource client twice, which is when the double free vulnerability occurs.
Figure 1 (bottom) shows that, if v15 is equal to 0x8c00, the accelResource_offset8 and accelResource_offset12 are both taken from IOAccelShared2, with shared memory offset 24 and 28 values as indexes. Finally, this function will release accelResource_offset12 from IOAccelShared2 _rst, and if accelResource_offset8->member2 is not equal to 10, this function will also release the accelResource_offset8 from IOAccelShared2. However, setting the shared memory offset 24 and 28 to the same value releases the same accelResource twice.
In process_StretchTex2Tex() function, completing the stretch operation will release two resource clients using the IOAccelResource2::clientRelease() function. However, these two accelResource2 are taken from accelShare2 shared memory within the AMDRadeonX4000_AMDSIGLContext class, using the IOAccelShared2::lookupResource function with each index. These indexes can be controlled from userspace by map memory with the IOAccelContext2 userclient. If userspace maps the same index for the lookupResource function, clientRelease will release the same resource client twice, which is when the double free vulnerability occurs.
The pseudo code snippets for both flaws show that the shared memory address points to command stream info offset 24. However, the command stream info buffer is actually set in the IOAccelContext2::processSidebandBuffer function, as shown in the next figure. In Figure 2, v5 points to shareMem offset 16, and this->member196 points to the commandStreamInfo offset 24.
Figure 2. The pseudo code snippet of IOAccelContext2::processSidebandBuffer
Figure 3 is the pseudo code snippet of the IOAccelContext2::clientMemoryForType function. The function is called by known API "IOConnectMapMemory64", which can map a userspace buffer to kernel space. When using the IOConnectMapMemory64 function, one should set the connect object, memory type, and other arguments. Here, connect object is the instance of IOAccelContext2, with memory type as 0, as shown in Figure 3. When we set the memory type to 0, the clientMemoryForType function will create a buffer memory descriptor and return the start address to userspace. Furthermore, it will also set the buffer memory address to the "shareMem_start_vm_address_187" var, which we named. This var is the exact value used in the IOAccelContext2::processSidebandBuffer function.
From there, the shared buffer can be controlled and the two resource indexes can be set similarly, which then triggers the double free bug.
Figure 3. The pseudo code snippet of the IOAccelContext2::clientMemoryForType function
The following provides the backtrace information in the application’s crash log, where only “AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex” and the function offset "process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893" will differ if the discard_StretchTex2Tex function is used.
* thread #1, stop reason = signal SIGSTOP frame #0: 0xffffff7f8d7adc37 IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13 frame #1: 0xffffff7f8d880dad AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::process_StretchTex2Tex(IOAccelCommandStreamInfo&) + 2893 frame #2: 0xffffff7f8d79b5d5 IOAcceleratorFamily2`IOAccelContext2::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 273 frame #3: 0xffffff7f8d8885e4 AMDRadeonX4000`AMDRadeonX4000_AMDSIGLContext::processSidebandBuffer(IOAccelCommandDescriptor*, bool) + 182 frame #4: 0xffffff7f8d79bae7 IOAcceleratorFamily2`IOAccelContext2::processDataBuffers(unsigned int) + 85 frame #5: 0xffffff7f8d7a2380 IOAcceleratorFamily2`IOAccelGLContext2::processDataBuffers(unsigned int) + 804 frame #6: 0xffffff7f8d798c30 IOAcceleratorFamily2`IOAccelContext2::submit_data_buffers(IOAccelContextSubmitDataBuffersIn*, IOAccelContextSubmitDataBuffersOut*, unsigned long long, unsigned long long*) + 1208 frame #7: 0xffffff800b027a3c kernel.development`::shim_io_connect_method_structureI_structureO(method=, object=, input=, inputCount=, output=, outputCount=0xffffff8742023968) at IOUserClient.cpp:0 [opt] frame #8: 0xffffff800b025ca0 kernel.development`IOUserClient::externalMethod(this=, selector=, args=0xffffff87420239b8, dispatch=0x0000000000000000, target=0x0000000000000000, reference=) at IOUserClient.cpp:5459 [opt] *frame #9: 0xffffff800b02ebff kernel.development`::is_io_connect_method(connection=0xffffff80b094e000, selector=2, scalar_input=, scalar_inputCnt=, inband_input=, inband_inputCnt=136, ool_input=0, ool_input_size=0, inband_output="", inband_outputCnt=0xffffff80b0d81e0c, scalar_output=0xffffff8742023ce0, scalar_outputCnt=0xffffff8742023cdc, ool_output=0, ool_output_size=0xffffff80ab5c7574) at IOUserClient.cpp:3994 [opt] frame #10: 0xffffff7f913044c2 frame #11: 0xffffff800a9bbd64 kernel.development`_Xio_connect_method(InHeadP=, OutHeadP=0xffffff8742023ce0) at device_server.c:8379 [opt] frame #12: 0xffffff800a88d27d kernel.development`ipc_kobject_server(request=0xffffff80ab5c7400, option=) at ipc_kobject.c:359 [opt] frame #13: 0xffffff800a859465 kernel.development`ipc_kmsg_send(kmsg=0xffffff80ab5c7400, option=3, send_timeout=0) at ipc_kmsg.c:1832 [opt] frame #14: 0xffffff800a878a75 kernel.development`mach_msg_overwrite_trap(args=) at mach_msg.c:549 [opt] frame #15: 0xffffff800a9f63a3 kernel.development`mach_call_munger64(state=0xffffff80af471bc0) at bsd_i386.c:573 [opt] frame #16: 0xffffff800a823486 kernel.development`hndl_mach_scall64 + 22
In the event of a Mac system experiences a kernel panic, the panic text is added to a log. A kernel panic is a system error detected by the kernel (as opposed to similar errors detected by user space code) resulting from unhandled processor exceptions in kernel code, such as references to invalid memory addresses, or a bug in the call chain. The panic log, as follows, can be examined:
panic(cpu 6 caller 0xffffff800aa1391c): Kernel trap at 0xffffff7f8d7adc37, type 14=page fault, registers: CR0: 0x0000000080010033, CR2: 0x0000000000000018, CR3: 0x0000000fea85f063, CR4: 0x00000000001626e0 RAX: 0x0000000000000000, RBX: 0xffffff800b473e28, RCX: 0x00000000ffffffff, RDX: 0x0000000000000000 RSP: 0xffffff8742023610, RBP: 0xffffff8742023610, RSI: 0xffffff80b0f8e470, RDI: 0xffffff80afa29300 R8: 0x0000000000000229, R9: 0xffffff800b2c4d00, R10: 0xffffff800b2c2c70, R11: 0x0000000000000058 R12: 0xffffff87299cb9b4, R13: 0x0000000000000001, R14: 0xffffff80b094e608, R15: 0xffffff80b094e000 RFL: 0x0000000000010282, RIP: 0xffffff7f8d7adc37, CS: 0x0000000000000008, SS: 0x0000000000000010 Fault CR2: 0x0000000000000018, Error code: 0x0000000000000002, Fault CPU: 0x6, PL: 0, VF: 0
The following, meanwhile, shows the registers' debug information of the kernel panic. $r12 register points to the shared memory address offset 16. The resource indexes are also both shown as 0x42.
(lldb) register read General Purpose Registers: rax = 0x0000000000000000 rbx = 0xffffff800b473e28 kernel.development`kdebug_enable rcx = 0x00000000ffffffff rdx = 0x0000000000000000 rdi = 0xffffff80afa29300 rsi = 0xffffff80b0f8e470 rbp = 0xffffff8742023610 rsp = 0xffffff8742023610 r8 = 0x0000000000000229 r9 = 0xffffff800b2c4d00 kernel.development`zone_array + 8336 r10 = 0xffffff800b2c2c70 kernel.development`zone_array r11 = 0x0000000000000058 r12 = 0xffffff87299cb9b4 r13 = 0x0000000000000001 r14 = 0xffffff80b094e608 r15 = 0xffffff80b094e000 rip = 0xffffff7f8d7adc37 IOAcceleratorFamily2`IOAccelResource2::clientRelease(IOAccelShared2*) + 13 rflags = 0x0000000000010282 cs = 0x0000000000000008 fs = 0x00000000ffff0000 gs = 0x00000000afa20000 (lldb) x/20g $r12 0xffffff87299cb9b4: 0x00000364001a8c00 0x0000004200000042 0xffffff87299cb9c4: 0x0000104000000101 0x0055550000900002 0xffffff87299cb9d4: 0x0004000800040008 0x1048000000010001 0xffffff87299cb9e4: 0x0055560000900002 0x0002000800020008 0xffffff87299cb9f4: 0x0000000000010001 0x0000000000000000 0xffffff87299cba04: 0x0000000400000004 0x0000000000000000 0xffffff87299cba14: 0x0000000200000002 0x00000364001a8c00 0xffffff87299cba24: 0x0000004200000042 0x0000104800000101 0xffffff87299cba34: 0x0055560000900002 0x0002000800020008 0xffffff87299cba44: 0x1050000000010001 0x0055570000900002
Protecting Mac devices from possible CVE-2019-8635 exploitation
Attackers can use the double free vulnerability to attack unpatched macOS systems and compromise machines with elevated privileges. Apple has provided a fix for the memory corruption issue by improving memory handling. A security update is now available for macOS Mojave 10.14.4, and users are advised to upgrade their OS X as soon as possible. Users can also install solutions such as the Trend Micro Antivirus for Mac and Trend Micro Protection Suites that detect and block attack attempts that use various flaws.