xref: /xnu-12377.1.9/tests/runaway_mitigation.c (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
1 /*
2  * Copyright (c) 2025 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 /* test that the header doesn't implicitly depend on others */
30 #include <sys/resource_private.h>
31 #include <sys/resource.h>
32 
33 #include <libproc.h>
34 
35 #include <sys/types.h>
36 #include <unistd.h>
37 
38 #include <mach/task.h>
39 #include <mach/task_policy.h>
40 #include <mach/mach.h>
41 
42 #include <darwintest.h>
43 #include <darwintest_utils.h>
44 
45 #include <sys/sfi.h>
46 #include <Kernel/kern/ledger.h>  /* TODO: this should be installed for userspace */
47 extern int ledger(int cmd, caddr_t arg1, caddr_t arg2, caddr_t arg3);
48 
49 #include <kern/debug.h>
50 extern int __microstackshot(char *tracebuf, uint32_t tracebuf_size, uint32_t flags);
51 
52 
53 T_GLOBAL_META(T_META_NAMESPACE("xnu.scheduler"),
54     T_META_RADAR_COMPONENT_NAME("xnu"),
55     T_META_RADAR_COMPONENT_VERSION("scheduler"),
56     T_META_OWNER("chimene"),
57     T_META_RUN_CONCURRENTLY(false), /* because of messing with global SFI */
58     T_META_ASROOT(true), /* for TASK_POLICY_STATE, and setting SFI */
59     T_META_TAG_VM_PREFERRED);
60 
61 static void
check_is_bg(bool wants_bg)62 check_is_bg(bool wants_bg)
63 {
64 	kern_return_t kr;
65 	struct task_policy_state policy_state;
66 
67 	mach_msg_type_number_t count = TASK_POLICY_STATE_COUNT;
68 	boolean_t get_default = FALSE;
69 
70 	kr = task_policy_get(mach_task_self(), TASK_POLICY_STATE,
71 	    (task_policy_t)&policy_state, &count, &get_default);
72 
73 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "task_policy_get(TASK_POLICY_STATE)");
74 
75 	/*
76 	 * A test reporting type=APPLICATION should have the live donor bit set.
77 	 * If this fails, the test may have been launched as a daemon instead.
78 	 */
79 	T_QUIET; T_ASSERT_BITS_SET(policy_state.flags, TASK_IMP_LIVE_DONOR, "test should be live donor enabled");
80 
81 	/*
82 	 * The BG bit is updated via task_policy_update_internal_locked,
83 	 * checking this proves that the first phase update ran on this task.
84 	 */
85 	if (wants_bg) {
86 		T_ASSERT_BITS_SET(policy_state.effective, POLICY_EFF_DARWIN_BG, "%d: is BG", getpid());
87 	} else {
88 		T_ASSERT_BITS_NOTSET(policy_state.effective, POLICY_EFF_DARWIN_BG, "%d: is not BG", getpid());
89 	}
90 
91 	/*
92 	 * The live donor bit is updated via task_policy_update_complete_unlocked,
93 	 * checking this proves that the second phase update ran on this task.
94 	 */
95 	if (wants_bg) {
96 		T_ASSERT_BITS_NOTSET(policy_state.flags, TASK_IMP_DONOR, "%d: is not live donor", getpid());
97 	} else {
98 		T_ASSERT_BITS_SET(policy_state.flags, TASK_IMP_DONOR, "%d: is live donor", getpid());
99 	}
100 }
101 
102 static void
check_runaway_mode(bool expected_mode)103 check_runaway_mode(bool expected_mode)
104 {
105 	int runaway_mode = getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0);
106 
107 	T_QUIET;
108 	T_ASSERT_POSIX_SUCCESS(runaway_mode, "getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION)");
109 
110 	T_LOG("pid %d: runaway mitigation mode is: %d", getpid(), runaway_mode);
111 
112 	if (expected_mode) {
113 		T_QUIET;
114 		T_ASSERT_EQ(runaway_mode, PRIO_DARWIN_RUNAWAY_MITIGATION_ON, "should be on");
115 		check_is_bg(true);
116 	} else {
117 		T_QUIET;
118 		T_ASSERT_EQ(runaway_mode, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF, "should be off");
119 		check_is_bg(false);
120 	}
121 }
122 
123 T_DECL(entitled_runaway_mode, "runaway mitigation mode should be settable while entitled")
124 {
125 	T_LOG("uid: %d", getuid());
126 
127 	check_runaway_mode(false);
128 
129 	T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON),
130 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON)");
131 
132 	check_runaway_mode(true);
133 
134 	T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF),
135 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF)");
136 
137 	check_runaway_mode(false);
138 }
139 
140 T_DECL(entitled_runaway_mode_read_root, "runaway mitigation mode should be readable as root",
141     T_META_ASROOT(true))
142 {
143 	T_LOG("uid: %d", getuid());
144 
145 	check_runaway_mode(false);
146 }
147 
148 T_DECL(entitled_runaway_mode_read_notroot, "runaway mitigation mode should be readable as not root but entitled",
149     T_META_ASROOT(false))
150 {
151 	T_LOG("uid: %d", getuid());
152 
153 	int runaway_mode = getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, getpid());
154 
155 	T_QUIET;
156 	T_ASSERT_POSIX_SUCCESS(runaway_mode, "getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION)");
157 
158 	T_ASSERT_EQ(runaway_mode, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF, "should be off");
159 }
160 
161 T_DECL(runaway_mode_child_exit, "runaway mitigation mode should disappear when child exits")
162 {
163 	T_LOG("uid: %d", getuid());
164 
165 	check_runaway_mode(false);
166 
167 	T_LOG("Spawning child");
168 
169 	pid_t child_pid = fork();
170 
171 	if (child_pid == 0) {
172 		/* child process */
173 
174 		check_runaway_mode(false);
175 
176 		T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON),
177 		    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON)");
178 
179 		check_runaway_mode(true);
180 
181 		T_LOG("Exit pid %d with runaway mitigation mode on", getpid());
182 
183 		exit(0);
184 	} else {
185 		T_ASSERT_POSIX_SUCCESS(child_pid, "fork, pid %d", child_pid);
186 
187 		/* wait for child process to exit */
188 		int exit_status = 0, signum = 0;
189 
190 		T_ASSERT_TRUE(dt_waitpid(child_pid, &exit_status, &signum, 5),
191 		    "wait for child (%d) complete", child_pid);
192 
193 		T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status");
194 		T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum");
195 	}
196 
197 	check_runaway_mode(false);
198 }
199 
200 T_DECL(runaway_mode_child_set, "runaway mitigation mode should be settable on child pid")
201 {
202 	T_LOG("uid: %d", getuid());
203 
204 	check_runaway_mode(false);
205 
206 	int fd[2];
207 
208 	T_QUIET; T_ASSERT_POSIX_SUCCESS(pipe(fd), "pipe()");
209 
210 	T_LOG("Spawning child");
211 
212 	pid_t child_pid = fork();
213 
214 	if (child_pid == 0) {
215 		char buf[10];
216 
217 		/* child process */
218 		T_ASSERT_POSIX_SUCCESS(child_pid, "fork, in child with pid %d", getpid());
219 
220 		T_ASSERT_POSIX_SUCCESS(close(fd[1]), "close(fd[1])");
221 
222 		T_ASSERT_POSIX_SUCCESS(read(fd[0], buf, sizeof(buf)), "read(fd[0], buf, sizeof(buf)");
223 
224 		T_ASSERT_POSIX_SUCCESS(close(fd[0]), "close(fd[0])");
225 
226 		check_runaway_mode(true);
227 
228 		T_LOG("Exit pid %d with runaway mitigation mode on", getpid());
229 
230 		exit(0);
231 	} else {
232 		T_ASSERT_POSIX_SUCCESS(child_pid, "fork parent: child pid %d", child_pid);
233 
234 		T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, child_pid, PRIO_DARWIN_RUNAWAY_MITIGATION_ON),
235 		    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, child_pid, PRIO_DARWIN_RUNAWAY_MITIGATION_ON)");
236 
237 		int runaway_mode = getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, child_pid);
238 
239 		T_QUIET;
240 		T_ASSERT_POSIX_SUCCESS(runaway_mode, "getpriority(PRIO_DARWIN_RUNAWAY_MITIGATION)");
241 
242 		T_ASSERT_EQ(runaway_mode, PRIO_DARWIN_RUNAWAY_MITIGATION_ON, "should be on");
243 
244 		T_QUIET; T_LOG("Signalling child to continue");
245 		T_ASSERT_POSIX_SUCCESS(close(fd[1]), "close(fd[1])");
246 
247 		/* wait for child process to exit */
248 		int exit_status = 0, signum = 0;
249 
250 		T_ASSERT_TRUE(dt_waitpid(child_pid, &exit_status, &signum, 5),
251 		    "wait for child (%d) complete", child_pid);
252 
253 		T_QUIET; T_ASSERT_EQ(exit_status, 0, "dt_waitpid: exit_status");
254 		T_QUIET; T_ASSERT_EQ(signum, 0, "dt_waitpid: signum");
255 	}
256 
257 	check_runaway_mode(false);
258 }
259 
260 
261 /*
262  * TODO: This should be in a test utils library,
263  * but it requires including Kernel.framework header kern/ledger.h, which is Bad
264  */
265 static size_t
ledger_index_for_string(size_t * num_entries,char * string)266 ledger_index_for_string(size_t *num_entries, char* string)
267 {
268 	struct ledger_info li;
269 	struct ledger_template_info *templateInfo = NULL;
270 	int ret;
271 	size_t i, footprint_index;
272 	bool found = false;
273 
274 	ret = ledger(LEDGER_INFO, (caddr_t)(uintptr_t)getpid(), (caddr_t)&li, NULL);
275 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_INFO)");
276 
277 	T_QUIET; T_ASSERT_GT(li.li_entries, (int64_t) 0, "num ledger entries is valid");
278 	*num_entries = (size_t) li.li_entries;
279 	templateInfo = malloc((size_t)li.li_entries * sizeof(struct ledger_template_info));
280 	T_QUIET; T_ASSERT_NOTNULL(templateInfo, "malloc entries");
281 
282 	footprint_index = 0;
283 	ret = ledger(LEDGER_TEMPLATE_INFO, (caddr_t) templateInfo, (caddr_t) num_entries, NULL);
284 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_TEMPLATE_INFO)");
285 	for (i = 0; i < *num_entries; i++) {
286 		if (strcmp(templateInfo[i].lti_name, string) == 0) {
287 			footprint_index = i;
288 			found = true;
289 		}
290 	}
291 	free(templateInfo);
292 	T_QUIET; T_ASSERT_TRUE(found, "found %s in ledger", string);
293 	return footprint_index;
294 }
295 
296 /*
297  * sadly there's no 'get just this one ledger index' syscall,
298  * we have to read all ledgers and filter for the one we want
299  */
300 static int64_t
get_ledger_entry_for_pid(pid_t pid,size_t index,size_t num_entries)301 get_ledger_entry_for_pid(pid_t pid, size_t index, size_t num_entries)
302 {
303 	int ret;
304 	int64_t value;
305 	struct ledger_entry_info *lei = NULL;
306 
307 	lei = malloc(num_entries * sizeof(*lei));
308 	ret = ledger(LEDGER_ENTRY_INFO, (caddr_t) (uintptr_t) pid, (caddr_t) lei, (caddr_t) &num_entries);
309 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "ledger(LEDGER_ENTRY_INFO)");
310 	value = lei[index].lei_balance;
311 	free(lei);
312 	return value;
313 }
314 
315 
316 uint64_t initial_sfi_window = 0, initial_class_offtime = 0;
317 
318 static void
restore_sfi_state(void)319 restore_sfi_state(void)
320 {
321 	T_LOG("Restoring initial system SFI window %lld, SFI_CLASS_RUNAWAY_MITIGATION class offtime %lld",
322 	    initial_sfi_window, initial_class_offtime);
323 
324 	/*
325 	 * Setting window will fail if there is a larger offtime set, and
326 	 * setting class will fail if the window is smaller.
327 	 * To avoid this, disable the window, configure new values, then finally
328 	 * re-enable the window.
329 	 */
330 
331 	T_QUIET; T_ASSERT_POSIX_SUCCESS(system_set_sfi_window(0),
332 	    "system_set_sfi_window(0)");
333 
334 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sfi_set_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, initial_class_offtime),
335 	    "system_set_sfi_window(%lld)", initial_class_offtime);
336 	T_QUIET; T_ASSERT_POSIX_SUCCESS(system_set_sfi_window(initial_sfi_window),
337 	    "system_set_sfi_window(%lld)", initial_sfi_window);
338 }
339 
340 const int spin_seconds = 1;
341 
342 
343 static void *
spin_thread(void * arg)344 spin_thread(void *arg)
345 {
346 	static mach_timebase_info_data_t timebase_info;
347 	mach_timebase_info(&timebase_info);
348 
349 	uint64_t duration = spin_seconds * NSEC_PER_SEC * timebase_info.denom / timebase_info.numer;
350 	uint64_t deadline = mach_absolute_time() + duration;
351 
352 	while (mach_absolute_time() < deadline) {
353 		;
354 	}
355 
356 	return NULL;
357 }
358 
359 T_DECL(runaway_mode_child_sfi, "runaway mitigation mode should cause SFI")
360 {
361 	T_LOG("uid: %d", getuid());
362 
363 	check_runaway_mode(false);
364 
365 	T_QUIET; T_ASSERT_POSIX_SUCCESS(system_get_sfi_window(&initial_sfi_window),
366 	    "system_get_sfi_window(&initial_sfi_window)");
367 
368 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sfi_get_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, &initial_class_offtime),
369 	    "sfi_get_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, &initial_class_offtime)");
370 
371 	T_LOG("Initial System SFI window %lld, SFI_CLASS_RUNAWAY_MITIGATION class offtime %lld\n", initial_sfi_window, initial_class_offtime);
372 
373 	size_t num_ledger_entries = 0;
374 	size_t ledger_index = ledger_index_for_string(&num_ledger_entries, "SFI_CLASS_RUNAWAY_MITIGATION");
375 	uint64_t sfi_time_before = get_ledger_entry_for_pid(getpid(), ledger_index, num_ledger_entries);
376 
377 	T_LOG("SFI_CLASS_RUNAWAY_MITIGATION ledger index: %zu out of %zu\n", ledger_index, num_ledger_entries);
378 
379 	T_LOG("Initial accumulated SFI time: %lld\n", sfi_time_before);
380 
381 	T_ATEND(restore_sfi_state);
382 
383 	uint64_t custom_sfi_window = 100000; /* microseconds */
384 	uint64_t custom_class_offtime = 50000;
385 
386 	T_LOG("Setting custom system SFI window %lld, SFI_CLASS_RUNAWAY_MITIGATION class offtime %lld",
387 	    custom_sfi_window, custom_class_offtime);
388 
389 	T_QUIET; T_ASSERT_POSIX_SUCCESS(system_set_sfi_window(0),
390 	    "system_set_sfi_window(0)");
391 	T_ASSERT_POSIX_SUCCESS(sfi_set_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, custom_class_offtime),
392 	    "sfi_set_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, %lld)", custom_class_offtime);
393 	T_ASSERT_POSIX_SUCCESS(system_set_sfi_window(custom_sfi_window),
394 	    "system_set_sfi_window(%lld)", custom_sfi_window);
395 
396 	pthread_t thread;
397 
398 	T_LOG("Spawning thread to spin for %d seconds\n", spin_seconds);
399 
400 	int rv = pthread_create(&thread, NULL, spin_thread, NULL);
401 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_create");
402 
403 	T_LOG("Enable mitigation mode\n");
404 
405 	T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON),
406 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON)");
407 
408 	check_runaway_mode(true);
409 
410 	T_LOG("Wait %d seconds for spin to finish\n", spin_seconds);
411 
412 	rv = pthread_join(thread, NULL);
413 	T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "pthread_join");
414 
415 	T_LOG("Thread joined, disable mitigation mode\n");
416 
417 	T_ASSERT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF),
418 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF)");
419 
420 	uint64_t sfi_time_after = get_ledger_entry_for_pid(getpid(), ledger_index, num_ledger_entries);
421 
422 	T_LOG("Ending accumulated SFI time: %lld\n", sfi_time_after);
423 
424 	T_ASSERT_LT(sfi_time_before, sfi_time_after, "SFI_CLASS_RUNAWAY_MITIGATION SFI time must have increased");
425 
426 	check_runaway_mode(false);
427 
428 	uint64_t final_sfi_window = 0, final_class_offtime = 0;
429 
430 	T_QUIET; T_ASSERT_POSIX_SUCCESS(system_get_sfi_window(&final_sfi_window),
431 	    "system_get_sfi_window(&final_sfi_window)");
432 
433 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sfi_get_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, &final_class_offtime),
434 	    "sfi_get_class_offtime(SFI_CLASS_RUNAWAY_MITIGATION, &final_class_offtime)");
435 
436 	/*
437 	 * If the System SFI configuration was changed out from under us during the test, either us or them will be confused.
438 	 */
439 	T_QUIET; T_ASSERT_EQ(custom_sfi_window, final_sfi_window, "System SFI window should not unexpectedly change during the test");
440 	T_QUIET; T_ASSERT_EQ(custom_class_offtime, final_class_offtime, "System SFI offtime should not unexpectedly change during the test");
441 }
442 
443 #if defined(__arm64__)
444 
445 static bool found_flag = false;
446 static bool found_self = false;
447 
448 static const size_t microstackshot_buf_size = 16 * 1024;
449 
450 static bool
search_for_self_microstackshot(bool log_details)451 search_for_self_microstackshot(bool log_details)
452 {
453 	void *buf = calloc(microstackshot_buf_size, 1);
454 	T_QUIET; T_ASSERT_NOTNULL(buf, "allocate buffer");
455 
456 	int ret = __microstackshot(buf, microstackshot_buf_size, STACKSHOT_GET_MICROSTACKSHOT);
457 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "microstackshot");
458 
459 	if (!log_details) {
460 		T_QUIET;
461 	}
462 	T_EXPECT_EQ(*(uint32_t *)buf,
463 	    (uint32_t)STACKSHOT_MICRO_SNAPSHOT_MAGIC,
464 	    "magic value for microstackshot matches");
465 
466 	uint32_t magic = STACKSHOT_TASK_SNAPSHOT_MAGIC;
467 
468 	void* next_tsnap = memmem(buf, microstackshot_buf_size, &magic, sizeof(magic));
469 
470 	void* buf_end = buf + microstackshot_buf_size;
471 
472 	while (next_tsnap != NULL && next_tsnap + sizeof(struct task_snapshot) < buf_end) {
473 		struct task_snapshot *tsnap = (struct task_snapshot *)next_tsnap;
474 		unsigned int offset = next_tsnap - buf;
475 
476 		if (log_details) {
477 			T_LOG("%6d: found snap pid %d name %s\n", offset, tsnap->pid, (char*)&tsnap->p_comm);
478 		}
479 
480 		if (tsnap->pid == getpid()) {
481 			if (log_details) {
482 				T_LOG("%6d: found self snap: flags 0x%x 0x%llx\n", offset, tsnap->ss_flags, tsnap->disk_reads_count);
483 			}
484 			found_self = true;
485 
486 			if (tsnap->disk_reads_count & kTaskRunawayMitigation) {
487 				T_LOG("%6d: found runaway flag: pid %d, name %s, flags: 0x%x 0x%llx, \n",
488 				    offset, tsnap->pid, (char*)&tsnap->p_comm, tsnap->ss_flags, tsnap->disk_reads_count);
489 				found_flag = true;
490 			}
491 		}
492 
493 		void* search_start = next_tsnap + sizeof(struct task_snapshot);
494 		size_t remaining_size = buf_end - search_start;
495 		next_tsnap = memmem(search_start, remaining_size, &magic, sizeof(magic));
496 	}
497 
498 	free(buf);
499 
500 	return found_flag;
501 }
502 
503 T_DECL(runaway_mode_microstackshot_flag,
504     "check that mitigated processes show up in microstackshot",
505     T_META_REQUIRES_SYSCTL_EQ("kern.monotonic.supported", 1),
506     T_META_TAG_VM_NOT_ELIGIBLE, T_META_TIMEOUT(120))
507 {
508 	unsigned int pmi_counter;
509 	size_t sysctl_size = sizeof(pmi_counter);
510 	int ret = sysctlbyname(
511 		"kern.microstackshot.pmi_sample_counter",
512 		&pmi_counter, &sysctl_size, NULL, 0);
513 	if (ret == -1 && errno == ENOENT) {
514 		T_SKIP("no PMI support");
515 	} else {
516 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "query PMI counter");
517 	}
518 	uint64_t pmi_period;
519 	sysctl_size = sizeof(pmi_period);
520 	T_QUIET;
521 	T_ASSERT_POSIX_SUCCESS(sysctlbyname(
522 		    "kern.microstackshot.pmi_sample_period",
523 		    &pmi_period, &sysctl_size, NULL, 0),
524 	    "query PMI period");
525 
526 	T_LOG("PMI counter: %u", pmi_counter);
527 	T_LOG("PMI period: %llu", pmi_period);
528 
529 	if (pmi_period == 0) {
530 		T_SKIP("PMI microstackshots not enabled");
531 	}
532 
533 	T_LOG("Enable mitigation mode on self\n");
534 
535 	T_EXPECT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION,
536 	    0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON),
537 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_ON)");
538 
539 	uint32_t iterations = 100;
540 
541 	/* Over-spin to make it likely we get sampled at least once before failing */
542 	uint32_t multiplier = 10;
543 	uint64_t target_cycles = multiplier * pmi_period;
544 
545 	T_LOG("Spinning for %d iterations or %lld*%d cycles or until self-sample is found\n",
546 	    iterations, pmi_period, multiplier);
547 
548 	struct rusage_info_v6 ru = {};
549 
550 	for (int i = 0; i < iterations; i++) {
551 		spin_thread(NULL);
552 
553 		int rv = proc_pid_rusage(getpid(), RUSAGE_INFO_V6, (rusage_info_t *)&ru);
554 		T_QUIET; T_ASSERT_POSIX_SUCCESS(rv, "proc_pid_rusage");
555 
556 		T_LOG("iteration %3d: %14lld / %14lld cycles executed (%.2f%%)\n", i,
557 		    ru.ri_cycles, target_cycles,
558 		    ((double)ru.ri_cycles) * 100.0 / (double)target_cycles);
559 
560 		T_QUIET; T_ASSERT_NE(ru.ri_cycles, (uint64_t)0,
561 		    "should be able to measure cycles with proc_pid_rusage");
562 
563 		bool found = search_for_self_microstackshot(false);
564 		if (ru.ri_cycles > target_cycles || found) {
565 			break;
566 		}
567 	}
568 
569 	T_LOG("Complete, executed %lld cycles.  Disable mitigation mode.\n", ru.ri_cycles);
570 
571 	T_EXPECT_POSIX_SUCCESS(setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION,
572 	    0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF),
573 	    "setpriority(PRIO_DARWIN_RUNAWAY_MITIGATION, 0, PRIO_DARWIN_RUNAWAY_MITIGATION_OFF)");
574 
575 	search_for_self_microstackshot(true);
576 
577 	T_EXPECT_EQ(found_self, true,
578 	    "Should have found self in microstackshot buffer");
579 	T_EXPECT_EQ(found_flag, true,
580 	    "Should have found kTaskRunawayMitigation flag in microstackshot buffer");
581 }
582 #endif // defined(__arm64__)
583