How to use host_set_exception_ports?

Questions and Answers about all things *OS (macOS, iOS, tvOS, watchOS)

How to use host_set_exception_ports?

Postby jade » Mon Apr 22, 2019 6:17 pm

Hi,

I'm experimenting with Mach Exception Ports. I'm trying to use host_set_exception_ports instead of task_set_exception_ports. However, host_set_exception_ports returns KERN_NO_ACCESS for me on macOS 10.14.4. I am using sudo to execute my program.

The following code is a minimal example to show what I am doing to set the port.

Code: Select all
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>

void catchMachExceptions() {
    mach_port_t exception_port;

    kern_return_t rc = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to allocate exception port: %d\n", rc);
        exit(-1);
    }

    rc = mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to insert right: %d\n", rc);
        exit(-1);
    }

    rc = host_set_exception_ports(mach_host_self(), EXC_MASK_ALL, exception_port, EXCEPTION_STATE_IDENTITY, MACHINE_THREAD_STATE);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr,"Unable to set exception: %d\n", rc);
        exit(-1);
    }
}

int main(int argc, char **argv) {
    catchMachExceptions();
}


Am I doing something wrong with the parameters or is it prohibited to use host_set_exception_ports?

Best,
jade
jade
 
Posts: 5
Joined: Sun Apr 14, 2019 1:40 pm

Re: How to use host_set_exception_ports?

Postby scknight » Tue Apr 23, 2019 2:11 pm

I think you're being stopped by the Sandbox kext. If I run your test application and watch the logs for sandbox lines I see the following

error 09:45:24.616016 -0400 sandboxd sandboxd Sandbox: except(37589) System Policy: deny(1) mach-host-exception-port-set

The kernel code does the following:

Code: Select all
if (mac_task_check_set_host_exception_ports(current_task(), exception_mask) != 0)
    return KERN_NO_ACCESS;


Not sure if there's a valid exception_mask that you can pass in that would be allowed.
scknight
 
Posts: 56
Joined: Thu Nov 10, 2016 1:01 pm

Re: How to use host_set_exception_ports?

Postby ccnut » Tue Apr 23, 2019 9:45 pm

The above is true. You probably just need host_priv. You could RE the Sandbox implementation of the hook mentioned above to know for sure. I've not played around too much with that variant of the API. Try using mach_host_self() or host_get_host_priv_port() rather than mach_task_self(). The host privileged port is returned in either case if you are root. If you are not root then the latter will fail and the former returns the unprivileged host port.
ccnut
 
Posts: 7
Joined: Fri Mar 15, 2019 5:11 pm

Re: How to use host_set_exception_ports?

Postby jade » Tue Apr 23, 2019 10:39 pm

Hi,

thanks for your input!

@scknight, I already tried to make sense of the kernel code and tested different exceptions masks. I always get KERN_NO_ACCESS.

@ccnut, Do you mean like in the following version of catchMatchException? Same behavior. mach_port_allocate and mach_port_insert_right only work with mach_task_self.

Code: Select all
void catchMachExceptions() {
    mach_port_t exception_port;
    kern_return_t rc;
    host_priv_t host_priv;

    rc = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to allocate exception port: %d\n", rc);
        exit(-1);
    }

    rc = mach_port_insert_right(mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to insert right: %d\n", rc);
        exit(-1);
    }

    rc = host_get_host_priv_port(mach_host_self(), &host_priv);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to get host private port: %d\n", rc);
        exit(-1);
    }
    fprintf(stderr, "Host private port: %d, Host self: %d\n", host_priv, mach_host_self());

    rc = host_set_exception_ports(host_priv, EXC_MASK_CRASH, exception_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACHINE_THREAD_STATE);
    if (rc != KERN_SUCCESS) {
        fprintf(stderr, "Unable to set exception: %d\n", rc);
        exit(-1);
    }
}
jade
 
Posts: 5
Joined: Sun Apr 14, 2019 1:40 pm

Re: How to use host_set_exception_ports?

Postby ccnut » Wed Apr 24, 2019 7:37 pm

Are you running as root? Honestly I'm less familiar with setting the host exception ports. The idea of a "host" is left over from an earlier hope that Mach would be distributed across many different physical machines. So setting a "host" exception would be specific to the physical machine the exception occurred on. Frankly I'm unsure of the current difference between task and host exception ports. Let's find out.

Check out exception_triag_thread in osfmk/kern/exception.c. This is a core function called when handling thread exceptions.

