Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions subsys/bootloader/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,27 @@ config SB_CLEANUP_RAM
help
Sets contents of memory to 0 before jumping to application.

config SB_INFINITE_LOOP_AFTER_RAM_CLEANUP
bool "Infinite loop after RAM cleanup"
depends on SB_CLEANUP_RAM
help
Verification option that keeps execution in infinite loop after
RAM cleanup has been performed.

config SB_DISABLE_SELF_RWX
bool "Disable read and execution on self NVM"
depends on (SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP) && !FPROTECT_ALLOW_COMBINED_REGIONS
help
Sets RRAMC's BOOTCONF region protection before jumping to application.
It disables reads writes and execution memory area which holds NSIB.

config SB_DISABLE_NEXT_W
bool "Disable writes for next stage"
depends on (SOC_NRF54L15_CPUAPP || SOC_NRF54L05_CPUAPP || SOC_NRF54L10_CPUAPP) && !FPROTECT
help
NSIB disables writes on next stage in bootloading chain.
It uses RRAMC's region 4 and is limited to 31KB.

endif # IS_SECURE_BOOTLOADER

config IS_BOOTLOADER_IMG
Expand Down
229 changes: 133 additions & 96 deletions subsys/bootloader/bl_boot/bl_boot.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,144 @@
#include <hal/nrf_gpio.h>
#endif

#include <zephyr/linker/linker-defs.h>
#define CLEANUP_RAM_GAP_START ((int)__ramfunc_region_start)
#define CLEANUP_RAM_GAP_SIZE ((int) (__ramfunc_end - __ramfunc_region_start))

#if defined(CONFIG_SB_DISABLE_NEXT_W)
#include <hal/nrf_rramc.h>
#define RRAMC_REGION_FOR_NEXT_W 4
#define NRF_RRAM_REGION_SIZE_UNIT 0x400
#define NRF_RRAM_REGION_ADDRESS_RESOLUTION 0x400
#define NEXT_W_SIZE_KB (PM_MCUBOOT_SIZE / NRF_RRAM_REGION_SIZE_UNIT)

BUILD_ASSERT((PM_MCUBOOT_ADDRESS % NRF_RRAM_REGION_ADDRESS_RESOLUTION) == 0,
"Start of protected region is not aligned");

BUILD_ASSERT((PM_MCUBOOT_SIZE % NRF_RRAM_REGION_SIZE_UNIT) == 0,
"Size of protected region is not aligned");

BUILD_ASSERT(NEXT_W_SIZE_KB < 31,
"Size of requested protection is too big");

static int disable_next_w(void)
{
nrf_rramc_region_config_t config = {
.address = PM_MCUBOOT_ADDRESS,
.permissions = NRF_RRAMC_REGION_PERM_READ_MASK |
NRF_RRAMC_REGION_PERM_EXECUTE_MASK,
.writeonce = false,
.lock = false,
.size_kb = NEXT_W_SIZE_KB,
};

nrf_rramc_region_config_set(NRF_RRAMC, RRAMC_REGION_FOR_NEXT_W, &config);
nrf_rramc_region_config_get(NRF_RRAMC, RRAMC_REGION_FOR_NEXT_W, &config);
if (config.permissions & (NRF_RRAMC_REGION_PERM_WRITE_MASK)) {
return -ENOSPC;
}
if (config.size_kb != NEXT_W_SIZE_KB) {
return -ENOSPC;
}

return 0;
}

#endif

#if defined(CONFIG_SB_DISABLE_SELF_RWX)
/* Disabling R_X has to be done while running from RAM for obvious reasons.
* Moreover as a last step before jumping to application it must work even after
* RAM has been cleared, therefore we are using custom RAM function relocator.
* This relocator runs after RAM cleanup.
* Size of the relocated 'locking' function isn't known but it doesn't matter
* as long as at least entire aforementioned function is copied to RAM.
* RAM has been cleared, therefore these operations are performed while executing from RAM.
* RAM cleanup ommits portion of the memory where code lives.
*/
#include <hal/nrf_rramc.h>

