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