//! ELF-owned user stack and bounded argv live experiment. Its file-backed //! program reads argc and argv[0][0], writes a stack witness, restores SP, //! writes the separate data page, then faults on the read-only code page. use super::*; use turkos_kernel::elf::{self, Error}; static mut ROOT: u64 = 0; static mut CODE_FRAME: u64 = 0; static mut DATA_FRAME: u64 = 0; static mut STACK_FRAME: u64 = 0; static mut INITIAL_SP: u64 = 0; unsafe extern "C" { static __user_blob_start: u8; static __user_blob_end: u8; } fn fail(reason: &str) -> ! { write_str("TURKOS/ELF-STACK-ERROR: "); write_line(reason); park() } fn fixture() -> [u8; 512] { let mut file = [0u8; 512]; file[..20].copy_from_slice(&[ 0x7f, b'E', b'L', b'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0xb7, 0, ]); file[20..24].copy_from_slice(&1u32.to_le_bytes()); file[24..32].copy_from_slice(&mmu::USER_CODE_VA.to_le_bytes()); file[32..40].copy_from_slice(&64u64.to_le_bytes()); file[52..54].copy_from_slice(&64u16.to_le_bytes()); file[54..56].copy_from_slice(&56u16.to_le_bytes()); file[56..58].copy_from_slice(&2u16.to_le_bytes()); file[64..68].copy_from_slice(&1u32.to_le_bytes()); file[68..72].copy_from_slice(&5u32.to_le_bytes()); file[72..80].copy_from_slice(&320u64.to_le_bytes()); file[80..88].copy_from_slice(&mmu::USER_CODE_VA.to_le_bytes()); file[96..104].copy_from_slice(&76u64.to_le_bytes()); file[104..112].copy_from_slice(&80u64.to_le_bytes()); file[112..120].copy_from_slice(&1u64.to_le_bytes()); file[120..124].copy_from_slice(&1u32.to_le_bytes()); file[124..128].copy_from_slice(&6u32.to_le_bytes()); file[128..136].copy_from_slice(&448u64.to_le_bytes()); file[136..144].copy_from_slice(&mmu::USER_DATA_VA.to_le_bytes()); file[152..160].copy_from_slice(&4u64.to_le_bytes()); file[160..168].copy_from_slice(&16u64.to_le_bytes()); file[168..176].copy_from_slice(&1u64.to_le_bytes()); file[448..452].copy_from_slice(b"DATA"); let start = core::ptr::addr_of!(__user_blob_start) as usize; let end = core::ptr::addr_of!(__user_blob_end) as usize; if end.checked_sub(start) != Some(76) { fail("EL0 argv program length"); } unsafe { core::ptr::copy_nonoverlapping(start as *const u8, file[320..396].as_mut_ptr(), 76) }; file } pub(super) fn smoke_test( allocator: &mut memory::FrameAllocator, tables: &mmu::Tables, ram: mmu::Layout, ) -> ! { let file = fixture(); let user = unsafe { tables.build_user_space(allocator, ram.ram_base, ram.ram_size) } .unwrap_or_else(|_| fail("owned user stack allocation")); if !user.contract_valid(tables.root) { fail("private stack/data/code descriptors"); } let stack = unsafe { core::slice::from_raw_parts_mut(user.stack_frame() as *mut u8, memory::PAGE_SIZE as usize) }; stack[0] = 0xa5; let five: [&[u8]; 5] = [b"a", b"b", b"c", b"d", b"e"]; if elf::prepare_user_stack(stack, &five) != Err(Error::BadArguments) || elf::prepare_user_stack(stack, &[&[b'x'; 33]]) != Err(Error::BadArguments) || elf::prepare_user_stack(stack, &[b"a\0b"]) != Err(Error::BadArguments) || stack[0] != 0xa5 { fail("argument preflight changed stack"); } write_line("TURKOS/ELF-STACK-ARGS: COUNT=REJECT LENGTH=REJECT NUL=REJECT UNCHANGED=PASS"); let start = elf::prepare_user_stack(stack, &[b"hello", b"TurkOS"]) .unwrap_or_else(|_| fail("valid initial argv")); let base = memory::PAGE_SIZE as usize - 256; if start.sp % 16 != 0 || start.sp != mmu::USER_STACK_TOP - 256 || start.argc != 2 || start.argv != start.sp + 16 || stack[base + 64..base + 70] != *b"hello\0" || stack[base + 70..base + 77] != *b"TurkOS\0" || stack[base + 32..base + 40] != [0; 8] { fail("initial stack ABI layout"); } write_str("TURKOS/ELF-STACK-LAYOUT: SP=0x"); write_hex(start.sp as usize); write_str(" ARGC=0x"); write_hex(start.argc); write_str(" ARGV=0x"); write_hex(start.argv as usize); write_str(" ARG0=0x"); write_hex((start.sp + 64) as usize); write_str(" ARG1=0x"); write_hex((start.sp + 70) as usize); write_str("\r\n"); let loaded = unsafe { elf::load_into_user_space(&file, &user, tables.root) } .unwrap_or_else(|_| fail("ELF two-segment install")); if loaded.code_bytes != 76 || loaded.data_bytes != 4 || loaded.entry != mmu::USER_CODE_VA { fail("ELF stack program layout"); } let ctr: usize; unsafe { asm!("mrs {value}, CTR_EL0", value = out(reg) ctr, options(nomem, nostack, preserves_flags)) }; let dline = 4usize << ((ctr >> 16) & 15); for offset in (0..loaded.code_bytes).step_by(dline) { unsafe { asm!("dc cvau, {addr}", addr = in(reg) (user.code_frame() as usize + offset), options(nostack)) }; } unsafe { asm!("dsb ish", "ic iallu", "dsb ish", "isb", options(nostack)) }; unsafe { write_volatile(core::ptr::addr_of_mut!(ROOT), user.root); write_volatile(core::ptr::addr_of_mut!(CODE_FRAME), user.code_frame()); write_volatile(core::ptr::addr_of_mut!(DATA_FRAME), user.data_frame()); write_volatile(core::ptr::addr_of_mut!(STACK_FRAME), user.stack_frame()); write_volatile(core::ptr::addr_of_mut!(INITIAL_SP), start.sp); asm!( "dsb ishst", "msr TTBR0_EL1, {root}", "isb", "tlbi vmalle1is", "dsb ish", "isb", root = in(reg) user.root, options(nostack) ); } write_line("TURKOS/ELF-STACK-EL0: ATTEMPT"); unsafe { asm!( "msr SP_EL0, {stack}", "msr ELR_EL1, {entry}", "msr SPSR_EL1, {mode}", "isb", "eret", stack = in(reg) start.sp, entry = in(reg) loaded.entry, mode = in(reg) 0x3c0usize, options(noreturn) ) } } #[unsafe(no_mangle)] pub extern "C" fn kernel_user_first_exception( esr: usize, elr: usize, user_sp: usize, spsr: usize, root: usize, token: usize, far: usize, ) -> ! { let code = unsafe { read_volatile(core::ptr::addr_of!(CODE_FRAME)) }; let data = unsafe { read_volatile(core::ptr::addr_of!(DATA_FRAME)) }; let stack = unsafe { read_volatile(core::ptr::addr_of!(STACK_FRAME)) }; let sp = unsafe { read_volatile(core::ptr::addr_of!(INITIAL_SP)) }; let argc = unsafe { read_volatile((stack + memory::PAGE_SIZE - 256 + 208) as *const u64) }; let first = unsafe { read_volatile((stack + memory::PAGE_SIZE - 256 + 216) as *const u64) }; if (esr >> 26) & 0x3f != 0x24 || esr & 0x3f != 0x0f || esr & (1 << 6) == 0 || elr != (mmu::USER_CODE_VA + 64) as usize || far != mmu::USER_CODE_VA as usize || user_sp != sp as usize || spsr & 15 != 0 || root != unsafe { read_volatile(core::ptr::addr_of!(ROOT)) } as usize || token != 0x5a5a || argc != 2 || first != b'h' as u64 || unsafe { read_volatile(code as *const u32) } != 0xd28b4b40 || unsafe { read_volatile(data as *const u64) } != 0x5a5a { fail("EL0 argc/argv/push-pop witness"); } write_str("TURKOS/ELF-STACK-FAULT: ESR=0x"); write_hex(esr); write_str(" ELR=0x"); write_hex(elr); write_str(" FAR=0x"); write_hex(far); write_str(" SP=0x"); write_hex(user_sp); write_str(" ARGC=0x"); write_hex(argc as usize); write_str(" FIRST=0x"); write_hex(first as usize); write_str(" DATA=0x"); write_hex(unsafe { read_volatile(data as *const u64) } as usize); write_str("\r\n"); write_line("TURKOS/ELF-STACK: PASS"); park() }