#[path = "support/user_copy_fixture.rs"] mod fixture; use fixture::*; use turkos_os_exceptions::exceptions::{FRAME_CANARY, Frame}; use turkos_os_syscall_abi::syscall_abi::*; fn dispatcher() -> Dispatcher { Dispatcher::new(0, stack(), SysBackend::new()).unwrap_or_else(|_| panic!("valid dispatcher")) } fn run(d: &mut Dispatcher, f: &mut Frame) -> Result { d.fixture_backend().ingress(f, FRAME_ADDRESS); d.dispatch(f, FRAME_ADDRESS) } fn write_frame() -> Frame { frame(CALL_WRITE, [1, DATA_ADDRESS + 4095, 128, 0, 0, 0]) } fn reject(d: &mut Dispatcher, f: &mut Frame, error: Error) { assert!(matches!(run(d,f),Err(e)if e==error)); } fn unchanged_except_result(before: Frame, after: Frame) { let mut expected = before; expected.x[0] = after.x[0]; assert_eq!(after, expected) } #[test] fn known_version_unknown_full_number_and_unknown_version_return_typed_results_then_progress() { let mut d = dispatcher(); for (number, version, expected) in [ (CALL_VERSION, 1, AbiResult::Ok(Success::new(1).unwrap())), (u64::MAX, 1, AbiResult::Err(Errno::NoSys)), (0x1_0000_0000, 1, AbiResult::Err(Errno::NoSys)), (CALL_VERSION, 2, AbiResult::Err(Errno::NoSys)), (CALL_VERSION, 1, AbiResult::Ok(Success::new(1).unwrap())), ] { let mut f = frame(number, [0; 6]); f.x[7] = version; let before = f; let out = run(&mut d, &mut f).unwrap(); assert_eq!(out.result(), expected); assert_eq!(AbiResult::decode(f.x[0]), Ok(expected)); let r = out.into_return(); assert_eq!(r.user(), user()); assert_eq!(r.frame_address(), FRAME_ADDRESS); assert_eq!(r.entry(), f.elr); unchanged_except_result(before, f); } assert_eq!(d.observation().completed, 5); assert_eq!(d.observation().ingress_depth, 0); assert_eq!(d.fixture_backend().prepares, 0); } #[test] fn all_six_argument_widths_normalize_dirty_unsigned_and_signed_upper_words() { let raw = [ 0x8877_6655_4433_2211, 0xffff_eeee_0000_0007, 0xabcd_1234_ffff_fff9, 0x0123_4567_89ab_cdef, 0x8765_4321_ffff_ffff, 0x5555_5555_8000_0000, ]; let normalized = [ raw[0], 7, (-7i64) as u64, raw[3], u32::MAX as u64, (i32::MIN as i64) as u64, ]; assert_eq!( Arguments::normalize(raw, ARGUMENT_WIDTHS).values(), normalized ); let mut expected = 0u64; for (i, n) in normalized.iter().enumerate() { expected ^= n.rotate_left(i as u32 * 9) } expected &= i64::MAX as u64; let mut d = dispatcher(); let mut f = frame(CALL_ARGUMENTS, raw); let before = f; assert_eq!( run(&mut d, &mut f).unwrap().result(), AbiResult::Ok(Success::new(expected).unwrap()) ); unchanged_except_result(before, f); for i in 0..6 { let mut changed = raw; changed[i] ^= 1; let mut f = frame(CALL_ARGUMENTS, changed); assert_ne!(run(&mut d, &mut f).unwrap().result().encode(), expected); } assert_eq!(d.fixture_backend().prepares, 0); } #[test] fn encoded_errors_never_become_unsigned_lengths_and_invalid_negative_words_refuse() { for e in [ Errno::BadFd, Errno::Fault, Errno::NoMem, Errno::Access, Errno::Invalid, Errno::NoSys, Errno::Overflow, Errno::Io, ] { let r = AbiResult::Err(e); assert_eq!(AbiResult::decode(r.encode()), Ok(r)); assert_eq!(r.length(), Err(e)); assert!(Success::new(r.encode()).is_err()); } for n in [0, 1, 256, i64::MAX as u64] { assert_eq!( AbiResult::decode(n), Ok(AbiResult::Ok(Success::new(n).unwrap())) ); } assert_eq!( AbiResult::decode((-1i64) as u64), Err(DecodeError::UnknownError) ); assert_eq!( AbiResult::decode((-4095i64) as u64), Err(DecodeError::UnknownError) ); assert_eq!( AbiResult::decode((-4096i64) as u64), Err(DecodeError::NegativeSuccess) ); assert_eq!( AbiResult::decode(1 << 63), Err(DecodeError::NegativeSuccess) ); } #[test] fn authorized_cross_page_write_copies_exact_bytes_releases_real_pins_and_preserves_full_frame() { let mut d = dispatcher(); let mut f = write_frame(); let before = f; let expected = d.fixture_backend().source(f.x[1], 128); assert_eq!( run(&mut d, &mut f).unwrap().result(), AbiResult::Ok(Success::new(128).unwrap()) ); unchanged_except_result(before, f); let b = d.fixture_backend(); assert_eq!(b.output, expected); assert_eq!(b.copy_accesses(), (128, 0)); assert_eq!(b.pins(), 0); assert_eq!(b.references(), 0); assert_eq!((b.prepares, b.commits, b.releases), (1, 1, 1)); } #[test] fn dirty_u32_descriptor_normalizes_without_truncating_pointer_or_length() { let mut d = dispatcher(); let mut f = write_frame(); f.x[0] = 0xaaaa_bbbb_0000_0001; assert_eq!(run(&mut d, &mut f).unwrap().result().length(), Ok(128)); assert_eq!(d.fixture_backend().observed_fd, Some(1)); for (pointer, len, error) in [ (u64::MAX - 8, 128, Errno::Fault), (DATA_ADDRESS, u64::MAX, Errno::Invalid), (DATA_ADDRESS, 257, Errno::Invalid), ] { let mut f = frame(CALL_WRITE, [1, pointer, len, 0, 0, 0]); assert_eq!(run(&mut d, &mut f).unwrap().result(), AbiResult::Err(error)); } assert_eq!(d.fixture_backend().prepares, 1); assert_eq!(d.fixture_backend().output.len(), 128); } #[test] fn bad_descriptor_access_and_no_memory_have_no_copy_or_output_and_good_service_continues() { let mut d = dispatcher(); for (fd, mode, error) in [ (3, Mode::Normal, Errno::BadFd), (2, Mode::Normal, Errno::Access), (1, Mode::NoMem, Errno::NoMem), ] { d.fixture_backend().mode = mode; let mut f = write_frame(); f.x[0] = fd; assert_eq!(run(&mut d, &mut f).unwrap().result(), AbiResult::Err(error)); assert!(d.fixture_backend().output.is_empty()); assert_eq!(d.fixture_backend().copy_accesses(), (0, 0)); assert_eq!(d.fixture_backend().references(), 0); } d.fixture_backend().mode = Mode::Normal; let mut f = write_frame(); assert_eq!(run(&mut d, &mut f).unwrap().result().length(), Ok(128)); assert_eq!(d.fixture_backend().output.len(), 128); } #[test] fn foreign_pointer_and_missing_later_page_fail_before_any_copy_commit_or_pin_leak() { for address in [0, 0xffff_0000_0000_0000, DATA_ADDRESS + 8192] { let mut d = dispatcher(); let mut f = write_frame(); f.x[1] = address; assert_eq!( run(&mut d, &mut f).unwrap().result(), AbiResult::Err(Errno::Fault) ); assert_eq!(d.fixture_backend().commits, 0); assert!(d.fixture_backend().output.is_empty()); assert_eq!(d.fixture_backend().copy_accesses(), (0, 0)); assert_eq!(d.fixture_backend().pins(), 0); } let mut d = dispatcher(); d.fixture_backend().remove_last_data_page(); let mut f = write_frame(); f.sp_el0 = DATA_ADDRESS + 4096 - 16; assert_eq!( run(&mut d, &mut f).unwrap().result(), AbiResult::Err(Errno::Fault) ); assert_eq!(d.fixture_backend().copy_accesses(), (0, 0)); assert_eq!(d.fixture_backend().commits, 0); assert_eq!(d.fixture_backend().references(), 0); } #[test] fn actual_cpu_mask_level_irq_and_stack_gate_precedes_owner_or_service_access() { for variant in 0..5 { let mut d = dispatcher(); let mut f = write_frame(); d.fixture_backend().ingress(&f, FRAME_ADDRESS); d.fixture_backend().set_execution(|e| match variant { 0 => e.cpu = 1, 1 => e.irq_masked = false, 2 => e.in_irq = true, 3 => e.current_el = 8, _ => { e.kernel_stack = turkos_os_exceptions::exceptions::Stack::new(STACK_LOW + 4096, STACK_HIGH) .unwrap() } }); let before = d.observation(); assert!(matches!( d.dispatch(&mut f, FRAME_ADDRESS), Err(Error::Context) )); assert_eq!(d.observation(), before); assert_eq!(d.fixture_backend().authority_accesses(), 0); assert_eq!(d.fixture_backend().prepares, 0); } } #[test] fn malformed_non_svc_privileged_irq_and_stale_full_frames_cannot_reach_owner_or_service() { for variant in 0..9 { let mut d = dispatcher(); let mut f = write_frame(); match variant { 0 => f.vector_slot = 9, 1 => { f.vector_slot = 4; f.spsr = 5 } 2 => f.esr ^= 1, 3 => f.esr ^= 1 << 25, 4 => f.canary = FRAME_CANARY ^ 1, 5 => f.padding = 1, 6 => f.spsr |= 1 << 20, 7 => f.interrupted_sp += 16, _ => f.elr = DATA_ADDRESS, }; let before = f; assert!(run(&mut d, &mut f).is_err()); assert_eq!(f, before); assert_eq!(d.fixture_backend().prepares, 0); assert_eq!(d.observation().ingress_depth, 0); } let mut d = dispatcher(); let mut f = write_frame(); d.fixture_backend().ingress(&f, FRAME_ADDRESS); f.x[1] += 1; assert!(matches!( d.dispatch(&mut f, FRAME_ADDRESS), Err(Error::Ingress) )); assert_eq!(d.fixture_backend().authority_accesses(), 0); } #[test] fn replayed_or_regressed_ingress_sequence_cannot_repeat_an_output() { let mut d = dispatcher(); let mut f = write_frame(); let original = f; run(&mut d, &mut f).unwrap(); // Replay the exact originally bound complete frame, so a sequence-gate // mutant reaches the actual write callback and would repeat its output. f = original; let output = d.fixture_backend().output.clone(); let before = d.observation(); assert!(matches!( d.dispatch(&mut f, FRAME_ADDRESS), Err(Error::Replay) )); assert_eq!(d.observation(), before); assert_eq!(d.fixture_backend().output, output); d.fixture_backend().set_execution(|e| e.entry_sequence = 0); assert!(matches!( d.dispatch(&mut f, FRAME_ADDRESS), Err(Error::Replay) )); assert_eq!(d.fixture_backend().output, output); } #[test] fn return_owner_root_rx_code_and_rw_stack_are_checked_before_any_service() { for variant in 0..5 { let mut d = dispatcher(); let mut f = write_frame(); match variant { 0 => d.fixture_backend().set_root_valid(false), 1 => d .fixture_backend() .set_execution(|e| e.user.generation += 1), 2 => d.fixture_backend().set_execution(|e| e.user.root += 4096), 3 => f.elr = DATA_ADDRESS, _ => f.sp_el0 = DATA_ADDRESS - 16, }; let before = f; reject(&mut d, &mut f, Error::Return); assert_eq!(f, before); assert_eq!(d.fixture_backend().prepares, 0); assert_eq!(d.observation().ingress_depth, 0); } } #[test] fn prepare_cpu_or_return_root_drift_retains_actual_permit_and_entry_before_output() { for (mode, error) in [ (Mode::PrepareCpuDrift, Error::Context), (Mode::PrepareRootInvalid, Error::Return), ] { let mut d = dispatcher(); d.fixture_backend().mode = mode; let mut f = write_frame(); let before = f; reject(&mut d, &mut f, error); assert_eq!(f, before); let o = d.observation(); assert!(o.quarantined && o.retained_prepared); assert_eq!(o.ingress_depth, 1); assert_eq!(o.completed, 0); assert_eq!(d.fixture_prepared().unwrap().bytes.len(), 128); assert_eq!(d.fixture_backend().references(), 1); assert_eq!(d.fixture_backend().commits, 0); assert!(d.fixture_backend().output.is_empty()); } } #[test] fn commit_cpu_or_return_root_drift_retains_published_bytes_without_user_error_or_return() { for (mode, error) in [ (Mode::CommitCpuDrift, Error::Context), (Mode::CommitRootInvalid, Error::Return), ] { let mut d = dispatcher(); d.fixture_backend().mode = mode; let mut f = write_frame(); let before = f; reject(&mut d, &mut f, error); assert_eq!(f, before); assert!(d.observation().quarantined && d.observation().retained_prepared); assert_eq!(d.fixture_backend().output.len(), 128); assert_eq!(d.fixture_backend().references(), 1); assert_eq!(d.fixture_backend().releases, 0); assert_eq!(d.observation().completed, 0); } } #[test] fn commit_errno_has_no_output_releases_permit_and_returns_tagged_user_error() { let mut d = dispatcher(); d.fixture_backend().mode = Mode::CommitIo; let mut f = write_frame(); assert_eq!( run(&mut d, &mut f).unwrap().result(), AbiResult::Err(Errno::Io) ); assert!(d.fixture_backend().output.is_empty()); assert_eq!(d.fixture_backend().references(), 0); assert_eq!(d.fixture_backend().releases, 1); assert!(!d.observation().quarantined); assert_eq!(d.observation().ingress_depth, 0); } #[test] fn failed_actual_permit_release_retains_identical_bytes_and_refuses_dispatch_and_extraction() { let mut d = dispatcher(); d.fixture_backend().mode = Mode::ReleaseFail; let mut f = write_frame(); let expected = d.fixture_backend().source(f.x[1], 128); let before = f; reject(&mut d, &mut f, Error::Backend); assert_eq!(f, before); assert_eq!(d.fixture_prepared().unwrap().bytes, expected); assert_eq!(d.fixture_backend().references(), 1); assert!(matches!( d.dispatch(&mut f, FRAME_ADDRESS), Err(Error::Quarantined) )); assert!(d.into_backend().is_err()); } #[test] fn successful_release_then_context_drift_retains_only_actual_entry_no_invented_permit() { let mut d = dispatcher(); d.fixture_backend().mode = Mode::ReleaseCpuDrift; let mut f = write_frame(); let before = f; reject(&mut d, &mut f, Error::Context); assert_eq!(f, before); let o = d.observation(); assert!(o.quarantined); assert!(!o.retained_prepared); assert_eq!(o.ingress_depth, 1); assert_eq!(d.fixture_backend().references(), 0); assert_eq!(d.fixture_backend().output.len(), 128); assert_eq!(o.completed, 0); } #[test] fn inconsistent_commit_length_quarantines_real_permit_before_release_or_result_publication() { let mut d = dispatcher(); d.fixture_backend().mode = Mode::WrongLength; let mut f = write_frame(); let before = f; reject(&mut d, &mut f, Error::Result); assert_eq!(f, before); assert_eq!(d.fixture_backend().references(), 1); assert_eq!(d.fixture_backend().releases, 0); assert!(d.observation().retained_prepared); } #[test] fn zero_length_boundary_and_maximum_write_use_exact_whole_range() { let mut d = dispatcher(); for (address, len) in [(DATA_ADDRESS, 0), (DATA_ADDRESS + 4000, MAX_WRITE as u64)] { let mut f = frame(CALL_WRITE, [1, address, len, 0, 0, 0]); assert_eq!( run(&mut d, &mut f).unwrap().result().length(), Ok(len as usize) ); assert_eq!(d.fixture_backend().pins(), 0); assert_eq!(d.fixture_backend().references(), 0); } assert_eq!(d.fixture_backend().output.len(), MAX_WRITE); assert_eq!(d.fixture_backend().copy_accesses(), (MAX_WRITE, 0)); } #[test] fn forgotten_faulted_dispatcher_keeps_actual_nonclone_descriptor_lease() { let mut d = dispatcher(); let refs = d.fixture_backend().reference_counter(); d.fixture_backend().mode = Mode::ReleaseFail; let mut f = write_frame(); reject(&mut d, &mut f, Error::Backend); drop(d); assert_eq!(refs.get(), 1); } #[test] fn independent_dispatchers_have_distinct_private_identity_and_healthy_extraction_returns_actual_backend() { let a = dispatcher(); let b = dispatcher(); assert_ne!(a.observation().identity, b.observation().identity); let mut a = a; let mut f = write_frame(); run(&mut a, &mut f).unwrap(); let actual = a .into_backend() .unwrap_or_else(|_| panic!("healthy extraction")); assert_eq!(actual.output.len(), 128); assert_eq!(actual.references(), 0); assert_eq!(actual.pins(), 0); } #[test] fn rejected_constructor_returns_actual_backend_without_implicit_resource_or_source_change() { let mut b = SysBackend::new(); b.output = b"prior".to_vec(); b.set_execution(|e| e.in_irq = true); let (error, b) = Dispatcher::new(0, stack(), b).err().unwrap(); assert_eq!(error, Error::Context); assert_eq!(b.output, b"prior"); assert_eq!(b.authority_accesses(), 0); assert_eq!(b.prepares, 0); }