//! Install a bounded two-segment ELF into real owned EL0 pages, then run its //! file-backed instructions. The last instruction deliberately faults when //! EL0 tries to write the R|X code page. use super::*; use turkos_kernel::elf::{self, Error}; const USER_TOKEN: usize = 0x5a5a; static mut ROOT: u64 = 0; static mut CODE_FRAME: u64 = 0; static mut DATA_FRAME: u64 = 0; static mut READY: usize = 0; unsafe extern "C" { static __user_blob_start: u8; static __user_blob_end: u8; } fn fail(reason: &str) -> ! { write_str("TURKOS/ELF-LOAD-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()); // PT_LOAD R|X, file-backed instructions at 0x80000000. 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(&44u64.to_le_bytes()); file[104..112].copy_from_slice(&48u64.to_le_bytes()); file[112..120].copy_from_slice(&1u64.to_le_bytes()); // PT_LOAD R|W, four file bytes and twelve BSS 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(44) { fail("ELF user instruction length"); } unsafe { core::ptr::copy_nonoverlapping(start as *const u8, file[320..364].as_mut_ptr(), 44) }; file } pub(super) fn smoke_test( allocator: &mut memory::FrameAllocator, tables: &mmu::Tables, ram: mmu::Layout, ) -> ! { let file = fixture(); let before = allocator.remaining_pages(); let user = unsafe { tables.build_user_space(allocator, ram.ram_base, ram.ram_size) } .unwrap_or_else(|_| fail("owned user-space allocation")); if !user.contract_valid(tables.root) || allocator.remaining_pages() + 6 != before { fail("RO code / RW data descriptor contract"); } unsafe { write_volatile(user.code_frame() as *mut u8, 0xa5); write_volatile(user.data_frame() as *mut u8, 0x5a); } let mut bad = file; bad[56..58].copy_from_slice(&3u16.to_le_bytes()); let duplicate: [u8; 56] = bad[64..120].try_into().unwrap(); bad[176..232].copy_from_slice(&duplicate); if unsafe { elf::load_into_user_space(&bad, &user, tables.root) } != Err(Error::OverlappingSegments) { fail("overlap rejection"); } bad = file; bad[24..32].copy_from_slice(&mmu::USER_DATA_VA.to_le_bytes()); if unsafe { elf::load_into_user_space(&bad, &user, tables.root) } != Err(Error::BadEntry) { fail("entry rejection"); } bad = file; bad[124..128].copy_from_slice(&5u32.to_le_bytes()); if unsafe { elf::load_into_user_space(&bad, &user, tables.root) } != Err(Error::BadSegmentPermissions) { fail("data permission rejection"); } if unsafe { read_volatile(user.code_frame() as *const u8) } != 0xa5 || unsafe { read_volatile(user.data_frame() as *const u8) } != 0x5a { fail("rejected file modified destination"); } write_line("TURKOS/ELF-LOAD-PREFLIGHT: OVERLAP=REJECT ENTRY=REJECT PERM=REJECT UNCHANGED=PASS"); let loaded = unsafe { elf::load_into_user_space(&file, &user, tables.root) } .unwrap_or_else(|_| fail("valid ELF install")); if loaded.entry != mmu::USER_CODE_VA || loaded.code_bytes != 44 || loaded.data_bytes != 4 || unsafe { read_volatile(user.code_frame() as *const u32) } != 0xd28b4b40 || unsafe { read_volatile(user.data_frame() as *const u32) } != 0x41544144 || unsafe { read_volatile((user.data_frame() + 4) as *const u32) } != 0 || unsafe { read_volatile((user.data_frame() + 12) as *const u32) } != 0 || unsafe { read_volatile((user.code_frame() + 44) as *const u32) } != 0 { fail("installed bytes or zero tail"); } write_str("TURKOS/ELF-LOAD-INSTALL: ENTRY=0x"); write_hex(loaded.entry as usize); write_str(" CODE=0x"); write_hex(loaded.code_bytes); write_str(" DATA=0x"); write_hex(loaded.data_bytes); write_str(" CANARY=0x"); write_hex(unsafe { read_volatile(user.data_frame() as *const u32) } as usize); write_str(" BSS=0x"); write_hex(unsafe { read_volatile((user.data_frame() + 4) as *const u32) } as usize); write_str("\r\nTURKOS/ELF-LOAD-DESC: CODE=0x"); write_hex(user.descriptor(0).unwrap() as usize); write_str(" DATA=0x"); write_hex(user.descriptor(1).unwrap() as usize); write_str(" GUARD=0x"); write_hex(user.descriptor(2).unwrap() as usize); write_str("\r\n"); // File-backed instructions were written through EL1 identity mapping. // Publish them to the EL0 instruction view before switching TTBR0. 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!(READY), 1); asm!( "dsb ishst", "msr TTBR0_EL1, {root}", "isb", "tlbi vmalle1is", "dsb ish", "isb", root = in(reg) user.root, options(nostack) ); } write_line("TURKOS/ELF-LOAD-EL0: ATTEMPT"); unsafe { asm!( "msr SP_EL0, {stack}", "msr ELR_EL1, {entry}", "msr SPSR_EL1, {mode}", "isb", "eret", stack = in(reg) mmu::USER_STACK_TOP, 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_frame = unsafe { read_volatile(core::ptr::addr_of!(CODE_FRAME)) }; let data_frame = unsafe { read_volatile(core::ptr::addr_of!(DATA_FRAME)) }; if unsafe { read_volatile(core::ptr::addr_of!(READY)) } != 1 || (esr >> 26) & 0x3f != 0x24 || esr & 0x3f != 0x0f || esr & (1 << 6) == 0 || elr != (mmu::USER_CODE_VA + 32) as usize || far != mmu::USER_CODE_VA as usize || user_sp != mmu::USER_STACK_TOP as usize || spsr & 15 != 0 || root != unsafe { read_volatile(core::ptr::addr_of!(ROOT)) } as usize || token != USER_TOKEN || unsafe { read_volatile(code_frame as *const u32) } != 0xd28b4b40 || unsafe { read_volatile(data_frame as *const usize) } != USER_TOKEN || unsafe { read_volatile((data_frame + 4) as *const u32) } != 0 { fail("EL0 file-backed code/data permission witness"); } write_str("TURKOS/ELF-LOAD-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(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" CODE-WORD=0x"); write_hex(unsafe { read_volatile(code_frame as *const u32) } as usize); write_str(" DATA-WORD=0x"); write_hex(unsafe { read_volatile(data_frame as *const usize) }); write_str(" BSS=0x"); write_hex(unsafe { read_volatile((data_frame + 4) as *const u32) } as usize); write_str("\r\n"); write_line("TURKOS/ELF-LOAD: PASS"); park() }