#define FUNCTION_BUFFER_LEN 64
#define RRAMC_REGION_RWX_LSB 0
#define RRAMC_REGION_RWX_WIDTH 3
#define RRAMC_REGION_TO_LOCK_ADDR NRF_RRAMC->REGION[3].CONFIG
#define RRAMC_REGION_TO_LOCK_ADDR_H (((uint32_t)(&(RRAMC_REGION_TO_LOCK_ADDR))) >> 16)
#define RRAMC_REGION_TO_LOCK_ADDR_L (((uint32_t)(&(RRAMC_REGION_TO_LOCK_ADDR))) & 0x0000fffful)
static uint8_t ram_exec_buf[FUNCTION_BUFFER_LEN];
#endif /* CONFIG_SB_DISABLE_SELF_RWX */

static void __ramfunc jump_in(uint32_t reset)
{
__asm__ volatile (
/* reset -> r0 */
" mov r0, %0\n"
#ifdef CONFIG_SB_CLEANUP_RAM
/* Base to write -> r1 */
" mov r1, %1\n"
/* Size to write -> r2 */
" mov r2, %2\n"
/* Value to write -> r3 */
" movw r3, %5\n"
/* gap start */
" mov r4, %3\n"
/* gap size */
" mov r5, %4\n"
"clear:\n"
" subs r6, r4, r1\n"
" cbnz r6, skip_gap\n"
" add r1, r5\n"
"skip_gap:\n"
" str r3, [r1]\n"
" add r1, r1, #1\n"
" sub r2, r2, #1\n"
" cbz r2, clear_end\n"
" b clear\n"
"clear_end:\n"
" dsb\n"
#ifdef CONFIG_SB_INFINITE_LOOP_AFTER_RAM_CLEANUP
" b clear_end\n"
#endif /* CONFIG_SB_INFINITE_LOOP_AFTER_RAM_CLEANUP */
#endif /* CONFIG_SB_CLEANUP_RAM */

#ifdef CONFIG_SB_DISABLE_SELF_RWX
".thumb_func\n"
"bootconf_disable_rwx:\n"
" movw r1, %6\n"
" movt r1, %7\n"
" ldr r2, [r1]\n"
/* Size of the region should be set at this point
* by provisioning through BOOTCONF.
* If not, set it according partition size.
*/
" ands r4, r2, %12\n"
" cbnz r4, clear_rwx\n"
" movt r2, %8\n"
"clear_rwx:\n"
" bfc r2, %9, %10\n"
/* Disallow further modifications */
" orr r2, %11\n"
" str r2, [r1]\n"
" dsb\n"
/* Next assembly line is important for current function */

#endif /* CONFIG_SB_DISABLE_SELF_RWX */

/* Jump to reset vector of an app */
" bx r0\n"
:
: "r" (reset),
"i" (CONFIG_SRAM_BASE_ADDRESS),
"i" (CONFIG_SRAM_SIZE * 1024),
"r" (CLEANUP_RAM_GAP_START),
"r" (CLEANUP_RAM_GAP_SIZE),
"i" (0)
#ifdef CONFIG_SB_DISABLE_SELF_RWX
, "i" (RRAMC_REGION_TO_LOCK_ADDR_L),
"i" (RRAMC_REGION_TO_LOCK_ADDR_H),
"i" (CONFIG_PM_PARTITION_SIZE_B0_IMAGE / 1024),
"i" (RRAMC_REGION_RWX_LSB),
"i" (RRAMC_REGION_RWX_WIDTH),
"i" (RRAMC_REGION_CONFIG_LOCK_Msk),
"i" (RRAMC_REGION_CONFIG_SIZE_Msk)
#endif /* CONFIG_SB_DISABLE_SELF_RWX */
: "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory"
);
}

