#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef OCCLUM_DISABLE_DCAP #include #include #endif #include "test.h" #define SGX_LEAF 0x12 #define SGX2_SHIFT 1 typedef struct t_cpuid { unsigned int eax; unsigned int ebx; unsigned int ecx; unsigned int edx; } t_cpuid_t; static inline void native_cpuid(int leaf, int subleaf, t_cpuid_t *p) { memset(p, 0, sizeof(*p)); /* ecx is often an input as well as an output. */ asm volatile("cpuid" : "=a" (p->eax), "=b" (p->ebx), "=c" (p->ecx), "=d" (p->edx) : "a" (leaf), "c" (subleaf)); } // check the hw is SGX1 or SGX2 int is_sgx2_supported() { t_cpuid_t cpu_info = {0, 0, 0, 0}; native_cpuid(SGX_LEAF, 0, &cpu_info); if (!(cpu_info.eax & (1 << SGX2_SHIFT))) { return 0; } return 1; } // ============================================================================ // Test cases for TTY ioctls // ============================================================================ int test_tty_ioctl_TIOCGWINSZ(void) { struct winsize winsize; if (isatty(STDOUT_FILENO)) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize) < 0) { THROW_ERROR("failed to ioctl TIOCGWINSZ"); } } else { // FIXME: /dev/tty should be opened. But it has not been implemented in Occlum yet. // So we just skip this test if STDOUT is redirected. printf("Warning: test_tty_ioctl_TIOCGWINSZ is skipped\n"); } return 0; } int test_ioctl_TCGETS_TCSETS(void) { struct termios term; // FIXME: /dev/tty should be opened. But it has not been implemented in Occlum yet. // So we just skip this test if STDOUT is redirected. if (!isatty(STDOUT_FILENO)) { printf("Warning: test_tty_ioctl_TIOCGWINSZ is skipped\n"); return 0; } if (ioctl(STDOUT_FILENO, TCGETS, &term) < 0) { THROW_ERROR("failed to ioctl TCGETS"); } if (ioctl(STDOUT_FILENO, TCSETS, &term) < 0) { THROW_ERROR("failed to ioctl TCSETS"); } const char *file_path = "/root/test_ioctl.txt"; int flags = O_RDONLY | O_CREAT | O_TRUNC; int mode = 00666; int fd = open(file_path, flags, mode); if (fd < 0) { THROW_ERROR("failed to open test file"); } int pipefds[2]; int ret = pipe(pipefds); if (ret != 0) { THROW_ERROR("failed to create pipe"); } ret = ioctl(fd, TCGETS, &term); if (ret != -1 || errno != ENOTTY) { THROW_ERROR("failed catch error"); } ret = ioctl(pipefds[0], TCGETS, &term); if (ret != -1 || errno != ENOTTY) { THROW_ERROR("failed catch error"); } close(fd); close(pipefds[0]); close(pipefds[1]); return 0; } // ============================================================================ // Test cases for SGX ioctls // ============================================================================ typedef struct { sgx_report_data_t report_data; // input sgx_quote_sign_type_t quote_type; // input sgx_spid_t spid; // input sgx_quote_nonce_t nonce; // input const uint8_t *sigrl_ptr; // input (optional) uint32_t sigrl_len; // input (optional) uint32_t quote_buf_len; // input union { uint8_t *as_buf; sgx_quote_t *as_quote; } quote; // output } sgxioc_gen_epid_quote_arg_t; typedef struct { const sgx_target_info_t *target_info; // input (optinal) const sgx_report_data_t *report_data; // input (optional) sgx_report_t *report; // output } sgxioc_create_report_arg_t; typedef struct { const sgx_key_request_t *key_request; // Input sgx_key_128bit_t *key; // Output } sgxioc_get_key_arg_t; #ifndef OCCLUM_DISABLE_DCAP typedef struct { sgx_report_data_t *report_data; // input uint32_t *quote_len; // input/output uint8_t *quote_buf; // output } sgxioc_gen_dcap_quote_arg_t; typedef struct { const uint8_t *quote_buf; // input uint32_t quote_size; // input uint32_t *collateral_expiration_status; // output sgx_ql_qv_result_t *quote_verification_result; // output uint32_t supplemental_data_size; // input uint8_t *supplemental_data; // output } sgxioc_ver_dcap_quote_arg_t; #endif #define SGXIOC_IS_EDMM_SUPPORTED _IOR('s', 0, int) #define SGXIOC_GET_EPID_GROUP_ID _IOR('s', 1, sgx_epid_group_id_t) #define SGXIOC_GEN_EPID_QUOTE _IOWR('s', 2, sgxioc_gen_epid_quote_arg_t) #define SGXIOC_SELF_TARGET _IOR('s', 3, sgx_target_info_t) #define SGXIOC_CREATE_REPORT _IOWR('s', 4, sgxioc_create_report_arg_t) #define SGXIOC_VERIFY_REPORT _IOW('s', 5, sgx_report_t) #define SGXIOC_DETECT_DCAP_DRIVER _IOR('s', 6, int) #ifndef OCCLUM_DISABLE_DCAP #define SGXIOC_GET_DCAP_QUOTE_SIZE _IOR('s', 7, uint32_t) #define SGXIOC_GEN_DCAP_QUOTE _IOWR('s', 8, sgxioc_gen_dcap_quote_arg_t) #define SGXIOC_GET_DCAP_SUPPLEMENTAL_SIZE _IOR('s', 9, uint32_t) #define SGXIOC_VER_DCAP_QUOTE _IOWR('s', 10, sgxioc_ver_dcap_quote_arg_t) #endif #define SGXIOC_GET_KEY _IOWR('s', 11, sgxioc_get_key_arg_t) // The max number of retries if ioctl returns EBUSY #define IOCTL_MAX_RETRIES 20 typedef int(*sgx_ioctl_test_body_t)(int sgx_fd); static int do_SGXIOC_IS_EDMM_SUPPORTED(int sgx_fd) { int is_edmm_supported = 0; if (ioctl(sgx_fd, SGXIOC_IS_EDMM_SUPPORTED, &is_edmm_supported) < 0) { THROW_ERROR("failed to ioctl /dev/sgx"); } printf(" SGX EDMM support: %d\n", is_edmm_supported); return 0; } static int do_SGXIOC_GET_EPID_GROUP_ID(int sgx_fd) { int nretries = 0; while (nretries < IOCTL_MAX_RETRIES) { sgx_epid_group_id_t epid_group_id = { 0 }; int ret = ioctl(sgx_fd, SGXIOC_GET_EPID_GROUP_ID, &epid_group_id); if (ret == 0) { break; } else if (errno != EBUSY) { THROW_ERROR("failed to ioctl /dev/sgx"); } printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second."); sleep(1); nretries++; } if (nretries == IOCTL_MAX_RETRIES) { THROW_ERROR("failed to ioctl /dev/sgx due to timeout"); } return 0; } static int do_SGXIOC_GEN_QUOTE(int sgx_fd) { uint8_t quote_buf[2048] = { 0 }; sgxioc_gen_epid_quote_arg_t gen_quote_arg = { .report_data = { { 0 } }, // input (empty is ok) .quote_type = SGX_LINKABLE_SIGNATURE, // input .spid = { { 0 } }, // input (empty is ok) .nonce = { { 0 } }, // input (empty is ok) .sigrl_ptr = NULL, // input (optional) .sigrl_len = 0, // input (optional) .quote_buf_len = sizeof(quote_buf), // input .quote = { .as_buf = (uint8_t *) quote_buf } // output }; int nretries = 0; while (nretries < IOCTL_MAX_RETRIES) { int ret = ioctl(sgx_fd, SGXIOC_GEN_EPID_QUOTE, &gen_quote_arg); if (ret == 0) { break; } else if (errno != EBUSY) { THROW_ERROR("failed to ioctl /dev/sgx"); } printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second."); sleep(1); nretries++; } if (nretries == IOCTL_MAX_RETRIES) { THROW_ERROR("failed to ioctl /dev/sgx due to timeout"); } sgx_quote_t *quote = (sgx_quote_t *)quote_buf; #ifndef SGX_MODE_HYPER if (quote->sign_type != SGX_LINKABLE_SIGNATURE) { THROW_ERROR("invalid quote: wrong sign type"); } #endif if (quote->signature_len == 0) { THROW_ERROR("invalid quote: zero-length signature"); } if (memcmp(&gen_quote_arg.report_data, "e->report_body.report_data, sizeof(sgx_report_data_t)) != 0) { THROW_ERROR("invalid quote: wrong report data"); } return 0; } static int do_sgx_ioctl_test(sgx_ioctl_test_body_t test_body) { // Init test int sgx_fd; if ((sgx_fd = open("/dev/sgx", O_RDONLY)) < 0) { THROW_ERROR("failed to open /dev/sgx "); } // Do test int ret = test_body(sgx_fd); // Clean up test close(sgx_fd); return ret; } static int do_SGXIOC_SELF_TARGET(int sgx_fd) { sgx_target_info_t target_info; if (ioctl(sgx_fd, SGXIOC_SELF_TARGET, &target_info) < 0) { THROW_ERROR("failed to ioctl /dev/sgx"); } return 0; } static int do_SGXIOC_CREATE_AND_VERIFY_REPORT(int sgx_fd) { sgx_target_info_t target_info; if (ioctl(sgx_fd, SGXIOC_SELF_TARGET, &target_info) < 0) { THROW_ERROR("failed to ioctl /dev/sgx"); } sgx_report_data_t report_data; sgx_report_t report; sgxioc_create_report_arg_t args[] = { { .target_info = (const sgx_target_info_t *) &target_info, .report_data = NULL, .report = &report }, { .target_info = (const sgx_target_info_t *) &target_info, .report_data = (const sgx_report_data_t *) &report_data, .report = &report } }; for (int arg_i = 0; arg_i < ARRAY_SIZE(args); arg_i++) { memset(&report, 0, sizeof(report)); sgxioc_create_report_arg_t *arg = &args[arg_i]; if (ioctl(sgx_fd, SGXIOC_CREATE_REPORT, arg) < 0) { THROW_ERROR("failed to create report"); } if (ioctl(sgx_fd, SGXIOC_VERIFY_REPORT, &report) < 0) { THROW_ERROR("failed to verify report"); } } return 0; } static int do_SGXIOC_GET_KEY(int sgx_fd) { sgx_key_request_t key_request = { 0 }; sgx_key_128bit_t key = { 0 }; key_request.key_name = SGX_KEYSELECT_SEAL; // SGX_KEYSELECT_REPORT key_request.key_policy = SGX_KEYPOLICY_MRENCLAVE; // SGX_KEYPOLICY_MRSIGNER sgxioc_get_key_arg_t args = { .key_request = (const sgx_key_request_t *) &key_request, .key = &key, }; if (ioctl(sgx_fd, SGXIOC_GET_KEY, &args) < 0) { THROW_ERROR("failed to ioctl /dev/sgx"); } printf("key: \n"); for (int i = 0; i < 16; i++) { printf("%x ", key[i]); } printf("\n"); return 0; } #ifndef OCCLUM_DISABLE_DCAP #define REPORT_BODY_OFFSET 48 static int generate_and_verify_dcap_quote(int sgx_fd) { // get quote size uint32_t quote_size = 0; if (ioctl(sgx_fd, SGXIOC_GET_DCAP_QUOTE_SIZE, "e_size) < 0) { THROW_ERROR("failed to get quote size"); } // get quote uint8_t *quote_buffer = (uint8_t *)malloc(quote_size); if (NULL == quote_buffer) { THROW_ERROR("Couldn't allocate quote_buffer"); } memset(quote_buffer, 0, quote_size); sgx_report_data_t report_data = { 0 }; char *data = "ioctl DCAP report data example"; memcpy(report_data.d, data, strlen(data)); sgxioc_gen_dcap_quote_arg_t gen_quote_arg = { .report_data = &report_data, .quote_len = "e_size, .quote_buf = quote_buffer }; if (ioctl(sgx_fd, SGXIOC_GEN_DCAP_QUOTE, &gen_quote_arg) < 0) { THROW_ERROR("failed to get quote"); } if (memcmp((void *) & ((sgx_report_body_t *)(quote_buffer + REPORT_BODY_OFFSET))->report_data, (void *)&report_data, sizeof(sgx_report_data_t)) != 0) { THROW_ERROR("mismathced report data"); } uint32_t collateral_expiration_status = 1; sgx_ql_qv_result_t quote_verification_result = SGX_QL_QV_RESULT_UNSPECIFIED; uint32_t supplemental_size = 0; if (ioctl(sgx_fd, SGXIOC_GET_DCAP_SUPPLEMENTAL_SIZE, &supplemental_size) < 0) { THROW_ERROR("failed to get supplemental data size"); } uint8_t *supplemental_buffer = (uint8_t *)malloc(supplemental_size); if (NULL == supplemental_buffer) { THROW_ERROR("Couldn't allocate quote_buffer"); } memset(supplemental_buffer, 0, supplemental_size); sgxioc_ver_dcap_quote_arg_t ver_quote_arg = { .quote_buf = quote_buffer, .quote_size = quote_size, .collateral_expiration_status = &collateral_expiration_status, .quote_verification_result = "e_verification_result, .supplemental_data_size = supplemental_size, .supplemental_data = supplemental_buffer }; if (ioctl(sgx_fd, SGXIOC_VER_DCAP_QUOTE, &ver_quote_arg) < 0) { THROW_ERROR("failed to verify quote"); } switch (quote_verification_result) { case SGX_QL_QV_RESULT_OK: return 0; case SGX_QL_QV_RESULT_CONFIG_NEEDED: case SGX_QL_QV_RESULT_OUT_OF_DATE: case SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED: case SGX_QL_QV_RESULT_SW_HARDENING_NEEDED: case SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED: printf("WARN: App: Verification completed with Non-terminal result: %x\n", quote_verification_result); return 0; case SGX_QL_QV_RESULT_INVALID_SIGNATURE: case SGX_QL_QV_RESULT_REVOKED: case SGX_QL_QV_RESULT_UNSPECIFIED: default: THROW_ERROR("\tError: App: Verification completed with Terminal result: %x\n", quote_verification_result); } } static int do_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE(int sgx_fd) { int is_dcap_driver_installed = 0; if (ioctl(sgx_fd, SGXIOC_DETECT_DCAP_DRIVER, &is_dcap_driver_installed) < 0) { THROW_ERROR("failed to detect DCAP driver"); } if (is_dcap_driver_installed == 0) { printf("Warning: test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE is skipped\n"); return 0; } int nretries = 0; while (nretries < IOCTL_MAX_RETRIES) { int ret = generate_and_verify_dcap_quote(sgx_fd); if (ret == 0) { break; } else if (errno != EBUSY) { THROW_ERROR("failed to ioctl /dev/sgx"); } printf("WARN: /dev/sgx is temporarily busy. Try again after 1 second."); sleep(1); nretries++; } if (nretries == IOCTL_MAX_RETRIES) { THROW_ERROR("failed to ioctl /dev/sgx due to timeout"); } return 0; } int test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE(void) { if (is_sgx2_supported()) { return do_sgx_ioctl_test(do_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE); } else { printf("Warning: test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE is skipped\n"); return 0; } } #endif int test_sgx_ioctl_SGXIOC_IS_EDMM_SUPPORTED(void) { return do_sgx_ioctl_test(do_SGXIOC_IS_EDMM_SUPPORTED); } int test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID(void) { // skip the EPID test on SGX2 HW if (is_sgx2_supported()) { printf("Warning: test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID is skipped\n"); return 0; } return do_sgx_ioctl_test(do_SGXIOC_GET_EPID_GROUP_ID); } int test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE(void) { // skip the EPID test on SGX2 HW if (is_sgx2_supported()) { printf("Warning: test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE is skipped\n"); return 0; } return do_sgx_ioctl_test(do_SGXIOC_GEN_QUOTE); } int test_sgx_ioctl_SGXIOC_SELF_TARGET(void) { return do_sgx_ioctl_test(do_SGXIOC_SELF_TARGET); } int test_sgx_ioctl_SGXIOC_CREATE_AND_VERIFY_REPORT(void) { return do_sgx_ioctl_test(do_SGXIOC_CREATE_AND_VERIFY_REPORT); } int test_sgx_ioctl_SGXIOC_GET_KEY(void) { return do_sgx_ioctl_test(do_SGXIOC_GET_KEY); } #define CONFIG_SIZE 512 int test_ioctl_SIOCGIFCONF(void) { struct ifreq *req; struct ifconf conf; char *buf = (char *)malloc(CONFIG_SIZE); if (buf == NULL) { THROW_ERROR("malloc failed"); } memset(buf, 0, CONFIG_SIZE); int sock = socket(AF_INET, SOCK_STREAM, 0); conf.ifc_len = 0; conf.ifc_buf = buf; if (ioctl(sock, SIOCGIFCONF, &conf) < 0) { close(sock); THROW_ERROR("empty length ioctl failed"); } if (conf.ifc_len != 0) { close(sock); THROW_ERROR("wrong returned length"); } conf.ifc_len = CONFIG_SIZE; conf.ifc_buf = 0; if (ioctl(sock, SIOCGIFCONF, &conf) < 0) { close(sock); THROW_ERROR("empty buffer ioctl failed"); } int ret_len = conf.ifc_len; // use a larger buffer when the original one is insufficient if (ret_len > CONFIG_SIZE) { free(buf); char *new_buf = (char *)malloc(ret_len); if (new_buf == NULL) { close(sock); THROW_ERROR("malloc failed"); } buf = new_buf; memset(buf, 0, ret_len); } else { conf.ifc_len = CONFIG_SIZE; } conf.ifc_buf = buf; if (ioctl(sock, SIOCGIFCONF, &conf) < 0) { close(sock); THROW_ERROR("buffer passed ioctl failed"); } if (conf.ifc_len != ret_len) { close(sock); THROW_ERROR("wrong return length"); } close(sock); req = (struct ifreq *)buf; int num = conf.ifc_len / sizeof (struct ifreq); printf(" interface names got:\n"); for (int i = 0; i < num; i++) { printf(" %d: %s\n", i + 1, req->ifr_name); req ++; } return 0; } int test_ioctl_FIONBIO(void) { int test_sock[2], sock; test_sock[0] = socket(AF_INET, SOCK_STREAM, 0); test_sock[1] = socket(AF_UNIX, SOCK_STREAM, 0); for (int i = 0; i < 2; i++) { sock = test_sock[i]; int on = 1; if (ioctl(sock, FIONBIO, &on) < 0) { close(sock); THROW_ERROR("ioctl FIONBIO failed"); } int actual_flags = fcntl(sock, F_GETFL); if ((actual_flags & O_NONBLOCK) == 0) { close(sock); THROW_ERROR("failed to check the O_NONBLOCK flag after FIONBIO"); } close(sock); } return 0; } int test_ioctl_FIOCLEX(void) { // Open a file with O_CLOEXEC (close-on-exec) char *tmp_file = "/tmp/test_fioclex"; int fd = open(tmp_file, O_CREAT | O_CLOEXEC, 0666); if (fd < 0) { THROW_ERROR("failed to open the tmp file"); } // change this fd to "no close-on-exec" int ret = ioctl(fd, FIONCLEX, NULL); if (ret != 0) { THROW_ERROR("ioctl FIONCLEX failed"); } int pipefds[2]; ret = pipe(pipefds); if (ret != 0) { THROW_ERROR("failed to create pipe"); } // set close on exec on reader end ret = ioctl(pipefds[0], FIOCLEX, NULL); if (ret != 0) { THROW_ERROR("ioctl FIOCLEX failed"); } // construct child process args int child_pid, status; int child_argc = 6; // ./nauty_child -t fioclex regular_file_fd pipe_reader_fd pipe_writer_fd char **child_argv = calloc(1, sizeof(char *) * (child_argc + 1)); child_argv[0] = strdup("naughty_child"); child_argv[1] = strdup("-t"); child_argv[2] = strdup("fioclex"); if (asprintf(&child_argv[3], "%d", fd) < 0 || asprintf(&child_argv[4], "%d", pipefds[0]) < 0 || asprintf(&child_argv[5], "%d", pipefds[1]) < 0) { THROW_ERROR("failed to call asprintf"); } ret = posix_spawn(&child_pid, "/bin/naughty_child", NULL, NULL, child_argv, NULL); if (ret != 0) { THROW_ERROR("failed to spawn a child process\n"); } ret = waitpid(child_pid, &status, 0); if (ret < 0) { THROW_ERROR("failed to wait4 the child process"); } printf("child process %d exit status = %d\n", child_pid, status); return 0; } // ============================================================================ // Test suite // ============================================================================ static test_case_t test_cases[] = { TEST_CASE(test_tty_ioctl_TIOCGWINSZ), TEST_CASE(test_ioctl_TCGETS_TCSETS), TEST_CASE(test_sgx_ioctl_SGXIOC_IS_EDMM_SUPPORTED), TEST_CASE(test_sgx_ioctl_SGXIOC_GET_EPID_GROUP_ID), TEST_CASE(test_sgx_ioctl_SGXIOC_GEN_EPID_QUOTE), TEST_CASE(test_sgx_ioctl_SGXIOC_SELF_TARGET), TEST_CASE(test_sgx_ioctl_SGXIOC_CREATE_AND_VERIFY_REPORT), TEST_CASE(test_sgx_ioctl_SGXIOC_GET_KEY), #ifndef OCCLUM_DISABLE_DCAP TEST_CASE(test_sgx_ioctl_SGXIOC_GENERATE_AND_VERIFY_DCAP_QUOTE), #endif TEST_CASE(test_ioctl_SIOCGIFCONF), TEST_CASE(test_ioctl_FIONBIO), TEST_CASE(test_ioctl_FIOCLEX), }; int main() { return test_suite_run(test_cases, ARRAY_SIZE(test_cases)); }