Message ID | 20241008225010.1861630-14-debug@rivosinc.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | riscv support for control flow integrity extensions | expand |
On Wed, Oct 9, 2024 at 8:54 AM Deepak Gupta <debug@rivosinc.com> wrote: > > zicfiss protects shadow stack using new page table encodings PTE.W=1, > PTE.R=0 and PTE.X=0. This encoding is reserved if zicfiss is not > implemented or if shadow stack are not enabled. > Loads on shadow stack memory are allowed while stores to shadow stack > memory leads to access faults. Shadow stack accesses to RO memory > leads to store page fault. > > To implement special nature of shadow stack memory where only selected > stores (shadow stack stores from sspush) have to be allowed while rest > of regular stores disallowed, new MMU TLB index is created for shadow > stack. > > Furthermore, `check_zicbom_access` (`cbo.clean/flush/inval`) may probe > shadow stack memory and must always raise store/AMO access fault because > it has store semantics. For non-shadow stack memory even though > `cbo.clean/flush/inval` have store semantics, it will not fault if read > is allowed (probably to follow `clflush` on x86). Although if read is not > allowed, eventually `probe_write` will do store page (or access) fault (if > permissions don't allow it). cbo operations on shadow stack memory must > always raise store access fault. Thus extending `get_physical_address` to > recieve `probe` parameter as well. > > Signed-off-by: Deepak Gupta <debug@rivosinc.com> > Suggested-by: Richard Henderson <richard.henderson@linaro.org> > Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Alistair > --- > target/riscv/cpu_helper.c | 64 ++++++++++++++++++++++++++++++--------- > target/riscv/internals.h | 3 ++ > 2 files changed, 53 insertions(+), 14 deletions(-) > > diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c > index 93d199748e..8f7871c92b 100644 > --- a/target/riscv/cpu_helper.c > +++ b/target/riscv/cpu_helper.c > @@ -884,7 +884,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, > target_ulong *fault_pte_addr, > int access_type, int mmu_idx, > bool first_stage, bool two_stage, > - bool is_debug) > + bool is_debug, bool is_probe) > { > /* > * NOTE: the env->pc value visible here will not be > @@ -898,6 +898,8 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, > hwaddr ppn; > int napot_bits = 0; > target_ulong napot_mask; > + bool is_sstack_idx = ((mmu_idx & MMU_IDX_SS_WRITE) == MMU_IDX_SS_WRITE); > + bool sstack_page = false; > > /* > * Check if we should use the background registers for the two > @@ -1028,7 +1030,7 @@ restart: > int vbase_ret = get_physical_address(env, &vbase, &vbase_prot, > base, NULL, MMU_DATA_LOAD, > MMUIdx_U, false, true, > - is_debug); > + is_debug, false); > > if (vbase_ret != TRANSLATE_SUCCESS) { > if (fault_pte_addr) { > @@ -1106,21 +1108,43 @@ restart: > return TRANSLATE_FAIL; > } > > + target_ulong rwx = pte & (PTE_R | PTE_W | PTE_X); > /* Check for reserved combinations of RWX flags. */ > - switch (pte & (PTE_R | PTE_W | PTE_X)) { > - case PTE_W: > + switch (rwx) { > case PTE_W | PTE_X: > return TRANSLATE_FAIL; > + case PTE_W: > + /* if bcfi enabled, PTE_W is not reserved and shadow stack page */ > + if (cpu_get_bcfien(env) && first_stage) { > + sstack_page = true; > + /* > + * if ss index, read and write allowed. else if not a probe > + * then only read allowed > + */ > + rwx = is_sstack_idx ? (PTE_R | PTE_W) : (is_probe ? 0 : PTE_R); > + break; > + } > + return TRANSLATE_FAIL; > + case PTE_R: > + /* > + * no matter what's the `access_type`, shadow stack access to readonly > + * memory are always store page faults. During unwind, loads will be > + * promoted as store fault. > + */ > + if (is_sstack_idx) { > + return TRANSLATE_FAIL; > + } > + break; > } > > int prot = 0; > - if (pte & PTE_R) { > + if (rwx & PTE_R) { > prot |= PAGE_READ; > } > - if (pte & PTE_W) { > + if (rwx & PTE_W) { > prot |= PAGE_WRITE; > } > - if (pte & PTE_X) { > + if (rwx & PTE_X) { > bool mxr = false; > > /* > @@ -1164,8 +1188,11 @@ restart: > } > > if (!((prot >> access_type) & 1)) { > - /* Access check failed */ > - return TRANSLATE_FAIL; > + /* > + * Access check failed, access check failures for shadow stack are > + * access faults. > + */ > + return sstack_page ? TRANSLATE_PMP_FAIL : TRANSLATE_FAIL; > } > > target_ulong updated_pte = pte; > @@ -1303,13 +1330,13 @@ hwaddr riscv_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) > int mmu_idx = riscv_env_mmu_index(&cpu->env, false); > > if (get_physical_address(env, &phys_addr, &prot, addr, NULL, 0, mmu_idx, > - true, env->virt_enabled, true)) { > + true, env->virt_enabled, true, false)) { > return -1; > } > > if (env->virt_enabled) { > if (get_physical_address(env, &phys_addr, &prot, phys_addr, NULL, > - 0, MMUIdx_U, false, true, true)) { > + 0, MMUIdx_U, false, true, true, false)) { > return -1; > } > } > @@ -1352,9 +1379,17 @@ void riscv_cpu_do_unaligned_access(CPUState *cs, vaddr addr, > break; > case MMU_DATA_LOAD: > cs->exception_index = RISCV_EXCP_LOAD_ADDR_MIS; > + /* shadow stack mis aligned accesses are access faults */ > + if (mmu_idx & MMU_IDX_SS_WRITE) { > + cs->exception_index = RISCV_EXCP_LOAD_ACCESS_FAULT; > + } > break; > case MMU_DATA_STORE: > cs->exception_index = RISCV_EXCP_STORE_AMO_ADDR_MIS; > + /* shadow stack mis aligned accesses are access faults */ > + if (mmu_idx & MMU_IDX_SS_WRITE) { > + cs->exception_index = RISCV_EXCP_STORE_AMO_ACCESS_FAULT; > + } > break; > default: > g_assert_not_reached(); > @@ -1415,7 +1450,7 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, > /* Two stage lookup */ > ret = get_physical_address(env, &pa, &prot, address, > &env->guest_phys_fault_addr, access_type, > - mmu_idx, true, true, false); > + mmu_idx, true, true, false, probe); > > /* > * A G-stage exception may be triggered during two state lookup. > @@ -1438,7 +1473,7 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, > > ret = get_physical_address(env, &pa, &prot2, im_address, NULL, > access_type, MMUIdx_U, false, true, > - false); > + false, probe); > > qemu_log_mask(CPU_LOG_MMU, > "%s 2nd-stage address=%" VADDR_PRIx > @@ -1475,7 +1510,8 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, > } else { > /* Single stage lookup */ > ret = get_physical_address(env, &pa, &prot, address, NULL, > - access_type, mmu_idx, true, false, false); > + access_type, mmu_idx, true, false, false, > + probe); > > qemu_log_mask(CPU_LOG_MMU, > "%s address=%" VADDR_PRIx " ret %d physical " > diff --git a/target/riscv/internals.h b/target/riscv/internals.h > index 0ac17bc5ad..ddbdee885b 100644 > --- a/target/riscv/internals.h > +++ b/target/riscv/internals.h > @@ -30,12 +30,15 @@ > * - U+2STAGE 0b100 > * - S+2STAGE 0b101 > * - S+SUM+2STAGE 0b110 > + * - Shadow stack+U 0b1000 > + * - Shadow stack+S 0b1001 > */ > #define MMUIdx_U 0 > #define MMUIdx_S 1 > #define MMUIdx_S_SUM 2 > #define MMUIdx_M 3 > #define MMU_2STAGE_BIT (1 << 2) > +#define MMU_IDX_SS_WRITE (1 << 3) > > static inline int mmuidx_priv(int mmu_idx) > { > -- > 2.45.0 > >
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 93d199748e..8f7871c92b 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -884,7 +884,7 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, target_ulong *fault_pte_addr, int access_type, int mmu_idx, bool first_stage, bool two_stage, - bool is_debug) + bool is_debug, bool is_probe) { /* * NOTE: the env->pc value visible here will not be @@ -898,6 +898,8 @@ static int get_physical_address(CPURISCVState *env, hwaddr *physical, hwaddr ppn; int napot_bits = 0; target_ulong napot_mask; + bool is_sstack_idx = ((mmu_idx & MMU_IDX_SS_WRITE) == MMU_IDX_SS_WRITE); + bool sstack_page = false; /* * Check if we should use the background registers for the two @@ -1028,7 +1030,7 @@ restart: int vbase_ret = get_physical_address(env, &vbase, &vbase_prot, base, NULL, MMU_DATA_LOAD, MMUIdx_U, false, true, - is_debug); + is_debug, false); if (vbase_ret != TRANSLATE_SUCCESS) { if (fault_pte_addr) { @@ -1106,21 +1108,43 @@ restart: return TRANSLATE_FAIL; } + target_ulong rwx = pte & (PTE_R | PTE_W | PTE_X); /* Check for reserved combinations of RWX flags. */ - switch (pte & (PTE_R | PTE_W | PTE_X)) { - case PTE_W: + switch (rwx) { case PTE_W | PTE_X: return TRANSLATE_FAIL; + case PTE_W: + /* if bcfi enabled, PTE_W is not reserved and shadow stack page */ + if (cpu_get_bcfien(env) && first_stage) { + sstack_page = true; + /* + * if ss index, read and write allowed. else if not a probe + * then only read allowed + */ + rwx = is_sstack_idx ? (PTE_R | PTE_W) : (is_probe ? 0 : PTE_R); + break; + } + return TRANSLATE_FAIL; + case PTE_R: + /* + * no matter what's the `access_type`, shadow stack access to readonly + * memory are always store page faults. During unwind, loads will be + * promoted as store fault. + */ + if (is_sstack_idx) { + return TRANSLATE_FAIL; + } + break; } int prot = 0; - if (pte & PTE_R) { + if (rwx & PTE_R) { prot |= PAGE_READ; } - if (pte & PTE_W) { + if (rwx & PTE_W) { prot |= PAGE_WRITE; } - if (pte & PTE_X) { + if (rwx & PTE_X) { bool mxr = false; /* @@ -1164,8 +1188,11 @@ restart: } if (!((prot >> access_type) & 1)) { - /* Access check failed */ - return TRANSLATE_FAIL; + /* + * Access check failed, access check failures for shadow stack are + * access faults. + */ + return sstack_page ? TRANSLATE_PMP_FAIL : TRANSLATE_FAIL; } target_ulong updated_pte = pte; @@ -1303,13 +1330,13 @@ hwaddr riscv_cpu_get_phys_page_debug(CPUState *cs, vaddr addr) int mmu_idx = riscv_env_mmu_index(&cpu->env, false); if (get_physical_address(env, &phys_addr, &prot, addr, NULL, 0, mmu_idx, - true, env->virt_enabled, true)) { + true, env->virt_enabled, true, false)) { return -1; } if (env->virt_enabled) { if (get_physical_address(env, &phys_addr, &prot, phys_addr, NULL, - 0, MMUIdx_U, false, true, true)) { + 0, MMUIdx_U, false, true, true, false)) { return -1; } } @@ -1352,9 +1379,17 @@ void riscv_cpu_do_unaligned_access(CPUState *cs, vaddr addr, break; case MMU_DATA_LOAD: cs->exception_index = RISCV_EXCP_LOAD_ADDR_MIS; + /* shadow stack mis aligned accesses are access faults */ + if (mmu_idx & MMU_IDX_SS_WRITE) { + cs->exception_index = RISCV_EXCP_LOAD_ACCESS_FAULT; + } break; case MMU_DATA_STORE: cs->exception_index = RISCV_EXCP_STORE_AMO_ADDR_MIS; + /* shadow stack mis aligned accesses are access faults */ + if (mmu_idx & MMU_IDX_SS_WRITE) { + cs->exception_index = RISCV_EXCP_STORE_AMO_ACCESS_FAULT; + } break; default: g_assert_not_reached(); @@ -1415,7 +1450,7 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, /* Two stage lookup */ ret = get_physical_address(env, &pa, &prot, address, &env->guest_phys_fault_addr, access_type, - mmu_idx, true, true, false); + mmu_idx, true, true, false, probe); /* * A G-stage exception may be triggered during two state lookup. @@ -1438,7 +1473,7 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, ret = get_physical_address(env, &pa, &prot2, im_address, NULL, access_type, MMUIdx_U, false, true, - false); + false, probe); qemu_log_mask(CPU_LOG_MMU, "%s 2nd-stage address=%" VADDR_PRIx @@ -1475,7 +1510,8 @@ bool riscv_cpu_tlb_fill(CPUState *cs, vaddr address, int size, } else { /* Single stage lookup */ ret = get_physical_address(env, &pa, &prot, address, NULL, - access_type, mmu_idx, true, false, false); + access_type, mmu_idx, true, false, false, + probe); qemu_log_mask(CPU_LOG_MMU, "%s address=%" VADDR_PRIx " ret %d physical " diff --git a/target/riscv/internals.h b/target/riscv/internals.h index 0ac17bc5ad..ddbdee885b 100644 --- a/target/riscv/internals.h +++ b/target/riscv/internals.h @@ -30,12 +30,15 @@ * - U+2STAGE 0b100 * - S+2STAGE 0b101 * - S+SUM+2STAGE 0b110 + * - Shadow stack+U 0b1000 + * - Shadow stack+S 0b1001 */ #define MMUIdx_U 0 #define MMUIdx_S 1 #define MMUIdx_S_SUM 2 #define MMUIdx_M 3 #define MMU_2STAGE_BIT (1 << 2) +#define MMU_IDX_SS_WRITE (1 << 3) static inline int mmuidx_priv(int mmu_idx) {