| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #include <autoconf.h> |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sel4/sel4.h> |
| #include <sel4utils/arch/util.h> |
| #include <utils/util.h> |
| |
| #include <vka/object.h> |
| |
| #include "../test.h" |
| #include "../helpers.h" |
| |
| enum { |
| FAULT_DATA_READ_PAGEFAULT = 1, |
| FAULT_DATA_WRITE_PAGEFAULT = 2, |
| FAULT_INSTRUCTION_PAGEFAULT = 3, |
| FAULT_BAD_SYSCALL = 4, |
| FAULT_BAD_INSTRUCTION = 5, |
| }; |
| |
| enum { |
| BADGED = seL4_WordBits - 1, |
| RESTART = seL4_WordBits - 2, |
| }; |
| |
| /* Use a different test virtual address on 32 and 64-bit systems so that we can exercise |
| the full address space to make sure fault messages are not truncating fault information. */ |
| #if CONFIG_WORD_SIZE == 32 |
| #ifdef CONFIG_ARCH_RISCV32 |
| #define BAD_VADDR 0x7ffedcba /* 32-bit RISCV userspace */ |
| #else |
| #define BAD_VADDR 0xf123456C |
| #endif |
| #elif CONFIG_WORD_SIZE == 64 |
| #ifdef CONFIG_ARCH_RISCV64 |
| #define BAD_VADDR 0x3CBA987650 /* Valid Sv39 Virtual Address */ |
| #else |
| /* virtual address we test is in the valid 48-bit portion of the virtual address space */ |
| #define BAD_VADDR 0x7EDCBA987650 |
| #endif |
| #endif |
| #define GOOD_MAGIC 0x15831851 |
| #define BAD_MAGIC ~GOOD_MAGIC |
| #define BAD_SYSCALL_NUMBER 0xc1 |
| |
| #define EXPECTED_BADGE 0xabababa |
| |
| #ifdef CONFIG_ARCH_RISCV |
| #if __riscv_xlen == 32 |
| #define LOAD lw |
| #define STORE sw |
| #else /* __riscv_xlen == 64 */ |
| #define LOAD ld |
| #define STORE sd |
| #endif |
| |
| #define LOAD_S STRINGIFY(LOAD) |
| #define STORE_S STRINGIFY(STORE) |
| #endif |
| |
| extern char read_fault_address[]; |
| extern char read_fault_restart_address[]; |
| static void __attribute__((noinline)) |
| do_read_fault(void) |
| { |
| int *x = (int *)BAD_VADDR; |
| int val = BAD_MAGIC; |
| /* Do a read fault. */ |
| #if defined(CONFIG_ARCH_AARCH32) |
| asm volatile( |
| "mov r0, %[val]\n\t" |
| "read_fault_address:\n\t" |
| "ldr r0, [%[addrreg]]\n\t" |
| "read_fault_restart_address:\n\t" |
| "mov %[val], r0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "r0" |
| ); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| asm volatile( |
| "mov x0, %[val]\n\t" |
| "read_fault_address:\n\t" |
| "ldr x0, [%[addrreg]]\n\t" |
| "read_fault_restart_address:\n\t" |
| "mov %[val], x0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "x0" |
| ); |
| #elif defined(CONFIG_ARCH_RISCV) |
| asm volatile( |
| "mv a0, %[val]\n\t" |
| "read_fault_address:\n\t" |
| LOAD_S " a0, 0(%[addrreg])\n\t" |
| "read_fault_restart_address:\n\t" |
| "mv %[val], a0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "a0" |
| ); |
| #elif defined(CONFIG_ARCH_X86) |
| asm volatile( |
| "mov %[val], %%eax\n\t" |
| "read_fault_address:\n\t" |
| "mov (%[addrreg]), %%eax\n\t" |
| "read_fault_restart_address:\n\t" |
| "mov %%eax, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "eax" |
| ); |
| #else |
| #error "Unknown architecture." |
| #endif |
| test_check(val == GOOD_MAGIC); |
| } |
| |
| extern char write_fault_address[]; |
| extern char write_fault_restart_address[]; |
| static void __attribute__((noinline)) |
| do_write_fault(void) |
| { |
| int *x = (int *)BAD_VADDR; |
| int val = BAD_MAGIC; |
| /* Do a write fault. */ |
| #if defined(CONFIG_ARCH_AARCH32) |
| asm volatile( |
| "mov r0, %[val]\n\t" |
| "write_fault_address:\n\t" |
| "str r0, [%[addrreg]]\n\t" |
| "write_fault_restart_address:\n\t" |
| "mov %[val], r0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "r0" |
| ); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| asm volatile( |
| "mov x0, %[val]\n\t" |
| "write_fault_address:\n\t" |
| "str x0, [%[addrreg]]\n\t" |
| "write_fault_restart_address:\n\t" |
| "mov %[val], x0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "x0" |
| ); |
| #elif defined(CONFIG_ARCH_RISCV) |
| asm volatile( |
| "mv a0, %[val]\n\t" |
| "write_fault_address:\n\t" |
| STORE_S " a0, 0(%[addrreg])\n\t" |
| "write_fault_restart_address:\n\t" |
| "mv %[val], a0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "a0" |
| ); |
| #elif defined(CONFIG_ARCH_X86) |
| asm volatile( |
| "mov %[val], %%eax\n\t" |
| "write_fault_address:\n\t" |
| "mov %%eax, (%[addrreg])\n\t" |
| "write_fault_restart_address:\n\t" |
| "mov %%eax, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "eax" |
| ); |
| #else |
| #error "Unknown architecture." |
| #endif |
| test_check(val == GOOD_MAGIC); |
| } |
| |
| extern char instruction_fault_restart_address[]; |
| static void __attribute__((noinline)) |
| do_instruction_fault(void) |
| { |
| int *x = (int *)BAD_VADDR; |
| int val = BAD_MAGIC; |
| /* Jump to a crazy address. */ |
| #if defined(CONFIG_ARCH_AARCH32) |
| asm volatile( |
| "mov r0, %[val]\n\t" |
| "blx %[addrreg]\n\t" |
| "instruction_fault_restart_address:\n\t" |
| "mov %[val], r0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "r0", "lr" |
| ); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| asm volatile( |
| "mov x0, %[val]\n\t" |
| "blr %[addrreg]\n\t" |
| "instruction_fault_restart_address:\n\t" |
| "mov %[val], x0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "x0", "x30" |
| ); |
| #elif defined(CONFIG_ARCH_RISCV) |
| asm volatile( |
| "mv a0, %[val]\n\t" |
| "jalr %[addrreg]\n\t" |
| "instruction_fault_restart_address:\n\t" |
| "mv %[val], a0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "a0", "ra" |
| ); |
| #elif defined(CONFIG_ARCH_X86) |
| asm volatile( |
| "mov %[val], %%eax\n\t" |
| "instruction_fault_address:\n\t" |
| "jmp *%[addrreg]\n\t" |
| "instruction_fault_restart_address:\n\t" |
| "mov %%eax, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x) |
| : "eax" |
| ); |
| #else |
| #error "Unknown architecture." |
| #endif |
| test_check(val == GOOD_MAGIC); |
| } |
| |
| extern char bad_syscall_address[]; |
| extern char bad_syscall_restart_address[]; |
| static void __attribute__((noinline)) |
| do_bad_syscall(void) |
| { |
| int *x = (int *)BAD_VADDR; |
| int val = BAD_MAGIC; |
| /* Do an undefined system call. */ |
| #if defined(CONFIG_ARCH_AARCH32) |
| asm volatile( |
| "mov r7, %[scno]\n\t" |
| "mov r0, %[val]\n\t" |
| "bad_syscall_address:\n\t" |
| "svc %[scno]\n\t" |
| "bad_syscall_restart_address:\n\t" |
| "mov %[val], r0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "i"(BAD_SYSCALL_NUMBER) |
| : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "memory", "cc" |
| ); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| asm volatile( |
| "mov x7, %[scno]\n\t" |
| "mov x0, %[val]\n\t" |
| "bad_syscall_address:\n\t" |
| "svc %[scno]\n\t" |
| "bad_syscall_restart_address:\n\t" |
| "mov %[val], x0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "i"(BAD_SYSCALL_NUMBER) |
| : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "memory", "cc" |
| ); |
| #elif defined(CONFIG_ARCH_RISCV) |
| asm volatile( |
| "li a7, %[scno]\n\t" |
| "mv a0, %[val]\n\t" |
| "bad_syscall_address:\n\t" |
| "ecall \n\t" |
| "bad_syscall_restart_address:\n\t" |
| "mv %[val], a0\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "i"(BAD_SYSCALL_NUMBER) |
| : "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "memory", "cc" |
| ); |
| #elif defined(CONFIG_ARCH_X86_64) && defined(CONFIG_SYSENTER) |
| asm volatile( |
| "movl %[val], %%ebx\n\t" |
| "movq %%rsp, %%rcx\n\t" |
| "leaq 1f, %%rdx\n\t" |
| "bad_syscall_address:\n\t" |
| "1: \n\t" |
| "sysenter\n\t" |
| "bad_syscall_restart_address:\n\t" |
| "movl %%ebx, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "a"(BAD_SYSCALL_NUMBER) |
| : "rbx", "rcx", "rdx" |
| ); |
| #elif defined(CONFIG_SYSCALL) |
| asm volatile( |
| "movl %[val], %%ebx\n\t" |
| "movq %%rsp, %%r12\n\t" |
| "bad_syscall_address:\n\t" |
| "syscall\n\t" |
| "bad_syscall_restart_address:\n\t" |
| "movq %%r12, %%rsp\n" |
| "movl %%ebx, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "d"(BAD_SYSCALL_NUMBER) |
| : "rax", "rbx", "rcx", "r11", "r12" |
| ); |
| #elif defined(CONFIG_ARCH_IA32) |
| asm volatile( |
| "mov %[scno], %%eax\n\t" |
| "mov %[val], %%ebx\n\t" |
| "mov %%esp, %%ecx\n\t" |
| "leal 1f, %%edx\n\t" |
| "bad_syscall_address:\n\t" |
| "1:\n\t" |
| "sysenter\n\t" |
| "bad_syscall_restart_address:\n\t" |
| "mov %%ebx, %[val]\n\t" |
| : [val] "+r"(val) |
| : [addrreg] "r"(x), |
| [scno] "i"(BAD_SYSCALL_NUMBER) |
| : "eax", "ebx", "ecx", "edx" |
| ); |
| #else |
| #error "Unknown architecture." |
| #endif |
| test_check(val == GOOD_MAGIC); |
| } |
| |
| extern char bad_instruction_address[]; |
| extern char bad_instruction_restart_address[]; |
| static seL4_Word bad_instruction_sp; /* To reset afterwards. */ |
| static seL4_Word bad_instruction_cpsr; /* For checking against. */ |
| static void __attribute__((noinline)) |
| do_bad_instruction(void) |
| { |
| int val = BAD_MAGIC; |
| /* Execute an undefined instruction. */ |
| #if defined(CONFIG_ARCH_AARCH32) |
| asm volatile( |
| /* Save SP */ |
| "str sp, [%[sp]]\n\t" |
| |
| /* Save CPSR */ |
| "mrs r0, cpsr\n\t" |
| "str r0, [%[cpsr]]\n\t" |
| |
| /* Set SP to val. */ |
| "mov sp, %[valptr]\n\t" |
| |
| "bad_instruction_address:\n\t" |
| ".word 0xe7f000f0\n\t" /* Guaranteed to be undefined by ARM. */ |
| "bad_instruction_restart_address:\n\t" |
| : |
| : [sp] "r"(&bad_instruction_sp), |
| [cpsr] "r"(&bad_instruction_cpsr), |
| [valptr] "r"(&val) |
| : "r0", "memory" |
| ); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| asm volatile( |
| /* Save SP */ |
| "mov x0, sp\n\t" |
| "str x0, [%[sp]]\n\t" |
| |
| /* Save PSTATE.nzcv */ |
| "mrs x0, nzcv\n\t" |
| "str x0, [%[cpsr]]\n\t" |
| |
| /* Set SP to val. */ |
| "mov sp, %[valptr]\n\t" |
| |
| "bad_instruction_address:\n\t" |
| ".word 0xe7f000f0\n\t" /* Guaranteed to be undefined by ARM. */ |
| "bad_instruction_restart_address:\n\t" |
| : |
| : [sp] "r"(&bad_instruction_sp), |
| [cpsr] "r"(&bad_instruction_cpsr), |
| [valptr] "r"(&val) |
| : "x0", "memory" |
| ); |
| #elif defined(CONFIG_ARCH_RISCV) |
| asm volatile( |
| /* Save SP */ |
| "mv a0, sp\n\t" |
| STORE_S " a0, 0(%[sp])\n\t" |
| |
| /* Set SP to val. */ |
| "mv sp, %[valptr]\n\t" |
| |
| /* All ones is used as the illegal instruction because it is reasonable |
| * to assume that all current targets support a maximum instruction |
| * length (ILEN) of 32 bits. All zeros was considered and not used |
| * because all ones is easier to validated for the purposes of this |
| * test. Note: on targets that require 32 bit aligned instructions, |
| * this will be 32 bit aligned due to the previous instruction. */ |
| "bad_instruction_address:\n\t" |
| ".word 0xffffffff\n\t" |
| "bad_instruction_restart_address:\n\t" |
| : |
| : [sp] "r"(&bad_instruction_sp), |
| [valptr] "r"(&val) |
| : "a0", "memory" |
| ); |
| #elif defined(CONFIG_ARCH_X86_64) |
| asm volatile( |
| /* save RSP */ |
| "movq %%rsp, (%[sp])\n\t" |
| "pushf\n\t" |
| "pop %%rax\n\t" |
| "movq %%rax, (%[cpsr])\n\t" |
| "movq %[valptr], %%rsp\n\t" |
| "bad_instruction_address:\n\t" |
| "ud2\n\t" |
| "bad_instruction_restart_address:\n\t" |
| : |
| : [sp] "r"(&bad_instruction_sp), |
| [cpsr] "r"(&bad_instruction_cpsr), |
| [valptr] "r"(&val) |
| : "rax", "memory" |
| ); |
| #elif defined(CONFIG_ARCH_IA32) |
| asm volatile( |
| /* Save SP */ |
| "mov %%esp, (%[sp])\n\t" |
| |
| /* Save CPSR */ |
| "pushf\n\t" |
| "pop %%eax\n\t" |
| "mov %%eax, (%[cpsr])\n\t" |
| |
| /* Set SP to val. */ |
| "mov %[valptr], %%esp\n\t" |
| |
| "bad_instruction_address:\n\t" |
| "ud2\n\t" |
| "bad_instruction_restart_address:\n\t" |
| : |
| : [sp] "r"(&bad_instruction_sp), |
| [cpsr] "r"(&bad_instruction_cpsr), |
| [valptr] "r"(&val) |
| : "eax", "memory" |
| ); |
| #else |
| #error "Unknown architecture." |
| #endif |
| test_check(val == GOOD_MAGIC); |
| } |
| |
| static void __attribute__((noinline)) |
| set_good_magic_and_set_pc(seL4_CPtr tcb, seL4_Word new_pc) |
| { |
| /* Set their register to GOOD_MAGIC and set PC past fault. */ |
| int error; |
| seL4_UserContext ctx; |
| error = seL4_TCB_ReadRegisters(tcb, |
| false, |
| 0, |
| sizeof(ctx) / sizeof(seL4_Word), |
| &ctx); |
| test_check(!error); |
| #if defined(CONFIG_ARCH_AARCH32) |
| test_check(ctx.r0 == BAD_MAGIC); |
| ctx.r0 = GOOD_MAGIC; |
| ctx.pc = new_pc; |
| #elif defined(CONFIG_ARCH_AARCH64) |
| test_check((int)ctx.x0 == BAD_MAGIC); |
| ctx.x0 = GOOD_MAGIC; |
| ctx.pc = new_pc; |
| #elif defined(CONFIG_ARCH_RISCV) |
| test_check((int)ctx.a0 == BAD_MAGIC); |
| ctx.a0 = GOOD_MAGIC; |
| ctx.pc = new_pc; |
| #elif defined(CONFIG_ARCH_X86_64) |
| test_check((int)ctx.rax == BAD_MAGIC); |
| ctx.rax = GOOD_MAGIC; |
| ctx.rip = new_pc; |
| #elif defined(CONFIG_ARCH_IA32) |
| test_check(ctx.eax == BAD_MAGIC); |
| ctx.eax = GOOD_MAGIC; |
| ctx.eip = new_pc; |
| #else |
| #error "Unknown architecture." |
| #endif |
| error = seL4_TCB_WriteRegisters(tcb, |
| false, |
| 0, |
| sizeof(ctx) / sizeof(seL4_Word), |
| &ctx); |
| test_check(!error); |
| } |
| |
| static int handle_fault(seL4_CPtr fault_ep, seL4_CPtr tcb, seL4_Word expected_fault, |
| seL4_Word flags_and_reply) |
| { |
| seL4_MessageInfo_t tag; |
| seL4_Word sender_badge = 0; |
| seL4_CPtr reply = flags_and_reply & MASK(RESTART); |
| bool badged = flags_and_reply & BIT(BADGED); |
| bool restart = flags_and_reply & BIT(RESTART); |
| |
| tag = api_recv(fault_ep, &sender_badge, reply); |
| |
| if (badged) { |
| test_check(sender_badge == EXPECTED_BADGE); |
| } else { |
| test_check(sender_badge == 0); |
| } |
| |
| switch (expected_fault) { |
| case FAULT_DATA_READ_PAGEFAULT: |
| test_check(seL4_MessageInfo_get_label(tag) == seL4_Fault_VMFault); |
| test_check(seL4_MessageInfo_get_length(tag) == seL4_VMFault_Length); |
| test_check(seL4_GetMR(seL4_VMFault_IP) == (seL4_Word)read_fault_address); |
| test_check(seL4_GetMR(seL4_VMFault_Addr) == BAD_VADDR); |
| test_check(seL4_GetMR(seL4_VMFault_PrefetchFault) == 0); |
| test_check(sel4utils_is_read_fault()); |
| |
| /* Clear MRs to ensure they get repopulated. */ |
| seL4_SetMR(seL4_VMFault_Addr, 0); |
| |
| set_good_magic_and_set_pc(tcb, (seL4_Word)read_fault_restart_address); |
| if (restart) { |
| api_reply(reply, tag); |
| } |
| break; |
| |
| case FAULT_DATA_WRITE_PAGEFAULT: |
| test_check(seL4_MessageInfo_get_label(tag) == seL4_Fault_VMFault); |
| test_check(seL4_MessageInfo_get_length(tag) == seL4_VMFault_Length); |
| test_check(seL4_GetMR(seL4_VMFault_IP) == (seL4_Word)write_fault_address); |
| test_check(seL4_GetMR(seL4_VMFault_Addr) == BAD_VADDR); |
| test_check(seL4_GetMR(seL4_VMFault_PrefetchFault) == 0); |
| test_check(!sel4utils_is_read_fault()); |
| |
| /* Clear MRs to ensure they get repopulated. */ |
| seL4_SetMR(seL4_VMFault_Addr, 0); |
| |
| set_good_magic_and_set_pc(tcb, (seL4_Word)write_fault_restart_address); |
| if (restart) { |
| api_reply(reply, tag); |
| } |
| break; |
| |
| case FAULT_INSTRUCTION_PAGEFAULT: |
| test_check(seL4_MessageInfo_get_label(tag) == seL4_Fault_VMFault); |
| test_check(seL4_MessageInfo_get_length(tag) == seL4_VMFault_Length); |
| test_check(seL4_GetMR(seL4_VMFault_IP) == BAD_VADDR); |
| test_check(seL4_GetMR(seL4_VMFault_Addr) == BAD_VADDR); |
| #if defined(CONFIG_ARCH_ARM) || defined(CONFIG_ARCH_RISCV) |
| /* Prefetch fault is only set on ARM and RISCV. */ |
| test_check(seL4_GetMR(seL4_VMFault_PrefetchFault) == 1); |
| #endif |
| test_check(sel4utils_is_read_fault()); |
| |
| /* Clear MRs to ensure they get repopulated. */ |
| seL4_SetMR(seL4_VMFault_Addr, 0); |
| |
| set_good_magic_and_set_pc(tcb, (seL4_Word)instruction_fault_restart_address); |
| if (restart) { |
| api_reply(reply, tag); |
| } |
| break; |
| |
| case FAULT_BAD_SYSCALL: |
| test_eq(seL4_MessageInfo_get_label(tag), (seL4_Word) seL4_Fault_UnknownSyscall); |
| test_eq(seL4_MessageInfo_get_length(tag), (seL4_Word) seL4_UnknownSyscall_Length); |
| test_eq(seL4_GetMR(seL4_UnknownSyscall_FaultIP), (seL4_Word) bad_syscall_address); |
| test_eq((int)seL4_GetMR(seL4_UnknownSyscall_Syscall), BAD_SYSCALL_NUMBER); |
| seL4_SetMR(seL4_UnknownSyscall_FaultIP, (seL4_Word)bad_syscall_restart_address); |
| #if defined(CONFIG_ARCH_AARCH32) |
| test_eq(seL4_GetMR(seL4_UnknownSyscall_R0), BAD_MAGIC); |
| seL4_SetMR(seL4_UnknownSyscall_R0, GOOD_MAGIC); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| test_eq((int)seL4_GetMR(seL4_UnknownSyscall_X0), BAD_MAGIC); |
| seL4_SetMR(seL4_UnknownSyscall_X0, GOOD_MAGIC); |
| #elif defined(CONFIG_ARCH_X86_64) |
| test_eq((int)seL4_GetMR(seL4_UnknownSyscall_RBX), BAD_MAGIC); |
| test_eq(seL4_GetMR(seL4_UnknownSyscall_FaultIP), (seL4_Word) bad_syscall_restart_address); |
| seL4_SetMR(seL4_UnknownSyscall_RBX, GOOD_MAGIC); |
| #elif defined(CONFIG_ARCH_RISCV) |
| test_eq((int)seL4_GetMR(seL4_UnknownSyscall_A0), BAD_MAGIC); |
| test_eq(seL4_GetMR(seL4_UnknownSyscall_FaultIP), (seL4_Word) bad_syscall_restart_address); |
| seL4_SetMR(seL4_UnknownSyscall_A0, GOOD_MAGIC); |
| seL4_SetMR(seL4_UnknownSyscall_FaultIP, (seL4_Word)bad_syscall_restart_address); |
| #elif defined(CONFIG_ARCH_IA32) |
| test_eq(seL4_GetMR(seL4_UnknownSyscall_EBX), BAD_MAGIC); |
| seL4_SetMR(seL4_UnknownSyscall_EBX, GOOD_MAGIC); |
| /* Syscalls on ia32 seem to restart themselves with sysenter. */ |
| #else |
| #error "Unknown architecture." |
| #endif |
| |
| /* Flag that the thread should be restarted. */ |
| if (restart) { |
| seL4_MessageInfo_ptr_set_label(&tag, 0); |
| } else { |
| seL4_MessageInfo_ptr_set_label(&tag, 1); |
| } |
| api_reply(reply, tag); |
| break; |
| |
| case FAULT_BAD_INSTRUCTION: |
| test_check(seL4_MessageInfo_get_label(tag) == seL4_Fault_UserException); |
| test_check(seL4_MessageInfo_get_length(tag) == seL4_UserException_Length); |
| test_check(seL4_GetMR(0) == (seL4_Word)bad_instruction_address); |
| int *valptr = (int *)seL4_GetMR(1); |
| test_check(*valptr == BAD_MAGIC); |
| #if defined(CONFIG_ARCH_AARCH32) |
| test_check(seL4_GetMR(2) == bad_instruction_cpsr); |
| test_check(seL4_GetMR(3) == 0); |
| test_check(seL4_GetMR(4) == 0); |
| #elif defined(CONFIG_ARCH_AARCH64) |
| /* We only can access PSTATE.nzcv flags in EL0 in aarch64 |
| * so, just make sure ther are preserved ... */ |
| test_check((seL4_GetMR(2) & ~MASK(27)) == bad_instruction_cpsr); |
| /* instruction fault on a 32 bit instruction with ISS = 0 */ |
| test_check(seL4_GetMR(3) == 0x02000000); |
| test_check(seL4_GetMR(4) == 0); |
| #elif defined(CONFIG_ARCH_RISCV) |
| test_check(seL4_GetMR(2) == 2); |
| test_check(seL4_GetMR(3) == 0); |
| #elif defined(CONFIG_ARCH_X86) |
| /* |
| * Curiously, the "resume flag" (bit 16) is set between the |
| * undefined syscall and seL4 grabbing the tasks's flags. This only |
| * happens on x86 hardware, but not qemu. Just ignore it when |
| * checking flags. |
| */ |
| seL4_Word mask_out = ~(1 << 16); |
| test_check(((seL4_GetMR(2) ^ bad_instruction_cpsr) & mask_out) == 0); |
| #else |
| #error "Unknown architecture." |
| #endif |
| |
| *valptr = GOOD_MAGIC; |
| seL4_SetMR(0, (seL4_Word)bad_instruction_restart_address); |
| seL4_SetMR(1, bad_instruction_sp); |
| |
| /* Flag that the thread should be restarted. */ |
| if (restart) { |
| seL4_MessageInfo_ptr_set_label(&tag, 0); |
| } else { |
| seL4_MessageInfo_ptr_set_label(&tag, 1); |
| } |
| |
| api_reply(reply, tag); |
| break; |
| |
| default: |
| /* What? Why are we here? What just happened? */ |
| test_assert(0); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int cause_fault(int fault_type) |
| { |
| switch (fault_type) { |
| case FAULT_DATA_READ_PAGEFAULT: |
| do_read_fault(); |
| break; |
| case FAULT_DATA_WRITE_PAGEFAULT: |
| do_write_fault(); |
| break; |
| case FAULT_INSTRUCTION_PAGEFAULT: |
| do_instruction_fault(); |
| break; |
| case FAULT_BAD_SYSCALL: |
| do_bad_syscall(); |
| break; |
| case FAULT_BAD_INSTRUCTION: |
| do_bad_instruction(); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int test_fault(env_t env, int fault_type, bool inter_as) |
| { |
| helper_thread_t handler_thread; |
| helper_thread_t faulter_thread; |
| int error; |
| |
| vka_object_t reply; |
| error = vka_alloc_reply(&env->vka, &reply); |
| |
| for (int restart = 0; restart <= 1; restart++) { |
| for (int prio = 100; prio <= 102; prio++) { |
| for (int badged = 0; badged <= 1; badged++) { |
| seL4_Word handler_arg0, handler_arg1; |
| /* The endpoint on which faults are received. */ |
| seL4_CPtr fault_ep = vka_alloc_endpoint_leaky(&env->vka); |
| if (badged) { |
| seL4_CPtr badged_fault_ep = get_free_slot(env); |
| cnode_mint(env, fault_ep, badged_fault_ep, seL4_AllRights, EXPECTED_BADGE); |
| |
| fault_ep = badged_fault_ep; |
| } |
| |
| seL4_CPtr faulter_vspace, faulter_cspace, reply_cptr; |
| |
| if (inter_as) { |
| create_helper_process(env, &faulter_thread); |
| create_helper_process(env, &handler_thread); |
| |
| /* copy the fault endpoint to the faulter */ |
| cspacepath_t path; |
| vka_cspace_make_path(&env->vka, fault_ep, &path); |
| seL4_CPtr remote_fault_ep = sel4utils_copy_path_to_process(&faulter_thread.process, path); |
| assert(remote_fault_ep != -1); |
| |
| if (!config_set(CONFIG_KERNEL_MCS)) { |
| fault_ep = remote_fault_ep; |
| } |
| |
| /* copy the fault endpoint to the handler */ |
| handler_arg0 = sel4utils_copy_path_to_process(&handler_thread.process, path); |
| assert(handler_arg0 != -1); |
| |
| /* copy the fault tcb to the handler */ |
| vka_cspace_make_path(&env->vka, get_helper_tcb(&faulter_thread), &path); |
| handler_arg1 = sel4utils_copy_path_to_process(&handler_thread.process, path); |
| assert(handler_arg1 != -1); |
| |
| reply_cptr = sel4utils_copy_cap_to_process(&handler_thread.process, &env->vka, |
| reply.cptr); |
| faulter_cspace = faulter_thread.process.cspace.cptr; |
| faulter_vspace = faulter_thread.process.pd.cptr; |
| } else { |
| create_helper_thread(env, &faulter_thread); |
| create_helper_thread(env, &handler_thread); |
| faulter_cspace = env->cspace_root; |
| faulter_vspace = env->page_directory; |
| handler_arg0 = fault_ep; |
| handler_arg1 = get_helper_tcb(&faulter_thread); |
| reply_cptr = reply.cptr; |
| } |
| |
| set_helper_priority(env, &handler_thread, 101); |
| error = api_tcb_set_space(get_helper_tcb(&faulter_thread), |
| fault_ep, |
| faulter_cspace, |
| api_make_guard_skip_word(seL4_WordBits - env->cspace_size_bits), |
| faulter_vspace, seL4_NilData); |
| test_error_eq(error, seL4_NoError); |
| set_helper_priority(env, &faulter_thread, prio); |
| |
| // Ensure that the BADGED and RESTART bits are not |
| // already set on the cptr. |
| test_assert(!(reply_cptr & (BIT(RESTART) | BIT(BADGED)))); |
| seL4_Word flags_and_reply = reply_cptr | |
| (badged ? BIT(BADGED) : 0) | |
| (restart ? BIT(RESTART) : 0); |
| |
| start_helper(env, &handler_thread, (helper_fn_t) handle_fault, |
| handler_arg0, handler_arg1, fault_type, flags_and_reply); |
| start_helper(env, &faulter_thread, (helper_fn_t) cause_fault, |
| fault_type, 0, 0, 0); |
| wait_for_helper(&handler_thread); |
| |
| if (restart) { |
| wait_for_helper(&faulter_thread); |
| } |
| |
| cleanup_helper(env, &handler_thread); |
| cleanup_helper(env, &faulter_thread); |
| } |
| } |
| } |
| |
| return sel4test_get_result(); |
| } |
| |
| static int test_read_fault(env_t env) |
| { |
| return test_fault(env, FAULT_DATA_READ_PAGEFAULT, false); |
| } |
| DEFINE_TEST(PAGEFAULT0001, "Test read page fault", test_read_fault, !config_set(CONFIG_FT)) |
| |
| static int test_write_fault(env_t env) |
| { |
| return test_fault(env, FAULT_DATA_WRITE_PAGEFAULT, false); |
| } |
| DEFINE_TEST(PAGEFAULT0002, "Test write page fault", test_write_fault, !config_set(CONFIG_FT)) |
| |
| static int test_execute_fault(env_t env) |
| { |
| return test_fault(env, FAULT_INSTRUCTION_PAGEFAULT, false); |
| } |
| DEFINE_TEST(PAGEFAULT0003, "Test execute page fault", test_execute_fault, !config_set(CONFIG_FT)) |
| |
| static int test_bad_syscall(env_t env) |
| { |
| return test_fault(env, FAULT_BAD_SYSCALL, false); |
| } |
| DEFINE_TEST(PAGEFAULT0004, "Test unknown system call", test_bad_syscall, true) |
| |
| static int test_bad_instruction(env_t env) |
| { |
| return test_fault(env, FAULT_BAD_INSTRUCTION, false); |
| } |
| /* TODO(b/249537690) causes a kernel fault; disable for now */ |
| DEFINE_TEST(PAGEFAULT0005, "Test undefined instruction", test_bad_instruction, false) |
| |
| static int test_read_fault_interas(env_t env) |
| { |
| return test_fault(env, FAULT_DATA_READ_PAGEFAULT, true); |
| } |
| DEFINE_TEST(PAGEFAULT1001, "Test read page fault (inter-AS)", test_read_fault_interas, true) |
| |
| static int test_write_fault_interas(env_t env) |
| { |
| return test_fault(env, FAULT_DATA_WRITE_PAGEFAULT, true); |
| } |
| DEFINE_TEST(PAGEFAULT1002, "Test write page fault (inter-AS)", test_write_fault_interas, true) |
| |
| static int test_execute_fault_interas(env_t env) |
| { |
| return test_fault(env, FAULT_INSTRUCTION_PAGEFAULT, true); |
| } |
| DEFINE_TEST(PAGEFAULT1003, "Test execute page fault (inter-AS)", test_execute_fault_interas, true) |
| |
| static int test_bad_syscall_interas(env_t env) |
| { |
| return test_fault(env, FAULT_BAD_SYSCALL, true); |
| } |
| DEFINE_TEST(PAGEFAULT1004, "Test unknown system call (inter-AS)", test_bad_syscall_interas, true) |
| |
| /* This test currently fails. It is disabled until it can be investigated and fixed. */ |
| static int test_bad_instruction_interas(env_t env) |
| { |
| return test_fault(env, FAULT_BAD_INSTRUCTION, true); |
| } |
| DEFINE_TEST(PAGEFAULT1005, "Test undefined instruction (inter-AS)", test_bad_instruction_interas, false) |
| |
| static void |
| timeout_fault_0001_fn(void) |
| { |
| while (1); |
| } |
| |
| int test_timeout_fault(env_t env) |
| { |
| helper_thread_t helper; |
| seL4_Word data = 1; |
| seL4_CPtr endpoint = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr ro = vka_alloc_reply_leaky(&env->vka); |
| |
| create_helper_thread(env, &helper); |
| set_helper_sched_params(env, &helper, US_IN_MS, US_IN_S, data); |
| set_helper_tfep(env, &helper, endpoint); |
| start_helper(env, &helper, (helper_fn_t) timeout_fault_0001_fn, 0, 0, 0, 0); |
| |
| /* wait for timeout fault */ |
| UNUSED seL4_MessageInfo_t info = api_recv(endpoint, NULL, ro); |
| for (int i = 0; i < 10; i++) { |
| #ifdef CONFIG_KERNEL_MCS |
| test_eq(seL4_MessageInfo_get_length(info), (seL4_Word) seL4_Timeout_Length); |
| test_check(seL4_isTimeoutFault_tag(info)); |
| test_eq(seL4_GetMR(seL4_Timeout_Data), data); |
| #endif |
| info = api_reply_recv(endpoint, seL4_MessageInfo_new(0, 0, 0, 0), NULL, ro); |
| } |
| |
| return sel4test_get_result(); |
| } |
| DEFINE_TEST(TIMEOUTFAULT0001, "Test timeout fault", test_timeout_fault, config_set(CONFIG_KERNEL_MCS)) |
| |
| void |
| timeout_fault_server_fn(seL4_CPtr ep, env_t env, seL4_CPtr ro) |
| { |
| /* signal to initialiser that we are done, and wait for a message from |
| * the client */ |
| ZF_LOGD("Server signal recv"); |
| api_nbsend_recv(ep, seL4_MessageInfo_new(0, 0, 0, 0), ep, NULL, ro); |
| /* spin, this will use up all of the clients budget */ |
| while (true); |
| /* we should not get here, as a timeout fault should have been raised |
| * and the handler will reset us */ |
| ZF_LOGF("Should not get here"); |
| } |
| |
| static int timeout_fault_client_fn(seL4_CPtr ep) |
| { |
| seL4_MessageInfo_t info = seL4_MessageInfo_new(0, 0, 0, 0); |
| while (1) { |
| info = seL4_Call(ep, info); |
| /* call should have failed, timeout fault handler will send a -1 */ |
| test_eq(seL4_GetMR(0), (seL4_Word) - 1); |
| } |
| return 0; |
| } |
| |
| int create_passive_thread_with_tfep(env_t env, helper_thread_t *passive, seL4_CPtr tfep, |
| seL4_Word badge, helper_fn_t fn, seL4_CPtr ep, seL4_Word arg1, |
| seL4_Word arg2, seL4_Word arg3, sel4utils_checkpoint_t *cp) |
| { |
| seL4_CPtr minted_tfep = get_free_slot(env); |
| int error = cnode_mint(env, tfep, minted_tfep, seL4_AllRights, badge); |
| test_eq(error, seL4_NoError); |
| |
| error = create_passive_thread(env, passive, fn, ep, arg1, arg2, arg3); |
| set_helper_tfep(env, passive, minted_tfep); |
| test_eq(error, 0); |
| |
| /* checkpoint */ |
| return sel4utils_checkpoint_thread(&passive->thread, cp, false); |
| } |
| |
| static int handle_timeout_fault(seL4_CPtr tfep, seL4_Word expected_badge, sel4utils_thread_t *server, |
| seL4_CPtr reply, sel4utils_checkpoint_t *cp, seL4_CPtr ep, |
| seL4_Word expected_data, env_t env) |
| { |
| seL4_Word badge; |
| seL4_CPtr server_reply = vka_alloc_reply_leaky(&env->vka); |
| |
| /* wait for timeout fault */ |
| ZF_LOGD("Wait for tf"); |
| seL4_MessageInfo_t info = api_recv(tfep, &badge, server_reply); |
| test_eq(badge, expected_badge); |
| #ifdef CONFIG_KERNEL_MCS |
| test_check(seL4_isTimeoutFault_tag(info)); |
| test_eq(seL4_GetMR(seL4_Timeout_Data), expected_data); |
| test_eq(seL4_MessageInfo_get_length(info), (seL4_Word) seL4_Timeout_Length); |
| #endif |
| /* reply to client on behalf of server */ |
| seL4_SetMR(0, -1); |
| seL4_Send(reply, info); |
| |
| size_t stack_size = (uintptr_t) cp->thread->stack_top - cp->sp; |
| memcpy((void *) cp->sp, cp->stack, stack_size); |
| |
| /* restore server */ |
| ZF_LOGD("Restoring server"); |
| int error = api_sc_bind(server->sched_context.cptr, server->tcb.cptr); |
| test_eq(error, seL4_NoError); |
| |
| ZF_LOGD("Reply to server"); |
| #ifdef CONFIG_KERNEL_MCS |
| info = seL4_TimeoutReply_new(true, cp->regs, sizeof(seL4_UserContext) / sizeof(seL4_Word)); |
| #endif |
| /* reply, restoring server state, and wait for server to init */ |
| api_reply_recv(ep, info, NULL, server_reply); |
| |
| error = api_sc_unbind(server->sched_context.cptr); |
| test_eq(error, seL4_NoError); |
| |
| return 0; |
| } |
| |
| static int test_timeout_fault_in_server(env_t env) |
| { |
| helper_thread_t client, server; |
| seL4_Word client_data = 1; |
| seL4_Word server_badge = 2; |
| sel4utils_checkpoint_t cp; |
| |
| seL4_CPtr tfep = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr ep = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr ro = vka_alloc_reply_leaky(&env->vka); |
| |
| /* create the server */ |
| int error = create_passive_thread_with_tfep(env, &server, tfep, server_badge, |
| (helper_fn_t) timeout_fault_server_fn, ep, |
| (seL4_Word)env, ro, 0, &cp); |
| test_eq(error, 0); |
| |
| /* create the client */ |
| create_helper_thread(env, &client); |
| set_helper_sched_params(env, &client, 0.1 * US_IN_S, US_IN_S, client_data); |
| start_helper(env, &client, (helper_fn_t) timeout_fault_client_fn, ep, 0, 0, 0); |
| |
| /* Ensure the client doesn't preempt the server when the server is |
| * being reset */ |
| set_helper_priority(env, &server, OUR_PRIO - 1); |
| set_helper_priority(env, &client, OUR_PRIO - 2); |
| |
| /* handle a few faults */ |
| for (int i = 0; i < 5; i++) { |
| ZF_LOGD("Handling fault"); |
| error = handle_timeout_fault(tfep, server_badge, &server.thread, ro, &cp, ep, |
| client_data, env); |
| test_eq(error, 0); |
| } |
| |
| return sel4test_get_result(); |
| |
| } |
| DEFINE_TEST(TIMEOUTFAULT0002, "Handle a timeout fault in a server", |
| test_timeout_fault_in_server, config_set(CONFIG_KERNEL_MCS)) |
| |
| static void |
| timeout_fault_proxy_fn(seL4_CPtr in, seL4_CPtr out, seL4_CPtr ro) |
| { |
| seL4_MessageInfo_t info = seL4_MessageInfo_new(0, 0, 0, 1); |
| info = api_nbsend_recv(in, info, in, NULL, ro); |
| while (1) { |
| info = seL4_Call(out, info); |
| api_reply_recv(in, info, NULL, ro); |
| } |
| } |
| |
| static int test_timeout_fault_nested_servers(env_t env) |
| { |
| helper_thread_t client, server, proxy; |
| sel4utils_checkpoint_t proxy_cp, server_cp; |
| |
| seL4_Word client_data = 1; |
| seL4_Word server_badge = 2; |
| seL4_Word proxy_badge = 3; |
| |
| seL4_CPtr client_proxy_ep = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr proxy_server_ep = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr tfep = vka_alloc_endpoint_leaky(&env->vka); |
| seL4_CPtr proxy_ro = vka_alloc_reply_leaky(&env->vka); |
| seL4_CPtr server_ro = vka_alloc_reply_leaky(&env->vka); |
| |
| /* create server */ |
| int error = create_passive_thread_with_tfep(env, &server, tfep, server_badge, |
| (helper_fn_t) timeout_fault_server_fn, proxy_server_ep, |
| (seL4_Word)env, server_ro, 0, &server_cp); |
| test_eq(error, 0); |
| |
| /* create proxy */ |
| error = create_passive_thread_with_tfep(env, &proxy, tfep, proxy_badge, |
| (helper_fn_t) timeout_fault_proxy_fn, client_proxy_ep, |
| proxy_server_ep, proxy_ro, 0, &proxy_cp); |
| test_eq(error, 0); |
| |
| /* create client */ |
| create_helper_thread(env, &client); |
| |
| error = api_sched_ctrl_configure(simple_get_sched_ctrl(&env->simple, 0), |
| client.thread.sched_context.cptr, |
| 0.1 * US_IN_S, 0.5 * US_IN_S, 0, client_data); |
| test_eq(error, 0); |
| |
| start_helper(env, &client, (helper_fn_t) timeout_fault_client_fn, client_proxy_ep, 0, 0, 0); |
| |
| /* handle some faults */ |
| for (int i = 0; i < 5; i++) { |
| /* server fault */ |
| ZF_LOGD("server fault\n"); |
| error = handle_timeout_fault(tfep, server_badge, &server.thread, server_ro, &server_cp, |
| proxy_server_ep, client_data, env); |
| test_eq(error, 0); |
| |
| /* proxy fault */ |
| ZF_LOGD("proxy fault\n"); |
| error = handle_timeout_fault(tfep, proxy_badge, &proxy.thread, proxy_ro, &proxy_cp, |
| client_proxy_ep, client_data, env); |
| test_eq(error, 0); |
| } |
| |
| return sel4test_get_result(); |
| } |
| // TODO(b/264478941): disable for raspi/bcm2837 due to qemu timer problems |
| // (no way to identify simulator so use platform as a proxy) |
| DEFINE_TEST(TIMEOUTFAULT0003, "Nested timeout fault", test_timeout_fault_nested_servers, |
| config_set(CONFIG_KERNEL_MCS) && !config_set(CONFIG_PLAT_BCM2837)) |
| |
| static void vm_enter(void) |
| { |
| #ifdef CONFIG_VTX |
| seL4_VMEnter(NULL); |
| #endif |
| } |
| |
| static int test_vm_enter_non_vm(env_t env) |
| { |
| seL4_Error err; |
| helper_thread_t helper; |
| seL4_CPtr fault_ep = vka_alloc_endpoint_leaky(&env->vka); |
| create_helper_thread(env, &helper); |
| |
| seL4_Word guard = seL4_WordBits - env->cspace_size_bits; |
| err = api_tcb_set_space(get_helper_tcb(&helper), fault_ep, env->cspace_root, |
| api_make_guard_skip_word(guard), |
| env->page_directory, seL4_NilData); |
| test_eq(err, 0); |
| |
| seL4_CPtr reply = vka_alloc_reply_leaky(&env->vka); |
| start_helper(env, &helper, (helper_fn_t) vm_enter, 0, 0, 0, 0); |
| seL4_MessageInfo_t tag = api_recv(fault_ep, NULL, reply); |
| test_eq(seL4_MessageInfo_get_label(tag), (seL4_Word) seL4_Fault_UnknownSyscall); |
| return sel4test_get_result(); |
| } |
| DEFINE_TEST(UNKNOWN_SYSCALL_001, "Test seL4_VMEnter in a non-vm thread", |
| test_vm_enter_non_vm, config_set(CONFIG_VTX)); |