xref: /xnu-11417.140.69/tests/vm_test_code_signing.c (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
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