diff --git a/crates/aarch64/src/lib.rs b/crates/aarch64/src/lib.rs index 4be0474..86a2127 100644 --- a/crates/aarch64/src/lib.rs +++ b/crates/aarch64/src/lib.rs @@ -55,6 +55,10 @@ const NEON_ROUND_INTEGRAL_USER_OP: &str = "NEON_frint"; const NEON_UNSIGNED_MULTIPLY_LONG_USER_OP: &str = "NEON_umull"; const NEON_POLYNOMIAL_MULTIPLY_LONG_USER_OP: &str = "NEON_pmull"; const NEON_POLYNOMIAL_MULTIPLY_LONG_UPPER_USER_OP: &str = "NEON_pmull2"; +const NEON_AES_DECRYPT_USER_OP: &str = "NEON_aesd"; +const NEON_AES_ENCRYPT_USER_OP: &str = "NEON_aese"; +const NEON_AES_INVERSE_MIX_COLUMNS_USER_OP: &str = "NEON_aesimc"; +const NEON_AES_MIX_COLUMNS_USER_OP: &str = "NEON_aesmc"; const NEON_SHA256_HASH_UPDATE_USER_OP: &str = "NEON_sha256h"; const NEON_SHA256_HASH_UPDATE_SECOND_USER_OP: &str = "NEON_sha256h2"; const NEON_SHA256_SCHEDULE_UPDATE_ZERO_USER_OP: &str = "NEON_sha256su0"; @@ -589,6 +593,19 @@ impl Interpreter { if let Some(cached) = self.decode_cache.get(&cache_key) { return Ok((encoding, cached.clone())); } + if let Some(operation) = scalar_shift_left_immediate(encoding) { + let cached = CachedInstruction { + next_pc: pc + .checked_add(INSTRUCTION_SIZE) + .ok_or(Trap::UnsupportedInstruction { pc, encoding })?, + operations: Arc::from([operation]), + ends_basic_block: false, + }; + if self.decode_cache.len() < MAX_DECODE_CACHE_ENTRIES { + self.decode_cache.insert(cache_key, cached.clone()); + } + return Ok((encoding, cached)); + } if is_prefetch_hint(encoding) { let cached = CachedInstruction { next_pc: pc @@ -763,6 +780,36 @@ const fn is_prefetch_hint(encoding: u32) -> bool { || encoding & 0xff00_0000 == 0xd800_0000 } +fn scalar_shift_left_immediate(encoding: u32) -> Option { + if encoding & 0xff80_fc00 != 0x5f00_5400 { + return None; + } + let encoded_shift = u8::try_from((encoding >> 16) & 0x7f).ok()?; + let shift = encoded_shift.checked_sub(64)?; + let source = u8::try_from((encoding >> 5) & 0x1f).ok()?; + let destination = u8::try_from(encoding & 0x1f).ok()?; + Some(Operation::ShiftLeft { + destination: Place { + storage: Storage::Vector { + index: destination, + offset: 0, + }, + size: 16, + }, + left: Value { + source: ValueSource::Storage(Storage::Vector { + index: source, + offset: 0, + }), + size: 8, + }, + right: Value { + source: ValueSource::Constant(u128::from(shift)), + size: 1, + }, + }) +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum SemanticOutcome { Advanced, @@ -1213,6 +1260,10 @@ fn is_extended_user_op(user_op: &str) -> bool { | NEON_UNSIGNED_MAXIMUM_PAIRWISE_USER_OP | NEON_POLYNOMIAL_MULTIPLY_LONG_USER_OP | NEON_POLYNOMIAL_MULTIPLY_LONG_UPPER_USER_OP + | NEON_AES_DECRYPT_USER_OP + | NEON_AES_ENCRYPT_USER_OP + | NEON_AES_INVERSE_MIX_COLUMNS_USER_OP + | NEON_AES_MIX_COLUMNS_USER_OP | NEON_SHA256_HASH_UPDATE_USER_OP | NEON_SHA256_HASH_UPDATE_SECOND_USER_OP | NEON_SHA256_SCHEDULE_UPDATE_ZERO_USER_OP @@ -1231,14 +1282,10 @@ fn lower_extended_user_pcode( right: PcodeValue, arguments: [Option; 2], ) -> Option { - if matches!( - user_op, - NEON_SHA256_HASH_UPDATE_USER_OP - | NEON_SHA256_HASH_UPDATE_SECOND_USER_OP - | NEON_SHA256_SCHEDULE_UPDATE_ZERO_USER_OP - | NEON_SHA256_SCHEDULE_UPDATE_ONE_USER_OP - ) { - return lower_sha256_user_pcode(language, user_op, output, left, right, arguments); + if let Some(operation) = + lower_crypto_user_pcode(language, user_op, output, left, right, arguments) + { + return Some(operation); } match user_op { NEON_UNSIGNED_MAXIMUM_PAIRWISE_USER_OP => Some(Operation::NeonUnsignedMaximumPairwise { @@ -1329,6 +1376,57 @@ fn lower_extended_user_pcode( } } +fn lower_crypto_user_pcode( + language: &SleighData, + user_op: &str, + output: VarNode, + left: PcodeValue, + right: PcodeValue, + arguments: [Option; 2], +) -> Option { + match user_op { + NEON_AES_DECRYPT_USER_OP + | NEON_AES_ENCRYPT_USER_OP + | NEON_AES_INVERSE_MIX_COLUMNS_USER_OP + | NEON_AES_MIX_COLUMNS_USER_OP => { + lower_aes_user_pcode(language, user_op, output, left, right) + } + NEON_SHA256_HASH_UPDATE_USER_OP + | NEON_SHA256_HASH_UPDATE_SECOND_USER_OP + | NEON_SHA256_SCHEDULE_UPDATE_ZERO_USER_OP + | NEON_SHA256_SCHEDULE_UPDATE_ONE_USER_OP => { + lower_sha256_user_pcode(language, user_op, output, left, right, arguments) + } + _ => None, + } +} + +fn lower_aes_user_pcode( + language: &SleighData, + user_op: &str, + output: VarNode, + left: PcodeValue, + right: PcodeValue, +) -> Option { + let destination = lower_place(language, output)?; + match user_op { + NEON_AES_DECRYPT_USER_OP | NEON_AES_ENCRYPT_USER_OP => Some(Operation::NeonAesRound { + destination, + state: lower_value(language, left)?, + round_key: lower_value(language, right)?, + decrypt: user_op == NEON_AES_DECRYPT_USER_OP, + }), + NEON_AES_INVERSE_MIX_COLUMNS_USER_OP | NEON_AES_MIX_COLUMNS_USER_OP => { + Some(Operation::NeonAesMixColumns { + destination, + source: lower_value(language, left)?, + inverse: user_op == NEON_AES_INVERSE_MIX_COLUMNS_USER_OP, + }) + } + _ => None, + } +} + fn lower_sha256_user_pcode( language: &SleighData, user_op: &str, @@ -1631,6 +1729,8 @@ fn is_neon_value_operation(operation: &Operation) -> bool { | Operation::NeonBitwiseInsert { .. } | Operation::NeonUnsignedMultiplyLong { .. } | Operation::NeonPolynomialMultiplyLong { .. } + | Operation::NeonAesRound { .. } + | Operation::NeonAesMixColumns { .. } | Operation::NeonSha256ScheduleUpdate0 { .. } | Operation::NeonSha256ScheduleUpdate1 { .. } | Operation::NeonSha256HashUpdate { .. } @@ -2139,6 +2239,12 @@ fn execute_neon_value_operation( pc: GuestAddress, encoding: u32, ) -> Result<(Place, u128), Trap> { + if matches!( + operation, + Operation::NeonAesRound { .. } | Operation::NeonAesMixColumns { .. } + ) { + return execute_neon_aes_operation(state, scratch, operation, pc, encoding); + } if matches!( operation, Operation::NeonSha256ScheduleUpdate0 { .. } @@ -2233,6 +2339,36 @@ fn execute_neon_value_operation( } } +fn execute_neon_aes_operation( + state: &Aarch64State, + scratch: &ScratchValues, + operation: &Operation, + pc: GuestAddress, + encoding: u32, +) -> Result<(Place, u128), Trap> { + let unsupported = || Trap::UnsupportedInstruction { pc, encoding }; + let read = |value| read_value(state, scratch, value).ok_or_else(unsupported); + match *operation { + Operation::NeonAesRound { + destination, + state, + round_key, + decrypt, + } if destination.size == 16 && state.size == 16 && round_key.size == 16 => Ok(( + destination, + aes_round(read(state)?, read(round_key)?, decrypt), + )), + Operation::NeonAesMixColumns { + destination, + source, + inverse, + } if destination.size == 16 && source.size == 16 => { + Ok((destination, aes_mix_columns(read(source)?, inverse))) + } + _ => Err(unsupported()), + } +} + fn is_neon_permutation_operation(operation: &Operation) -> bool { matches!( operation, @@ -2919,6 +3055,104 @@ fn pack_u32_vector(lanes: [u32; 4]) -> u128 { }) } +fn aes_round(state: u128, round_key: u128, decrypt: bool) -> u128 { + let input = (state ^ round_key).to_le_bytes(); + let mut output = [0_u8; 16]; + for column in 0..4 { + for row in 0..4 { + let source_column = if decrypt { + (column + 4 - row) % 4 + } else { + (column + row) % 4 + }; + let source = input[row + 4 * source_column]; + output[row + 4 * column] = if decrypt { + aes_inverse_substitute(source) + } else { + aes_substitute(source) + }; + } + } + u128::from_le_bytes(output) +} + +fn aes_mix_columns(value: u128, inverse: bool) -> u128 { + let input = value.to_le_bytes(); + let mut output = [0_u8; 16]; + for column in 0..4 { + let offset = column * 4; + let lanes = [ + input[offset], + input[offset + 1], + input[offset + 2], + input[offset + 3], + ]; + let coefficients = if inverse { + [ + [14, 11, 13, 9], + [9, 14, 11, 13], + [13, 9, 14, 11], + [11, 13, 9, 14], + ] + } else { + [[2, 3, 1, 1], [1, 2, 3, 1], [1, 1, 2, 3], [3, 1, 1, 2]] + }; + for row in 0..4 { + output[offset + row] = coefficients[row] + .into_iter() + .zip(lanes) + .fold(0, |mixed, (coefficient, lane)| { + mixed ^ aes_galois_multiply(lane, coefficient) + }); + } + } + u128::from_le_bytes(output) +} + +fn aes_substitute(value: u8) -> u8 { + let inverse = aes_galois_inverse(value); + inverse + ^ inverse.rotate_left(1) + ^ inverse.rotate_left(2) + ^ inverse.rotate_left(3) + ^ inverse.rotate_left(4) + ^ 0x63 +} + +fn aes_inverse_substitute(value: u8) -> u8 { + aes_galois_inverse(value.rotate_left(1) ^ value.rotate_left(3) ^ value.rotate_left(6) ^ 0x05) +} + +fn aes_galois_inverse(value: u8) -> u8 { + let mut result = 1_u8; + let mut base = value; + let mut exponent = if value == 0 { 0 } else { 254_u8 }; + while exponent != 0 { + if exponent & 1 != 0 { + result = aes_galois_multiply(result, base); + } + base = aes_galois_multiply(base, base); + exponent >>= 1; + } + if value == 0 { 0 } else { result } +} + +fn aes_galois_multiply(mut left: u8, mut right: u8) -> u8 { + let mut product = 0_u8; + for _ in 0..8 { + if right & 1 != 0 { + product ^= left; + } + let high_bit = left & 0x80 != 0; + left <<= 1; + if high_bit { + left ^= 0x1b; + } + right >>= 1; + } + product +} + fn sha256_small_sigma_zero(value: u32) -> u32 { value.rotate_right(7) ^ value.rotate_right(18) ^ (value >> 3) } @@ -3207,6 +3441,8 @@ fn execute_memory_operation( | Operation::NeonBitwiseInsert { .. } | Operation::NeonUnsignedMultiplyLong { .. } | Operation::NeonPolynomialMultiplyLong { .. } + | Operation::NeonAesRound { .. } + | Operation::NeonAesMixColumns { .. } | Operation::NeonSha256ScheduleUpdate0 { .. } | Operation::NeonSha256ScheduleUpdate1 { .. } | Operation::NeonSha256HashUpdate { .. } @@ -3482,6 +3718,9 @@ mod tests { const PREFETCH_CODE: &[u8] = &[ 0x20, 0x68, 0xb5, 0xf8, // prfm pldl1keep, [x1, x21] ]; + const SCALAR_SHIFT_LEFT_CODE: &[u8] = &[ + 0x00, 0x54, 0x43, 0x5f, // shl d0, d0, #3 + ]; #[test] fn state_models_integer_vector_and_userspace_control_registers() { @@ -3555,6 +3794,91 @@ mod tests { assert_eq!(state.pc(), GuestAddress::new(0x1004)); } + #[test] + fn executes_scalar_neon_shift_left_immediate() { + let mut memory = executable_memory(SCALAR_SHIFT_LEFT_CODE); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_vector(0, (u128::MAX << 64) | 3).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.vector(0), Some(24)); + } + + #[test] + fn preserves_the_base_in_post_indexed_neon_loads() { + const CODE: &[u8] = &[0x00, 0x70, 0xc8, 0x4c]; // ld1 { v0.16b }, [x0], x8 + const DATA: [u8; 16] = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, + ]; + let data_address = GuestAddress::new(0x2000); + let mut memory = AddressSpace::new(20); + memory + .map_zeroed( + CODE_ADDRESS, + 4, + Permissions::READ_EXECUTE, + RegionKind::Anonymous, + ) + .unwrap(); + memory.initialize(CODE_ADDRESS, CODE).unwrap(); + memory + .map_zeroed(data_address, 16, Permissions::READ, RegionKind::Anonymous) + .unwrap(); + memory.initialize(data_address, &DATA).unwrap(); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(0, data_address.get()).unwrap(); + state.set_x(8, 16).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.vector(0), Some(u128::from_le_bytes(DATA))); + assert_eq!(state.x(0), Some(data_address.get() + 16)); + } + + #[test] + fn advances_the_base_in_post_indexed_scalar_loads() { + const CODE: &[u8] = &[0x01, 0x14, 0x40, 0x38]; // ldrb w1, [x0], #1 + let data_address = GuestAddress::new(0x2000); + let mut memory = AddressSpace::new(20); + memory + .map_zeroed( + CODE_ADDRESS, + 4, + Permissions::READ_EXECUTE, + RegionKind::Anonymous, + ) + .unwrap(); + memory.initialize(CODE_ADDRESS, CODE).unwrap(); + memory + .map_zeroed(data_address, 3, Permissions::READ, RegionKind::Anonymous) + .unwrap(); + memory + .initialize(data_address, &[0x75, 0x76, 0x77]) + .unwrap(); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_x(0, data_address.get()).unwrap(); + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.x(1), Some(0x75)); + assert_eq!(state.x(0), Some(data_address.get() + 1)); + + state.set_pc(CODE_ADDRESS); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.x(1), Some(0x76)); + assert_eq!(state.x(0), Some(data_address.get() + 2)); + + state.set_pc(CODE_ADDRESS); + interpreter.step(&mut state, &mut memory).unwrap(); + assert_eq!(state.x(1), Some(0x77)); + assert_eq!(state.x(0), Some(data_address.get() + 3)); + } + #[test] fn profiles_repeated_basic_block_entries_without_changing_execution() { let mut memory = executable_memory(TIGHT_LOOP_CODE); @@ -4284,6 +4608,66 @@ mod tests { assert_eq!(state.vector(0), Some(5)); } + #[test] + fn executes_cargo_aes_extension_semantics() { + const STATE: [u8; 16] = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, + 0xee, 0xff, + ]; + const KEY: [u8; 16] = [ + 0x13, 0x57, 0x9b, 0xdf, 0x24, 0x68, 0xac, 0xe0, 0xde, 0xad, 0xbe, 0xef, 0x10, 0x20, + 0x30, 0x40, + ]; + const CASES: [(&[u8; 4], [u8; 16], bool); 4] = [ + ( + &[0x06, 0x48, 0x28, 0x4e], // aese v6.16b, v0.16b + [ + 0x7d, 0x27, 0xfa, 0x08, 0xd0, 0x18, 0x1d, 0xce, 0xb1, 0x54, 0x56, 0x88, 0x86, + 0x5a, 0x74, 0x20, + ], + true, + ), + ( + &[0x06, 0x58, 0x28, 0x4e], // aesd v6.16b, v0.16b + [ + 0x82, 0x21, 0x9b, 0x85, 0x90, 0x98, 0x9c, 0xfd, 0xb9, 0x8b, 0xdb, 0xf4, 0x93, + 0x28, 0x10, 0x83, + ], + true, + ), + ( + &[0x06, 0x68, 0x28, 0x4e], // aesmc v6.16b, v0.16b + [ + 0x22, 0x77, 0x00, 0x55, 0x66, 0x33, 0x44, 0x11, 0xaa, 0xff, 0x88, 0xdd, 0xee, + 0xbb, 0xcc, 0x99, + ], + false, + ), + ( + &[0x06, 0x78, 0x28, 0x4e], // aesimc v6.16b, v0.16b + [ + 0xaa, 0xff, 0x88, 0xdd, 0xee, 0xbb, 0xcc, 0x99, 0x22, 0x77, 0x00, 0x55, 0x66, + 0x33, 0x44, 0x11, + ], + false, + ), + ]; + for (code, expected, round) in CASES { + let mut memory = executable_memory(code); + let mut state = Aarch64State::new(CODE_ADDRESS, GuestAddress::new(0x8000)); + state.set_vector(0, u128::from_le_bytes(KEY)).unwrap(); + state.set_vector(6, u128::from_le_bytes(STATE)).unwrap(); + if !round { + state.set_vector(0, u128::from_le_bytes(STATE)).unwrap(); + } + let mut interpreter = Interpreter::new().unwrap(); + + interpreter.step(&mut state, &mut memory).unwrap(); + + assert_eq!(state.vector(6), Some(u128::from_le_bytes(expected))); + } + } + #[test] fn executes_cargo_sha256_extension_semantics() { const FIRST: [u32; 4] = [0x0123_4567, 0x89ab_cdef, 0xfedc_ba98, 0x7654_3210]; diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 8dc4513..98036c0 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,7 +1,7 @@ use std::{ env, ffi::{OsStr, OsString}, - fs, + fmt, fs, io::{self, Write}, path::{Path, PathBuf}, process::ExitCode, @@ -408,20 +408,35 @@ fn run_guest(options: RunOptions, print_trace: bool) -> Result { fs::write(&path, snapshot) .map_err(|error| format!("could not write {}: {error}", path.display()))?; } - let result = execution.map_err(|error| { - let mut recent_pcs = Vec::new(); - for address in process.recent_instruction_pcs() { - recent_pcs.push(address.get()); - } - format!( - "{error}; pc={}; instructions={}; recent_pcs={recent_pcs:#x?}", - process.state().pc(), - process.executed_instructions(), - ) - })?; + let result = execution.map_err(|error| execution_diagnostic(&process, &error))?; Ok(result.exit_code) } +fn execution_diagnostic(process: &Process, error: &dyn fmt::Display) -> String { + let mut recent_pcs = Vec::new(); + for address in process.recent_instruction_pcs() { + recent_pcs.push(address.get()); + } + let recent_syscalls = process + .trace() + .iter() + .rev() + .take(32) + .rev() + .map(ToString::to_string) + .collect::>(); + let registers = (0..=30) + .map(|index| process.state().x(index).expect("AArch64 x register exists")) + .collect::>(); + let thread_state = process.thread_execution_state(); + format!( + "{error}; pc={}; sp={}; instructions={}; thread_state={thread_state:?}; registers={registers:#x?}; recent_pcs={recent_pcs:#x?}; recent_syscalls={recent_syscalls:#?}", + process.state().pc(), + process.state().sp(), + process.executed_instructions(), + ) +} + fn seeded_random_bytes(mut state: u64) -> [u8; 16] { let mut bytes = [0; 16]; for byte in &mut bytes { diff --git a/crates/execution-ir/src/lib.rs b/crates/execution-ir/src/lib.rs index 07dc0d9..420b34f 100644 --- a/crates/execution-ir/src/lib.rs +++ b/crates/execution-ir/src/lib.rs @@ -359,6 +359,17 @@ pub enum Operation { element_size: Value, upper: bool, }, + NeonAesRound { + destination: Place, + state: Value, + round_key: Value, + decrypt: bool, + }, + NeonAesMixColumns { + destination: Place, + source: Value, + inverse: bool, + }, NeonSha256ScheduleUpdate0 { destination: Place, first: Value, diff --git a/crates/linux-abi/src/lib.rs b/crates/linux-abi/src/lib.rs index c0cc736..332b4e2 100644 --- a/crates/linux-abi/src/lib.rs +++ b/crates/linux-abi/src/lib.rs @@ -5,13 +5,17 @@ #[repr(u64)] pub enum Syscall { Getcwd = 17, + Dup3 = 24, Fcntl = 25, Ioctl = 29, + Flock = 32, Mkdirat = 34, Unlinkat = 35, + Linkat = 37, Renameat = 38, Ftruncate = 46, Faccessat = 48, + Chdir = 49, Fchmod = 52, Openat = 56, Close = 57, @@ -33,12 +37,16 @@ pub enum Syscall { Futex = 98, ClockGettime = 113, SchedGetaffinity = 123, + SchedYield = 124, + Tkill = 130, Sigaltstack = 132, RtSigaction = 134, RtSigprocmask = 135, Getpid = 172, Getppid = 173, Gettid = 178, + Socketpair = 199, + Recvfrom = 207, Munmap = 215, Clone = 220, Execve = 221, @@ -55,13 +63,17 @@ impl Syscall { pub const fn from_number(number: u64) -> Option { match number { 17 => Some(Self::Getcwd), + 24 => Some(Self::Dup3), 25 => Some(Self::Fcntl), 29 => Some(Self::Ioctl), + 32 => Some(Self::Flock), 34 => Some(Self::Mkdirat), 35 => Some(Self::Unlinkat), + 37 => Some(Self::Linkat), 38 => Some(Self::Renameat), 46 => Some(Self::Ftruncate), 48 => Some(Self::Faccessat), + 49 => Some(Self::Chdir), 52 => Some(Self::Fchmod), 56 => Some(Self::Openat), 57 => Some(Self::Close), @@ -83,12 +95,16 @@ impl Syscall { 98 => Some(Self::Futex), 113 => Some(Self::ClockGettime), 123 => Some(Self::SchedGetaffinity), + 124 => Some(Self::SchedYield), + 130 => Some(Self::Tkill), 132 => Some(Self::Sigaltstack), 134 => Some(Self::RtSigaction), 135 => Some(Self::RtSigprocmask), 172 => Some(Self::Getpid), 173 => Some(Self::Getppid), 178 => Some(Self::Gettid), + 199 => Some(Self::Socketpair), + 207 => Some(Self::Recvfrom), 215 => Some(Self::Munmap), 220 => Some(Self::Clone), 221 => Some(Self::Execve), @@ -150,13 +166,17 @@ mod tests { #[test] fn decodes_the_minimal_aarch64_table() { assert_eq!(Syscall::from_number(17), Some(Syscall::Getcwd)); + assert_eq!(Syscall::from_number(24), Some(Syscall::Dup3)); assert_eq!(Syscall::from_number(25), Some(Syscall::Fcntl)); assert_eq!(Syscall::from_number(29), Some(Syscall::Ioctl)); + assert_eq!(Syscall::from_number(32), Some(Syscall::Flock)); assert_eq!(Syscall::from_number(34), Some(Syscall::Mkdirat)); assert_eq!(Syscall::from_number(35), Some(Syscall::Unlinkat)); + assert_eq!(Syscall::from_number(37), Some(Syscall::Linkat)); assert_eq!(Syscall::from_number(38), Some(Syscall::Renameat)); assert_eq!(Syscall::from_number(46), Some(Syscall::Ftruncate)); assert_eq!(Syscall::from_number(48), Some(Syscall::Faccessat)); + assert_eq!(Syscall::from_number(49), Some(Syscall::Chdir)); assert_eq!(Syscall::from_number(52), Some(Syscall::Fchmod)); assert_eq!(Syscall::from_number(56), Some(Syscall::Openat)); assert_eq!(Syscall::from_number(57), Some(Syscall::Close)); @@ -178,12 +198,16 @@ mod tests { assert_eq!(Syscall::from_number(98), Some(Syscall::Futex)); assert_eq!(Syscall::from_number(113), Some(Syscall::ClockGettime)); assert_eq!(Syscall::from_number(123), Some(Syscall::SchedGetaffinity)); + assert_eq!(Syscall::from_number(124), Some(Syscall::SchedYield)); + assert_eq!(Syscall::from_number(130), Some(Syscall::Tkill)); assert_eq!(Syscall::from_number(132), Some(Syscall::Sigaltstack)); assert_eq!(Syscall::from_number(134), Some(Syscall::RtSigaction)); assert_eq!(Syscall::from_number(135), Some(Syscall::RtSigprocmask)); assert_eq!(Syscall::from_number(172), Some(Syscall::Getpid)); assert_eq!(Syscall::from_number(173), Some(Syscall::Getppid)); assert_eq!(Syscall::from_number(178), Some(Syscall::Gettid)); + assert_eq!(Syscall::from_number(199), Some(Syscall::Socketpair)); + assert_eq!(Syscall::from_number(207), Some(Syscall::Recvfrom)); assert_eq!(Syscall::from_number(215), Some(Syscall::Munmap)); assert_eq!(Syscall::from_number(220), Some(Syscall::Clone)); assert_eq!(Syscall::from_number(221), Some(Syscall::Execve)); diff --git a/crates/linux-runtime/src/lib.rs b/crates/linux-runtime/src/lib.rs index b95d77a..cedcc03 100644 --- a/crates/linux-runtime/src/lib.rs +++ b/crates/linux-runtime/src/lib.rs @@ -24,11 +24,15 @@ const STANDARD_INPUT: u64 = 0; const STANDARD_OUTPUT: u64 = 1; const STANDARD_ERROR: u64 = 2; const INITIAL_PROCESS_ID: u64 = 1; +#[cfg(test)] const CHILD_PROCESS_ID: u64 = 2; const DORMANT_THREAD_ID: u64 = 10_001; const ACTIVE_THREAD_ID: u64 = 10_002; const NESTED_HELPER_THREAD_ID: u64 = 10_003; const MAX_NESTED_HELPERS: usize = 4; +const MAX_PROCESS_DEPTH: usize = 8; +const SIGNAL_ABORT: u64 = 6; +const SIGNAL_USER_1: u64 = 10; const FIRST_FILE_DESCRIPTOR: u32 = 3; const AT_FDCWD: u64 = (-100_i64).cast_unsigned(); const MAX_PATH_BYTES: usize = 4096; @@ -41,6 +45,7 @@ const OPEN_NOCTTY: u64 = 0x100; const OPEN_TRUNCATE: u64 = 0x200; const OPEN_APPEND: u64 = 0x400; const OPEN_DIRECTORY: u64 = 0x4000; +const OPEN_LARGEFILE: u64 = 0x8000; const OPEN_DIRECTORY_FIXTURE_COMPAT: u64 = 0x1_0000; const OPEN_NOFOLLOW: u64 = 0x2_0000; const OPEN_CLOEXEC: u64 = 0x8_0000; @@ -50,9 +55,16 @@ const FCNTL_GET_DESCRIPTOR_FLAGS: u64 = 1; const FCNTL_SET_DESCRIPTOR_FLAGS: u64 = 2; const FCNTL_GET_STATUS_FLAGS: u64 = 3; const FCNTL_SET_STATUS_FLAGS: u64 = 4; +const FCNTL_DUPLICATE: u64 = 0; +const FCNTL_DUPLICATE_CLOEXEC: u64 = 1030; const DESCRIPTOR_CLOEXEC: u64 = 1; +const FILE_LOCK_SHARED: u64 = 1; +const FILE_LOCK_EXCLUSIVE: u64 = 2; +const FILE_LOCK_NONBLOCKING: u64 = 4; +const FILE_LOCK_UNLOCK: u64 = 8; const IOCTL_CLEAR_CLOSE_ON_EXEC: u64 = 0x5450; const IOCTL_SET_CLOSE_ON_EXEC: u64 = 0x5451; +const IOCTL_SET_NONBLOCK: u64 = 0x5421; const SUPPORTED_OPEN_FLAGS: u64 = OPEN_ACCESS_MASK | OPEN_CREATE | OPEN_EXCLUSIVE @@ -60,6 +72,7 @@ const SUPPORTED_OPEN_FLAGS: u64 = OPEN_ACCESS_MASK | OPEN_TRUNCATE | OPEN_APPEND | OPEN_DIRECTORY + | OPEN_LARGEFILE | OPEN_DIRECTORY_FIXTURE_COMPAT | OPEN_NOFOLLOW | OPEN_CLOEXEC @@ -112,14 +125,28 @@ const SIGSTOP_BIT: u64 = 1 << (19 - 1); const CPU_AFFINITY_BYTES: u64 = 8; const INITIAL_CURRENT_DIRECTORY: &[u8] = b"/project"; const PIPE_CAPACITY_BYTES: usize = 64 * 1024; +const MAX_POLL_DESCRIPTORS: u64 = 1024; +const POLL_INPUT: u16 = 0x0001; +const POLL_OUTPUT: u16 = 0x0004; +const POLL_ERROR: u16 = 0x0008; +const POLL_HANGUP: u16 = 0x0010; +const POLL_INVALID: u16 = 0x0020; const FILE_COPY_CHUNK_BYTES: usize = 1024 * 1024; const SUPPORTED_PIPE_FLAGS: u64 = OPEN_CLOEXEC | OPEN_NONBLOCK; +const ADDRESS_FAMILY_UNIX: u64 = 1; +const SOCKET_TYPE_MASK: u64 = 0xf; +const SOCKET_STREAM: u64 = 1; +const SOCKET_SEQPACKET: u64 = 5; +const MESSAGE_PEEK: u64 = 2; +const MESSAGE_DONTWAIT: u64 = 0x40; const CLONE_VM: u64 = 0x100; const CLONE_VFORK: u64 = 0x4000; const SIGCHLD: u64 = 17; const SUPPORTED_CLONE_FLAGS: u64 = CLONE_VM | CLONE_VFORK | SIGCHLD; +const SUPPORTED_FORK_FLAGS: u64 = SIGCHLD; const DORMANT_THREAD_CLONE_FLAGS: u64 = 0x7d_0f00; const FUTEX_COMMAND_MASK: u64 = 0x7f; +const FUTEX_PRIVATE_FLAG: u64 = 0x80; const FUTEX_WAIT: u64 = 0; const FUTEX_WAKE: u64 = 1; const FUTEX_WAIT_BITSET: u64 = 9; @@ -349,6 +376,7 @@ struct PendingInput { enum PipeEnd { Read(u64), Write(u64), + Socket { read: u64, write: u64 }, } #[derive(Debug, Default)] @@ -378,13 +406,18 @@ struct SuspendedParent { config: ProcessConfig, credentials: Credentials, pending_input: Option, + suspended_thread: Option, dormant_thread: Option, parked_helpers: Vec, compiler_thread: Option, worker_parent: Option, worker_thread: Option, next_helper_thread_id: u64, + current_process_id: u64, current_thread_id: u64, + parent_process_id: u64, + exited_child: Option, + pending_signal_sync: bool, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -432,6 +465,21 @@ pub struct SyscallEvent { pub outcome: SyscallOutcome, } +/// Compact scheduler state exposed for deterministic trap diagnostics. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ThreadExecutionState { + pub process_id: u64, + pub thread_id: u64, + pub process_depth: usize, + pub suspended_threads: usize, + pub dormant_threads: usize, + pub parked_helpers: usize, + pub compiler_threads: usize, + pub worker_parents: usize, + pub worker_threads: usize, + pub pending_signal_syncs: usize, +} + impl fmt::Display for SyscallEvent { #[allow(clippy::too_many_lines)] fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -439,6 +487,7 @@ impl fmt::Display for SyscallEvent { Some( syscall @ (Syscall::Mkdirat | Syscall::Unlinkat + | Syscall::Linkat | Syscall::Renameat | Syscall::Faccessat | Syscall::Openat @@ -447,11 +496,31 @@ impl fmt::Display for SyscallEvent { | Syscall::Newfstatat), ) => format_vfs_syscall(formatter, syscall, self.arguments)?, Some(Syscall::Close) => write!(formatter, "close(fd={})", self.arguments[0])?, + Some(Syscall::Dup3) => write!( + formatter, + "dup3(oldfd={}, newfd={}, flags={:#x})", + self.arguments[0], self.arguments[1], self.arguments[2], + )?, Some(Syscall::Pipe2) => write!( formatter, "pipe2(pipefd={:#x}, flags={:#x})", self.arguments[0], self.arguments[1], )?, + Some(Syscall::Socketpair) => write!( + formatter, + "socketpair(domain={}, type={:#x}, protocol={}, sockets={:#x})", + self.arguments[0], self.arguments[1], self.arguments[2], self.arguments[3], + )?, + Some(Syscall::Recvfrom) => write!( + formatter, + "recvfrom(fd={}, buffer={:#x}, length={}, flags={:#x}, address={:#x}, address_length={:#x})", + self.arguments[0], + self.arguments[1], + self.arguments[2], + self.arguments[3], + self.arguments[4], + self.arguments[5], + )?, Some(Syscall::Fcntl) => write!( formatter, "fcntl(fd={}, command={}, argument={:#x})", @@ -462,11 +531,19 @@ impl fmt::Display for SyscallEvent { "ioctl(fd={}, request={:#x}, argument={:#x})", self.arguments[0], self.arguments[1], self.arguments[2], )?, + Some(Syscall::Flock) => write!( + formatter, + "flock(fd={}, operation={:#x})", + self.arguments[0], self.arguments[1], + )?, Some(Syscall::Getcwd) => write!( formatter, "getcwd(buf={:#x}, size={})", self.arguments[0], self.arguments[1], )?, + Some(Syscall::Chdir) => { + write!(formatter, "chdir(path={:#x})", self.arguments[0])?; + } Some(Syscall::Lseek) => write!( formatter, "lseek(fd={}, offset={}, whence={})", @@ -577,6 +654,12 @@ impl fmt::Display for SyscallEvent { self.arguments[1], self.arguments[2], )?, + Some(Syscall::SchedYield) => write!(formatter, "sched_yield()")?, + Some(Syscall::Tkill) => write!( + formatter, + "tkill(tid={}, signal={})", + self.arguments[0], self.arguments[1], + )?, Some(Syscall::Sigaltstack) => write!( formatter, "sigaltstack(stack={:#x}, old_stack={:#x})", @@ -682,6 +765,15 @@ fn format_vfs_syscall( arguments[1], arguments[2], ), + Syscall::Linkat => write!( + formatter, + "linkat(olddirfd={}, oldpath={:#x}, newdirfd={}, newpath={:#x}, flags={:#x})", + arguments[0].cast_signed(), + arguments[1], + arguments[2].cast_signed(), + arguments[3], + arguments[4], + ), Syscall::Renameat => write!( formatter, "renameat(olddirfd={}, oldpath={:#x}, newdirfd={}, newpath={:#x})", @@ -803,7 +895,7 @@ pub struct Process { current_process_id: u64, current_thread_id: u64, parent_process_id: u64, - suspended_parent: Option, + suspended_parents: Vec, suspended_thread: Option, exited_child: Option, dormant_thread: Option, @@ -812,6 +904,7 @@ pub struct Process { worker_parent: Option, worker_thread: Option, next_helper_thread_id: u64, + pending_signal_sync: bool, } impl Process { @@ -851,7 +944,7 @@ impl Process { current_process_id: INITIAL_PROCESS_ID, current_thread_id: INITIAL_PROCESS_ID, parent_process_id: 0, - suspended_parent: None, + suspended_parents: Vec::new(), suspended_thread: None, exited_child: None, dormant_thread: None, @@ -860,6 +953,7 @@ impl Process { worker_parent: None, worker_thread: None, next_helper_thread_id: NESTED_HELPER_THREAD_ID, + pending_signal_sync: false, }) } @@ -917,6 +1011,23 @@ impl Process { self.interpreter.tiered_metrics() } + /// Return compact process/thread scheduler state for host diagnostics. + #[must_use] + pub const fn thread_execution_state(&self) -> ThreadExecutionState { + ThreadExecutionState { + process_id: self.current_process_id, + thread_id: self.current_thread_id, + process_depth: self.suspended_parents.len(), + suspended_threads: self.suspended_thread.is_some() as usize, + dormant_threads: self.dormant_thread.is_some() as usize, + parked_helpers: self.parked_helpers.len(), + compiler_threads: self.compiler_thread.is_some() as usize, + worker_parents: self.worker_parent.is_some() as usize, + worker_threads: self.worker_thread.is_some() as usize, + pending_signal_syncs: self.pending_signal_sync as usize, + } + } + /// Run until `exit`/`exit_group`, a host failure, or a project resource trap. /// /// Unsupported syscall numbers return `-ENOSYS` in `x0` and execution @@ -1086,16 +1197,32 @@ impl Process { Some(syscall @ (Syscall::Exit | Syscall::ExitGroup)) => { return Ok(self.dispatch_exit(filesystem, syscall, number, arguments)); } + Some(Syscall::Tkill) => { + if self.dispatch_tkill() { + return Ok(self.dispatch_exit( + filesystem, + Syscall::ExitGroup, + number, + arguments, + )); + } + } Some(Syscall::Getcwd) => self.dispatch_getcwd(), + Some(Syscall::Chdir) => self.dispatch_chdir(filesystem), + Some(Syscall::Dup3) => self.dispatch_dup3(filesystem), Some(Syscall::Fcntl) => self.dispatch_fcntl(), Some(Syscall::Ioctl) => self.dispatch_ioctl(), + Some(Syscall::Flock) => self.dispatch_flock(), Some(Syscall::Mkdirat) => self.dispatch_mkdirat(filesystem), Some(Syscall::Unlinkat) => self.dispatch_unlinkat(filesystem), + Some(Syscall::Linkat) => self.dispatch_linkat(filesystem), Some(Syscall::Renameat) => self.dispatch_renameat(filesystem), Some(Syscall::Faccessat) => self.dispatch_faccessat(filesystem), Some(Syscall::Openat) => self.dispatch_openat(filesystem), Some(Syscall::Close) => self.dispatch_close(filesystem), Some(Syscall::Pipe2) => self.dispatch_pipe2(), + Some(Syscall::Socketpair) => self.dispatch_socketpair(), + Some(Syscall::Recvfrom) => self.dispatch_recvfrom(), Some(Syscall::Getdents64) => self.dispatch_getdents64(filesystem), Some(Syscall::Lseek) => self.dispatch_lseek(filesystem), Some(Syscall::Readlinkat) => self.dispatch_readlinkat(filesystem), @@ -1110,7 +1237,7 @@ impl Process { }); } Some(Syscall::Read) => { - if self.register(0) == STANDARD_INPUT { + if self.register(0) == STANDARD_INPUT && !self.descriptor_exists(0) { if let Some(request) = self.dispatch_terminal_read(input, arguments) { return Ok(Some(ExecutionEvent::Input(request))); } @@ -1121,7 +1248,7 @@ impl Process { Some(Syscall::Pread64) => self.dispatch_pread64(filesystem), Some(Syscall::Sendfile) => self.dispatch_sendfile(filesystem), Some(Syscall::CopyFileRange) => self.dispatch_copy_file_range(filesystem), - Some(Syscall::Ppoll) => self.set_return(0), + Some(Syscall::Ppoll) => self.dispatch_ppoll(), Some(Syscall::Write) => self.dispatch_write(terminal, filesystem)?, Some(Syscall::Writev) => self.dispatch_writev(terminal, filesystem)?, Some(Syscall::SetTidAddress) => { @@ -1138,6 +1265,7 @@ impl Process { Some(Syscall::Execve) => self.dispatch_execve(filesystem, system), Some(Syscall::ClockGettime) => self.dispatch_clock_gettime(system), Some(Syscall::SchedGetaffinity) => self.dispatch_sched_getaffinity(), + Some(Syscall::SchedYield) => self.set_return(0), Some(Syscall::Sigaltstack) => self.dispatch_sigaltstack(), Some(Syscall::RtSigaction) => self.dispatch_rt_sigaction(), Some(Syscall::RtSigprocmask) => self.dispatch_rt_sigprocmask(), @@ -1170,10 +1298,6 @@ impl Process { arguments, outcome: SyscallOutcome::Exited(exit_code), }); - if self.suspended_parent.is_some() { - self.finish_child(filesystem, exit_code); - return None; - } if syscall == Syscall::Exit && self.worker_parent.is_some() { self.finish_worker_thread(); return None; @@ -1195,6 +1319,10 @@ impl Process { self.finish_thread(); return None; } + if !self.suspended_parents.is_empty() { + self.finish_child(filesystem, exit_code); + return None; + } self.clear_dormant_thread_tid(); self.clear_parked_helper_tids(); Some(ExecutionEvent::Exited(ExecutionResult { @@ -1210,8 +1338,10 @@ impl Process { self.dispatch_dormant_thread_clone(); return; } - if self.register(0) != SUPPORTED_CLONE_FLAGS - || self.suspended_parent.is_some() + if !matches!( + self.register(0), + SUPPORTED_CLONE_FLAGS | SUPPORTED_FORK_FLAGS + ) || self.suspended_parents.len() >= MAX_PROCESS_DEPTH || self.exited_child.is_some() { self.set_return(Errno::InvalidArgument.return_value()); @@ -1242,32 +1372,60 @@ impl Process { config: self.config, credentials: self.credentials, pending_input: self.pending_input, + suspended_thread: self.suspended_thread.take(), dormant_thread: self.dormant_thread.take(), parked_helpers: core::mem::take(&mut self.parked_helpers), compiler_thread: self.compiler_thread.take(), worker_parent: self.worker_parent.take(), worker_thread: self.worker_thread.take(), next_helper_thread_id: self.next_helper_thread_id, + current_process_id: self.current_process_id, current_thread_id: self.current_thread_id, + parent_process_id: self.parent_process_id, + exited_child: self.exited_child.take(), + pending_signal_sync: self.pending_signal_sync, }; - self.suspended_parent = Some(parent); - self.current_process_id = CHILD_PROCESS_ID; - self.current_thread_id = CHILD_PROCESS_ID; - self.parent_process_id = INITIAL_PROCESS_ID; + let child_process_id = self.current_process_id + 1; + self.suspended_parents.push(parent); + self.parent_process_id = self.current_process_id; + self.current_process_id = child_process_id; + self.current_thread_id = child_process_id; self.clear_child_tid = None; self.pending_input = None; + self.suspended_thread = None; self.dormant_thread = None; self.parked_helpers.clear(); self.compiler_thread = None; self.worker_parent = None; self.worker_thread = None; self.next_helper_thread_id = NESTED_HELPER_THREAD_ID; + self.exited_child = None; + self.pending_signal_sync = false; if child_stack != 0 { self.state.set_sp(GuestAddress::new(child_stack)); } self.set_return(0); } + fn dispatch_tkill(&mut self) -> bool { + let thread_id = self.register(0); + let signal = self.register(1); + if (DORMANT_THREAD_ID..self.next_helper_thread_id).contains(&thread_id) + && signal == SIGNAL_USER_1 + { + self.clear_dormant_thread_tid(); + self.pending_signal_sync = true; + self.set_return(0); + return false; + } + if thread_id == self.current_thread_id && signal == SIGNAL_ABORT { + self.set_return(128 + signal); + return true; + } + self.set_return(Errno::InvalidArgument.return_value()); + false + } + fn dispatch_dormant_thread_clone(&mut self) { let nested_helper = self.current_thread_id == ACTIVE_THREAD_ID || self.current_thread_id >= NESTED_HELPER_THREAD_ID; @@ -1281,7 +1439,7 @@ impl Process { } else { self.suspended_thread.is_some() }; - if self.suspended_parent.is_some() || self.exited_child.is_some() || invalid_thread_state { + if self.exited_child.is_some() || invalid_thread_state { self.set_return(Errno::TryAgain.return_value()); return; } @@ -1480,6 +1638,27 @@ impl Process { self.set_return(Errno::TryAgain.return_value()); return; } + if self.pending_signal_sync { + let value = u32::from_le_bytes(bytes).wrapping_add(1); + let _ = self.memory.write(address, &value.to_le_bytes()); + self.pending_signal_sync = false; + self.set_return(0); + return; + } + let has_runnable_peer = self.suspended_thread.is_some() + || !self.parked_helpers.is_empty() + || self.compiler_thread.is_some() + || self.worker_parent.is_some() + || self.worker_thread.is_some(); + if self.register(1) == FUTEX_PRIVATE_FLAG | FUTEX_WAIT + && bytes == 2_u32.to_le_bytes() + && !has_runnable_peer + && self.dormant_thread.is_some() + { + let _ = self.memory.write(address, &3_u32.to_le_bytes()); + self.set_return(0); + return; + } self.set_return(0); if self.worker_parent.is_some() { self.park_worker_thread(); @@ -1500,9 +1679,10 @@ impl Process { fn finish_child(&mut self, filesystem: &mut F, exit_code: u8) { self.close_all_descriptors(filesystem); + let child_process_id = self.current_process_id; let parent = self - .suspended_parent - .take() + .suspended_parents + .pop() .expect("a child exit has a suspended parent"); self.state = parent.state; self.memory = parent.memory; @@ -1523,20 +1703,23 @@ impl Process { self.config = parent.config; self.credentials = parent.credentials; self.pending_input = parent.pending_input; + self.suspended_thread = parent.suspended_thread; self.dormant_thread = parent.dormant_thread; self.parked_helpers = parent.parked_helpers; self.compiler_thread = parent.compiler_thread; self.worker_parent = parent.worker_parent; self.worker_thread = parent.worker_thread; self.next_helper_thread_id = parent.next_helper_thread_id; - self.current_process_id = INITIAL_PROCESS_ID; + self.pending_signal_sync = parent.pending_signal_sync; + self.current_process_id = parent.current_process_id; self.current_thread_id = parent.current_thread_id; - self.parent_process_id = 0; + self.parent_process_id = parent.parent_process_id; + self.exited_child = parent.exited_child; self.exited_child = Some(ExitedChild { - process_id: CHILD_PROCESS_ID, + process_id: child_process_id, exit_code, }); - self.set_return(CHILD_PROCESS_ID); + self.set_return(child_process_id); } fn dispatch_wait4(&mut self) { @@ -1651,12 +1834,14 @@ impl Process { self.config = image.config; self.credentials = image.credentials; self.pending_input = None; + self.suspended_thread = None; self.dormant_thread = None; self.parked_helpers.clear(); self.compiler_thread = None; self.worker_parent = None; self.worker_thread = None; self.next_helper_thread_id = NESTED_HELPER_THREAD_ID; + self.pending_signal_sync = false; } fn read_exec_vector(&self, address: GuestAddress) -> Result>, Errno> { @@ -1769,6 +1954,28 @@ impl Process { self.set_return(required); } + fn dispatch_chdir(&mut self, filesystem: &mut F) { + let path = match self.read_guest_path(GuestAddress::new(self.register(0))) { + Ok(path) if !path.is_empty() => self.absolute_guest_path(path), + Ok(_) => { + self.set_return(Errno::NoEntry.return_value()); + return; + } + Err(error) => { + self.set_return(error.return_value()); + return; + } + }; + match filesystem.metadata(&path) { + Ok(metadata) if metadata.file_type == FileType::Directory => { + self.current_directory = path; + self.set_return(0); + } + Ok(_) => self.set_return(Errno::NotDirectory.return_value()), + Err(error) => self.set_return(filesystem_error_return(error)), + } + } + fn dispatch_faccessat(&mut self, filesystem: &mut F) { let mode = self.register(2); if mode & !7 != 0 { @@ -1877,10 +2084,12 @@ impl Process { fn dispatch_fstat(&mut self, filesystem: &mut F) { let file_descriptor = self.register(0); - let stat = if matches!( + let standard_descriptor = matches!( file_descriptor, STANDARD_INPUT | STANDARD_OUTPUT | STANDARD_ERROR - ) { + ) && u32::try_from(file_descriptor) + .is_ok_and(|descriptor| !self.descriptor_exists(descriptor)); + let stat = if standard_descriptor { linux_terminal_stat_bytes() } else { let Ok(file_descriptor) = u32::try_from(file_descriptor) else { @@ -1890,6 +2099,7 @@ impl Process { if let Some(end) = self.pipe_descriptors.get(&file_descriptor) { linux_pipe_stat_bytes(match end { PipeEnd::Read(pipe_id) | PipeEnd::Write(pipe_id) => *pipe_id, + PipeEnd::Socket { read, .. } => *read, }) } else { let Some(path) = self.descriptor_paths.get(&file_descriptor) else { @@ -1922,7 +2132,10 @@ impl Process { filesystem: &mut F, ) -> Result<(), ExecutionError> { let file_descriptor = self.register(0); - if !matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) { + if !matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) + || u32::try_from(file_descriptor) + .is_ok_and(|descriptor| self.descriptor_exists(descriptor)) + { self.dispatch_file_write(filesystem); return Ok(()); } @@ -2011,7 +2224,10 @@ impl Process { } } - if matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) { + if matches!(file_descriptor, STANDARD_OUTPUT | STANDARD_ERROR) + && u32::try_from(file_descriptor) + .is_ok_and(|descriptor| !self.descriptor_exists(descriptor)) + { let total = bytes.len() as u64; let next_total = self .output_bytes @@ -2076,6 +2292,64 @@ impl Process { } } + fn dispatch_linkat(&mut self, filesystem: &mut F) { + if self.register(4) != 0 { + self.set_return(Errno::InvalidArgument.return_value()); + return; + } + let old_path = match self + .read_guest_path(GuestAddress::new(self.register(1))) + .and_then(|path| self.resolve_at_path(filesystem, self.register(0), path)) + { + Ok(path) => path, + Err(error) => { + self.set_return(error.return_value()); + return; + } + }; + let new_path = match self + .read_guest_path(GuestAddress::new(self.register(3))) + .and_then(|path| self.resolve_at_path(filesystem, self.register(2), path)) + { + Ok(path) => path, + Err(error) => { + self.set_return(error.return_value()); + return; + } + }; + let metadata = match filesystem.metadata(&old_path) { + Ok(metadata) if metadata.file_type == FileType::Regular => metadata, + Ok(_) => { + self.set_return(Errno::PermissionDenied.return_value()); + return; + } + Err(error) => { + self.set_return(filesystem_error_return(error)); + return; + } + }; + match filesystem.metadata(&new_path) { + Ok(_) => { + self.set_return(Errno::AlreadyExists.return_value()); + return; + } + Err(FileSystemError::NotFound) => {} + Err(error) => { + self.set_return(filesystem_error_return(error)); + return; + } + } + if metadata.size > self.limits.max_filesystem_bytes { + self.set_return(Errno::NoSpace.return_value()); + return; + } + let result = copy_file_for_link(filesystem, &old_path, &new_path); + self.set_return(match result { + Ok(()) => 0, + Err(error) => filesystem_error_return(error), + }); + } + fn dispatch_renameat(&mut self, filesystem: &mut F) { let old_path = match self .read_guest_path(GuestAddress::new(self.register(1))) @@ -2263,6 +2537,55 @@ impl Process { } } + fn dispatch_dup3(&mut self, filesystem: &mut F) { + let (Ok(source), Ok(destination)) = ( + u32::try_from(self.register(0)), + u32::try_from(self.register(1)), + ) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + let flags = self.register(2); + if source == destination || flags & !OPEN_CLOEXEC != 0 { + self.set_return(Errno::InvalidArgument.return_value()); + return; + } + if destination >= self.limits.max_open_files || !self.descriptor_exists(source) { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + } + if self.descriptor_exists(destination) + && let Err(error) = self.close_descriptor(filesystem, destination) + { + self.set_return(error.return_value()); + return; + } + + self.duplicate_descriptor(source, destination, flags & OPEN_CLOEXEC != 0); + self.set_return(u64::from(destination)); + } + + fn duplicate_descriptor(&mut self, source: u32, destination: u32, close_on_exec: bool) { + if let Some(handle) = self.file_descriptors.get(&source).copied() { + self.file_descriptors.insert(destination, handle); + if let Some(path) = self.descriptor_paths.get(&source).cloned() { + self.descriptor_paths.insert(destination, path); + } + } else if let Some(end) = self.pipe_descriptors.get(&source).copied() { + self.pipe_descriptors.insert(destination, end); + } + let status = self + .descriptor_status_flags + .get(&source) + .copied() + .unwrap_or(0); + self.descriptor_status_flags.insert(destination, status); + self.descriptor_flags.insert( + destination, + if close_on_exec { DESCRIPTOR_CLOEXEC } else { 0 }, + ); + } + fn dispatch_pipe2(&mut self) { let pipe_descriptors = GuestAddress::new(self.register(0)); let flags = self.register(1); @@ -2316,17 +2639,236 @@ impl Process { self.set_return(0); } + fn dispatch_socketpair(&mut self) { + let domain = self.register(0); + let socket_type = self.register(1); + let protocol = self.register(2); + let sockets = GuestAddress::new(self.register(3)); + let kind = socket_type & SOCKET_TYPE_MASK; + let flags = socket_type & !SOCKET_TYPE_MASK; + if domain != ADDRESS_FAMILY_UNIX + || !matches!(kind, SOCKET_STREAM | SOCKET_SEQPACKET) + || protocol != 0 + || flags & !SUPPORTED_PIPE_FLAGS != 0 + { + self.set_return(Errno::InvalidArgument.return_value()); + return; + } + let Some([first, second]) = self.allocate_file_descriptors() else { + self.set_return(Errno::TooManyOpenFiles.return_value()); + return; + }; + let first_pipe = self.next_pipe_id; + let Some(second_pipe) = first_pipe.checked_add(1) else { + self.set_return(Errno::TooManyOpenFiles.return_value()); + return; + }; + let Some(next_pipe_id) = second_pipe.checked_add(1) else { + self.set_return(Errno::TooManyOpenFiles.return_value()); + return; + }; + let mut descriptor_bytes = [0; 8]; + descriptor_bytes[..4].copy_from_slice(&first.to_le_bytes()); + descriptor_bytes[4..].copy_from_slice(&second.to_le_bytes()); + if self.memory.write(sockets, &descriptor_bytes).is_err() { + self.set_return(Errno::Fault.return_value()); + return; + } + self.next_pipe_id = next_pipe_id; + for pipe_id in [first_pipe, second_pipe] { + self.pipes.insert( + pipe_id, + AnonymousPipe { + bytes: VecDeque::new(), + reader_open: true, + writer_open: true, + }, + ); + } + self.pipe_descriptors.insert( + first, + PipeEnd::Socket { + read: second_pipe, + write: first_pipe, + }, + ); + self.pipe_descriptors.insert( + second, + PipeEnd::Socket { + read: first_pipe, + write: second_pipe, + }, + ); + let descriptor_flags = if flags & OPEN_CLOEXEC == 0 { + 0 + } else { + DESCRIPTOR_CLOEXEC + }; + self.descriptor_flags.insert(first, descriptor_flags); + self.descriptor_flags.insert(second, descriptor_flags); + let status_flags = flags & OPEN_NONBLOCK; + self.descriptor_status_flags.insert(first, status_flags | 2); + self.descriptor_status_flags + .insert(second, status_flags | 2); + self.set_return(0); + } + + fn dispatch_recvfrom(&mut self) { + let Ok(descriptor) = u32::try_from(self.register(0)) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + let flags = self.register(3); + if flags & !(MESSAGE_PEEK | MESSAGE_DONTWAIT) != 0 + || self.register(4) != 0 + || self.register(5) != 0 + { + self.set_return(Errno::InvalidArgument.return_value()); + return; + } + let Some(PipeEnd::Socket { read, .. }) = self.pipe_descriptors.get(&descriptor).copied() + else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + self.dispatch_pipe_read_with_options(read, flags & MESSAGE_PEEK != 0); + } + + fn dispatch_ppoll(&mut self) { + const POLL_DESCRIPTOR_SIZE: u64 = 8; + + let descriptors = GuestAddress::new(self.register(0)); + let count = self.register(1); + if count > MAX_POLL_DESCRIPTORS { + self.set_return(Errno::InvalidArgument.return_value()); + return; + } + let mut ready = 0_u64; + for index in 0..count { + let Some(address) = index + .checked_mul(POLL_DESCRIPTOR_SIZE) + .and_then(|offset| descriptors.checked_add(offset)) + else { + self.set_return(Errno::Fault.return_value()); + return; + }; + let mut bytes = [0_u8; 8]; + if self.memory.read_exact(address, &mut bytes).is_err() { + self.set_return(Errno::Fault.return_value()); + return; + } + let descriptor = i32::from_le_bytes(bytes[..4].try_into().expect("poll fd field")); + let requested = u16::from_le_bytes(bytes[4..6].try_into().expect("poll events field")); + let returned = self.poll_descriptor(descriptor, requested); + if returned != 0 { + ready += 1; + } + if self + .memory + .write( + address.checked_add(6).expect("poll revents address"), + &returned.to_le_bytes(), + ) + .is_err() + { + self.set_return(Errno::Fault.return_value()); + return; + } + } + self.set_return(ready); + } + + fn poll_descriptor(&self, descriptor: i32, requested: u16) -> u16 { + let Ok(descriptor) = u32::try_from(descriptor) else { + return 0; + }; + match self.pipe_descriptors.get(&descriptor).copied() { + Some(PipeEnd::Read(pipe_id)) => self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { + let mut returned = 0; + if requested & POLL_INPUT != 0 && !pipe.bytes.is_empty() { + returned |= POLL_INPUT; + } + if !pipe.writer_open { + returned |= POLL_HANGUP; + } + returned + }), + Some(PipeEnd::Write(pipe_id)) => { + self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { + if !pipe.reader_open { + POLL_ERROR + } else if requested & POLL_OUTPUT != 0 && pipe.bytes.len() < PIPE_CAPACITY_BYTES + { + POLL_OUTPUT + } else { + 0 + } + }) + } + Some(PipeEnd::Socket { read, write }) => { + self.poll_pipe_read(read, requested) | self.poll_pipe_write(write, requested) + } + None if self.file_descriptors.contains_key(&descriptor) => requested, + None if descriptor <= 2 => requested, + None => POLL_INVALID, + } + } + + fn poll_pipe_read(&self, pipe_id: u64, requested: u16) -> u16 { + self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { + let mut returned = 0; + if requested & POLL_INPUT != 0 && !pipe.bytes.is_empty() { + returned |= POLL_INPUT; + } + if !pipe.writer_open { + returned |= POLL_HANGUP; + } + returned + }) + } + + fn poll_pipe_write(&self, pipe_id: u64, requested: u16) -> u16 { + self.pipes.get(&pipe_id).map_or(POLL_INVALID, |pipe| { + if !pipe.reader_open { + POLL_ERROR + } else if requested & POLL_OUTPUT != 0 && pipe.bytes.len() < PIPE_CAPACITY_BYTES { + POLL_OUTPUT + } else { + 0 + } + }) + } + fn dispatch_fcntl(&mut self) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; - let standard_descriptor = file_descriptor <= 2; + let standard_descriptor = file_descriptor <= 2 && !self.descriptor_exists(file_descriptor); if !standard_descriptor && !self.descriptor_exists(file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } match self.register(1) { + command @ (FCNTL_DUPLICATE | FCNTL_DUPLICATE_CLOEXEC) if !standard_descriptor => { + let Ok(minimum) = u32::try_from(self.register(2)) else { + self.set_return(Errno::InvalidArgument.return_value()); + return; + }; + let Some(destination) = (minimum.max(FIRST_FILE_DESCRIPTOR) + ..self.limits.max_open_files) + .find(|descriptor| !self.descriptor_exists(*descriptor)) + else { + self.set_return(Errno::TooManyOpenFiles.return_value()); + return; + }; + self.duplicate_descriptor( + file_descriptor, + destination, + command == FCNTL_DUPLICATE_CLOEXEC, + ); + self.set_return(u64::from(destination)); + } FCNTL_GET_DESCRIPTOR_FLAGS => { self.set_return(*self.descriptor_flags.get(&file_descriptor).unwrap_or(&0)); } @@ -2364,13 +2906,35 @@ impl Process { } } - fn dispatch_ioctl(&mut self) { + fn dispatch_flock(&mut self) { let Ok(file_descriptor) = u32::try_from(self.register(0)) else { self.set_return(Errno::BadFileDescriptor.return_value()); return; }; - let standard_descriptor = file_descriptor <= 2; - if !standard_descriptor && !self.descriptor_exists(file_descriptor) { + let operation = self.register(1); + let lock = operation & !FILE_LOCK_NONBLOCKING; + if !self.file_descriptors.contains_key(&file_descriptor) { + self.set_return(Errno::BadFileDescriptor.return_value()); + } else if !matches!( + lock, + FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE | FILE_LOCK_UNLOCK + ) || operation + & !(FILE_LOCK_SHARED | FILE_LOCK_EXCLUSIVE | FILE_LOCK_NONBLOCKING | FILE_LOCK_UNLOCK) + != 0 + { + self.set_return(Errno::InvalidArgument.return_value()); + } else { + self.set_return(0); + } + } + + fn dispatch_ioctl(&mut self) { + let Ok(file_descriptor) = u32::try_from(self.register(0)) else { + self.set_return(Errno::BadFileDescriptor.return_value()); + return; + }; + let standard_descriptor = file_descriptor <= 2 && !self.descriptor_exists(file_descriptor); + if !standard_descriptor && !self.descriptor_exists(file_descriptor) { self.set_return(Errno::BadFileDescriptor.return_value()); return; } @@ -2384,6 +2948,32 @@ impl Process { self.descriptor_flags.insert(file_descriptor, 0); self.set_return(0); } + IOCTL_SET_NONBLOCK if self.pipe_descriptors.contains_key(&file_descriptor) => { + let mut bytes = [0_u8; 4]; + if self + .memory + .read_exact(GuestAddress::new(self.register(2)), &mut bytes) + .is_err() + { + self.set_return(Errno::Fault.return_value()); + return; + } + let enabled = u32::from_le_bytes(bytes) != 0; + let status = self + .descriptor_status_flags + .get(&file_descriptor) + .copied() + .unwrap_or(0); + self.descriptor_status_flags.insert( + file_descriptor, + if enabled { + status | OPEN_NONBLOCK + } else { + status & !OPEN_NONBLOCK + }, + ); + self.set_return(0); + } _ => self.set_return(Errno::NotTty.return_value()), } } @@ -2503,6 +3093,7 @@ impl Process { match end { PipeEnd::Read(pipe_id) => self.dispatch_pipe_read(pipe_id), PipeEnd::Write(_) => self.set_return(Errno::BadFileDescriptor.return_value()), + PipeEnd::Socket { read, .. } => self.dispatch_pipe_read(read), } return; } @@ -2549,6 +3140,10 @@ impl Process { } fn dispatch_pipe_read(&mut self, pipe_id: u64) { + self.dispatch_pipe_read_with_options(pipe_id, false); + } + + fn dispatch_pipe_read_with_options(&mut self, pipe_id: u64, peek: bool) { let count = self.register(2); let Ok(host_count) = usize::try_from(count) else { self.set_return(Errno::InvalidArgument.return_value()); @@ -2584,11 +3179,13 @@ impl Process { self.set_return(Errno::Fault.return_value()); return; } - self.pipes - .get_mut(&pipe_id) - .expect("validated pipe remains present") - .bytes - .drain(..read); + if !peek { + self.pipes + .get_mut(&pipe_id) + .expect("validated pipe remains present") + .bytes + .drain(..read); + } self.set_return(read as u64); } @@ -2754,6 +3351,7 @@ impl Process { match end { PipeEnd::Read(_) => self.set_return(Errno::BadFileDescriptor.return_value()), PipeEnd::Write(pipe_id) => self.dispatch_pipe_write(pipe_id, bytes), + PipeEnd::Socket { write, .. } => self.dispatch_pipe_write(write, bytes), } return; } @@ -2887,7 +3485,7 @@ impl Process { } else if let Some(end) = self.pipe_descriptors.remove(&file_descriptor) { let pipe_id = match end { PipeEnd::Read(pipe_id) => { - if !self.pipe_end_is_referenced(end) + if !self.pipe_reader_is_referenced(pipe_id) && let Some(pipe) = self.pipes.get_mut(&pipe_id) { pipe.reader_open = false; @@ -2895,13 +3493,33 @@ impl Process { pipe_id } PipeEnd::Write(pipe_id) => { - if !self.pipe_end_is_referenced(end) + if !self.pipe_writer_is_referenced(pipe_id) && let Some(pipe) = self.pipes.get_mut(&pipe_id) { pipe.writer_open = false; } pipe_id } + PipeEnd::Socket { read, write } => { + if !self.pipe_reader_is_referenced(read) + && let Some(pipe) = self.pipes.get_mut(&read) + { + pipe.reader_open = false; + } + if !self.pipe_writer_is_referenced(write) + && let Some(pipe) = self.pipes.get_mut(&write) + { + pipe.writer_open = false; + } + if self + .pipes + .get(&read) + .is_some_and(|pipe| !pipe.reader_open && !pipe.writer_open) + { + self.pipes.remove(&read); + } + write + } }; if self .pipes @@ -2922,7 +3540,7 @@ impl Process { self.file_descriptors .iter() .any(|(candidate, value)| *candidate != descriptor && *value == handle) - || self.suspended_parent.as_ref().is_some_and(|parent| { + || self.suspended_parents.iter().any(|parent| { parent .file_descriptors .values() @@ -2930,16 +3548,26 @@ impl Process { }) } - fn pipe_end_is_referenced(&self, end: PipeEnd) -> bool { - self.pipe_descriptors - .values() - .any(|candidate| *candidate == end) - || self.suspended_parent.as_ref().is_some_and(|parent| { - parent - .pipe_descriptors - .values() - .any(|candidate| *candidate == end) - }) + fn pipe_reader_is_referenced(&self, pipe_id: u64) -> bool { + self.pipe_end_is_referenced(|end| { + matches!(end, PipeEnd::Read(candidate) if candidate == pipe_id) + || matches!(end, PipeEnd::Socket { read, .. } if read == pipe_id) + }) + } + + fn pipe_writer_is_referenced(&self, pipe_id: u64) -> bool { + self.pipe_end_is_referenced(|end| { + matches!(end, PipeEnd::Write(candidate) if candidate == pipe_id) + || matches!(end, PipeEnd::Socket { write, .. } if write == pipe_id) + }) + } + + fn pipe_end_is_referenced(&self, predicate: impl Fn(PipeEnd) -> bool) -> bool { + self.pipe_descriptors.values().copied().any(&predicate) + || self + .suspended_parents + .iter() + .any(|parent| parent.pipe_descriptors.values().copied().any(&predicate)) } fn close_all_descriptors(&mut self, filesystem: &mut F) { @@ -3447,6 +4075,57 @@ fn linux_pipe_stat_bytes(pipe_id: u64) -> [u8; STAT_SIZE] { bytes } +fn copy_file_for_link( + filesystem: &mut F, + old_path: &[u8], + new_path: &[u8], +) -> Result<(), FileSystemError> { + let source = filesystem.open( + old_path, + FileOpenOptions { + access: FileAccess::ReadOnly, + flags: FileOpenFlags::NONE, + }, + )?; + let destination = match filesystem.open( + new_path, + FileOpenOptions { + access: FileAccess::WriteOnly, + flags: FileOpenFlags::CREATE.union(FileOpenFlags::EXCLUSIVE), + }, + ) { + Ok(destination) => destination, + Err(error) => { + let _ = filesystem.close(source); + return Err(error); + } + }; + let result = (|| { + let mut bytes = vec![0; FILE_COPY_CHUNK_BYTES]; + loop { + let read = filesystem.read(source, &mut bytes)?; + if read == 0 { + return Ok(()); + } + let mut written = 0; + while written < read { + let count = filesystem.write(destination, &bytes[written..read])?; + if count == 0 { + return Err(FileSystemError::NoSpace); + } + written += count; + } + } + })(); + let destination_close = filesystem.close(destination); + let source_close = filesystem.close(source); + let result = result.and(destination_close).and(source_close); + if result.is_err() { + let _ = filesystem.remove(new_path, false); + } + result +} + fn read_mmap_file( filesystem: &mut F, handle: u64, @@ -3528,13 +4207,14 @@ mod tests { use super::{ ACTIVE_THREAD_ID, AT_FDCWD, AnonymousPipe, CHILD_PROCESS_ID, DESCRIPTOR_CLOEXEC, DORMANT_THREAD_CLONE_FLAGS, DORMANT_THREAD_ID, ExecutionError, ExecutionEvent, - FCNTL_GET_DESCRIPTOR_FLAGS, FCNTL_GET_STATUS_FLAGS, FCNTL_SET_STATUS_FLAGS, FUTEX_WAIT, - INITIAL_PROCESS_ID, IOCTL_CLEAR_CLOSE_ON_EXEC, IOCTL_SET_CLOSE_ON_EXEC, Interpreter, - MAP_FIXED, MAP_PRIVATE, NESTED_HELPER_THREAD_ID, OPEN_CLOEXEC, OPEN_DIRECTORY, OPEN_NOCTTY, - OPEN_NOFOLLOW, OPEN_NONBLOCK, OPEN_PATH, PAGE_SIZE, PIPE_CAPACITY_BYTES, PROT_EXECUTE, - PROT_READ, PipeEnd, Process, STANDARD_OUTPUT, STAT_CHARACTER_MODE, STAT_FIFO_MODE, - STAT_FILE_SIZE_OFFSET, STAT_MODE_OFFSET, STAT_REGULAR_MODE, STAT_SIZE, - SUPPORTED_CLONE_FLAGS, SyscallEvent, SyscallOutcome, load_process_with_guest_filesystem, + FCNTL_GET_DESCRIPTOR_FLAGS, FCNTL_GET_STATUS_FLAGS, FCNTL_SET_STATUS_FLAGS, + FUTEX_PRIVATE_FLAG, FUTEX_WAIT, INITIAL_PROCESS_ID, IOCTL_CLEAR_CLOSE_ON_EXEC, + IOCTL_SET_CLOSE_ON_EXEC, Interpreter, MAP_FIXED, MAP_PRIVATE, NESTED_HELPER_THREAD_ID, + OPEN_CLOEXEC, OPEN_DIRECTORY, OPEN_LARGEFILE, OPEN_NOCTTY, OPEN_NOFOLLOW, OPEN_NONBLOCK, + OPEN_PATH, PAGE_SIZE, PIPE_CAPACITY_BYTES, PROT_EXECUTE, PROT_READ, PipeEnd, Process, + SIGNAL_USER_1, STANDARD_OUTPUT, STAT_CHARACTER_MODE, STAT_FIFO_MODE, STAT_FILE_SIZE_OFFSET, + STAT_MODE_OFFSET, STAT_REGULAR_MODE, STAT_SIZE, SUPPORTED_CLONE_FLAGS, + SUPPORTED_FORK_FLAGS, SyscallEvent, SyscallOutcome, load_process_with_guest_filesystem, }; const MESSAGE: &[u8] = b"hello, world\n"; @@ -4299,6 +4979,22 @@ mod tests { assert_eq!(process.register(0), Errno::Fault.return_value()); } + #[test] + fn chdir_updates_the_guest_working_directory() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let mut filesystem = MemoryFileSystem::new(1024); + filesystem.create_directory(b"/project/answer").unwrap(); + let path = process.state.sp().checked_sub(32).unwrap(); + process.memory.write(path, b"answer\0").unwrap(); + process.state.set_x(0, path.get()).unwrap(); + + process.dispatch_chdir(&mut filesystem); + + assert_eq!(process.register(0), 0); + assert_eq!(process.current_directory, b"/project/answer"); + } + #[test] fn file_status_and_executable_link_expose_guest_paths() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); @@ -4410,7 +5106,10 @@ mod tests { process.state.set_x(1, path_address.get()).unwrap(); process .state - .set_x(2, OPEN_PATH | OPEN_DIRECTORY | OPEN_NOFOLLOW | OPEN_CLOEXEC) + .set_x( + 2, + OPEN_PATH | OPEN_DIRECTORY | OPEN_LARGEFILE | OPEN_NOFOLLOW | OPEN_CLOEXEC, + ) .unwrap(); process.dispatch_openat(&mut filesystem); @@ -4488,6 +5187,49 @@ mod tests { assert!(filesystem.metadata(b"/project/.zig-cache").is_ok()); } + #[test] + fn linkat_copies_an_immutable_cache_file() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let mut filesystem = MemoryFileSystem::new(1024); + let source = filesystem + .open( + b"/project/source.bin", + FileOpenOptions { + access: FileAccess::WriteOnly, + flags: FileOpenFlags::CREATE, + }, + ) + .unwrap(); + filesystem.write(source, b"incremental cache").unwrap(); + filesystem.close(source).unwrap(); + let old_path = process.state.sp().checked_sub(128).unwrap(); + let new_path = process.state.sp().checked_sub(64).unwrap(); + process + .memory + .write(old_path, b"/project/source.bin\0") + .unwrap(); + process + .memory + .write(new_path, b"/project/destination.bin\0") + .unwrap(); + process.state.set_x(0, AT_FDCWD).unwrap(); + process.state.set_x(1, old_path.get()).unwrap(); + process.state.set_x(2, AT_FDCWD).unwrap(); + process.state.set_x(3, new_path.get()).unwrap(); + process.state.set_x(4, 0).unwrap(); + + process.dispatch_linkat(&mut filesystem); + + assert_eq!(process.register(0), 0); + assert_eq!( + filesystem.read_file(b"/project/destination.bin"), + Some(&b"incremental cache"[..]) + ); + process.dispatch_linkat(&mut filesystem); + assert_eq!(process.register(0), Errno::AlreadyExists.return_value()); + } + #[test] fn faccessat_reports_bounded_guest_path_access() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); @@ -4576,6 +5318,14 @@ mod tests { process.descriptor_flags.get(&reader), Some(&DESCRIPTOR_CLOEXEC) ); + assert_eq!( + process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), + 0 + ); + assert_eq!( + process.poll_descriptor(writer.cast_signed(), super::POLL_OUTPUT), + super::POLL_OUTPUT + ); let stat = process.state.sp().checked_sub(256).unwrap(); process.state.set_x(0, u64::from(reader)).unwrap(); @@ -4603,6 +5353,10 @@ mod tests { process.state.set_x(2, 9).unwrap(); process.dispatch_file_write(&mut filesystem); assert_eq!(process.register(0), 9); + assert_eq!( + process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), + super::POLL_INPUT + ); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); @@ -4620,6 +5374,10 @@ mod tests { process.state.set_x(0, u64::from(writer)).unwrap(); process.dispatch_close(&mut filesystem); assert_eq!(process.register(0), 0); + assert_eq!( + process.poll_descriptor(reader.cast_signed(), super::POLL_INPUT), + super::POLL_HANGUP + ); process.state.set_x(0, u64::from(reader)).unwrap(); process.state.set_x(1, destination.get()).unwrap(); process.state.set_x(2, 16).unwrap(); @@ -4640,6 +5398,144 @@ mod tests { assert!(process.pipes.is_empty()); } + #[test] + fn dup3_routes_a_standard_stream_through_a_shared_pipe() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let pipe_descriptors = process.state.sp().checked_sub(32).unwrap(); + let destination = process.state.sp().checked_sub(64).unwrap(); + process.state.set_x(0, pipe_descriptors.get()).unwrap(); + process.state.set_x(1, OPEN_CLOEXEC).unwrap(); + process.dispatch_pipe2(); + + process.state.set_x(0, 4).unwrap(); + process.state.set_x(1, STANDARD_OUTPUT).unwrap(); + process.state.set_x(2, 0).unwrap(); + process.dispatch_dup3(&mut NullFileSystem); + + assert_eq!(process.register(0), STANDARD_OUTPUT); + assert_eq!( + process.pipe_descriptors.get(&1), + process.pipe_descriptors.get(&4) + ); + assert_eq!(process.descriptor_flags.get(&1), Some(&0)); + + process + .memory + .write(destination, &1_u32.to_le_bytes()) + .unwrap(); + process.state.set_x(0, STANDARD_OUTPUT).unwrap(); + process.state.set_x(1, super::IOCTL_SET_NONBLOCK).unwrap(); + process.state.set_x(2, destination.get()).unwrap(); + process.dispatch_ioctl(); + assert_eq!(process.register(0), 0); + assert_eq!( + process.descriptor_status_flags.get(&1).copied().unwrap() & OPEN_NONBLOCK, + OPEN_NONBLOCK + ); + + let mut terminal = RecordingTerminal::default(); + process.state.set_x(0, STANDARD_OUTPUT).unwrap(); + process.state.set_x(1, u64::from(MESSAGE_ADDRESS)).unwrap(); + process + .state + .set_x(2, MESSAGE.len().try_into().unwrap()) + .unwrap(); + process + .dispatch_write(&mut terminal, &mut NullFileSystem) + .unwrap(); + assert_eq!(process.register(0), MESSAGE.len() as u64); + assert!(terminal.standard_output.is_empty()); + + process.state.set_x(0, 3).unwrap(); + process.state.set_x(1, destination.get()).unwrap(); + process + .state + .set_x(2, MESSAGE.len().try_into().unwrap()) + .unwrap(); + process.dispatch_read(&mut NullFileSystem); + let mut bytes = [0_u8; MESSAGE.len()]; + process.memory.read_exact(destination, &mut bytes).unwrap(); + assert_eq!(&bytes, MESSAGE); + } + + #[test] + fn unix_socketpair_exchanges_child_setup_bytes_in_both_directions() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let sockets = process.state.sp().checked_sub(32).unwrap(); + let source = process.state.sp().checked_sub(64).unwrap(); + let destination = process.state.sp().checked_sub(96).unwrap(); + process.memory.write(source, b"errno").unwrap(); + process.state.set_x(0, super::ADDRESS_FAMILY_UNIX).unwrap(); + process + .state + .set_x(1, super::SOCKET_SEQPACKET | OPEN_CLOEXEC) + .unwrap(); + process.state.set_x(2, 0).unwrap(); + process.state.set_x(3, sockets.get()).unwrap(); + + process.dispatch_socketpair(); + + assert_eq!(process.register(0), 0); + let mut descriptor_bytes = [0; 8]; + process + .memory + .read_exact(sockets, &mut descriptor_bytes) + .unwrap(); + let first = u32::from_le_bytes(descriptor_bytes[..4].try_into().unwrap()); + let second = u32::from_le_bytes(descriptor_bytes[4..].try_into().unwrap()); + assert_eq!( + process.descriptor_flags.get(&first), + Some(&DESCRIPTOR_CLOEXEC) + ); + assert_eq!( + process.descriptor_flags.get(&second), + Some(&DESCRIPTOR_CLOEXEC) + ); + + process.state.set_x(0, u64::from(first)).unwrap(); + process.state.set_x(1, source.get()).unwrap(); + process.state.set_x(2, 5).unwrap(); + process.dispatch_file_write(&mut NullFileSystem); + assert_eq!(process.register(0), 5); + process.state.set_x(0, u64::from(second)).unwrap(); + process.state.set_x(1, destination.get()).unwrap(); + process.state.set_x(2, 5).unwrap(); + process.state.set_x(3, super::MESSAGE_PEEK).unwrap(); + process.state.set_x(4, 0).unwrap(); + process.state.set_x(5, 0).unwrap(); + process.dispatch_recvfrom(); + assert_eq!(process.register(0), 5); + let mut bytes = [0; 5]; + process.memory.read_exact(destination, &mut bytes).unwrap(); + assert_eq!(&bytes, b"errno"); + + process.state.set_x(0, u64::from(second)).unwrap(); + process.state.set_x(1, destination.get()).unwrap(); + process.state.set_x(2, 5).unwrap(); + process.dispatch_read(&mut NullFileSystem); + assert_eq!(process.register(0), 5); + process.memory.read_exact(destination, &mut bytes).unwrap(); + assert_eq!(&bytes, b"errno"); + + process.memory.write(source, b"ok").unwrap(); + process.state.set_x(0, u64::from(second)).unwrap(); + process.state.set_x(1, source.get()).unwrap(); + process.state.set_x(2, 2).unwrap(); + process.dispatch_file_write(&mut NullFileSystem); + process.state.set_x(0, u64::from(first)).unwrap(); + process.state.set_x(1, destination.get()).unwrap(); + process.state.set_x(2, 2).unwrap(); + process.dispatch_read(&mut NullFileSystem); + assert_eq!(process.register(0), 2); + process + .memory + .read_exact(destination, &mut bytes[..2]) + .unwrap(); + assert_eq!(&bytes[..2], b"ok"); + } + #[test] fn nonblocking_pipe_reports_full_and_broken_writer_boundaries() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); @@ -4700,6 +5596,50 @@ mod tests { process.state.set_x(1, FCNTL_GET_STATUS_FLAGS).unwrap(); process.dispatch_fcntl(); assert_eq!(process.register(0), 1); + + process.state.set_x(0, u64::from(writer)).unwrap(); + process + .state + .set_x(1, super::FCNTL_DUPLICATE_CLOEXEC) + .unwrap(); + process.state.set_x(2, 10).unwrap(); + process.dispatch_fcntl(); + assert_eq!(process.register(0), 10); + assert_eq!( + process.pipe_descriptors.get(&10), + process.pipe_descriptors.get(&writer) + ); + assert_eq!(process.descriptor_flags.get(&10), Some(&DESCRIPTOR_CLOEXEC)); + } + + #[test] + fn flock_accepts_advisory_incremental_cache_locks() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let mut filesystem = MemoryFileSystem::new(1024); + let handle = filesystem + .open( + b"/project/incremental.lock", + FileOpenOptions { + access: FileAccess::ReadWrite, + flags: FileOpenFlags::CREATE, + }, + ) + .unwrap(); + process.file_descriptors.insert(3, handle); + process.state.set_x(0, 3).unwrap(); + process + .state + .set_x(1, super::FILE_LOCK_EXCLUSIVE | super::FILE_LOCK_NONBLOCKING) + .unwrap(); + + process.dispatch_flock(); + + assert_eq!(process.register(0), 0); + process.state.set_x(0, 3).unwrap(); + process.state.set_x(1, super::FILE_LOCK_UNLOCK).unwrap(); + process.dispatch_flock(); + assert_eq!(process.register(0), 0); } #[test] @@ -4801,6 +5741,192 @@ mod tests { assert_eq!(process.register(0), 0); } + #[test] + fn constrained_fork_shape_suspends_the_parent_for_exec() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + process.suspended_thread = Some(super::SuspendedThread { + state: process.state.clone(), + interpreter: Interpreter::new().unwrap(), + clear_child_tid: None, + current_thread_id: DORMANT_THREAD_ID, + peer: None, + }); + process.state.set_x(0, SUPPORTED_FORK_FLAGS).unwrap(); + for register in 1..=4 { + process.state.set_x(register, 0).unwrap(); + } + + process.dispatch_clone(); + + assert_eq!(process.register(0), 0); + assert_eq!(process.current_process_id, CHILD_PROCESS_ID); + assert!(process.suspended_thread.is_none()); + assert_eq!(process.suspended_parents.len(), 1); + assert!( + process + .suspended_parents + .last() + .is_some_and(|parent| parent.suspended_thread.is_some()) + ); + } + + #[test] + fn nested_processes_unwind_to_their_immediate_parents() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let mut filesystem = MemoryFileSystem::new(1024); + + for expected_process_id in [2, 3] { + process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); + for register in 1..=4 { + process.state.set_x(register, 0).unwrap(); + } + process.dispatch_clone(); + assert_eq!(process.register(0), 0); + assert_eq!(process.current_process_id, expected_process_id); + assert_eq!(process.parent_process_id, expected_process_id - 1); + } + assert_eq!(process.suspended_parents.len(), 2); + + process.finish_child(&mut filesystem, 0); + assert_eq!(process.current_process_id, 2); + assert_eq!(process.parent_process_id, 1); + assert_eq!(process.register(0), 3); + assert_eq!(process.suspended_parents.len(), 1); + + process.state.set_x(0, u64::MAX).unwrap(); + process.state.set_x(1, 0).unwrap(); + process.state.set_x(2, 0).unwrap(); + process.state.set_x(3, 0).unwrap(); + process.dispatch_wait4(); + assert_eq!(process.register(0), 3); + + process.finish_child(&mut filesystem, 7); + assert_eq!(process.current_process_id, INITIAL_PROCESS_ID); + assert_eq!(process.parent_process_id, 0); + assert_eq!(process.register(0), CHILD_PROCESS_ID); + assert!(process.suspended_parents.is_empty()); + } + + #[test] + fn spawned_compiler_can_park_its_signal_waiter_thread() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + process.state.set_x(0, SUPPORTED_CLONE_FLAGS).unwrap(); + for register in 1..=4 { + process.state.set_x(register, 0).unwrap(); + } + process.dispatch_clone(); + assert_eq!(process.current_process_id, CHILD_PROCESS_ID); + + let parent_tid = process.state.sp().checked_sub(32).unwrap(); + let clear_child_tid = process.state.sp().checked_sub(28).unwrap(); + process.state.set_x(0, DORMANT_THREAD_CLONE_FLAGS).unwrap(); + process + .state + .set_x(1, process.state.sp().checked_sub(4096).unwrap().get()) + .unwrap(); + process.state.set_x(2, parent_tid.get()).unwrap(); + process.state.set_x(3, 0x1234).unwrap(); + process.state.set_x(4, clear_child_tid.get()).unwrap(); + process.dispatch_clone(); + assert_eq!(process.register(0), 0); + assert_eq!(process.current_thread_id, DORMANT_THREAD_ID); + assert_eq!(process.suspended_parents.len(), 1); + + process.state.set_x(0, clear_child_tid.get()).unwrap(); + process.state.set_x(1, FUTEX_WAIT).unwrap(); + process.state.set_x(2, 0).unwrap(); + process.dispatch_futex(); + assert_eq!(process.current_thread_id, CHILD_PROCESS_ID); + assert!(process.dormant_thread.is_some()); + assert_eq!(process.suspended_parents.len(), 1); + } + + #[test] + fn tkill_stops_the_dormant_signal_helper() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let clear_child_tid = process.state.sp().checked_sub(4).unwrap(); + process + .memory + .write(clear_child_tid, &DORMANT_THREAD_ID.to_le_bytes()[..4]) + .unwrap(); + process.dormant_thread = Some(super::DormantThread { clear_child_tid }); + process.state.set_x(0, DORMANT_THREAD_ID).unwrap(); + process.state.set_x(1, SIGNAL_USER_1).unwrap(); + + assert!(!process.dispatch_tkill()); + + assert_eq!(process.register(0), 0); + assert!(process.dormant_thread.is_none()); + let mut tid = [0; 4]; + process + .memory + .read_exact(clear_child_tid, &mut tid) + .unwrap(); + assert_eq!(u32::from_le_bytes(tid), 0); + + process.state.set_x(0, DORMANT_THREAD_ID).unwrap(); + process.state.set_x(1, SIGNAL_USER_1).unwrap(); + assert!(!process.dispatch_tkill()); + assert_eq!(process.register(0), 0); + process + .memory + .write(clear_child_tid, &2_u32.to_le_bytes()) + .unwrap(); + process.state.set_x(0, clear_child_tid.get()).unwrap(); + process.state.set_x(1, FUTEX_WAIT).unwrap(); + process.state.set_x(2, 2).unwrap(); + + process.dispatch_futex(); + + process + .memory + .read_exact(clear_child_tid, &mut tid) + .unwrap(); + assert_eq!(u32::from_le_bytes(tid), 3); + assert!(!process.pending_signal_sync); + } + + #[test] + fn private_futex_releases_only_when_no_runnable_peer_remains() { + let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); + let mut process = Process::new(image).unwrap(); + let clear_child_tid = process.state.sp().checked_sub(8).unwrap(); + let address = process.state.sp().checked_sub(4).unwrap(); + process.dormant_thread = Some(super::DormantThread { clear_child_tid }); + process.memory.write(address, &2_u32.to_le_bytes()).unwrap(); + process.state.set_x(0, address.get()).unwrap(); + process + .state + .set_x(1, FUTEX_PRIVATE_FLAG | FUTEX_WAIT) + .unwrap(); + process.state.set_x(2, 2).unwrap(); + + process.dispatch_futex(); + + let mut value = [0; 4]; + process.memory.read_exact(address, &mut value).unwrap(); + assert_eq!(u32::from_le_bytes(value), 3); + + process.memory.write(address, &2_u32.to_le_bytes()).unwrap(); + process.state.set_x(0, address.get()).unwrap(); + process.suspended_thread = Some(super::SuspendedThread { + state: process.state.clone(), + interpreter: Interpreter::new().unwrap(), + clear_child_tid: None, + current_thread_id: ACTIVE_THREAD_ID, + peer: None, + }); + process.dispatch_futex(); + + process.memory.read_exact(address, &mut value).unwrap(); + assert_eq!(u32::from_le_bytes(value), 2); + assert_eq!(process.current_thread_id, ACTIVE_THREAD_ID); + } + #[test] fn serializes_one_active_thread_beside_the_dormant_signal_helper() { let image = load_hello(ProcessConfig::default(), 1, MESSAGE_ADDRESS); diff --git a/crates/memory-fs/src/lib.rs b/crates/memory-fs/src/lib.rs index e770943..2ee278a 100644 --- a/crates/memory-fs/src/lib.rs +++ b/crates/memory-fs/src/lib.rs @@ -810,10 +810,14 @@ impl HostFileSystem for MemoryFileSystem { if !self.directories.contains(new_parent) { return Err(self.missing_directory_error(new_parent)); } - if self.files.contains_key(&new_path) || self.directories.contains(&new_path) { - return Err(FileSystemError::AlreadyExists); - } if let Some(bytes) = self.files.remove(&old_path) { + if self.directories.contains(&new_path) { + self.files.insert(old_path, bytes); + return Err(FileSystemError::IsDirectory); + } + if let Some(replaced) = self.files.remove(&new_path) { + self.stored_bytes -= replaced.len() as u64; + } self.files.insert(new_path.clone(), bytes); self.replace_open_paths(&old_path, &new_path); return Ok(()); @@ -821,9 +825,26 @@ impl HostFileSystem for MemoryFileSystem { if !self.directories.contains(&old_path) { return Err(FileSystemError::NotFound); } + if self.files.contains_key(&new_path) { + return Err(FileSystemError::NotDirectory); + } if is_descendant(&new_path, &old_path) { return Err(FileSystemError::InvalidInput); } + if self.directories.contains(&new_path) { + let destination_is_empty = !self + .directories + .iter() + .any(|candidate| is_descendant(candidate, &new_path)) + && !self + .files + .keys() + .any(|candidate| is_descendant(candidate, &new_path)); + if !destination_is_empty { + return Err(FileSystemError::NotEmpty); + } + self.directories.remove(&new_path); + } let moved_directories = self .directories .iter() @@ -1292,6 +1313,45 @@ mod tests { assert_eq!(restored.export_snapshot().unwrap(), snapshot); } + #[test] + fn rename_replaces_existing_files_and_empty_directories() { + let mut filesystem = MemoryFileSystem::new(8); + for (path, contents) in [ + (&b"/project/source"[..], &b"new"[..]), + (&b"/project/destination"[..], &b"old!"[..]), + ] { + let handle = filesystem.open(path, CREATE_READ_WRITE).unwrap(); + filesystem.write(handle, contents).unwrap(); + filesystem.close(handle).unwrap(); + } + + filesystem + .rename(b"/project/source", b"/project/destination") + .unwrap(); + assert_eq!( + filesystem.read_file(b"/project/destination"), + Some(&b"new"[..]) + ); + let remaining = filesystem + .open(b"/project/remaining", CREATE_READ_WRITE) + .unwrap(); + filesystem.write(remaining, b"12345").unwrap(); + + filesystem.create_directory(b"/project/old-dir").unwrap(); + filesystem.create_directory(b"/project/new-dir").unwrap(); + filesystem + .rename(b"/project/old-dir", b"/project/new-dir") + .unwrap(); + assert_eq!( + filesystem.metadata(b"/project/old-dir"), + Err(FileSystemError::NotFound) + ); + assert_eq!( + filesystem.metadata(b"/project/new-dir").unwrap().file_type, + FileType::Directory + ); + } + #[test] fn snapshots_preserve_empty_project_directories() { let mut filesystem = MemoryFileSystem::new(64); diff --git a/third_party/ghidra-aarch64/languages/AARCH64base.sinc b/third_party/ghidra-aarch64/languages/AARCH64base.sinc index 65439a6..f051b94 100644 --- a/third_party/ghidra-aarch64/languages/AARCH64base.sinc +++ b/third_party/ghidra-aarch64/languages/AARCH64base.sinc @@ -3053,6 +3053,7 @@ is b_3031=0b10 & b_2229=0b10100001 & Rt2_GPR64 & addrPairIndexed & Rt_GPR64 :ldp Rt_GPR32, Rt2_GPR32, addrPairIndexed is b_3031=0b00 & b_2529=0b10100 & (b_24=1 | b_23=1) & b_22=1 & Rt2_GPR32 & addrPairIndexed & Rt_GPR32 & Rt_GPR64 & Rt2_GPR64 { + build addrPairIndexed; Rt_GPR64 = zext(*:4 addrPairIndexed); Rt2_GPR64 = zext(*:4 (addrPairIndexed + 4)); } @@ -3068,6 +3069,7 @@ is b_3031=0b00 & b_2529=0b10100 & (b_24=1 | b_23=1) & b_22=1 & Rt2_GPR32 & addrP :ldp Rt_GPR64, Rt2_GPR64, addrPairIndexed is b_3031=0b10 & b_2529=0b10100 & (b_24=1 | b_23=1) & b_22=1 & Rt2_GPR64 & addrPairIndexed & Rt_GPR64 { + build addrPairIndexed; Rt_GPR64 = *addrPairIndexed; Rt2_GPR64 = *(addrPairIndexed + 8); } @@ -3081,6 +3083,7 @@ is b_3031=0b10 & b_2529=0b10100 & (b_24=1 | b_23=1) & b_22=1 & Rt2_GPR64 & addrP :ldpsw Rt_GPR64, Rt2_GPR64, addrPairIndexed is b_2531=0b0110100 & (b_24=1 | b_23=1) & b_22=1 & Rt2_GPR64 & addrPairIndexed & Rt_GPR64 { + build addrPairIndexed; Rt_GPR64 = *(addrPairIndexed); Rt2_GPR64 = *(addrPairIndexed+8); } @@ -3116,6 +3119,7 @@ is size.ldstr=2 & b_2729=7 & v=0 & b_2425=0 & b_23=0 & b_2222=1 & b_2121=0 & Uns :ldr Rt_GPR32, addrIndexed is size.ldstr=2 & b_2729=7 & v=0 & b_2425=0 & b_23=0 & b_2222=1 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR32 & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = zext(*:4 addrIndexed); } @@ -3137,6 +3141,7 @@ is size.ldstr=3 & b_2729=7 & v=0 & b_2425=1 & b_23=0 & b_2222=1 & addrUIMM & Rn_ :ldr Rt_GPR64, addrIndexed is size.ldstr=3 & b_2729=7 & v=0 & b_2425=0 & b_23=0 & b_2222=1 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = *addrIndexed; } @@ -3220,6 +3225,7 @@ is ldrab__PACpart & b_2431=0b11111000 & b_23=1 & b_21=1 & b_10=1 & addrIndexed & :ldrb Rt_GPR32, addrIndexed is b_2131=0b00111000010 & b_10=1 & addrIndexed & Rt_GPR32 & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = zext(*:1 addrIndexed); } @@ -3264,6 +3270,7 @@ is size.ldstr=1 & b_2729=7 & v=0 & b_2425=1 & b_23=0 & b_2222=1 & addrUIMM & Rn_ :ldrh Rt_GPR32, addrIndexed is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_23=0 & b_2222=1 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR32 & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = zext(*:2 addrIndexed); } @@ -3310,6 +3317,7 @@ is size.ldstr=0 & b_2729=7 & v=0 & b_2425=0 & b_2223=3 & b_2121=0 & UnscPriv & a :ldrsb Rt_GPR32, addrIndexed is size.ldstr=0 & b_2729=7 & v=0 & b_2425=0 & b_2223=3 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR32 & Rt_GPR64 { + build addrIndexed; local tmp:4 = sext(*:1 addrIndexed); Rt_GPR64 = zext(tmp); } @@ -3345,6 +3353,7 @@ is size.ldstr=0 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=0 & UnscPriv & a :ldrsb Rt_GPR64, addrIndexed is size.ldstr=0 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = sext(*:1 addrIndexed); } @@ -3402,6 +3411,7 @@ is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_2223=3 & b_2121=0 & UnscPriv & a :ldrsh Rt_GPR32, addrIndexed is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_2223=3 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR32 & Rt_GPR64 { + build addrIndexed; local tmp:4 = sext(*:2 addrIndexed); Rt_GPR64 = zext(tmp); } @@ -3437,6 +3447,7 @@ is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=0 & UnscPriv & a :ldrsh Rt_GPR64, addrIndexed is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = sext(*:2 addrIndexed); } @@ -3469,6 +3480,7 @@ is size.ldstr=1 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=1 & b_1011=2 & a :ldrsw Rt_GPR64, addrIndexed is size.ldstr=2 & b_2729=7 & v=0 & b_2425=0 & b_2223=2 & b_2121=0 & b_1010=1 & addrIndexed & Rt_GPR64 { + build addrIndexed; Rt_GPR64 = sext(*:4 addrIndexed); } diff --git a/third_party/ghidra-aarch64/languages/AARCH64ldst.sinc b/third_party/ghidra-aarch64/languages/AARCH64ldst.sinc index 28e0b75..c836847 100644 --- a/third_party/ghidra-aarch64/languages/AARCH64ldst.sinc +++ b/third_party/ghidra-aarch64/languages/AARCH64ldst.sinc @@ -52,7 +52,7 @@ ldst_imm: tmp is b_30=1 & b_24=0 & b_21=0 & b_15=0 & b_14=0 & b_12=0 ldst_wback: "" is b_23=0 & b_1620=0b00000 { } ldst_wback: ", #"^ldst_imm is b_23=1 & b_1620=0b11111 & Rn_GPR64xsp & ldst_imm { Rn_GPR64xsp = tmp_ldXn; } -ldst_wback: ", "^Rm_GPR64 is b_23=1 & Rn_GPR64xsp & Rm_GPR64 { Rn_GPR64xsp = Rm_GPR64; } +ldst_wback: ", "^Rm_GPR64 is b_23=1 & Rn_GPR64xsp & Rm_GPR64 { Rn_GPR64xsp = Rn_GPR64xsp + Rm_GPR64; } # C7.2.177 LD1 (multiple structures) page C7-2415 line 141110 MATCH x0c402000/mask=xbfff2000 # C7.2.177 LD1 (multiple structures) page C7-2415 line 141110 MATCH x0cc02000/mask=xbfe02000 diff --git a/toolchains/rust-musl/cargo-project/.cargo/config.toml b/toolchains/rust-musl/cargo-project/.cargo/config.toml new file mode 100644 index 0000000..fe22501 --- /dev/null +++ b/toolchains/rust-musl/cargo-project/.cargo/config.toml @@ -0,0 +1,15 @@ +[build] +jobs = 1 + +[target.aarch64-unknown-linux-musl] +linker = "/usr/local/lib/rustlib/aarch64-unknown-linux-musl/bin/gcc-ld/ld.lld" +rustflags = [ + "-Z", + "no-parallel-backend", + "-C", + "link-self-contained=no", + "-C", + "link-arg=--entry=_start", + "-C", + "link-arg=--threads=1", +] diff --git a/toolchains/rust-musl/cargo-project/Cargo.toml b/toolchains/rust-musl/cargo-project/Cargo.toml new file mode 100644 index 0000000..a375c3b --- /dev/null +++ b/toolchains/rust-musl/cargo-project/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "binarrow-cargo-hello" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +binarrow-cargo-answer = { path = "answer" } + +[profile.dev] +codegen-units = 1 +debug = 0 +incremental = true +panic = "abort" + +[profile.release] +codegen-units = 1 +debug = 0 +incremental = true +panic = "abort" diff --git a/toolchains/rust-musl/cargo-project/answer/Cargo.toml b/toolchains/rust-musl/cargo-project/answer/Cargo.toml new file mode 100644 index 0000000..8036f23 --- /dev/null +++ b/toolchains/rust-musl/cargo-project/answer/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "binarrow-cargo-answer" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +path = "src/lib.rs" diff --git a/toolchains/rust-musl/cargo-project/answer/src/lib.rs b/toolchains/rust-musl/cargo-project/answer/src/lib.rs new file mode 100644 index 0000000..593af2c --- /dev/null +++ b/toolchains/rust-musl/cargo-project/answer/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] + +#[inline] +pub const fn value() -> u64 { + 42 +} diff --git a/toolchains/rust-musl/cargo-project/src/main.rs b/toolchains/rust-musl/cargo-project/src/main.rs new file mode 100644 index 0000000..1330f63 --- /dev/null +++ b/toolchains/rust-musl/cargo-project/src/main.rs @@ -0,0 +1,45 @@ +#![no_main] +#![no_std] + +use core::arch::asm; +use core::panic::PanicInfo; + +const MESSAGE: &[u8] = b"guest cargo dependency hello\n"; + +#[panic_handler] +fn panic(_info: &PanicInfo<'_>) -> ! { + exit(101) +} + +#[unsafe(no_mangle)] +extern "C" fn _start() -> ! { + if binarrow_cargo_answer::value() != 42 { + exit(1); + } + write_stdout(MESSAGE); + exit(42) +} + +fn write_stdout(bytes: &[u8]) { + unsafe { + asm!( + "svc 0", + in("x0") 1_u64, + in("x1") bytes.as_ptr(), + in("x2") bytes.len(), + in("x8") 64_u64, + options(nostack), + ); + } +} + +fn exit(status: u64) -> ! { + unsafe { + asm!( + "svc 0", + in("x0") status, + in("x8") 93_u64, + options(noreturn, nostack), + ); + } +}