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
96 T_DECL(TESTNAME,
97 "Verify that entitled processes can allocate up to the entitled memory limit",
98 T_META_CHECK_LEAKS(false))
99 {
100 int32_t entitled_max_task_pmem = MAX_TASK_MEM_ENTITLED_VALUE, max_task_pmem = 0, expected_limit;
101 size_t size_entitled_max_task_pmem = sizeof(entitled_max_task_pmem);
102 size_t size_old_entitled_max_task_pmem = sizeof(old_entitled_max_task_pmem);
103 size_t size_max_task_pmem = sizeof(max_task_pmem);
104 int status;
105 pid_t pid, rc;
106 bool signaled;
107 memorystatus_memlimit_properties2_t mmprops;
108
109 int ret = 0;
110
111 // Get the unentitled limit
112 ret = sysctlbyname(MAX_TASK_MEM, &max_task_pmem, &size_max_task_pmem, NULL, 0);
113 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to get max task physical memory.");
114 if (max_task_pmem >= MAX_TASK_MEM_ENTITLED_VALUE) {
115 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);
116 }
117
118 // Use sysctl to change entitled limit
119 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);
120 T_ASSERT_POSIX_SUCCESS(ret, "call sysctlbyname to set entitled hardware mem size.");
121
122 T_ATEND(reset_old_entitled_max_task_mem);
123
124 /*
125 * Spawn child with the normal task limit (just as launchd does for an app)
126 * The child will start suspended, so we can check its memlimit.
127 */
128
129 pid = spawn_child_with_memlimit(max_task_pmem);
130 T_ASSERT_POSIX_SUCCESS(pid, "spawn child with task limit");
131
132 // Check its memlimt
133 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &mmprops, sizeof(mmprops));
134 T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
135 #if ENTITLED
136 expected_limit = MAX_TASK_MEM_ENTITLED_VALUE;
137 #else /* ENTITLED */
138 expected_limit = max_task_pmem;
139 #endif /* ENTITLED */
140 T_ASSERT_EQ(mmprops.v1.memlimit_active, expected_limit, "active limit");
141 T_ASSERT_EQ(mmprops.v1.memlimit_inactive, expected_limit, "inactive limit");
142
143 // Resume the child. It should exit immediately.
144 ret = kill(pid, SIGCONT);
145 T_ASSERT_POSIX_SUCCESS(ret, "kill child");
146
147 // Check child's exit code.
148 while (true) {
149 rc = waitpid(pid, &status, 0);
150 if (rc == -1 && errno == EINTR) {
151 continue;
152 }
153 T_ASSERT_EQ(rc, pid, "waitpid");
154 signaled = WIFSIGNALED(status);
155 T_ASSERT_FALSE(signaled, "Child exited cleanly");
156 ret = WEXITSTATUS(status);
157 T_ASSERT_EQ(ret, 0, "child exited with code 0.");
158 break;
159 }
160 }
161