1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3
4 #include <errno.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <libgen.h>
10
11 #include <sys/stat.h>
12
13 #include <mach/mach_init.h>
14 #include <mach/mach_vm.h>
15 #include <mach-o/dyld.h>
16
17 T_GLOBAL_META(
18 T_META_NAMESPACE("xnu.vm"),
19 T_META_RADAR_COMPONENT_NAME("xnu"),
20 T_META_RADAR_COMPONENT_VERSION("VM"));
21
22 static int expected_code = 0;
23 static int panic_on_unsigned_orig = 0;
24
25 static void *
get_sysctl_value_byname(const char * name,size_t * len)26 get_sysctl_value_byname(const char *name, size_t *len)
27 {
28 int rc = -1;
29 char *val = NULL;
30 size_t val_len = 0;
31
32 rc = sysctlbyname(name, NULL, &val_len, NULL, 0);
33 T_QUIET;
34 T_EXPECT_POSIX_SUCCESS(rc, "retrieve sysctl length");
35 if (T_RESULT == T_RESULT_FAIL) {
36 return NULL;
37 }
38
39 T_WITH_ERRNO;
40 val = malloc(val_len);
41 T_QUIET;
42 T_EXPECT_NOTNULL(val, "malloc fail for sysctl value");
43 if (T_RESULT == T_RESULT_FAIL) {
44 return NULL;
45 }
46
47 rc = sysctlbyname(name, (void *)val, &val_len, NULL, 0);
48 T_QUIET;
49 T_EXPECT_POSIX_SUCCESS(rc, "retrieve sysctl byname");
50 if (T_RESULT == T_RESULT_FAIL) {
51 return NULL;
52 } else {
53 *len = val_len;
54 return (void *)val;
55 }
56 }
57
58 static bool
cs_enforcement_disabled(void)59 cs_enforcement_disabled(void)
60 {
61 int *cs_enforcement = NULL;
62 size_t sysctl_val_len = 0;
63 bool cs_enforcement_disable = false;
64
65 cs_enforcement = (int *)get_sysctl_value_byname("vm.cs_process_enforcement", &sysctl_val_len);
66 T_EXPECT_NOTNULL(cs_enforcement, "sysctl vm.cs_process_enforcement");
67 if (T_RESULT == T_RESULT_FAIL) {
68 goto bail;
69 }
70
71 cs_enforcement_disable = (*cs_enforcement == 0);
72
73 bail:
74 if (cs_enforcement) {
75 free(cs_enforcement);
76 }
77
78 return cs_enforcement_disable;
79 }
80
81 static bool
pmap_cs_enabled(void)82 pmap_cs_enabled(void)
83 {
84 const char *unavailable_reason = "<unknown>";
85 char *kern_version = NULL;
86 char *bootargs = NULL;
87 bool platform_arm64 = false;
88 bool platform_macos = false;
89 bool pmap_cs_enabled = false;
90 size_t sysctl_val_len = 0;
91 unsigned long i;
92
93 #if TARGET_CPU_ARM64
94 platform_arm64 = true;
95 #endif
96
97 if (platform_arm64 == false) {
98 unavailable_reason = "not supported on Intel platform";
99 goto exit;
100 }
101
102 #if TARGET_OS_OSX
103 platform_macos = true;
104 #endif
105
106 if (platform_macos == true) {
107 unavailable_reason = "not supported on macOS";
108 goto exit;
109 }
110
111 /* PMAP_CS technology is not present on below SoCs */
112 const char *pmap_cs_absent_platforms[] = {"T7000", "T7001", "S8000", "S8001", "S8003", "T8002", "T8004"};
113
114 kern_version = (char *)get_sysctl_value_byname("kern.version", &sysctl_val_len);
115 T_EXPECT_NOTNULL(kern_version, "sysctl kern.version(%s)", kern_version);
116 if (T_RESULT == T_RESULT_FAIL) {
117 unavailable_reason = "unable to query sysctl kern.version";
118 goto exit;
119 }
120
121 for (i = 0; i < sizeof(pmap_cs_absent_platforms) / sizeof(pmap_cs_absent_platforms[0]); i++) {
122 if (strstr(kern_version, pmap_cs_absent_platforms[i])) {
123 unavailable_reason = "not supported on this SoC platform";
124 goto exit;
125 }
126 }
127
128 /*
129 * If we reach this point, it means the platform kernel has PMAP_CS code present. However
130 * the code is disabled by default on certain SoCs. Moreover, the code can be disabled
131 * through an explicit boot-arg as well.
132 */
133
134 bootargs = (char *)get_sysctl_value_byname("kern.bootargs", &sysctl_val_len);
135 T_EXPECT_NOTNULL(bootargs, "sysctl kern.bootargs(%s)", bootargs);
136 if (T_RESULT == T_RESULT_FAIL) {
137 unavailable_reason = "unable to query sysctl kern.bootargs";
138 goto exit;
139 }
140
141 /* Disabled explicitly through boot-arg */
142 if (strstr(bootargs, "pmap_cs=0")) {
143 unavailable_reason = "disabled by explicit pmap_cs=0 boot-arg";
144 goto exit;
145 }
146
147 /* PMAP_CS technology is disabled by default on below SoCs */
148 const char *pmap_cs_disabled_platforms[] = {"T8010", "T8011", "T8012", "T8015"};
149
150 for (i = 0; i < sizeof(pmap_cs_disabled_platforms) / sizeof(pmap_cs_disabled_platforms[0]); i++) {
151 if (strstr(kern_version, pmap_cs_disabled_platforms[i]) && !strstr(bootargs, "pmap_cs=1")) {
152 unavailable_reason = "disabled by default on this SoC platform";
153 goto exit;
154 }
155 }
156
157 /* If we reach here, it means PMAP_CS is enabled */
158 pmap_cs_enabled = true;
159
160 exit:
161 if (bootargs) {
162 free(bootargs);
163 }
164
165 if (kern_version) {
166 free(kern_version);
167 }
168
169 if (pmap_cs_enabled == false) {
170 T_LOG("INFO: PMAP_CS is either not available or is disabled on this platform: %s", unavailable_reason);
171 }
172 return pmap_cs_enabled;
173 }
174
175 static bool
pmap_cs_unsigned_pages_allowed(void)176 pmap_cs_unsigned_pages_allowed(void)
177 {
178 char *bootargs = NULL;
179 bool pmap_cs_unsigned_pages_allow = false;
180 size_t sysctl_val_len = 0;
181
182 bootargs = (char *)get_sysctl_value_byname("kern.bootargs", &sysctl_val_len);
183 T_EXPECT_NOTNULL(bootargs, "sysctl kern.bootargs(%s)", bootargs);
184 if (T_RESULT == T_RESULT_FAIL) {
185 goto exit;
186 }
187
188 /*
189 * Checking for boot-args can be tricky, since `strstr` will return based on the
190 * first match for the boot-arg.
191 *
192 * For example: boot-args="pmap_cs_unrestrict_pmap_cs_disable=1 pmap_cs_unrestrict_pmap_cs_disable=0"
193 * The following code will only catch the first one, and believe the boot-arg is set, even though
194 * the kernel will parse both, and consider the latter as the actual value.
195 *
196 * This can be potentially fixed with `strrstr`, but that isn't standard in the C library,
197 * so we don't use it.
198 */
199
200 if (strstr(bootargs, "pmap_cs_unrestrict_pmap_cs_disable=1")) {
201 pmap_cs_unsigned_pages_allow = true;
202 goto exit;
203 } else if (strstr(bootargs, "amfi=1") || strstr(bootargs, "amfi=3") || strstr(bootargs, "amfi=-1")) {
204 /* Any of these boot-args enable pmap_cs_unrestrict_pmap_cs_disable, but it can be overridden */
205 if (!strstr(bootargs, "pmap_cs_unrestrict_pmap_cs_disable=0")) {
206 /* Boot-arg is NOT overridden, so PMAP_CS will allow unsigned pages */
207 pmap_cs_unsigned_pages_allow = true;
208 goto exit;
209 }
210 }
211
212 if (strstr(bootargs, "pmap_cs_allow_modified_code_pages=1")) {
213 pmap_cs_unsigned_pages_allow = true;
214 goto exit;
215 } else if (cs_enforcement_disabled()) {
216 /* cs_enforcement_disable enables pmap_cs_allow_modified_code_pages, but it can be overridden */
217 if (!strstr(bootargs, "pmap_cs_allow_modified_code_pages=0")) {
218 /* Boot-arg is NOT overridden, so PMAP_CS will allow unsigned pages */
219 pmap_cs_unsigned_pages_allow = true;
220 goto exit;
221 }
222 }
223
224 exit:
225 if (bootargs) {
226 free(bootargs);
227 }
228
229 return pmap_cs_unsigned_pages_allow;
230 }
231
232 static void
pre_test(void)233 pre_test(void)
234 {
235 bool end_test = false;
236 int *panic_on_unsigned = NULL;
237 size_t sysctl_val_len = 0;
238
239 /* When the test helper executes unsigned code, it returns a 1 */
240 expected_code = 1;
241
242 if (pmap_cs_enabled()) {
243 /*
244 * When PMAP_CS is enabled, VM layer delegates all executable code signing enforcement
245 * to it, and doesn't participate in executable code validation. If PMAP_CS isn't allowing
246 * unsigned code pages to execute, then we expect a SIGBUS error from the helper.
247 */
248 if (!pmap_cs_unsigned_pages_allowed()) {
249 expected_code = 10;
250 } else {
251 T_LOG("WANRING: PMAP_CS is present but allowing unsigned code pages");
252 }
253 } else {
254 /*
255 * When PMAP_CS isn't enabled, VM layer handles all code signing enforcement, including
256 * that for executable code. If VM layer isn't allowing unsigned code pages to execute, then
257 * we expect a SIGKILL error from the helper.
258 */
259 if (!cs_enforcement_disabled()) {
260 expected_code = 9;
261 } else {
262 T_LOG("WANRING: unsigned code pages are allowed as code signing enforcement is disabled");
263 }
264 }
265
266 #if defined(__arm64__)
267 panic_on_unsigned = (int *)get_sysctl_value_byname("vm.panic_on_unsigned_execute",
268 &sysctl_val_len);
269 if (panic_on_unsigned) {
270 if (*panic_on_unsigned == 1) {
271 panic_on_unsigned_orig = 1;
272 *panic_on_unsigned = 0;
273 T_EXPECT_POSIX_SUCCESS(sysctlbyname("vm.panic_on_unsigned_execute", NULL, 0, panic_on_unsigned, sizeof(int)),
274 "set sysctl vm.panic_on_unsigned_execute to 0");
275 if (T_RESULT == T_RESULT_FAIL) {
276 end_test = true;
277 goto bail;
278 }
279 }
280 }
281 #endif /* defined(__arm64__) */
282
283 bail:
284 if (panic_on_unsigned) {
285 free(panic_on_unsigned);
286 }
287
288 if (end_test) {
289 T_END;
290 }
291
292 return;
293 }
294
295 static void
post_test(void)296 post_test(void)
297 {
298 #if defined(__arm64__)
299 if (panic_on_unsigned_orig == 1) {
300 T_EXPECT_POSIX_SUCCESS(sysctlbyname("vm.panic_on_unsigned_execute", NULL, 0, &panic_on_unsigned_orig, sizeof(int)),
301 "restore sysctl vm.panic_on_unsigned_execute to 1");
302 }
303 #endif
304 return;
305 }
306
307 static void
check_executable(char * exec_path)308 check_executable(char *exec_path)
309 {
310 int ret = -1;
311 struct stat sb;
312
313 ret = stat(exec_path, &sb);
314 T_QUIET;
315 T_ASSERT_POSIX_SUCCESS(ret, "check executable %s", exec_path);
316 T_QUIET;
317 T_ASSERT_BITS_SET(sb.st_mode, S_IXUSR, "check %s EXEC permission", exec_path);
318 }
319
320 T_DECL(code_signing, "testing code siging with unsigned syscall code - \
321 rdar://problem/23770418", T_META_RUN_CONCURRENTLY(true),
322 T_META_IGNORECRASHES(".*vm_test_code_signing_helper.*"),
323 T_META_ENABLED(false) /* rdar://98779213 */, T_META_TAG_VM_NOT_ELIGIBLE)
324 {
325 int ret = 0;
326 int exit_code = 0;
327 int status = 0;
328 int signal = 0;
329 int timeout = 30;
330
331 pid_t child_pid = 0;
332 bool wait_ret = true;
333
334 char binary_path[MAXPATHLEN], *binary_dir = NULL;
335 uint32_t path_size = sizeof(binary_path);
336
337 ret = _NSGetExecutablePath(binary_path, &path_size);
338 T_QUIET;
339 T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath: %s, size: %d",
340 binary_path, path_size);
341 binary_dir = dirname(binary_path);
342 T_QUIET;
343 T_WITH_ERRNO;
344 T_ASSERT_NOTNULL(binary_dir, "get binary directory: %s", binary_dir);
345
346 char *helper_binary = "vm_test_code_signing_helper";
347 snprintf(binary_path, MAXPATHLEN, "%s/%s", binary_dir, helper_binary);
348 check_executable(binary_path);
349
350 char *helper_args[] = {binary_path, NULL};
351
352 pre_test();
353 T_ATEND(post_test);
354
355 ret = dt_launch_tool(&child_pid, helper_args, false, NULL, NULL);
356 T_ASSERT_EQ(ret, 0, "launch helper: %s", helper_binary);
357
358 wait_ret = dt_waitpid(child_pid, &status, &signal, timeout);
359 if (wait_ret) {
360 T_LOG("helper returned: %d", status);
361 exit_code = status;
362 } else {
363 if (signal != 0) {
364 T_LOG("signal terminated helper: %d", signal);
365 exit_code = signal;
366 }
367
368 if (status != 0) {
369 T_LOG("helper exited: %d", status);
370 exit_code = status;
371 }
372 }
373
374 T_ASSERT_EQ(exit_code, expected_code, "helper exits: %d, expected: %d",
375 exit_code, expected_code);
376 }
377