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