Code: Select all
kern_return_t
exception_triage_thread(
   exception_type_t   exception,
   mach_exception_data_t   code,
   mach_msg_type_number_t  codeCnt,
   thread_t       thread)
{
   task_t         task;
   host_priv_t      host_priv;
   lck_mtx_t      *mutex;
   kern_return_t   kr = KERN_FAILURE;

   assert(exception != EXC_RPC_ALERT);

   /*
    * If this behavior has been requested by the the kernel
    * (due to the boot environment), we should panic if we
    * enter this function.  This is intended as a debugging
    * aid; it should allow us to debug why we caught an
    * exception in environments where debugging is especially
    * difficult.
    */
   if (panic_on_exception_triage) {
      panic("called exception_triage when it was forbidden by the boot environment");
   }

   /*
    * Try to raise the exception at the activation level.
    */
   mutex = &thread->mutex;
   if (KERN_SUCCESS == check_exc_receiver_dependency(exception, thread->exc_actions, mutex))
   {
      kr = exception_deliver(thread, exception, code, codeCnt, thread->exc_actions, mutex);
      if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
         goto out;
   }

   /*
    * Maybe the task level will handle it.
    */
   task = thread->task;
   mutex = &task->itk_lock_data;
   if (KERN_SUCCESS == check_exc_receiver_dependency(exception, task->exc_actions, mutex))
   {
      kr = exception_deliver(thread, exception, code, codeCnt, task->exc_actions, mutex);
      if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
         goto out;
   }

   /*
    * How about at the host level?
    */
   host_priv = host_priv_self();
   mutex = &host_priv->lock;

   if (KERN_SUCCESS == check_exc_receiver_dependency(exception, host_priv->exc_actions, mutex))
   {
      kr = exception_deliver(thread, exception, code, codeCnt, host_priv->exc_actions, mutex);
      if (kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED)
         goto out;
   }

out:
   if ((exception != EXC_CRASH) && (exception != EXC_RESOURCE) &&
       (exception != EXC_GUARD) && (exception != EXC_CORPSE_NOTIFY))
      thread_exception_return();
   return kr;
}


You'll note that first the thread exception handlers are consulted (e.g. exc_actions), then the task (which will handle all thread exception if there are non registered per-thread), and then the host. Look closer and you'll see that the only difference between the various levels is a pointer to an array of `struct exception_action`, each taken from a different data structure representing the various levels.

The "host" exceptions are those registered with the task port referred to as the host_priv. Specifically, the function host_priv_self, where the return is set to the local host_priv reference, just returns the address of realhost.
Code: Select all
host_priv_t
host_priv_self(void)
{
   return (&realhost);
}


As such, changing the host exception ports is a system-wide change. You are certainly getting denied by failing the Sandbox MACF hook for "mac_task_check_host_exception_ports" as per the answer above. The policy is probably just such that you can't own the host exception ports from userspace, or similar. What happens, for example, if you register for the host exception ports with an ephemeral task port that is deallocated when your test program terminates? You own the only receive right on the port, so all exceptions that route to the host level will go unhandled.

Note that the default host exception port appears to be owned by the kernel. This function is called from `bsdinit_task` during early startup.
Code: Select all
void
ux_handler_setup(void)
{
   ipc_port_t ux_handler_send_right = ipc_port_make_send(ux_handler_port);

   if (!IP_VALID(ux_handler_send_right))
      panic("Couldn't allocate send right for ux_handler_port!\n");

   kern_return_t kr = KERN_SUCCESS;

   /*
    * Consumes 1 send right.
    *
    * Instruments uses the RPC_ALERT port, so don't register for that.
    */
   kr = host_set_exception_ports(host_priv_self(),
                                 EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT),
                                 ux_handler_send_right,
                                 EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
                                 0);

   if (kr != KERN_SUCCESS)
      panic("host_set_exception_ports failed to set ux_handler! %d", kr);
}


Try disabling SIP. That's the last suggestion I have, and it is unlikely to work. Otherwise unsandbox yourself.
ccnut
 
Posts: 7
Joined: Fri Mar 15, 2019 5:11 pm

Re: How to use host_set_exception_ports?

Postby jade » Wed Apr 24, 2019 8:23 pm

Thanks for the details feedback, ccnut. Your effort is very much appreciated. I am running my program using sudo.

What you write makes sense. I was already wondering what would happen if a process owning the receive right for the host exception port terminates before giving it back to the default process. Without an automatic fallback mechanism this would cause havoc of course.

Getting rid of the sandbox is not an option, unfortunately. What I want to achieve needs to work on a normal macOS installation without too much tinkering and definitely with security measures turned on.

I think I will focus on task_set_exception_ports for now. Let's see if I can handle exceptions for all or most of the processes running on my system by using task-level exception ports.
jade
 
Posts: 5
Joined: Sun Apr 14, 2019 1:40 pm


Return to Questions and Answers

Who is online

Users browsing this forum: No registered users and 4 guests