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();
}
Mach ports are capabilities, and are also essentially similar to UNIX pipes. They are unforgeable communication channels, implemented by kernel queues. Each port has associated with it one receive right and one or more send rights and send-once rights.
Exceptions are synchronous interruptions to the normal flow of program control caused by the program itself. The following are examples of reasons why exceptions can occur: Attempting to access nonexistent memory. Attempting to access memory that violates address-space protection.
A task port, or more precisely a send right to a task port, is basically just a send right to a Mach port for which the kernel owns the receive right.
It is capable of running as a stand–alone kernel, with other traditional operating-system services such as I/O, file systems, and networking stacks running as user-mode servers. However, in OS X, Mach is linked with other kernel components into a single kernel address space.
With the exception of the task’s virtual address space, all other Mach resources are accessed through a level of indirection known as a port. A port is an endpoint of a unidirectional communication channel between a client who requests a service and a server who provides the service.
Mach Kernel Abstractions Mach provides a small set of abstractions that have been designed to be both simple and powerful. These are the main kernel abstractions: Tasks. The units of resource ownership; each task consists of a virtual address space, a port rightnamespace, and one or more threads. (Similar to a process.) Threads.
Mach Overview The fundamental services and primitives of the OS X kernel are based on Mach 3.0. Apple has modified and extended Mach to better meet OS X functional and performance goals. Mach 3.0 was originally conceived as a simple, extensible, communications microkernel.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With