//! First genuinely executed EL0 image: private owned TTBR0 root, separate //! RO+X code / RW+XN data / unmapped guard / RW+XN stack, then SVC #0x42. use super::*; const USER_TOKEN: usize = 0x5a5a; const EL0T_MASKED: usize = 0x3c0; static mut USER_ROOT: u64 = 0; static mut USER_CODE_FRAME: u64 = 0; static mut USER_DATA_FRAME: u64 = 0; static mut USER_STACK_FRAME: u64 = 0; static mut USER_READY: usize = 0; static mut USER_SVC_COUNT: usize = 0; unsafe extern "C" { static __user_blob_start: u8; static __user_blob_end: u8; } fn fail(reason: &str) -> ! { write_str("TURKOS/USER-ERROR: "); write_line(reason); park() } pub(super) fn smoke_test( allocator: &mut memory::FrameAllocator, tables: &mmu::Tables, layout: mmu::Layout, ) -> ! { let before = allocator.remaining_pages(); let user = unsafe { tables.build_user_space(allocator, layout.ram_base, layout.ram_size) } .unwrap_or_else(|_| fail("private root allocation")); if !user.contract_valid(tables.root) || allocator.remaining_pages() + 6 != before { fail("root ownership or descriptor permissions"); } let kernel_wx = tables .audit_wx(None) .unwrap_or_else(|_| fail("kernel W+X audit")); let user_wx = tables .audit_wx(Some(&user)) .unwrap_or_else(|_| fail("user W+X audit")); if kernel_wx.leaves == 0 || user_wx.leaves <= kernel_wx.leaves { fail("W+X audit coverage"); } write_str("TURKOS/WX-SCAN: KERNEL-TABLES=0x"); write_hex(kernel_wx.tables); write_str(" KERNEL-LEAVES=0x"); write_hex(kernel_wx.leaves); write_str(" USER-TABLES=0x"); write_hex(user_wx.tables); write_str(" USER-LEAVES=0x"); write_hex(user_wx.leaves); write_line(" PASS"); let start = core::ptr::addr_of!(__user_blob_start) as usize; let end = core::ptr::addr_of!(__user_blob_end) as usize; let length = end.checked_sub(start).unwrap_or(0); if length == 0 || length > memory::PAGE_SIZE as usize || length != 36 { fail("user instruction blob length"); } // SAFETY: all six frames are exclusively owned, identity mapped and // retained while the user root is active. Only the code frame receives // executable bytes; the other data pages were zeroed by the builder. unsafe { core::ptr::copy_nonoverlapping(start as *const u8, user.code_frame() as *mut u8, length) }; 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..length).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!(USER_ROOT), user.root); write_volatile(core::ptr::addr_of_mut!(USER_CODE_FRAME), user.code_frame()); write_volatile(core::ptr::addr_of_mut!(USER_DATA_FRAME), user.data_frame()); write_volatile( core::ptr::addr_of_mut!(USER_STACK_FRAME), user.stack_frame(), ); write_volatile(core::ptr::addr_of_mut!(USER_READY), 1); } write_str("TURKOS/USER-WINDOW: RAM-END=0x"); write_hex((layout.ram_base + layout.ram_size) as usize); write_str(" CODE-VA=0x"); write_hex(mmu::USER_CODE_VA as usize); write_str(" DATA-VA=0x"); write_hex(mmu::USER_DATA_VA as usize); write_str(" GUARD-VA=0x"); write_hex(mmu::USER_GUARD_VA as usize); write_str(" STACK-VA=0x"); write_hex(mmu::USER_STACK_VA as usize); write_str(" STACK-TOP=0x"); write_hex(mmu::USER_STACK_TOP as usize); write_str("\r\nTURKOS/USER-ROOT: KERNEL=0x"); write_hex(tables.root as usize); write_str(" PRIVATE=0x"); write_hex(user.root as usize); write_str(" BEFORE=0x"); write_hex(before as usize); write_str(" AFTER=0x"); write_hex(allocator.remaining_pages() as usize); write_str("\r\nTURKOS/USER-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(" STACK=0x"); write_hex(user.descriptor(3).unwrap() as usize); write_str("\r\n"); unsafe { asm!( "dsb ishst", "msr TTBR0_EL1, {root}", "isb", "tlbi vmalle1is", "dsb ish", "isb", root = in(reg) user.root, options(nostack) ) }; let actual_root: u64; unsafe { asm!("mrs {value}, TTBR0_EL1", value = out(reg) actual_root, options(nomem, nostack, preserves_flags)) }; if actual_root != user.root { fail("TTBR0 private root readback"); } write_str("TURKOS/USER-TTBR: 0x"); write_hex(actual_root as usize); write_str("\r\n"); write_line("TURKOS/USER-ENTRY: EL0"); 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) mmu::USER_CODE_VA, mode = in(reg) EL0T_MASKED, 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 count = unsafe { read_volatile(core::ptr::addr_of!(USER_SVC_COUNT)) }; let expected_root = unsafe { read_volatile(core::ptr::addr_of!(USER_ROOT)) }; #[cfg(feature = "user-code-write-test")] { let code_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_CODE_FRAME)) }; if unsafe { read_volatile(core::ptr::addr_of!(USER_READY)) } != 1 || count != 0 || (esr >> 26) & 0x3f != 0x24 || esr & 0x3f != 0x0f || esr & (1 << 6) == 0 || elr != (mmu::USER_CODE_VA + 12) as usize || far != mmu::USER_CODE_VA as usize || user_sp != mmu::USER_STACK_TOP as usize || spsr & 15 != 0 || root != expected_root as usize || token != USER_TOKEN || unsafe { read_volatile(code_frame as *const u32) } != 0xd28b4b40 { fail("EL0 code write fault witness"); } unsafe { write_volatile(core::ptr::addr_of_mut!(USER_SVC_COUNT), 1) }; write_str("TURKOS/USER-CODE-WRITE: SLOT=0x08 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(" SPSR=0x"); write_hex(spsr); 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("\r\n"); write_line("TURKOS/USER-CODE-WRITE: PASS"); park() } #[cfg(feature = "user-data-exec-test")] { let data_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_DATA_FRAME)) }; if unsafe { read_volatile(core::ptr::addr_of!(USER_READY)) } != 1 || count != 0 || (esr >> 26) & 0x3f != 0x20 || esr & 0x3f != 0x0f || elr != mmu::USER_DATA_VA as usize || far != mmu::USER_DATA_VA as usize || user_sp != mmu::USER_STACK_TOP as usize || spsr & 15 != 0 || root != expected_root as usize || token != USER_TOKEN || unsafe { read_volatile(data_frame as *const u32) } != 0 { fail("EL0 data execute fault witness"); } unsafe { write_volatile(core::ptr::addr_of_mut!(USER_SVC_COUNT), 1) }; write_str("TURKOS/USER-DATA-EXEC: SLOT=0x08 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(" SPSR=0x"); write_hex(spsr); write_str(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" DATA-WORD=0x"); write_hex(unsafe { read_volatile(data_frame as *const u32) } as usize); write_str("\r\n"); write_line("TURKOS/USER-DATA-EXEC: PASS"); park() } #[cfg(feature = "user-stack-overflow-test")] { let stack_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_STACK_FRAME)) }; let guard_target = mmu::USER_STACK_VA as usize - 16; if unsafe { read_volatile(core::ptr::addr_of!(USER_READY)) } != 1 || count != 0 || (esr >> 26) & 0x3f != 0x24 || esr & 0x3f != 0x07 || esr & (1 << 6) == 0 || elr != (mmu::USER_CODE_VA + 12) as usize || far != guard_target || user_sp != guard_target || spsr & 15 != 0 || root != expected_root as usize || token != USER_TOKEN || unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) } != 0 { fail("EL0 stack guard overflow witness"); } unsafe { write_volatile(core::ptr::addr_of_mut!(USER_SVC_COUNT), 1) }; write_str("TURKOS/USER-STACK-OVERFLOW: SLOT=0x08 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(" SPSR=0x"); write_hex(spsr); write_str(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" STACK-TAIL=0x"); write_hex(unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) }); write_str("\r\n"); write_line("TURKOS/USER-STACK-OVERFLOW: PASS"); park() } #[cfg(feature = "user-align-test")] { let data_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_DATA_FRAME)) }; let stack_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_STACK_FRAME)) }; write_str("TURKOS/USER-ALIGN-OBSERVED: 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(" SPSR=0x"); write_hex(spsr); write_str(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" DATA=0x"); write_hex(unsafe { read_volatile(data_frame as *const usize) }); write_str(" STACK=0x"); write_hex(unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) }); write_str("\r\n"); if unsafe { read_volatile(core::ptr::addr_of!(USER_READY)) } != 1 || count != 0 || (esr >> 26) & 0x3f != 0x24 // QEMU/HVF classifies this exclusive-load failure as the // implementation-defined unsupported Exclusive/Atomic DFSC 0x35. || esr & 0x3f != 0x35 || esr & (1 << 6) != 0 || elr != (mmu::USER_CODE_VA + 16) as usize || far != (mmu::USER_DATA_VA + 1) as usize || user_sp != mmu::USER_STACK_TOP as usize || spsr & 15 != 0 || root != expected_root as usize || token != USER_TOKEN || unsafe { read_volatile(data_frame as *const usize) } != 0 || unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) } != 0 { fail("EL0 unaligned exclusive load witness"); } unsafe { write_volatile(core::ptr::addr_of_mut!(USER_SVC_COUNT), 1) }; write_str("TURKOS/USER-ALIGN: SLOT=0x08 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(" SPSR=0x"); write_hex(spsr); write_str(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" DATA=0x"); write_hex(unsafe { read_volatile(data_frame as *const usize) }); write_str(" STACK=0x"); write_hex(unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) }); write_str("\r\n"); write_line("TURKOS/USER-ALIGN: PASS"); park() } #[cfg(feature = "user-first-test")] { let _ = far; let data_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_DATA_FRAME)) }; let stack_frame = unsafe { read_volatile(core::ptr::addr_of!(USER_STACK_FRAME)) }; if unsafe { read_volatile(core::ptr::addr_of!(USER_READY)) } != 1 || count != 0 || (esr >> 26) & 0x3f != 0x15 || esr & 0xffff != 0x42 || elr != (mmu::USER_CODE_VA + 32) as usize || user_sp != mmu::USER_STACK_TOP as usize || spsr & 15 != 0 || root != expected_root as usize || token != USER_TOKEN || unsafe { read_volatile(data_frame as *const usize) } != USER_TOKEN || unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) } != USER_TOKEN { fail("EL0 SVC, data or stack witness"); } unsafe { write_volatile(core::ptr::addr_of_mut!(USER_SVC_COUNT), 1) }; write_str("TURKOS/USER-SVC: SLOT=0x08 EC=0x15 IMM=0x0042 ESR=0x"); write_hex(esr); write_str(" ELR=0x"); write_hex(elr); write_str(" SP=0x"); write_hex(user_sp); write_str(" SPSR=0x"); write_hex(spsr); write_str(" TTBR=0x"); write_hex(root); write_str(" X0=0x"); write_hex(token); write_str(" DATA=0x"); write_hex(unsafe { read_volatile(data_frame as *const usize) }); write_str(" STACK=0x"); write_hex(unsafe { read_volatile((stack_frame + memory::PAGE_SIZE - 16) as *const usize) }); write_str("\r\n"); write_line("TURKOS/USER: PASS"); park() } }