1 /*
2 * Copyright (c) 2024 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 #include <signal.h>
30 #include <spawn.h>
31 #include <stdlib.h>
32 #include <sys/sysctl.h>
33 #include <sys/ptrace.h>
34 #include <sys/types.h>
35 #include <mach/mach.h>
36 #include <excserver.h>
37 #include <sys/mman.h>
38 #include <kern/exc_resource.h>
39 #include <TargetConditionals.h>
40 #include <mach/vm_page_size.h>
41 #include <sys/spawn_internal.h>
42 #include <mach/mach_vm.h>
43
44 #include <darwintest.h>
45 #include <dispatch/dispatch.h>
46 #include <mach-o/dyld.h>
47
48 /* internal */
49 #include <spawn_private.h>
50 #include <sys/kern_memorystatus.h>
51
52 #define TEST_MEMLIMIT_MB 10
53 #define SEM_TIMEOUT dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC)
54 #define OWNED_VMOBJECTS_SYSCTL "vm.get_owned_vmobjects"
55
56 T_GLOBAL_META(
57 T_META_NAMESPACE("xnu.memorystatus"),
58 T_META_RADAR_COMPONENT_NAME("xnu"),
59 T_META_RADAR_COMPONENT_VERSION("VM"));
60
61 /* Globals */
62 static dispatch_semaphore_t sync_sema;
63 static pid_t child_pid;
64 static bool caught_crash = false, caught_corpse = false;
65 mach_port_t exception_port;
66
67 /* Exception */
68 kern_return_t
catch_mach_exception_raise_state(mach_port_t exception_port,exception_type_t exception,const mach_exception_data_t code,mach_msg_type_number_t code_count,int * flavor,const thread_state_t old_state,mach_msg_type_number_t old_state_count,thread_state_t new_state,mach_msg_type_number_t * new_state_count)69 catch_mach_exception_raise_state(mach_port_t exception_port,
70 exception_type_t exception,
71 const mach_exception_data_t code,
72 mach_msg_type_number_t code_count,
73 int * flavor,
74 const thread_state_t old_state,
75 mach_msg_type_number_t old_state_count,
76 thread_state_t new_state,
77 mach_msg_type_number_t * new_state_count)
78 {
79 #pragma unused(exception_port, exception, code, code_count, flavor, old_state, old_state_count, new_state, new_state_count)
80 T_FAIL("Unsupported catch_mach_exception_raise_state");
81 return KERN_NOT_SUPPORTED;
82 }
83
84 kern_return_t
catch_mach_exception_raise_state_identity(mach_port_t exception_port,mach_port_t thread,mach_port_t task,exception_type_t exception,mach_exception_data_t code,mach_msg_type_number_t code_count,int * flavor,thread_state_t old_state,mach_msg_type_number_t old_state_count,thread_state_t new_state,mach_msg_type_number_t * new_state_count)85 catch_mach_exception_raise_state_identity(mach_port_t exception_port,
86 mach_port_t thread,
87 mach_port_t task,
88 exception_type_t exception,
89 mach_exception_data_t code,
90 mach_msg_type_number_t code_count,
91 int * flavor,
92 thread_state_t old_state,
93 mach_msg_type_number_t old_state_count,
94 thread_state_t new_state,
95 mach_msg_type_number_t * new_state_count)
96 {
97 #pragma unused(exception_port, exception, code, code_count, flavor, old_state, old_state_count, new_state, new_state_count)
98 T_FAIL("Unsupported catch_mach_exception_raise_state_identity");
99 return KERN_NOT_SUPPORTED;
100 }
101
102 kern_return_t
catch_mach_exception_raise_state_identity_protected(mach_port_t exception_port,uint64_t thread_id,mach_port_t task_id_token,exception_type_t exception,mach_exception_data_t codes,mach_msg_type_number_t codeCnt,int * flavor,thread_state_t old_state,mach_msg_type_number_t old_state_count,thread_state_t new_state,mach_msg_type_number_t * new_state_count)103 catch_mach_exception_raise_state_identity_protected(
104 mach_port_t exception_port,
105 uint64_t thread_id,
106 mach_port_t task_id_token,
107 exception_type_t exception,
108 mach_exception_data_t codes,
109 mach_msg_type_number_t codeCnt,
110 int * flavor,
111 thread_state_t old_state,
112 mach_msg_type_number_t old_state_count,
113 thread_state_t new_state,
114 mach_msg_type_number_t * new_state_count)
115 {
116 #pragma unused(exception_port, thread_id, task_id_token, exception, codes, codeCnt, flavor, old_state, old_state_count, new_state, new_state_count)
117 T_FAIL("Unsupported catch_mach_exception_raise_state_identity");
118 return KERN_NOT_SUPPORTED;
119 }
120
121 kern_return_t
catch_mach_exception_raise_identity_protected(mach_port_t exception_port,uint64_t thread_id,mach_port_t task_id_token,exception_type_t exception,mach_exception_data_t codes,mach_msg_type_number_t codeCnt)122 catch_mach_exception_raise_identity_protected(
123 mach_port_t exception_port,
124 uint64_t thread_id,
125 mach_port_t task_id_token,
126 exception_type_t exception,
127 mach_exception_data_t codes,
128 mach_msg_type_number_t codeCnt)
129 {
130 #pragma unused(exception_port, thread_id, task_id_token, exception, codes, codeCnt)
131 T_FAIL("Unsupported catch_mach_exception_raise_state_identity");
132 return KERN_NOT_SUPPORTED;
133 }
134
135 void
verify_owned_vmobjects(task_t task)136 verify_owned_vmobjects(task_t task)
137 {
138 int ret;
139 size_t owned_vmobjects_len;
140
141 ret = sysctlbyname(OWNED_VMOBJECTS_SYSCTL, NULL, &owned_vmobjects_len, &task, sizeof(task));
142 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl " OWNED_VMOBJECTS_SYSCTL);
143 T_EXPECT_GT((int) owned_vmobjects_len, 0, "owned vmobjects list is populated on %s", (task == mach_task_self()) ? "self" : "corpse");
144 }
145
146 kern_return_t
catch_mach_exception_raise(mach_port_t exception_port,mach_port_t thread,mach_port_t task,exception_type_t exception,mach_exception_data_t code,mach_msg_type_number_t code_count)147 catch_mach_exception_raise(mach_port_t exception_port,
148 mach_port_t thread,
149 mach_port_t task,
150 exception_type_t exception,
151 mach_exception_data_t code,
152 mach_msg_type_number_t code_count)
153 {
154 #pragma unused(thread, task, code, code_count)
155 T_QUIET; T_EXPECT_TRUE((exception == EXC_CRASH) || (exception == EXC_CORPSE_NOTIFY), "catch_mach_exception_raise() catches EXC_CRASH or EXC_CORPSE_NOTIFY");
156 if (exception == EXC_CRASH) {
157 caught_crash = true;
158 return KERN_SUCCESS;
159 } else if (exception == EXC_CORPSE_NOTIFY) {
160 caught_corpse = true;
161 verify_owned_vmobjects(task);
162 dispatch_semaphore_signal(sync_sema);
163 return KERN_SUCCESS;
164 }
165 return KERN_NOT_SUPPORTED;
166 }
167
168 /*
169 * Background process that will allocate enough memory to push
170 * itself over the threshold, hopefully triggering EXC_RESOURCE.
171 */
172 T_HELPER_DECL(i_eat_memory_for_breakfast, "") {
173 int ret, j, num_pages = 0;
174 unsigned char *buf;
175
176 if (argc == 1) {
177 num_pages = atoi(argv[0]);
178 } else {
179 T_FAIL("No arguments passed to memory eater");
180 }
181
182 /* Allocate a purgeable buffer that will show up in owned vmobjects */
183 mach_vm_address_t addr = 0;
184 ret = mach_vm_allocate(mach_task_self(), &addr, vm_page_size, VM_FLAGS_ANYWHERE | VM_FLAGS_PURGABLE);
185 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "allocate purgeable buffer");
186 T_QUIET; T_ASSERT_NE((int) addr, 0, "purgeable buffer not null");
187 verify_owned_vmobjects(mach_task_self());
188
189 /* Allocate and touch all our pages */
190 T_LOG("Allocating %d pages...", num_pages);
191 buf = mmap(NULL, vm_page_size * num_pages, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
192 T_QUIET; T_ASSERT_POSIX_SUCCESS(buf, "mmap");
193 for (j = 0; j < num_pages; j++) {
194 ((volatile unsigned char *)buf)[j * vm_page_size] = 1;
195 }
196
197 exit(0);
198 }
199
200 static void
kill_child(void)201 kill_child(void)
202 {
203 int ret = kill(child_pid, SIGKILL);
204 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kill");
205 }
206
207 static pid_t
launch_child(int num_pages)208 launch_child(int num_pages)
209 {
210 extern char **environ;
211 int ret;
212 char testpath[PATH_MAX];
213 posix_spawnattr_t spawn_attrs;
214
215 uint32_t testpath_buf_size = PATH_MAX;
216 char num_pages_str[32] = {0};
217 char *argv[5] = {testpath, "-n", "i_eat_memory_for_breakfast", num_pages_str, NULL};
218
219 T_LOG("Spawning child process...");
220
221 /* Fork so we can keep the exception port. */
222 if ((child_pid = fork()) == 0) {
223 ret = posix_spawnattr_init(&spawn_attrs);
224 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_init");
225 ret = posix_spawnattr_setjetsam_ext(&spawn_attrs, POSIX_SPAWN_JETSAM_MEMLIMIT_FATAL, JETSAM_PRIORITY_FOREGROUND, TEST_MEMLIMIT_MB, TEST_MEMLIMIT_MB);
226 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_setjetsam_ext");
227 ret = posix_spawnattr_setflags(&spawn_attrs, POSIX_SPAWN_SETEXEC);
228 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "posix_spawnattr_setflags");
229 ret = snprintf(num_pages_str, sizeof(num_pages_str), "%d", num_pages);
230 T_QUIET; T_ASSERT_LE((size_t) ret, sizeof(num_pages_str), "Don't allocate too many pages.");
231 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
232 T_QUIET; T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath");
233 ret = posix_spawn(&child_pid, argv[0], NULL, &spawn_attrs, argv, environ);
234 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn");
235 }
236
237 T_ATEND(kill_child);
238
239 return child_pid;
240 }
241
242 void*
exc_thread(void * arg)243 exc_thread(void *arg)
244 {
245 #pragma unused(arg)
246 kern_return_t kr;
247
248 while (1) {
249 kr = mach_msg_server(mach_exc_server, MACH_MSG_SIZE_RELIABLE, exception_port, 0);
250 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_msg_server");
251 }
252 }
253
254 T_DECL(corpse_owned_vmobjects, "vm.get_owned_vmobjects sysctl on corpses",
255 T_META_ASROOT(true),
256 T_META_TAG_VM_PREFERRED
257 )
258 {
259 int ret;
260 pthread_t handle_thread;
261 task_t task;
262
263 T_SETUPBEGIN;
264
265 sync_sema = dispatch_semaphore_create(0);
266
267 task = mach_task_self();
268 T_QUIET; T_ASSERT_NE(task, MACH_PORT_NULL, "mach_task_self");
269
270 /* Allocate a port for receiving EXC_CRASH and EXC_CORPSE_NOTIFY */
271 ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &exception_port);
272 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "mach_port_allocate");
273 ret = mach_port_insert_right(task, exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND);
274 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "mach_port_insert_right");
275 ret = task_set_exception_ports(task, EXC_MASK_CRASH | EXC_MASK_CORPSE_NOTIFY, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, 0);
276 T_QUIET; T_ASSERT_MACH_SUCCESS(ret, "task_set_exception_ports");
277
278 T_SETUPEND;
279
280 /* Spawn exception handling thread */
281 ret = pthread_create(&handle_thread, NULL, exc_thread, 0);
282
283 /* Spawn child to eat memory and trigger EXC_RESOURCE */
284 launch_child((TEST_MEMLIMIT_MB * (1 << 20)) / vm_page_size);
285
286 /* We should receive an exception */
287 dispatch_semaphore_wait(sync_sema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
288 T_QUIET; T_EXPECT_EQ(caught_crash, true, "Caught EXC_CRASH");
289 T_QUIET; T_EXPECT_EQ(caught_corpse, true, "Caught EXC_CORPSE_NOTIFY");
290 }
291