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