diff --git a/Makefile.dep b/Makefile.dep index d8a1074c6d..43e358b99d 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -33,6 +33,10 @@ ifneq (,$(filter mpu_noexec_ram,$(USEMODULE))) FEATURES_REQUIRED += cortexm_mpu endif +ifneq (,$(filter pmp_noexec_ram,$(USEMODULE))) + FEATURES_REQUIRED += periph_pmp +endif + ifneq (,$(filter lwip_%,$(USEMODULE))) USEPKG += lwip endif diff --git a/core/lib/include/panic.h b/core/lib/include/panic.h index 51854b5160..2fd453805f 100644 --- a/core/lib/include/panic.h +++ b/core/lib/include/panic.h @@ -37,13 +37,13 @@ typedef enum { PANIC_HARD_REBOOT, PANIC_ASSERT_FAIL, PANIC_EXPECT_FAIL, + PANIC_MEM_MANAGE, /**< memory management fault */ #ifdef MODULE_CORTEXM_COMMON PANIC_NMI_HANDLER, /**< non maskable interrupt */ PANIC_HARD_FAULT, /**< hard fault */ #if defined(CPU_CORE_CORTEX_M3) || defined(CPU_CORE_CORTEX_M33) || \ defined(CPU_CORE_CORTEX_M4) || defined(CPU_CORE_CORTEX_M4F) || \ defined(CPU_CORE_CORTEX_M7) - PANIC_MEM_MANAGE, /**< memory controller interrupt */ PANIC_BUS_FAULT, /**< bus fault */ PANIC_USAGE_FAULT, /**< undefined instruction or unaligned access */ PANIC_DEBUG_MON, /**< debug interrupt */ diff --git a/cpu/fe310/Kconfig b/cpu/fe310/Kconfig index 25c61c6c64..c8e3a02f37 100644 --- a/cpu/fe310/Kconfig +++ b/cpu/fe310/Kconfig @@ -13,6 +13,7 @@ config CPU_FAM_FE310 select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ select HAS_PERIPH_PM + select HAS_PERIPH_PMP select HAS_PERIPH_PLIC select HAS_PERIPH_RTT_OVERFLOW select HAS_PERIPH_RTT_SET_COUNTER diff --git a/cpu/fe310/Makefile.features b/cpu/fe310/Makefile.features index 9850ba39b4..905b891a0b 100644 --- a/cpu/fe310/Makefile.features +++ b/cpu/fe310/Makefile.features @@ -3,6 +3,7 @@ CPU_CORE := rv32imac FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_pm +FEATURES_PROVIDED += periph_pmp FEATURES_PROVIDED += periph_plic FEATURES_PROVIDED += periph_rtt_overflow FEATURES_PROVIDED += periph_rtt_set_counter diff --git a/cpu/fe310/include/cpu_conf.h b/cpu/fe310/include/cpu_conf.h index 0138911993..f510fbcc58 100644 --- a/cpu/fe310/include/cpu_conf.h +++ b/cpu/fe310/include/cpu_conf.h @@ -36,6 +36,12 @@ extern "C" { */ #define PLIC_BASE_ADDR (PLIC_CTRL_ADDR) +/** + * @brief Number of available PMP regions + * Note, the upper 8 regions are hardwired to zero! + */ +#define NUM_PMP_ENTRIES 16 + #ifdef __cplusplus } #endif diff --git a/cpu/riscv_common/include/pmp.h b/cpu/riscv_common/include/pmp.h new file mode 100644 index 0000000000..31e8f147c9 --- /dev/null +++ b/cpu/riscv_common/include/pmp.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 Bennet Blischke + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_riscv_common + * @{ + * + * @file + * @brief RISC-V PMP configuration options + * + * RISCV implementations using this peripheral must define the `NUM_PMP_ENTRIES` + * `NUM_PMP_ENTRIES` must be 16 or 64. + * + * @author Bennet Blischke + */ + +#ifndef PMP_H +#define PMP_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Bit masks for the PMP configuration register + * @{ + */ +#define PMP_NONE 0x00 /**< No access allowed at all */ +#define PMP_R 0x01 /**< Allow read access */ +#define PMP_W 0x02 /**< Allow write access */ +#define PMP_X 0x04 /**< Allow execution */ + +#define PMP_A 0x18 /**< Addressing mode mask */ +#define PMP_OFF 0x00 /**< Disable this pmp entry */ +#define PMP_TOR 0x08 /**< Top-of-range addressing mode */ +#define PMP_NA4 0x10 /**< Naturally aligned four-byte region */ +#define PMP_NAPOT 0x18 /**< Naturally aligned power-of-two region, ≥8 bytes */ + +#define PMP_L 0x80 /**< Lock; read-only config & applies to machine-mode */ +/** @} */ + +/** + * @brief Create a NAPOT formatted address + * + * @param addr Base address, must be aligned to the size of the region + * @param size Size of the region in bytes + */ +static inline uint32_t make_napot(uint32_t addr, uint32_t size) +{ + assert(addr % size == 0); + return addr | ((size - 1) >> 1); +} + +/** + * @brief Writes a complete pmpcfg register + * + * @param reg_num Register number + * @param value Value to write + */ +void write_pmpcfg(uint8_t reg_num, uint32_t value); + +/** + * @brief Read a complete pmpcfg register + * + * @param[in] reg_num Register number + * + * @return Contents of the specified register + */ +uint32_t read_pmpcfg(uint8_t reg_num); + +/** + * @brief Writes a complete pmpaddr register + * + * @param reg_num Register number + * @param value Value to write + */ +void write_pmpaddr(uint8_t reg_num, uint32_t value); + +/** + * @brief Read a complete pmpaddr register + * + * @param[in] reg_num Register number + * + * @return Contents of the specified register + */ +uint32_t read_pmpaddr(uint8_t reg_num); + +/** + * @brief Read a single pmpcfg sub-register + * + * @param[in] entry Sub-register number + * + * @return Contents of the specified sub-register + */ +uint8_t get_pmpcfg(uint8_t entry); + +/** + * @brief Set's a single pmpcfg sub-register + * + * @param entry Sub-register number + * @param value Value to write + */ +void set_pmpcfg(uint8_t entry, uint8_t value); + +/** + * @brief Prints a single pmpcfg sub-register human readable + * + * @param entry Register number to print + */ +void print_pmpcfg(uint8_t entry); + +#ifdef __cplusplus +} +#endif + +#endif /* PMP_H */ +/** @} */ diff --git a/cpu/riscv_common/include/vendor/riscv_csr.h b/cpu/riscv_common/include/vendor/riscv_csr.h index 16c0bf4b73..e7f068c978 100644 --- a/cpu/riscv_common/include/vendor/riscv_csr.h +++ b/cpu/riscv_common/include/vendor/riscv_csr.h @@ -171,6 +171,9 @@ #define PTE_TABLE(PTE) (((PTE) & (PTE_V | PTE_R | PTE_W | PTE_X)) == PTE_V) +#define CSR_PMPCFG0 0x3a0 // PMP configuration base register +#define CSR_PMPADDR0 0x3b0 // PMP address base register + #ifdef __riscv #ifdef __riscv64 diff --git a/cpu/riscv_common/irq_arch.c b/cpu/riscv_common/irq_arch.c index 41782d5877..ebd530f44b 100644 --- a/cpu/riscv_common/irq_arch.c +++ b/cpu/riscv_common/irq_arch.c @@ -134,6 +134,14 @@ static void handle_trap(uword_t mcause) write_csr(mepc, return_pc + 4); break; } +#ifdef MODULE_PERIPH_PMP + case CAUSE_FAULT_FETCH: + core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (fetch)"); + case CAUSE_FAULT_LOAD: + core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (load)"); + case CAUSE_FAULT_STORE: + core_panic(PANIC_MEM_MANAGE, "MEM MANAGE HANDLER (store)"); +#endif default: #ifdef DEVELHELP printf("Unhandled trap:\n"); diff --git a/cpu/riscv_common/periph/Kconfig b/cpu/riscv_common/periph/Kconfig index 1718db641b..5e80d2a1c7 100644 --- a/cpu/riscv_common/periph/Kconfig +++ b/cpu/riscv_common/periph/Kconfig @@ -32,4 +32,10 @@ config MODULE_PERIPH_PLIC help Platform-Level interrupt controller driver. +config MODULE_PERIPH_PMP + bool + depends on HAS_PERIPH_PMP + help + Physical memory protection driver. + endif # MODULE_RISCV_COMMON_PERIPH diff --git a/cpu/riscv_common/periph/pmp.c b/cpu/riscv_common/periph/pmp.c new file mode 100644 index 0000000000..3df14bc63e --- /dev/null +++ b/cpu/riscv_common/periph/pmp.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2023 Bennet Blischke + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ +/** + * @ingroup cpu_riscv_common + * @{ + * + * @file + * @brief RISCV PMP implementation + * + * @author Bennet Blischke + */ + +#include +#include +#include + +#include "cpu_conf.h" +#include "vendor/riscv_csr.h" +#include "pmp.h" + +#if NUM_PMP_ENTRIES != 16 && NUM_PMP_ENTRIES != 64 +#error "Only 16 or 64 PMP entries allowed." +#endif + +#define _WRITE_PMPCFG(REG, VALUE) write_csr(REG, VALUE) +#define _READ_PMPCFG(REG) read_csr(REG) +#define WRITE_PMPCFG(REG, VALUE) _WRITE_PMPCFG(CSR_PMPCFG0 + REG, VALUE) +#define READ_PMPCFG(REG) _READ_PMPCFG(CSR_PMPCFG0 + REG) + +/* These shifts are needed as the PMP Address registers stores + * 34 Bit addresses with 4 byte alignment in a 32 bit register + */ +#define _WRITE_PMPADDR(REG, VALUE) write_csr(REG, VALUE >> 2) +#define _READ_PMPADDR(REG) (read_csr(REG) << 2) +#define WRITE_PMPADDR(REG, VALUE) _WRITE_PMPADDR(CSR_PMPADDR0 + REG, VALUE) +#define READ_PMPADDR(REG) _READ_PMPADDR(CSR_PMPADDR0 + REG) + +#define _NAPOT_base(addr) (addr & (addr + 1)) +#define _NAPOT_end(addr) (addr | (addr + 1)) + +/* The assembly instructions for reading riscv CSRs, encode the CSR-address + * in the immediate field which is not changeable at run-time. + * Hence it is needed to generate all possible register accesses at build-time. + * Settled for switch-case as they are easiest to read, understand and maintain. + */ +void write_pmpcfg(uint8_t reg_num, uint32_t value) +{ + assert(reg_num < NUM_PMP_ENTRIES / 4); + switch (reg_num) { + case 0: WRITE_PMPCFG(0, value); break; + case 1: WRITE_PMPCFG(1, value); break; + case 2: WRITE_PMPCFG(2, value); break; + case 3: WRITE_PMPCFG(3, value); break; +#if NUM_PMP_ENTRIES == 64 + case 4: WRITE_PMPCFG(4, value); break; + case 5: WRITE_PMPCFG(5, value); break; + case 6: WRITE_PMPCFG(6, value); break; + case 7: WRITE_PMPCFG(7, value); break; + case 8: WRITE_PMPCFG(8, value); break; + case 9: WRITE_PMPCFG(9, value); break; + case 10: WRITE_PMPCFG(10, value); break; + case 11: WRITE_PMPCFG(11, value); break; + case 12: WRITE_PMPCFG(12, value); break; + case 13: WRITE_PMPCFG(13, value); break; + case 14: WRITE_PMPCFG(14, value); break; + case 15: WRITE_PMPCFG(15, value); break; +#endif + } +} + +uint32_t read_pmpcfg(uint8_t reg_num) +{ + assert(reg_num < NUM_PMP_ENTRIES / 4); + switch (reg_num) { + case 0: return READ_PMPCFG(0); + case 1: return READ_PMPCFG(1); + case 2: return READ_PMPCFG(2); + case 3: return READ_PMPCFG(3); +#if NUM_PMP_ENTRIES == 64 + case 4: return READ_PMPCFG(4); + case 5: return READ_PMPCFG(5); + case 6: return READ_PMPCFG(6); + case 7: return READ_PMPCFG(7); + case 8: return READ_PMPCFG(8); + case 9: return READ_PMPCFG(9); + case 10: return READ_PMPCFG(10); + case 11: return READ_PMPCFG(11); + case 12: return READ_PMPCFG(12); + case 13: return READ_PMPCFG(13); + case 14: return READ_PMPCFG(14); + case 15: return READ_PMPCFG(15); +#endif + } + return 0; +} + +void write_pmpaddr(uint8_t reg_num, uint32_t value) +{ + assert(reg_num < NUM_PMP_ENTRIES); + switch (reg_num) { + case 0: WRITE_PMPADDR(0, value); break; + case 1: WRITE_PMPADDR(1, value); break; + case 2: WRITE_PMPADDR(2, value); break; + case 3: WRITE_PMPADDR(3, value); break; + case 4: WRITE_PMPADDR(4, value); break; + case 5: WRITE_PMPADDR(5, value); break; + case 6: WRITE_PMPADDR(6, value); break; + case 7: WRITE_PMPADDR(7, value); break; + case 8: WRITE_PMPADDR(8, value); break; + case 9: WRITE_PMPADDR(9, value); break; + case 10: WRITE_PMPADDR(10, value); break; + case 11: WRITE_PMPADDR(11, value); break; + case 12: WRITE_PMPADDR(12, value); break; + case 13: WRITE_PMPADDR(13, value); break; + case 14: WRITE_PMPADDR(14, value); break; + case 15: WRITE_PMPADDR(15, value); break; +#if NUM_PMP_ENTRIES == 64 + case 16: WRITE_PMPADDR(16, value); break; + case 17: WRITE_PMPADDR(17, value); break; + case 18: WRITE_PMPADDR(18, value); break; + case 19: WRITE_PMPADDR(19, value); break; + case 20: WRITE_PMPADDR(20, value); break; + case 21: WRITE_PMPADDR(21, value); break; + case 22: WRITE_PMPADDR(22, value); break; + case 23: WRITE_PMPADDR(23, value); break; + case 24: WRITE_PMPADDR(24, value); break; + case 25: WRITE_PMPADDR(25, value); break; + case 26: WRITE_PMPADDR(26, value); break; + case 27: WRITE_PMPADDR(27, value); break; + case 28: WRITE_PMPADDR(28, value); break; + case 29: WRITE_PMPADDR(29, value); break; + case 30: WRITE_PMPADDR(30, value); break; + case 31: WRITE_PMPADDR(31, value); break; + case 32: WRITE_PMPADDR(32, value); break; + case 33: WRITE_PMPADDR(33, value); break; + case 34: WRITE_PMPADDR(34, value); break; + case 35: WRITE_PMPADDR(35, value); break; + case 36: WRITE_PMPADDR(36, value); break; + case 37: WRITE_PMPADDR(37, value); break; + case 38: WRITE_PMPADDR(38, value); break; + case 39: WRITE_PMPADDR(39, value); break; + case 40: WRITE_PMPADDR(40, value); break; + case 41: WRITE_PMPADDR(41, value); break; + case 42: WRITE_PMPADDR(42, value); break; + case 43: WRITE_PMPADDR(43, value); break; + case 44: WRITE_PMPADDR(44, value); break; + case 45: WRITE_PMPADDR(45, value); break; + case 46: WRITE_PMPADDR(46, value); break; + case 47: WRITE_PMPADDR(47, value); break; + case 48: WRITE_PMPADDR(48, value); break; + case 49: WRITE_PMPADDR(49, value); break; + case 50: WRITE_PMPADDR(50, value); break; + case 51: WRITE_PMPADDR(51, value); break; + case 52: WRITE_PMPADDR(52, value); break; + case 53: WRITE_PMPADDR(53, value); break; + case 54: WRITE_PMPADDR(54, value); break; + case 55: WRITE_PMPADDR(55, value); break; + case 56: WRITE_PMPADDR(56, value); break; + case 57: WRITE_PMPADDR(57, value); break; + case 58: WRITE_PMPADDR(58, value); break; + case 59: WRITE_PMPADDR(59, value); break; + case 60: WRITE_PMPADDR(60, value); break; + case 61: WRITE_PMPADDR(61, value); break; + case 62: WRITE_PMPADDR(62, value); break; + case 63: WRITE_PMPADDR(63, value); break; +#endif + } +} + +uint32_t read_pmpaddr(uint8_t reg_num) +{ + assert(reg_num < NUM_PMP_ENTRIES); + switch (reg_num) { + case 0: return READ_PMPADDR(0); + case 1: return READ_PMPADDR(1); + case 2: return READ_PMPADDR(2); + case 3: return READ_PMPADDR(3); + case 4: return READ_PMPADDR(4); + case 5: return READ_PMPADDR(5); + case 6: return READ_PMPADDR(6); + case 7: return READ_PMPADDR(7); + case 8: return READ_PMPADDR(8); + case 9: return READ_PMPADDR(9); + case 10: return READ_PMPADDR(10); + case 11: return READ_PMPADDR(11); + case 12: return READ_PMPADDR(12); + case 13: return READ_PMPADDR(13); + case 14: return READ_PMPADDR(14); + case 15: return READ_PMPADDR(15); +#if NUM_PMP_ENTRIES == 64 + case 16: return READ_PMPADDR(16); + case 17: return READ_PMPADDR(17); + case 18: return READ_PMPADDR(18); + case 19: return READ_PMPADDR(19); + case 20: return READ_PMPADDR(20); + case 21: return READ_PMPADDR(21); + case 22: return READ_PMPADDR(22); + case 23: return READ_PMPADDR(23); + case 24: return READ_PMPADDR(24); + case 25: return READ_PMPADDR(25); + case 26: return READ_PMPADDR(26); + case 27: return READ_PMPADDR(27); + case 28: return READ_PMPADDR(28); + case 29: return READ_PMPADDR(29); + case 30: return READ_PMPADDR(30); + case 31: return READ_PMPADDR(31); + case 32: return READ_PMPADDR(32); + case 33: return READ_PMPADDR(33); + case 34: return READ_PMPADDR(34); + case 35: return READ_PMPADDR(35); + case 36: return READ_PMPADDR(36); + case 37: return READ_PMPADDR(37); + case 38: return READ_PMPADDR(38); + case 39: return READ_PMPADDR(39); + case 40: return READ_PMPADDR(40); + case 41: return READ_PMPADDR(41); + case 42: return READ_PMPADDR(42); + case 43: return READ_PMPADDR(43); + case 44: return READ_PMPADDR(44); + case 45: return READ_PMPADDR(45); + case 46: return READ_PMPADDR(46); + case 47: return READ_PMPADDR(47); + case 48: return READ_PMPADDR(48); + case 49: return READ_PMPADDR(49); + case 50: return READ_PMPADDR(50); + case 51: return READ_PMPADDR(51); + case 52: return READ_PMPADDR(52); + case 53: return READ_PMPADDR(53); + case 54: return READ_PMPADDR(54); + case 55: return READ_PMPADDR(55); + case 56: return READ_PMPADDR(56); + case 57: return READ_PMPADDR(57); + case 58: return READ_PMPADDR(58); + case 59: return READ_PMPADDR(59); + case 60: return READ_PMPADDR(60); + case 61: return READ_PMPADDR(61); + case 62: return READ_PMPADDR(62); + case 63: return READ_PMPADDR(63); +#endif + } + return 0; +} + +uint8_t get_pmpcfg(uint8_t entry) +{ + return (read_pmpcfg(entry / 4) >> (entry % 4) * 8) & 0xFF; +} + +void set_pmpcfg(uint8_t entry, uint8_t value) +{ + uint32_t cur_value = read_pmpcfg(entry / 4); + uint32_t mask = 0xff << (entry % 4) * 8; + + cur_value &= ~mask; + cur_value |= value << (entry % 4) * 8; + write_pmpcfg(entry / 4, cur_value); +} + +void print_pmpcfg(uint8_t entry) +{ + uint8_t cfg = get_pmpcfg(entry); + uint32_t start = 0; + uint32_t stop = 0; + char *mode = "OFF "; + + switch (cfg & PMP_A) { + case PMP_TOR: + mode = "TOR "; + start = (entry > 0) ? read_pmpaddr(entry - 1) : 0; + stop = read_pmpaddr(entry); + break; + case PMP_NA4: + mode = "NA4 "; + start = read_pmpaddr(entry); + stop = start + 3; + break; + case PMP_NAPOT: + mode = "NAPOT"; + /* Flipp last two bits in NAPOT mode */ + uint32_t _tmp = read_pmpaddr(entry) | 0x03; + start = _NAPOT_base(_tmp); + stop = _NAPOT_end(_tmp); + break; + } + printf("pmp%02dcfg: %c %c%c%c %s 0x%08x - 0x%08x\n", + entry, + (cfg & PMP_L) ? 'L' : '-', + (cfg & PMP_R) ? 'R' : '-', + (cfg & PMP_W) ? 'W' : '-', + (cfg & PMP_X) ? 'X' : '-', + mode, + (unsigned int)start, + (unsigned int)stop + ); +} diff --git a/cpu/riscv_common/riscv_init.c b/cpu/riscv_common/riscv_init.c index 149a0069c1..d3a4f379e4 100644 --- a/cpu/riscv_common/riscv_init.c +++ b/cpu/riscv_common/riscv_init.c @@ -21,6 +21,10 @@ #include "cpu_conf_common.h" #include "periph_cpu_common.h" +#ifdef MODULE_PMP_NOEXEC_RAM +#include "pmp.h" +#endif + #ifdef MODULE_PUF_SRAM #include "puf_sram.h" @@ -45,4 +49,13 @@ void riscv_init(void) { riscv_fpu_init(); riscv_irq_init(); +#ifdef MODULE_PMP_NOEXEC_RAM + /* This marks the (main) RAM region as non + * executable. Using PMP entry 0. + */ + write_pmpaddr(0, make_napot(CPU_RAM_BASE, CPU_RAM_SIZE)); + + /* Lock & select NAPOT, only allow write and read */ + set_pmpcfg(0, PMP_L | PMP_NAPOT | PMP_W | PMP_R); +#endif } diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 557406b58c..11dfc78189 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -288,6 +288,11 @@ config HAS_PERIPH_PLIC help Indicates that a RISC-V Platform-local Interrupt Controller (PLIC) peripheral is present. +config HAS_PERIPH_PMP + bool + help + Indicates that a RISC-V physical memory protection (PMP) peripheral is present. + config HAS_PERIPH_PM bool help diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 8d0ecdb504..cd438327ed 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -320,6 +320,15 @@ PSEUDOMODULES += mpu_stack_guard PSEUDOMODULES += mpu_noexec_ram ## @} +## @defgroup pseudomodule_pmp_noexec_ram pmp_noexec_ram +## @{ +## @brief Mark RAM as non-executable using the PMP +## +## Mark the RAM non executable. +## This is a protection mechanism which makes exploitation of buffer overflows significantly harder. +PSEUDOMODULES += pmp_noexec_ram +## @} + ## @defgroup pseudomodule_md5sum md5sum ## @ingroup sys_shell_commands ## @{ diff --git a/tests/cpu/pmp_noexec_ram/Makefile b/tests/cpu/pmp_noexec_ram/Makefile new file mode 100644 index 0000000000..a98d3a00a3 --- /dev/null +++ b/tests/cpu/pmp_noexec_ram/Makefile @@ -0,0 +1,7 @@ +BOARD ?= hifive1b + +include ../Makefile.cpu_common + +USEMODULE += pmp_noexec_ram + +include $(RIOTBASE)/Makefile.include diff --git a/tests/cpu/pmp_noexec_ram/README.md b/tests/cpu/pmp_noexec_ram/README.md new file mode 100644 index 0000000000..d42df319a1 --- /dev/null +++ b/tests/cpu/pmp_noexec_ram/README.md @@ -0,0 +1,11 @@ +# mpu_noexec_ram + +Tests for the `pmp_noexec_ram` pseudomodule. +Only supported on RISC-V devices with PMP. + +## Output + +With `USEMODULE += pmp_noexec_ram` in `Makefile` this application should +execute a kernel panic, stating `Instruction access fault` (0x01) in the +`mcause` register. Without this pseudomodule activated, the hard fault +will be triggered by `Illegal instruction` (0x02) instead. diff --git a/tests/cpu/pmp_noexec_ram/main.c b/tests/cpu/pmp_noexec_ram/main.c new file mode 100644 index 0000000000..8b6c705146 --- /dev/null +++ b/tests/cpu/pmp_noexec_ram/main.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Bennet Blischke + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the pmp_noexec_ram pseudo-module + * + * @author Sören Tempel + * @author Bennet Blischke + * + * @} + */ + +#include +#include + +#include "cpu.h" +#include "pmp.h" + +#define JMPBUF_SIZE 3 + +int main(void) +{ + uint32_t buf[JMPBUF_SIZE]; + + /* Fill the buffer with invalid instructions */ + for (unsigned i = 0; i < JMPBUF_SIZE; i++) { + buf[i] = UINT32_MAX; + } + + puts("Attempting to jump to stack buffer ...\n"); + __asm__ volatile ("jr %0" :: "r" ((uint8_t*)&buf)); + + return 0; +} diff --git a/tests/cpu/pmp_noexec_ram/tests/01-run.py b/tests/cpu/pmp_noexec_ram/tests/01-run.py new file mode 100755 index 0000000000..8e644f6eb0 --- /dev/null +++ b/tests/cpu/pmp_noexec_ram/tests/01-run.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2020 Sören Tempel +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("MEM MANAGE HANDLER (fetch)\r\n") + + +if __name__ == "__main__": + sys.exit(run(testfunc, timeout=10))