4

I'm trying to pass a mach port to a child process created with fork on Mac OSX. I saw this SO question Sharing Mach ports with child processes but it doesn't have a solution it just describes the problem. Looking at this site https://robert.sesek.com/2014/1/changes_to_xnu_mach_ipc.html it contains directions on passing mach ports to a child process but their is no example code unfortunately.

I tried implementing the port swap but the child process can't receive the message that was sent by the parent process, mach_msg inside of recv_port fails with invalid name. Below is what I have so far. Sorry for so much code, mach IPC kind of makes it hard to be brief.

So how do I pass a mach port to a child process on Mac OSX now that the bootstrap port hack no longer works?

Edit

I changed the code example to reflect the points that Ken Thomases made in his answer, the child process creates a port with a send right and sends it to the parent. However the parent process can't receive the port created and sent by the child and just hangs on recv_port.

#include <stdio.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <mach/message.h>
#include <unistd.h>

static int32_t
send_port(mach_port_t remote_port, mach_port_t port)
{
    kern_return_t err;

    struct
    {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
    } msg;

    msg.header.msgh_remote_port = remote_port;
    msg.header.msgh_local_port = MACH_PORT_NULL;
    msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) |
        MACH_MSGH_BITS_COMPLEX;
    msg.header.msgh_size = sizeof msg;

    msg.body.msgh_descriptor_count = 1;
    msg.task_port.name = port;
    msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND;
    msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR;

    err = mach_msg_send(&msg.header);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't send mach msg\n", err);
        return (-1);
    }

    return (0);
}

static int32_t
recv_port(mach_port_t recv_port, mach_port_t *port)
{
    kern_return_t err;
    struct
    {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
        mach_msg_trailer_t         trailer;
    } msg;

    err = mach_msg(&msg.header, MACH_RCV_MSG,
                    0, sizeof msg, recv_port,
                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't recieve mach message\n", err);
        return (-1);
    }

    *port = msg.task_port.name;
    return 0;
}

static int32_t
setup_recv_port(mach_port_t *recv_port)
{
    kern_return_t       err;
    mach_port_t         port = MACH_PORT_NULL;
    err = mach_port_allocate(mach_task_self (),
                              MACH_PORT_RIGHT_RECEIVE, &port);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't allocate mach port\n", err);
        return (-1);
    }

    err = mach_port_insert_right(mach_task_self (),
                                  port,
                                  port,
                                  MACH_MSG_TYPE_MAKE_SEND);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't insert port right\n", err);
        return (-1);
    }

    (*recv_port) = port;
    return (0);
}

pid_t
fork_pass_port(mach_port_t pass_port, int32_t (*child_start)(mach_port_t port, void *arg), void *arg)
{
    pid_t pid = 0;
    int32_t rtrn = 0;
    kern_return_t err;
    mach_port_t special_port = MACH_PORT_NULL;

    /* Setup the mach port. */
    if(setup_recv_port(&pass_port) != 0)
    {
        printf("Can't setup mach port\n");
        return (-1);
    }

    /* Grab our current task's(process's) HOST_NAME special port. */
    err = task_get_special_port(mach_task_self(), TASK_HOST_PORT, &special_port);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't get special port:\n", err);
        return (-1);
    }

    /* Set the HOST_NAME special port as the parent recv port.  */
    err = task_set_special_port(mach_task_self(), TASK_HOST_PORT, pass_port);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't set special port:\n", err);
        return (-1);
    }

    pid = fork();
    if(pid == 0)
    {
        mach_port_t host_port = MACH_PORT_NULL;
        mach_port_t port = MACH_PORT_NULL;

        /* In the child process grab the port passed by the parent. */
        err = task_get_special_port(mach_task_self(), TASK_HOST_PORT, &pass_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't get special port:\n", err);
            return (-1);
        }

        /* Create a port with a send right. */
        if(setup_recv_port(&port) != 0)
        {
            printf("Can't setup mach port\n");
            return (-1);
        }
        
        /* Send port to parent. */
        rtrn = send_port(pass_port, port);
        if(rtrn < 0)
        {
            printf("Can't send port\n");
            return (-1);
        }

        /* Now that were done passing the mach port, start the function passed by the caller. */
        child_start(pass_port, arg);
        
        /* Exit and clean up the child process. */
        _exit(0);
    }
    else if(pid > 0)
    {
        mach_port_t child_port = MACH_PORT_NULL;

        rtrn = recv_port(pass_port, &child_port);
        if(rtrn < 0)
        {
            printf("Can't recv port\n");
            return (-1);
        }

        /* Reset parents special port. */
        err = task_set_special_port(mach_task_self(), TASK_HOST_PORT, special_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't set special port:\n", err);
            return (-1);
        }

        return (0);
    }
    else
    {
        /* Error, so cleanup the mach port. */
        err = mach_port_deallocate(mach_task_self(), pass_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't deallocate mach port\n", err);
            return (-1);
        }
    
        perror("fork");

        return (-1);
    }
}

