1 #include <stdlib.h>
2 #include <string.h>
3 #include <signal.h>
4 #include <spawn.h>
5 #include <spawn_private.h>
6
7 #include <sys/sysctl.h>
8 #include <sys/errno.h>
9 #include <sys/kern_memorystatus.h>
10
11 #include <crt_externs.h>
12 #include <mach-o/dyld.h>
13 #include <darwintest.h>
14 #include <darwintest_utils.h>
15 #include <TargetConditionals.h>
16
17 #include "memorystatus_assertion_helpers.h"
18 #include "jumbo_va_spaces_common.h"
19
20 #define MAX_TASK_MEM "kern.max_task_pmem"
21
22 #if ENTITLED_DEBUGGING
23 // Keep aligned with kern_memorystatus.c:
24 #define MAX_TASK_MEM_ENTITLED "kern.entitled_dev_max_task_pmem"
25 #else
26 #if BINCOMPAT
27 #define MAX_TASK_MEM_ENTITLED "kern.entitled_bincompat_max_task_pmem"
28 #else // BINCOMPAT
29 #define MAX_TASK_MEM_ENTITLED "kern.entitled_max_task_pmem"
30 #endif // !BINCOMPAT
31 #endif // ENTITLED_DEBUGGING
32
33 #define ENTITLED_EXTRA_MEM 512
34
35 #if ENTITLED_DEBUGGING
36 #define TESTNAME entitlement_debugging_increased_memory_limit_entitled
37 #define SET_MEMLIMIT_TESTNAME entitlement_debugging_increased_memory_limit_set_memlimit
38 #define CONVERT_MEMLIMIT_TESTNAME entitlement_debugging_increased_memory_limit_convert_memlimit_mb
39 #define LIMIT_WITH_SWAP_TESTNAME entitlement_debugging_increased_memory_limit_with_swap
40 #define EXTRA_META
41 #elif ENTITLED
42 #if BINCOMPAT
43 #define TESTNAME entitlement_increased_memory_limit_entitled_bincompat
44 #define SET_MEMLIMIT_TESTNAME entitlement_increased_memory_limit_set_memlimit_bincompat
45 #define CONVERT_MEMLIMIT_TESTNAME entitlement_increased_memory_limit_convert_memlimit_mb_bincompat
46 #define LIMIT_WITH_SWAP_TESTNAME entitlement_increased_memory_limit_with_swap_bincompat
47 #else /* BINCOMPAT */
48 #define TESTNAME entitlement_increased_memory_limit_entitled
49 #define SET_MEMLIMIT_TESTNAME entitlement_increased_memory_limit_set_memlimit
50 #define CONVERT_MEMLIMIT_TESTNAME entitlement_increased_memory_limit_convert_memlimit_mb
51 #define LIMIT_WITH_SWAP_TESTNAME entitlement_increased_memory_limit_with_swap
52 #endif
53 #define EXTRA_META
54 #else /* ENTITLED, ENTITLED_DEBUGGING */
55 #define TESTNAME entitlement_increased_memory_limit_unentitled
56 /* rdar://133954365 */
57 #define EXTRA_META , T_META_ENABLED(false)
58 #endif /* ENTITLED */
59
60 T_GLOBAL_META(
61 T_META_NAMESPACE("xnu.vm"),
62 T_META_RADAR_COMPONENT_NAME("xnu"),
63 T_META_RADAR_COMPONENT_VERSION("VM"),
64 T_META_TAG_VM_PREFERRED);
65
66 static int32_t old_entitled_max_task_pmem = 0;
67
68 static void
reset_old_entitled_max_task_mem(void)69 reset_old_entitled_max_task_mem(void)
70 {
71 int ret;
72 size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
73 // Use sysctl to change entitled limit
74 ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, NULL, 0, &old_entitled_max_task_pmem, size_old_entitled_max_task_pmem);
75 }
76
77 T_HELPER_DECL(child, "Child") {
78 // Doesn't do anything. Will start suspended
79 // so that its parent can check its memlimits
80 // and then kill it.
81 T_PASS("Child exiting");
82
83 if (dt_64_bit_kernel()) {
84 #if ENTITLED || ENTITLED_DEBUGGING
85 verify_jumbo_va(true);
86 #else /* ENTITLED || ENTITLED_DEBUGGING */
87 verify_jumbo_va(false);
88 #endif /* ENTITLED || ENTITLED_DEBUGGING */
89 }
90 }
91
92 static pid_t
spawn_child_with_memlimit(int32_t memlimit)93 spawn_child_with_memlimit(int32_t memlimit)
94 {
95 posix_spawnattr_t attr;
96 int ret;
97 char **args;
98 char testpath[PATH_MAX];
99 uint32_t testpath_buf_size;
100 pid_t pid;
101
102 ret = posix_spawnattr_init(&attr);
103 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
104
105 testpath_buf_size = sizeof(testpath);
106 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
107 T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
108 T_LOG("Executable path: %s", testpath);
109 args = (char *[]){
110 testpath,
111 "-n",
112 "child",
113 NULL
114 };
115
116 ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
117 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setflags() failed");
118 ret = posix_spawnattr_setjetsam_ext(&attr,
119 0, JETSAM_PRIORITY_FOREGROUND, memlimit, memlimit);
120 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setjetsam_ext");
121 ret = posix_spawn(&pid, testpath, NULL, &attr, args, *_NSGetEnviron());
122 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn() failed");
123
124 return pid;
125 }
126
127 static void
resume_child_and_verify_exit(pid_t pid)128 resume_child_and_verify_exit(pid_t pid)
129 {
130 pid_t rc;
131 bool signaled;
132 int status, ret;
133
134 // Resume the child. It should exit immediately.
135 ret = kill(pid, SIGCONT);
136 T_ASSERT_POSIX_SUCCESS(ret, "kill child");
137
138 // Check child's exit code.
139 while (true) {
140 rc = waitpid(pid, &status, 0);
141 if (rc == -1 && errno == EINTR) {
142 continue;
143 }
144 T_ASSERT_EQ(rc, pid, "waitpid");
145 signaled = WIFSIGNALED(status);
146 T_ASSERT_FALSE(signaled, "Child exited cleanly");
147 ret = WEXITSTATUS(status);
148 T_ASSERT_EQ(ret, 0, "child exited with code 0.");
149 break;
150 }
151 }
152
153
154 T_DECL(TESTNAME,
155 "Verify that entitled processes can allocate up to the entitled memory limit",
156 T_META_CHECK_LEAKS(false) EXTRA_META)
157 {
158 int32_t entitled_max_task_pmem = 0, max_task_pmem = 0, expected_limit;
159 size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
160 size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
161 size_t size_max_task_pmem = sizeof(max_task_pmem);
162 pid_t pid;
163 memorystatus_memlimit_properties2_t mmprops;
164
165 int ret = 0;
166
167 // Get the unentitled limit
168 ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
169 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
170
171 // Use sysctl to change entitled limit
172 entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
173 ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, &old_entitled_max_task_pmem, &size_old_entitled_max_task_pmem, &entitled_max_task_pmem, size_entitled_max_task_pmem);
174 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
175 T_ATEND(reset_old_entitled_max_task_mem);
176
177 if (max_task_pmem >= old_entitled_max_task_pmem) {
178 T_SKIP("max_task_pmem (%lld) is larger than entitled value (%lld). Skipping test on this device.", max_task_pmem, old_entitled_max_task_pmem);
179 }
180
181 /*
182 * Spawn child with the normal task limit (just as launchd does for an app)
183 * The child will start suspended, so we can check its memlimit.
184 */
185
186 pid = spawn_child_with_memlimit(max_task_pmem);
187 T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
188
189 // Check its memlimt
190 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
191 T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
192 #if ENTITLED || ENTITLED_DEBUGGING
193 expected_limit = entitled_max_task_pmem;
194 #else /* ENTITLED || ENTITLED_DEBUGGING */
195 expected_limit = max_task_pmem;
196 #endif /* ENTITLED || ENTITLED_DEBUGGING */
197 T_ASSERT_EQ(mmprops.v1.memlimit_active, expected_limit, "active limit");
198 T_ASSERT_EQ(mmprops.v1.memlimit_inactive, expected_limit, "inactive limit");
199 resume_child_and_verify_exit(pid);
200 }
201
202 #if ENTITLED || ENTITLED_DEBUGGING
203 T_DECL(SET_MEMLIMIT_TESTNAME,
204 "set memlimit to -1 for entitled process should keep entitled limit.",
205 T_META_CHECK_LEAKS(false))
206 {
207 int ret;
208 int32_t entitled_max_task_pmem = 0, max_task_pmem = 0;
209 size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
210 size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
211 size_t size_max_task_pmem = sizeof(max_task_pmem);
212 memorystatus_memlimit_properties2_t mmprops;
213 pid_t pid;
214
215 // Get the unentitled limit
216 ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
217 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
218
219 // Use sysctl to change entitled limit
220 entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
221 ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, &old_entitled_max_task_pmem, &size_old_entitled_max_task_pmem, &entitled_max_task_pmem, size_entitled_max_task_pmem);
222 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
223 T_ATEND(reset_old_entitled_max_task_mem);
224
225 if (max_task_pmem >= old_entitled_max_task_pmem) {
226 T_SKIP("max_task_pmem (%lld) is larger than entitled value (%lld). Skipping test on this device.", max_task_pmem, old_entitled_max_task_pmem);
227 }
228
229 pid = spawn_child_with_memlimit(-1);
230 T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
231
232 // Check its memlimt
233 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
234 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
235
236 T_ASSERT_EQ(mmprops.v1.memlimit_active, entitled_max_task_pmem, "active limit");
237 T_ASSERT_EQ(mmprops.v1.memlimit_inactive, entitled_max_task_pmem, "inactive limit");
238
239 mmprops.v1.memlimit_active = -1;
240 mmprops.v1.memlimit_inactive = -1;
241 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops.v1, sizeof(mmprops.v1));
242 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
243
244 // Check its memlimt
245 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
246 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
247
248 T_ASSERT_EQ(mmprops.v1.memlimit_active, entitled_max_task_pmem, "active limit");
249 T_ASSERT_EQ(mmprops.v1.memlimit_inactive, entitled_max_task_pmem, "inactive limit");
250
251 resume_child_and_verify_exit(pid);
252 }
253
254 T_DECL(CONVERT_MEMLIMIT_TESTNAME,
255 "convert_memlimit_mb returns entitled limit.")
256 {
257 int ret;
258 int32_t entitled_max_task_pmem = 0, max_task_pmem = 0;
259 size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
260 size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
261 size_t size_max_task_pmem = sizeof(max_task_pmem);
262 pid_t pid;
263
264 // Get the unentitled limit
265 ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
266 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
267
268 // Use sysctl to change entitled limit
269 entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
270 ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, &old_entitled_max_task_pmem, &size_old_entitled_max_task_pmem, &entitled_max_task_pmem, size_entitled_max_task_pmem);
271 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
272 T_ATEND(reset_old_entitled_max_task_mem);
273
274 if (max_task_pmem >= old_entitled_max_task_pmem) {
275 T_SKIP("max_task_pmem (%lld) is larger than entitled value (%lld). Skipping test on this device.", max_task_pmem, old_entitled_max_task_pmem);
276 }
277
278 pid = spawn_child_with_memlimit(0);
279 T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
280
281 ret = memorystatus_control(MEMORYSTATUS_CMD_CONVERT_MEMLIMIT_MB, pid, (uint32_t) -1, NULL, 0);
282 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
283 T_QUIET; T_ASSERT_EQ(ret, entitled_max_task_pmem, "got entitled value");
284
285 resume_child_and_verify_exit(pid);
286 }
287
288 T_DECL(LIMIT_WITH_SWAP_TESTNAME, "entitled memory limit equals dram size when swap is enabled.",
289 T_META_ENABLED(!TARGET_OS_VISION),
290 T_META_BOOTARGS_SET("kern.swap_all_apps=1"))
291 {
292 int32_t entitled_max_task_pmem = 0;
293 size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
294 uint64_t memsize_physical;
295 size_t size_memsize_physical = sizeof(memsize_physical);
296 int ret;
297
298 // Get the entitled limit
299 ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, &entitled_max_task_pmem, &size_entitled_max_task_pmem, NULL, 0);
300 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
301 // Get the dram size. Convert it to MB since entitled_max_task_pmem is in MB too and slight discrepancies can occur.
302 ret = sysctlbyname("hw.memsize_physical", &memsize_physical, &size_memsize_physical, NULL, 0);
303 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
304 uint64_t memsize_physical_mb = (uint64_t) memsize_physical / (1ULL << 20);
305 T_QUIET; T_ASSERT_EQ((uint64_t)entitled_max_task_pmem, memsize_physical_mb, "entitled limit == dram size");
306 }
307 #endif /* ENTITLED || ENTITLED_DEBUGGING */
308