1 #include <signal.h>
2 #include <spawn_private.h>
3 #include <sys/coalition.h>
4 #include <sys/types.h>
5 #include <sys/spawn_internal.h>
6 #include <sys/sysctl.h>
7 #include <sys/kern_memorystatus.h>
8 #include <mach/coalition.h>
9 #include <mach-o/dyld.h>
10 #include <TargetConditionals.h>
11
12 #include <darwintest.h>
13 #include <darwintest_utils.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 T_META_ENABLED(TARGET_OS_IOS)
20 );
21
22 T_HELPER_DECL(helper, "Dummy helper")
23 {
24 exit(0);
25 }
26
27 static pid_t
get_coalition_leader(pid_t p)28 get_coalition_leader(pid_t p)
29 {
30 static const size_t kMaxPids = 500;
31 int ret;
32 int pid_list[kMaxPids];
33 size_t pid_list_size = sizeof(pid_list);
34
35 int iparam[3];
36 #define p_type iparam[0]
37 #define p_order iparam[1]
38 #define p_pid iparam[2]
39 p_type = COALITION_TYPE_JETSAM;
40 p_order = COALITION_SORT_DEFAULT;
41 p_pid = p;
42
43 ret = sysctlbyname("kern.coalition_pid_list", pid_list, &pid_list_size, iparam, sizeof(iparam));
44 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.coalition_pid_list");
45 T_QUIET; T_ASSERT_LE(pid_list_size, kMaxPids * sizeof(int), "coalition is small enough");
46
47 for (size_t i = 0; i < pid_list_size / sizeof(int); i++) {
48 int curr_pid = pid_list[i];
49 int roles[COALITION_NUM_TYPES] = {};
50 size_t roles_size = sizeof(roles);
51
52 ret = sysctlbyname("kern.coalition_roles", roles, &roles_size, &curr_pid, sizeof(curr_pid));
53 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.coalition_roles");
54 if (roles[COALITION_TYPE_JETSAM] == COALITION_TASKROLE_LEADER) {
55 return curr_pid;
56 }
57 }
58
59 T_FAIL("No leader in coalition!");
60 return 0;
61 }
62
63 static pid_t child_pid = 0;
64 static uint64_t resource_coalition_id = 0;
65 static uint64_t jetsam_coalition_id = 0;
66
67 static void
continue_child_and_wait_for_exit()68 continue_child_and_wait_for_exit()
69 {
70 int ret, stat;
71 /* Resume the child and wait for it to exit. It should exit cleanly. */
72 ret = kill(child_pid, SIGCONT);
73 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to send SIGCONT to child process");
74 ret = waitpid(child_pid, &stat, 0);
75 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "waitpid");
76 T_QUIET; T_ASSERT_TRUE(WIFEXITED(stat), "child exited.");
77 T_QUIET; T_ASSERT_EQ(WEXITSTATUS(stat), 0, "child exited cleanly.");
78 }
79
80 static int original_unrestrict_coalitions_val;
81
82 static void
unrestrict_coalitions()83 unrestrict_coalitions()
84 {
85 int ret, val = 1;
86 size_t val_size = sizeof(val);
87 size_t original_unrestrict_coalitions_size = sizeof(original_unrestrict_coalitions_val);
88 ret = sysctlbyname("kern.unrestrict_coalitions", &original_unrestrict_coalitions_val, &original_unrestrict_coalitions_size, &val, val_size);
89 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "unrestrict_coalitions");
90 }
91
92 static void
reset_unrestrict_coalitions()93 reset_unrestrict_coalitions()
94 {
95 size_t size = sizeof(original_unrestrict_coalitions_val);
96 int ret = sysctlbyname("kern.unrestrict_coalitions", NULL, NULL, &original_unrestrict_coalitions_val, size);
97 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "unrestrict_coalitions");
98 }
99
100 static uint64_t
create_coalition(int type)101 create_coalition(int type)
102 {
103 uint64_t id = 0;
104 uint32_t flags = 0;
105 uint64_t param[2];
106 int ret;
107
108 COALITION_CREATE_FLAGS_SET_TYPE(flags, type);
109 ret = coalition_create(&id, flags);
110 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "coalition_create");
111 T_QUIET; T_ASSERT_GE(id, 0ULL, "coalition_create returned a valid id");
112
113 /* disable notifications for this coalition so launchd doesn't freak out */
114 param[0] = id;
115 param[1] = 0;
116 ret = sysctlbyname("kern.coalition_notify", NULL, NULL, param, sizeof(param));
117 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.coalition_notify");
118
119 return id;
120 }
121
122 static void
terminate_and_reap_coalition(uint64_t coalition_id)123 terminate_and_reap_coalition(uint64_t coalition_id)
124 {
125 int ret = 0;
126 ret = coalition_terminate(coalition_id, 0);
127 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "coalition_terminate");
128
129 ret = coalition_reap(coalition_id, 0);
130 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "coalition_reap");
131 }
132
133 static void
terminate_and_reap_coalitions()134 terminate_and_reap_coalitions()
135 {
136 terminate_and_reap_coalition(jetsam_coalition_id);
137 terminate_and_reap_coalition(resource_coalition_id);
138 }
139
140 /*
141 * Spawns the given command as the leader of the given coalitions.
142 * Process will start in a stopped state (waiting for SIGCONT)
143 */
144 static pid_t
spawn_coalition_leader(const char * path,char * const * argv,uint64_t resource_coal_id,uint64_t jetsam_coal_id)145 spawn_coalition_leader(const char *path, char *const *argv, uint64_t resource_coal_id, uint64_t jetsam_coal_id)
146 {
147 int ret;
148 posix_spawnattr_t attr;
149 extern char **environ;
150 pid_t new_pid = 0;
151 short spawn_flags = POSIX_SPAWN_START_SUSPENDED;
152
153 ret = posix_spawnattr_init(&attr);
154 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawnattr_init failed with %s", strerror(ret));
155
156 ret = posix_spawnattr_setcoalition_np(&attr, jetsam_coal_id,
157 COALITION_TYPE_JETSAM, COALITION_TASKROLE_LEADER);
158 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawnattr_setcoalition_np failed with %s", strerror(ret));
159 ret = posix_spawnattr_setcoalition_np(&attr, resource_coal_id,
160 COALITION_TYPE_RESOURCE, COALITION_TASKROLE_LEADER);
161 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawnattr_setcoalition_np failed with %s", strerror(ret));
162
163 ret = posix_spawnattr_setflags(&attr, spawn_flags);
164 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawnattr_setflags failed with %s", strerror(ret));
165
166 ret = posix_spawn(&new_pid, path, NULL, &attr, argv, environ);
167 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawn failed with %s", strerror(ret));
168
169 ret = posix_spawnattr_destroy(&attr);
170 T_QUIET; T_ASSERT_EQ(ret, 0, "posix_spawnattr_destroy failed with %s\n", strerror(ret));
171 return new_pid;
172 }
173
174 T_DECL(mark_coalition_swappable, "Set coalition is swappable",
175 T_META_ASROOT(true),
176 T_META_BOOTARGS_SET("kern.swap_all_apps=1"))
177 {
178 char testpath[PATH_MAX];
179 uint32_t testpath_buf_size;
180 int ret = 0;
181 pid_t leader_pid;
182
183 unrestrict_coalitions();
184 T_ATEND(reset_unrestrict_coalitions);
185
186 resource_coalition_id = create_coalition(COALITION_TYPE_RESOURCE);
187 jetsam_coalition_id = create_coalition(COALITION_TYPE_JETSAM);
188 T_ATEND(terminate_and_reap_coalitions);
189
190 testpath_buf_size = sizeof(testpath);
191 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
192 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
193
194 char *const args[] = {
195 testpath,
196 "-n",
197 "helper",
198 NULL
199 };
200 child_pid = spawn_coalition_leader(testpath, args, resource_coalition_id, jetsam_coalition_id);
201
202 T_ATEND(continue_child_and_wait_for_exit);
203
204 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_COALITION_IS_SWAPPABLE, child_pid, 0, NULL, 0);
205 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_CMD_GET_PROCESS_COALITION_IS_SWAPPABLE");
206 T_QUIET; T_ASSERT_EQ(ret, 0, "process is not swappable at launch");
207
208 leader_pid = get_coalition_leader(child_pid);
209
210 ret = memorystatus_control(MEMORYSTATUS_CMD_MARK_PROCESS_COALITION_SWAPPABLE, leader_pid, 0, NULL, 0);
211 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_CMD_MARK_PROCESS_COALITION_SWAPPABLE");
212
213 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_COALITION_IS_SWAPPABLE, child_pid, 0, NULL, 0);
214 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_CMD_GET_PROCESS_COALITION_IS_SWAPPABLE");
215 T_QUIET; T_ASSERT_EQ(ret, 1, "process is swappable");
216 }
217