Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | #include <darwintest.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <mach/mach.h> #include <mach/exception.h> #include <mach/thread_status.h> #include <mach/mach_error.h> #include <mach/port.h> #include <string.h> #include <errno.h> #include <ptrauth.h> #include <kern/exc_guard.h> #include <semaphore.h> #include <fcntl.h> #include <sys/mman.h> #include "../exc_helpers.h" #include "ipc_utils.h" #if __x86_64__ #include <mach/i386/thread_status.h> #endif /* Include MIG-generated server prototypes */ extern boolean_t mach_exc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); #if __arm64__ #define EXCEPTION_THREAD_STATE ARM_THREAD_STATE64 #define EXCEPTION_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT #elif __x86_64__ #define EXCEPTION_THREAD_STATE x86_THREAD_STATE64 #define EXCEPTION_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT #else #error Unsupported architecture #endif T_GLOBAL_META( T_META_NAMESPACE("xnu.ipc"), T_META_RADAR_COMPONENT_NAME("xnu"), T_META_RADAR_COMPONENT_VERSION("IPC")); /* * Helper function to check if a child exited with a guard exception */ bool did_receive_exc_tss = false; static size_t exception_handler_callback( task_id_token_t token, uint64_t thread_id, exception_type_t type, mach_exception_data_t codes, thread_state_t in_state, mach_msg_type_number_t in_state_count, thread_state_t out_state, mach_msg_type_number_t *out_state_count) { #pragma unused(token, thread_id, type, in_state, in_state_count, out_state, out_state_count) T_LOG("Exception received successfully"); uint32_t exc_guard_flavor = EXC_GUARD_DECODE_GUARD_FLAVOR(codes[0]); if (exc_guard_flavor == kGUARD_EXC_MACH_EXC_THREAD_SET_STATE) { did_receive_exc_tss = true; } /* Advance PC by 4 bytes to skip the faulting instruction */ return 4; } T_DECL(mach_exc_interprocess_thread_state, "Test that inter-process exception handler cannot modify thread state") { mach_port_t exc_port = MACH_PORT_NULL; mach_port_t child_task = MACH_PORT_NULL; mach_port_t child_thread = MACH_PORT_NULL; pid_t child_pid; int status; sem_t *sync_sem; char sem_name[32]; T_LOG("Starting inter-process mach exception thread state test"); /* Create a unique semaphore name */ snprintf(sem_name, sizeof(sem_name), "/exc_sync_%d", getpid()); /* Create shared semaphore for parent-child synchronization */ sem_unlink(sem_name); /* Clean up any stale semaphore */ sync_sem = sem_open(sem_name, O_CREAT | O_EXCL, 0644, 0); T_ASSERT_NE(sync_sem, SEM_FAILED, "sem_open"); /* Fork child process first */ child_pid = fork(); T_ASSERT_NE(child_pid, -1, "fork"); if (child_pid == 0) { /* Child process - will trigger exception and be handled by parent */ T_LOG("Child process %d started", getpid()); /* Wait for parent to set up exception handler */ T_LOG("Child waiting for parent to signal readiness"); int ret = sem_wait(sync_sem); T_ASSERT_POSIX_SUCCESS(ret, "sem_wait"); T_LOG("Child received signal from parent, proceeding"); /* Close semaphore in child */ sem_close(sync_sem); /* Trigger EXC_BAD_ACCESS exception */ T_LOG("Child triggering EXC_BAD_ACCESS exception"); *(volatile int*)0 = 0; T_LOG("Child recovered"); exit(0); } else { /* Parent process - will act as exception handler for child */ T_LOG("Parent process %d waiting for child %d", getpid(), child_pid); /* Get child's task port */ kern_return_t kr = task_for_pid(mach_task_self(), child_pid, &child_task); T_ASSERT_MACH_SUCCESS(kr, "task_for_pid"); T_ASSERT_NE(child_task, MACH_PORT_NULL, "child task port"); /* Create exception port for EXCEPTION_STATE_IDENTITY behavior */ exc_port = create_exception_port_behavior64(EXC_MASK_ALL, EXCEPTION_STATE_IDENTITY_PROTECTED); T_ASSERT_NE(exc_port, MACH_PORT_NULL, "create_exception_port_behavior64"); T_LOG("Created exception port"); /* Register exception handler on child's task */ kr = task_set_exception_ports(child_task, EXC_MASK_ALL, exc_port, EXCEPTION_STATE_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES, EXCEPTION_THREAD_STATE); T_ASSERT_MACH_SUCCESS(kr, "task_set_exception_ports"); T_LOG("Registered exception handler for child"); /* * Start exception handler thread that will attempt to handle the child's exception. * When the child crashes, the parent's exception handler will receive the exception * and try to modify the child's thread state via the MIG reply. This should trigger * kGUARD_EXC_MACH_EXC_THREAD_SET_STATE because the parent doesn't have proper * entitlements/permissions to modify the child's thread state. */ run_exception_handler_behavior64(exc_port, NULL, exception_handler_callback, EXCEPTION_STATE_IDENTITY_PROTECTED, false); T_LOG("Exception handler thread started, signaling child to proceed"); /* Signal child that exception handler is ready */ int ret = sem_post(sync_sem); T_ASSERT_POSIX_SUCCESS(ret, "sem_post"); /* Wait for child to complete or be terminated */ int wait_result = waitpid(child_pid, &status, 0); T_ASSERT_EQ(wait_result, child_pid, "waitpid"); T_EXPECT_TRUE(WIFEXITED(status), "child should exit normally"); T_EXPECT_EQ(WEXITSTATUS(status), 0, "child exited with status 0"); /* This might fail while we are in CA telemetry mode */ T_MAYFAIL_WITH_RADAR(164960066); /* Child should have received guard exception */ T_EXPECT_TRUE(did_receive_exc_tss, "should have received kGUARD_EXC_MACH_EXC_THREAD_SET_STATE"); /* Cleanup semaphore */ sem_close(sync_sem); sem_unlink(sem_name); T_PASS("Inter-process mach exception thread state test completed"); } /* Cleanup */ if (exc_port != MACH_PORT_NULL) { mach_port_deallocate(mach_task_self(), exc_port); } if (child_task != MACH_PORT_NULL) { mach_port_deallocate(mach_task_self(), child_task); } if (child_thread != MACH_PORT_NULL) { mach_port_deallocate(mach_task_self(), child_thread); } } |