c00000fd
c00000fd

Reputation: 22255

Example of IPC communication between a daemon running as root and another non-root process using Mach messages for macOS

I'm trying to understand the low-level mechanism of IPC using Mach messages between a launch daemon (running as root) and another process, running in a user-content.

Assuming the following data structs:

struct MACH_MSG_BASE
{
    mach_msg_header_t hdr;
    mach_msg_body_t body;
};


struct MACH_MSG_UINT32
{
    MACH_MSG_BASE base;
    
    unsigned int val;      //Sending this value as a test
};

So I'm running the following code in the daemon:

//Server:

//No error checks for brevity
mach_port_t port = MACH_PORT_NULL;
mach_port_t task = mach_task_self();

mach_port_allocate(task,
                   MACH_PORT_RIGHT_RECEIVE,
                   &port);

mach_port_insert_right(task,
                       port,
                       port,
                       MACH_MSG_TYPE_MAKE_SEND);

MACH_MSG_UINT32 msg = {};
msg.base.hdr.msgh_local_port = port;
msg.base.hdr.msgh_size = sizeof(msg.base);
        
mach_msg(&msg.base.hdr,
         MACH_RCV_MSG,
         0,
         sizeof(msg),
         port,
         MACH_MSG_TIMEOUT_NONE,
         MACH_PORT_NULL);

When I run the code above, it goes into a waiting mode at the mach_msg call, as I would expect.

But then, the first issue - how do you get the port number of the daemon from another process? I'm assuming using task_for_pid, as such:

//Client(s):

//No error checks for brevity
mach_port_t port = MACH_PORT_NULL;
mach_port_t task;
task_for_pid(mach_task_self(), server_pid, &task);  //I guess we get server_pid by daemon process name?

mach_port_allocate(task,
                   MACH_PORT_RIGHT_RECEIVE,
                   &port);

MACH_MSG_UINT32 msg = {};
    
msg.base.hdr.msgh_remote_port = _port;
msg.base.hdr.msgh_local_port = MACH_PORT_NULL;
msg.base.hdr.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MAKE_SEND);
msg.base.hdr.msgh_size = sizeof(msg.base);
    
msg.val = 0x12345678;
    
mach_msg(&msg.base.hdr,
         MACH_SEND_MSG,
         sizeof(msg),
         0,
         MACH_PORT_NULL,
         MACH_MSG_TIMEOUT_NONE,
         MACH_PORT_NULL);

But when I run the code above, the mach_msg returns 0x10000003 or MACH_SEND_INVALID_DEST.

What am I doing wrong?

Upvotes: 0

Views: 264

Answers (1)

pmdj
pmdj

Reputation: 23428

task_for_pid almost never works these days, Apple has significantly restricted it for security reasons.

The way to find a Mach server is for it to register a port name, and for clients to look up the port in either the privileged or user's namespace. For a launch daemon or agent, you specify the ports it offers in the MachServices section of the launchd plist:

<key>MachServices</key>
<dict>
    <key>com.example.mydaemon.MyMachService1</key>
    <true/>
    <key>com.example.mydaemon.MyMachService2</key>
    <true/>
</dict>

The client would then look up these ports to obtain a send right using the bootstrap API:

kern_return_t kr = bootstrap_look_up(bootstrap_port, "com.example.mydaemon.MyMachService1", &service_port);

All of that said, working with low level Mach ports is extremely cumbersome. I strongly recommend using XPC instead unless you have some legacy or system level Mach service you need to interface with. With XPC you still register the MachServices in the launchd plist, the code side is a lot easier to work with though. As you're using C++, your starting point would be the xpc_connection_create_mach_service() function.

Upvotes: 2

Related Questions