static int32_t start(mach_port_t port, void *arg)
{
    printf("Started\n");

    return (0);
}

int main(void)
{
    char *arg = "argument";
    mach_port_t port = MACH_PORT_NULL;

    pid_t pid = fork_pass_port(port, start, arg);
    if(pid < 0)
    {
        printf("Can't fork and pass msg port\n");
        return (-1);
    }

    return (0);
}
Community
  • 1
  • 1
2trill2spill
  • 1,333
  • 2
  • 20
  • 41
  • Is this Q&A any help? http://stackoverflow.com/questions/8481138/how-to-use-sendmsg-to-send-a-file-descriptor-via-sockets-between-2-processes – John Hascall Jan 19 '16 at 00:41
  • @John Hascall Unfortunately it doesn't, I need to use mach OOL messaging between processes. – 2trill2spill Jan 19 '16 at 00:43
  • I think the problem is inherent in trying to use the special host port for inheritance. Either the child isn't properly inheriting the parent's host port or the kernel is somehow interfering with using that to send a message. If I use your code to send a message (including a port right) within a single process, it works fine. – Ken Thomases Feb 16 '16 at 03:08
  • Shouldn't the send port call in the child throw an error message if the inherited port was invalid? – 2trill2spill Feb 16 '16 at 03:16
  • The child inherits a send right to **some** port as the host port. It's allowed to send to that. It may not be the port the parent set, though. Somewhere, there's a host service that got (and probably ignored) a message in an unexpected format. – Ken Thomases Feb 16 '16 at 04:53
  • So if I get this problem correctly, the program forks and the child needs to send the parent some information. If that's all, why not create a pipe before you fork and simply read/write the information through the pipe? – Bing Bang Feb 19 '16 at 17:26
  • The max size for pipes is too low and OOL mach messages are much more efficient at flinging large buffers around. – 2trill2spill Feb 19 '16 at 20:20
  • @2trill2spill You could still use a pipe just for the initial exchange, couldn't you? – Bing Bang Feb 22 '16 at 18:06

2 Answers2

4

I figured out how to pass mach ports via special ports inheritance. You have to temporarily replace TASK_BOOTSTRAP_PORT with the port you want to pass before calling fork. The other special ports fail in one way or another. Below is an example of the "port swap dance".

Note, this code was only tested on OSX 10.11.3 and may not work on previous or future versions of OSX.

#include <stdio.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <mach/message.h>
#include <unistd.h>

#define SPECIAL_PORT TASK_BOOTSTRAP_PORT

static int32_t
send_port(mach_port_t remote_port, mach_port_t port)
{
    kern_return_t err;

    struct
    {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
    } msg;

    msg.header.msgh_remote_port = remote_port;
    msg.header.msgh_local_port = MACH_PORT_NULL;
    msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) |
        MACH_MSGH_BITS_COMPLEX;
    msg.header.msgh_size = sizeof msg;

    msg.body.msgh_descriptor_count = 1;
    msg.task_port.name = port;
    msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND;
    msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR;

    err = mach_msg_send(&msg.header);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't send mach msg\n", err);
        return (-1);
    }

    return (0);
}

static int32_t
recv_port(mach_port_t recv_port, mach_port_t *port)
{
    kern_return_t err;
    struct
    {
        mach_msg_header_t          header;
        mach_msg_body_t            body;
        mach_msg_port_descriptor_t task_port;
        mach_msg_trailer_t         trailer;
    } msg;

    err = mach_msg(&msg.header, MACH_RCV_MSG,
                    0, sizeof msg, recv_port,
                    MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't recieve mach message\n", err);
        return (-1);
    }

    (*port) = msg.task_port.name;
    return 0;
}

static int32_t
setup_recv_port(mach_port_t *recv_port)
{
    kern_return_t       err;
    mach_port_t         port = MACH_PORT_NULL;
    err = mach_port_allocate(mach_task_self (),
                              MACH_PORT_RIGHT_RECEIVE, &port);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't allocate mach port\n", err);
        return (-1);
    }

    err = mach_port_insert_right(mach_task_self (),
                                  port,
                                  port,
                                  MACH_MSG_TYPE_MAKE_SEND);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't insert port right\n", err);
        return (-1);
    }

    (*recv_port) = port;
    return (0);
}

static int32_t
start(mach_port_t port, void *arg)
{

    return (0);
}

