Hi Sascha,
On 1/5/26 12:27 PM, Sascha Hauer wrote:
> Enable hardware-enforced W^X (Write XOR Execute) memory protection through
> ELF segment-based permissions using the RISC-V MMU.
>
> This implementation provides memory protection for RISC-V S-mode using
> Sv39 (RV64) or Sv32 (RV32) page tables.
>
> Linker Script Changes:
> - Add PHDRS directives to pbl.lds.S and barebox.lds.S
> - Create three separate PT_LOAD segments with proper permissions:
> * text segment (FLAGS(5) = PF_R|PF_X): code sections
> * rodata segment (FLAGS(4) = PF_R): read-only data
> * data segment (FLAGS(6) = PF_R|PF_W): data and BSS
> - Add 4K alignment between segments for page-granular protection
>
> S-mode MMU Implementation (mmu.c):
> - Implement page table walking for Sv39/Sv32
> - pbl_remap_range(): remap segments with ELF-derived permissions
> - mmu_early_enable(): create identity mapping and enable SATP CSR
> - Map ELF flags to PTE bits:
> * MAP_CODE → PTE_R | PTE_X (read + execute)
> * MAP_CACHED_RO → PTE_R (read only)
> * MAP_CACHED → PTE_R | PTE_W (read + write)
>
> Integration:
> - Update uncompress.c to call mmu_early_enable() before decompression
> (enables caching for faster decompression)
> - Call pbl_mmu_setup_from_elf() after ELF relocation to apply final
> segment-based permissions
> - Uses portable pbl/mmu.c infrastructure to parse PT_LOAD segments
>
> Configuration:
> - Add CONFIG_MMU option (default y for RISCV_S_MODE)
> - Update asm/mmu.h with ARCH_HAS_REMAP and function declarations
>
> Security Benefits:
> - Text sections are read-only and executable (cannot be modified)
> - Read-only data sections are read-only and non-executable
> - Data sections are read-write and non-executable (cannot be executed)
> - Hardware-enforced W^X prevents code injection attacks
>
> This matches the ARM implementation philosophy and provides genuine
> security improvements on RISC-V S-mode platforms.
>
> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
>
> Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
> Signed-off-by: Sascha Hauer <[email protected]>
> ---
> arch/riscv/Kconfig | 16 ++
> arch/riscv/Kconfig.socs | 1 -
> arch/riscv/boot/uncompress.c | 25 +++
> arch/riscv/cpu/Makefile | 1 +
> arch/riscv/cpu/mmu.c | 386
> +++++++++++++++++++++++++++++++++++++++++++
> arch/riscv/cpu/mmu.h | 144 ++++++++++++++++
> arch/riscv/include/asm/asm.h | 3 +-
> arch/riscv/include/asm/mmu.h | 44 +++++
> include/mmu.h | 6 +-
> 9 files changed, 621 insertions(+), 5 deletions(-)
>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index
> f8c8b38ed6d7fdae48669e6d7b737f695f1c4cc9..1eec3c6c684cfc16f92f612cf45a1511f072948b
> 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -130,4 +130,20 @@ config RISCV_MULTI_MODE
> config RISCV_SBI
> def_bool RISCV_S_MODE
>
> +config MMU
> + bool "MMU-based memory protection"
> + help
> + Enable MMU (Memory Management Unit) support for RISC-V S-mode.
> + This provides hardware-enforced W^X (Write XOR Execute) memory
> + protection using page tables (Sv39 for RV64, Sv32 for RV32).
> +
> + The PBL sets up page table entries based on ELF segment permissions,
> + ensuring that:
> + - Text sections are read-only and executable
> + - Read-only data sections are read-only and non-executable
> + - Data sections are read-write and non-executable
> +
> + Say Y if running in S-mode (supervisor mode) with virtual memory.
> + Say N if running in M-mode or if you don't need memory protection.
> +
> endmenu
> diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs
> index
> 4a3b56b5fff48c86901ed0346be490a6847ac14e..0d9984dd2888e6cab81939e3ee97ef83851362a0
> 100644
> --- a/arch/riscv/Kconfig.socs
> +++ b/arch/riscv/Kconfig.socs
> @@ -123,7 +123,6 @@ if SOC_ALLWINNER_SUN20I
> config BOARD_ALLWINNER_D1
> bool "Allwinner D1 Nezha"
> select RISCV_S_MODE
> - select RISCV_M_MODE
I don't know why this board select both S- and M-Mode, but maybe you can
explain why it drops the latter of them?
> +#ifdef CONFIG_MMU
if (IS_ENABLED()) or stubs in header?
> + /*
> + * Enable MMU early to enable caching for faster decompression.
> + * This creates an initial identity mapping that will be refined
> + * later based on ELF segments.
> + */
> + mmu_early_enable(membase, memsize, barebox_base);
> +#endif
> +
> pr_debug("uncompressing barebox binary at 0x%p (size 0x%08x) to 0x%08lx
> (uncompressed size: 0x%08x)\n",
> pg_start, pg_len, barebox_base, uncompressed_len);
>
> @@ -82,6 +94,19 @@ void __noreturn barebox_pbl_start(unsigned long membase,
> unsigned long memsize,
> hang();
> }
>
> + /*
> + * Now that the ELF image is relocated, we know the exact addresses
> + * of all segments. Set up MMU with proper permissions based on
> + * ELF segment flags (PF_R/W/X).
> + */
> +#ifdef CONFIG_MMU
> + ret = pbl_mmu_setup_from_elf(&elf, membase, memsize);
> + if (ret) {
> + pr_err("Failed to setup memory protection from ELF: %d\n", ret);
> + hang();
> + }
> +#endif
> +
> barebox = (void *)elf.entry;
>
> pr_debug("jumping to uncompressed image at 0x%p. dtb=0x%p\n", barebox,
> fdt);
> diff --git a/arch/riscv/cpu/Makefile b/arch/riscv/cpu/Makefile
> index
> d79bafc6f142a0060d2a86078f0fb969b298ba98..6bf31b574cd6242df6393fbdc8accc08dceb822a
> 100644
> --- a/arch/riscv/cpu/Makefile
> +++ b/arch/riscv/cpu/Makefile
> @@ -7,3 +7,4 @@ obj-pbl-$(CONFIG_RISCV_M_MODE) += mtrap.o
> obj-pbl-$(CONFIG_RISCV_S_MODE) += strap.o
> obj-pbl-y += interrupts.o
> endif
> +obj-pbl-$(CONFIG_MMU) += mmu.o
> diff --git a/arch/riscv/cpu/mmu.c b/arch/riscv/cpu/mmu.c
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..6cf4586f364c98dd69105dfa1c558b560755b7d4
> --- /dev/null
> +++ b/arch/riscv/cpu/mmu.c
> @@ -0,0 +1,386 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +// SPDX-FileCopyrightText: 2026 Sascha Hauer <[email protected]>,
> Pengutronix
> +
> +#define pr_fmt(fmt) "mmu: " fmt
> +
> +#include <common.h>
> +#include <init.h>
> +#include <mmu.h>
> +#include <errno.h>
> +#include <linux/sizes.h>
> +#include <linux/bitops.h>
> +#include <asm/sections.h>
> +
> +#include "mmu.h"
> +
> +#ifdef __PBL__
Drop #ifdef and build file only for pbl- and move stub into header?
I won't dig deeper into review here for v1, but you may want to compare
against the RISC-V MMU implementation here:
https://github.com/AndreyLalaev/barebox/tree/riscv-mmu
> +/*
> + * Convert maptype flags to PTE permission bits
> + */
> +static unsigned long flags_to_pte(maptype_t flags)
> +{
> + unsigned long pte = PTE_V; /* Valid bit always set */
> +
> + /*
> + * Map barebox memory types to RISC-V PTE flags:
> + * - ARCH_MAP_CACHED_RWX: read + write + execute (early boot, full RAM
> access)
> + * - MAP_CODE: read + execute (text sections)
> + * - MAP_CACHED_RO: read only (rodata sections)
> + * - MAP_CACHED: read + write (data/bss sections)
> + * - MAP_UNCACHED: read + write, uncached (device memory)
> + */
> + switch (flags & MAP_TYPE_MASK) {
> + case ARCH_MAP_CACHED_RWX:
> + /* Full access for early boot: R + W + X */
> + pte |= PTE_R | PTE_W | PTE_X;
> + break;
> + case MAP_CACHED_RO:
> + /* Read-only data: R, no W, no X */
> + pte |= PTE_R;
> + break;
> + case MAP_CODE:
> + /* Code: R + X, no W */
> + pte |= PTE_R | PTE_X;
> + break;
> + case MAP_CACHED:
> + case MAP_UNCACHED:
The real world happened and there is a sizable amount of RISC-V silicon
that optimized cost by sacrificing cache coherency, including the
Starfive and Nezha we currently support. Each implements it a different
way and neither supports the Svpbmt extension...
> +#ifndef __ASSEMBLY__
> +
> +/* CSR access */
> +#define csr_read(csr) \
> +({ \
> + unsigned long __v; \
> + __asm__ __volatile__ ("csrr %0, " #csr \
> + : "=r" (__v) : \
> + : "memory"); \
> + __v; \
> +})
> +
> +#define csr_write(csr, val) \
> +({ \
> + unsigned long __v = (unsigned long)(val); \
> + __asm__ __volatile__ ("csrw " #csr ", %0" \
> + : : "rK" (__v) \
> + : "memory"); \
> +})
These duplicate <asm/csr.h> I think.
> - if (maptype_is_compatible(map_type, MAP_ARCH_DEFAULT) &&
> + if (maptype_is_compatible(map_type, MAP_DEFAULT) &&
spurious change?
Cheers,
Ahmad
--
Pengutronix e.K. | |
Steuerwalder Str. 21 | http://www.pengutronix.de/ |
31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |