//! Isolated EL1t stack-overflow experiment. Two privileged tasks use SP_EL0; //! the boot/exception path retains SP_EL1. A genuine recursive overflow hits //! an unmapped page, and only the failed task is discarded before B runs. use super::*; const STACK_BYTES: usize = 16 * 1024; const B_CANARY: u64 = 0x5455_524b_4755_4142; const BOOT_CANARY: u64 = 0x5455_524b_4755_414d; const EL1T_MASKED: usize = 0x3c4; #[repr(C, align(4096))] struct GuardedStack { guard: [u8; 4096], stack: [u8; STACK_BYTES], } #[repr(align(4096))] struct Stack([u8; STACK_BYTES]); static mut A: GuardedStack = GuardedStack { guard: [0; 4096], stack: [0; STACK_BYTES], }; static mut B: Stack = Stack([0; STACK_BYTES]); static mut A_STARTED: usize = 0; static mut A_STOPPED: usize = 0; static mut MAX_DEPTH: usize = 0; fn bounds() -> (usize, usize, usize, usize) { let guard = unsafe { core::ptr::addr_of!(A.guard) as usize }; let a_start = unsafe { core::ptr::addr_of!(A.stack) as usize }; let b_start = unsafe { core::ptr::addr_of!(B.0) as usize }; (guard, a_start, a_start + STACK_BYTES, b_start) } fn fail(reason: &str) -> ! { write_str("TURKOS/TASK-GUARD-ERROR: "); write_line(reason); park() } #[inline(never)] fn consume_stack(depth: u64) -> u64 { // Every byte is volatile before and after the recursive call, so the // optimizer cannot shrink the 512-byte live frame to two scalar slots. // Guard failure is expected well before the artificial depth cap. let mut frame = [0u8; 512]; unsafe { write_volatile(core::ptr::addr_of_mut!(MAX_DEPTH), depth as usize) }; for index in 0..frame.len() { unsafe { write_volatile(frame.as_mut_ptr().add(index), (depth as u8) ^ index as u8) }; } let nested = if depth < 128 { consume_stack(depth + 1) } else { 0 }; let mut checksum = nested; for index in 0..frame.len() { checksum += unsafe { read_volatile(frame.as_ptr().add(index)) } as u64; } checksum } extern "C" fn entry_a() -> ! { unsafe { write_volatile(core::ptr::addr_of_mut!(A_STARTED), 1) }; write_line("TURKOS/TASK-GUARD-A: START"); let _ = consume_stack(0); fail("A recursion returned without guard fault") } extern "C" fn entry_b() -> ! { let (guard, a_start, _a_top, b_start) = bounds(); let b_top = b_start + STACK_BYTES; let mut sp: usize; unsafe { asm!("mov {value}, sp", value = out(reg) sp, options(nomem, nostack, preserves_flags)) }; if unsafe { read_volatile(core::ptr::addr_of!(A_STOPPED)) } != 1 || sp < b_start + 16 || sp > b_top || unsafe { read_volatile(b_start as *const u64) } != B_CANARY || unsafe { read_volatile(core::ptr::addr_of!(__stack_start)) } != BOOT_CANARY { fail("healthy B task state or stack"); } write_str("TURKOS/TASK-GUARD-B: RESUMED SP=0x"); write_hex(sp); write_str(" GUARD=0x"); write_hex(guard); write_str(" A-START=0x"); write_hex(a_start); write_str(" B-CANARY=0x"); write_hex(B_CANARY as usize); write_str(" BOOT-CANARY=0x"); write_hex(BOOT_CANARY as usize); write_str("\r\n"); write_line("TURKOS/TASK-GUARD: PASS"); park() } pub(super) fn smoke_test(tables: &mmu::Tables, layout: mmu::Layout) -> ! { let (guard, a_start, a_top, b_start) = bounds(); let b_top = b_start + STACK_BYTES; let boot_start = core::ptr::addr_of!(__stack_start) as usize; let boot_top = core::ptr::addr_of!(__stack_top) as usize; if guard & 4095 != 0 || a_start != guard + 4096 || b_start & 4095 != 0 || a_top > b_start && b_top > guard || boot_start + 16 >= boot_top { fail("stack regions overlap or misalign"); } unsafe { write_volatile(b_start as *mut u64, B_CANARY); write_volatile(core::ptr::addr_of_mut!(__stack_start), BOOT_CANARY); } // SAFETY: this one-CPU guest owns the BSS page and first-RAM L3 table. // No task has entered yet. The page below A's stack is disconnected // before its VA can be touched by the recursive task. if unsafe { tables.unmap_task_stack_guard( guard as u64, a_start as u64, layout.image_start, layout.image_end, ) } .is_err() { fail("guard descriptor ownership"); } unsafe { asm!( "dsb ishst", "tlbi vaae1is, {page}", "dsb ish", "isb", page = in(reg) guard >> 12, options(nostack) ); } if !tables.task_stack_guard_unmapped(guard as u64) { fail("guard descriptor remained valid"); } write_line("TURKOS/TASK-GUARD-DESCRIPTOR: INVALID"); write_str("TURKOS/TASK-GUARD-REGION: GUARD=0x"); write_hex(guard); write_str(" A-START=0x"); write_hex(a_start); write_str(" A-TOP=0x"); write_hex(a_top); write_str(" B-START=0x"); write_hex(b_start); write_str(" B-TOP=0x"); write_hex(b_top); write_str(" BOOT-START=0x"); write_hex(boot_start); write_str(" BOOT-TOP=0x"); write_hex(boot_top); write_str("\r\n"); write_line("TURKOS/TASK-GUARD: ARMED"); // SP_EL1 remains the boot/exception stack. A runs at privileged EL1t // using only SP_EL0; its synchronous fault enters vector slot zero. unsafe { asm!( "msr SP_EL0, {stack}", "msr ELR_EL1, {entry}", "msr SPSR_EL1, {mode}", "isb", "eret", stack = in(reg) a_top, entry = in(reg) entry_a as *const () as usize, mode = in(reg) EL1T_MASKED, options(noreturn) ) } } #[unsafe(no_mangle)] pub extern "C" fn kernel_task_guard_exception( esr: usize, far: usize, elr: usize, task_sp: usize, spsr: usize, handler_sp: usize, ) -> ! { let (guard, a_start, _a_top, b_start) = bounds(); let boot_start = core::ptr::addr_of!(__stack_start) as usize; let boot_top = core::ptr::addr_of!(__stack_top) as usize; let text_start = core::ptr::addr_of!(_text_start) as usize; let text_end = core::ptr::addr_of!(_text_end) as usize; let depth = unsafe { read_volatile(core::ptr::addr_of!(MAX_DEPTH)) }; if unsafe { read_volatile(core::ptr::addr_of!(A_STARTED)) } != 1 || unsafe { read_volatile(core::ptr::addr_of!(A_STOPPED)) } != 0 || (esr >> 26) & 0x3f != 0x25 || esr & 0x3f != 0x07 || esr & (1 << 6) == 0 || far < guard || far >= a_start || task_sp < guard || task_sp >= a_start + 512 || task_sp & 15 != 0 || spsr & 15 != 4 || handler_sp < boot_start + 16 || handler_sp > boot_top || elr < text_start || elr >= text_end || !(20..128).contains(&depth) || unsafe { read_volatile(b_start as *const u64) } != B_CANARY || unsafe { read_volatile(core::ptr::addr_of!(__stack_start)) } != BOOT_CANARY { fail("unexpected fault class, page, task SP or handler stack"); } write_str("TURKOS/TASK-GUARD-FAULT: SLOT=0x00 EC=0x25 DFSC=0x07 WNR=0x01 ESR=0x"); write_hex(esr); write_str(" FAR=0x"); write_hex(far); write_str(" SP=0x"); write_hex(task_sp); write_str(" HANDLER-SP=0x"); write_hex(handler_sp); write_str(" ELR=0x"); write_hex(elr); write_str(" SPSR=0x"); write_hex(spsr); write_str(" DEPTH=0x"); write_hex(depth); write_str("\r\n"); unsafe { write_volatile(core::ptr::addr_of_mut!(A_STOPPED), 1) }; write_line("TURKOS/TASK-GUARD-A: STOPPED"); let b_top = b_start + STACK_BYTES; unsafe { asm!( "msr SP_EL0, {stack}", "msr ELR_EL1, {entry}", "msr SPSR_EL1, {mode}", "isb", "eret", stack = in(reg) b_top, entry = in(reg) entry_b as *const () as usize, mode = in(reg) EL1T_MASKED, options(noreturn) ) } }