| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright(c) 2016-20 Intel Corporation. */ |
| |
| #include <cpuid.h> |
| #include <elf.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/auxv.h> |
| #include "defines.h" |
| #include "../kselftest_harness.h" |
| #include "main.h" |
| |
| static const uint64_t MAGIC = 0x1122334455667788ULL; |
| static const uint64_t MAGIC2 = 0x8877665544332211ULL; |
| vdso_sgx_enter_enclave_t vdso_sgx_enter_enclave; |
| |
| /* |
| * Security Information (SECINFO) data structure needed by a few SGX |
| * instructions (eg. ENCLU[EACCEPT] and ENCLU[EMODPE]) holds meta-data |
| * about an enclave page. &enum sgx_secinfo_page_state specifies the |
| * secinfo flags used for page state. |
| */ |
| enum sgx_secinfo_page_state { |
| SGX_SECINFO_PENDING = (1 << 3), |
| SGX_SECINFO_MODIFIED = (1 << 4), |
| SGX_SECINFO_PR = (1 << 5), |
| }; |
| |
| struct vdso_symtab { |
| Elf64_Sym *elf_symtab; |
| const char *elf_symstrtab; |
| Elf64_Word *elf_hashtab; |
| }; |
| |
| static Elf64_Dyn *vdso_get_dyntab(void *addr) |
| { |
| Elf64_Ehdr *ehdr = addr; |
| Elf64_Phdr *phdrtab = addr + ehdr->e_phoff; |
| int i; |
| |
| for (i = 0; i < ehdr->e_phnum; i++) |
| if (phdrtab[i].p_type == PT_DYNAMIC) |
| return addr + phdrtab[i].p_offset; |
| |
| return NULL; |
| } |
| |
| static void *vdso_get_dyn(void *addr, Elf64_Dyn *dyntab, Elf64_Sxword tag) |
| { |
| int i; |
| |
| for (i = 0; dyntab[i].d_tag != DT_NULL; i++) |
| if (dyntab[i].d_tag == tag) |
| return addr + dyntab[i].d_un.d_ptr; |
| |
| return NULL; |
| } |
| |
| static bool vdso_get_symtab(void *addr, struct vdso_symtab *symtab) |
| { |
| Elf64_Dyn *dyntab = vdso_get_dyntab(addr); |
| |
| symtab->elf_symtab = vdso_get_dyn(addr, dyntab, DT_SYMTAB); |
| if (!symtab->elf_symtab) |
| return false; |
| |
| symtab->elf_symstrtab = vdso_get_dyn(addr, dyntab, DT_STRTAB); |
| if (!symtab->elf_symstrtab) |
| return false; |
| |
| symtab->elf_hashtab = vdso_get_dyn(addr, dyntab, DT_HASH); |
| if (!symtab->elf_hashtab) |
| return false; |
| |
| return true; |
| } |
| |
| static inline int sgx2_supported(void) |
| { |
| unsigned int eax, ebx, ecx, edx; |
| |
| __cpuid_count(SGX_CPUID, 0x0, eax, ebx, ecx, edx); |
| |
| return eax & 0x2; |
| } |
| |
| static unsigned long elf_sym_hash(const char *name) |
| { |
| unsigned long h = 0, high; |
| |
| while (*name) { |
| h = (h << 4) + *name++; |
| high = h & 0xf0000000; |
| |
| if (high) |
| h ^= high >> 24; |
| |
| h &= ~high; |
| } |
| |
| return h; |
| } |
| |
| static Elf64_Sym *vdso_symtab_get(struct vdso_symtab *symtab, const char *name) |
| { |
| Elf64_Word bucketnum = symtab->elf_hashtab[0]; |
| Elf64_Word *buckettab = &symtab->elf_hashtab[2]; |
| Elf64_Word *chaintab = &symtab->elf_hashtab[2 + bucketnum]; |
| Elf64_Sym *sym; |
| Elf64_Word i; |
| |
| for (i = buckettab[elf_sym_hash(name) % bucketnum]; i != STN_UNDEF; |
| i = chaintab[i]) { |
| sym = &symtab->elf_symtab[i]; |
| if (!strcmp(name, &symtab->elf_symstrtab[sym->st_name])) |
| return sym; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Return the offset in the enclave where the TCS segment can be found. |
| * The first RW segment loaded is the TCS. |
| */ |
| static off_t encl_get_tcs_offset(struct encl *encl) |
| { |
| int i; |
| |
| for (i = 0; i < encl->nr_segments; i++) { |
| struct encl_segment *seg = &encl->segment_tbl[i]; |
| |
| if (i == 0 && seg->prot == (PROT_READ | PROT_WRITE)) |
| return seg->offset; |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * Return the offset in the enclave where the data segment can be found. |
| * The first RW segment loaded is the TCS, skip that to get info on the |
| * data segment. |
| */ |
| static off_t encl_get_data_offset(struct encl *encl) |
| { |
| int i; |
| |
| for (i = 1; i < encl->nr_segments; i++) { |
| struct encl_segment *seg = &encl->segment_tbl[i]; |
| |
| if (seg->prot == (PROT_READ | PROT_WRITE)) |
| return seg->offset; |
| } |
| |
| return -1; |
| } |
| |
| FIXTURE(enclave) { |
| struct encl encl; |
| struct sgx_enclave_run run; |
| }; |
| |
| static bool setup_test_encl(unsigned long heap_size, struct encl *encl, |
| struct __test_metadata *_metadata) |
| { |
| Elf64_Sym *sgx_enter_enclave_sym = NULL; |
| struct vdso_symtab symtab; |
| struct encl_segment *seg; |
| char maps_line[256]; |
| FILE *maps_file; |
| unsigned int i; |
| void *addr; |
| |
| if (!encl_load("test_encl.elf", encl, heap_size)) { |
| encl_delete(encl); |
| TH_LOG("Failed to load the test enclave."); |
| return false; |
| } |
| |
| if (!encl_measure(encl)) |
| goto err; |
| |
| if (!encl_build(encl)) |
| goto err; |
| |
| /* |
| * An enclave consumer only must do this. |
| */ |
| for (i = 0; i < encl->nr_segments; i++) { |
| struct encl_segment *seg = &encl->segment_tbl[i]; |
| |
| addr = mmap((void *)encl->encl_base + seg->offset, seg->size, |
| seg->prot, MAP_SHARED | MAP_FIXED, encl->fd, 0); |
| EXPECT_NE(addr, MAP_FAILED); |
| if (addr == MAP_FAILED) |
| goto err; |
| } |
| |
| /* Get vDSO base address */ |
| addr = (void *)getauxval(AT_SYSINFO_EHDR); |
| if (!addr) |
| goto err; |
| |
| if (!vdso_get_symtab(addr, &symtab)) |
| goto err; |
| |
| sgx_enter_enclave_sym = vdso_symtab_get(&symtab, "__vdso_sgx_enter_enclave"); |
| if (!sgx_enter_enclave_sym) |
| goto err; |
| |
| vdso_sgx_enter_enclave = addr + sgx_enter_enclave_sym->st_value; |
| |
| return true; |
| |
| err: |
| for (i = 0; i < encl->nr_segments; i++) { |
| seg = &encl->segment_tbl[i]; |
| |
| TH_LOG("0x%016lx 0x%016lx 0x%02x", seg->offset, seg->size, seg->prot); |
| } |
| |
| maps_file = fopen("/proc/self/maps", "r"); |
| if (maps_file != NULL) { |
| while (fgets(maps_line, sizeof(maps_line), maps_file) != NULL) { |
| maps_line[strlen(maps_line) - 1] = '\0'; |
| |
| if (strstr(maps_line, "/dev/sgx_enclave")) |
| TH_LOG("%s", maps_line); |
| } |
| |
| fclose(maps_file); |
| } |
| |
| TH_LOG("Failed to initialize the test enclave."); |
| |
| encl_delete(encl); |
| |
| return false; |
| } |
| |
| FIXTURE_SETUP(enclave) |
| { |
| } |
| |
| FIXTURE_TEARDOWN(enclave) |
| { |
| encl_delete(&self->encl); |
| } |
| |
| #define ENCL_CALL(op, run, clobbered) \ |
| ({ \ |
| int ret; \ |
| if ((clobbered)) \ |
| ret = vdso_sgx_enter_enclave((unsigned long)(op), 0, 0, \ |
| EENTER, 0, 0, (run)); \ |
| else \ |
| ret = sgx_enter_enclave((void *)(op), NULL, 0, EENTER, NULL, NULL, \ |
| (run)); \ |
| ret; \ |
| }) |
| |
| #define EXPECT_EEXIT(run) \ |
| do { \ |
| EXPECT_EQ((run)->function, EEXIT); \ |
| if ((run)->function != EEXIT) \ |
| TH_LOG("0x%02x 0x%02x 0x%016llx", (run)->exception_vector, \ |
| (run)->exception_error_code, (run)->exception_addr); \ |
| } while (0) |
| |
| TEST_F(enclave, unclobbered_vdso) |
| { |
| struct encl_op_get_from_buf get_op; |
| struct encl_op_put_to_buf put_op; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| put_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_op, &self->run, false), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| get_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_op, &self->run, false), 0); |
| |
| EXPECT_EQ(get_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| } |
| |
| /* |
| * A section metric is concatenated in a way that @low bits 12-31 define the |
| * bits 12-31 of the metric and @high bits 0-19 define the bits 32-51 of the |
| * metric. |
| */ |
| static unsigned long sgx_calc_section_metric(unsigned int low, |
| unsigned int high) |
| { |
| return (low & GENMASK_ULL(31, 12)) + |
| ((high & GENMASK_ULL(19, 0)) << 32); |
| } |
| |
| /* |
| * Sum total available physical SGX memory across all EPC sections |
| * |
| * Return: total available physical SGX memory available on system |
| */ |
| static unsigned long get_total_epc_mem(void) |
| { |
| unsigned int eax, ebx, ecx, edx; |
| unsigned long total_size = 0; |
| unsigned int type; |
| int section = 0; |
| |
| while (true) { |
| __cpuid_count(SGX_CPUID, section + SGX_CPUID_EPC, eax, ebx, ecx, edx); |
| |
| type = eax & SGX_CPUID_EPC_MASK; |
| if (type == SGX_CPUID_EPC_INVALID) |
| break; |
| |
| if (type != SGX_CPUID_EPC_SECTION) |
| break; |
| |
| total_size += sgx_calc_section_metric(ecx, edx); |
| |
| section++; |
| } |
| |
| return total_size; |
| } |
| |
| TEST_F(enclave, unclobbered_vdso_oversubscribed) |
| { |
| struct encl_op_get_from_buf get_op; |
| struct encl_op_put_to_buf put_op; |
| unsigned long total_mem; |
| |
| total_mem = get_total_epc_mem(); |
| ASSERT_NE(total_mem, 0); |
| ASSERT_TRUE(setup_test_encl(total_mem, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| put_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_op, &self->run, false), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| get_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_op, &self->run, false), 0); |
| |
| EXPECT_EQ(get_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| } |
| |
| TEST_F_TIMEOUT(enclave, unclobbered_vdso_oversubscribed_remove, 900) |
| { |
| struct sgx_enclave_remove_pages remove_ioc; |
| struct sgx_enclave_modify_types modt_ioc; |
| struct encl_op_get_from_buf get_op; |
| struct encl_op_eaccept eaccept_op; |
| struct encl_op_put_to_buf put_op; |
| struct encl_segment *heap; |
| unsigned long total_mem; |
| int ret, errno_save; |
| unsigned long addr; |
| unsigned long i; |
| |
| /* |
| * Create enclave with additional heap that is as big as all |
| * available physical SGX memory. |
| */ |
| total_mem = get_total_epc_mem(); |
| ASSERT_NE(total_mem, 0); |
| TH_LOG("Creating an enclave with %lu bytes heap may take a while ...", |
| total_mem); |
| ASSERT_TRUE(setup_test_encl(total_mem, &self->encl, _metadata)); |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* SGX2 is supported by kernel and hardware, test can proceed. */ |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| heap = &self->encl.segment_tbl[self->encl.nr_segments - 1]; |
| |
| put_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_op, &self->run, false), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| get_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_op, &self->run, false), 0); |
| |
| EXPECT_EQ(get_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| /* Trim entire heap. */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| |
| modt_ioc.offset = heap->offset; |
| modt_ioc.length = heap->size; |
| modt_ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| |
| TH_LOG("Changing type of %zd bytes to trimmed may take a while ...", |
| heap->size); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(modt_ioc.result, 0); |
| EXPECT_EQ(modt_ioc.count, heap->size); |
| |
| /* EACCEPT all removed pages. */ |
| addr = self->encl.encl_base + heap->offset; |
| |
| eaccept_op.flags = SGX_SECINFO_TRIM | SGX_SECINFO_MODIFIED; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| TH_LOG("Entering enclave to run EACCEPT for each page of %zd bytes may take a while ...", |
| heap->size); |
| for (i = 0; i < heap->size; i += 4096) { |
| eaccept_op.epc_addr = addr + i; |
| eaccept_op.ret = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| ASSERT_EQ(eaccept_op.ret, 0); |
| ASSERT_EQ(self->run.function, EEXIT); |
| } |
| |
| /* Complete page removal. */ |
| memset(&remove_ioc, 0, sizeof(remove_ioc)); |
| |
| remove_ioc.offset = heap->offset; |
| remove_ioc.length = heap->size; |
| |
| TH_LOG("Removing %zd bytes from enclave may take a while ...", |
| heap->size); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_REMOVE_PAGES, &remove_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(remove_ioc.count, heap->size); |
| } |
| |
| TEST_F(enclave, clobbered_vdso) |
| { |
| struct encl_op_get_from_buf get_op; |
| struct encl_op_put_to_buf put_op; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| put_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| get_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| } |
| |
| static int test_handler(long rdi, long rsi, long rdx, long ursp, long r8, long r9, |
| struct sgx_enclave_run *run) |
| { |
| run->user_data = 0; |
| |
| return 0; |
| } |
| |
| TEST_F(enclave, clobbered_vdso_and_user_function) |
| { |
| struct encl_op_get_from_buf get_op; |
| struct encl_op_put_to_buf put_op; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| self->run.user_handler = (__u64)test_handler; |
| self->run.user_data = 0xdeadbeef; |
| |
| put_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| |
| get_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| } |
| |
| /* |
| * Sanity check that it is possible to enter either of the two hardcoded TCS |
| */ |
| TEST_F(enclave, tcs_entry) |
| { |
| struct encl_op_header op; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| op.type = ENCL_OP_NOP; |
| |
| EXPECT_EQ(ENCL_CALL(&op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Move to the next TCS. */ |
| self->run.tcs = self->encl.encl_base + PAGE_SIZE; |
| |
| EXPECT_EQ(ENCL_CALL(&op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| } |
| |
| /* |
| * Second page of .data segment is used to test changing PTE permissions. |
| * This spans the local encl_buffer within the test enclave. |
| * |
| * 1) Start with a sanity check: a value is written to the target page within |
| * the enclave and read back to ensure target page can be written to. |
| * 2) Change PTE permissions (RW -> RO) of target page within enclave. |
| * 3) Repeat (1) - this time expecting a regular #PF communicated via the |
| * vDSO. |
| * 4) Change PTE permissions of target page within enclave back to be RW. |
| * 5) Repeat (1) by resuming enclave, now expected to be possible to write to |
| * and read from target page within enclave. |
| */ |
| TEST_F(enclave, pte_permissions) |
| { |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| unsigned long data_start; |
| int ret; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + |
| PAGE_SIZE; |
| |
| /* |
| * Sanity check to ensure it is possible to write to page that will |
| * have its permissions manipulated. |
| */ |
| |
| /* Write MAGIC to page */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = data_start; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory that was just written to, confirming that it is the |
| * value previously written (MAGIC). |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = data_start; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Change PTE permissions of target page within the enclave */ |
| ret = mprotect((void *)data_start, PAGE_SIZE, PROT_READ); |
| if (ret) |
| perror("mprotect"); |
| |
| /* |
| * PTE permissions of target page changed to read-only, EPCM |
| * permissions unchanged (EPCM permissions are RW), attempt to |
| * write to the page, expecting a regular #PF. |
| */ |
| |
| put_addr_op.value = MAGIC2; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(self->run.exception_vector, 14); |
| EXPECT_EQ(self->run.exception_error_code, 0x7); |
| EXPECT_EQ(self->run.exception_addr, data_start); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| /* |
| * Change PTE permissions back to enable enclave to write to the |
| * target page and resume enclave - do not expect any exceptions this |
| * time. |
| */ |
| ret = mprotect((void *)data_start, PAGE_SIZE, PROT_READ | PROT_WRITE); |
| if (ret) |
| perror("mprotect"); |
| |
| EXPECT_EQ(vdso_sgx_enter_enclave((unsigned long)&put_addr_op, 0, |
| 0, ERESUME, 0, 0, &self->run), |
| 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| get_addr_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC2); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| } |
| |
| /* |
| * Modifying permissions of TCS page should not be possible. |
| */ |
| TEST_F(enclave, tcs_permissions) |
| { |
| struct sgx_enclave_restrict_permissions ioc; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| memset(&ioc, 0, sizeof(ioc)); |
| |
| /* |
| * Ensure kernel supports needed ioctl() and system supports needed |
| * commands. |
| */ |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS, &ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| ASSERT_EQ(ret, -1); |
| |
| /* ret == -1 */ |
| if (errno_save == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS ioctl()"); |
| else if (errno_save == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| |
| /* |
| * Attempt to make TCS page read-only. This is not allowed and |
| * should be prevented by the kernel. |
| */ |
| ioc.offset = encl_get_tcs_offset(&self->encl); |
| ioc.length = PAGE_SIZE; |
| ioc.permissions = SGX_SECINFO_R; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS, &ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, -1); |
| EXPECT_EQ(errno_save, EINVAL); |
| EXPECT_EQ(ioc.result, 0); |
| EXPECT_EQ(ioc.count, 0); |
| } |
| |
| /* |
| * Enclave page permission test. |
| * |
| * Modify and restore enclave page's EPCM (enclave) permissions from |
| * outside enclave (ENCLS[EMODPR] via kernel) as well as from within |
| * enclave (via ENCLU[EMODPE]). Check for page fault if |
| * VMA allows access but EPCM permissions do not. |
| */ |
| TEST_F(enclave, epcm_permissions) |
| { |
| struct sgx_enclave_restrict_permissions restrict_ioc; |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| struct encl_op_eaccept eaccept_op; |
| struct encl_op_emodpe emodpe_op; |
| unsigned long data_start; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Ensure kernel supports needed ioctl() and system supports needed |
| * commands. |
| */ |
| memset(&restrict_ioc, 0, sizeof(restrict_ioc)); |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS, |
| &restrict_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| ASSERT_EQ(ret, -1); |
| |
| /* ret == -1 */ |
| if (errno_save == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS ioctl()"); |
| else if (errno_save == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| |
| /* |
| * Page that will have its permissions changed is the second data |
| * page in the .data segment. This forms part of the local encl_buffer |
| * within the enclave. |
| * |
| * At start of test @data_start should have EPCM as well as PTE and |
| * VMA permissions of RW. |
| */ |
| |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| |
| /* |
| * Sanity check that page at @data_start is writable before making |
| * any changes to page permissions. |
| * |
| * Start by writing MAGIC to test page. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = data_start; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory that was just written to, confirming that |
| * page is writable. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = data_start; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Change EPCM permissions to read-only. Kernel still considers |
| * the page writable. |
| */ |
| memset(&restrict_ioc, 0, sizeof(restrict_ioc)); |
| |
| restrict_ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| restrict_ioc.length = PAGE_SIZE; |
| restrict_ioc.permissions = SGX_SECINFO_R; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS, |
| &restrict_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(restrict_ioc.result, 0); |
| EXPECT_EQ(restrict_ioc.count, 4096); |
| |
| /* |
| * EPCM permissions changed from kernel, need to EACCEPT from enclave. |
| */ |
| eaccept_op.epc_addr = data_start; |
| eaccept_op.flags = SGX_SECINFO_R | SGX_SECINFO_REG | SGX_SECINFO_PR; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* |
| * EPCM permissions of page is now read-only, expect #PF |
| * on EPCM when attempting to write to page from within enclave. |
| */ |
| put_addr_op.value = MAGIC2; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(self->run.function, ERESUME); |
| EXPECT_EQ(self->run.exception_vector, 14); |
| EXPECT_EQ(self->run.exception_error_code, 0x8007); |
| EXPECT_EQ(self->run.exception_addr, data_start); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| /* |
| * Received AEX but cannot return to enclave at same entrypoint, |
| * need different TCS from where EPCM permission can be made writable |
| * again. |
| */ |
| self->run.tcs = self->encl.encl_base + PAGE_SIZE; |
| |
| /* |
| * Enter enclave at new TCS to change EPCM permissions to be |
| * writable again and thus fix the page fault that triggered the |
| * AEX. |
| */ |
| |
| emodpe_op.epc_addr = data_start; |
| emodpe_op.flags = SGX_SECINFO_R | SGX_SECINFO_W; |
| emodpe_op.header.type = ENCL_OP_EMODPE; |
| |
| EXPECT_EQ(ENCL_CALL(&emodpe_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Attempt to return to main TCS to resume execution at faulting |
| * instruction, PTE should continue to allow writing to the page. |
| */ |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Wrong page permissions that caused original fault has |
| * now been fixed via EPCM permissions. |
| * Resume execution in main TCS to re-attempt the memory access. |
| */ |
| self->run.tcs = self->encl.encl_base; |
| |
| EXPECT_EQ(vdso_sgx_enter_enclave((unsigned long)&put_addr_op, 0, 0, |
| ERESUME, 0, 0, |
| &self->run), |
| 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| get_addr_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC2); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.user_data, 0); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| } |
| |
| /* |
| * Test the addition of pages to an initialized enclave via writing to |
| * a page belonging to the enclave's address space but was not added |
| * during enclave creation. |
| */ |
| TEST_F(enclave, augment) |
| { |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| struct encl_op_eaccept eaccept_op; |
| size_t total_size = 0; |
| void *addr; |
| int i; |
| |
| if (!sgx2_supported()) |
| SKIP(return, "SGX2 not supported"); |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| for (i = 0; i < self->encl.nr_segments; i++) { |
| struct encl_segment *seg = &self->encl.segment_tbl[i]; |
| |
| total_size += seg->size; |
| } |
| |
| /* |
| * Actual enclave size is expected to be larger than the loaded |
| * test enclave since enclave size must be a power of 2 in bytes |
| * and test_encl does not consume it all. |
| */ |
| EXPECT_LT(total_size + PAGE_SIZE, self->encl.encl_size); |
| |
| /* |
| * Create memory mapping for the page that will be added. New |
| * memory mapping is for one page right after all existing |
| * mappings. |
| * Kernel will allow new mapping using any permissions if it |
| * falls into the enclave's address range but not backed |
| * by existing enclave pages. |
| */ |
| addr = mmap((void *)self->encl.encl_base + total_size, PAGE_SIZE, |
| PROT_READ | PROT_WRITE | PROT_EXEC, |
| MAP_SHARED | MAP_FIXED, self->encl.fd, 0); |
| EXPECT_NE(addr, MAP_FAILED); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| /* |
| * Attempt to write to the new page from within enclave. |
| * Expected to fail since page is not (yet) part of the enclave. |
| * The first #PF will trigger the addition of the page to the |
| * enclave, but since the new page needs an EACCEPT from within the |
| * enclave before it can be used it would not be possible |
| * to successfully return to the failing instruction. This is the |
| * cause of the second #PF captured here having the SGX bit set, |
| * it is from hardware preventing the page from being used. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = (unsigned long)addr; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(self->run.function, ERESUME); |
| EXPECT_EQ(self->run.exception_vector, 14); |
| EXPECT_EQ(self->run.exception_addr, (unsigned long)addr); |
| |
| if (self->run.exception_error_code == 0x6) { |
| munmap(addr, PAGE_SIZE); |
| SKIP(return, "Kernel does not support adding pages to initialized enclave"); |
| } |
| |
| EXPECT_EQ(self->run.exception_error_code, 0x8007); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| /* Handle AEX by running EACCEPT from new entry point. */ |
| self->run.tcs = self->encl.encl_base + PAGE_SIZE; |
| |
| eaccept_op.epc_addr = self->encl.encl_base + total_size; |
| eaccept_op.flags = SGX_SECINFO_R | SGX_SECINFO_W | SGX_SECINFO_REG | SGX_SECINFO_PENDING; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* Can now return to main TCS to resume execution. */ |
| self->run.tcs = self->encl.encl_base; |
| |
| EXPECT_EQ(vdso_sgx_enter_enclave((unsigned long)&put_addr_op, 0, 0, |
| ERESUME, 0, 0, |
| &self->run), |
| 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory from newly added page that was just written to, |
| * confirming that data previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = (unsigned long)addr; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| munmap(addr, PAGE_SIZE); |
| } |
| |
| /* |
| * Test for the addition of pages to an initialized enclave via a |
| * pre-emptive run of EACCEPT on page to be added. |
| */ |
| TEST_F(enclave, augment_via_eaccept) |
| { |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| struct encl_op_eaccept eaccept_op; |
| size_t total_size = 0; |
| void *addr; |
| int i; |
| |
| if (!sgx2_supported()) |
| SKIP(return, "SGX2 not supported"); |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| for (i = 0; i < self->encl.nr_segments; i++) { |
| struct encl_segment *seg = &self->encl.segment_tbl[i]; |
| |
| total_size += seg->size; |
| } |
| |
| /* |
| * Actual enclave size is expected to be larger than the loaded |
| * test enclave since enclave size must be a power of 2 in bytes while |
| * test_encl does not consume it all. |
| */ |
| EXPECT_LT(total_size + PAGE_SIZE, self->encl.encl_size); |
| |
| /* |
| * mmap() a page at end of existing enclave to be used for dynamic |
| * EPC page. |
| * |
| * Kernel will allow new mapping using any permissions if it |
| * falls into the enclave's address range but not backed |
| * by existing enclave pages. |
| */ |
| |
| addr = mmap((void *)self->encl.encl_base + total_size, PAGE_SIZE, |
| PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED, |
| self->encl.fd, 0); |
| EXPECT_NE(addr, MAP_FAILED); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| /* |
| * Run EACCEPT on new page to trigger the #PF->EAUG->EACCEPT(again |
| * without a #PF). All should be transparent to userspace. |
| */ |
| eaccept_op.epc_addr = self->encl.encl_base + total_size; |
| eaccept_op.flags = SGX_SECINFO_R | SGX_SECINFO_W | SGX_SECINFO_REG | SGX_SECINFO_PENDING; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| if (self->run.exception_vector == 14 && |
| self->run.exception_error_code == 4 && |
| self->run.exception_addr == self->encl.encl_base + total_size) { |
| munmap(addr, PAGE_SIZE); |
| SKIP(return, "Kernel does not support adding pages to initialized enclave"); |
| } |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* |
| * New page should be accessible from within enclave - attempt to |
| * write to it. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = (unsigned long)addr; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory from newly added page that was just written to, |
| * confirming that data previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = (unsigned long)addr; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| munmap(addr, PAGE_SIZE); |
| } |
| |
| /* |
| * SGX2 page type modification test in two phases: |
| * Phase 1: |
| * Create a new TCS, consisting out of three new pages (stack page with regular |
| * page type, SSA page with regular page type, and TCS page with TCS page |
| * type) in an initialized enclave and run a simple workload within it. |
| * Phase 2: |
| * Remove the three pages added in phase 1, add a new regular page at the |
| * same address that previously hosted the TCS page and verify that it can |
| * be modified. |
| */ |
| TEST_F(enclave, tcs_create) |
| { |
| struct encl_op_init_tcs_page init_tcs_page_op; |
| struct sgx_enclave_remove_pages remove_ioc; |
| struct encl_op_get_from_addr get_addr_op; |
| struct sgx_enclave_modify_types modt_ioc; |
| struct encl_op_put_to_addr put_addr_op; |
| struct encl_op_get_from_buf get_buf_op; |
| struct encl_op_put_to_buf put_buf_op; |
| void *addr, *tcs, *stack_end, *ssa; |
| struct encl_op_eaccept eaccept_op; |
| size_t total_size = 0; |
| uint64_t val_64; |
| int errno_save; |
| int ret, i; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, |
| _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* |
| * Add three regular pages via EAUG: one will be the TCS stack, one |
| * will be the TCS SSA, and one will be the new TCS. The stack and |
| * SSA will remain as regular pages, the TCS page will need its |
| * type changed after populated with needed data. |
| */ |
| for (i = 0; i < self->encl.nr_segments; i++) { |
| struct encl_segment *seg = &self->encl.segment_tbl[i]; |
| |
| total_size += seg->size; |
| } |
| |
| /* |
| * Actual enclave size is expected to be larger than the loaded |
| * test enclave since enclave size must be a power of 2 in bytes while |
| * test_encl does not consume it all. |
| */ |
| EXPECT_LT(total_size + 3 * PAGE_SIZE, self->encl.encl_size); |
| |
| /* |
| * mmap() three pages at end of existing enclave to be used for the |
| * three new pages. |
| */ |
| addr = mmap((void *)self->encl.encl_base + total_size, 3 * PAGE_SIZE, |
| PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, |
| self->encl.fd, 0); |
| EXPECT_NE(addr, MAP_FAILED); |
| |
| self->run.exception_vector = 0; |
| self->run.exception_error_code = 0; |
| self->run.exception_addr = 0; |
| |
| stack_end = (void *)self->encl.encl_base + total_size; |
| tcs = (void *)self->encl.encl_base + total_size + PAGE_SIZE; |
| ssa = (void *)self->encl.encl_base + total_size + 2 * PAGE_SIZE; |
| |
| /* |
| * Run EACCEPT on each new page to trigger the |
| * EACCEPT->(#PF)->EAUG->EACCEPT(again without a #PF) flow. |
| */ |
| |
| eaccept_op.epc_addr = (unsigned long)stack_end; |
| eaccept_op.flags = SGX_SECINFO_R | SGX_SECINFO_W | SGX_SECINFO_REG | SGX_SECINFO_PENDING; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| if (self->run.exception_vector == 14 && |
| self->run.exception_error_code == 4 && |
| self->run.exception_addr == (unsigned long)stack_end) { |
| munmap(addr, 3 * PAGE_SIZE); |
| SKIP(return, "Kernel does not support adding pages to initialized enclave"); |
| } |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| eaccept_op.epc_addr = (unsigned long)ssa; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| eaccept_op.epc_addr = (unsigned long)tcs; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* |
| * Three new pages added to enclave. Now populate the TCS page with |
| * needed data. This should be done from within enclave. Provide |
| * the function that will do the actual data population with needed |
| * data. |
| */ |
| |
| /* |
| * New TCS will use the "encl_dyn_entry" entrypoint that expects |
| * stack to begin in page before TCS page. |
| */ |
| val_64 = encl_get_entry(&self->encl, "encl_dyn_entry"); |
| EXPECT_NE(val_64, 0); |
| |
| init_tcs_page_op.tcs_page = (unsigned long)tcs; |
| init_tcs_page_op.ssa = (unsigned long)total_size + 2 * PAGE_SIZE; |
| init_tcs_page_op.entry = val_64; |
| init_tcs_page_op.header.type = ENCL_OP_INIT_TCS_PAGE; |
| |
| EXPECT_EQ(ENCL_CALL(&init_tcs_page_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Change TCS page type to TCS. */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| |
| modt_ioc.offset = total_size + PAGE_SIZE; |
| modt_ioc.length = PAGE_SIZE; |
| modt_ioc.page_type = SGX_PAGE_TYPE_TCS; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(modt_ioc.result, 0); |
| EXPECT_EQ(modt_ioc.count, 4096); |
| |
| /* EACCEPT new TCS page from enclave. */ |
| eaccept_op.epc_addr = (unsigned long)tcs; |
| eaccept_op.flags = SGX_SECINFO_TCS | SGX_SECINFO_MODIFIED; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* Run workload from new TCS. */ |
| self->run.tcs = (unsigned long)tcs; |
| |
| /* |
| * Simple workload to write to data buffer and read value back. |
| */ |
| put_buf_op.header.type = ENCL_OP_PUT_TO_BUFFER; |
| put_buf_op.value = MAGIC; |
| |
| EXPECT_EQ(ENCL_CALL(&put_buf_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| get_buf_op.header.type = ENCL_OP_GET_FROM_BUFFER; |
| get_buf_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_buf_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_buf_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Phase 2 of test: |
| * Remove pages associated with new TCS, create a regular page |
| * where TCS page used to be and verify it can be used as a regular |
| * page. |
| */ |
| |
| /* Start page removal by requesting change of page type to PT_TRIM. */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| |
| modt_ioc.offset = total_size; |
| modt_ioc.length = 3 * PAGE_SIZE; |
| modt_ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(modt_ioc.result, 0); |
| EXPECT_EQ(modt_ioc.count, 3 * PAGE_SIZE); |
| |
| /* |
| * Enter enclave via TCS #1 and approve page removal by sending |
| * EACCEPT for each of three removed pages. |
| */ |
| self->run.tcs = self->encl.encl_base; |
| |
| eaccept_op.epc_addr = (unsigned long)stack_end; |
| eaccept_op.flags = SGX_SECINFO_TRIM | SGX_SECINFO_MODIFIED; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| eaccept_op.epc_addr = (unsigned long)tcs; |
| eaccept_op.ret = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| eaccept_op.epc_addr = (unsigned long)ssa; |
| eaccept_op.ret = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* Send final ioctl() to complete page removal. */ |
| memset(&remove_ioc, 0, sizeof(remove_ioc)); |
| |
| remove_ioc.offset = total_size; |
| remove_ioc.length = 3 * PAGE_SIZE; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_REMOVE_PAGES, &remove_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(remove_ioc.count, 3 * PAGE_SIZE); |
| |
| /* |
| * Enter enclave via TCS #1 and access location where TCS #3 was to |
| * trigger dynamic add of regular page at that location. |
| */ |
| eaccept_op.epc_addr = (unsigned long)tcs; |
| eaccept_op.flags = SGX_SECINFO_R | SGX_SECINFO_W | SGX_SECINFO_REG | SGX_SECINFO_PENDING; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* |
| * New page should be accessible from within enclave - write to it. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = (unsigned long)tcs; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory from newly added page that was just written to, |
| * confirming that data previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = (unsigned long)tcs; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| munmap(addr, 3 * PAGE_SIZE); |
| } |
| |
| /* |
| * Ensure sane behavior if user requests page removal, does not run |
| * EACCEPT from within enclave but still attempts to finalize page removal |
| * with the SGX_IOC_ENCLAVE_REMOVE_PAGES ioctl(). The latter should fail |
| * because the removal was not EACCEPTed from within the enclave. |
| */ |
| TEST_F(enclave, remove_added_page_no_eaccept) |
| { |
| struct sgx_enclave_remove_pages remove_ioc; |
| struct encl_op_get_from_addr get_addr_op; |
| struct sgx_enclave_modify_types modt_ioc; |
| struct encl_op_put_to_addr put_addr_op; |
| unsigned long data_start; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* |
| * Page that will be removed is the second data page in the .data |
| * segment. This forms part of the local encl_buffer within the |
| * enclave. |
| */ |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| |
| /* |
| * Sanity check that page at @data_start is writable before |
| * removing it. |
| * |
| * Start by writing MAGIC to test page. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = data_start; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory that was just written to, confirming that data |
| * previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = data_start; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Start page removal by requesting change of page type to PT_TRIM */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| |
| modt_ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| modt_ioc.length = PAGE_SIZE; |
| modt_ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(modt_ioc.result, 0); |
| EXPECT_EQ(modt_ioc.count, 4096); |
| |
| /* Skip EACCEPT */ |
| |
| /* Send final ioctl() to complete page removal */ |
| memset(&remove_ioc, 0, sizeof(remove_ioc)); |
| |
| remove_ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| remove_ioc.length = PAGE_SIZE; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_REMOVE_PAGES, &remove_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| /* Operation not permitted since EACCEPT was omitted. */ |
| EXPECT_EQ(ret, -1); |
| EXPECT_EQ(errno_save, EPERM); |
| EXPECT_EQ(remove_ioc.count, 0); |
| } |
| |
| /* |
| * Request enclave page removal but instead of correctly following with |
| * EACCEPT a read attempt to page is made from within the enclave. |
| */ |
| TEST_F(enclave, remove_added_page_invalid_access) |
| { |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| struct sgx_enclave_modify_types ioc; |
| unsigned long data_start; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&ioc, 0, sizeof(ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* |
| * Page that will be removed is the second data page in the .data |
| * segment. This forms part of the local encl_buffer within the |
| * enclave. |
| */ |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| |
| /* |
| * Sanity check that page at @data_start is writable before |
| * removing it. |
| * |
| * Start by writing MAGIC to test page. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = data_start; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory that was just written to, confirming that data |
| * previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = data_start; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Start page removal by requesting change of page type to PT_TRIM. */ |
| memset(&ioc, 0, sizeof(ioc)); |
| |
| ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| ioc.length = PAGE_SIZE; |
| ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(ioc.result, 0); |
| EXPECT_EQ(ioc.count, 4096); |
| |
| /* |
| * Read from page that was just removed. |
| */ |
| get_addr_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| /* |
| * From kernel perspective the page is present but according to SGX the |
| * page should not be accessible so a #PF with SGX bit set is |
| * expected. |
| */ |
| |
| EXPECT_EQ(self->run.function, ERESUME); |
| EXPECT_EQ(self->run.exception_vector, 14); |
| EXPECT_EQ(self->run.exception_error_code, 0x8005); |
| EXPECT_EQ(self->run.exception_addr, data_start); |
| } |
| |
| /* |
| * Request enclave page removal and correctly follow with |
| * EACCEPT but do not follow with removal ioctl() but instead a read attempt |
| * to removed page is made from within the enclave. |
| */ |
| TEST_F(enclave, remove_added_page_invalid_access_after_eaccept) |
| { |
| struct encl_op_get_from_addr get_addr_op; |
| struct encl_op_put_to_addr put_addr_op; |
| struct sgx_enclave_modify_types ioc; |
| struct encl_op_eaccept eaccept_op; |
| unsigned long data_start; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&ioc, 0, sizeof(ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* |
| * Page that will be removed is the second data page in the .data |
| * segment. This forms part of the local encl_buffer within the |
| * enclave. |
| */ |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| |
| /* |
| * Sanity check that page at @data_start is writable before |
| * removing it. |
| * |
| * Start by writing MAGIC to test page. |
| */ |
| put_addr_op.value = MAGIC; |
| put_addr_op.addr = data_start; |
| put_addr_op.header.type = ENCL_OP_PUT_TO_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&put_addr_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* |
| * Read memory that was just written to, confirming that data |
| * previously written (MAGIC) is present. |
| */ |
| get_addr_op.value = 0; |
| get_addr_op.addr = data_start; |
| get_addr_op.header.type = ENCL_OP_GET_FROM_ADDRESS; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| EXPECT_EQ(get_addr_op.value, MAGIC); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| |
| /* Start page removal by requesting change of page type to PT_TRIM. */ |
| memset(&ioc, 0, sizeof(ioc)); |
| |
| ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| ioc.length = PAGE_SIZE; |
| ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(ioc.result, 0); |
| EXPECT_EQ(ioc.count, 4096); |
| |
| eaccept_op.epc_addr = (unsigned long)data_start; |
| eaccept_op.ret = 0; |
| eaccept_op.flags = SGX_SECINFO_TRIM | SGX_SECINFO_MODIFIED; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| /* Skip ioctl() to remove page. */ |
| |
| /* |
| * Read from page that was just removed. |
| */ |
| get_addr_op.value = 0; |
| |
| EXPECT_EQ(ENCL_CALL(&get_addr_op, &self->run, true), 0); |
| |
| /* |
| * From kernel perspective the page is present but according to SGX the |
| * page should not be accessible so a #PF with SGX bit set is |
| * expected. |
| */ |
| |
| EXPECT_EQ(self->run.function, ERESUME); |
| EXPECT_EQ(self->run.exception_vector, 14); |
| EXPECT_EQ(self->run.exception_error_code, 0x8005); |
| EXPECT_EQ(self->run.exception_addr, data_start); |
| } |
| |
| TEST_F(enclave, remove_untouched_page) |
| { |
| struct sgx_enclave_remove_pages remove_ioc; |
| struct sgx_enclave_modify_types modt_ioc; |
| struct encl_op_eaccept eaccept_op; |
| unsigned long data_start; |
| int ret, errno_save; |
| |
| ASSERT_TRUE(setup_test_encl(ENCL_HEAP_SIZE_DEFAULT, &self->encl, _metadata)); |
| |
| /* |
| * Hardware (SGX2) and kernel support is needed for this test. Start |
| * with check that test has a chance of succeeding. |
| */ |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| |
| if (ret == -1) { |
| if (errno == ENOTTY) |
| SKIP(return, |
| "Kernel does not support SGX_IOC_ENCLAVE_MODIFY_TYPES ioctl()"); |
| else if (errno == ENODEV) |
| SKIP(return, "System does not support SGX2"); |
| } |
| |
| /* |
| * Invalid parameters were provided during sanity check, |
| * expect command to fail. |
| */ |
| EXPECT_EQ(ret, -1); |
| |
| /* SGX2 is supported by kernel and hardware, test can proceed. */ |
| memset(&self->run, 0, sizeof(self->run)); |
| self->run.tcs = self->encl.encl_base; |
| |
| data_start = self->encl.encl_base + |
| encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| |
| memset(&modt_ioc, 0, sizeof(modt_ioc)); |
| |
| modt_ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| modt_ioc.length = PAGE_SIZE; |
| modt_ioc.page_type = SGX_PAGE_TYPE_TRIM; |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_MODIFY_TYPES, &modt_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(modt_ioc.result, 0); |
| EXPECT_EQ(modt_ioc.count, 4096); |
| |
| /* |
| * Enter enclave via TCS #1 and approve page removal by sending |
| * EACCEPT for removed page. |
| */ |
| |
| eaccept_op.epc_addr = data_start; |
| eaccept_op.flags = SGX_SECINFO_TRIM | SGX_SECINFO_MODIFIED; |
| eaccept_op.ret = 0; |
| eaccept_op.header.type = ENCL_OP_EACCEPT; |
| |
| EXPECT_EQ(ENCL_CALL(&eaccept_op, &self->run, true), 0); |
| EXPECT_EEXIT(&self->run); |
| EXPECT_EQ(self->run.exception_vector, 0); |
| EXPECT_EQ(self->run.exception_error_code, 0); |
| EXPECT_EQ(self->run.exception_addr, 0); |
| EXPECT_EQ(eaccept_op.ret, 0); |
| |
| memset(&remove_ioc, 0, sizeof(remove_ioc)); |
| |
| remove_ioc.offset = encl_get_data_offset(&self->encl) + PAGE_SIZE; |
| remove_ioc.length = PAGE_SIZE; |
| ret = ioctl(self->encl.fd, SGX_IOC_ENCLAVE_REMOVE_PAGES, &remove_ioc); |
| errno_save = ret == -1 ? errno : 0; |
| |
| EXPECT_EQ(ret, 0); |
| EXPECT_EQ(errno_save, 0); |
| EXPECT_EQ(remove_ioc.count, 4096); |
| } |
| |
| TEST_HARNESS_MAIN |