//! Bounded ELF64/AArch64 identification. Later loader stages must validate //! every offset and segment before copying or mapping any program bytes. pub const IDENT_AND_MACHINE_BYTES: usize = 20; pub const EM_AARCH64: u16 = 183; pub const ELF64_HEADER_BYTES: usize = 64; pub const ELF64_PROGRAM_HEADER_BYTES: usize = 56; pub const MAX_PROGRAM_HEADERS: usize = 4; pub const MAX_ARGUMENTS: usize = 4; pub const MAX_ARGUMENT_BYTES: usize = 32; const STACK_BOOT_AREA: usize = 256; use crate::memory::PAGE_SIZE; use crate::mmu::{USER_CODE_VA, USER_DATA_VA}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Truncated, BadMagic, WrongClass, WrongEndian, WrongVersion, WrongMachine, WrongType, BadProgramCount, BadProgramTable, BadFileRange, FileLargerThanMemory, BadUserAddress, WritableExecutable, UnsupportedSegment, BadSegmentPermissions, OverlappingSegments, BadEntry, BadDestination, BadArguments, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Ident { pub machine: u16, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Segment { pub file_offset: u64, pub virtual_address: u64, pub file_size: u64, pub memory_size: u64, pub flags: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Layout { pub entry: u64, pub segments: [Option; MAX_PROGRAM_HEADERS], pub count: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Loaded { pub entry: u64, pub code_bytes: usize, pub data_bytes: usize, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct StackStart { pub sp: u64, pub argc: usize, pub argv: u64, } /// Fixed one-page initial stack ABI: [argc, argv, argv[0..4], NULL] at /// SP=STACK_TOP-256, strings from SP+64, and the top 48 bytes reserved for /// first-instruction witnesses. All argument checks precede any page write. pub fn prepare_user_stack(stack_page: &mut [u8], arguments: &[&[u8]]) -> Result { if stack_page.len() != PAGE_SIZE as usize { return Err(Error::BadDestination); } if arguments.len() > MAX_ARGUMENTS { return Err(Error::BadArguments); } let mut used = 0usize; for argument in arguments { if argument.len() > MAX_ARGUMENT_BYTES || argument.contains(&0) { return Err(Error::BadArguments); } used = used .checked_add(argument.len() + 1) .ok_or(Error::BadArguments)?; } if used > STACK_BOOT_AREA - 64 - 48 { return Err(Error::BadArguments); } let base = PAGE_SIZE as usize - STACK_BOOT_AREA; let sp = crate::mmu::USER_STACK_TOP - STACK_BOOT_AREA as u64; let argv = sp + 16; stack_page.fill(0); stack_page[base..base + 8].copy_from_slice(&(arguments.len() as u64).to_le_bytes()); stack_page[base + 8..base + 16].copy_from_slice(&argv.to_le_bytes()); let mut cursor = base + 64; for (index, argument) in arguments.iter().enumerate() { let pointer = crate::mmu::USER_STACK_VA + cursor as u64; let slot = base + 16 + index * 8; stack_page[slot..slot + 8].copy_from_slice(&pointer.to_le_bytes()); stack_page[cursor..cursor + argument.len()].copy_from_slice(argument); cursor += argument.len() + 1; } Ok(StackStart { sp, argc: arguments.len(), argv, }) } /// Validate the complete two-page executable before changing either page. /// The present loader intentionally supports one R|X code segment and at /// most one R|W data segment; other layouts need an explicit future policy. fn load_plan(bytes: &[u8]) -> Result<(Layout, Segment, Option), Error> { let layout = parse_layout(bytes)?; let mut code = None; let mut data = None; for segment in layout.segments.iter().take(layout.count).flatten().copied() { if segment.virtual_address >= USER_CODE_VA && segment.virtual_address < USER_CODE_VA + PAGE_SIZE { if code.is_some() { return Err(Error::OverlappingSegments); } if segment.flags != 5 { return Err(Error::BadSegmentPermissions); } code = Some(segment); } else { if data.is_some() { return Err(Error::OverlappingSegments); } if segment.flags != 6 { return Err(Error::BadSegmentPermissions); } data = Some(segment); } } let code = code.ok_or(Error::BadSegmentPermissions)?; let executable_end = code.virtual_address + code.file_size; if layout.entry & 3 != 0 || layout.entry < code.virtual_address || layout .entry .checked_add(4) .is_none_or(|end| end > executable_end) { return Err(Error::BadEntry); } Ok((layout, code, data)) } /// Install validated file bytes into two private 4 KiB pages. Both pages are /// first zeroed, including the entire BSS tail and bytes outside PT_LOAD. /// A failed preflight leaves both destination pages unchanged. pub fn load_pages( bytes: &[u8], code_page: &mut [u8], data_page: &mut [u8], ) -> Result { if code_page.len() != PAGE_SIZE as usize || data_page.len() != PAGE_SIZE as usize { return Err(Error::BadDestination); } let (layout, code, data) = load_plan(bytes)?; code_page.fill(0); data_page.fill(0); for segment in [Some(code), data].into_iter().flatten() { let (page, base) = if segment.virtual_address < USER_DATA_VA { (&mut *code_page, USER_CODE_VA) } else { (&mut *data_page, USER_DATA_VA) }; let start = (segment.virtual_address - base) as usize; let count = segment.file_size as usize; let source = segment.file_offset as usize; page[start..start + count].copy_from_slice(&bytes[source..source + count]); } Ok(Loaded { entry: layout.entry, code_bytes: code.file_size as usize, data_bytes: data.map_or(0, |segment| segment.file_size as usize), }) } /// # Safety /// `space` must exclusively own identity-mapped writable physical frames, /// and no other CPU may read them until the caller performs cache maintenance /// and publishes the private TTBR0 root. pub unsafe fn load_into_user_space( bytes: &[u8], space: &crate::mmu::UserSpace, kernel_root: u64, ) -> Result { if !space.contract_valid(kernel_root) { return Err(Error::BadDestination); } // Preflight before constructing mutable slices to owned physical pages. load_plan(bytes)?; let code = unsafe { core::slice::from_raw_parts_mut(space.code_frame() as *mut u8, PAGE_SIZE as usize) }; let data = unsafe { core::slice::from_raw_parts_mut(space.data_frame() as *mut u8, PAGE_SIZE as usize) }; load_pages(bytes, code, data) } pub fn parse_ident(bytes: &[u8]) -> Result { let header = bytes .get(..IDENT_AND_MACHINE_BYTES) .ok_or(Error::Truncated)?; if header[..4] != [0x7f, b'E', b'L', b'F'] { return Err(Error::BadMagic); } if header[4] != 2 { return Err(Error::WrongClass); } if header[5] != 1 { return Err(Error::WrongEndian); } if header[6] != 1 { return Err(Error::WrongVersion); } let machine = u16::from_le_bytes([header[18], header[19]]); if machine != EM_AARCH64 { return Err(Error::WrongMachine); } Ok(Ident { machine }) } fn field(bytes: &[u8], offset: usize) -> Result<[u8; N], Error> { let end = offset.checked_add(N).ok_or(Error::Truncated)?; bytes .get(offset..end) .ok_or(Error::Truncated)? .try_into() .map_err(|_| Error::Truncated) } fn u16_at(bytes: &[u8], offset: usize) -> Result { Ok(u16::from_le_bytes(field::<2>(bytes, offset)?)) } fn u32_at(bytes: &[u8], offset: usize) -> Result { Ok(u32::from_le_bytes(field::<4>(bytes, offset)?)) } fn u64_at(bytes: &[u8], offset: usize) -> Result { Ok(u64::from_le_bytes(field::<8>(bytes, offset)?)) } /// Parse a narrow, static ELF64 execution plan. Nothing is copied to user /// memory here; later loader gates must still validate entry and mappings. pub fn parse_layout(bytes: &[u8]) -> Result { parse_ident(bytes)?; bytes.get(..ELF64_HEADER_BYTES).ok_or(Error::Truncated)?; if u16_at(bytes, 16)? != 2 || u32_at(bytes, 20)? != 1 || u16_at(bytes, 52)? != 64 { return Err(Error::WrongType); } let count = usize::from(u16_at(bytes, 56)?); if !(1..=MAX_PROGRAM_HEADERS).contains(&count) { return Err(Error::BadProgramCount); } if usize::from(u16_at(bytes, 54)?) != ELF64_PROGRAM_HEADER_BYTES { return Err(Error::BadProgramTable); } let table_start = usize::try_from(u64_at(bytes, 32)?).map_err(|_| Error::BadProgramTable)?; let table_bytes = count .checked_mul(ELF64_PROGRAM_HEADER_BYTES) .ok_or(Error::BadProgramTable)?; let table_end = table_start .checked_add(table_bytes) .ok_or(Error::BadProgramTable)?; if table_start < ELF64_HEADER_BYTES || bytes.get(table_start..table_end).is_none() { return Err(Error::BadProgramTable); } let mut segments = [None; MAX_PROGRAM_HEADERS]; for (index, destination) in segments.iter_mut().take(count).enumerate() { let start = table_start + index * ELF64_PROGRAM_HEADER_BYTES; if u32_at(bytes, start)? != 1 { return Err(Error::UnsupportedSegment); } let flags = u32_at(bytes, start + 4)?; if flags & !7 != 0 { return Err(Error::UnsupportedSegment); } if flags & 3 == 3 { return Err(Error::WritableExecutable); } let file_offset = u64_at(bytes, start + 8)?; let virtual_address = u64_at(bytes, start + 16)?; let file_size = u64_at(bytes, start + 32)?; let memory_size = u64_at(bytes, start + 40)?; if file_size > memory_size { return Err(Error::FileLargerThanMemory); } let file_end = file_offset .checked_add(file_size) .ok_or(Error::BadFileRange)?; let start_file = usize::try_from(file_offset).map_err(|_| Error::BadFileRange)?; let end_file = usize::try_from(file_end).map_err(|_| Error::BadFileRange)?; if bytes.get(start_file..end_file).is_none() { return Err(Error::BadFileRange); } let address_ok = [USER_CODE_VA, USER_DATA_VA].iter().any(|base| { virtual_address >= *base && virtual_address .checked_add(memory_size) .is_some_and(|end| end <= *base + PAGE_SIZE) }); if memory_size == 0 || !address_ok { return Err(Error::BadUserAddress); } *destination = Some(Segment { file_offset, virtual_address, file_size, memory_size, flags, }); } Ok(Layout { entry: u64_at(bytes, 24)?, segments, count, }) } #[cfg(test)] mod tests { extern crate std; use super::*; use sha2::{Digest, Sha256}; use std::collections::BTreeSet; use std::vec::Vec; const VALID: [u8; 20] = [ 0x7f, b'E', b'L', b'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0xb7, 0, ]; #[test] fn elf_magic_accepts_only_exact_prefix_and_never_reads_short_input() { assert_eq!( parse_ident(&VALID), Ok(Ident { machine: EM_AARCH64 }) ); for len in 0..IDENT_AND_MACHINE_BYTES { assert_eq!(parse_ident(&VALID[..len]), Err(Error::Truncated)); } for offset in 0..4 { let mut damaged = VALID; damaged[offset] ^= 1; assert_eq!(parse_ident(&damaged), Err(Error::BadMagic)); } } #[test] fn elf_class_32_and_unknown_are_rejected() { for class in [0, 1, 3, 0xff] { let mut damaged = VALID; damaged[4] = class; assert_eq!(parse_ident(&damaged), Err(Error::WrongClass)); } } #[test] fn elf_big_endian_and_unknown_encoding_are_rejected() { for encoding in [0, 2, 3, 0xff] { let mut damaged = VALID; damaged[5] = encoding; assert_eq!(parse_ident(&damaged), Err(Error::WrongEndian)); } } #[test] fn elf_other_machine_is_rejected() { for machine in [0u16, 40, 62, 0xffff] { let mut damaged = VALID; damaged[18..20].copy_from_slice(&machine.to_le_bytes()); assert_eq!(parse_ident(&damaged), Err(Error::WrongMachine)); } } #[test] fn elf_ident_version_is_checked_before_architecture() { let mut damaged = VALID; damaged[6] = 2; damaged[18] = 62; assert_eq!(parse_ident(&damaged), Err(Error::WrongVersion)); } fn layout_fixture() -> [u8; 124] { let mut bytes = [0u8; 124]; bytes[..20].copy_from_slice(&VALID); bytes[16..18].copy_from_slice(&2u16.to_le_bytes()); // ET_EXEC bytes[20..24].copy_from_slice(&1u32.to_le_bytes()); bytes[24..32].copy_from_slice(&USER_CODE_VA.to_le_bytes()); bytes[32..40].copy_from_slice(&64u64.to_le_bytes()); bytes[52..54].copy_from_slice(&64u16.to_le_bytes()); bytes[54..56].copy_from_slice(&56u16.to_le_bytes()); bytes[56..58].copy_from_slice(&1u16.to_le_bytes()); bytes[64..68].copy_from_slice(&1u32.to_le_bytes()); // PT_LOAD bytes[68..72].copy_from_slice(&5u32.to_le_bytes()); // R|X bytes[72..80].copy_from_slice(&120u64.to_le_bytes()); bytes[80..88].copy_from_slice(&USER_CODE_VA.to_le_bytes()); bytes[96..104].copy_from_slice(&4u64.to_le_bytes()); bytes[104..112].copy_from_slice(&4u64.to_le_bytes()); bytes[112..120].copy_from_slice(&1u64.to_le_bytes()); bytes[120..124].copy_from_slice(b"CODE"); bytes } #[test] fn program_header_count_and_entry_size_are_bounded() { let valid = layout_fixture(); let layout = parse_layout(&valid).unwrap(); assert_eq!(layout.count, 1); assert_eq!(layout.entry, USER_CODE_VA); assert_eq!(layout.segments[0].unwrap().file_size, 4); assert_eq!(layout.segments[1], None); for count in [0u16, 5, u16::MAX] { let mut bad = valid; bad[56..58].copy_from_slice(&count.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadProgramCount)); } let mut bad = valid; bad[54..56].copy_from_slice(&55u16.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadProgramTable)); assert_eq!(parse_layout(&valid[..63]), Err(Error::Truncated)); } #[test] fn file_offsets_and_sizes_cannot_escape_input_buffer() { let valid = layout_fixture(); let mut bad = valid; bad[32..40].copy_from_slice(&u64::MAX.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadProgramTable)); bad = valid; bad[32..40].copy_from_slice(&120u64.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadProgramTable)); bad = valid; bad[72..80].copy_from_slice(&u64::MAX.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadFileRange)); bad = valid; bad[72..80].copy_from_slice(&123u64.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadFileRange)); } #[test] fn segment_file_bytes_never_exceed_declared_memory() { let mut bad = layout_fixture(); bad[104..112].copy_from_slice(&3u64.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::FileLargerThanMemory)); } #[test] fn segment_virtual_address_stays_in_one_owned_user_page() { let valid = layout_fixture(); for address in [ 0x4000_0000u64, 0x0900_0000, USER_CODE_VA + PAGE_SIZE - 1, USER_DATA_VA + PAGE_SIZE, u64::MAX - 1, ] { let mut bad = valid; bad[80..88].copy_from_slice(&address.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::BadUserAddress)); } let mut data = valid; data[80..88].copy_from_slice(&USER_DATA_VA.to_le_bytes()); assert_eq!( parse_layout(&data).unwrap().segments[0] .unwrap() .virtual_address, USER_DATA_VA ); } #[test] fn writable_executable_segment_is_rejected_before_loading() { let valid = layout_fixture(); for flags in [3u32, 7] { let mut bad = valid; bad[68..72].copy_from_slice(&flags.to_le_bytes()); assert_eq!(parse_layout(&bad), Err(Error::WritableExecutable)); } let mut unknown = valid; unknown[68..72].copy_from_slice(&0x80u32.to_le_bytes()); assert_eq!(parse_layout(&unknown), Err(Error::UnsupportedSegment)); } fn executable_fixture() -> [u8; 512] { let mut bytes = [0u8; 512]; bytes[..124].copy_from_slice(&layout_fixture()); bytes[72..80].copy_from_slice(&320u64.to_le_bytes()); bytes[96..104].copy_from_slice(&8u64.to_le_bytes()); bytes[104..112].copy_from_slice(&12u64.to_le_bytes()); bytes[320..328].copy_from_slice(&[0x40, 0x4b, 0x8b, 0xd2, 0x01, 0x00, 0x00, 0xd4]); bytes[56..58].copy_from_slice(&2u16.to_le_bytes()); bytes[120..124].copy_from_slice(&1u32.to_le_bytes()); bytes[124..128].copy_from_slice(&6u32.to_le_bytes()); bytes[128..136].copy_from_slice(&448u64.to_le_bytes()); bytes[136..144].copy_from_slice(&USER_DATA_VA.to_le_bytes()); bytes[152..160].copy_from_slice(&4u64.to_le_bytes()); bytes[160..168].copy_from_slice(&16u64.to_le_bytes()); bytes[168..176].copy_from_slice(&1u64.to_le_bytes()); bytes[448..452].copy_from_slice(b"DATA"); bytes } #[test] fn p15_k585_elf_boundary_corpus_v2() { use sha2::{Digest, Sha256}; let corpus = include_str!("../tests/corpus/elf-v2.txt"); let mut count = 0; for line in corpus .lines() .filter(|s| !s.is_empty() && !s.starts_with('#')) { let fields: Vec<_> = line.split('|').collect(); assert_eq!(fields.len(), 4); let mut bytes = executable_fixture(); let offset: usize = fields[1].parse().unwrap(); for (index, pair) in fields[2].as_bytes().chunks_exact(2).enumerate() { bytes[offset + index] = u8::from_str_radix(core::str::from_utf8(pair).unwrap(), 16).unwrap(); } let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0x5a; PAGE_SIZE as usize]; let result = load_pages(&bytes, &mut code, &mut data).unwrap_err(); assert_eq!(std::format!("{result:?}"), fields[3]); assert_eq!(code, [0xa5; PAGE_SIZE as usize]); assert_eq!(data, [0x5a; PAGE_SIZE as usize]); let hash = Sha256::digest(bytes) .iter() .map(|b| std::format!("{b:02x}")) .collect::(); std::eprintln!("K585 elf {} {:?} {}", fields[0], result, hash); count += 1; } assert_eq!(count, 4); } #[test] fn k442_replays_versioned_elf_corpus() { let corpus = include_str!("../tests/corpus/elf-v1.txt"); assert!(corpus.starts_with("# TurkOS K442 ELF corpus v1.")); let mut seen = BTreeSet::new(); let mut accepted = 0; let mut rejected = 0; for line in corpus .lines() .filter(|line| !line.starts_with('#') && !line.is_empty()) { let fields: Vec<_> = line.split('|').collect(); assert_eq!(fields.len(), 6, "malformed ELF corpus record: {line}"); let [id, api, operation, offset, bytes, expected]: [&str; 6] = fields.try_into().unwrap(); assert!(seen.insert(id), "duplicate ELF corpus id: {id}"); let mut image = executable_fixture().to_vec(); let offset = offset.parse::().expect("numeric mutation offset"); match operation { "write" => { assert!(!bytes.is_empty() && bytes.len() % 2 == 0, "bad hex: {id}"); let end = offset + bytes.len() / 2; assert!(end <= image.len(), "out-of-bounds ELF mutation: {id}"); for (index, chunk) in bytes.as_bytes().chunks_exact(2).enumerate() { let hex = core::str::from_utf8(chunk).unwrap(); image[offset + index] = u8::from_str_radix(hex, 16).expect("hex byte"); } } "cut" => { assert_eq!(offset, 0, "cut offset must be zero: {id}"); let length = bytes.parse::().expect("numeric cut length"); assert!(length < image.len(), "cut must shorten ELF: {id}"); image.truncate(length); } _ => panic!("unknown ELF mutation operation: {id}"), } let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0x5a; PAGE_SIZE as usize]; let observed = match api { "ident" => parse_ident(&image).map(|_| ()), "layout" => parse_layout(&image).map(|_| ()), "load" => load_pages(&image, &mut code, &mut data).map(|_| ()), _ => panic!("unknown ELF parser API: {id}"), }; let expected_result = match expected { "ok" => Ok(()), "Truncated" => Err(Error::Truncated), "BadMagic" => Err(Error::BadMagic), "WrongClass" => Err(Error::WrongClass), "WrongEndian" => Err(Error::WrongEndian), "WrongVersion" => Err(Error::WrongVersion), "WrongMachine" => Err(Error::WrongMachine), "WrongType" => Err(Error::WrongType), "BadProgramCount" => Err(Error::BadProgramCount), "BadProgramTable" => Err(Error::BadProgramTable), "UnsupportedSegment" => Err(Error::UnsupportedSegment), "WritableExecutable" => Err(Error::WritableExecutable), "BadFileRange" => Err(Error::BadFileRange), "FileLargerThanMemory" => Err(Error::FileLargerThanMemory), "BadUserAddress" => Err(Error::BadUserAddress), "BadEntry" => Err(Error::BadEntry), "BadSegmentPermissions" => Err(Error::BadSegmentPermissions), "OverlappingSegments" => Err(Error::OverlappingSegments), _ => panic!("unknown ELF expected outcome: {id}"), }; assert_eq!(observed, expected_result, "ELF corpus case {id}"); if observed.is_ok() { accepted += 1; } else { rejected += 1; } if api == "load" { if observed.is_err() { assert!( code.iter().all(|byte| *byte == 0xa5), "code touched on {id}" ); assert!( data.iter().all(|byte| *byte == 0x5a), "data touched on {id}" ); } else { assert_eq!(&code[..8], &image[320..328]); assert_eq!(&data[..4], &image[448..452]); } } let digest = Sha256::digest(&image) .iter() .map(|byte| std::format!("{byte:02x}")) .collect::(); std::eprintln!("K442 {id} {api} {expected} {digest}"); } assert_eq!(seen.len(), 32, "K442 corpus completeness changed"); assert_eq!((accepted, rejected), (3, 29)); } #[test] fn elf_code_bytes_land_in_the_separate_executable_page() { let file = executable_fixture(); let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0xa5; PAGE_SIZE as usize]; assert_eq!( load_pages(&file, &mut code, &mut data).unwrap().code_bytes, 8 ); assert_eq!(&code[..8], &file[320..328]); assert_eq!(code[8], 0); assert_eq!(&data[..4], b"DATA"); let mut wrong = file; wrong[68..72].copy_from_slice(&6u32.to_le_bytes()); assert_eq!( load_pages(&wrong, &mut code, &mut data), Err(Error::BadSegmentPermissions) ); } #[test] fn elf_writable_data_is_distinct_from_executable_code() { let file = executable_fixture(); let mut code = [0u8; PAGE_SIZE as usize]; let mut data = [0u8; PAGE_SIZE as usize]; assert_eq!( load_pages(&file, &mut code, &mut data).unwrap().data_bytes, 4 ); data[0] = b'X'; assert_eq!(data[0], b'X'); assert_eq!(&code[..4], &file[320..324]); let mut wrong = file; wrong[124..128].copy_from_slice(&5u32.to_le_bytes()); assert_eq!( load_pages(&wrong, &mut code, &mut data), Err(Error::BadSegmentPermissions) ); } #[test] fn elf_bss_and_uncovered_private_page_bytes_are_zeroed() { let file = executable_fixture(); let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0x5a; PAGE_SIZE as usize]; load_pages(&file, &mut code, &mut data).unwrap(); assert!(code[8..].iter().all(|value| *value == 0)); assert_eq!(&data[..4], b"DATA"); assert!(data[4..].iter().all(|value| *value == 0)); } #[test] fn duplicate_page_segments_fail_before_destination_mutation() { let mut file = executable_fixture(); file[56..58].copy_from_slice(&3u16.to_le_bytes()); let duplicate: [u8; 56] = file[64..120].try_into().unwrap(); file[176..232].copy_from_slice(&duplicate); let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0x5a; PAGE_SIZE as usize]; assert_eq!( load_pages(&file, &mut code, &mut data), Err(Error::OverlappingSegments) ); assert!(code.iter().all(|value| *value == 0xa5)); assert!(data.iter().all(|value| *value == 0x5a)); } #[test] fn entry_must_be_aligned_and_inside_file_backed_code() { let mut file = executable_fixture(); let mut code = [0xa5; PAGE_SIZE as usize]; let mut data = [0x5a; PAGE_SIZE as usize]; for entry in [ USER_DATA_VA, USER_CODE_VA + 1, USER_CODE_VA + 8, USER_CODE_VA + 12, ] { file[24..32].copy_from_slice(&entry.to_le_bytes()); assert_eq!( load_pages(&file, &mut code, &mut data), Err(Error::BadEntry) ); assert_eq!(code[0], 0xa5); assert_eq!(data[0], 0x5a); } } #[test] fn initial_user_stack_has_aligned_sp_argv_and_nul_terminated_strings() { let mut page = [0xa5; PAGE_SIZE as usize]; let start = prepare_user_stack(&mut page, &[b"hello", b"TurkOS"]).unwrap(); assert_eq!(start.sp % 16, 0); assert_eq!(start.sp, crate::mmu::USER_STACK_TOP - 256); assert_eq!(start.argc, 2); assert_eq!(start.argv, start.sp + 16); let base = PAGE_SIZE as usize - 256; assert_eq!( u64::from_le_bytes(page[base..base + 8].try_into().unwrap()), 2 ); assert_eq!( u64::from_le_bytes(page[base + 8..base + 16].try_into().unwrap()), start.argv ); assert_eq!( u64::from_le_bytes(page[base + 16..base + 24].try_into().unwrap()), start.sp + 64 ); assert_eq!( u64::from_le_bytes(page[base + 24..base + 32].try_into().unwrap()), start.sp + 70 ); assert_eq!( u64::from_le_bytes(page[base + 32..base + 40].try_into().unwrap()), 0 ); assert_eq!(&page[base + 64..base + 70], b"hello\0"); assert_eq!(&page[base + 70..base + 77], b"TurkOS\0"); assert!(page[base + 77..].iter().all(|byte| *byte == 0)); } #[test] fn argument_count_length_and_nul_fail_before_touching_stack() { let mut page = [0xa5; PAGE_SIZE as usize]; let five: [&[u8]; 5] = [b"a", b"b", b"c", b"d", b"e"]; assert_eq!( prepare_user_stack(&mut page, &five), Err(Error::BadArguments) ); assert_eq!( prepare_user_stack(&mut page, &[&[b'x'; 33]]), Err(Error::BadArguments) ); assert_eq!( prepare_user_stack(&mut page, &[b"a\0b"]), Err(Error::BadArguments) ); assert!(page.iter().all(|byte| *byte == 0xa5)); assert_eq!( prepare_user_stack(&mut page[..100], &[b"a"]), Err(Error::BadDestination) ); } }