1 /*
2 * Copyright (c) 2025 Apple Computer, 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 #include <darwintest.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <mach/exception_types.h>
33 #include <sys/wait.h>
34 #include <sys/sysctl.h>
35 #include <sys/code_signing.h>
36 #include <signal.h>
37 #include <sys/ptrace.h>
38 #include <sys/mman.h>
39 #include <fcntl.h>
40 #include <os/crashlog_private.h>
41
42 #include "exc_helpers.h"
43 #include "test_utils.h"
44
45 T_GLOBAL_META(
46 T_META_NAMESPACE("xnu"),
47 T_META_RADAR_COMPONENT_NAME("xnu"),
48 T_META_RADAR_COMPONENT_VERSION("arm"),
49 T_META_OWNER("p_tennen")
50 );
51
52 static size_t
exception_handler_expect_not_called(mach_port_t task __unused,mach_port_t thread __unused,exception_type_t type __unused,mach_exception_data_t codes __unused)53 exception_handler_expect_not_called(mach_port_t task __unused, mach_port_t thread __unused,
54 exception_type_t type __unused, mach_exception_data_t codes __unused)
55 {
56 T_ASSERT_FAIL("kernel ran exception handler instead of terminating process");
57 return 0;
58 }
59
60 static void
signal_handler_expect_not_called(int sig,siginfo_t * sip __unused,void * ucontext __unused)61 signal_handler_expect_not_called(int sig, siginfo_t *sip __unused, void *ucontext __unused)
62 {
63 T_FAIL("kernel dispatched signal handler instead of terminating process");
64 }
65
66 T_DECL(uncatchable_fatal_trap_developer_mode_disabled,
67 "Ensure a maybe-unrecoverable trap label is uncatchable with !developer_mode",
68 T_META_REQUIRES_SYSCTL_EQ("security.mac.amfi.developer_mode_status", 0),
69 T_META_ENABLED(TARGET_CPU_ARM64)
70 )
71 {
72 /* Given a child process that sets up some mechanisms to catch an exception/signal */
73 /* And developer mode is disabled and we're not being debugged */
74 pid_t pid = fork();
75 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "fork");
76
77 if (pid == 0) {
78 /*
79 * Try to catch the exception in two ways:
80 * - via setting up a Mach exception handler, and
81 * - via sigaction
82 */
83 mach_port_t exc_port = create_exception_port(EXC_MASK_ALL);
84 run_exception_handler(exc_port, (exc_handler_callback_t)exception_handler_expect_not_called);
85
86 struct sigaction sa = {
87 .sa_sigaction = signal_handler_expect_not_called,
88 .sa_flags = SA_SIGINFO
89 };
90 sigfillset(&sa.sa_mask);
91
92 T_ASSERT_POSIX_ZERO(sigaction(SIGILL, &sa, NULL), NULL);
93
94 /* When the child issues a maybe-fatal trap label */
95 /* 0xB000 is the start of the 'runtimes-owned traps' range in xnu */
96 os_fatal_trap(0xB000);
97 /* The brk above should have been treated as unrecoverable by the kernel */
98 T_FAIL("child ran past unrecoverable brk");
99 } else {
100 int status;
101 int err = waitpid(pid, &status, 0);
102 T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "waitpid");
103
104 /* Then the child does not have an opportunity to run its exception handlers, and is immediately killed */
105 T_EXPECT_TRUE(WIFSIGNALED(status), "child terminated due to signal");
106 T_EXPECT_EQ(SIGKILL, WTERMSIG(status), "child terminated due to SIGKILL");
107 }
108 }
109
110 T_DECL(uncatchable_fatal_trap_developer_mode_enabled,
111 "Ensure an maybe-unrecoverable trap label is uncatchable with developer_mode",
112 T_META_REQUIRES_SYSCTL_EQ("security.mac.amfi.developer_mode_status", 1),
113 T_META_ENABLED(TARGET_CPU_ARM64)
114 )
115 {
116 /* Given a child process that sets up some mechanisms to catch an exception/signal */
117 /* And developer mode is enabled, but we're not being debugged */
118 pid_t pid = fork();
119 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "fork");
120
121 if (pid == 0) {
122 /*
123 * Try to catch the exception in two ways:
124 * - via setting up a Mach exception handler, and
125 * - via sigaction
126 */
127 mach_port_t exc_port = create_exception_port(EXC_MASK_ALL);
128 run_exception_handler(exc_port, (exc_handler_callback_t)exception_handler_expect_not_called);
129
130 struct sigaction sa = {
131 .sa_sigaction = signal_handler_expect_not_called,
132 .sa_flags = SA_SIGINFO
133 };
134 sigfillset(&sa.sa_mask);
135
136 T_ASSERT_POSIX_ZERO(sigaction(SIGILL, &sa, NULL), NULL);
137
138 /* When the child issues a maybe-fatal trap label */
139 /* 0xB000 is the start of the 'runtimes-owned traps' range in xnu */
140 os_fatal_trap(0xB000);
141 /* The brk above should have been treated as unrecoverable by the kernel */
142 T_FAIL("child ran past brk");
143 } else {
144 int status;
145 int err = waitpid(pid, &status, 0);
146 T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "waitpid");
147
148 /* Then the child does not have an opportunity to run its exception handlers, and is immediately killed */
149 T_EXPECT_TRUE(WIFSIGNALED(status), "child terminated due to signal");
150 T_EXPECT_EQ(SIGKILL, WTERMSIG(status), "child terminated due to SIGKILL");
151 }
152 }
153
154 static bool* shared_was_mach_exception_handler_called = NULL;
155 static bool* shared_was_posix_signal_handler_called = NULL;
156
157 static size_t
exception_handler_expect_called(mach_port_t task __unused,mach_port_t thread __unused,exception_type_t type __unused,mach_exception_data_t codes __unused)158 exception_handler_expect_called(mach_port_t task __unused, mach_port_t thread __unused,
159 exception_type_t type __unused, mach_exception_data_t codes __unused)
160 {
161 T_PASS("Our Mach exception handler ran");
162 *shared_was_mach_exception_handler_called = true;
163 exit(0);
164 return 0;
165 }
166
167 static void
signal_handler_expect_called(int sig,siginfo_t * sip __unused,void * ucontext __unused)168 signal_handler_expect_called(int sig, siginfo_t *sip __unused, void *ucontext __unused)
169 {
170 T_PASS("Our BSD signal handler ran");
171 *shared_was_posix_signal_handler_called = true;
172 exit(0);
173 }
174
175
176 T_DECL(uncatchable_fatal_trap_debugged,
177 "Ensure an maybe-unrecoverable trap label is catchable under a debugger",
178 T_META_REQUIRES_SYSCTL_EQ("security.mac.amfi.developer_mode_status", 1),
179 /* It's not straightforward to ptrace on platforms other than macOS, so don't bother */
180 T_META_ENABLED(TARGET_CPU_ARM64 && TARGET_OS_OSX)
181 )
182 {
183 /* Given a child process that sets up some mechanisms to catch an exception/signal */
184 /* And developer mode is enabled, and the child is being debugged */
185 int ret;
186
187 const char* memory_path = "uncatchable_fatal_trap_debugged";
188 shm_unlink(memory_path);
189 int shm_fd = shm_open(memory_path, O_RDWR | O_CREAT);
190 T_ASSERT_POSIX_SUCCESS(shm_fd, "Created shared memory");
191 ret = ftruncate(shm_fd, sizeof(bool) * 2);
192 T_ASSERT_POSIX_SUCCESS(ret, "ftruncate");
193
194 shared_was_mach_exception_handler_called = (bool*)mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
195 shared_was_posix_signal_handler_called = (bool*)mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
196 bool* has_parent_connected = (bool*)mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
197 *has_parent_connected = false;
198
199 pid_t pid = fork();
200 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "fork");
201
202 if (pid == 0) {
203 /* Allow the parent to attach */
204 while (!*has_parent_connected) {
205 sleep(1);
206 }
207
208 /*
209 * Try to catch the exception in two ways:
210 * - via setting up a Mach exception handler, and
211 * - via sigaction
212 */
213 mach_port_t exc_port = create_exception_port(EXC_MASK_ALL);
214 run_exception_handler(exc_port, (exc_handler_callback_t)exception_handler_expect_called);
215
216 struct sigaction sa = {
217 .sa_sigaction = signal_handler_expect_called,
218 .sa_flags = SA_SIGINFO
219 };
220 sigfillset(&sa.sa_mask);
221
222 T_ASSERT_POSIX_ZERO(sigaction(SIGILL, &sa, NULL), NULL);
223
224 /* When the child issues a maybe-fatal trap label */
225 /* 0xB000 is the start of the 'runtimes-owned traps' range in xnu */
226 os_fatal_trap(0xB000);
227 /* The brk above should have terminated this thread */
228 T_FAIL("child ran past brk");
229 } else {
230 /* Attach to the child so it's marked as being debugged */
231 ret = ptrace(PT_ATTACHEXC, pid, 0, 0);
232 T_EXPECT_POSIX_SUCCESS(ret, "ptrace PT_ATTACHEXC");
233 ret = ptrace(PT_CONTINUE, pid, (caddr_t)1, 0);
234 T_EXPECT_POSIX_SUCCESS(ret, "ptrace PT_CONTINUE");
235 /* And let the child know that it can carry on */
236 *has_parent_connected = true;
237
238 int status;
239 int err = waitpid(pid, &status, 0);
240 T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "waitpid");
241
242 /*
243 * Then the child is given an opportunity to run its exception handlers,
244 * which we witness by its setting of a shared boolean and clean exit(0).
245 */
246 T_EXPECT_TRUE(WIFEXITED(status), "child exited");
247 T_EXPECT_TRUE(*shared_was_mach_exception_handler_called
248 || *shared_was_posix_signal_handler_called,
249 "Expected one of our handlers to be dispatched");
250
251 T_ASSERT_POSIX_SUCCESS(close(shm_fd), "Closed shm fd");
252 T_ASSERT_POSIX_SUCCESS(shm_unlink(memory_path), "Unlinked");
253 }
254 }
255