Mac IPC
Mach IPC It enables tasks (processes) to exchange information through ports asynchronously. Main components:
-
Ports: Kernel-managed communication channels, similar to pipes.
-
Port Rights: Permissions that control how processes can interact with ports (via handles).
-
Messages: Structured data units exchanged between ports.
-
Service: A named port registered with the bootstrap server.
-
Bootstrap Server: A service (typically launchd) responsible for service registration and discovery.
// Process A (Server/ Task with recieve right, attach send right to port for bootstrap)
mach_port_t server_port;
// Create port with RECEIVE right
kern_return_t kr = mach_port_allocate(
mach_task_self(), // our task
MACH_PORT_RIGHT_RECEIVE, // want receive right
&server_port // port name to create
);
// Create SEND right
mach_port_insert_right(
mach_task_self(), // our task
server_port, // port
server_port, // same port
MACH_MSG_TYPE_MAKE_SEND // convert to send right
);
// Register with launchd
bootstrap_check_in(
bootstrap_port, // launchd port
"com.example.service", // service name
server_port // port with send right
);
// Process B (Client/ Task that obtains send right from bootstrap)
mach_port_t client_port;
bootstrap_look_up(
bootstrap_port, // launchd port
"com.example.service", // service name
&client_port // receives send right
);
MAC Message Header
typedef struct {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
A brief description of these fields is the following:
- msgh_bits - options and message metadata, such as disposition of port rights in the message
- msgh_size - total message size, including header
- msgh_remote_port - remote Mach port, used as the destination when sending a message, or a reply port when receiving
- msgh_local_port - local Mach port, the port the message was received on, or a reply port when sending a message
- msgh_voucher_port - port identifying a Mach Voucher, that’s an optional field
- msgh_id - user defined message identifier
📦 What is a Port Disposition? A port disposition defines how the port right is passed or interpreted in the message.
Examples of dispositions include:
Disposition | Meaning |
---|---|
MACH_MSG_TYPE_MOVE_SEND | Move the send right to the receiver (sender loses it) |
MACH_MSG_TYPE_COPY_SEND | Copy the send right to the receiver (sender keeps it too) |
MACH_MSG_TYPE_MOVE_RECEIVE | Move the receive right to the receiver |
MACH_MSG_TYPE_MAKE_SEND | Give a new send right based on a receive right the sender holds |
Bidirectional Message
get service port
mach_port_t port;
if (bootstrap_look_up(bootstrapPort, <service name>", &port) !=
KERN_SUCCESS) {
return EXIT_FAILURE;
}
Create Local port (with receive right)
mach_port_t replyPort;
if (mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &replyPort) !=
KERN_SUCCESS) {
return EXIT_FAILURE;
}
Insert send right
if (mach_port_insert_right(
task, replyPort, replyPort, MACH_MSG_TYPE_MAKE_SEND) !=
KERN_SUCCESS) {
return EXIT_FAILURE;
}
Prepare Message to send
Message message = {0};
message.header.msgh_remote_port = port;
message.header.msgh_local_port = replyPort;
Create Port Disposition
how the port rights are transferred
message.header.msgh_bits = MACH_MSGH_BITS_SET(
/* remote */ MACH_MSG_TYPE_COPY_SEND,
/* local */ MACH_MSG_TYPE_MAKE_SEND_ONCE,
/* voucher */ 0,
/* other */ 0);
MACH_MSG_TYPE_COPY_SEND tells the kernel:
“Use this send right to deliver the message, but do not remove it from my process.”
MACH_MSG_TYPE_MAKE_SEND_ONCE tells the kernel:
“allow the receiver to send only one reply message, after which the right is destroyed”.
Complex Message
complex Mach message:
typedef struct {
mach_msg_header_t header;
mach_msg_size_t msgh_descriptor_count;
mach_msg_port_descriptor_t descriptor;
} PortMessage;
port descriptors
Unlike msgh_remote_port and msgh_local_port, which are part of the message header, port descriptors live inside the body of a message — and they allow you to send additional ports along with the message.
typedef struct{
mach_port_t name;
mach_msg_size_t pad1;
unsigned int pad2 : 16;
mach_msg_type_name_t disposition : 8;
mach_msg_descriptor_type_t type : 8;
} mach_msg_port_descriptor_t;
typedef struct {
mach_msg_type_descriptor_t type;
mach_port_t name;
mach_msg_type_name_t disposition;
} mach_msg_port_descriptor_t;
OOL Messages
typedef struct {
mach_msg_header_t header;
mach_msg_size_t msgh_descriptor_count;
mach_msg_ool_descriptor_t descriptor;
} OOLMachMessage;
typedef struct{
void* address;
mach_msg_size_t size;
boolean_t deallocate: 8;
mach_msg_copy_options_t copy: 8;
unsigned int pad1: 8;
mach_msg_descriptor_type_t type: 8;
} mach_msg_ool_descriptor_t;
reference:
-
https://karol-mazurek.medium.com/mach-ipc-security-on-macos-63ee350cb59b
- https://ulexec.github.io/post/2022-12-01-xnu_ipc/
- https://dmcyk.xyz/post/xnu_ipc_ii_message_apis/xnu_ipc_ii_message_apis/