// Disposable pre-SDK observation boundary. No control task port or process writes. #include #include #include #include #include #include #include #include #include #include #include #include #include // Private exported BSD API/selector, verified against XNU10063.141.1. Its // return is BSD int + errno, not a Mach kern_return_t. See manifest sources. extern int task_read_for_pid(mach_port_name_t, int, mach_port_name_t *) __attribute__((weak_import)); #define READ_ONLY_DARWIN_ROLE 6 static unsigned output_bytes; static void emit(const char *format, ...) { char line[256]; va_list args; va_start(args, format); int length = vsnprintf(line, sizeof line, format, args); va_end(args); if (length < 0 || length >= (int)sizeof line || output_bytes + (unsigned)length > 32768) exit(70); output_bytes += (unsigned)length; fwrite(line, 1, (size_t)length, stderr); } static void phase(const char *name, const char *point) { emit("[phase] %s %s\n", name, point); } int main(int argc, char **argv) { setvbuf(stderr, NULL, _IONBF, 0); emit("[probe-entry] self=%d architecture=%s snapshot-only=true\n", getpid(), #if defined(__x86_64__) "x86_64" #else "other" #endif ); if (argc != 3) { emit("[invalid-pid] require target and expected-parent decimal PIDs greater than1\n"); return 64; } long parsed[2]; for (int argument = 1; argument <= 2; ++argument) { if (!argv[argument][0]) { emit("[invalid-pid] empty PID\n"); return 64; } for (const char *p = argv[argument]; *p; ++p) if (*p < '0' || *p > '9') { emit("[invalid-pid] decimal digits required\n"); return 64; } errno = 0; char *end; parsed[argument - 1] = strtol(argv[argument], &end, 10); if (errno || *end || parsed[argument - 1] <= 1 || parsed[argument - 1] > INT_MAX) { emit("[invalid-pid] PID outside permitted numeric range\n"); return 64; } } int pid = (int)parsed[0], expected_parent = (int)parsed[1]; struct proc_bsdinfo bsd = {0}; phase("proc_pidinfo", "before"); errno = 0; int bytes = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bsd, sizeof bsd); int bsd_errno = errno; emit("[phase] proc_pidinfo after return=%d errno=%d\n", bytes, bsd_errno); if (bytes != sizeof bsd) { emit("[bsd-unavailable] identity was not established\n"); return 66; } emit("[bsd] pid=%u ppid=%u flags=0x%x nice=%d status=%u\n", bsd.pbi_pid, bsd.pbi_ppid, bsd.pbi_flags, bsd.pbi_nice, bsd.pbi_status); if (bsd.pbi_pid != (unsigned)pid || bsd.pbi_ppid != (unsigned)expected_parent) { emit("[ownership-rejected] target=%d expected-parent=%d actual-pid=%u actual-parent=%u\n", pid, expected_parent, bsd.pbi_pid, bsd.pbi_ppid); return 65; } phase("getpriority-role", "before"); errno = 0; int role = getpriority(READ_ONLY_DARWIN_ROLE, (id_t)pid); int role_errno = errno; emit("[role] selector=6 value=%d errno=%d\n", role, role_errno); phase("getpriority-role", "after"); struct proc_taskinfo taskinfo = {0}; phase("proc_pidinfo-task", "before"); errno = 0; bytes = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskinfo, sizeof taskinfo); int taskinfo_errno = errno; emit("[phase] proc_pidinfo-task after return=%d errno=%d\n", bytes, taskinfo_errno); if (bytes == sizeof taskinfo) { emit("[bsd-task] total-user-raw=%llu total-system-raw=%llu threads-user-raw=%llu threads-system-raw=%llu\n", taskinfo.pti_total_user, taskinfo.pti_total_system, taskinfo.pti_threads_user, taskinfo.pti_threads_system); emit("[bsd-task] threads=%d running=%d policy=%d priority=%d faults=%d pageins=%d virtual-bytes=%llu resident-bytes=%llu\n", taskinfo.pti_threadnum, taskinfo.pti_numrunning, taskinfo.pti_policy, taskinfo.pti_priority, taskinfo.pti_faults, taskinfo.pti_pageins, taskinfo.pti_virtual_size, taskinfo.pti_resident_size); } if (!task_read_for_pid) { emit("[task-read-unavailable] optional private symbol absent; public BSD snapshot remains observational\n"); emit("[probe-end] snapshot-only=true qualified-readiness=false\n"); return 0; } mach_port_t task = MACH_PORT_NULL; phase("task_read_for_pid", "before"); errno = 0; int read_result = task_read_for_pid(mach_task_self(), pid, &task); int read_errno = errno; emit("[task-read] return=%d errno=%d port=0x%x\n", read_result, read_errno, task); phase("task_read_for_pid", "after"); if (read_result != 0 || task == MACH_PORT_NULL) { emit("[mach-unavailable] read-only capability denied; BSD snapshot remains observational\n"); if (task != MACH_PORT_NULL) mach_port_deallocate(mach_task_self(), task); emit("[probe-end] snapshot-only=true qualified-readiness=false\n"); return 0; } emit("[mach-capability] read-only=true; unsuspended snapshots may be incomplete\n"); task_dyld_info_data_t dyld = {0}; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; phase("task_info-dyld", "before"); kern_return_t kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld, &count); emit("[phase] task_info-dyld after kern=%d count=%u\n", kr, count); size_t prefix = offsetof(struct dyld_all_image_infos, jitInfo); if (kr == KERN_SUCCESS && count == TASK_DYLD_INFO_COUNT && dyld.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_64 && dyld.all_image_info_size >= prefix) { struct dyld_all_image_infos info = {0}; mach_vm_size_t received = 0; phase("dyld-prefix-read", "before"); kr = mach_vm_read_overwrite(task, dyld.all_image_info_addr, prefix, (mach_vm_address_t)(uintptr_t)&info, &received); emit("[phase] dyld-prefix-read after kern=%d bytes=%llu\n", kr, (unsigned long long)received); if (kr == KERN_SUCCESS && received == prefix) emit("[dyld] version=%u images=%u array=0x%llx libSystemInitialized=%d dyld=0x%llx array-null-is-pending=true\n", info.version, info.infoArrayCount, (unsigned long long)(uintptr_t)info.infoArray, info.version >= 2 ? info.libSystemInitialized : -1, info.version >= 2 ? (unsigned long long)(uintptr_t)info.dyldImageLoadAddress : 0); } thread_act_array_t threads = NULL; mach_msg_type_number_t thread_count = 0; phase("task_threads", "before"); kr = task_threads(task, &threads, &thread_count); emit("[phase] task_threads after kern=%d count=%u observed-limit=32\n", kr, thread_count); if (kr == KERN_SUCCESS) { for (unsigned i = 0; i < thread_count && i < 32; ++i) { thread_basic_info_data_t basic = {0}; count = THREAD_BASIC_INFO_COUNT; phase("thread_info", "before"); kr = thread_info(threads[i], THREAD_BASIC_INFO, (thread_info_t)&basic, &count); emit("[phase] thread_info after index=%u kern=%d count=%u\n", i, kr, count); if (kr == KERN_SUCCESS && count == THREAD_BASIC_INFO_COUNT) emit("[thread-basic] index=%u run-state=%d policy=%d cpu=%d user=%d.%06d system=%d.%06d\n", i, basic.run_state, basic.policy, basic.cpu_usage, basic.user_time.seconds, basic.user_time.microseconds, basic.system_time.seconds, basic.system_time.microseconds); x86_thread_state64_t registers = {0}; count = x86_THREAD_STATE64_COUNT; phase("thread_get_state-x86_64", "before"); kr = thread_get_state(threads[i], x86_THREAD_STATE64, (thread_state_t)®isters, &count); emit("[phase] thread_get_state-x86_64 after index=%u kern=%d count=%u\n", i, kr, count); if (kr == KERN_SUCCESS && count == x86_THREAD_STATE64_COUNT) emit("[thread-registers] index=%u rip=0x%llx rbp=0x%llx rsp=0x%llx\n", i, registers.__rip, registers.__rbp, registers.__rsp); } for (unsigned i = 0; i < thread_count; ++i) mach_port_deallocate(mach_task_self(), threads[i]); vm_deallocate(mach_task_self(), (vm_address_t)threads, thread_count * sizeof *threads); } mach_port_deallocate(mach_task_self(), task); emit("[probe-end] snapshot-only=true qualified-readiness=false\n"); return 0; }