Add a test to verify the kernel and vDSO provide the correct exception info when using an exit handler, e.g. leaf, trapnr and error_code, and that the vDSO correctly interprets the return from the exit handler. To do so, change the enclave's protections to read-only and iteratively fix the faults encountered, with various assertions along the way, e.g. the first fault should always be a !writable fault on the TCS, at least three total faults should occur, etc... Suggested-by: Cedric Xing <cedric.xing@xxxxxxxxx> Signed-off-by: Sean Christopherson <sean.j.christopherson@xxxxxxxxx> --- tools/testing/selftests/x86/sgx/defines.h | 2 + tools/testing/selftests/x86/sgx/main.c | 87 ++++++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/x86/sgx/defines.h b/tools/testing/selftests/x86/sgx/defines.h index ab9671b8a993..199a830e198a 100644 --- a/tools/testing/selftests/x86/sgx/defines.h +++ b/tools/testing/selftests/x86/sgx/defines.h @@ -37,7 +37,9 @@ typedef uint64_t u64; #include "../../../../../arch/x86/include/uapi/asm/sgx.h" #define ENCLU_EENTER 2 +#define ENCLU_ERESUME 3 #define GP_VECTOR 13 +#define PF_VECTOR 14 #endif /* TYPES_H */ diff --git a/tools/testing/selftests/x86/sgx/main.c b/tools/testing/selftests/x86/sgx/main.c index ae1822b10c6f..8c3f0cd41098 100644 --- a/tools/testing/selftests/x86/sgx/main.c +++ b/tools/testing/selftests/x86/sgx/main.c @@ -337,14 +337,58 @@ static int basic_exit_handler(long rdi, long rsi, long rdx, int ret, return 0; } +static int nr_page_faults; + +static int mprotect_exit_handler(long rdi, long rsi, long rdx, int ret, + long r8, long r9, void *tcs, long ursp, + struct sgx_enclave_exception *e) +{ + int prot, rc; + + if (!ret) + return 0; + + ++nr_page_faults; + + ASSERT_EQ(ret, -EFAULT); + ASSERT_EQ(e->trapnr, PF_VECTOR); + TEST_ASSERT(e->leaf == ENCLU_EENTER || e->leaf == ENCLU_ERESUME, + "Expected #PF on EENTER or ERESUME, leaf = %d\n", e->leaf); + TEST_ASSERT(e->error_code & 1, "Unexpected !PRESENT #PF"); + + /* The first #PF should be on the TCS, passed in via R9. */ + if (nr_page_faults == 1) + ASSERT_EQ(r9, (e->address & ~0xfff)); + + prot = PROT_READ; + if (e->error_code & 0x2) + prot |= PROT_WRITE; + if (e->error_code & 0x10) + prot |= PROT_EXEC; + rc = mprotect((void *)(e->address & ~0xfff), PAGE_SIZE, prot); + ASSERT_EQ(rc, 0); + + /* + * If EENTER faulted, bounce all the way back to the test to verify + * the vDSO is handling the return value correctly. + */ + if (e->leaf == ENCLU_EENTER) + return -EAGAIN; + + /* Else ERESUME faulted, simply do ERESUME again. */ + return e->leaf; +} + /* * Test the vDSO API, __vdso_sgx_enter_enclave(), with an exit handler. */ -static void test_vdso_with_exit_handler(struct sgx_secs *secs) +static void test_vdso_with_exit_handler(struct sgx_secs *secs, + unsigned long encl_size) { struct sgx_enclave_exception exception; uint64_t result = 0; long ret; + int r; memset(&exception, 0, sizeof(exception)); @@ -352,6 +396,45 @@ static void test_vdso_with_exit_handler(struct sgx_secs *secs) &exception, basic_exit_handler); ASSERT_EQ(ret, 0); ASSERT_EQ(result, MAGIC); + + /* + * Map the enclave read-only, then re-enter the enclave. The exit + * handler will service the resulting page faults using mprotect() to + * restore the correct permissions. + */ + r = mprotect((void *)secs->base, encl_size, PROT_READ); + TEST_ASSERT(!r, "mprotect() on enclave failed: %s\n", strerror(errno)); + + + /* Loop on EENTER until it succeeds or it fails unexpectedly. */ + result = 0; + do { + /* + * Pass the address of the TCS to the exit handler via R9. + * The first page fault should be on the TCS and R9 should + * not be modified prior to entering the enclave (which + * requires an accessible TCS page). + */ + ret = sgx_call((void *)&MAGIC, &result, 0, 0, 0, secs->base, + (void *)secs->base, &exception, + mprotect_exit_handler); + } while (ret == -EAGAIN); + ASSERT_EQ(ret, 0); + ASSERT_EQ(result, MAGIC); + + /* Enclave should re-execute cleanly. */ + result = 0; + ret = sgx_call((void *)&MAGIC, &result, 0, 0, 0, 0, (void *)secs->base, + &exception, basic_exit_handler); + ASSERT_EQ(ret, 0); + ASSERT_EQ(result, MAGIC); + + /* + * At least three faults should occur: one for the TCS, one for the + * executable code, and one for the writable data (@result). + */ + TEST_ASSERT(nr_page_faults >= 3, "Expected 3+ page faults, only hit %d", + nr_page_faults); } int main(int argc, char *argv[], char *envp[]) @@ -381,7 +464,7 @@ int main(int argc, char *argv[], char *envp[]) encl_build(&secs, bin, bin_size, &sigstruct); test_vdso_no_exit_handler(&secs); - test_vdso_with_exit_handler(&secs); + test_vdso_with_exit_handler(&secs, bin_size); printf("All tests passed!\n"); exit(0); -- 2.22.0