// K1375 experimental EL0 shared-frame handle and mapping lifetime probe. use super::*; use turkos_kernel::runtime::process_guest::{self, SharedObject}; use turkos_kernel::runtime::process_table::{Outcome, ProcessId}; use turkos_kernel::runtime::syscall_abi as abi; use super::k1365_shared_model::{Error, Handle, SharedPages}; use super::k1370_seal_model::{self as seal, Permission, SealedPages}; use super::k1375_lifetime_model::{self as lifetime, Lifetime}; static mut OS_K1375_OBJECT: Option = None; static mut OS_K1375_MODEL: Option> = None; static mut OS_K1375_SEAL_MODEL: Option> = None; static mut OS_K1375_LIFETIME_MODEL: Option> = None; static mut OS_K1375_A_HANDLE: Option> = None; static mut OS_K1375_B_HANDLE: Option> = None; static mut OS_K1375_C_PID: Option = None; static mut OS_K1375_STAGE: u8 = 0; static mut OS_K1375_NEGATIVE_DONE: bool = false; static mut OS_K1375_B_CLOSED: bool = false; static mut OS_K1375_B_READ_AFTER_CLOSE: bool = false; static mut OS_K1375_A_CLOSED: bool = false; static mut OS_K1375_A_READ_AFTER_CLOSE: bool = false; static mut OS_K1375_EARLY_REFUSED: bool = false; static mut OS_K1375_DOUBLE_REFUSED: bool = false; const HEAP: u64 = process_guest::HEAP; const PAGE: u64 = process_guest::PAGE; fn object() -> &'static mut SharedObject { // Mode 64 runs on one CPU with IRQs masked and no concurrent object user. unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1375_OBJECT)) .as_mut().unwrap_or_else(|| park()) } } fn model() -> &'static mut SharedPages { unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1375_MODEL)) .as_mut().unwrap_or_else(|| park()) } } fn seal_model() -> &'static mut SealedPages { unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1375_SEAL_MODEL)) .as_mut().unwrap_or_else(|| park()) } } fn lifetime_model() -> &'static mut Lifetime { unsafe { (&mut *core::ptr::addr_of_mut!(OS_K1375_LIFETIME_MODEL)) .as_mut().unwrap_or_else(|| park()) } } fn a_handle() -> Handle { unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_HANDLE)) } .unwrap_or_else(|| park()) } fn b_handle() -> Handle { unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_HANDLE)) } .unwrap_or_else(|| park()) } fn stage() -> u8 { unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_STAGE)) } } fn set_stage(value: u8) { unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_STAGE), value); } } pub(super) fn begin() { unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_OBJECT), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_MODEL), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_SEAL_MODEL), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_LIFETIME_MODEL), None); write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_HANDLE), None); write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_HANDLE), None); write_volatile(core::ptr::addr_of_mut!(OS_K1375_C_PID), None); } set_stage(0); unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_NEGATIVE_DONE), false); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_CLOSED), false); write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_READ_AFTER_CLOSE), false); write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_CLOSED), false); write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_READ_AFTER_CLOSE), false); write_volatile(core::ptr::addr_of_mut!(OS_K1375_EARLY_REFUSED), false); write_volatile(core::ptr::addr_of_mut!(OS_K1375_DOUBLE_REFUSED), false); } } pub(super) fn finish() -> Result<(), &'static str> { let snapshot = model().snapshot(); let seal_snapshot = seal_model().snapshot(); let lifetime_snapshot = lifetime_model().snapshot(); if stage() != 17 || !object().released() || !object().sealed() || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_NEGATIVE_DONE)) } || !snapshot.closed || snapshot.mapped_count != 0 || snapshot.recipient.is_some() || snapshot.owner_maps != [false; 2] || snapshot.recipient_maps != [false; 2] || !seal_snapshot.sealed || seal_snapshot.contents != [0xa5, 0x5a] || seal_snapshot.owner_maps != [Permission::Unmapped; 2] || seal_snapshot.recipient_maps != [Permission::Unmapped; 2] || lifetime_snapshot.backing_live || lifetime_snapshot.free_count != 1 || lifetime_snapshot.owner_maps != [false; 2] || lifetime_snapshot.recipient_maps != [false; 2] || lifetime_snapshot.owner_handle || lifetime_snapshot.recipient_handle || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_CLOSED)) } || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_READ_AFTER_CLOSE)) } || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_CLOSED)) } || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_READ_AFTER_CLOSE)) } || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_EARLY_REFUSED)) } || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_DOUBLE_REFUSED)) } { return Err("shared page object or mapping leak"); } unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_OBJECT), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_MODEL), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_SEAL_MODEL), None); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_LIFETIME_MODEL), None); } Ok(()) } pub(super) fn dispatch( episode: &mut OsK1215AEl0Episode, frame: &mut os_k920_exception::Frame, process_table: &mut OsK1105Table<'_>, ) -> u64 { if frame.elr < episode.launch.user_pc || frame.elr >= episode.launch.user_pc + 4096 || !frame.elr.is_multiple_of(4) { park(); } if frame.x[7] == 3 { if process_table.record(episode.pid).is_err() { park(); } let pid = episode.pid; let arg = frame.x[0]; match frame.x[8] { 120 => { if episode.case != 0 || episode.calls != 1 || stage() != 1 || arg != 0 { park(); } let created = process_table.create_current_shared() .unwrap_or_else(|_| park()); let frames = [created.frame_address(0).unwrap_or_else(|| park()), created.frame_address(1).unwrap_or_else(|| park())]; let shared = SharedPages::new(pid, frames).unwrap_or_else(|_| park()); let seal_state = SealedPages::new(pid, frames) .unwrap_or_else(|_| park()); let lifetime_state = Lifetime::new(pid, frames) .unwrap_or_else(|_| park()); let owner_handle = shared.owner_handle(pid).unwrap_or_else(|_| park()); unsafe { core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_OBJECT), Some(created)); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_MODEL), Some(shared)); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_SEAL_MODEL), Some(seal_state)); core::ptr::write(core::ptr::addr_of_mut!(OS_K1375_LIFETIME_MODEL), Some(lifetime_state)); write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_HANDLE), Some(owner_handle)); } frame.x[0] = 2; set_stage(2); write_line("TURKOS/OS/K1375L-TRACE: TWO-ZEROED-PHYSICAL-FRAMES-OWNED"); } 121 | 122 => { let (expected, page, next) = if frame.x[8] == 121 { (2, 0, 3) } else { (3, 1, 4) }; if episode.case != 0 || episode.calls != 1 || stage() != expected || arg != 0 { park(); } let physical = model().map(pid, a_handle(), page) .unwrap_or_else(|_| park()); if object().frame_address(page) != Some(physical) { park(); } let virtual_address = process_table.map_current_shared(object(), page, page) .unwrap_or_else(|_| park()); if virtual_address != HEAP + page as u64 * PAGE { park(); } if seal_model().map(pid, page, true) != Ok((physical, Permission::ReadWrite)) { park(); } if lifetime_model().map(pid, page) != Ok(physical) { park(); } frame.x[0] = virtual_address; set_stage(next); write_line("TURKOS/OS/K1375L-TRACE: OWNER-EL0-SHARED-PAGE-MAPPED"); } 133 => { if episode.case != 0 || episode.calls != 1 || stage() != 4 || arg != 0 || object().sealed() { park(); } process_table.seal_current_shared(object()) .unwrap_or_else(|_| park()); if !object().sealed() { park(); } seal_model().write(pid, 0, 0xa5).unwrap_or_else(|_| park()); seal_model().write(pid, 1, 0x5a).unwrap_or_else(|_| park()); lifetime_model().write(pid, 0, 0xa5).unwrap_or_else(|_| park()); lifetime_model().write(pid, 1, 0x5a).unwrap_or_else(|_| park()); seal_model().seal(pid).unwrap_or_else(|_| park()); if seal_model().snapshot().owner_maps != [Permission::ReadOnly; 2] { park(); } frame.x[0] = 0; write_line("TURKOS/OS/K1375L-TRACE: OWNER-TWO-LIVE-PTES-DOWNGRADED-RO-TLBI"); } 134 => { if episode.case != 0 || episode.calls != 1 || stage() != 4 || arg != 0 || !object().sealed() || unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_NEGATIVE_DONE)) } { park(); } let frames = [object().frame_address(0), object().frame_address(1)]; let before = seal_model().snapshot(); if process_table.request_writable_current_shared(object(), 0, 0) != Err(abi::Errno::Access) || frames != [object().frame_address(0), object().frame_address(1)] || seal_model().map(pid, 1, true) != Err(seal::Error::Access) || seal_model().snapshot() != before { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_NEGATIVE_DONE), true); } frame.x[0] = abi::AbiResult::Err(abi::Errno::Access).encode(); write_line("TURKOS/OS/K1375L-TRACE: POST-SEAL-RW-REQUEST-EACCES-NO-MAP"); } 123 | 124 => { let (expected, page, next) = if frame.x[8] == 123 { (5, 0, 6) } else { (6, 1, 7) }; if episode.case != 1 || episode.calls != 1 || stage() != expected || arg != 0 || !object().sealed() { park(); } let physical = model().map(pid, b_handle(), page) .unwrap_or_else(|_| park()); if object().frame_address(page) != Some(physical) { park(); } let virtual_address = process_table.map_current_shared(object(), page, page) .unwrap_or_else(|_| park()); if virtual_address != HEAP + page as u64 * PAGE { park(); } if seal_model().map(pid, page, false) != Ok((physical, Permission::ReadOnly)) { park(); } if lifetime_model().map(pid, page) != Ok(physical) { park(); } frame.x[0] = virtual_address; set_stage(next); write_line("TURKOS/OS/K1375L-TRACE: RECIPIENT-EL0-RO-SAME-FRAME-MAPPED"); } 125 => { if episode.case != 1 || stage() != 7 || arg != 0 { park(); } let c = unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_C_PID)) } .unwrap_or_else(|| park()); if process_table.record(c).is_err() || object().allows(c) { park(); } let before = model().snapshot(); if model().acquire(c, b_handle()) != Err(Error::Access) || model().acquire(c, Handle::forge(1, c)) != Err(Error::Access) || model().map(c, Handle::forge(1, c), 0) != Err(Error::Access) || model().snapshot() != before { park(); } frame.x[0] = abi::AbiResult::Err(abi::Errno::Access).encode(); set_stage(8); write_line("TURKOS/OS/K1375L-TRACE: THIRD-PUBLISHED-PROCESS-EACCES-NO-MAP"); } 126 => { if episode.case != 1 || stage() != 8 || arg != 0x5aa5 { park(); } if seal_model().read(pid, 0) != Ok(0xa5) || seal_model().read(pid, 1) != Ok(0x5a) || lifetime_model().read(pid, 0) != Ok(0xa5) || lifetime_model().read(pid, 1) != Ok(0x5a) { park(); } frame.x[0] = 2; set_stage(9); write_line("TURKOS/OS/K1375L-TRACE: EL0-TWO-PAGE-CONTENT-OBSERVED"); } 135 => { if episode.case != 1 || stage() != 9 || arg != 0 || unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_CLOSED)) } { park(); } process_table.close_current_shared_handle(object()) .unwrap_or_else(|_| park()); lifetime_model().close_handle(pid).unwrap_or_else(|_| park()); if object().allows(pid) || !object().may_unmap(pid) || !lifetime_model().snapshot().backing_live { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_CLOSED), true); } frame.x[0] = 0; write_line("TURKOS/OS/K1375L-TRACE: RECIPIENT-HANDLE-CLOSED-MAPS-LIVE"); } 136 => { if episode.case != 1 || stage() != 9 || arg != 0x5aa5 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_CLOSED)) } || lifetime_model().read(pid, 0) != Ok(0xa5) || lifetime_model().read(pid, 1) != Ok(0x5a) { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_READ_AFTER_CLOSE), true); } frame.x[0] = 2; write_line("TURKOS/OS/K1375L-TRACE: RECIPIENT-READ-AFTER-HANDLE-CLOSE"); } 137 => { if episode.case != 0 || episode.calls != 2 || stage() != 13 || arg != 0 || unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_CLOSED)) } { park(); } process_table.close_current_shared_handle(object()) .unwrap_or_else(|_| park()); lifetime_model().close_handle(pid).unwrap_or_else(|_| park()); if object().allows(pid) || !object().may_unmap(pid) || !object().handles_closed() || !lifetime_model().snapshot().backing_live { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_CLOSED), true); } frame.x[0] = 0; write_line("TURKOS/OS/K1375L-TRACE: OWNER-HANDLE-CLOSED-MAPS-LIVE"); } 138 => { if episode.case != 0 || episode.calls != 2 || stage() != 13 || arg != 0x5aa5 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_CLOSED)) } || lifetime_model().read(pid, 0) != Ok(0xa5) || lifetime_model().read(pid, 1) != Ok(0x5a) { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_A_READ_AFTER_CLOSE), true); } frame.x[0] = 2; write_line("TURKOS/OS/K1375L-TRACE: OWNER-READ-AFTER-HANDLE-CLOSE"); } 139 => { if episode.case != 0 || episode.calls != 2 || stage() != 13 || arg != 0 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_A_READ_AFTER_CLOSE)) } { park(); } let before = lifetime_model().snapshot(); if process_table.probe_early_release_current_shared(object()) != Err(abi::Errno::Again) || lifetime_model().free_backing(pid) != Err(lifetime::Error::Busy) || lifetime_model().snapshot() != before { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_EARLY_REFUSED), true); } frame.x[0] = abi::AbiResult::Err(abi::Errno::Again).encode(); write_line("TURKOS/OS/K1375L-TRACE: PINNED-BACKING-EARLY-FREE-REJECTED"); } 127 | 128 => { let (expected, slot, next) = if frame.x[8] == 127 { (9, 0, 10) } else { (10, 1, 11) }; if episode.case != 1 || stage() != expected || arg != 0 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_B_READ_AFTER_CLOSE)) } { park(); } process_table.unmap_current_shared(object(), slot) .unwrap_or_else(|_| park()); seal_model().unmap(pid, slot).unwrap_or_else(|_| park()); lifetime_model().unmap(pid, slot).unwrap_or_else(|_| park()); if slot == 1 { let parent = process_table.record(pid) .unwrap_or_else(|_| park()).parent.unwrap_or_else(|| park()); model().revoke(parent).unwrap_or_else(|_| park()); } frame.x[0] = 0; set_stage(next); write_line("TURKOS/OS/K1375L-TRACE: RECIPIENT-SHARED-PIN-RELEASED"); } 130 | 131 => { let (expected, slot, next) = if frame.x[8] == 130 { (13, 0, 14) } else { (14, 1, 15) }; if episode.case != 0 || episode.calls != 2 || stage() != expected || arg != 0 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_EARLY_REFUSED)) } || process_table.snapshot().published != 1 { park(); } process_table.unmap_current_shared(object(), slot) .unwrap_or_else(|_| park()); seal_model().unmap(pid, slot).unwrap_or_else(|_| park()); lifetime_model().unmap(pid, slot).unwrap_or_else(|_| park()); frame.x[0] = 0; set_stage(next); write_line("TURKOS/OS/K1375L-TRACE: OWNER-SHARED-PIN-RELEASED"); } 132 => { if episode.case != 0 || episode.calls != 2 || stage() != 15 || arg != 0 { park(); } process_table.destroy_current_shared(object()) .unwrap_or_else(|_| park()); if !object().released() { park(); } if lifetime_model().snapshot().free_count != 1 { park(); } model().close(pid).unwrap_or_else(|_| park()); if model().snapshot().mapped_count != 0 { park(); } frame.x[0] = 0; set_stage(16); write_line("TURKOS/OS/K1375L-TRACE: OBJECT-FRAMES-RELEASED-NO-PINS"); } 140 => { if episode.case != 0 || episode.calls != 2 || stage() != 16 || arg != 0 { park(); } if process_table.destroy_current_shared(object()) != Err(abi::Errno::Access) || lifetime_model().free_backing(pid) != Err(lifetime::Error::Stale) || lifetime_model().snapshot().free_count != 1 { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_DOUBLE_REFUSED), true); } frame.x[0] = abi::AbiResult::Err(abi::Errno::Access).encode(); write_line("TURKOS/OS/K1375L-TRACE: SECOND-FRAME-RELEASE-REJECTED"); } _ => { frame.x[0] = abi::AbiResult::Err(abi::Errno::NoSys).encode(); } } 0 } else if frame.x[7] == 2 && frame.x[8] == 3 { let request = turkos_os_user_runtime_abi::decode( frame.x[7], frame.x[8], core::array::from_fn(|i| frame.x[i])) .unwrap_or_else(|_| park()); let turkos_os_user_runtime_abi::Request::Exit(code) = request else { park(); }; if episode.case == 0 { if code != 0 || episode.calls != 2 || stage() != 16 || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_DOUBLE_REFUSED)) } { park(); } } else if code != 42 || episode.calls != 1 || stage() != 12 || model().snapshot().recipient_maps != [false; 2] { park(); } process_table.exit_current(code).unwrap_or_else(|_| park()); let outcome = Outcome::Exited(code); if process_table.record(episode.pid).map(|r| r.outcome) != Ok(Some(outcome)) { park(); } if episode.case == 1 { set_stage(13); } else { set_stage(17); } episode.outcome = Some(outcome); 1 } else { if frame.x[7] != abi::ABI_VERSION { park(); } let mut abi_frame = turkos_os_exceptions::exceptions::Frame::EMPTY; abi_frame.x = frame.x; let call = abi::decode_call(&abi_frame).unwrap_or_else(|_| park()); if call == abi::Call::Role { if episode.calls != 0 { park(); } if episode.case == 0 { if stage() != 0 { park(); } set_stage(1); write_line("TURKOS/OS/K1375L-TRACE: OWNER-SHELL-READY"); } else { if stage() != 4 || !object().sealed() { park(); } let parent = process_table.record(episode.pid) .unwrap_or_else(|_| park()).parent.unwrap_or_else(|| park()); if unsafe { read_volatile(core::ptr::addr_of!(OS_K1215A_INIT_PID)) } != Some(parent) { park(); } let published: [Option; 3] = { let mut rows = [None; 3]; for (index, id) in process_table.published().enumerate() { if index >= 3 { park(); } rows[index] = Some(id); } rows }; if published.iter().flatten().count() != 3 { park(); } let c = published.into_iter().flatten() .find(|id| *id != parent && *id != episode.pid) .unwrap_or_else(|| park()); if process_table.record(c).map(|r| r.parent) != Ok(Some(parent)) { park(); } object().grant(parent, episode.pid).unwrap_or_else(|_| park()); let handle = model().grant(parent, episode.pid) .unwrap_or_else(|_| park()); model().acquire(episode.pid, handle).unwrap_or_else(|_| park()); let seal_grant = seal_model().grant(parent, episode.pid) .unwrap_or_else(|_| park()); seal_model().acquire(episode.pid, seal_grant) .unwrap_or_else(|_| park()); lifetime_model().grant(parent, episode.pid) .unwrap_or_else(|_| park()); unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1375_B_HANDLE), Some(handle)); write_volatile(core::ptr::addr_of_mut!(OS_K1375_C_PID), Some(c)); } set_stage(5); write_line("TURKOS/OS/K1375L-TRACE: THREE-REAL-PROCESS-IDS-GRANT-B-ONLY"); } episode.calls = 1; frame.x[0] = episode.case as u64; return 0; } if let abi::Call::RunChild(1) = call { if episode.case != 0 || episode.calls != 1 || stage() != 4 || !object().sealed() || !unsafe { read_volatile(core::ptr::addr_of!(OS_K1375_NEGATIVE_DONE)) } { park(); } return os_k1215a_run_child(episode as *mut _, frame as *mut _, episode.launch.kernel_root); } if let abi::Call::Write(request) = call { if episode.case != 1 || request.fd() != 1 || request.bytes() != 16 || stage() != 11 { park(); } let mut bytes = [0u8; 16]; process_table.copy_current_user_data(episode.pid, request.address(), &mut bytes).unwrap_or_else(|_| park()); if &bytes != b"LIFE:ONCE:FREE!\n" { park(); } for byte in bytes { write_byte(byte); } frame.x[0] = 16; set_stage(12); write_line("TURKOS/OS/K1375L-TRACE: USER-WROTE-SHARED-RESULT"); return 0; } park() } }