esp32c3: memprot API upgrade and test application

Closes IDF-2641
This commit is contained in:
Martin Vychodil
2021-01-27 22:03:07 +01:00
committed by Angus Gratton
parent 2ed3e8b344
commit 6dfff2fdbd
23 changed files with 1464 additions and 311 deletions

View File

@@ -0,0 +1,71 @@
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riscv/rvruntime-frames.h"
/* The riscv panic handler in components/riscv/vectors.S doesn't allow the panic
handler function to return.
However, for the purposes of this test we want to allow the panic handler to return.
There is functionality in vectors.S restore the CPU state, but it only
restores when CONTEXT_SIZE registers are
pushed onto the stack not RV_STK_FRMSZ registers
Instead of messing with that, implement a full "restore from RvExcFrame"
function here which restores the CPU and then
returns from exception.
Called as return_from_panic_handler(RvExcFrame *frame)
*/
.global return_from_panic_handler
return_from_panic_handler:
or t0, a0, a0 /* use t0 as the working register */
/* save general registers */
lw ra, RV_STK_RA(t0)
lw sp, RV_STK_SP(t0)
lw gp, RV_STK_GP(t0)
lw tp, RV_STK_TP(t0)
lw s0, RV_STK_S0(t0)
lw s1, RV_STK_S1(t0)
lw a0, RV_STK_A0(t0)
lw a1, RV_STK_A1(t0)
lw a2, RV_STK_A2(t0)
lw a3, RV_STK_A3(t0)
lw a4, RV_STK_A4(t0)
lw a5, RV_STK_A5(t0)
lw a6, RV_STK_A6(t0)
lw a7, RV_STK_A7(t0)
lw s2, RV_STK_S2(t0)
lw s3, RV_STK_S3(t0)
lw s4, RV_STK_S4(t0)
lw s5, RV_STK_S5(t0)
lw s6, RV_STK_S6(t0)
lw s7, RV_STK_S7(t0)
lw s8, RV_STK_S8(t0)
lw s9, RV_STK_S9(t0)
lw s10, RV_STK_S10(t0)
lw s11, RV_STK_S11(t0)
lw t3, RV_STK_T3(t0)
lw t4, RV_STK_T4(t0)
lw t5, RV_STK_T5(t0)
lw t6, RV_STK_T6(t0)
lw t2, RV_STK_MEPC(t0)
csrw mepc, t2
lw t1, RV_STK_T1(t0)
lw t2, RV_STK_T2(t0)
lw t0, RV_STK_T0(t0)
mret

View File

