xref: /xnu-10002.61.3/tests/signal_exit_reason.c (revision 0f4c859e951fba394238ab619495c4e1d54d0f34)
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 
43 T_GLOBAL_META(
44 	T_META_NAMESPACE("xnu.misc"),
45 	T_META_RADAR_COMPONENT_NAME("xnu"),
46 	T_META_RADAR_COMPONENT_VERSION("misc"));
47 
48 static dispatch_queue_t exit_queue;
49 
50 static void
cleanup(void)51 cleanup(void)
52 {
53 	dispatch_release(exit_queue);
54 }
55 
56 static void
57 dispatch_test(void (^test)(void))
58 {
59 	// Use dispatch to schedule DISPATCH_PROC_EXIT blocks to read out exit reasons
60 	exit_queue = dispatch_queue_create("exit queue", DISPATCH_QUEUE_SERIAL);
61 	dispatch_async(dispatch_get_main_queue(), ^{
62 		test();
63 	});
64 	T_ATEND(cleanup);
65 	dispatch_main();
66 }
67 
68 static bool
audit_token_for_pid(pid_t pid,audit_token_t * token)69 audit_token_for_pid(pid_t pid, audit_token_t *token)
70 {
71 	kern_return_t err;
72 	task_name_t task_name = TASK_NAME_NULL;
73 	mach_msg_type_number_t info_size = TASK_AUDIT_TOKEN_COUNT;
74 
75 	err = task_name_for_pid(mach_task_self(), pid, &task_name);
76 	if (err != KERN_SUCCESS) {
77 		T_LOG("task_for_pid returned %d\n", err);
78 		return false;
79 	}
80 
81 	err = task_info(task_name, TASK_AUDIT_TOKEN, (integer_t *)token, &info_size);
82 	if (err != KERN_SUCCESS) {
83 		T_LOG("task_info returned %d\n", err);
84 		return false;
85 	}
86 
87 	return true;
88 }
89 
90 static void
check_exit_reason(int pid,uint64_t expected_reason_namespace,uint64_t expected_signal)91 check_exit_reason(int pid, uint64_t expected_reason_namespace, uint64_t expected_signal)
92 {
93 	T_LOG("check_exit_reason %d", expected_signal);
94 	int ret, status;
95 	struct proc_exitreasonbasicinfo exit_reason;
96 
97 	T_QUIET; T_ASSERT_POSIX_SUCCESS(
98 		ret = proc_pidinfo(pid, PROC_PIDEXITREASONBASICINFO, 1, &exit_reason, PROC_PIDEXITREASONBASICINFOSIZE),
99 		"verify proc_pidinfo success"
100 		);
101 	T_QUIET; T_ASSERT_EQ(ret, PROC_PIDEXITREASONBASICINFOSIZE, "retrieve basic exit reason info");
102 
103 	waitpid(pid, &status, 0);
104 	T_QUIET; T_EXPECT_FALSE(WIFEXITED(status), "process did not exit normally");
105 	T_QUIET; T_EXPECT_TRUE(WIFSIGNALED(status), "process was terminated because of a signal");
106 	T_QUIET; T_EXPECT_EQ(WTERMSIG(status), expected_signal, "process should terminate due to signal %llu", expected_signal);
107 
108 	T_EXPECT_EQ(exit_reason.beri_namespace, expected_reason_namespace, "expect OS_REASON_SIGNAL");
109 	T_EXPECT_EQ(exit_reason.beri_code, expected_signal, "expect reason code: %llu", expected_signal);
110 }
111 
112 static void
wait_collect_exit_reason(int pid,int signal)113 wait_collect_exit_reason(int pid, int signal)
114 {
115 	dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
116 	dispatch_semaphore_t sem = dispatch_semaphore_create(0);
117 	dispatch_source_set_event_handler(ds_proc, ^{
118 		check_exit_reason(pid, OS_REASON_SIGNAL, signal);
119 		dispatch_semaphore_signal(sem);
120 	});
121 	dispatch_activate(ds_proc);
122 
123 	// Wait till exit reason is processed
124 	dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
125 	dispatch_release(ds_proc);
126 	dispatch_release(sem);
127 }
128 
129 static void
wait_with_timeout_expected(int pid,int seconds)130 wait_with_timeout_expected(int pid, int seconds)
131 {
132 	long timeout = 0;
133 	dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
134 	dispatch_semaphore_t sem = dispatch_semaphore_create(0);
135 	dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);;
136 	dispatch_source_set_event_handler(ds_proc, ^{
137 		dispatch_semaphore_signal(sem);
138 	});
139 	dispatch_activate(ds_proc);
140 
141 	// Wait till exit reason is processed or timeout
142 	timeout = dispatch_semaphore_wait(sem, milestone);
143 	T_QUIET; T_EXPECT_TRUE(timeout != 0, "process exited and was not expected to");
144 	dispatch_release(ds_proc);
145 	dispatch_release(sem);
146 }
147 
148 static void
__test_exit_reason_abort()149 __test_exit_reason_abort()
150 {
151 	pid_t child = fork();
152 	if (child > 0) {
153 		wait_collect_exit_reason(child, SIGABRT);
154 	} else {
155 		abort();
156 	}
157 }
158 
159 T_DECL(test_exit_reason_abort, "tests exit reason for abort()")
160 {
161 	dispatch_test(^{
162 		__test_exit_reason_abort();
163 		T_END;
164 	});
165 }
166 
167 static void
__test_exit_reason_external_signal(int signal)168 __test_exit_reason_external_signal(int signal)
169 {
170 	T_LOG("Testing external signal %d", signal);
171 	pid_t child = fork();
172 	if (child > 0) {
173 		// Send external signal
174 		kill(child, signal);
175 		wait_collect_exit_reason(child, signal);
176 	} else {
177 		pause();
178 	}
179 }
180 
181 static void
__test_exit_reason_signal_with_audittoken(int signal)182 __test_exit_reason_signal_with_audittoken(int signal)
183 {
184 	int ret = 0;
185 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
186 	pid_t child = fork();
187 	if (child > 0) {
188 		audit_token_for_pid(child, &token);
189 		// Send signal to the child with its audit token
190 		ret = proc_signal_with_audittoken(&token, signal);
191 		wait_collect_exit_reason(child, signal);
192 		T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_with_audittoken return: %d", ret);
193 		// Send signal to the child with its audit token who has exited by now
194 		ret = proc_signal_with_audittoken(&token, signal);
195 		T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
196 	} else {
197 		pause();
198 		// This exit should not hit, but we exit abnormally in case something went wrong
199 		_exit(-1);
200 	}
201 }
202 
203 static void
__test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)204 __test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)
205 {
206 	int ret = 0;
207 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
208 	pid_t child = fork();
209 	if (child > 0) {
210 		audit_token_for_pid(child, &token);
211 		// Send signal to the child with its audit token, modified so pidversion is bad
212 		token.val[7] += 1;
213 		ret = proc_signal_with_audittoken(&token, signal);
214 		wait_with_timeout_expected(child, 2);
215 		T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
216 		// Cleanup child
217 		kill(child, signal);
218 	} else {
219 		pause();
220 	}
221 }
222 
223 static void
__test_exit_reason_signal_with_audittoken_fail_null_token(int signal)224 __test_exit_reason_signal_with_audittoken_fail_null_token(int signal)
225 {
226 	int ret = 0;
227 	pid_t child = fork();
228 	if (child > 0) {
229 		// Send signal to the child with null audit token
230 		ret = proc_signal_with_audittoken(NULL, signal);
231 		wait_with_timeout_expected(child, 2);
232 		T_EXPECT_EQ_INT(ret, EINVAL, "expect null audit token return: %d", ret);
233 		// Cleanup child
234 		kill(child, signal);
235 	} else {
236 		pause();
237 	}
238 }
239 
240 static void
__test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)241 __test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)
242 {
243 	int ret = 0;
244 	audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
245 	pid_t child = fork();
246 	if (child > 0) {
247 		audit_token_for_pid(child, &token);
248 		ret = proc_signal_with_audittoken(&token, signal);
249 		wait_with_timeout_expected(child, 2);
250 		T_EXPECT_EQ_INT(ret, EINVAL, "expect invalid sig num return: %d", ret);
251 		kill(child, signal);
252 	} else {
253 		pause();
254 	}
255 }
256 
257 T_DECL(proc_signal_with_audittoken_success, "proc_signal_with_audittoken should work")
258 {
259 	dispatch_test(^{
260 		__test_exit_reason_signal_with_audittoken(SIGABRT);
261 		__test_exit_reason_signal_with_audittoken(SIGKILL);
262 		__test_exit_reason_signal_with_audittoken(SIGSYS);
263 		__test_exit_reason_signal_with_audittoken(SIGUSR1);
264 		T_END;
265 	});
266 }
267 
268 T_DECL(proc_signal_with_audittoken_fail_bad_token, "proc_signal_with_audittoken should fail with invalid audit token")
269 {
270 	dispatch_test(^{
271 		__test_exit_reason_signal_with_audittoken_fail_bad_token(SIGKILL);
272 		T_END;
273 	});
274 }
275 
276 T_DECL(proc_signal_with_audittoken_fail_null_token, "proc_signal_with_audittoken should fail with a null audit token")
277 {
278 	dispatch_test(^{
279 		__test_exit_reason_signal_with_audittoken_fail_null_token(SIGKILL);
280 		T_END;
281 	});
282 }
283 
284 T_DECL(proc_signal_with_audittoken_fail_bad_signal, "proc_signal_with_audittoken should fail with invalid signals")
285 {
286 	dispatch_test(^{
287 		__test_exit_reason_signal_with_audittoken_fail_bad_signal(0);
288 		__test_exit_reason_signal_with_audittoken_fail_bad_signal(NSIG + 1);
289 		T_END;
290 	});
291 }
292 
293 T_DECL(test_exit_reason_external_signal, "tests exit reason for external signals")
294 {
295 	dispatch_test(^{
296 		__test_exit_reason_external_signal(SIGABRT);
297 		__test_exit_reason_external_signal(SIGKILL);
298 		__test_exit_reason_external_signal(SIGSYS);
299 		__test_exit_reason_external_signal(SIGUSR1);
300 		T_END;
301 	});
302 }
303 
304 struct pthread_kill_helper_args {
305 	pthread_t *pthread;
306 	int signal;
307 };
308 
309 static void
pthread_kill_helper(void * msg)310 pthread_kill_helper(void *msg)
311 {
312 	struct pthread_kill_helper_args *args = (struct pthread_kill_helper_args *)msg;
313 	pthread_kill(*args->pthread, args->signal);
314 }
315 
316 static void
__test_exit_reason_pthread_kill_self(int signal)317 __test_exit_reason_pthread_kill_self(int signal)
318 {
319 	T_LOG("Testing pthread_kill for signal %d", signal);
320 	pid_t child = fork();
321 	if (child > 0) {
322 		wait_collect_exit_reason(child, signal);
323 	} else {
324 		pthread_t t;
325 		struct pthread_kill_helper_args args = {&t, signal};
326 		pthread_create(&t, NULL, pthread_kill_helper, (void *)&args);
327 		pthread_join(t, NULL);
328 	}
329 }
330 
331 T_DECL(test_exit_reason_pthread_kill_self, "tests exit reason for pthread_kill on caller thread")
332 {
333 	dispatch_test(^{
334 		__test_exit_reason_pthread_kill_self(SIGABRT);
335 		__test_exit_reason_pthread_kill_self(SIGKILL);
336 		__test_exit_reason_pthread_kill_self(SIGSYS);
337 		__test_exit_reason_pthread_kill_self(SIGUSR1);
338 		T_END;
339 	});
340 }
341