Mach IPC is the backbone of Darwin. All communication between processes, between userspace and the kernel, and between kernel subsystems goes through Mach messages and ports. This is the most important component for kernel exploitation.


Why This Is the Most Important Component

  1. Mach messages are allocated on the kernel heap – used for heap spraying/grooming
  2. Port objects are kernel objects – targets for type confusion, UAF
  3. OOL (out-of-line) descriptors – controlled kernel allocations with arbitrary data
  4. Task ports – anyone with a send right to the kernel task = full kernel r/w
  5. MIG-generated handlers – large attack surface, historically buggy

Mach Ports

Concept

A Mach port is a kernel-managed endpoint for a message queue. Conceptually similar to a file descriptor but more powerful.

// Port in the kernel
struct ipc_port {
    struct ipc_object       ip_object;        // Header (type, refs, lock)
    struct ipc_mqueue       ip_messages;      // Message queue
    natural_t               ip_mscount;       // Make-send count
    natural_t               ip_srights;       // Send rights count
    mach_port_rights_t      ip_sorights;      // Send-once rights count

    union {
        struct ipc_kmsg     *imq_messages;    // Message list
        struct task         *task;            // For IKOT_TASK ports
        struct thread       *thread;          // For IKOT_THREAD ports
        struct semaphore    *semaphore;       // For IKOT_SEMAPHORE
        IOUserClient        *iokit_object;    // For IKOT_IOKIT_CONNECT
    } kdata;

    struct ipc_port         *ip_nsrequest;    // No-senders notification
    struct ipc_port         *ip_pdrequest;    // Port-destroyed notification
    // ...
};

Port Rights

Right Meaning Limit
Receive right Allows receiving messages from the port Only 1 holder in the entire system
Send right Allows sending messages Multiple holders, reference counted
Send-once right Send exactly 1 message then self-destructs Used for reply ports
Process A ─── send right ───→ Port ←── receive right ─── Process B
Process C ─── send right ──╱         (only B receives messages)

Capability model:

  • Having a right = having a capability
  • No other mechanism controls access (not ACL-based)
  • Transferring a right = transferring a capability (via Mach messages)

Port Lifecycle

// 1. Allocate port (get receive right)
mach_port_t port;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);

// 2. Insert send right
mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);

// 3. Send message
mach_msg_header_t msg = {
    .msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0),
    .msgh_size = sizeof(msg),
    .msgh_remote_port = port,  // destination
    .msgh_local_port = MACH_PORT_NULL,
    .msgh_id = 0x1234
};
mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);

// 4. Receive message
mach_msg(&msg, MACH_RCV_MSG, 0, sizeof(msg), port, 0, 0);

// 5. Destroy port
mach_port_destroy(mach_task_self(), port);

Mach Messages

Message Structure

// Simple message (no ports or OOL data)
struct {
    mach_msg_header_t header;    // 24 bytes
    // optional: inline data
    char data[256];
} simple_msg;

// Complex message (contains ports or OOL data)
struct {
    mach_msg_header_t header;
    mach_msg_body_t body;        // descriptor count
    // Descriptors:
    mach_msg_port_descriptor_t port_desc;    // Send port right
    mach_msg_ool_descriptor_t ool_desc;      // OOL memory pointer
    // Trailing data
} complex_msg;
typedef struct {
    mach_msg_bits_t    msgh_bits;        // Type bits (complex, send/receive types)
    mach_msg_size_t    msgh_size;        // Total message size
    mach_port_t        msgh_remote_port; // Destination port
    mach_port_t        msgh_local_port;  // Reply port (optional)
    mach_port_name_t   msgh_voucher_port;// Voucher port
    mach_msg_id_t      msgh_id;          // Message ID (for MIG dispatch)
} mach_msg_header_t;

msgh_bits Encoding

// Remote port type (destination)
MACH_MSG_TYPE_COPY_SEND     // Copy send right
MACH_MSG_TYPE_MOVE_SEND     // Move send right (sender loses it)
MACH_MSG_TYPE_MAKE_SEND     // Make send from receive right
MACH_MSG_TYPE_MOVE_SEND_ONCE // Move send-once right

// Complex bit
MACH_MSGH_BITS_COMPLEX      // Message contains descriptors

// Helper macro
MACH_MSGH_BITS(remote, local)  // Combine remote + local types

Out-of-Line (OOL) Data – Exploitation Primitive

OOL Memory Descriptor

