xref: /xnu-12377.81.4/tests/vm/entitlement_increased_memory_limit.c (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
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_BOOTARGS_SET("memstat_no_task_limit_increase=1"),
65 	T_META_TAG_VM_PREFERRED);
66 
67 static int32_t old_entitled_max_task_pmem = 0;
68 
69 static void
reset_old_entitled_max_task_mem(void)70 reset_old_entitled_max_task_mem(void)
71 {
72 	int ret;
73 	size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
74 	// Use sysctl to change entitled limit
75 	ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, NULL, 0, &old_entitled_max_task_pmem, size_old_entitled_max_task_pmem);
76 }
77 
78 T_HELPER_DECL(child, "Child") {
79 	// Doesn't do anything. Will start suspended
80 	// so that its parent can check its memlimits
81 	// and then kill it.
82 	T_PASS("Child exiting");
83 
84 	if (dt_64_bit_kernel()) {
85 #if ENTITLED || ENTITLED_DEBUGGING
86 		verify_jumbo_va(true);
87 #else /* ENTITLED || ENTITLED_DEBUGGING */
88 		verify_jumbo_va(false);
89 #endif /* ENTITLED || ENTITLED_DEBUGGING */
90 	}
91 }
92 
93 static pid_t
spawn_child_with_memlimit(int32_t memlimit)94 spawn_child_with_memlimit(int32_t memlimit)
95 {
96 	posix_spawnattr_t attr;
97 	int ret;
98 	char **args;
99 	char testpath[PATH_MAX];
100 	uint32_t testpath_buf_size;
101 	pid_t pid;
102 
103 	ret = posix_spawnattr_init(&attr);
104 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
105 
106 	testpath_buf_size = sizeof(testpath);
107 	ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
108 	T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
109 	T_LOG("Executable path: %s", testpath);
110 	args = (char *[]){
111 		testpath,
112 		"-n",
113 		"child",
114 		NULL
115 	};
116 
117 	ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
118 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setflags() failed");
119 	ret = posix_spawnattr_setjetsam_ext(&attr,
120 	    0, JETSAM_PRIORITY_FOREGROUND, memlimit, memlimit);
121 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setjetsam_ext");
122 	ret = posix_spawn(&pid, testpath, NULL, &attr, args, *_NSGetEnviron());
123 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn() failed");
124 
125 	return pid;
126 }
127 
128 static void
resume_child_and_verify_exit(pid_t pid)129 resume_child_and_verify_exit(pid_t pid)
130 {
131 	pid_t rc;
132 	bool signaled;
133 	int status, ret;
134 
135 	// Resume the child. It should exit immediately.
136 	ret = kill(pid, SIGCONT);
137 	T_ASSERT_POSIX_SUCCESS(ret, "kill child");
138 
139 	// Check child's exit code.
140 	while (true) {
141 		rc = waitpid(pid, &status, 0);
142 		if (rc == -1 && errno == EINTR) {
143 			continue;
144 		}
145 		T_ASSERT_EQ(rc, pid, "waitpid");
146 		signaled = WIFSIGNALED(status);
147 		T_ASSERT_FALSE(signaled, "Child exited cleanly");
148 		ret = WEXITSTATUS(status);
149 		T_ASSERT_EQ(ret, 0, "child exited with code 0.");
150 		break;
151 	}
152 }
153 
154 
155 T_DECL(TESTNAME,
156     "Verify that entitled processes can allocate up to the entitled memory limit",
157     T_META_CHECK_LEAKS(false) EXTRA_META)
158 {
159 	int32_t entitled_max_task_pmem = 0, max_task_pmem = 0, expected_limit;
160 	size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
161 	size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
162 	size_t size_max_task_pmem = sizeof(max_task_pmem);
163 	pid_t pid;
164 	memorystatus_memlimit_properties2_t mmprops;
165 
166 	int ret = 0;
167 
168 	// Get the unentitled limit
169 	ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
170 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
171 
172 	// Use sysctl to change entitled limit
173 	entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
174 	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);
175 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
176 	T_ATEND(reset_old_entitled_max_task_mem);
177 
178 	if (max_task_pmem >= old_entitled_max_task_pmem) {
179 		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);
180 	}
181 
182 	/*
183 	 * Spawn child with the normal task limit (just as launchd does for an app)
184 	 * The child will start suspended, so we can check its memlimit.
185 	 */
186 
187 	pid = spawn_child_with_memlimit(max_task_pmem);
188 	T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
189 
190 	// Check its memlimt
191 	ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
192 	T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
193 #if ENTITLED || ENTITLED_DEBUGGING
194 	expected_limit = entitled_max_task_pmem;
195 #else /* ENTITLED || ENTITLED_DEBUGGING */
196 	expected_limit = max_task_pmem;
197 #endif /* ENTITLED || ENTITLED_DEBUGGING */
198 	T_ASSERT_EQ(mmprops.v1.memlimit_active, expected_limit, "active limit");
199 	T_ASSERT_EQ(mmprops.v1.memlimit_inactive, expected_limit, "inactive limit");
200 	resume_child_and_verify_exit(pid);
201 }
202 
203 #if ENTITLED || ENTITLED_DEBUGGING
204 T_DECL(SET_MEMLIMIT_TESTNAME,
205     "set memlimit to -1 for entitled process should keep entitled limit.",
206     T_META_CHECK_LEAKS(false))
207 {
208 	int ret;
209 	int32_t entitled_max_task_pmem = 0, max_task_pmem = 0;
210 	size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
211 	size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
212 	size_t size_max_task_pmem = sizeof(max_task_pmem);
213 	memorystatus_memlimit_properties2_t mmprops;
214 	pid_t pid;
215 
216 	// Get the unentitled limit
217 	ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
218 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
219 
220 	// Use sysctl to change entitled limit
221 	entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
222 	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);
223 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
224 	T_ATEND(reset_old_entitled_max_task_mem);
225 
226 	if (max_task_pmem >= old_entitled_max_task_pmem) {
227 		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);
228 	}
229 
230 	pid = spawn_child_with_memlimit(-1);
231 	T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
232 
233 	// Check its memlimt
234 	ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
235 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
236 
237 	T_ASSERT_EQ(mmprops.v1.memlimit_active, entitled_max_task_pmem, "active limit");
238 	T_ASSERT_EQ(mmprops.v1.memlimit_inactive, entitled_max_task_pmem, "inactive limit");
239 
240 	mmprops.v1.memlimit_active = -1;
241 	mmprops.v1.memlimit_inactive = -1;
242 	ret = memorystatus_control(MEMORYSTATUS_CMD_SET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops.v1, sizeof(mmprops.v1));
243 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
244 
245 	// Check its memlimt
246 	ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
247 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
248 
249 	T_ASSERT_EQ(mmprops.v1.memlimit_active, entitled_max_task_pmem, "active limit");
250 	T_ASSERT_EQ(mmprops.v1.memlimit_inactive, entitled_max_task_pmem, "inactive limit");
251 
252 	resume_child_and_verify_exit(pid);
253 }
254 
255 T_DECL(CONVERT_MEMLIMIT_TESTNAME,
256     "convert_memlimit_mb returns entitled limit.")
257 {
258 	int ret;
259 	int32_t entitled_max_task_pmem = 0, max_task_pmem = 0;
260 	size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
261 	size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
262 	size_t size_max_task_pmem = sizeof(max_task_pmem);
263 	pid_t pid;
264 
265 	// Get the unentitled limit
266 	ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
267 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
268 
269 	// Use sysctl to change entitled limit
270 	entitled_max_task_pmem = max_task_pmem + ENTITLED_EXTRA_MEM;
271 	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);
272 	T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
273 	T_ATEND(reset_old_entitled_max_task_mem);
274 
275 	if (max_task_pmem >= old_entitled_max_task_pmem) {
276 		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);
277 	}
278 
279 	pid = spawn_child_with_memlimit(0);
280 	T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
281 
282 	ret = memorystatus_control(MEMORYSTATUS_CMD_CONVERT_MEMLIMIT_MB, pid, (uint32_t) -1, NULL, 0);
283 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
284 	T_QUIET; T_ASSERT_EQ(ret, entitled_max_task_pmem, "got entitled value");
285 
286 	resume_child_and_verify_exit(pid);
287 }
288 
289 T_DECL(LIMIT_WITH_SWAP_TESTNAME, "entitled memory limit equals dram size when swap is enabled.",
290     T_META_ENABLED(!TARGET_OS_VISION),
291     T_META_BOOTARGS_SET("kern.swap_all_apps=1"))
292 {
293 	int32_t entitled_max_task_pmem = 0;
294 	size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
295 	uint64_t memsize_physical;
296 	size_t size_memsize_physical = sizeof(memsize_physical);
297 	int ret;
298 
299 	// Get the entitled limit
300 	ret = sysctlbyname(MAX_TASK_MEM_ENTITLED, &entitled_max_task_pmem, &size_entitled_max_task_pmem, NULL, 0);
301 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
302 	// Get the dram size. Convert it to MB since entitled_max_task_pmem is in MB too and slight discrepancies can occur.
303 	ret = sysctlbyname("hw.memsize_physical", &memsize_physical, &size_memsize_physical, NULL, 0);
304 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
305 	uint64_t memsize_physical_mb = (uint64_t) memsize_physical / (1ULL << 20);
306 	T_QUIET; T_ASSERT_EQ((uint64_t)entitled_max_task_pmem, memsize_physical_mb, "entitled limit == dram size");
307 }
308 #endif /* ENTITLED || ENTITLED_DEBUGGING */
309