xref: /xnu-11417.121.6/tests/signal_exit_reason.c (revision a1e26a70f38d1d7daa7b49b258e2f8538ad81650)
1 /*
2  * Copyright (c) 2023 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*
30  * These tests verify that proc_pidinfo returns the expected exit reason and
31  * namespace for signal-related process termination.
32  */
33 
34 #include <darwintest.h>
35 #include <signal.h>
36 #include <libproc.h>
37 #include <sys/wait.h>
38 #include <sys/reason.h>
39 #include <stdlib.h>
40 #include <dispatch/dispatch.h>
41 #include <unistd.h>
42 #include <mach/kern_return.h>
43 #include <mach/mach.h>
44 #include <mach/mach_port.h>
45 #include <mach/message.h>
46 #include <mach/port.h>
47 
48 
49 T_GLOBAL_META(
50 	T_META_NAMESPACE("xnu.misc"),
51 	T_META_RADAR_COMPONENT_NAME("xnu"),
52 	T_META_RADAR_COMPONENT_VERSION("misc"),
53 	T_META_RUN_CONCURRENTLY(true));
54 
55 static dispatch_queue_t exit_queue;
56 
57 static void
cleanup(void)58 cleanup(void)
59 {
60 	dispatch_release(exit_queue);
61 }
62 
63 static void
64 dispatch_test(void (^test)(void))
65 {
66 	// Use dispatch to schedule DISPATCH_PROC_EXIT blocks to read out exit reasons
67 	exit_queue = dispatch_queue_create("exit queue", DISPATCH_QUEUE_SERIAL);
68 	dispatch_async(dispatch_get_main_queue(), ^{
69 		test();
70 	});
71 	T_ATEND(cleanup);
72 	dispatch_main();
73 }
74 
75 static bool
audit_token_for_pid(pid_t pid,audit_token_t * token)76 audit_token_for_pid(pid_t pid, audit_token_t *token)
77 {
78 	kern_return_t err;
79 	task_name_t task_name = TASK_NAME_NULL;
80 	mach_msg_type_number_t info_size = TASK_AUDIT_TOKEN_COUNT;
81 
82 	err = task_name_for_pid(mach_task_self(), pid, &task_name);
83 	if (err != KERN_SUCCESS) {
84 		T_LOG("task_for_pid returned %d\n", err);
85 		return false;
86 	}
87 
88 	err = task_info(task_name, TASK_AUDIT_TOKEN, (integer_t *)token, &info_size);
89 	if (err != KERN_SUCCESS) {
90 		T_LOG("task_info returned %d\n", err);
91 		return false;
92 	}
93 
94 	return true;
95 }
96 
97 static void
check_exit_reason(int pid,uint64_t expected_reason_namespace,uint64_t expected_signal)98 check_exit_reason(int pid, uint64_t expected_reason_namespace, uint64_t expected_signal)
99 {
100 	T_LOG("check_exit_reason %d", expected_signal);
101 	int ret, status;
102 	struct proc_exitreasonbasicinfo exit_reason;
103 
104 	ret = proc_pidinfo(pid, PROC_PIDEXITREASONBASICINFO, 1, &exit_reason, PROC_PIDEXITREASONBASICINFOSIZE);
105 	T_WITH_ERRNO; T_QUIET; T_ASSERT_EQ(ret, PROC_PIDEXITREASONBASICINFOSIZE, "retrieve basic exit reason info");
106 
107 	waitpid(pid, &status, 0);
108 	T_QUIET; T_EXPECT_FALSE(WIFEXITED(status), "process did not exit normally");
109 	T_QUIET; T_EXPECT_TRUE(WIFSIGNALED(status), "process was terminated because of a signal");
110 	T_QUIET; T_EXPECT_EQ(WTERMSIG(status), expected_signal, "process should terminate due to signal %llu", expected_signal);
111 
112 	T_EXPECT_EQ(exit_reason.beri_namespace, expected_reason_namespace, "expect OS_REASON_SIGNAL");
113 	T_EXPECT_EQ(exit_reason.beri_code, expected_signal, "expect reason code: %llu", expected_signal);
114 }
115 
116 static void
wait_collect_exit_reason(int pid,int signal)117 wait_collect_exit_reason(int pid, int signal)
118 {
119 	dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
120 	dispatch_semaphore_t sem = dispatch_semaphore_create(0);
121 	dispatch_source_set_event_handler(ds_proc, ^{
122 		check_exit_reason(pid, OS_REASON_SIGNAL, signal);
123 		dispatch_semaphore_signal(sem);
124 	});
125 	dispatch_activate(ds_proc);
126 
127 	// Wait till exit reason is processed
128 	dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
129 	dispatch_release(ds_proc);
130 	dispatch_release(sem);
131 }
132 
133 static void
wait_with_timeout_expected(int pid,int seconds)134 wait_with_timeout_expected(int pid, int seconds)
135 {
136 	long timeout = 0;
137 	dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
138 	dispatch_semaphore_t sem = dispatch_semaphore_create(0);
139 	dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);;
140 	dispatch_source_set_event_handler(ds_proc, ^{
141 		dispatch_semaphore_signal(sem);
142 	});
143 	dispatch_activate(ds_proc);
144 
145 	// Wait till exit reason is processed or timeout
146 	timeout = dispatch_semaphore_wait(sem, milestone);
147 	T_QUIET; T_EXPECT_TRUE(timeout != 0, "process exited and was not expected to");
148 	dispatch_release(ds_proc);
149 	dispatch_release(sem);
150 }
151 
152 static void
__test_exit_reason_abort()153 __test_exit_reason_abort()
154 {
155 	pid_t child = fork();
156 	if (child > 0) {
157 		wait_collect_exit_reason(child, SIGABRT);
158 	} else {
159 		abort();
160 	}
161 }
162 
163 T_DECL(test_exit_reason_abort, "tests exit reason for abort()", T_META_TAG_VM_PREFERRED)
164 {
165 	dispatch_test(^{
166 		__test_exit_reason_abort();
167 		T_END;
168 	});
169 }
170 
171 static void
__test_exit_reason_external_signal(int signal)172 __test_exit_reason_external_signal(int signal)
173 {
174 	T_LOG("Testing external signal %d", signal);
175 	pid_t child = fork();
176 	if (child > 0) {
177 		// Send external signal
178 		kill(child, signal);
179 		wait_collect_exit_reason(child, signal);
180 	} else {
181 		pause();
182 	}
183 }
184 
185 static void
__test_exit_reason_delegate_signal(int signal)186 __test_exit_reason_delegate_signal(int signal)
187 {
188 	int ret = 0;
189 	audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
190 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
191 	pid_t child = fork();
192 	if (child > 0) {
193 		audit_token_for_pid(getpid(), &instigator);
194 		audit_token_for_pid(child, &token);
195 		// Send signal to the child with its audit token
196 		ret = proc_signal_delegate(instigator, token, signal);
197 		T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_delegate return: %d", ret);
198 		wait_collect_exit_reason(child, signal);
199 		// Send signal to the child with its audit token who has exited by now
200 		ret = proc_signal_delegate(instigator, token, signal);
201 		T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
202 	} else {
203 		pause();
204 		// This exit should not hit, but we exit abnormally in case something went wrong
205 		_exit(-1);
206 	}
207 }
208 
209 static void
__test_exit_reason_delegate_terminate()210 __test_exit_reason_delegate_terminate()
211 {
212 	int ret = 0;
213 	audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
214 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
215 	pid_t child = fork();
216 	int sentsignal = 0;
217 	if (child > 0) {
218 		audit_token_for_pid(getpid(), &instigator);
219 		audit_token_for_pid(child, &token);
220 		// Send signal to the child with its audit token
221 		ret = proc_terminate_delegate(instigator, token, &sentsignal);
222 		T_EXPECT_EQ_INT(ret, 0, "expect proc_terminate_delegate return: %d", ret);
223 		T_EXPECT_TRUE(sentsignal == SIGTERM || sentsignal == SIGKILL, "sentsignal retval %d", sentsignal);
224 		wait_collect_exit_reason(child, SIGTERM);
225 		// Send signal to the child with its audit token who has exited by now
226 		ret = proc_terminate_delegate(instigator, token, &sentsignal);
227 		T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
228 		// Terminating PID 1 should fail with EPERM
229 		audit_token_for_pid(1, &token);
230 		ret = proc_terminate_delegate(instigator, token, &sentsignal);
231 		T_EXPECT_EQ_INT(ret, EPERM, "expected eperm return: %d", ret);
232 	} else {
233 		pause();
234 		// This exit should not hit, but we exit abnormally in case something went wrong
235 		_exit(-1);
236 	}
237 }
238 
239 static void
__test_exit_reason_terminate()240 __test_exit_reason_terminate()
241 {
242 	int ret = 0;
243 	pid_t child = fork();
244 	int sentsignal = 0;
245 	if (child > 0) {
246 		// Send signal to the child with its audit token
247 		ret = proc_terminate(child, &sentsignal);
248 		T_EXPECT_EQ_INT(ret, 0, "expect proc_terminate_delegate return: %d", ret);
249 		T_EXPECT_TRUE(sentsignal == SIGTERM || sentsignal == SIGKILL, "sentsignal retval %d", sentsignal);
250 		wait_collect_exit_reason(child, SIGTERM);
251 		// Send signal to the child with its audit token who has exited by now
252 		ret = proc_terminate(child, &sentsignal);
253 		T_EXPECT_EQ_INT(ret, ESRCH, "expected no such process return: %d", ret);
254 		// Terminating PID 1 should fail with EPERM
255 		ret = proc_terminate(1, &sentsignal);
256 		T_EXPECT_EQ_INT(ret, EPERM, "expected eperm return: %d", ret);
257 	} else {
258 		pause();
259 		// This exit should not hit, but we exit abnormally in case something went wrong
260 		_exit(-1);
261 	}
262 }
263 
264 static void
__test_exit_reason_signal_with_audittoken(int signal)265 __test_exit_reason_signal_with_audittoken(int signal)
266 {
267 	int ret = 0;
268 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
269 	pid_t child = fork();
270 	if (child > 0) {
271 		audit_token_for_pid(child, &token);
272 		// Send signal to the child with its audit token
273 		ret = proc_signal_with_audittoken(&token, signal);
274 		wait_collect_exit_reason(child, signal);
275 		T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_with_audittoken return: %d", ret);
276 		// Send signal to the child with its audit token who has exited by now
277 		ret = proc_signal_with_audittoken(&token, signal);
278 		T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
279 	} else {
280 		pause();
281 		// This exit should not hit, but we exit abnormally in case something went wrong
282 		_exit(-1);
283 	}
284 }
285 
286 static void
__test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)287 __test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)
288 {
289 	int ret = 0;
290 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
291 	pid_t child = fork();
292 	if (child > 0) {
293 		audit_token_for_pid(child, &token);
294 		// Send signal to the child with its audit token, modified so pidversion is bad
295 		token.val[7] += 1;
296 		ret = proc_signal_with_audittoken(&token, signal);
297 		wait_with_timeout_expected(child, 2);
298 		T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
299 		// Cleanup child
300 		kill(child, signal);
301 	} else {
302 		pause();
303 	}
304 }
305 
306 static void
__test_exit_reason_delegated_signal_fail_bad_instigator_token(int signal)307 __test_exit_reason_delegated_signal_fail_bad_instigator_token(int signal)
308 {
309 	int ret = 0;
310 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
311 	audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
312 	pid_t child = fork();
313 	if (child > 0) {
314 		audit_token_for_pid(child, &token);
315 		ret = proc_signal_delegate(instigator, token, signal);
316 		wait_with_timeout_expected(child, 2);
317 		T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
318 		// Cleanup child
319 		kill(child, signal);
320 	} else {
321 		pause();
322 	}
323 }
324 
325 static void
__test_exit_reason_signal_with_audittoken_fail_null_token(int signal)326 __test_exit_reason_signal_with_audittoken_fail_null_token(int signal)
327 {
328 	int ret = 0;
329 	pid_t child = fork();
330 	if (child > 0) {
331 		// Send signal to the child with null audit token
332 		ret = proc_signal_with_audittoken(NULL, signal);
333 		wait_with_timeout_expected(child, 2);
334 		T_EXPECT_EQ_INT(ret, EINVAL, "expect null audit token return: %d", ret);
335 		// Cleanup child
336 		kill(child, signal);
337 	} else {
338 		pause();
339 	}
340 }
341 
342 static void
__test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)343 __test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)
344 {
345 	int ret = 0;
346 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
347 	pid_t child = fork();
348 	if (child > 0) {
349 		audit_token_for_pid(child, &token);
350 		ret = proc_signal_with_audittoken(&token, signal);
351 		wait_with_timeout_expected(child, 2);
352 		T_EXPECT_EQ_INT(ret, EINVAL, "expect invalid sig num return: %d", ret);
353 		kill(child, signal);
354 	} else {
355 		pause();
356 	}
357 }
358 
359 T_DECL(proc_signal_delegate_success, "proc_signal_delegate should work", T_META_TAG_VM_PREFERRED)
360 {
361 	dispatch_test(^{
362 		__test_exit_reason_delegate_signal(SIGABRT);
363 		__test_exit_reason_delegate_signal(SIGKILL);
364 		__test_exit_reason_delegate_signal(SIGSYS);
365 		__test_exit_reason_delegate_signal(SIGUSR1);
366 		__test_exit_reason_delegate_terminate();
367 		T_END;
368 	});
369 }
370 
371 T_DECL(proc_terminate_success, "proc_terminate should work", T_META_TAG_VM_PREFERRED)
372 {
373 	dispatch_test(^{
374 		__test_exit_reason_terminate();
375 		T_END;
376 	});
377 }
378 
379 T_DECL(proc_signal_with_audittoken_success, "proc_signal_with_audittoken should work", T_META_TAG_VM_PREFERRED)
380 {
381 	dispatch_test(^{
382 		__test_exit_reason_signal_with_audittoken(SIGABRT);
383 		__test_exit_reason_signal_with_audittoken(SIGKILL);
384 		__test_exit_reason_signal_with_audittoken(SIGSYS);
385 		__test_exit_reason_signal_with_audittoken(SIGUSR1);
386 		T_END;
387 	});
388 }
389 
390 T_DECL(proc_signal_with_audittoken_fail_bad_token, "proc_signal_with_audittoken should fail with invalid audit token", T_META_TAG_VM_PREFERRED)
391 {
392 	dispatch_test(^{
393 		__test_exit_reason_signal_with_audittoken_fail_bad_token(SIGKILL);
394 		T_END;
395 	});
396 }
397 
398 T_DECL(proc_delegated_signal_fail_bad_instigator_token, "proc_signal_delegated should fail with invalid instigator audit token", T_META_TAG_VM_PREFERRED)
399 {
400 	dispatch_test(^{
401 		__test_exit_reason_delegated_signal_fail_bad_instigator_token(SIGKILL);
402 		T_END;
403 	});
404 }
405 
406 T_DECL(proc_signal_with_audittoken_fail_null_token, "proc_signal_with_audittoken should fail with a null audit token", T_META_TAG_VM_PREFERRED)
407 {
408 	dispatch_test(^{
409 		__test_exit_reason_signal_with_audittoken_fail_null_token(SIGKILL);
410 		T_END;
411 	});
412 }
413 
414 T_DECL(proc_signal_with_audittoken_fail_bad_signal, "proc_signal_with_audittoken should fail with invalid signals", T_META_TAG_VM_PREFERRED)
415 {
416 	dispatch_test(^{
417 		__test_exit_reason_signal_with_audittoken_fail_bad_signal(0);
418 		__test_exit_reason_signal_with_audittoken_fail_bad_signal(NSIG + 1);
419 		T_END;
420 	});
421 }
422 
423 T_DECL(test_exit_reason_external_signal, "tests exit reason for external signals", T_META_TAG_VM_PREFERRED)
424 {
425 	dispatch_test(^{
426 		__test_exit_reason_external_signal(SIGABRT);
427 		__test_exit_reason_external_signal(SIGKILL);
428 		__test_exit_reason_external_signal(SIGSYS);
429 		__test_exit_reason_external_signal(SIGUSR1);
430 		T_END;
431 	});
432 }
433 
434 struct pthread_kill_helper_args {
435 	pthread_t *pthread;
436 	int signal;
437 };
438 
439 static void *_Nullable
pthread_kill_helper(void * _Nullable msg)440 pthread_kill_helper(void *_Nullable msg)
441 {
442 	struct pthread_kill_helper_args *args = (struct pthread_kill_helper_args *)msg;
443 	pthread_kill(*args->pthread, args->signal);
444 	return NULL;
445 }
446 
447 static void
__test_exit_reason_pthread_kill_self(int signal)448 __test_exit_reason_pthread_kill_self(int signal)
449 {
450 	T_LOG("Testing pthread_kill for signal %d", signal);
451 	pid_t child = fork();
452 	if (child > 0) {
453 		wait_collect_exit_reason(child, signal);
454 	} else {
455 		pthread_t t;
456 		struct pthread_kill_helper_args args = {&t, signal};
457 		pthread_create(&t, NULL, pthread_kill_helper, (void *)&args);
458 		pthread_join(t, NULL);
459 	}
460 }
461 
462 T_DECL(test_exit_reason_pthread_kill_self, "tests exit reason for pthread_kill on caller thread", T_META_TAG_VM_PREFERRED)
463 {
464 	dispatch_test(^{
465 		__test_exit_reason_pthread_kill_self(SIGABRT);
466 		__test_exit_reason_pthread_kill_self(SIGKILL);
467 		__test_exit_reason_pthread_kill_self(SIGSYS);
468 		__test_exit_reason_pthread_kill_self(SIGUSR1);
469 		T_END;
470 	});
471 }
472