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();
}
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.