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