@@ -0,0 +1,392 @@
/* MEMPROT (PMS) testing code */
#include <stdio.h>
#include "sdkconfig.h"
#include "esp32c3/memprot.h"
#include "esp_rom_sys.h"
#include <string.h>
/**
* ESP32C3 MEMORY PROTECTION MODULE TEST
* =====================================
*
* In order to safely test all the memprot features, this test application uses memprot default settings
* plus proprietary testing buffers:
* - iram_test_buffer (.iram_end_test, 1kB) - all low region operations
* - dram_test_buffer (.dram0.data, 1kB) - all high region operations
* Testing addresses are set to the middle of the testing buffers:
* - test_ptr_low = iram_test_buffer + 0x200
* - test_ptr_high = dram_test_buffer + 0x200
* Each operation is tested at both low & high region addresses.
* Each test result checked against expected status of PMS violation interrupt status and
* against expected value stored in the memory tested (where applicable)
*
* Testing scheme is depicted below:
*
* DRam0/DMA IRam0
* -----------------------------------------------
* | IRam0_PMS_0 = IRam0_PMS_1 = IRam0_PMS_2 |
* | DRam0_PMS_0 |
* | |
* | |
* | - - - - - - - iram_test_buffer - - - - - - -| IRam0_line1_Split_addr
* DRam0_DMA_line0_Split_addr | -- test_ptr_low -- | =
* = =============================================== IRam0_line0_Split_addr
* DRam0_DMA_line1_Split_addr | | =
* | - - - - - - - dram_test_buffer - - - - - - -| IRam0_DRam0_Split_addr (main I/D)
* | -- test_ptr_high -- |
* | - - - - - - - - - - - - - - - - - - - - - - |
* | |
* | DRam0_PMS_1 = DRam0_PMS_2 = DRam0_PMS_3 |
* | IRam0_PMS_3 |
* -----------------------------------------------
*
* For more details on PMS memprot settings see 'esp_memprot_set_prot_int' function in esp32c3/memprot.h
*/
/* Binary code for the following asm [int func(int x) { return x+x; }]
slli a0,a0,0x1
ret
*/
static uint8_t fnc_buff[] = { 0x06, 0x05, 0x82, 0x80 };
typedef int (*fnc_ptr)(int);
#define SRAM_TEST_BUFFER_SIZE 0x400
#define SRAM_TEST_OFFSET 0x200
static uint8_t __attribute__((section(".iram_end_test"))) iram_test_buffer[SRAM_TEST_BUFFER_SIZE] = {0};
static uint8_t dram_test_buffer[SRAM_TEST_BUFFER_SIZE] = {0};
extern volatile bool g_override_illegal_instruction;
static void *test_memprot_addr_low(mem_type_prot_t mem_type)
{
switch ( mem_type ) {
case MEMPROT_IRAM0_SRAM:
return (void*)((uint32_t)iram_test_buffer + SRAM_TEST_OFFSET);
case MEMPROT_DRAM0_SRAM:
return (void*)MAP_IRAM_TO_DRAM((uint32_t)iram_test_buffer + SRAM_TEST_OFFSET);
default:
abort();
}
}
static void *test_memprot_addr_high(mem_type_prot_t mem_type)
{
switch ( mem_type ) {
case MEMPROT_IRAM0_SRAM:
return (void*)MAP_DRAM_TO_IRAM((uint32_t)dram_test_buffer + SRAM_TEST_OFFSET);
case MEMPROT_DRAM0_SRAM:
return (void*)((uint32_t)dram_test_buffer + SRAM_TEST_OFFSET);
default:
abort();
}
}
static void __attribute__((unused)) dump_status_register(mem_type_prot_t mem_type)
{
char operation = 0;
// IRAM fault: check instruction-fetch flag
if ( mem_type == MEMPROT_IRAM0_SRAM ) {
if ( esp_memprot_get_violate_loadstore(mem_type) ) {
operation = 'X';
}
}
// W/R - common
if ( operation == 0 ) {
operation = esp_memprot_get_violate_wr(mem_type) == MEMPROT_PMS_OP_WRITE ? 'W' : 'R';
}
esp_rom_printf(
" FAULT [ world: %u, fault addr: 0x%08X, operation: %c",
esp_memprot_get_violate_world(mem_type),
esp_memprot_get_violate_addr(mem_type),
operation
);
// DRAM/DMA fault: check byte-enables
if ( mem_type == MEMPROT_DRAM0_SRAM ) {
esp_rom_printf( ", byte en: 0x%08X", esp_memprot_get_violate_byte_en(mem_type) );
}
esp_rom_printf( " ]\n" );
}
static void check_test_result(mem_type_prot_t mem_type, bool expected_status)
{
bool test_result =
expected_status ?
!esp_memprot_get_violate_intr_on(mem_type) :
esp_memprot_get_violate_intr_on(mem_type);
if ( test_result ) {
esp_rom_printf("OK\n");
} else {
dump_status_register(mem_type);
}
}
static void test_memprot_get_permissions(bool low, mem_type_prot_t mem_type, bool *read, bool *write, bool *exec )
{
bool _r, _w, _x;
pms_area_t area = MEMPROT_PMS_AREA_NONE;
switch ( mem_type ) {
case MEMPROT_IRAM0_SRAM:
area = low ? MEMPROT_IRAM0_PMS_AREA_2 : MEMPROT_IRAM0_PMS_AREA_3;
esp_memprot_iram_get_pms_area(area, &_r, &_w, &_x);
break;
case MEMPROT_DRAM0_SRAM:
area = low ? MEMPROT_DRAM0_PMS_AREA_0 : MEMPROT_DRAM0_PMS_AREA_1;
esp_memprot_dram_get_pms_area(area, &_r, &_w);
break;
default:
abort();
}
if ( read ) {
*read = _r;
}
if ( write ) {
*write = _w;
}
if ( exec ) {
*exec = _x;
}
}
static void test_memprot_set_permissions(bool low, mem_type_prot_t mem_type, bool read, bool write, bool *exec)
{
switch ( mem_type ) {
case MEMPROT_IRAM0_SRAM: {
bool _ex;
if (!exec) {
test_memprot_get_permissions( low, mem_type, NULL, NULL, &_ex);
exec = &_ex;
}
if (low) {
esp_memprot_iram_set_pms_area(MEMPROT_IRAM0_PMS_AREA_0, read, write, *exec);
esp_memprot_iram_set_pms_area(MEMPROT_IRAM0_PMS_AREA_1, read, write, *exec);
esp_memprot_iram_set_pms_area(MEMPROT_IRAM0_PMS_AREA_2, read, write, *exec);
} else {
esp_memprot_iram_set_pms_area(MEMPROT_IRAM0_PMS_AREA_3, read, write, *exec);
}
}
break;
case MEMPROT_DRAM0_SRAM: {
if (low) {
esp_memprot_dram_set_pms_area( MEMPROT_DRAM0_PMS_AREA_0, read, write );
} else {
esp_memprot_dram_set_pms_area(MEMPROT_DRAM0_PMS_AREA_1, read, write);
esp_memprot_dram_set_pms_area(MEMPROT_DRAM0_PMS_AREA_2, read, write);
esp_memprot_dram_set_pms_area(MEMPROT_DRAM0_PMS_AREA_3, read, write);
}
}
break;
default:
abort();
}
}
static void test_memprot_read(mem_type_prot_t mem_type)
{
//get current READ & WRITE permission settings
bool write_perm_low, write_perm_high, read_perm_low, read_perm_high, exec_perm_low, exec_perm_high;
test_memprot_get_permissions(true, mem_type, &read_perm_low, &write_perm_low, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_low : NULL);
test_memprot_get_permissions(false, mem_type, &read_perm_high, &write_perm_high, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_high : NULL);
//get testing pointers for low & high regions
volatile uint32_t *ptr_low = test_memprot_addr_low(mem_type);
volatile uint32_t *ptr_high = test_memprot_addr_high(mem_type);
const uint32_t test_val = 100;
//temporarily allow WRITE for setting the test values
esp_memprot_set_monitor_en(mem_type, false);
test_memprot_set_permissions(true, mem_type, read_perm_low, true, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_low : NULL);
test_memprot_set_permissions(false, mem_type, read_perm_high, true, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_high : NULL);
*ptr_low = test_val;
*ptr_high = test_val + 1;
test_memprot_set_permissions(true, mem_type, read_perm_low, write_perm_low, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_low : NULL);
test_memprot_set_permissions(false, mem_type, read_perm_high, write_perm_high, mem_type == MEMPROT_IRAM0_SRAM ? &exec_perm_high : NULL);
esp_memprot_set_monitor_en(mem_type, true);
//perform READ test in low region
esp_rom_printf("%s read low: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
volatile uint32_t val = *ptr_low;
if ( read_perm_low && val != test_val ) {
esp_rom_printf( "UNEXPECTED VALUE 0x%08X -", val );
dump_status_register(mem_type);
} else {
check_test_result(mem_type, read_perm_low);
}
//perform READ in high region
esp_rom_printf("%s read high: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
val = *ptr_high;
if ( read_perm_high && val != (test_val + 1) ) {
esp_rom_printf( "UNEXPECTED VALUE 0x%08X -", val);
dump_status_register(mem_type);
} else {
check_test_result(mem_type, read_perm_high);
}
}
static void test_memprot_write(mem_type_prot_t mem_type)
{
//get current READ & WRITE permission settings
bool write_perm_low, write_perm_high, read_perm_low, read_perm_high;
test_memprot_get_permissions(true, mem_type, &read_perm_low, &write_perm_low, NULL);
test_memprot_get_permissions(false, mem_type, &read_perm_high, &write_perm_high, NULL);
//ensure READ enabled
esp_memprot_set_monitor_en(mem_type, false);
test_memprot_set_permissions(true, mem_type, true, write_perm_low, NULL);
test_memprot_set_permissions(false, mem_type, true, write_perm_high, NULL);
esp_memprot_set_monitor_en(mem_type, true);
//get testing pointers for low & high regions
volatile uint32_t *ptr_low = test_memprot_addr_low(mem_type);
volatile uint32_t *ptr_high = test_memprot_addr_high(mem_type);
const uint32_t test_val = 10;
//perform WRITE in low region
esp_rom_printf("%s write low: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
volatile uint32_t val = 0;
*ptr_low = test_val;
val = *ptr_low;
if ( val != test_val && write_perm_low ) {
esp_rom_printf( "UNEXPECTED VALUE 0x%08X -", val);
dump_status_register(mem_type);
} else {
check_test_result(mem_type, write_perm_low);
}
//perform WRITE in high region
esp_rom_printf("%s write high: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
val = 0;
*ptr_high = test_val + 1;
val = *ptr_high;
if ( val != (test_val + 1) && write_perm_high ) {
esp_rom_printf( "UNEXPECTED VALUE 0x%08X -", val);
dump_status_register(mem_type);
} else {
check_test_result(mem_type, write_perm_high);
}
//restore original permissions
esp_memprot_set_monitor_en(mem_type, false);
test_memprot_set_permissions(true, mem_type, true, read_perm_low, NULL);
test_memprot_set_permissions(false, mem_type, true, read_perm_high, NULL);
esp_memprot_set_monitor_en(mem_type, true);
}
static void test_memprot_exec(mem_type_prot_t mem_type)
{
if ( mem_type != MEMPROT_IRAM0_SRAM ) {
esp_rom_printf("Error: EXEC test available only for IRAM access.\n" );
return;
}
bool write_perm_low, write_perm_high, read_perm_low, read_perm_high, exec_perm_low, exec_perm_high;
//temporarily enable READ/WRITE
test_memprot_get_permissions(true, mem_type, &read_perm_low, &write_perm_low, &exec_perm_low);
test_memprot_get_permissions(false, mem_type, &read_perm_high, &write_perm_high, &exec_perm_high);
esp_memprot_set_monitor_en(mem_type, false);
test_memprot_set_permissions(true, mem_type, true, true, &exec_perm_low);
test_memprot_set_permissions(false, mem_type, true, true, &exec_perm_high);
esp_memprot_set_monitor_en(mem_type, true);
//get testing pointers for low & high regions, zero 8B slot
void *fnc_ptr_low = test_memprot_addr_low(mem_type);
void *fnc_ptr_high = test_memprot_addr_high(mem_type);
memset( fnc_ptr_low, 0, 8);
memset( fnc_ptr_high, 0, 8);
//inject the code to both low & high segments
memcpy( (void *)fnc_ptr_low, (const void *)fnc_buff, sizeof(fnc_buff) );
memcpy( (void *)fnc_ptr_high, (const void *)fnc_buff, sizeof(fnc_buff) );
uint32_t res = 0;
//LOW REGION: clear the intr flag & try to execute the code injected
esp_rom_printf("%s exec low: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
fnc_ptr fnc = (fnc_ptr)fnc_ptr_low;
g_override_illegal_instruction = true;
res = fnc( 5 );
g_override_illegal_instruction = false;
//check results
bool fnc_call_ok = res == 10;
if ( fnc_call_ok ) {
check_test_result(mem_type, exec_perm_low);
} else {
if ( !exec_perm_low ) {
check_test_result(mem_type, false);
} else {
esp_rom_printf(" FAULT [injected code not executed]\n");
}
}
//HIGH REGION: clear the intr-on flag & try to execute the code injected
esp_rom_printf("%s exec high: ", esp_memprot_mem_type_to_str(mem_type));
esp_memprot_monitor_clear_intr(mem_type);
fnc = (fnc_ptr)fnc_ptr_high;
g_override_illegal_instruction = true;
res = fnc( 6 );
g_override_illegal_instruction = false;
fnc_call_ok = res == 12;
if ( fnc_call_ok ) {
check_test_result(mem_type, exec_perm_high);
} else {
if ( !exec_perm_high ) {
check_test_result(mem_type, false);
} else {
esp_rom_printf(" FAULT [injected code not executed]\n");
}
}
//restore original permissions
esp_memprot_set_monitor_en(mem_type, false);
test_memprot_set_permissions(true, mem_type, read_perm_low, write_perm_low, &exec_perm_low);
test_memprot_set_permissions(false, mem_type, read_perm_high, write_perm_high, &exec_perm_high);
esp_memprot_set_monitor_en(mem_type, true);
}
/* ********************************************************************************************
* main test runner
*/
void app_main(void)
{
esp_memprot_set_prot_int(false, false, NULL, NULL);
test_memprot_read(MEMPROT_IRAM0_SRAM);
test_memprot_write(MEMPROT_IRAM0_SRAM);
test_memprot_exec(MEMPROT_IRAM0_SRAM);
test_memprot_read(MEMPROT_DRAM0_SRAM);
test_memprot_write(MEMPROT_DRAM0_SRAM);
}

View File

@@ -0,0 +1,52 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "riscv/rvruntime-frames.h"
#include "esp_private/panic_internal.h"
#define MCAUSE_ILLEGAL_INSTRUCTION 2
extern void esp_panic_handler(panic_info_t *info);
volatile bool g_override_illegal_instruction = false;
void __real_esp_panic_handler(panic_info_t *info);
void return_from_panic_handler(RvExcFrame *frm) __attribute__((noreturn));
/* Memprot test specific IllegalInstruction exception handler:
* when testing the protection against a code execution, sample code
* is being injected into various memory regions which produces
* Illegal instruction on execution attempt. Such a result is expected
* but it causes system reboot in the standard panic handler.
* The following variant of panic handling simply returns back to the
* next instruction and continues normal execution.
*
* NOTE: if Illegal instruction comes from a different source than the testing code
* the behavior is undefined
* */
void __wrap_esp_panic_handler(panic_info_t *info)
{
RvExcFrame *frm = (RvExcFrame *)info->frame;
if ( frm->mcause == MCAUSE_ILLEGAL_INSTRUCTION && g_override_illegal_instruction == true ) {
/* Return from exception to the return address that called the faulting function.
*/
frm->mepc = frm->ra;
/* Restore the CPU state and return from the exception.
*/
return_from_panic_handler(frm);
} else {
__real_esp_panic_handler(info);
}
}