1 #include <signal.h>
2 #include <spawn.h>
3 #include <stdlib.h>
4 #include <sys/sysctl.h>
5
6 #include <darwintest.h>
7 #include <dispatch/dispatch.h>
8 #include <mach-o/dyld.h>
9
10 /* internal */
11 #include <spawn_private.h>
12 #include <sys/coalition.h>
13 #include <sys/kern_memorystatus.h>
14
15 T_GLOBAL_META(
16 T_META_NAMESPACE("xnu.vm"),
17 T_META_RADAR_COMPONENT_NAME("xnu"),
18 T_META_RADAR_COMPONENT_VERSION("VM"));
19
20 #define kNumProcsInCoalition 4
21 typedef struct {
22 pid_t pids[kNumProcsInCoalition]; // An array of pids in this coalition. Owned by this struct.
23 pid_t expected_order[kNumProcsInCoalition]; // An array of pids in this coalition in proper sorted order.
24 uint64_t ids[COALITION_NUM_TYPES];
25 } coalition_info_t;
26
27 /*
28 * Children pids spawned by this test that need to be cleaned up.
29 * Has to be a global because the T_ATEND API doesn't take any arguments.
30 */
31 #define kMaxChildrenProcs 16
32 static pid_t children_pids[kMaxChildrenProcs];
33 static size_t num_children = 0;
34
35 /*
36 * Sets up a new coalition.
37 */
38 static void init_coalition(coalition_info_t*);
39
40 /*
41 * Places all procs in the coalition in the given band.
42 */
43 static void place_coalition_in_band(const coalition_info_t *, int band);
44
45 /*
46 * Place the given proc in the given band.
47 */
48 static void place_proc_in_band(pid_t pid, int band);
49
50 /*
51 * Cleans up any children processes.
52 */
53 static void cleanup_children(void);
54
55 /*
56 * Check if we're on a kernel where we can test coalitions.
57 */
58 static bool has_unrestrict_coalitions(void);
59
60 /*
61 * Unrestrict coalition syscalls.
62 */
63 static void unrestrict_coalitions(void);
64
65 /*
66 * Restrict coalition syscalls
67 */
68 static void restrict_coalitions(void);
69
70 /*
71 * Allocate the requested number of pages and fault them in.
72 * Used to achieve a desired footprint.
73 */
74 static void *allocate_pages(int);
75
76 /*
77 * Get the vm page size.
78 */
79 static int get_vmpage_size(void);
80
81 /*
82 * Launch a proc with a role in a coalition.
83 * If coalition_ids is NULL, skip adding the proc to the coalition.
84 */
85 static pid_t
86 launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages);
87
88 /*
89 * Background process that will munch some memory, signal its parent, and
90 * then sit in a loop.
91 */
92 T_HELPER_DECL(coalition_member, "Mock coalition member") {
93 int num_pages = 0;
94 if (argc == 1) {
95 num_pages = atoi(argv[0]);
96 }
97 allocate_pages(num_pages);
98 // Signal to the parent that we've touched all of our pages.
99 if (kill(getppid(), SIGUSR1) != 0) {
100 T_LOG("Unable to signal to parent process!");
101 exit(1);
102 }
103 while (true) {
104 ;
105 }
106 }
107
108 /*
109 * Test that sorting the fg bucket in coalition order works properly.
110 * Spawns children in the same coalition in the fg band. Each child
111 * has a different coalition role. Verifies that the coalition
112 * is sorted properly by role.
113 */
114 T_DECL(memorystatus_sort_coalition, "Coalition sort order",
115 T_META_ASROOT(true),
116 T_META_TAG_VM_PREFERRED,
117 T_META_ENABLED(false /* rdar://133461319 */)
118 )
119 {
120 int ret;
121 sig_t res;
122 coalition_info_t coalition;
123 if (!has_unrestrict_coalitions()) {
124 T_SKIP("Unable to test coalitions on this kernel.");
125 }
126 res = signal(SIGUSR1, SIG_IGN);
127 T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1");
128 unrestrict_coalitions();
129
130 // Set up a new coalition with various members.
131 init_coalition(&coalition);
132 T_ATEND(cleanup_children);
133 T_ATEND(restrict_coalitions);
134 // Place all procs in the coalition in the foreground band
135 place_coalition_in_band(&coalition, JETSAM_PRIORITY_FOREGROUND);
136 // Have the kernel sort the foreground bucket and verify that it's
137 // sorted correctly.
138 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, JETSAM_PRIORITY_FOREGROUND, 0,
139 coalition.expected_order, kNumProcsInCoalition * sizeof(pid_t));
140 T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n"
141 "Check os log output for details.\n"
142 "Look for memorystatus_verify_sort_order.");
143 }
144
145 /*
146 * Test that sorting the idle bucket in footprint order works properly.
147 *
148 * Spawns some children with very different footprints in the idle band,
149 * and then ensures that they get sorted properly.
150 */
151 T_DECL(memorystatus_sort_footprint, "Footprint sort order",
152 T_META_ASROOT(true), T_META_TAG_VM_PREFERRED) {
153 #define kNumChildren 3
154 static const int kChildrenFootprints[kNumChildren] = {500, 0, 2500};
155 /*
156 * The expected sort order of the children in the order that they were launched.
157 * Used to construct the expected_order pid array.
158 * Note that procs should be sorted in descending footprint order.
159 */
160 static const int kExpectedOrder[kNumChildren] = {2, 0, 1};
161 static const int kJetsamBand = JETSAM_PRIORITY_IDLE;
162 __block pid_t pid;
163 sig_t res;
164 dispatch_source_t ds_allocated;
165 T_ATEND(cleanup_children);
166
167 // After we spawn the children, they'll signal that they've touched their pages.
168 res = signal(SIGUSR1, SIG_IGN);
169 T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1");
170 ds_allocated = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
171 T_QUIET; T_ASSERT_NOTNULL(ds_allocated, "dispatch_source_create (ds_allocated)");
172
173 dispatch_source_set_event_handler(ds_allocated, ^{
174 if (num_children < kNumChildren) {
175 pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]);
176 place_proc_in_band(pid, kJetsamBand);
177 } else {
178 pid_t expected_order[kNumChildren] = {0};
179 int ret;
180 for (int i = 0; i < kNumChildren; i++) {
181 expected_order[i] = children_pids[kExpectedOrder[i]];
182 }
183 // Verify the sort order
184 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, kJetsamBand, 0,
185 expected_order, sizeof(expected_order));
186 T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n"
187 "Check os log output for details.\n"
188 "Look for memorystatus_verify_sort_order.");
189 T_END;
190 }
191 });
192 dispatch_activate(ds_allocated);
193
194 pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]);
195 place_proc_in_band(pid, kJetsamBand);
196
197 dispatch_main();
198
199 #undef kNumChildren
200 }
201
202 static pid_t
launch_proc_in_coalition(uint64_t * coalition_ids,int role,int num_pages)203 launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages)
204 {
205 int ret;
206 posix_spawnattr_t attr;
207 pid_t pid;
208 char testpath[PATH_MAX];
209 uint32_t testpath_buf_size = PATH_MAX;
210 char num_pages_str[32] = {0};
211 char *argv[5] = {testpath, "-n", "coalition_member", num_pages_str, NULL};
212 extern char **environ;
213 T_QUIET; T_ASSERT_LT(num_children + 1, (size_t) kMaxChildrenProcs, "Don't create too many children.");
214 ret = posix_spawnattr_init(&attr);
215 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
216 if (coalition_ids != NULL) {
217 for (int i = 0; i < COALITION_NUM_TYPES; i++) {
218 ret = posix_spawnattr_setcoalition_np(&attr, coalition_ids[i], i, role);
219 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setcoalition_np");
220 }
221 }
222
223 ret = snprintf(num_pages_str, sizeof(num_pages_str), "%d", num_pages);
224 T_QUIET; T_ASSERT_LE((size_t) ret, sizeof(num_pages_str), "Don't allocate too many pages.");
225 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
226 T_QUIET; T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath");
227 ret = posix_spawn(&pid, argv[0], NULL, &attr, argv, environ);
228 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn");
229 ret = posix_spawnattr_destroy(&attr);
230 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy");
231 children_pids[num_children++] = pid;
232 return pid;
233 }
234
235 static void
init_coalition(coalition_info_t * coalition)236 init_coalition(coalition_info_t *coalition)
237 {
238 int ret;
239 uint32_t flags = 0;
240 memset(coalition, 0, sizeof(coalition_info_t));
241 for (int i = 0; i < COALITION_NUM_TYPES; i++) {
242 COALITION_CREATE_FLAGS_SET_TYPE(flags, i);
243 ret = coalition_create(&coalition->ids[i], flags);
244 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "coalition_create");
245 }
246
247 /*
248 * Spawn procs for each coalition role, and construct the expected
249 * sorted order.
250 */
251 for (size_t i = 0; i < kNumProcsInCoalition; i++) {
252 int role;
253 if (i == 0) {
254 role = COALITION_TASKROLE_LEADER;
255 } else if (i == 1) {
256 role = COALITION_TASKROLE_EXT;
257 } else if (i == 2) {
258 role = COALITION_TASKROLE_UNDEF;
259 } else {
260 role = COALITION_TASKROLE_XPC;
261 }
262 pid_t pid = launch_proc_in_coalition(coalition->ids, role, 0);
263 coalition->pids[i] = pid;
264 /*
265 * Determine the expected sorted order.
266 * After a bucket has been coalition sorted, coalition members should
267 * be in the following kill order:
268 * undefined coalition members, extensions, xpc services, leader
269 */
270 if (role == COALITION_TASKROLE_LEADER) {
271 coalition->expected_order[3] = pid;
272 } else if (role == COALITION_TASKROLE_XPC) {
273 coalition->expected_order[2] = pid;
274 } else if (role == COALITION_TASKROLE_EXT) {
275 coalition->expected_order[1] = pid;
276 } else {
277 coalition->expected_order[0] = pid;
278 }
279 }
280 }
281
282 static void
place_proc_in_band(pid_t pid,int band)283 place_proc_in_band(pid_t pid, int band)
284 {
285 memorystatus_priority_properties_t props = {0};
286 int ret;
287 props.priority = band;
288 props.user_data = 0;
289 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props));
290 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "move proc to band");
291 }
292
293
294 static void
place_coalition_in_band(const coalition_info_t * coalition,int band)295 place_coalition_in_band(const coalition_info_t *coalition, int band)
296 {
297 for (size_t i = 0; i < kNumProcsInCoalition; i++) {
298 pid_t curr = coalition->pids[i];
299 place_proc_in_band(curr, band);
300 }
301 }
302
303 static void
cleanup_children(void)304 cleanup_children(void)
305 {
306 int ret, status;
307 for (size_t i = 0; i < num_children; i++) {
308 pid_t exited_pid = 0;
309 pid_t curr = children_pids[i];
310 ret = kill(curr, SIGKILL);
311 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kill");
312 while (exited_pid == 0) {
313 exited_pid = waitpid(curr, &status, 0);
314 }
315 T_QUIET; T_ASSERT_POSIX_SUCCESS(exited_pid, "waitpid");
316 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "proc was signaled.");
317 T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "proc was killed");
318 }
319 }
320
321 static bool
has_unrestrict_coalitions()322 has_unrestrict_coalitions()
323 {
324 int ret, val;
325 size_t val_sz;
326
327 val = 0;
328 val_sz = sizeof(val);
329 ret = sysctlbyname("kern.unrestrict_coalitions", &val, &val_sz, NULL, 0);
330 return ret >= 0;
331 }
332
333 static void
unrestrict_coalitions()334 unrestrict_coalitions()
335 {
336 int ret, val = 1;
337 size_t val_sz;
338 val_sz = sizeof(val);
339 ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz);
340 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 1");
341 }
342
343 static void
restrict_coalitions()344 restrict_coalitions()
345 {
346 int ret, val = 0;
347 size_t val_sz;
348 val_sz = sizeof(val);
349 ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz);
350 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 0");
351 }
352
353 static void *
allocate_pages(int num_pages)354 allocate_pages(int num_pages)
355 {
356 int page_size, i;
357 unsigned char *buf;
358
359 page_size = get_vmpage_size();
360 buf = malloc((unsigned long)(num_pages * page_size));
361 for (i = 0; i < num_pages; i++) {
362 ((volatile unsigned char *)buf)[i * page_size] = 1;
363 }
364 return buf;
365 }
366
367 static int
get_vmpage_size()368 get_vmpage_size()
369 {
370 int vmpage_size;
371 size_t size = sizeof(vmpage_size);
372 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
373 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
374 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
375 return vmpage_size;
376 }
377