#ifdef CONFIG_UART_NRFX_UARTE
static void uninit_used_uarte(NRF_UARTE_Type *p_reg)
{
Expand Down Expand Up @@ -163,6 +282,13 @@ void bl_boot(const struct fw_info *fw_info)
VTOR = fw_info->address;
uint32_t *vector_table = (uint32_t *)fw_info->address;

#if defined(CONFIG_SB_DISABLE_NEXT_W)
if (disable_next_w()) {
printk("Unable to disable writes on next stage.");
return;
}
#endif

#if defined(CONFIG_BUILTIN_STACK_GUARD) && \
defined(CONFIG_CPU_CORTEX_M_HAS_SPLIM)
/* Reset limit registers to avoid inflicting stack overflow on image
Expand All @@ -175,96 +301,7 @@ void bl_boot(const struct fw_info *fw_info)
__set_MSP(vector_table[0]);
__set_PSP(0);

__asm__ volatile (
/* vector_table[1] -> r0 */
" mov r0, %0\n"
#ifdef CONFIG_SB_CLEANUP_RAM
/* Base to write -> r1 */
" mov r1, %1\n"
/* Size to write -> r2 */
" mov r2, %2\n"
/* Value to write -> r3 */
" movw r3, %3\n"
"clear:\n"
" str r3, [r1]\n"
" add r1, r1, #4\n"
" sub r2, r2, #4\n"
" cbz r2, out\n"
" b clear\n"
"out:\n"
" dsb\n"
#endif /* CONFIG_SB_CLEANUP_RAM */

#ifdef CONFIG_SB_DISABLE_SELF_RWX
/* FUNCTION_BUFFER_LEN */
" movw r4, %4\n"
/* Address of ram_exec_buf goes to r2 */
" mov r2, %5\n"
" movw r3, :lower16:bootconf_disable_rwx\n"
" movt r3, :upper16:bootconf_disable_rwx\n"
/* Adjust address for thumb */
" and r3, #0xfffffffe\n"
/* Address of ram_exec_buf also goes to r5 */
" mov r5, %5\n"
/* Adjust buffer address for thumb */
" orr r5, #0x1\n"
/* End of the copy address in r4 */
" add r4, r2\n"
"ram_cpy:\n"
/* Read and increment */
" ldrb r1, [r3], #1\n"
/* Write and increment */
" strb r1, [r2], #1\n"
/* Check if end address is reached */
" cmp r2, r4\n"
" bne ram_cpy\n"
" dsb\n"
/* Jump to ram */
" bx r5\n"
/* CODE_UNREACHABLE */

".thumb_func\n"
"bootconf_disable_rwx:\n"
" movw r1, %6\n"
" movt r1, %7\n"
" ldr r2, [r1]\n"
/* Size of the region should be set at this point
* by provisioning through BOOTCONF.
* If not, set it according partition size.
*/
" ands r4, r2, %12\n"
" cbnz r4, clear_rwx\n"
" movt r2, %8\n"
"clear_rwx:\n"
" bfc r2, %9, %10\n"
/* Disallow further modifications */
" orr r2, %11\n"
" str r2, [r1]\n"
" dsb\n"
/* Next assembly line is important for current function */

#endif /* CONFIG_SB_DISABLE_SELF_RWX */

/* Jump to reset vector of an app */
" bx r0\n"
:
: "r" (vector_table[1]),
"i" (CONFIG_SRAM_BASE_ADDRESS),
"i" (CONFIG_SRAM_SIZE * 1024),
"i" (0)
#ifdef CONFIG_SB_DISABLE_SELF_RWX
, "i" (FUNCTION_BUFFER_LEN),
"r" (ram_exec_buf),
"i" (RRAMC_REGION_TO_LOCK_ADDR_L),
"i" (RRAMC_REGION_TO_LOCK_ADDR_H),
"i" (CONFIG_PM_PARTITION_SIZE_B0_IMAGE / 1024),
"i" (RRAMC_REGION_RWX_LSB),
"i" (RRAMC_REGION_RWX_WIDTH),
"i" (RRAMC_REGION_CONFIG_LOCK_Msk),
"i" (RRAMC_REGION_CONFIG_SIZE_Msk)
#endif /* CONFIG_SB_DISABLE_SELF_RWX */
: "r0", "r1", "r2", "r3", "r4", "r5", "memory"
);
jump_in((vector_table[1]));

CODE_UNREACHABLE;
}
11 changes: 0 additions & 11 deletions tests/subsys/bootloader/b0_lock/testcase.yaml

This file was deleted.

23 changes: 23 additions & 0 deletions tests/subsys/bootloader/b0_lock_rwx/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Copyright (c) 2022 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

config TEST_B0_LOCK_READS
bool "Test read prevention"
help
Reads are disabled after writes has been disabled, therefore testing against reads
implies portion of the memory cannot be accessed at all.
In case this symbol isn't selected, tests against writes are performed.

config TEST_B0_LOCK_REGION
int "Region number"
range 3 4
default 3
help
Region 3 is used for NSIB protection. Other one for MCUBoot.

menu "Zephyr"
source "Kconfig.zephyr"
endmenu
Loading
Loading