如何在 macOS 上使用 Mach 内核设置主机异常端口?

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

我想使用 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) 即使我用 sudo 执行我的程序。我的实验代码使用 task_set_exception_ports.

处理单个任务的异常

我已经看过 Mach kernel code of host_set_exception_ports. There is a single line of code,其中 KERN_NO_ACCESS 从函数返回。我很难理解那里发生了什么。似乎内核代码检查了我传递给 host_set_exception_ports 的异常掩码。我用不同的异常掩码进行了测试,但我总是得到相同的否定结果。

我的问题: 这是否意味着在用户-space 应用程序中存在使用 host_set_exception_ports 的一般限制?如果不是,我将如何设置主机异常端口以在我的应用程序中接收系统范围的异常?

以下程序是展示行为的最小示例,否则没有太大用处。使用gcc example.c编译程序,sudo ./a.out执行程序。

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

通过源代码挖掘,host_set_exception_ports 正在调用 mac_task_check_set_host_exception_ports,正如您所指出的。

深入研究,您会发现 mac_task_check_set_host_exception_ports 正在根据任务检索凭据,然后调用 MAC_CHECK(proc_check_set_host_exception_port, cred, exception).

这是 security/mac_internals.h 中定义的宏。参见 the xnu code here。阅读,这会遍历策略模块列表,检查策略模块是允许还是拒绝请求。

根据代码,所有策略模块都必须同意,而不是 post 错误。所以看起来你需要有正确的权限。 GNU Mach 项目中有一些文档表明发送到特权主机端口的权限已授予第一个任务,并且只能从那里传递。参见 GNU Mach definition of host_ports, etc

将它们拼凑在一起,您不仅不能以用户 proc 的身份进行更改,还需要从启动时通过链专门继承权限。

完成以上答案:

主机端口的 GNU mach 定义无关紧要。 XNU 在这一点上离它很远。这个缺失的部分是沙箱。 mac_task_check_set_host_exception_ports() 确实检查策略模块。大多数操作通常涉及两个策略模块:Sandbox.kext 和(对于端口)AppleMobileFileIntegrity.kext。前者对此进行了挂钩(在 Darwin 18-19 中使用挂钩 #127),并且 - 由于启用了 SIP,因此咨询了平台配置文件,因为几乎所有进程都在低级别进行了沙盒处理。

你可以通过权利来解决这个问题,或者如果你没有被沙盒化——就像 launchd (PID 1) 一样,它确实有能力在主机异常端口上设置接收权,然后将它传输给任何人在 /System/Library/LaunchDaemons 属性 列表中指定为 HostExceptionServer - 默认情况下为 ReportCrash。如果您可以添加自己的 LaunchDaemons(这需要禁用 SIP 的文件系统限制,至少是暂时的),那么您可以从 launchd 获取端口。

仅供参考,Mac OS X Internals 是一本很棒的书,但它已有 13 年历史,截止日期为 10.4。 MACF 在 10.5 开始发挥作用。考虑其非官方 sequel、“*OS 内部结构”(http://NewOSXBook.com/),其中详细介绍了这一点。