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
8 changes: 8 additions & 0 deletions arch/arm/cortex_m/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ if USE_MPU
bool "MPU stack guard supports"
default n

if MPU_STACK_GUARD
config STACK_GUARD_ALIGN_AND_SIZE
hex "Stack guard align and size"
default 0x40 if MPU_V8M
help
The align and size of Stack guard
endif

endif

endmenu
2 changes: 1 addition & 1 deletion arch/arm/cortex_m/link.x
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#define ROM_BASE CONFIG_FLASH_BASE_ADDRESS
#define ROM_SIZE (CONFIG_FLASH_SIZE * 1K)
#if defined(CONFIG_MPU_STACK_GUARD)
#define STACK_GUARD_SIZE 32
#define STACK_GUARD_SIZE CONFIG_STACK_GUARD_ALIGN_AND_SIZE
#else
#define STACK_GUARD_SIZE 0
#endif
Expand Down
3 changes: 3 additions & 0 deletions kernel/src/arch/arm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ macro_rules! arch_bootstrap {

extern "C" fn prepare_schedule() -> usize {
let current = scheduler::current_thread_ref();
// Program guard for the first thread before entering thread mode with PSP.
#[cfg(all(use_mpu, mpu_stack_guard))]
mpu::update_thread_stack_guard(current);
current.reset_saved_sp();
current.saved_sp()
}
Expand Down
2 changes: 2 additions & 0 deletions kernel/src/arch/arm/mpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
pub mod mpu_v8m;
#[cfg(mpu_v8m)]
pub use mpu_v8m::init_sys_stack_guard;
#[cfg(all(mpu_v8m, mpu_stack_guard))]
pub use mpu_v8m::update_thread_stack_guard;
40 changes: 40 additions & 0 deletions kernel/src/arch/arm/mpu/mpu_v8m.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use cortex_m::peripheral::{MPU, SCB};
const MPU_CTRL_ENABLE: u32 = 1 << 0;
const MPU_CTRL_PRIVDEFENA: u32 = 1 << 2;
const MPU_REGION_GUARD: u32 = 0;
const MPU_REGION_THREAD_GUARD: u32 = 1;
const MPU_REGION_ALIGN: usize = 32;
const MPU_RBAR_XN: u32 = 1 << 0;
// AP=0b10: privileged read-only, unprivileged no access.
const MPU_RBAR_AP_PRIV_RO: u32 = 0b10 << 1;
Expand Down Expand Up @@ -47,6 +49,11 @@ fn encode_rlar(end: usize) -> u32 {
(((end - 1) as u32) & !0x1f) | MPU_RLAR_REGION_ENABLE
}

#[inline]
const fn align_up(addr: usize, align: usize) -> usize {
(addr + align - 1) & !(align - 1)
}

pub fn init_sys_stack_guard() {
let guard_start = addr_of!(__sys_stack_guard_start) as usize;
let guard_end = addr_of!(__sys_stack_guard_end) as usize;
Expand Down Expand Up @@ -75,3 +82,36 @@ pub fn init_sys_stack_guard() {
unsafe { scb.shcsr.write(shcsr) };
barrier();
}

#[cfg(mpu_stack_guard)]
#[inline]
pub fn update_thread_stack_guard(next: &crate::thread::Thread) {
let guard_size = blueos_kconfig::CONFIG_STACK_GUARD_ALIGN_AND_SIZE as usize;
debug_assert!(guard_size % MPU_REGION_ALIGN == 0);
debug_assert!(guard_size >= MPU_REGION_ALIGN);

let stack_base = next.stack_base();
let stack_top = stack_base.saturating_add(next.stack_size());
debug_assert!(stack_top > stack_base);

// MPU RBAR/RLAR requires 32-byte alignment. Align up so we never
// protect bytes below stack_base().
let guard_start = align_up(stack_base, MPU_REGION_ALIGN);
debug_assert!(guard_start < stack_top);
debug_assert!(guard_start >= stack_base);

let guard_end = guard_start.saturating_add(guard_size);
debug_assert!(guard_end > guard_start);
debug_assert!(guard_end < stack_top);

debug_assert_eq!(guard_start & (MPU_REGION_ALIGN - 1), 0);
debug_assert_eq!(guard_end & (MPU_REGION_ALIGN - 1), 0);

let mpu = unsafe { &*MPU::PTR };
unsafe {
mpu.rnr.write(MPU_REGION_THREAD_GUARD);
mpu.rbar.write(encode_rbar(guard_start));
mpu.rlar.write(encode_rlar(guard_end));
}
barrier();
}
33 changes: 33 additions & 0 deletions kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ mod tests {
}
}

#[cfg(mpu_stack_guard)]
#[inline]
const fn align_up(addr: usize, align: usize) -> usize {
(addr + align - 1) & !(align - 1)
}

#[cfg(mpu_stack_guard)]
extern "C" fn handle_memfault_impl(ctx: &mut crate::arch::IsrContext) {
let scb = unsafe { &*cortex_m::peripheral::SCB::PTR };
Expand Down Expand Up @@ -370,6 +376,33 @@ mod tests {
);
}

#[cfg(mpu_stack_guard)]
#[test]
fn test_mpu_thread_stack_guard_write_fault() {
const MPU_REGION_ALIGN: usize = 32;
let current = scheduler::current_thread_ref();
let guard_size = blueos_kconfig::CONFIG_STACK_GUARD_ALIGN_AND_SIZE as usize;
assert!(
guard_size >= MPU_REGION_ALIGN && guard_size % MPU_REGION_ALIGN == 0,
"Invalid stack guard size: {guard_size}"
);

let stack_base = current.stack_base();
let stack_top = stack_base + current.stack_size();
let guard_start = align_up(stack_base, MPU_REGION_ALIGN);
assert!(
guard_start < stack_top,
"No valid guard start in stack range"
);

MEMFAULT_TRIGGERED.store(false, Ordering::Release);
unsafe { core::ptr::write_volatile(guard_start as *mut u32, 0xA5A5_5A5A) };
assert!(
MEMFAULT_TRIGGERED.load(Ordering::Acquire),
"Per-thread MPU stack guard write did not trigger MemManage"
);
}

#[test]
fn stress_trap() {
#[cfg(target_pointer_width = "32")]
Expand Down
4 changes: 4 additions & 0 deletions kernel/src/scheduler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ fn switch_current_thread(next: ThreadNode, old_sp: usize) -> usize {
let next_priority = next.priority();
let next_saved_sp = spin_until_ready_to_run(&next);
next.clear_saved_sp();
// Reprogram per-thread MPU guard before restoring `next` context so the
// upcoming PSP run is checked against the correct stack region.
#[cfg(all(target_arch = "arm", use_mpu, mpu_stack_guard))]
arch::mpu::update_thread_stack_guard(&next);
let ok = next.transfer_state(thread::READY, thread::RUNNING);
debug_assert_eq!(ok, Ok(()));
let mut old = set_current_thread(next);
Expand Down
16 changes: 16 additions & 0 deletions kernel/src/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,22 @@ impl Thread {
saved_sp - core::mem::size_of::<Context>()
}

/// High address
/// ------------------------------ <--- stack.top() == allocated buffer end
/// | optional align gap (0..A) |
/// ------------------------------
/// | Context for current thread |
/// ------------------------------ <--- saved_sp (initial SP)
/// | stack region | (stack grows down)
/// | ... |
/// ------------------------------ <--- guard upper bound (if enabled)
/// | stack guard region | (optional, usually no-access)
/// ------------------------------ <--- stack.base()
/// Low address
/// FIXME: If an exception happens when SP is very close to (or already inside)
/// the guard, hardware exception stacking (cortex-m) may touch this no-access region
/// and trigger MemManage (for example DACCVIOL/MSTKERR); if fault handling
/// cannot proceed, the fault may escalate to HardFault.
pub(crate) fn init(&mut self, stack: Stack, entry: Entry) -> &mut Self {
self.stack = stack;
// TODO: Stack sanity check.
Expand Down