From 68b8f0e5ce9041052bf0b3026a27e1b9f5b77dd0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 6 Apr 2022 18:44:43 -0400 Subject: [PATCH] riscv: pmp: new stackguard implementation Stackguard uses the PMP to prevents many types of stack overflow by making any access to the bottom stack area raise a CPU exception. Each thread has its set of precomputed PMP entries and those are written to PMP registers at context switch time. This is the code to set it up. It will be connected later. Signed-off-by: Nicolas Pitre --- arch/riscv/core/pmp.c | 90 ++++++++++++++++++++++++++++++ arch/riscv/include/pmp.h | 2 + include/zephyr/arch/riscv/thread.h | 10 ++++ 3 files changed, 102 insertions(+) diff --git a/arch/riscv/core/pmp.c b/arch/riscv/core/pmp.c index dbc52bdca44..c816c10fab8 100644 --- a/arch/riscv/core/pmp.c +++ b/arch/riscv/core/pmp.c @@ -14,6 +14,15 @@ * PMP slot configurations are updated in memory to avoid read-modify-write * cycles on corresponding CSR registers. Relevant CSR registers are always * written in batch from their shadow copy in RAM for better efficiency. + * + * In the stackguard case we keep an m-mode copy for each thread. Each user + * mode threads also has a u-mode copy. This makes faster context switching + * as precomputed content just have to be written to actual registers with + * no additional processing. + * + * Thread-specific m-mode and u-mode PMP entries start from the PMP slot + * indicated by global_pmp_end_index. Lower slots are used by global entries + * which are never modified. */ #include @@ -214,6 +223,15 @@ static void write_pmp_entries(unsigned int start, unsigned int end, pmp_addr, pmp_cfg); } +/** + * @brief Abstract the last 3 arguments to set_pmp_entry() and + * write_pmp_entries( for m-mode. + */ +#define PMP_M_MODE(thread) \ + thread->arch.m_mode_pmpaddr_regs, \ + thread->arch.m_mode_pmpcfg_regs, \ + ARRAY_SIZE(thread->arch.m_mode_pmpaddr_regs) + /* * This is used to seed thread PMP copies with global m-mode cfg entries * sharing the same cfg register. Locked entries aren't modifiable but @@ -239,6 +257,17 @@ void z_riscv_pmp_init(void) (size_t)__rom_region_size, pmp_addr, pmp_cfg, ARRAY_SIZE(pmp_addr)); +#ifdef CONFIG_PMP_STACK_GUARD + /* + * Set the stack guard for this CPU's IRQ stack by making the bottom + * addresses inaccessible. This will never change so we do it here. + */ + set_pmp_entry(&index, PMP_NONE, + (uintptr_t)_current_cpu->irq_stack - CONFIG_ISR_STACK_SIZE, + Z_RISCV_STACK_GUARD_SIZE, + pmp_addr, pmp_cfg, ARRAY_SIZE(pmp_addr)); +#endif + write_pmp_entries(0, index, true, pmp_addr, pmp_cfg, ARRAY_SIZE(pmp_addr)); #ifdef CONFIG_SMP @@ -256,3 +285,64 @@ void z_riscv_pmp_init(void) dump_pmp_regs("initial register dump"); } } + +#ifdef CONFIG_PMP_STACK_GUARD + +/** + * @brief Prepare the PMP stackguard content for given thread. + * + * This is called once during new thread creation. + */ +void z_riscv_pmp_stackguard_prepare(struct k_thread *thread) +{ + unsigned int index = global_pmp_end_index; + uintptr_t stack_bottom = thread->stack_info.start; + + /* Retrieve pmpcfg0 partial content from global entries */ + thread->arch.m_mode_pmpcfg_regs[0] = global_pmp_cfg[0]; + + /* make the bottom addresses of our stack inaccessible */ + set_pmp_entry(&index, PMP_NONE, + stack_bottom, Z_RISCV_STACK_GUARD_SIZE, + PMP_M_MODE(thread)); + + /* + * We'll be using MPRV. Make a fallback entry with everything + * accessible as if no PMP entries were matched which is otherwise + * the default behavior for m-mode without MPRV. + */ + set_pmp_entry(&index, PMP_R | PMP_W | PMP_X, + 0, 0, PMP_M_MODE(thread)); + + /* remember how many entries we use */ + thread->arch.m_mode_pmp_end_index = index; +} + +/** + * @brief Write PMP stackguard content to actual PMP registers + * + * This is called on every context switch. + */ +void z_riscv_pmp_stackguard_enable(struct k_thread *thread) +{ + /* + * Disable (non-locked) PMP entries for m-mode while we update them. + * While at it, also clear MSTATUS_MPP as it must be cleared for + * MSTATUS_MPRV to be effective later. + */ + csr_clear(mstatus, MSTATUS_MPRV | MSTATUS_MPP); + + /* Write our m-mode MPP entries */ + write_pmp_entries(global_pmp_end_index, thread->arch.m_mode_pmp_end_index, + false /* no need to clear to the end */, + PMP_M_MODE(thread)); + + if (PMP_DEBUG_DUMP) { + dump_pmp_regs("m-mode register dump"); + } + + /* Activate our non-locked PMP entries in m-mode */ + csr_set(mstatus, MSTATUS_MPRV); +} + +#endif /* CONFIG_PMP_STACK_GUARD */ diff --git a/arch/riscv/include/pmp.h b/arch/riscv/include/pmp.h index 0cc899efe30..5737f4a3589 100644 --- a/arch/riscv/include/pmp.h +++ b/arch/riscv/include/pmp.h @@ -8,5 +8,7 @@ #define PMP_H_ void z_riscv_pmp_init(void); +void z_riscv_pmp_stackguard_prepare(struct k_thread *thread); +void z_riscv_pmp_stackguard_enable(struct k_thread *thread); #endif /* PMP_H_ */ diff --git a/include/zephyr/arch/riscv/thread.h b/include/zephyr/arch/riscv/thread.h index 62a7c98a093..2d53137e0fa 100644 --- a/include/zephyr/arch/riscv/thread.h +++ b/include/zephyr/arch/riscv/thread.h @@ -90,7 +90,17 @@ struct _callee_saved { }; typedef struct _callee_saved _callee_saved_t; +#define PMP_M_MODE_SLOTS 8 /* 8 is plenty enough for m-mode */ + struct _thread_arch { +#ifdef CONFIG_PMP_STACK_GUARD + unsigned int m_mode_pmp_end_index; + ulong_t m_mode_pmpaddr_regs[PMP_M_MODE_SLOTS]; + ulong_t m_mode_pmpcfg_regs[PMP_M_MODE_SLOTS / sizeof(ulong_t)]; +#endif + + /* legacy stuff below */ + #ifdef CONFIG_PMP_STACK_GUARD ulong_t s_pmpcfg[PMP_CFG_CSR_NUM_FOR_STACK_GUARD]; ulong_t s_pmpaddr[PMP_REGION_NUM_FOR_STACK_GUARD];