When message data is too large, use OOL: the kernel allocates kernel memory and copies the data in.

mach_msg_ool_descriptor_t ool = {
    .address = user_buffer,     // Userspace buffer (source)
    .count = size,              // Size in bytes
    .deallocate = FALSE,        // Don't free source after copy
    .copy = MACH_MSG_VIRTUAL_COPY, // Copy strategy
    .type = MACH_MSG_OOL_DESCRIPTOR
};

Why this matters for exploitation:

1. Attacker sends an OOL message with controlled data
2. Kernel allocates a buffer on the kernel heap (kalloc zone)
3. Kernel copies the attacker's data into the buffer
4. Message enqueued → buffer stays allocated
5. Attacker has controlled data on the kernel heap!

Used for:
  - Heap spraying: send many OOL messages of the same size → fill the zone
  - Heap grooming: spray → free selected → allocate target object
  - Fake object creation: OOL data = fake kernel struct

OOL Ports Descriptor

Send an array of port rights:

mach_msg_ool_ports_descriptor_t ports_desc = {
    .address = port_array,       // Array of mach_port_t
    .count = num_ports,          // Number of ports
    .deallocate = FALSE,
    .disposition = MACH_MSG_TYPE_COPY_SEND,
    .type = MACH_MSG_OOL_PORTS_DESCRIPTOR
};

Exploitation: Each port right in OOL causes the kernel to allocate an ipc_port pointer entry. Controlling port array content = controlling kernel heap layout.


Task Ports – The Ultimate Goal

Task Port Concept

struct task {
    // ...
    struct ipc_port *itk_self;     // Task's self port
    struct ipc_port *itk_nself;    // Task's notify port
    struct vm_map   *map;          // Address space
    // ...
};
  • task_self() returns a send right to your own task port
  • task_for_pid(target_task, pid, &port) gets a send right to another task

tfp0 (Task-For-Pid 0 = Kernel Task Port)

mach_port_t kernel_task_port;
task_for_pid(mach_task_self(), 0, &kernel_task_port);
// If successful: kernel_task_port = send right to kernel task
// → Use mach_vm_read/mach_vm_write to read/write kernel memory

On modern iOS:

  • task_for_pid(0) is blocked by entitlement checks + sandbox
  • Must create a fake kernel task port after obtaining a kernel r/w primitive
  • Or use mach_vm_* through alternative primitives

Fake Task Port Attack (Classic)

1. Spray IOSurface objects on the kernel heap
2. Trigger vulnerability → kernel write primitive
3. Craft a fake ipc_port structure in controlled memory
4. Overwrite port pointer → point to fake port
5. Fake port's kdata.task → pointer to fake task struct
6. Fake task's map → pointer to kernel_map
7. mach_vm_read(fake_port, kernel_addr, ...) → reads kernel memory!

MIG (Mach Interface Generator)

MIG generates C code for Mach IPC interfaces:

.defs file → MIG compiler → client stubs + server stubs

MIG Definition Example

routine task_threads(
    target_task : task_inspect_t;
    out act_list : thread_act_array_t;
    out act_listCnt : mach_msg_type_number_t
);

MIG generates:

  • Client stub: serialize arguments, mach_msg, deserialize reply
  • Server stub: deserialize message, call implementation, serialize reply

Attack surface:

  • MIG handlers parse complex message structures
  • Historically many bugs in size validation, port handling
  • ipc_kmsg_copyin() handles complex descriptor parsing – bugs here = kernel exploitation

Exploitation Patterns Summary

Pattern Mechanism Example
Heap spray OOL messages of the same size fill a kalloc zone Fill zone before triggering vuln
Heap grooming Spray, free selected, allocate target Control adjacency on heap
Port UAF Free a port but keep a reference Overwrite freed port memory
Fake port Craft a fake ipc_port struct Redirect port operations
Port replacement Replace kernel object via dangling port Type confusion
Message race Race between send and receive TOCTOU on message data

Resources


Exercises

  1. Write a Mach IPC client/server: Create 2 processes that communicate via Mach messages on macOS
  2. Spray the kernel heap: Send 1000 OOL messages of 256 bytes, observe kernel memory usage
  3. Enumerate ports: Write a tool to list all Mach ports of a target process (using mach_port_names)
  4. Send a complex message: Send a message containing a port right + OOL data, receive in another process
  5. Trace a MIG call: Identify the MIG subsystem/routine ID for task_threads, trace the kernel handling path