//! Stage 11: guard pages. Below every kernel stack is an unmapped 4 KiB //! page, and below every user stack a 64 KiB unmapped zone. Running off the //! end of a stack hits them, and the kernel kills just the culprit with a //! clear "stack overflow" message, instead of corrupting other memory. #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(rustbelt::test_runner)] #![reexport_test_harness_main = "test_main"] extern crate alloc; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use core::sync::atomic::{AtomicBool, Ordering}; use rustbelt::process::{self, ExitStatus, SpawnArgs}; use rustbelt::stack::{self, KernelStack, STACK_SIZE}; use rustbelt::{fs, memory, thread}; entry_point!(main); fn main(boot_info: &'static BootInfo) -> ! { rustbelt::init(); rustbelt::init_memory(boot_info); rustbelt::init_smp(); fs::init(); rustbelt::init_storage(); test_main(); rustbelt::hlt_loop(); } #[test_case] fn guard_page_sits_right_below_each_kernel_stack() { let s = KernelStack::new().expect("no stack"); assert_eq!(s.top() - s.bottom(), STACK_SIZE); assert_eq!(s.guard_page(), s.bottom() - 4096); assert!(stack::is_guard_page(s.guard_page())); assert!(stack::is_guard_page(s.bottom() - 1)); assert!(!stack::is_guard_page(s.bottom())); assert!(!stack::is_guard_page(s.top() - 8)); } #[test_case] fn user_stack_guard_zone_bounds() { let stack_bottom = process::USER_STACK_TOP - process::USER_STACK_SIZE; assert!(process::in_user_stack_guard(stack_bottom - 1)); assert!(process::in_user_stack_guard(process::USER_STACK_ZONE_START)); assert!(!process::in_user_stack_guard(stack_bottom)); assert!(!process::in_user_stack_guard(process::USER_STACK_ZONE_START - 1)); } static REACHED_END: AtomicBool = AtomicBool::new(false); #[allow(unconditional_recursion)] fn recurse(depth: u64) -> u64 { let buf = core::hint::black_box([depth as u8; 256]); recurse(depth + 1) + buf[0] as u64 } fn overflowing_thread() { recurse(0); REACHED_END.store(true, Ordering::Relaxed); } #[test_case] fn kernel_stack_overflow_kills_only_that_thread() { let frames = memory::frames_in_use(); let stacks = stack::live_stacks(); let caught = thread::stack_overflows(); for _ in 0..3 { // Three times: the handler must keep working (on any CPU). let id = thread::spawn("recurser", overflowing_thread); thread::join(id); } assert!(!REACHED_END.load(Ordering::Relaxed)); assert_eq!(thread::stack_overflows(), caught + 3); // Its stack was freed like any other thread's, and we're still running. assert_eq!(stack::live_stacks(), stacks); assert_eq!(memory::frames_in_use(), frames); } #[test_case] fn user_stack_overflow_kills_the_process() { let run = || { let spawn = SpawnArgs::kernel("overflow"); let p = process::spawn_path("/mnt/disk0/bin/overflow", spawn).expect("spawn failed"); process::wait(&p) }; assert_eq!(run(), ExitStatus::Killed(rustbelt::signal::SIGSEGV, "stack overflow")); // warm up let frames = memory::frames_in_use(); assert_eq!(run(), ExitStatus::Killed(rustbelt::signal::SIGSEGV, "stack overflow")); // Everything it had (incl. the 16 KiB of stack it filled) is free again. assert_eq!(memory::frames_in_use(), frames); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }