/* * Copyright (c) 2024 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include "exc_helpers.h" #include "skywalk_test_driver.h" #define SKT_DT_HELPER_TIMEOUT 16 #define SKT_DT_NUM_HELPERS 1 #define test_exit(ecode) \ { \ T_LOG("%s:%d := Test exiting with error code %d\n", __func__, __LINE__, (ecode)); \ T_END; \ } static mach_port_t exc_port; static mach_exception_data_type_t exception_code; static mach_exception_data_type_t expected_exception_code; static mach_exception_data_type_t expected_exception_code_ignore; static uint64_t skywalk_features; static bool testing_shutdown_sockets; static bool ignore_test_failures; bool skywalk_in_driver; boolean_t mach_exc_server(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP); kern_return_t catch_mach_exception_raise(mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt, int *flavor, thread_state_t old_state, mach_msg_type_number_t old_stateCnt, thread_state_t new_state, mach_msg_type_number_t *new_stateCnt) { /* Set global variable to indicate exception received */ if (exception != EXC_CRASH && exception != EXC_GUARD) { T_LOG("Unexpected exception received: %d", exception); test_exit(1); return KERN_FAILURE; } exception_code = *code; if (!ignore_test_failures) { /* If the exception code is unexpected, * return failure so we generate a crash report. */ if ((exception_code ^ expected_exception_code) & ~expected_exception_code_ignore) { return KERN_FAILURE; } } /* Return KERN_SUCCESS to prevent report crash from being called. */ return KERN_SUCCESS; } kern_return_t catch_mach_exception_raise_state(mach_port_t exception_port, exception_type_t exception, const mach_exception_data_t code, mach_msg_type_number_t codeCnt, int *flavor, const thread_state_t old_state, mach_msg_type_number_t old_stateCnt, thread_state_t new_state, mach_msg_type_number_t *new_stateCnt) { T_LOG("Unexpected exception handler called: %s", __func__); test_exit(1); return KERN_FAILURE; } kern_return_t catch_mach_exception_raise_state_identity(mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt) { T_LOG("Unexpected exception handler called: %s", __func__); test_exit(1); return KERN_FAILURE; } static void * server_thread(void *arg) { kern_return_t kr; while (1) { /* Handle exceptions on exc_port */ if ((kr = mach_msg_server_once(mach_exc_server, 4096, exc_port, 0)) != KERN_SUCCESS) { mach_error("mach_msg_server_once", kr); test_exit(1); } } return NULL; } static void skywalk_test_driver_init(bool test_shutdown, bool ignore_failures) { kern_return_t kr; pthread_t exception_thread; int error; size_t len; assert(!exc_port); // This routine can only be called once skywalk_in_driver = true; testing_shutdown_sockets = test_shutdown; ignore_test_failures = ignore_failures; /* Allocate and initialize new exception port */ if ((kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exc_port)) != KERN_SUCCESS) { mach_error("mach_port_allocate", kr); test_exit(1); } if ((kr = mach_port_insert_right(mach_task_self(), exc_port, exc_port, MACH_MSG_TYPE_MAKE_SEND)) != KERN_SUCCESS) { mach_error("mach_port_insert_right", kr); test_exit(1); } /* Create exception serving thread */ if ((error = pthread_create(&exception_thread, NULL, server_thread, 0)) != 0) { T_LOG("pthread_create server_thread: %s\n", strerror(error)); test_exit(1); } /* Query the kernel for available skywalk features */ len = sizeof(skywalk_features); sysctlbyname("kern.skywalk.features", &skywalk_features, &len, NULL, 0); pthread_detach(exception_thread); } int skywalk_test_driver_run(struct skywalk_test *skt, int argc, char **argv, uint32_t memfail, bool ignorefail, bool doshutdown, int itersecs) { kern_return_t kr; mach_msg_type_number_t maskCount; exception_mask_t masks[2]; exception_handler_t handlers[2]; exception_behavior_t behaviors[2]; thread_state_flavor_t flavors[2]; int pid, child_status; int testid; size_t len; int error; int itercount = -1; struct timeval start, end; uint32_t memfail_original; len = sizeof(memfail_original); if (sysctlbyname("kern.skywalk.mem.region_mtbf", &memfail_original, &len, NULL, 0) != 0) { SKT_LOG("warning got errno %d getting kern.skywalk.mem.region_mtbf: %s\n", errno, strerror(errno)); } if (memfail_original) { CRSetCrashLogMessage("kern.skywalk.mem.region_mtbf was found already set"); } if (memfail) { CRSetCrashLogMessage("parent set kern.skywalk.mem.region_mtbf"); } T_LOG("setting kern.skywalk.mem.region_mtbf to %"PRId32, memfail); len = sizeof(memfail); if (sysctlbyname("kern.skywalk.mem.region_mtbf", NULL, NULL, &memfail, len) != 0) { SKT_LOG("warning got errno %d setting kern.skywalk.mem.region_mtbf: %s", errno, strerror(errno)); } skywalk_test_driver_init(doshutdown, ignorefail); gettimeofday(&start, NULL); do { /* Get Current exception ports */ if ((kr = task_get_exception_ports(mach_task_self(), EXC_MASK_GUARD | EXC_MASK_CRASH, masks, &maskCount, handlers, behaviors, flavors)) != KERN_SUCCESS) { mach_error("task_get_exception_ports", kr); test_exit(1); } assert(maskCount <= 2); exception_code = 0; if (skt->skt_init != NULL) { T_LOG("Running init"); skt->skt_init(); } expected_exception_code = skt->skt_expected_exception_code; expected_exception_code_ignore = skt->skt_expected_exception_code_ignore; for (int j = 0; j < maskCount; j++) { // Set Exception Ports for Current Task if ((kr = task_set_exception_ports(mach_task_self(), masks[j], exc_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, flavors[j])) != KERN_SUCCESS) { mach_error("task_set_exception_ports", kr); test_exit(1); } } pid = fork(); if (pid == 0) { int ret; len = sizeof(memfail_original); if (sysctlbyname("kern.skywalk.mem.region_mtbf", &memfail_original, &len, NULL, 0) != 0) { SKT_LOG("warning got errno %d getting kern.skywalk.mem.region_mtbf: %s\n", errno, strerror(errno)); } if (memfail_original) { CRSetCrashLogMessage("kern.skywalk.mem.region_mtbf was found already set"); } ret = skt->skt_main(argc, argv); /* return ret; results in "Unresolved" test results */ exit(ret); } T_QUIET; T_ASSERT_GT(pid, 0, "pid must be > 0"); /* Restore exception ports for parent */ for (int j = 0; j < maskCount; j++) { if ((kr = task_set_exception_ports(mach_task_self(), masks[j], handlers[j], EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, flavors[j])) != KERN_SUCCESS) { mach_error("task_set_exception_ports reset", kr); test_exit(1); } } if (testing_shutdown_sockets) { int shutdown_cnt = 0; while (1) { struct timespec st = { .tv_sec = 0, .tv_nsec = 0 }; /* * st.tv_nsec = arc4random_uniform(1000000); // 0-1 ms * st.tv_nsec = arc4random_uniform(10000000); // 0-10 ms */ st.tv_nsec = arc4random_uniform(100000000); /* 0-100 ms */ nanosleep(&st, NULL); error = pid_shutdown_sockets(pid, SHUTDOWN_SOCKET_LEVEL_DISCONNECT_ALL); if (error) { break; } shutdown_cnt++; } if (error == -1 && errno != ESRCH) { SKT_LOG(stderr, "pid_shutdown_sockets: %s", strerror(errno)); test_exit(1); } T_LOG("shutdown cnt %d", shutdown_cnt); } waitpid(pid, &child_status, 0); T_LOG("child_status: %d", child_status); if (skt->skt_fini != NULL) { T_LOG("Running fini"); skt->skt_fini(); } if (WIFEXITED(child_status)) { T_LOG("Child exited with status %d", WEXITSTATUS(child_status)); } if (WIFSIGNALED(child_status)) { T_LOG("Child signaled with signal %d coredump %d", WTERMSIG(child_status), WCOREDUMP(child_status)); } if (exception_code) { T_LOG("Got exception code: Yes (Code 0x%llx)", exception_code); } else { T_LOG("Got exception code: No"); } if (skt->skt_expected_exception_code & ~skt->skt_expected_exception_code_ignore) { T_LOG("Expected exception code: Yes (Code 0x%llx, ignore 0x%llx)", skt->skt_expected_exception_code, skt->skt_expected_exception_code_ignore); } else { T_LOG("Expected exception code: No"); } if ((WIFEXITED(child_status) && WEXITSTATUS(child_status)) || ((exception_code ^ skt->skt_expected_exception_code) & ~skt->skt_expected_exception_code_ignore)) { if (ignorefail) { T_PASS("Returning overall success because" " ignorefail is set for Test %s: %s", skt->skt_testname, skt->skt_testdesc); } else { T_FAIL("Test %s: %s", skt->skt_testname, skt->skt_testdesc); } } else { T_PASS("Test %s: %s", skt->skt_testname, skt->skt_testdesc); } gettimeofday(&end, NULL); gettimeofday(&end, NULL); timersub(&end, &start, &end); } while (--itercount > 0 || end.tv_sec < itersecs); memfail = 0; T_LOG("setting kern.skywalk.mem.region_mtbf to %"PRId32, memfail); len = sizeof(memfail); if (sysctlbyname("kern.skywalk.mem.region_mtbf", NULL, NULL, &memfail, len) != 0) { SKT_LOG("warning got errno %d setting kern.skywalk.mem.region_mtbf: %s", errno, strerror(errno)); } return 0; }