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 T_QUIET; T_ASSERT_POSIX_SUCCESS(
105 ret = proc_pidinfo(pid, PROC_PIDEXITREASONBASICINFO, 1, &exit_reason, PROC_PIDEXITREASONBASICINFOSIZE),
106 "verify proc_pidinfo success"
107 );
108 T_QUIET; T_ASSERT_EQ(ret, PROC_PIDEXITREASONBASICINFOSIZE, "retrieve basic exit reason info");
109
110 waitpid(pid, &status, 0);
111 T_QUIET; T_EXPECT_FALSE(WIFEXITED(status), "process did not exit normally");
112 T_QUIET; T_EXPECT_TRUE(WIFSIGNALED(status), "process was terminated because of a signal");
113 T_QUIET; T_EXPECT_EQ(WTERMSIG(status), expected_signal, "process should terminate due to signal %llu", expected_signal);
114
115 T_EXPECT_EQ(exit_reason.beri_namespace, expected_reason_namespace, "expect OS_REASON_SIGNAL");
116 T_EXPECT_EQ(exit_reason.beri_code, expected_signal, "expect reason code: %llu", expected_signal);
117 }
118
119 static void
wait_collect_exit_reason(int pid,int signal)120 wait_collect_exit_reason(int pid, int signal)
121 {
122 dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
123 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
124 dispatch_source_set_event_handler(ds_proc, ^{
125 check_exit_reason(pid, OS_REASON_SIGNAL, signal);
126 dispatch_semaphore_signal(sem);
127 });
128 dispatch_activate(ds_proc);
129
130 // Wait till exit reason is processed
131 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
132 dispatch_release(ds_proc);
133 dispatch_release(sem);
134 }
135
136 static void
wait_with_timeout_expected(int pid,int seconds)137 wait_with_timeout_expected(int pid, int seconds)
138 {
139 long timeout = 0;
140 dispatch_source_t ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, exit_queue);
141 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
142 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);;
143 dispatch_source_set_event_handler(ds_proc, ^{
144 dispatch_semaphore_signal(sem);
145 });
146 dispatch_activate(ds_proc);
147
148 // Wait till exit reason is processed or timeout
149 timeout = dispatch_semaphore_wait(sem, milestone);
150 T_QUIET; T_EXPECT_TRUE(timeout != 0, "process exited and was not expected to");
151 dispatch_release(ds_proc);
152 dispatch_release(sem);
153 }
154
155 static void
__test_exit_reason_abort()156 __test_exit_reason_abort()
157 {
158 pid_t child = fork();
159 if (child > 0) {
160 wait_collect_exit_reason(child, SIGABRT);
161 } else {
162 abort();
163 }
164 }
165
166 T_DECL(test_exit_reason_abort, "tests exit reason for abort()", T_META_TAG_VM_PREFERRED)
167 {
168 dispatch_test(^{
169 __test_exit_reason_abort();
170 T_END;
171 });
172 }
173
174 static void
__test_exit_reason_external_signal(int signal)175 __test_exit_reason_external_signal(int signal)
176 {
177 T_LOG("Testing external signal %d", signal);
178 pid_t child = fork();
179 if (child > 0) {
180 // Send external signal
181 kill(child, signal);
182 wait_collect_exit_reason(child, signal);
183 } else {
184 pause();
185 }
186 }
187
188 static void
__test_exit_reason_delegate_signal(int signal)189 __test_exit_reason_delegate_signal(int signal)
190 {
191 int ret = 0;
192 audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
193 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
194 pid_t child = fork();
195 if (child > 0) {
196 audit_token_for_pid(child, &token);
197 // Send signal to the child with its audit token
198 ret = proc_signal_delegate(instigator, token, signal);
199 T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_delegate return: %d", ret);
200 wait_collect_exit_reason(child, signal);
201 // Send signal to the child with its audit token who has exited by now
202 ret = proc_signal_delegate(instigator, token, signal);
203 T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
204 } else {
205 pause();
206 // This exit should not hit, but we exit abnormally in case something went wrong
207 _exit(-1);
208 }
209 }
210
211 static void
__test_exit_reason_delegate_terminate()212 __test_exit_reason_delegate_terminate()
213 {
214 int ret = 0;
215 audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
216 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
217 pid_t child = fork();
218 int sentsignal = 0;
219 if (child > 0) {
220 audit_token_for_pid(child, &token);
221 // Send signal to the child with its audit token
222 ret = proc_terminate_delegate(instigator, token, &sentsignal);
223 T_EXPECT_EQ_INT(ret, 0, "expect proc_terminate_delegate return: %d", ret);
224 T_EXPECT_TRUE(sentsignal == SIGTERM || sentsignal == SIGKILL, "sentsignal retval %d", sentsignal);
225 wait_collect_exit_reason(child, SIGTERM);
226 // Send signal to the child with its audit token who has exited by now
227 ret = proc_terminate_delegate(instigator, token, &sentsignal);
228 T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
229 } else {
230 pause();
231 // This exit should not hit, but we exit abnormally in case something went wrong
232 _exit(-1);
233 }
234 }
235
236 static void
__test_exit_reason_signal_with_audittoken(int signal)237 __test_exit_reason_signal_with_audittoken(int signal)
238 {
239 int ret = 0;
240 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
241 pid_t child = fork();
242 if (child > 0) {
243 audit_token_for_pid(child, &token);
244 // Send signal to the child with its audit token
245 ret = proc_signal_with_audittoken(&token, signal);
246 wait_collect_exit_reason(child, signal);
247 T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_with_audittoken return: %d", ret);
248 // Send signal to the child with its audit token who has exited by now
249 ret = proc_signal_with_audittoken(&token, signal);
250 T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
251 } else {
252 pause();
253 // This exit should not hit, but we exit abnormally in case something went wrong
254 _exit(-1);
255 }
256 }
257
258 static void
__test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)259 __test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)
260 {
261 int ret = 0;
262 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
263 pid_t child = fork();
264 if (child > 0) {
265 audit_token_for_pid(child, &token);
266 // Send signal to the child with its audit token, modified so pidversion is bad
267 token.val[7] += 1;
268 ret = proc_signal_with_audittoken(&token, signal);
269 wait_with_timeout_expected(child, 2);
270 T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
271 // Cleanup child
272 kill(child, signal);
273 } else {
274 pause();
275 }
276 }
277
278 static void
__test_exit_reason_signal_with_audittoken_fail_null_token(int signal)279 __test_exit_reason_signal_with_audittoken_fail_null_token(int signal)
280 {
281 int ret = 0;
282 pid_t child = fork();
283 if (child > 0) {
284 // Send signal to the child with null audit token
285 ret = proc_signal_with_audittoken(NULL, signal);
286 wait_with_timeout_expected(child, 2);
287 T_EXPECT_EQ_INT(ret, EINVAL, "expect null audit token return: %d", ret);
288 // Cleanup child
289 kill(child, signal);
290 } else {
291 pause();
292 }
293 }
294
295 static void
__test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)296 __test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)
297 {
298 int ret = 0;
299 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
300 pid_t child = fork();
301 if (child > 0) {
302 audit_token_for_pid(child, &token);
303 ret = proc_signal_with_audittoken(&token, signal);
304 wait_with_timeout_expected(child, 2);
305 T_EXPECT_EQ_INT(ret, EINVAL, "expect invalid sig num return: %d", ret);
306 kill(child, signal);
307 } else {
308 pause();
309 }
310 }
311
312 T_DECL(proc_signal_delegate_success, "proc_signal_delegate should work", T_META_TAG_VM_PREFERRED)
313 {
314 dispatch_test(^{
315 __test_exit_reason_delegate_signal(SIGABRT);
316 __test_exit_reason_delegate_signal(SIGKILL);
317 __test_exit_reason_delegate_signal(SIGSYS);
318 __test_exit_reason_delegate_signal(SIGUSR1);
319 __test_exit_reason_delegate_terminate();
320 T_END;
321 });
322 }
323
324 T_DECL(proc_signal_with_audittoken_success, "proc_signal_with_audittoken should work", T_META_TAG_VM_PREFERRED)
325 {
326 dispatch_test(^{
327 __test_exit_reason_signal_with_audittoken(SIGABRT);
328 __test_exit_reason_signal_with_audittoken(SIGKILL);
329 __test_exit_reason_signal_with_audittoken(SIGSYS);
330 __test_exit_reason_signal_with_audittoken(SIGUSR1);
331 T_END;
332 });
333 }
334
335 T_DECL(proc_signal_with_audittoken_fail_bad_token, "proc_signal_with_audittoken should fail with invalid audit token", T_META_TAG_VM_PREFERRED)
336 {
337 dispatch_test(^{
338 __test_exit_reason_signal_with_audittoken_fail_bad_token(SIGKILL);
339 T_END;
340 });
341 }
342
343 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)
344 {
345 dispatch_test(^{
346 __test_exit_reason_signal_with_audittoken_fail_null_token(SIGKILL);
347 T_END;
348 });
349 }
350
351 T_DECL(proc_signal_with_audittoken_fail_bad_signal, "proc_signal_with_audittoken should fail with invalid signals", T_META_TAG_VM_PREFERRED)
352 {
353 dispatch_test(^{
354 __test_exit_reason_signal_with_audittoken_fail_bad_signal(0);
355 __test_exit_reason_signal_with_audittoken_fail_bad_signal(NSIG + 1);
356 T_END;
357 });
358 }
359
360 T_DECL(test_exit_reason_external_signal, "tests exit reason for external signals", T_META_TAG_VM_PREFERRED)
361 {
362 dispatch_test(^{
363 __test_exit_reason_external_signal(SIGABRT);
364 __test_exit_reason_external_signal(SIGKILL);
365 __test_exit_reason_external_signal(SIGSYS);
366 __test_exit_reason_external_signal(SIGUSR1);
367 T_END;
368 });
369 }
370
371 struct pthread_kill_helper_args {
372 pthread_t *pthread;
373 int signal;
374 };
375
376 static void
pthread_kill_helper(void * msg)377 pthread_kill_helper(void *msg)
378 {
379 struct pthread_kill_helper_args *args = (struct pthread_kill_helper_args *)msg;
380 pthread_kill(*args->pthread, args->signal);
381 }
382
383 static void
__test_exit_reason_pthread_kill_self(int signal)384 __test_exit_reason_pthread_kill_self(int signal)
385 {
386 T_LOG("Testing pthread_kill for signal %d", signal);
387 pid_t child = fork();
388 if (child > 0) {
389 wait_collect_exit_reason(child, signal);
390 } else {
391 pthread_t t;
392 struct pthread_kill_helper_args args = {&t, signal};
393 pthread_create(&t, NULL, pthread_kill_helper, (void *)&args);
394 pthread_join(t, NULL);
395 }
396 }
397
398 T_DECL(test_exit_reason_pthread_kill_self, "tests exit reason for pthread_kill on caller thread", T_META_TAG_VM_PREFERRED)
399 {
400 dispatch_test(^{
401 __test_exit_reason_pthread_kill_self(SIGABRT);
402 __test_exit_reason_pthread_kill_self(SIGKILL);
403 __test_exit_reason_pthread_kill_self(SIGSYS);
404 __test_exit_reason_pthread_kill_self(SIGUSR1);
405 T_END;
406 });
407 }
408