static pid_t
fork_pass_port(mach_port_t *pass_port,
               int32_t (*child_start)(mach_port_t port, void *arg),
               void *arg)
{
    pid_t pid = 0;
    int32_t rtrn = 0;
    kern_return_t err;
    mach_port_t special_port = MACH_PORT_NULL;

    /* Allocate the mach port. */
    if(setup_recv_port(pass_port) != 0)
    {
        printf("Can't setup mach port\n");
        return (-1);
    }

    /* Grab our current process's bootstrap port. */
    err = task_get_special_port(mach_task_self(), SPECIAL_PORT, &special_port);
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't get special port:\n", err);
        return (-1);
    }

    /* Set the special port as the parent recv port.  */
    err = task_set_special_port(mach_task_self(), SPECIAL_PORT, (*pass_port));
    if(err != KERN_SUCCESS)
    {
        mach_error("Can't set special port:\n", err);
        return (-1);
    }

    pid = fork();
    if(pid == 0)
    {
        mach_port_t bootstrap_port = MACH_PORT_NULL;
        mach_port_t port = MACH_PORT_NULL;

        /* In the child process grab the port passed by the parent. */
        err = task_get_special_port(mach_task_self(), SPECIAL_PORT, pass_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't get special port:\n", err);
            return (-1);
        }

        /* Create a port with a send right. */
        if(setup_recv_port(&port) != 0)
        {
            printf("Can't setup mach port\n");
            return (-1);
        }

        /* Send port to parent. */
        rtrn = send_port((*pass_port), port);
        if(rtrn < 0)
        {
            printf("Can't send port\n");
            return (-1);
        }

        /* Receive the real bootstrap port from the parent. */
        rtrn = recv_port(port, &bootstrap_port);
        if(rtrn < 0)
        {
            printf("Can't receive bootstrap port\n");
            return (-1);
        }

        /* Set the bootstrap port back to normal. */
        err = task_set_special_port(mach_task_self(), SPECIAL_PORT, bootstrap_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't set special port:\n", err);
            return (-1);
        }

        /* Now that were done with the port dance, start the function passed by the caller. */
        child_start((*pass_port), arg);

        /* Exit and clean up the child process. */
        _exit(0);
    }
    else if(pid > 0)
    {
        mach_port_t child_port = MACH_PORT_NULL;

        /* Grab the child's recv port. */
        rtrn = recv_port((*pass_port), &child_port);
        if(rtrn < 0)
        {
            printf("Can't recv port\n");
            return (-1);
        }

        /* Send the child the original bootstrap port. */
        rtrn = send_port(child_port, special_port);
        if(rtrn < 0)
        {
            printf("Can't send bootstrap port\n");
            return (-1);
        }

        /* Reset parents special port. */
        err = task_set_special_port(mach_task_self(), SPECIAL_PORT, special_port);
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't set special port:\n", err);
            return (-1);
        }

        return (0);
    }
    else
    {
        /* Error, so cleanup the mach port. */
        err = mach_port_deallocate(mach_task_self(), (*pass_port));
        if(err != KERN_SUCCESS)
        {
            mach_error("Can't deallocate mach port\n", err);
            return (-1);
        }

        perror("fork");

        return (-1);
    }
}

int main(void)
{
    /* Argument to pass to the child process. */
    char *arg = "argument";

    /* Mach port we want to pass to the child. */
    mach_port_t port = MACH_PORT_NULL;

    pid_t pid = fork_pass_port(&port, start, arg);
    if(pid < 0)
    {
        printf("Can't fork and pass msg port\n");
        return (-1);
    }

    return (0);
}
2trill2spill
  • 1,333
  • 2
  • 20
  • 41
  • Hi, I'm currently dealing with a similar issue, with the exaction that the child process send the mach_port from the kernel space (from iokit driver command to be exact) and it should be received in user-space layer of the parent process.. do you know what's need to be done to make your code work on this scenario ? thanks –  Jun 25 '17 at 08:07
  • @osxUser Sorry for the late reply, but you should be able to send a mach_port from the kernel to user space. Instead of using fork() your going to need to know the pid of the process you want to send to and use task_for_pid() to get the mach_task then you can use mach_port_names() to get the available mach ports on the task. Hope that helps. – 2trill2spill Jun 28 '17 at 19:56
3

There's only ever one receive right for any given port. The parent has the receive right for the port it creates. The inheritance of special ports is only for send rights. So, the child only inherits the send right of your communications port.

What the article you linked to is suggesting is that the child send a message to the parent over this port. The child should have created its own new port with receive right. Its message will carry either a send or send-once right for that port to the parent, depending on if you need ongoing duplex communication. The child will put that send(-once) right in the msgh_local_port of the message; the parent will receive it in the msgh_remote_port. The parent can reply using that send(-once) right and the reply can carry a send right to the original host port. The child can use that to restore its host port.

Alternatively, you may be able to do this:

  • pass to the child, via special port inheritance, a send right for the parent's task port; with this, the child can do nearly anything to the parent
  • have the child extract the receive right of your communications port from the parent using mach_port_extract_right()
  • have it deallocate its send right of the parent task port, just for safety

It can also extract a send right for the original host port rather than receiving it via IPC, which should be simpler.

All of that said, what makes you think the host port is safer than the bootstrap port to use this way?

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • Using the bootstrap port hangs systems after Lion, I think it breaks XPC, and this is the way the Chromium project is thinking about using for the implementation of IPC for Chrome on Mac OSX. – 2trill2spill Jan 19 '16 at 04:16
  • I understand there are problems with the bootstrap port. I was suggesting that you're just as likely to encounter problems with the host port. Without an official promise from Apple that it's safe and supported, you're playing with fire. – Ken Thomases Jan 19 '16 at 04:23