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