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 if (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 }
233 } else {
234 pause();
235 // This exit should not hit, but we exit abnormally in case something went wrong
236 _exit(-1);
237 }
238 }
239
240 static void
__test_exit_reason_terminate()241 __test_exit_reason_terminate()
242 {
243 int ret = 0;
244 pid_t child = fork();
245 int sentsignal = 0;
246 if (child > 0) {
247 // Send signal to the child with its audit token
248 ret = proc_terminate(child, &sentsignal);
249 T_EXPECT_EQ_INT(ret, 0, "expect proc_terminate_delegate return: %d", ret);
250 T_EXPECT_TRUE(sentsignal == SIGTERM || sentsignal == SIGKILL, "sentsignal retval %d", sentsignal);
251 wait_collect_exit_reason(child, SIGTERM);
252 // Send signal to the child with its audit token who has exited by now
253 ret = proc_terminate(child, &sentsignal);
254 T_EXPECT_EQ_INT(ret, ESRCH, "expected no such process return: %d", ret);
255 // Terminating PID 1 should fail with EPERM
256 ret = proc_terminate(1, &sentsignal);
257 T_EXPECT_EQ_INT(ret, EPERM, "expected eperm return: %d", ret);
258 } else {
259 pause();
260 // This exit should not hit, but we exit abnormally in case something went wrong
261 _exit(-1);
262 }
263 }
264
265 static void
__test_exit_reason_signal_with_audittoken(int signal)266 __test_exit_reason_signal_with_audittoken(int signal)
267 {
268 int ret = 0;
269 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
270 pid_t child = fork();
271 if (child > 0) {
272 audit_token_for_pid(child, &token);
273 // Send signal to the child with its audit token
274 ret = proc_signal_with_audittoken(&token, signal);
275 wait_collect_exit_reason(child, signal);
276 T_EXPECT_EQ_INT(ret, 0, "expect proc_signal_with_audittoken return: %d", ret);
277 // Send signal to the child with its audit token who has exited by now
278 ret = proc_signal_with_audittoken(&token, signal);
279 T_EXPECT_EQ_INT(ret, ESRCH, "expect no such process return: %d", ret);
280 } else {
281 pause();
282 // This exit should not hit, but we exit abnormally in case something went wrong
283 _exit(-1);
284 }
285 }
286
287 static void
__test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)288 __test_exit_reason_signal_with_audittoken_fail_bad_token(int signal)
289 {
290 int ret = 0;
291 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
292 pid_t child = fork();
293 if (child > 0) {
294 audit_token_for_pid(child, &token);
295 // Send signal to the child with its audit token, modified so pidversion is bad
296 token.val[7] += 1;
297 ret = proc_signal_with_audittoken(&token, signal);
298 wait_with_timeout_expected(child, 2);
299 T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
300 // Cleanup child
301 kill(child, signal);
302 } else {
303 pause();
304 }
305 }
306
307 static void
__test_exit_reason_delegated_signal_fail_bad_instigator_token(int signal)308 __test_exit_reason_delegated_signal_fail_bad_instigator_token(int signal)
309 {
310 int ret = 0;
311 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
312 audit_token_t instigator = INVALID_AUDIT_TOKEN_VALUE;
313 pid_t child = fork();
314 if (child > 0) {
315 audit_token_for_pid(child, &token);
316 ret = proc_signal_delegate(instigator, token, signal);
317 wait_with_timeout_expected(child, 2);
318 T_EXPECT_EQ_INT(ret, ESRCH, "expect bad audit token return: %d", ret);
319 // Cleanup child
320 kill(child, signal);
321 } else {
322 pause();
323 }
324 }
325
326 static void
__test_exit_reason_signal_with_audittoken_fail_null_token(int signal)327 __test_exit_reason_signal_with_audittoken_fail_null_token(int signal)
328 {
329 int ret = 0;
330 pid_t child = fork();
331 if (child > 0) {
332 // Send signal to the child with null audit token
333 ret = proc_signal_with_audittoken(NULL, signal);
334 wait_with_timeout_expected(child, 2);
335 T_EXPECT_EQ_INT(ret, EINVAL, "expect null audit token return: %d", ret);
336 // Cleanup child
337 kill(child, signal);
338 } else {
339 pause();
340 }
341 }
342
343 static void
__test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)344 __test_exit_reason_signal_with_audittoken_fail_bad_signal(int signal)
345 {
346 int ret = 0;
347 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
348 pid_t child = fork();
349 if (child > 0) {
350 audit_token_for_pid(child, &token);
351 ret = proc_signal_with_audittoken(&token, signal);
352 wait_with_timeout_expected(child, 2);
353 T_EXPECT_EQ_INT(ret, EINVAL, "expect invalid sig num return: %d", ret);
354 kill(child, signal);
355 } else {
356 pause();
357 }
358 }
359
360 // Required signal handler for sigwait to work properly
361 static void
null_signal_handler(int sig)362 null_signal_handler(int sig)
363 {
364 }
365
366 static void
__test_signal_zombie(void)367 __test_signal_zombie(void)
368 {
369 pid_t child;
370 sigset_t set;
371 sigset_t oldset;
372 int sig = 0, ret = 0;
373 audit_token_t token = INVALID_AUDIT_TOKEN_VALUE;
374
375 // Set signal handler
376 signal(SIGCHLD, &null_signal_handler);
377
378 // Mask SIGCHLD so it becomes pending
379 // when the child dies.
380 sigemptyset(&set);
381 sigaddset(&set, SIGCHLD);
382 sigprocmask(SIG_BLOCK, &set, &oldset);
383
384 // Immediately exit child
385 if ((child = fork()) == 0) {
386 sleep(1);
387 exit(0);
388 }
389
390 // Calculate target audit token
391 T_EXPECT_TRUE(audit_token_for_pid(child, &token), "audit token determined");
392
393 // Wait for kernel to notify us of a dead child. Which means it's now in a
394 // zombie state.
395 sigwait(&set, &sig);
396
397 // Restore process mask
398 sigprocmask(SIG_SETMASK, &oldset, NULL);
399
400 // First test that kill succeeds for POSIX compliance
401 T_EXPECT_EQ_INT(kill(child, 0), 0, "kill() suceeds on a zombie");
402
403 // Then test that the proc_info path has a sensible error code
404 ret = proc_signal_with_audittoken(&token, SIGHUP);
405 T_EXPECT_EQ_INT(ret, ESRCH, "expect invalid sig num return: %d", ret);
406
407 // Cleanup zombie child
408 wait_with_timeout_expected(child, 0);
409 }
410
411
412 T_DECL(signal_zombie, "signaling a zombie should work", T_META_TAG_VM_PREFERRED)
413 {
414 dispatch_test(^{
415 __test_signal_zombie();
416 T_END;
417 });
418 }
419
420 T_DECL(proc_signal_delegate_success, "proc_signal_delegate should work",
421 T_META_TAG_VM_PREFERRED,
422 T_META_ENABLED(false) /* rdar://146369624 */)
423 {
424 dispatch_test(^{
425 __test_exit_reason_delegate_signal(SIGABRT);
426 __test_exit_reason_delegate_signal(SIGKILL);
427 __test_exit_reason_delegate_signal(SIGSYS);
428 __test_exit_reason_delegate_signal(SIGUSR1);
429 __test_exit_reason_delegate_terminate();
430 T_END;
431 });
432 }
433
434 T_DECL(proc_terminate_success, "proc_terminate should work", T_META_TAG_VM_PREFERRED)
435 {
436 dispatch_test(^{
437 __test_exit_reason_terminate();
438 T_END;
439 });
440 }
441
442 T_DECL(proc_signal_with_audittoken_success, "proc_signal_with_audittoken should work", T_META_TAG_VM_PREFERRED)
443 {
444 dispatch_test(^{
445 __test_exit_reason_signal_with_audittoken(SIGABRT);
446 __test_exit_reason_signal_with_audittoken(SIGKILL);
447 __test_exit_reason_signal_with_audittoken(SIGSYS);
448 __test_exit_reason_signal_with_audittoken(SIGUSR1);
449 T_END;
450 });
451 }
452
453 T_DECL(proc_signal_with_audittoken_fail_bad_token, "proc_signal_with_audittoken should fail with invalid audit token", T_META_TAG_VM_PREFERRED)
454 {
455 dispatch_test(^{
456 __test_exit_reason_signal_with_audittoken_fail_bad_token(SIGKILL);
457 T_END;
458 });
459 }
460
461 T_DECL(proc_delegated_signal_fail_bad_instigator_token, "proc_signal_delegated should fail with invalid instigator audit token", T_META_TAG_VM_PREFERRED)
462 {
463 dispatch_test(^{
464 __test_exit_reason_delegated_signal_fail_bad_instigator_token(SIGKILL);
465 T_END;
466 });
467 }
468
469 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)
470 {
471 dispatch_test(^{
472 __test_exit_reason_signal_with_audittoken_fail_null_token(SIGKILL);
473 T_END;
474 });
475 }
476
477 T_DECL(proc_signal_with_audittoken_fail_bad_signal, "proc_signal_with_audittoken should fail with invalid signals", T_META_TAG_VM_PREFERRED)
478 {
479 dispatch_test(^{
480 __test_exit_reason_signal_with_audittoken_fail_bad_signal(0);
481 __test_exit_reason_signal_with_audittoken_fail_bad_signal(NSIG + 1);
482 T_END;
483 });
484 }
485
486 T_DECL(test_exit_reason_external_signal, "tests exit reason for external signals", T_META_TAG_VM_PREFERRED)
487 {
488 dispatch_test(^{
489 __test_exit_reason_external_signal(SIGABRT);
490 __test_exit_reason_external_signal(SIGKILL);
491 __test_exit_reason_external_signal(SIGSYS);
492 __test_exit_reason_external_signal(SIGUSR1);
493 T_END;
494 });
495 }
496
497 struct pthread_kill_helper_args {
498 pthread_t *pthread;
499 int signal;
500 };
501
502 static void *_Nullable
pthread_kill_helper(void * _Nullable msg)503 pthread_kill_helper(void *_Nullable msg)
504 {
505 struct pthread_kill_helper_args *args = (struct pthread_kill_helper_args *)msg;
506 pthread_kill(*args->pthread, args->signal);
507 return NULL;
508 }
509
510 static void
__test_exit_reason_pthread_kill_self(int signal)511 __test_exit_reason_pthread_kill_self(int signal)
512 {
513 T_LOG("Testing pthread_kill for signal %d", signal);
514 pid_t child = fork();
515 if (child > 0) {
516 wait_collect_exit_reason(child, signal);
517 } else {
518 pthread_t t;
519 struct pthread_kill_helper_args args = {&t, signal};
520 pthread_create(&t, NULL, (void*(*)(void*))pthread_kill_helper, (void *)&args);
521 pthread_join(t, NULL);
522 }
523 }
524
525 T_DECL(test_exit_reason_pthread_kill_self, "tests exit reason for pthread_kill on caller thread", T_META_TAG_VM_PREFERRED)
526 {
527 dispatch_test(^{
528 __test_exit_reason_pthread_kill_self(SIGABRT);
529 __test_exit_reason_pthread_kill_self(SIGKILL);
530 __test_exit_reason_pthread_kill_self(SIGSYS);
531 __test_exit_reason_pthread_kill_self(SIGUSR1);
532 T_END;
533 });
534 }
535