]>
Commit | Line | Data |
---|---|---|
c3c9b80d A |
1 | #include <stdio.h> |
2 | #include <stdlib.h> | |
3 | #include <unistd.h> | |
4 | #include <darwintest.h> | |
5 | #include <mach/mach.h> | |
6 | #include <mach/mach_vm.h> | |
7 | #include <excserver.h> | |
8 | #include <sys/sysctl.h> | |
9 | #include <spawn.h> | |
10 | #include <signal.h> | |
11 | #include <TargetConditionals.h> | |
12 | ||
13 | #define MAX_ARGV 3 | |
14 | #define EXC_CODE_SHIFT 32 | |
15 | #define EXC_GUARD_TYPE_SHIFT 29 | |
16 | #define MAX_TEST_NUM 13 | |
17 | ||
18 | #define TASK_EXC_GUARD_MP_DELIVER 0x10 | |
19 | ||
20 | extern char **environ; | |
21 | static uint64_t exception_code = 0; | |
22 | static exception_type_t exception_taken = 0; | |
23 | ||
24 | #define IKOT_TASK_CONTROL 2 | |
25 | ||
26 | /* | |
27 | * This test verifies behaviors of immovable/pinned task/thread ports. | |
28 | * | |
29 | * 1. Compare and verifies port names of mach_{task, thread}_self(), | |
30 | * {TASK, THREAD}_KERNEL_PORT, and ports returned from task_threads() | |
31 | * and processor_set_tasks(). | |
32 | * 2. Make sure correct exceptions are raised resulting from moving immovable | |
33 | * task/thread control, read and inspect ports. | |
34 | * 3. Make sure correct exceptions are raised resulting from deallocating pinned | |
35 | * task/thread control ports. | |
36 | * 4. Make sure immovable ports cannot be stashed: | |
37 | * rdar://70585367 (Disallow immovable port stashing with *_set_special_port() and mach_port_register()) | |
38 | */ | |
39 | T_GLOBAL_META( | |
40 | T_META_NAMESPACE("xnu.ipc"), | |
41 | T_META_RUN_CONCURRENTLY(TRUE)); | |
42 | ||
43 | static uint64_t test_exception_code[] = { | |
44 | /* Pinning tests. Currently delivered as soft crash */ | |
45 | EXC_GUARD, // Soft crash delivered as EXC_CORPSE_NOTIFY | |
46 | EXC_GUARD, | |
47 | EXC_GUARD, | |
48 | EXC_GUARD, | |
49 | EXC_GUARD, | |
50 | ||
51 | /* Immovable tests. Currently delivered as hard crash */ | |
52 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
53 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
54 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
55 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
56 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
57 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
58 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
59 | (GUARD_TYPE_MACH_PORT << EXC_GUARD_TYPE_SHIFT) | kGUARD_EXC_IMMOVABLE, | |
60 | }; | |
61 | ||
62 | kern_return_t | |
63 | catch_mach_exception_raise_state(mach_port_t exception_port, | |
64 | exception_type_t exception, | |
65 | const mach_exception_data_t code, | |
66 | mach_msg_type_number_t code_count, | |
67 | int * flavor, | |
68 | const thread_state_t old_state, | |
69 | mach_msg_type_number_t old_state_count, | |
70 | thread_state_t new_state, | |
71 | mach_msg_type_number_t * new_state_count) | |
72 | { | |
73 | #pragma unused(exception_port, exception, code, code_count, flavor, old_state, old_state_count, new_state, new_state_count) | |
74 | T_FAIL("Unsupported catch_mach_exception_raise_state"); | |
75 | return KERN_NOT_SUPPORTED; | |
76 | } | |
77 | ||
78 | kern_return_t | |
79 | catch_mach_exception_raise_state_identity(mach_port_t exception_port, | |
80 | mach_port_t thread, | |
81 | mach_port_t task, | |
82 | exception_type_t exception, | |
83 | mach_exception_data_t code, | |
84 | mach_msg_type_number_t code_count, | |
85 | int * flavor, | |
86 | thread_state_t old_state, | |
87 | mach_msg_type_number_t old_state_count, | |
88 | thread_state_t new_state, | |
89 | mach_msg_type_number_t * new_state_count) | |
90 | { | |
91 | #pragma unused(exception_port, thread, task, exception, code, code_count, flavor, old_state, old_state_count, new_state, new_state_count) | |
92 | T_FAIL("Unsupported catch_mach_exception_raise_state_identity"); | |
93 | return KERN_NOT_SUPPORTED; | |
94 | } | |
95 | ||
96 | kern_return_t | |
97 | catch_mach_exception_raise(mach_port_t exception_port, | |
98 | mach_port_t thread, | |
99 | mach_port_t task, | |
100 | exception_type_t exception, | |
101 | mach_exception_data_t code, | |
102 | mach_msg_type_number_t code_count) | |
103 | { | |
104 | #pragma unused(exception_port, code_count) | |
105 | pid_t pid; | |
106 | kern_return_t kr = pid_for_task(task, &pid); | |
107 | T_EXPECT_MACH_SUCCESS(kr, "pid_for_task"); | |
108 | T_LOG("Crashing child pid: %d, continuing...\n", pid); | |
109 | ||
110 | kr = mach_port_deallocate(mach_task_self(), thread); | |
111 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "mach_port_deallocate"); | |
112 | kr = mach_port_deallocate(mach_task_self(), task); | |
113 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "mach_port_deallocate"); | |
114 | ||
115 | T_LOG("Caught exception type: %d code: 0x%llx", exception, *((uint64_t*)code)); | |
116 | if (exception == EXC_GUARD || exception == EXC_CORPSE_NOTIFY) { | |
117 | exception_taken = exception; | |
118 | exception_code = *((uint64_t *)code); | |
119 | } else { | |
120 | T_FAIL("Unexpected exception"); | |
121 | } | |
122 | return KERN_SUCCESS; | |
123 | } | |
124 | ||
125 | static void * | |
126 | exception_server_thread(void *arg) | |
127 | { | |
128 | kern_return_t kr; | |
129 | mach_port_t exc_port = *(mach_port_t *)arg; | |
130 | ||
131 | /* Handle exceptions on exc_port */ | |
132 | kr = mach_msg_server_once(mach_exc_server, 4096, exc_port, 0); | |
133 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "mach_msg_server_once"); | |
134 | ||
135 | return NULL; | |
136 | } | |
137 | ||
138 | static mach_port_t | |
139 | alloc_exception_port(void) | |
140 | { | |
141 | kern_return_t kret; | |
142 | mach_port_t exc_port = MACH_PORT_NULL; | |
143 | mach_port_t task = mach_task_self(); | |
144 | ||
145 | kret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exc_port); | |
146 | T_QUIET; T_EXPECT_MACH_SUCCESS(kret, "mach_port_allocate exc_port"); | |
147 | ||
148 | kret = mach_port_insert_right(task, exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND); | |
149 | T_QUIET; T_EXPECT_MACH_SUCCESS(kret, "mach_port_insert_right exc_port"); | |
150 | ||
151 | return exc_port; | |
152 | } | |
153 | ||
154 | static void | |
155 | test_immovable_port_stashing(void) | |
156 | { | |
157 | kern_return_t kr; | |
158 | mach_port_t port; | |
159 | ||
160 | kr = task_set_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, mach_task_self()); | |
161 | T_EXPECT_EQ(kr, KERN_INVALID_RIGHT, "should disallow task_set_special_port() with immovable port"); | |
162 | ||
163 | kr = thread_set_special_port(mach_thread_self(), THREAD_KERNEL_PORT, mach_thread_self()); | |
164 | T_EXPECT_EQ(kr, KERN_INVALID_RIGHT, "should disallow task_set_special_port() with immovable port"); | |
165 | ||
166 | mach_port_t stash[1] = {mach_task_self()}; | |
167 | kr = mach_ports_register(mach_task_self(), stash, 1); | |
168 | T_EXPECT_EQ(kr, KERN_INVALID_RIGHT, "should disallow mach_ports_register() with immovable port"); | |
169 | ||
170 | T_QUIET; T_ASSERT_MACH_SUCCESS(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port), "mach_port_allocate"); | |
171 | T_QUIET; T_ASSERT_MACH_SUCCESS(mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND), "mach_port_insert_right"); | |
172 | ||
173 | stash[0] = port; | |
174 | kr = mach_ports_register(mach_task_self(), stash, 1); | |
175 | T_EXPECT_MACH_SUCCESS(kr, "mach_ports_register() should succeed with movable port"); | |
176 | } | |
177 | ||
178 | static void | |
179 | test_task_thread_port_values(void) | |
180 | { | |
181 | T_LOG("Compare various task/thread control port values\n"); | |
182 | kern_return_t kr; | |
183 | mach_port_t port, th_self; | |
184 | thread_array_t threadList; | |
185 | mach_msg_type_number_t threadCount = 0; | |
186 | boolean_t found_self = false; | |
187 | processor_set_name_array_t psets; | |
188 | processor_set_t pset_priv; | |
189 | task_array_t taskList; | |
190 | mach_msg_type_number_t pcnt = 0, tcnt = 0; | |
191 | mach_port_t host = mach_host_self(); | |
192 | ||
193 | /* Compare with task/thread_get_special_port() */ | |
194 | kr = task_get_special_port(mach_task_self(), TASK_KERNEL_PORT, &port); | |
195 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "task_get_special_port() - TASK_KERNEL_PORT"); | |
196 | T_EXPECT_NE(port, mach_task_self(), "TASK_KERNEL_PORT should not match mach_task_self()"); | |
197 | mach_port_deallocate(mach_task_self(), port); | |
198 | ||
199 | kr = task_for_pid(mach_task_self(), getpid(), &port); | |
200 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "task_for_pid()"); | |
201 | T_EXPECT_EQ(port, mach_task_self(), "task_for_pid(self) should match mach_task_self()"); | |
202 | mach_port_deallocate(mach_task_self(), port); | |
203 | ||
204 | th_self = mach_thread_self(); | |
205 | kr = thread_get_special_port(th_self, THREAD_KERNEL_PORT, &port); | |
206 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "thread_get_special_port() - THREAD_KERNEL_PORT"); | |
207 | T_EXPECT_NE(port, th_self, "THREAD_KERNEL_PORT should not match mach_thread_self()"); | |
208 | mach_port_deallocate(mach_task_self(), port); | |
209 | ||
210 | /* Make sure task_threads() return immovable thread ports */ | |
211 | kr = task_threads(mach_task_self(), &threadList, &threadCount); | |
212 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "task_threads()"); | |
213 | T_QUIET; T_ASSERT_GE(threadCount, 1, "should have at least 1 thread"); | |
214 | ||
215 | for (size_t i = 0; i < threadCount; i++) { | |
216 | if (th_self == threadList[i]) { /* th_self is immovable */ | |
217 | found_self = true; | |
218 | break; | |
219 | } | |
220 | } | |
221 | ||
222 | T_EXPECT_TRUE(found_self, "task_threads() should return immovable thread self"); | |
223 | ||
224 | for (size_t i = 0; i < threadCount; i++) { | |
225 | mach_port_deallocate(mach_task_self(), threadList[i]); | |
226 | } | |
227 | ||
228 | if (threadCount > 0) { | |
229 | mach_vm_deallocate(mach_task_self(), | |
230 | (mach_vm_address_t)threadList, | |
231 | threadCount * sizeof(mach_port_t)); | |
232 | } | |
233 | ||
234 | mach_port_deallocate(mach_task_self(), th_self); | |
235 | ||
236 | /* Make sure processor_set_tasks() return immovable task self */ | |
237 | kr = host_processor_sets(host, &psets, &pcnt); | |
238 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "host_processor_sets"); | |
239 | T_QUIET; T_ASSERT_GE(pcnt, 1, "should have at least 1 processor set"); | |
240 | ||
241 | kr = host_processor_set_priv(host, psets[0], &pset_priv); | |
242 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "host_processor_set_priv"); | |
243 | for (size_t i = 0; i < pcnt; i++) { | |
244 | mach_port_deallocate(mach_task_self(), psets[i]); | |
245 | } | |
246 | mach_port_deallocate(mach_task_self(), host); | |
247 | vm_deallocate(mach_task_self(), (vm_address_t)psets, (vm_size_t)pcnt * sizeof(mach_port_t)); | |
248 | ||
249 | kr = processor_set_tasks_with_flavor(pset_priv, TASK_FLAVOR_CONTROL, &taskList, &tcnt); | |
250 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "processor_set_tasks_with_flavor"); | |
251 | T_QUIET; T_ASSERT_GE(tcnt, 1, "should have at least 1 task"); | |
252 | mach_port_deallocate(mach_task_self(), pset_priv); | |
253 | ||
254 | found_self = false; | |
255 | for (size_t i = 0; i < tcnt; i++) { | |
256 | if (taskList[i] == mach_task_self()) { | |
257 | found_self = true; | |
258 | break; | |
259 | } | |
260 | } | |
261 | ||
262 | T_EXPECT_TRUE(found_self, " processor_set_tasks() should return immovable task self"); | |
263 | ||
264 | for (size_t i = 0; i < tcnt; i++) { | |
265 | mach_port_deallocate(mach_task_self(), taskList[i]); | |
266 | } | |
267 | ||
268 | if (tcnt > 0) { | |
269 | mach_vm_deallocate(mach_task_self(), | |
270 | (mach_vm_address_t)taskList, | |
271 | tcnt * sizeof(mach_port_t)); | |
272 | } | |
273 | } | |
274 | ||
275 | T_DECL(imm_pinned_control_port, "Test pinned & immovable task and thread control ports", | |
276 | T_META_IGNORECRASHES(".*pinned_rights_child.*"), | |
277 | T_META_CHECK_LEAKS(false)) | |
278 | { | |
279 | uint32_t task_exc_guard = 0; | |
280 | size_t te_size = sizeof(&task_exc_guard); | |
281 | posix_spawnattr_t attrs; | |
282 | char *test_prog_name = "./imm_pinned_control_port_crasher"; | |
283 | char *child_args[MAX_ARGV]; | |
284 | pid_t client_pid = 0; | |
285 | uint32_t opts = 0; | |
286 | size_t size = sizeof(&opts); | |
287 | mach_port_t exc_port; | |
288 | pthread_t s_exc_thread; | |
289 | uint64_t exc_id; | |
290 | ||
291 | T_LOG("Check if task_exc_guard exception has been enabled\n"); | |
292 | int ret = sysctlbyname("kern.task_exc_guard_default", &task_exc_guard, &te_size, NULL, 0); | |
293 | T_ASSERT_EQ(ret, 0, "sysctlbyname"); | |
294 | ||
295 | if (!(task_exc_guard & TASK_EXC_GUARD_MP_DELIVER)) { | |
296 | T_SKIP("task_exc_guard exception is not enabled"); | |
297 | } | |
298 | ||
299 | T_LOG("Check if immovable control port has been enabled\n"); | |
300 | ret = sysctlbyname("kern.ipc_control_port_options", &opts, &size, NULL, 0); | |
301 | ||
302 | if (!ret && (opts & 0x30) == 0) { | |
303 | T_SKIP("immovable control port isn't enabled"); | |
304 | } | |
305 | ||
306 | /* first, try out comparing various task/thread ports */ | |
307 | test_task_thread_port_values(); | |
308 | ||
309 | /* try stashing immovable ports: rdar://70585367 */ | |
310 | test_immovable_port_stashing(); | |
311 | ||
312 | /* spawn a child and see if EXC_GUARD are correctly generated */ | |
313 | for (int i = 0; i < MAX_TEST_NUM; i++) { | |
314 | /* Create the exception port for the child */ | |
315 | exc_port = alloc_exception_port(); | |
316 | T_QUIET; T_ASSERT_NE(exc_port, MACH_PORT_NULL, "Create a new exception port"); | |
317 | ||
318 | /* Create exception serving thread */ | |
319 | ret = pthread_create(&s_exc_thread, NULL, exception_server_thread, &exc_port); | |
320 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create exception_server_thread"); | |
321 | ||
322 | /* Initialize posix_spawn attributes */ | |
323 | posix_spawnattr_init(&attrs); | |
324 | ||
325 | int err = posix_spawnattr_setexceptionports_np(&attrs, EXC_MASK_GUARD | EXC_MASK_CORPSE_NOTIFY, exc_port, | |
326 | (exception_behavior_t) (EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES), 0); | |
327 | T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "posix_spawnattr_setflags"); | |
328 | ||
329 | child_args[0] = test_prog_name; | |
330 | char test_num[10]; | |
331 | sprintf(test_num, "%d", i); | |
332 | child_args[1] = test_num; | |
333 | child_args[2] = NULL; | |
334 | ||
335 | T_LOG("========== Spawning new child =========="); | |
336 | err = posix_spawn(&client_pid, child_args[0], NULL, &attrs, &child_args[0], environ); | |
337 | T_ASSERT_POSIX_SUCCESS(err, "posix_spawn control_port_options_client = %d test_num = %d", client_pid, i); | |
338 | ||
339 | /* try extracting child task port: rdar://71744817 | |
340 | * Moved to tests/extract_right_soft_fail.c | |
341 | */ | |
342 | // test_extract_immovable_task_port(client_pid); | |
343 | ||
344 | int child_status; | |
345 | /* Wait for child and check for exception */ | |
346 | if (-1 == waitpid(-1, &child_status, 0)) { | |
347 | T_FAIL("waitpid: child mia"); | |
348 | } | |
349 | ||
350 | if (WIFEXITED(child_status) && WEXITSTATUS(child_status)) { | |
351 | T_FAIL("Child exited with status = %x", child_status); | |
352 | T_END; | |
353 | } | |
354 | ||
355 | sleep(1); | |
356 | kill(1, SIGKILL); | |
357 | ||
358 | ret = pthread_join(s_exc_thread, NULL); | |
359 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_join"); | |
360 | ||
361 | if (exception_taken == EXC_GUARD) { | |
362 | exc_id = exception_code >> EXC_CODE_SHIFT; | |
363 | } else { | |
364 | exc_id = exception_code; | |
365 | } | |
366 | ||
367 | T_LOG("Exception code: Received code = 0x%llx Expected code = 0x%llx", exc_id, test_exception_code[i]); | |
368 | T_EXPECT_EQ(exc_id, test_exception_code[i], "Exception code: Received == Expected"); | |
369 | } | |
370 | } |