How to set the host exception port using the Mach kernel on macOS?
Asked Answered
M

2

5

I want to use Mach Exception Ports to handle exceptions for all tasks (processes) running on macOS. My understanding is that host_set_exception_ports is to be used in this case. However, host_set_exception_ports returns KERN_NO_ACCESS (error code 8) even when I execute my program with sudo. My experimental code works to handle exceptions for a single task using task_set_exception_ports.

I already had a look at the Mach kernel code of host_set_exception_ports. There is a single line of code where KERN_NO_ACCESS is returned from the function. I have a bit of a hard time to understand what's going on there. It seems the kernel code checks the exception mask I pass to host_set_exception_ports. I tested with different exception masks but I always get the same negative result.

My questions: Does this mean there is a general restriction to use host_set_exception_ports in a user-space application? If not, how would I set the host exception ports to receive system-wide exceptions in my application?

The following program is a minimal example to show the behavior and does not have much use otherwise. Use gcc example.c to compile the program and sudo ./a.out to execute it.

#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>

void catchMachExceptions() {
    mach_port_t exception_port;
    kern_return_t rc;

    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();
}
Marzipan answered 22/4, 2019 at 7:46 Comment(2)
Just a guess, but this might require a kernel mode, try to make your program as a kernel extension.Fatherly
I believe @DisableR is pointing you at the right track... especially if you're trying to install exception handlers for kernel processes. Try limiting the processes you're listening to to user level processes and see if this solves your issue. I suspect (not sure) the answer to "Does this mean there is a general restriction to use host_set_exception_ports in a user-space application" is yes.Seibel
F
4

Digging through the source, host_set_exception_ports is calling mac_task_check_set_host_exception_ports, as you've noted.

Drilling down on that, you can find that mac_task_check_set_host_exception_ports is retrieving credentials based on the task, then invoking MAC_CHECK(proc_check_set_host_exception_port, cred, exception).

That's a macro defined in security/mac_internals.h. See the xnu code here. Reading, this walks the policy module list checking whether the policy modules will allow or deny the request.

Based on the code, all policy modules must agree and not post an error. So it looks like you need to have the right privileges. There is some documentation from the GNU Mach project that indicates rights to send to the privileged host port are granted to the first task, and can only be passed on from there. See GNU Mach definition of host_ports, etc.

Piecing that together, not only can you not make the change as a user proc, you will need to specially inherit the privileges through a chain from boot time.

Fulmis answered 1/5, 2019 at 7:41 Comment(1)
As an extra note, the book "Mac OS X Internals: A Systems Approach", though quite old, looks like it has good information on this subject.Fulmis
W
3

To complete the answer above:

GNU mach definition of host ports is irrelevant. XNU is pretty far removed from it at this point. This missing piece is the sandbox. The mac_task_check_set_host_exception_ports() indeed checks Policy modules. There are generally two policy modules involved in most operations: The Sandbox.kext and (for ports) AppleMobileFileIntegrity.kext. The former hooks this (with hook #127 in Darwin 18-19), and - since SIP is enabled, the platform profile is consulted, as nearly all processes are sandboxed at a low level.

You can get around this with entitlements, or if you are unsandboxed - like launchd (PID 1) is, and it indeed has the capability of setting the receive right on the host exception ports, and then transferring it to whomever is designated as the HostExceptionServer in the /System/Library/LaunchDaemons property list - which is, by default, ReportCrash. If you can add your own LaunchDaemons (which requires disabling filesystem restrictions of SIP, at least temporary), then you can nab the port from launchd.

FYI, Mac OS X Internals is a great book, but it's 13 years old, cuts off at 10.4. MACF came into play at 10.5. Consider its unofficial sequel, "*OS Internals" (http://NewOSXBook.com/) which covers this in detail.

Wycoff answered 29/8, 2019 at 21:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.